Skip to main content
Glama

shellphone ๐Ÿฆž๐Ÿ“ž

A context bridge between Claude Code and Claude Chat. Two surfaces that already talk to you individually now talk to each other, so you stop being the modem.

See SPEC.md for the design rationale. This README is how to run it.


What it actually does

At the end of every Claude Code session, a stop-hook makes Claude write a terse digest of what just happened into .shellphone/state.md โ€” plain markdown, in your repo, git-trackable. A small MCP server exposes those digests to Claude Chat, and lets Chat queue instructions back.

you, in chat:  "where's bitlattice at?"
chat:          get_state("bitlattice")
               โ†’ needs-input, 2h ago, blocked on fixed vs adaptive
                 temperature schedule; open question about PRNG streams
you, in chat:  "adaptive, seed from the repo state hash"
chat:          send_instruction("bitlattice", "...")
you, later:    $ claude          # in bitlattice
               ๐Ÿฆž๐Ÿ“ž shellphone: 1 instruction waiting from Claude Chat โ€ฆ

No code leaves the machine. shellphone moves state about code, never the code.

Related MCP server: Herald

Install

npm install -g shellphone     # or: npm link, from a clone

Then, once per repo you want on the wire:

cd ~/src/bitlattice
shellphone init
shellphone install-hooks --global   # safe: hooks no-op outside shellphone repos

install-hooks merges three hooks into ~/.claude/settings.json (or .claude/settings.json with no --global). It never overwrites hooks you already have, and re-running it is a no-op.

Hook

What it does

Stop

Asks for a digest, but only once the ledger has gone stale (see below)

SessionStart

Hands over any instruction still pending from chat

UserPromptSubmit

Catches instructions that land mid-session

It also installs a /digest slash command.

Install as a Claude Desktop extension

Download shellphone-<version>.mcpb from the releases page and double-click it, or drag it into Claude Desktop. No Node install needed โ€” Claude Desktop ships its own runtime.

The extension is the read side: it lets a chat session see your repos. To get digests written in the first place you also need the CLI and hooks:

npm install -g shellphone
cd ~/src/your-repo
shellphone init
shellphone install-hooks --global

Then run /survey once in a Claude Code session there, and /digest whenever you want the chat side brought up to date.

Example prompts

Once a repo has been surveyed and has at least one digest:

  • "What am I working on?" โ€” lists every registered repo with its status, one-liner, and anything waiting (list_repos)

  • "Where is lattice-relax at, and what is it?" โ€” the project manifest plus the latest digests (get_state)

  • "Tell lattice-relax to use the adaptive schedule, seeded from the repo state hash." โ€” queues an instruction for the next Claude Code session there (send_instruction)

  • "Did Code pick up what I sent?" โ€” whether the instruction was shown to Code and whether it acted on it (get_queue_status)

Trying it without a real project

There is no account to hand out, so there is a seed script instead. It builds a small populated repo using only the CLI, so every tool can be exercised before you have run a single Claude Code session:

bash scripts/demo-seed.sh          # creates ./shellphone-demo
shellphone forget lattice-relax    # undo, then delete the directory

Two documents, two clocks

A digest answers what just changed. It cannot answer what is this, and a reader who has never seen the repo needs the second question answered first.

So there are two files, deliberately not one:

state.md

manifest.md

answers

what just changed

what this project is

written by

/digest, or the stop-hook

/survey, on demand

lifecycle

append-only, one entry per drift

overwritten in place

changes

every session

when the project reshapes

/survey                     # in Claude Code โ€” reads the repo, writes the manifest
shellphone manifest         # read it back
shellphone manifest --json  # ...as JSON

The manifest records identity, stack, layout, entry points, settled decisions with their reasoning, constraints, gotchas, and open deliberations. The decisions field is the one that earns its place: a decision without its why doesn't stop anyone re-litigating it, which is the only reason to record it.

get_state leads with the manifest and follows with the digest, under separate headings, so a standing fact is never mistaken for something that happened last Tuesday. list_repos shows each repo's one-liner, which is what makes a list of repo names mean anything.

