Metadata-Version: 2.4
Name: session-saloon
Version: 0.1.2
Summary: A local, live dashboard that shows your Claude Code sessions as customers at a saloon bar.
License: MIT
Keywords: claude,claude-code,dashboard,sessions
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Environment :: Console
Classifier: Topic :: Utilities
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Dynamic: license-file

# Session Saloon

A local, live dashboard for people who run many Claude Code (and Codex CLI) sessions at once. Every session is a
pixel-art customer at a saloon bar. Customers whose turn is running stand near the door, drinking.
Customers waiting for **you** walk down the bar toward the barman (that is you), and the longer they
wait, the closer they get. Below the bar, **The Tab** lists the same sessions with numbers and a
copy-to-clipboard resume command.

It answers one question at a glance: **which of my Claude sessions is waiting on me, and for how long?**

![Session Saloon running on the built-in demo data](docs/screenshots/demo.jpg)

*The screenshot is `saloon --demo`: synthetic sessions, nothing real.*

## Install and run

```
pipx install session-saloon      # or: uvx session-saloon
saloon                           # serves on http://127.0.0.1:7317 and opens your browser
```

Python 3.9 or newer, no runtime dependencies. Running `saloon` a second time just opens the browser on
the instance that is already running.

```
saloon --hours 24               # time window (default 72, 0 = no limit)
saloon --port 7400
saloon --no-open
saloon --dump                   # print the snapshot as JSON and exit
saloon --projects-dir PATH      # Claude Code: default ~/.claude/projects (or $CLAUDE_CONFIG_DIR/projects)
saloon --codex-dir PATH         # Codex CLI home: default $CODEX_HOME or ~/.codex (reads its sessions/ folder)
saloon --only claude            # or --only codex: show one agent's sessions only
saloon --demo                   # synthetic, animated data that shows every state
saloon --version
```

**Double-click launchers.** Windows: `launchers/saloon.cmd` (put a shortcut to it in
`%APPDATA%\Microsoft\Windows\Start Menu\Programs`, or pin it to the taskbar). macOS: run
`pipx install session-saloon`, then drag a small Automator "Run Shell Script" app that runs `saloon`
into the Dock. Linux: add a `.desktop` file whose `Exec=` is `saloon`.

**Start at login.** Windows: put a shortcut to `saloon.cmd` (or to `saloon --no-open`) in
`shell:startup`. macOS: add a Login Item that runs `saloon --no-open`, or a LaunchAgent with
`ProgramArguments = [saloon, --no-open]`. Linux: a systemd user service with
`ExecStart=%h/.local/bin/saloon --no-open`. Then pin `http://127.0.0.1:7317/` as a browser tab; the tab
title shows `(3) Session Saloon` when three sessions wait on you.

## Reading the bar

| You see | It means |
|---|---|
| Near the door, drinking | Claude is working on that turn |
| Walking toward the barman | The turn ended and the session waits on you; the closer, the longer |
| Red `!` bubble, frown | Waiting 20 minutes or more (*late*) |
| `z z z`, dimmed | Waiting more than 2 hours (*dozing*); not counted in "waiting on you" |
| Head on the bar, `...18m` | Mid-turn but no transcript writes for 15 minutes (*stalled*): process gone, or stuck on a permission prompt |
| Top hat / crown / cap / beanie | Opus / Fable / Sonnet / Haiku |
| Robot | A headless (`sdk-cli`) session; it never waits on a human |
| Gold coins on the counter | Pull requests the session opened |
| Small mates | Its subagents (bright and bouncing while live) |

Hover a customer for details; click to pin it (its row in The Tab lights up); **double-click to serve**
it, which closes the session on the bar until you prompt it again. The same actions are in The Tab
(`SERVE` / `REOPEN`, `COPY`), reachable by keyboard.

## Claude Code and Codex

Both agents drink at the same bar. Codex sessions are tagged `codex` on The Tab, show the agent in the
hover card, and `COPY` gives `codex resume <id>` instead of `claude --resume <id>`.

