This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
mobile-code — a CLI that turns your phone into a remote control for Claude Code on your Mac.
Chain: phone (Telegram) → Hermes Gateway (runs on the Mac) → mobile-code <alias> "<task>" or friendly shortcuts like mobile-code 改 <alias> <task> → claude -p in the target repo → result pushed back to Telegram.
Hermes is only the message entry + router; the real coding is still done by the local claude CLI ("模式二/Mode 2"). Hermes parses the natural-language message and invokes mobile-code.
The repository now contains the TypeScript CLI implementation. The original planning docs are still useful context:
docs/mvp-spec.md— requirements & design (the authoritative spec)docs/implementation-plan.md— step-by-step build order + pitfallsacceptance.md— verification checklists (Phase 0 P0-, Phase 1 P1-) — 注:这些规划文档已归入docs/
Build in phases, do not implement everything at once (docs/mvp-spec.md §2.1):
- Phase 0 (walking skeleton):
mobile-code ping+ a synchronousmobile-code echo <task>that runsclaude -pdirectly, plusmobile-code detach-probeto verify detached stdio behavior. Purpose: validate three integration assumptions through real Telegram/Hermes before building anything else — H1 Hermes routes the message tomobile-code, H2 detach returns immediately (doesn't hang Hermes), H3clauderuns under launchd/detached without auth failure or interactive-prompt hang. - Phase 1: the rest (async worker + push, lock, branch, tiers, commit/push, log).
The project will be a TypeScript CLI built with tsup to a single executable file. These come from docs/implementation-plan.md / docs/mvp-spec.md §4.0:
npm install
npm run build # tsup (ESM, shebang via tsup.config.ts banner.js) && chmod +x dist/mobile-code.js
npm run dev # tsx src/cli.ts (run TS without building)
ln -sf "$PWD/dist/mobile-code.js" ~/.local/bin/mobile-code # put on PATH (where `claude` already lives)- Deps: runtime
commander(subcommands/args),execa(spawnclaude/git),grammy(Telegram Bot API). Dev:typescript,@types/node,tsup,tsx. dist/is what runs — after editingsrc/, you mustnpm run build(the~/.local/binsymlink points at the compiled file, not the TS).- No test runner is chosen for the CLI itself yet (unit tests are an iteration-two idea). The separate test fixture repo
~/Documents/project/mobile-code-testusesnode --test.
Two-process async model is the core design (docs/mvp-spec.md §4.1):
- The foreground
runcommand resolves the project/tier, acquires a per-repo lock, then detaches a__workersubprocess and returns within seconds so Hermes's terminal call never blocks. - The detached
__workerdoes the real work (runsclaude -p, collects results) and pushes the result to Telegram itself. This is why results survive even though the foreground call already returned.
Why the async/detach design exists (non-obvious, don't undo it): Hermes kills any agent turn after HERMES_AGENT_TIMEOUT (default 600s), and its terminal tool hangs if a background process holds file descriptors (upstream bug #8340). Therefore the worker MUST be spawned detached, with all stdio redirected to the log file + .unref() — never leave an fd attached to the parent.
Current module split (src/): cli.ts (commander entry: run / commit / push / log / register / projects / __worker; -t tier + --backend), projects.ts (alias→repo path; add a project here), tiers.ts (permission tier as a backend-agnostic intent + prompt suffix), backends/ (index.ts getBackend + claude.ts/codex.ts — each backend owns its tier→flags/sandbox mapping and result parsing), worker.ts (the detached worker), lock.ts (per-repo lock), jobs.ts (atomic job id + metadata incl. backend/startHead), notify.ts (Notifier interface + multi-channel dispatch + truncation; failures only logged, never thrown), telegram.ts (TelegramNotifier), feishu.ts (FeishuNotifier), config.ts (reads ~/.mobile-code/config), paths.ts (runtime paths). Plus skills/software-development/mobile-code/SKILL.md — a portable Agent Skill contract that teaches an orchestrating agent (Hermes/Claude Code/…) how to invoke this CLI.
Runtime state lives outside the repo in ~/.mobile-code/: config (push channels — Telegram TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID and/or Feishu self-built app FEISHU_APP_ID/FEISHU_APP_SECRET/FEISHU_CHAT_ID; optional NOTIFY_CHANNELS, DEFAULT_BACKEND, WORKER_TIMEOUT; mode 600 — at least one channel required), projects.json (alias→repo), logs/<job>.log, jobs/<job>.json, locks/<repo>.lock, job-counter.
- Daily phone UX should prefer intent shortcuts over numeric tiers:
mobile-code 看 <alias> <task>/查/分析/总结map to tier 1, andmobile-code 改 <alias> <task>/修/实现/新增map to tier 2. Keep-t 1/-t 2working for scripts and explicit tests. - Permission tiers are an allowlist (deny-by-default), not a denylist. Tier 2 (default, "can edit, can't commit") passes an explicit
--allowedToolslist and does not allow bareBash, sogit commit/push,rm,sudo,curl,sshare implicitly rejected. Do not "simplify" this back to allowingBash+ a denylist — that's bypassable and was deliberately changed (docs/mvp-spec.md §4.2). This is a guardrail, not a sandbox. - Tier 2 deliberately does not allow broad
Bash(npm run:*); it only allowsnpm test,npm run test:*, andnpm run buildpatterns. - commit and push are separate, explicitly-confirmed subcommands. A
runtask only edits files; the user confirmsmobile-code commitfrom their phone, then separatelymobile-code push. The agent has no authority to commit/push during a task. - Each task runs on a
mobile/<job>branch (isolation/rollback), not the user's current branch. - The worker must never fail silently — wrap it in try/catch/finally so every outcome (success, failure, timeout, exception) still pushes one Telegram message;
sendMessageretries, and on total failure writes the body into the job log. - launchd snapshots PATH at
hermes gateway installtime —~/.local/bin(and the dirs ofclaude/node) must be on it, ormobile-codealiases need absolute paths.
Run the relevant checklist in docs/acceptance.md (Phase 0 must pass before Phase 1). Many Phase 1 items can be checked locally without Hermes by invoking mobile-code directly; the end-to-end items require the Telegram → Hermes path.