Staleness here is reported, not thresholded โ€” you get "surveyed 12d ago, 40 commits since" and make your own call. Two hundred commits of bugfixes may not change what a project is, while one commit adding a subsystem does; a hard threshold would only cry wolf.

Drift, or: when does a digest get written

Digests work like context compaction. There's a verb you invoke whenever you want one, a passive signal that builds up, and an automatic trigger at the far end that you'll rarely hit.

Context window

shellphone

/compact

/digest โ€” write one now

"context left until auto-compact"

drift, shown in shellphone status and to Chat

auto-compact at the threshold

Stop hook asks, once drift is stale

Drift is measured against the ledger, not against your session. The question is whether what Chat would read is still true, and that doesn't depend on who is asking or how long they've been asking for. Concretely, drift is commits plus changed files since the last digest โ€” so a session where you only talked never drifts, and one that landed three commits drifts immediately.

Three levels:

  • fresh โ€” nothing has moved. No warning anywhere, no interruption.

  • drifting โ€” something moved, but not much. A quiet note in status and in what Chat reads. Never interrupts.

  • stale โ€” past a threshold. Chat is told, in the first line it reads, that the digest no longer describes the repo. The Stop hook asks for a fresh one.

Age alone never makes a digest stale. A month-old digest for a repo nobody has touched is still perfectly accurate, and nagging about it would train you to ignore the warning that matters.

shellphone config --staleFiles 5 --staleCommits 2 --staleMinutes 45   # defaults
shellphone config --autoDigest false     # never interrupt; /digest only

This design came out of dogfooding. v0.1.0 wrote one digest per session, at the first stop โ€” so it described the least-finished state a session ever had and then went quiet for hours. The ledger didn't just go stale, it went stale while reading as current, which is worse than having no ledger at all.

Connecting Claude Code (stdio)

claude mcp add shellphone -- shellphone mcp

Worth doing even though Code is the writing side: it gives Claude the record_digest tool, which is more reliable than the CLI fallback, and lets a Code session read the state of your other repos.

Connecting Claude Chat (HTTP)

claude.ai can't reach your laptop directly, so the HTTP transport is meant to sit behind a tunnel.

shellphone serve                       # binds 127.0.0.1:7373
cloudflared tunnel --url http://localhost:7373    # or ngrok / tailscale funnel

Add the resulting https://โ€ฆ.../mcp URL as a custom connector in claude.ai, with an Authorization: Bearer <token> header. Get the token with:

shellphone config --show-token

The token is generated on first run and lives in ~/.shellphone/config.json (mode 0600). /health is unauthenticated so tunnels can probe it; it reports nothing about your repos. Everything under /mcp requires the bearer token.

The tunnel is the real trust boundary here. Anyone with the URL and the token can read your digests and queue instructions into your repos. Treat the token like an SSH key, and take the tunnel down when you're not using it.

MCP tools

Tool

Direction

Notes

list_repos

read

name, branch, triage status, age, pending count

get_state(repo, limit)

read

latest digest + rolling history

get_queue_status(repo)

read

announced vs consumed, per instruction

send_instruction(repo, text)

write

queues to .shellphone/queue/inbox.md

record_digest(...)

write (Code-side)

what /digest and the stop-hook call

record_manifest(...)

write (Code-side)

what /survey calls

Read tools are cheap and safe. send_instruction is an instruction injection into a coding agent, and is described to Chat as something to confirm with you first.

CLI

shellphone status                    one line per repo: status, age, summary
shellphone attach [repo] [--watch]   full latest digest + pending instructions
shellphone manifest [repo] [--json]  what this project is
shellphone survey [repo] --stdin     write a manifest from JSON (CLI fallback for /survey)
shellphone inbox [repo] [--all]      instructions sent from chat
shellphone ack [repo] <id>           mark an instruction acted on
shellphone send <repo> <text...>     queue an instruction locally (test the write path)
shellphone prompt                    statusline fragment, silent when idle
shellphone config                    show or set config (see Drift, above)
shellphone forget <repo>             unregister (leaves files on disk)

Inside Claude Code, /digest writes one immediately โ€” that's the ergonomic path, and the one to reach for before switching over to Chat.