| | Claude Code | Codex CLI |
|---|---|---|
| Transcripts | `~/.claude/projects/<project>/<id>.jsonl` | `~/.codex/sessions/YYYY/MM/DD/rollout-<time>-<id>.jsonl` |
| Turn finished | `turn_duration` marker (sometimes late, so a quiet text-only reply also counts) | `task_complete` (explicit, no quiet rule needed) |
| Interrupted | `[Request interrupted` text | `turn_aborted` |
| Your prompt | `user` line, minus injected reminders | completed `UserMessage` item |
| Subagents | files under `<id>/subagents/` | separate rollouts naming their parent; shown as small mates of the parent |
| Headless | `entrypoint: sdk-cli` | `codex exec`, MCP server and internal reviewers (the guardian) |

If a source folder does not exist it is skipped silently, so having only one of the two is fine.
`sources = ["claude"]` in the config file turns one off permanently.

## Configuration

Click the gear in the page, or edit the file:

* Linux/macOS: `~/.config/session-saloon/config.toml`
* Windows: `%APPDATA%\session-saloon\config.toml`

On Python 3.11+ this file is TOML, read with the standard library's `tomllib`. On Python 3.9
or 3.10 (no `tomllib`), write the same settings as JSON instead — the keys and nesting are
identical, just JSON syntax (`{"hours": 72, "thresholds": {...}, ...}`); a TOML-formatted file
is silently ignored on those versions rather than applied. The settings panel writes whichever
format your interpreter can read, so this only matters if you edit the file by hand.

```toml
hours = 72
shell = "auto"                # resume command flavour: auto | powershell | posix
sources = ["claude", "codex"]

[thresholds]                   # seconds
quiet_end = 180                # text-only reply + this much quiet = waiting
stall_after = 900              # mid-turn + this much quiet = stalled
late_after = 1200
doze_after = 7200
subagent_live = 120

[projects.alias]
"example-app-main" = "Example App"   # rename, or map several folders to one lane
```

Command-line flags override the file. Served marks live in your data directory
(`%LOCALAPPDATA%\session-saloon\served.json`, `~/.local/share/session-saloon/served.json`,
`~/Library/Application Support/session-saloon/served.json`).

## How states are decided

| State | Rule |
|---|---|
| `waiting` | The last event is a finished turn or an interrupt, or it is a text-only reply and the transcript has been quiet for more than `quiet_end` |
| `working` | Otherwise |
| `stalled` | Mid-turn and quiet for more than `stall_after` (a live subagent counts as activity) |
| `done` | Served by you and not prompted since; or a headless session that finished or went quiet |

The transcript formats are not public contracts. All knowledge of them lives in
`src/session_saloon/transcript.py` (Claude Code) and `codex.py` (Codex); unknown fields and malformed
lines are tolerated. The page also has a size setting (small by default, or normal) in the gear panel.

## Themes

The stage is skinnable: **Saloon** (default) and **Tiki Isle**, picked in the gear panel. A theme is
one self-contained object in `static/index.html` (palette + copy + a handful of drawing hooks) — see
`docs/decisions.md` for the interface and, notably, the reasoning for why "Tiki Isle" is its own
original design rather than a licensed-character reskin. See
[docs/decisions.md](docs/decisions.md) for the interpretations made along the way.

## Privacy

* Runs on your machine only. The server binds to `127.0.0.1`, never sends anything off the machine,
  and has no telemetry. The page loads no third-party resources (fonts are bundled).
* Read-only toward Claude Code: it reads the transcripts Claude Code already writes and never modifies
  them. The only thing it writes is the list of sessions you marked as served.
* `POST` endpoints only accept `application/json` (so browsers must preflight, which the server never
  answers), a loopback `Host` header (DNS-rebinding protection), a 4 KB body and strict ids.
* Session titles and prompts are rendered with `textContent`, never `innerHTML`.

## Development

```
pip install -e .[dev]
pytest
python tests/fixture_builders.py     # regenerate tests/fixtures (synthetic transcripts)
saloon --dump --projects-dir tests/fixtures/projects --hours 0 --now 1790596800
```

MIT licensed. Fonts: Press Start 2P and VT323, SIL Open Font License.

### Releasing (maintainers)

Publishing to PyPI uses [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (OIDC) —
no API token is stored as a repo secret. One-time setup on the PyPI project's *Publishing* page:
add a GitHub publisher for `wasigh/session-saloon`, workflow `release.yml`, environment `pypi`.

To ship a release: bump `version` in `pyproject.toml`, then

```
git tag v0.1.0
git push origin v0.1.0
```

`.github/workflows/release.yml` builds the package, runs the test matrix, and publishes on a
green run. `.github/workflows/ci.yml` runs the same tests on every push and pull request.
