shellphone
Bridges Claude Code and Claude Chat by exposing session digests as MCP resources and allowing instruction queuing from Chat to Code.
README
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.
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
statusand 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
Stophook 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, thenshellphone 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
Stopfires 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
machinetag (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.statusis an enum (wip/blocked/needs-input/exploratory/shipped) so chat can triage a dozen repos at a glance;next_decisionstays 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.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。