Skip to content

Latest commit

 

History

History
62 lines (42 loc) · 6.86 KB

CLAUDE.md

File metadata and controls

62 lines (42 loc) · 6.86 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this project is

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.

Status: implemented MVP

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 + pitfalls
  • acceptance.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 synchronous mobile-code echo <task> that runs claude -p directly, plus mobile-code detach-probe to verify detached stdio behavior. Purpose: validate three integration assumptions through real Telegram/Hermes before building anything else — H1 Hermes routes the message to mobile-code, H2 detach returns immediately (doesn't hang Hermes), H3 claude runs under launchd/detached without auth failure or interactive-prompt hang.
  • Phase 1: the rest (async worker + push, lock, branch, tiers, commit/push, log).

Planned commands (once scaffolded per the spec)

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 (spawn claude/git), grammy (Telegram Bot API). Dev: typescript, @types/node, tsup, tsx.
  • dist/ is what runs — after editing src/, you must npm run build (the ~/.local/bin symlink 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-test uses node --test.

Architecture (the big picture)

Two-process async model is the core design (docs/mvp-spec.md §4.1):

  • The foreground run command resolves the project/tier, acquires a per-repo lock, then detaches a __worker subprocess and returns within seconds so Hermes's terminal call never blocks.
  • The detached __worker does the real work (runs claude -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.

Conventions & safety model that constrain implementation

  • Daily phone UX should prefer intent shortcuts over numeric tiers: mobile-code 看 <alias> <task> / 查 / 分析 / 总结 map to tier 1, and mobile-code 改 <alias> <task> / 修 / 实现 / 新增 map to tier 2. Keep -t 1/-t 2 working 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 --allowedTools list and does not allow bare Bash, so git commit/push, rm, sudo, curl, ssh are implicitly rejected. Do not "simplify" this back to allowing Bash + 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 allows npm test, npm run test:*, and npm run build patterns.
  • commit and push are separate, explicitly-confirmed subcommands. A run task only edits files; the user confirms mobile-code commit from their phone, then separately mobile-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; sendMessage retries, and on total failure writes the body into the job log.
  • launchd snapshots PATH at hermes gateway install time — ~/.local/bin (and the dirs of claude/node) must be on it, or mobile-code aliases need absolute paths.

Verifying work

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.