shellphone prompt prints ๐Ÿฆž๐Ÿ“ž2 when a repo has unread instructions and nothing at all otherwise, so it drops straight into a statusline:

{ "statusLine": { "type": "command", "command": "shellphone prompt" } }

The two trust levels

Per SPEC ยง2, read and write are gated differently, and per SPEC ยง5 the write path starts human-confirmed:

  • Default (autonomous: false) โ€” an arriving instruction is shown to Claude along with a protocol: show it to the user verbatim, wait for a go-ahead, then shellphone ack. Chat can put words in front of you; it can't put them in front of your compiler.

  • shellphone config --autonomous true โ€” Claude may act directly, but still has to say which instruction it's following before it starts.

Graduate to autonomous once you trust the digests. That's the whole point of running in confirmation mode first.

On-disk layout

<repo>/.shellphone/
  manifest.md           what the project is โ€” overwritten by /survey
  state.md              the ledger โ€” append-only markdown + fenced YAML
  queue/inbox.md        instructions from chat, pending/consumed in the heading
  queue/cursor.json     which ids Code has been shown (machine-only)

~/.shellphone/
  registry.json         known repos on this machine
  config.json           bearer token, host/port, autonomous flag

Both markdown files are the source of truth. Read them, hand-edit them, delete entries from them, commit them โ€” shellphone re-reads and agrees with you. If shellphone vanishes, less .shellphone/state.md still tells you what was going on, which is the point.

Answers to SPEC ยง7

  • Digest granularity โ€” neither per-stop nor per-session: per drift. Both of the original options are wrong, and dogfooding showed why. Per-stop spams the ledger, since Stop fires at the end of every turn. Per-session sounds right but collapses to the first stop, describing the least-finished state the session ever had. Writing when the repo has actually moved is the only version where the cost is proportional to the value. See Drift, above.

  • Multi-machine repos โ€” every digest carries a machine tag (hostname). The registry is per-machine, so two boxes with the same repo produce two ledgers that reconcile through git like any other file.

  • Structured next_decision โ€” split in two. status is an enum (wip / blocked / needs-input / exploratory / shipped) so chat can triage a dozen repos at a glance; next_decision stays free text for the actual content of the choice.

Development

npm install
npm run build
npm test

Tests cover the two parsers, which are the load-bearing correctness surface โ€” model-written prose full of colons, quotes, and code fences has to survive a YAML round trip, and the queue has to deliver each instruction exactly once without losing one whose session died mid-flight.

Privacy Policy

shellphone runs entirely on your machine. It has no backend, no accounts, and no telemetry, and the author receives nothing โ€” there is no server for data to be sent to. It reads and writes plain-text files under .shellphone/ in your repos and ~/.shellphone/, and reads repository metadata via git. It does not read, transmit, or store your source code.

The one case where data leaves your machine is opt-in and takes two deliberate steps: running shellphone serve and then exposing that port through a tunnel you operate, so claude.ai can reach it. That path is off by default.

Full policy, including retention and deletion: PRIVACY.md (https://github.com/gtaghon1/shellphone/blob/master/PRIVACY.md).

License

MIT โ€” see LICENSE.

A
license - permissive license
-
quality - not tested
A
maintenance

Maintenance

โ€“Maintainers
โ€“Response time
โ€“Release cycle
1Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    -
    quality
    B
    maintenance
    The self-hosted MCP bridge between Claude Chat and Claude Code.
    Last updated
    46
    AGPL 3.0
  • A
    license
    A
    quality
    D
    maintenance
    Wraps Claude Code as tools for MCP clients, enabling autonomous coding tasks via a 4-tool lifecycle with session management, async polling, and permission controls.
    Last updated
    4
    156
    17
    MIT

View all related MCP servers

Related MCP Connectors

  • Real-time chat hub for AI agents โ€” Claude Code, Cursor, Cline, Codex over MCP or REST.

  • Real-time chat hub for AI agents โ€” Claude Code, Cursor, Cline, Codex over MCP or REST.

  • Persistent memory and cross-session learning for AI coding assistants (hosted remote MCP).

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/gtaghon1/shellphone'

If you have feedback or need assistance with the MCP directory API, please join our Discord server