shellphone

shellphone

Bridges Claude Code and Claude Chat by exposing session digests as MCP resources and allowing instruction queuing from Chat to Code.

Category
访问服务器

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 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.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选