Metadata-Version: 2.5
Name: ai-sessions
Version: 3.1.2
Summary: A friendly terminal browser for local Codex CLI and Claude Code sessions
Project-URL: Homepage, https://github.com/vandyand/ai-sessions
Project-URL: Repository, https://github.com/vandyand/ai-sessions
Project-URL: Issues, https://github.com/vandyand/ai-sessions/issues
Author-email: Andrew VanDyke <vandyand@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: claude-code,codex,sessions,terminal,tui
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console :: Curses
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development
Requires-Python: >=3.11
Requires-Dist: psutil>=5.9
Requires-Dist: windows-curses>=2.4.1; sys_platform == 'win32'
Description-Content-Type: text/markdown

# ai-sessions

`ai-sessions` is a searchable terminal browser for local [Codex CLI](https://developers.openai.com/codex/cli/) and Claude Code conversations. It indexes each provider's existing on-disk history. Browsing is read-only; an explicit rename appends the provider's supported title record so the name also appears in that provider.

It runs as `sessions` on Linux and native Windows PowerShell.

## Features

- One navigable list for Codex and Claude sessions
- Search and filters for provider, directory, origin, open state, and visibility
- Human, cross-provider, and subagent/automation origin labels
- Started and updated timestamps plus user-message counts across compactions
- Rename that carries through to Claude Code and Codex, plus utility-local hiding
- Nickname and parent labels that tell sibling subagent threads apart
- Detection of currently open sessions on Linux and Windows
- tmux pane and desktop-terminal focus on Linux when the environment exposes it
- Cross-harness resume: continue any session in Codex or Claude regardless of where it was created
- Safe, dangerous, and custom launch profiles
- Native paths and argument handling on both operating systems

Windows Terminal does not expose a stable session-ID-to-tab interface. On Windows, open sessions are identified, but exact tab focusing is intentionally not attempted.

## Requirements

- Python 3.11 or newer
- Codex CLI, Claude Code, or both
- Linux or native Windows PowerShell

The Windows-only `windows-curses` dependency is installed automatically. `psutil` is used for portable process inspection.

## Install

From a checkout:

```bash
python -m pip install .
sessions
```

With `pipx`:

```bash
pipx install .
sessions
```

From PyPI:

```bash
pipx install ai-sessions
```

On Windows, `py -m pip` can be used in place of `python -m pip`.

## Everyday use

Run `sessions`, navigate with the arrow keys or `j`/`k`, and press Enter to resume the selected conversation.

| Key | Action |
| --- | --- |
| `Ctrl-F` or `/` | Start search mode |
| `Tab` | Cycle provider filter |
| `o` | Cycle Human, Cross, Agent, and All origins |
| `v` | Cycle visible, hidden, and all sessions |
| `d` | Choose a directory |
| `s` | Cycle sort order |
| `x` | Cycle launch harness for selected session (bridges a copy when needed) |
| `p` | Cycle Safe, Dangerous, and Custom launch modes |
| `r` | Rename in the utility and provider |
| `h` | Hide or restore locally |
| `Ctrl-R` | Refresh |
| `?` | Show complete help |

Useful noninteractive forms include:

```bash
sessions --list --tool codex
sessions --list --query "is:open dir:my-project"
sessions --list --visibility hidden
sessions --resume SESSION_ID
sessions --resume SESSION_ID --launch-tool claude
sessions --resume SESSION_ID --launch-tool codex
sessions --resume SESSION_ID --dry-run
```

## What is written, and when

Resuming a session in the harness that recorded it is a pure read: `sessions` runs
`codex resume ID` or `claude --resume ID` against the original id and touches nothing.
Sessions at rest are never rewritten, and no transcript is ever edited in place.

Only two actions write to provider storage, and both are additive:

- **Rename** (`r`) appends a title entry — a `custom-title` line to a Claude transcript, or
  a `thread_name` line to `~/.codex/session_index.jsonl`.
- **Bridging** creates a *new* session file next to the existing ones and appends its title.
  The source transcript is opened read-only and left byte for byte unchanged.

Everything else — hiding, sort order, per-session harness preference — stays in this
utility's own `state.json`.

## Cross-harness resume

Codex and Claude Code store transcripts in different formats, and neither recognises the
other's session id, so a conversation cannot simply be handed across by reference. Press
`x` (or pass `--launch-tool`) and `ai-sessions` bridges it instead: it reads the source
transcript, converts the conversation into the target harness's own on-disk format, and
writes it there as a new native session. That copy is an ordinary session — the target CLI
resumes it, appends to it, and lists it like any other.

```bash
sessions --resume CODEX_SESSION_ID --launch-tool claude
sessions --resume CLAUDE_SESSION_ID --launch-tool codex
```

The user/assistant conversation crosses over as messages. Tool calls cross over
*summarised*, folded into the turn that made them:

```text
⟦Bash⟧ python -m unittest discover -s tests
   → Ran 75 tests in 0.066s
     OK
```

They are deliberately not replayed as live tool calls: a `tool_use` block would name tools
the target harness does not have, and would need a matching result to stay a valid
conversation. Summarising keeps what was run and what it returned — usually the part worth
having — without inventing structure the target cannot honour. Arguments and output are
clipped, Codex's fixed result preamble is stripped, and a Codex `exec` snippet is reduced
to the shell command it actually ran. Reasoning and attachments are dropped entirely.

Because a summary is a record and not a result, the copy opens with a note saying where it
came from and warning that the filesystem state is unverified. The source transcript is
never modified, and the copy is named `<title> (from Codex)` or `<title> (from Claude)` so
the two are never confused in the list.

### Sessions that have been compacted

A long session is not one conversation but a chain of context windows. When a harness runs
out of room it summarises everything so far and carries on from the summary, so the
transcript on disk holds every superseded window *and* a summary of each.

Replaying all of that is both wasteful and untrue to where the session actually stands, so
a bridged copy starts at the most recent summary — exactly where the source session itself
picks up. On a real 7-compaction session here that is the difference between 949,000
characters with 739 messages silently dropped to fit, and 147,000 characters with nothing
dropped at all. Set `latest_window = false` to replay the whole transcript instead.

This applies to Claude Code, whose summaries are plain text. Codex stores its compaction
summaries as `encrypted_content` with no readable form, so there is nothing to resume from
and the copy carries the full pre-compaction history instead. The handoff note says which
happened, so an over-large copy is always explainable.

### Budget

Whatever survives the above is trimmed to a character budget, since the copy lands in the
target's context window in full. The opening request and the most recent exchanges are kept
and the middle gives way; the note says how many messages were dropped. Set `max_chars` in
`config.toml` to change the budget, or `tool_calls = false` for a conversation-only copy.

Bridged copies are remembered, so launching the same pairing again continues that copy
rather than making a new one. Once the source session picks up new messages, the next
launch bridges again from the current state.

### Adding a harness

Conversions run through a harness-neutral conversation rather than pairwise, so support
for another CLI costs one reader and one writer rather than a converter per existing
harness. Register it in `HARNESSES` in `bridge.py` with four things: a name, a display
label, a reader that turns a transcript file into `Turn` objects, a writer that turns
`Turn` objects into a resumable session file, and a check for whether a session id still
exists on disk. Bridging in both directions then works for free.

This seam covers bridging only. Listing, message counts, and open-session detection are
still provider-specific in `app.py`, because each CLI records them differently — Codex in a
SQLite state database and lock files, Claude Code in a PID registry and per-project
transcript directories. That side is the larger job and is deliberately left concrete until
a third harness makes the right abstraction obvious.

A Codex writer has one non-obvious obligation. Codex records the model's context
(`response_item`) separately from what its TUI redraws (`user_message` and `agent_message`
events), and groups both into turns delimited by `task_started`/`task_complete`. A rollout
carrying only the first kind resumes with the full conversation in context but a blank
screen, which looks exactly like a failed bridge. Writers for other harnesses should expect
a similar split and check the resumed session visually, not just by asking the model what
it remembers.

One more wrinkle: Codex enumerates its sessions from a local state database rather than
from the rollout files, so a copy bridged into Codex is resumable immediately but only
appears in the `sessions` list after Codex itself has opened it once. Copies bridged into
Claude Code are listed straight away.

## Launch safety

The package defaults to `safe`. This leaves approval and sandbox behavior to each provider's normal configuration:

```text
claude --resume SESSION_ID
codex resume SESSION_ID
```

Dangerous mode adds the providers' explicit bypass flags:

```text
claude --dangerously-skip-permissions --resume SESSION_ID
codex --dangerously-bypass-approvals-and-sandbox resume SESSION_ID
```

These options disable important protections. Use them only where you have consciously accepted that risk.

Set a persistent mode from the command line:

```bash
sessions --set-launch-mode safe
sessions --set-launch-mode dangerous
```

Use `--launch-mode` for a one-time override. The active mode is always displayed in the interface header.

## Configuration

Configuration is stored in:

- Linux: `~/.config/ai-sessions/config.toml`
- Windows: `%APPDATA%\ai-sessions\config.toml`

The optional custom profile uses structured argument arrays, avoiding shell interpolation:

```toml
[launch]
mode = "custom"
claude_command = ["claude"]
codex_command = ["codex"]

[launch.custom]
claude_args = ["--permission-mode", "acceptEdits"]
codex_args = ["--sandbox", "workspace-write", "--ask-for-approval", "on-request"]

[bridge]
max_chars = 950000
tool_calls = true
latest_window = true
```

Rename/hide state is kept alongside the configuration as `state.json`. Per-session
launch-harness preferences are stored there too, with unset sessions defaulting to
the harness where the session was started, along with the bridged copy made for each
cross-harness pairing. Caches use `~/.cache/ai-sessions` on
Linux and `%LOCALAPPDATA%\ai-sessions` on Windows. Environment overrides are available
through `AI_SESSIONS_CONFIG_FILE`, `AI_SESSIONS_STATE_FILE`, `CODEX_HOME`, and
`CLAUDE_CONFIG_DIR`.

## How open-session detection works

- Claude Code publishes a live PID/session registry.
- Codex on Linux holds per-thread writer locks.
- Codex on Windows records thread IDs alongside process IDs in its local log database.
- Linux focus support follows the process into tmux and then uses `wmctrl`/`xdotool` when available.

Detection is best-effort and read-only. See [What is written, and when](#what-is-written-and-when) for the complete list of operations that touch provider storage.

## Privacy

No transcripts, caches, credentials, local names, or hidden-session state belong in this repository. The defensive `.gitignore` excludes common provider and local data paths.

## Development

```bash
python -m unittest discover -s tests -v
python -m build
```

CI exercises Python 3.11–3.13 on Ubuntu and Windows.
