handoff-cli 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- handoff_cli-0.3.0/.github/workflows/publish.yml +31 -0
- handoff_cli-0.3.0/.gitignore +3 -0
- handoff_cli-0.3.0/CLAUDE.md +118 -0
- handoff_cli-0.3.0/PKG-INFO +7 -0
- handoff_cli-0.3.0/README.md +194 -0
- handoff_cli-0.3.0/README.zh-CN.md +194 -0
- handoff_cli-0.3.0/TODO.md +45 -0
- handoff_cli-0.3.0/assets/claude-code.jpg +0 -0
- handoff_cli-0.3.0/assets/codex.jpg +0 -0
- handoff_cli-0.3.0/assets/list-tui.jpg +0 -0
- handoff_cli-0.3.0/assets/parallel.jpg +0 -0
- handoff_cli-0.3.0/assets/shell.jpg +0 -0
- handoff_cli-0.3.0/assets/tail.jpg +0 -0
- handoff_cli-0.3.0/cli/__init__.py +3 -0
- handoff_cli-0.3.0/cli/backend.py +224 -0
- handoff_cli-0.3.0/cli/backend_types.yaml +91 -0
- handoff_cli-0.3.0/cli/commands/__init__.py +0 -0
- handoff_cli-0.3.0/cli/commands/env.py +30 -0
- handoff_cli-0.3.0/cli/commands/init.py +129 -0
- handoff_cli-0.3.0/cli/commands/list.py +81 -0
- handoff_cli-0.3.0/cli/commands/resume.py +179 -0
- handoff_cli-0.3.0/cli/commands/run.py +211 -0
- handoff_cli-0.3.0/cli/commands/tail.py +48 -0
- handoff_cli-0.3.0/cli/config.py +351 -0
- handoff_cli-0.3.0/cli/core.py +302 -0
- handoff_cli-0.3.0/cli/jsonl_parser.py +182 -0
- handoff_cli-0.3.0/cli/jsonl_viewer.py +440 -0
- handoff_cli-0.3.0/cli/main.py +98 -0
- handoff_cli-0.3.0/cli/skills/handoff-codex/SKILL.md +77 -0
- handoff_cli-0.3.0/cli/skills/handoff-ds/SKILL.md +77 -0
- handoff_cli-0.3.0/cli/skills/handoff-ds.toml +52 -0
- handoff_cli-0.3.0/cli/skills/handoff-opus/SKILL.md +77 -0
- handoff_cli-0.3.0/cli/stream.py +286 -0
- handoff_cli-0.3.0/cli/tui.py +317 -0
- handoff_cli-0.3.0/cli/user_config_template.yaml +31 -0
- handoff_cli-0.3.0/docs/cli-reference.zh-CN.md +131 -0
- handoff_cli-0.3.0/docs/configuration.zh-CN.md +150 -0
- handoff_cli-0.3.0/docs/design.zh-CN.md +93 -0
- handoff_cli-0.3.0/plans/archive/ds-cli-tail-go-cli-commands-go-py-magical-toucan.md +159 -0
- handoff_cli-0.3.0/plans/archive/handoff/00-overview.md +53 -0
- handoff_cli-0.3.0/plans/archive/handoff/01-rename-packaging.md +78 -0
- handoff_cli-0.3.0/plans/archive/handoff/02-multi-backend.md +152 -0
- handoff_cli-0.3.0/plans/archive/handoff/03-skills-docs-cleanup.md +73 -0
- handoff_cli-0.3.0/plans/archive/handoff/04-config-split-env.md +159 -0
- handoff_cli-0.3.0/plans/archive/readme-zh-cn-md-readme-md-screenshot-golden-church.md +70 -0
- handoff_cli-0.3.0/pyproject.toml +24 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write # needed for creating GitHub Release
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Install uv
|
|
19
|
+
uses: astral-sh/setup-uv@v5
|
|
20
|
+
|
|
21
|
+
- name: Build
|
|
22
|
+
run: uv build
|
|
23
|
+
|
|
24
|
+
- name: Publish to PyPI
|
|
25
|
+
run: uv publish --token ${{ secrets.PYPI }}
|
|
26
|
+
|
|
27
|
+
- name: Create GitHub Release
|
|
28
|
+
uses: softprops/action-gh-release@v2
|
|
29
|
+
with:
|
|
30
|
+
generate_release_notes: true
|
|
31
|
+
make_latest: true
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## What handoff is
|
|
6
|
+
|
|
7
|
+
A CLI proxy that dispatches coding tasks to configurable AI backends — Claude (any Anthropic-compatible endpoint) and Codex (`codex exec`). Users invoke it as a Claude Code skill (`/handoff-ds`, `/handoff-codex`, `/handoff-opus`) or Codex subagent (`handoff-ds`), rarely typing `handoff` directly.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Install: uv tool install -e . then run init
|
|
13
|
+
handoff --help
|
|
14
|
+
|
|
15
|
+
# Dispatch a task
|
|
16
|
+
echo "Refactor X and add tests" | handoff run -
|
|
17
|
+
handoff run --text "smoke test"
|
|
18
|
+
handoff run --backend codex --pro - <<'EOF'
|
|
19
|
+
...prompt...
|
|
20
|
+
EOF
|
|
21
|
+
|
|
22
|
+
# Browse/manage past runs
|
|
23
|
+
handoff list # interactive TUI (curses) when stdout is a terminal
|
|
24
|
+
handoff resume <seq> # reopen a past conversation interactively in claude/codex
|
|
25
|
+
handoff resume <seq> - # dispatch a follow-up task to that conversation (heredoc)
|
|
26
|
+
handoff tail <run-id> # live-tail a run's output stream
|
|
27
|
+
handoff env # print config / data paths (works even with broken config)
|
|
28
|
+
|
|
29
|
+
# Initial setup (creates ~/.handoff/config.yaml, symlinks skill/agent files)
|
|
30
|
+
handoff init -y
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
There are no test suites or linting setup in this repo.
|
|
34
|
+
|
|
35
|
+
## Architecture
|
|
36
|
+
|
|
37
|
+
### Entry point
|
|
38
|
+
|
|
39
|
+
`handoff` is installed via `uv tool install handoff-cli` (or `pip install handoff-cli`). The console script `handoff` calls `cli.main.main()`.
|
|
40
|
+
|
|
41
|
+
### Command dispatch (`cli/main.py`)
|
|
42
|
+
|
|
43
|
+
`main()` parses `sys.argv[1]` and dispatches to the matching `cli/commands/<subcmd>.py`. Known commands: `run`, `list`, `resume`, `tail`, `env`, `init`. Before dispatching, `_migrate_legacy_state()` checks for a legacy `~/.ds-cli/` directory and renames it to `~/.handoff/` if the new directory doesn't exist yet. `env` and `init` do NOT initialize `Config`; other subcommands do (validates user config, creates DB).
|
|
44
|
+
|
|
45
|
+
### Config (`cli/config.py`)
|
|
46
|
+
|
|
47
|
+
Two-layer split: `cli/backend_types.yaml` (mechanism) defines HOW each type is launched (command, PTY, flag templates) — NOT overridable by user config. `~/.handoff/config.yaml` (data) is the sole source of backend definitions, generated by `handoff init` from `user_config_template.yaml`. User config supports `include:` directives with cycle detection and `${ENV_VAR}` interpolation. Backend resolution: `types[<type>]` mechanism fields are deep-merged with the user's backend fields (lists replaced wholesale, not concatenated). The default backend is the first entry in `backends` (dict insertion order). Legacy top-level keys (`type_defaults`, `backend_types`, `backend_template`, `fast_backend`, `default_model`, `pro_model`, `default_backend`) are warned about and ignored. `system_prompt` can be overridden by user config (the only mechanism value with this capability).
|
|
48
|
+
|
|
49
|
+
### State (`cli/core.py`)
|
|
50
|
+
|
|
51
|
+
All state lives under `~/.handoff/`:
|
|
52
|
+
- `runs/handoff.db` — SQLite (WAL mode) with `runs` table (seq, run_id, uuid, session_id, cwd, prompt, jsonl_path, status, backend) and `run_counters` (daily auto-increment per day). `session_id` is the underlying conversation: equals `uuid` for a fresh run, or the parent's `session_id` for a `resume` continuation. `get_db()` performs in-place `ALTER TABLE` migrations to add `session_id` (backfilled from `uuid`) on old databases.
|
|
53
|
+
- `tasks/` — per-run files: `{run_id}.prompt.txt`, `.out.txt` (progress), `.result.md` (final)
|
|
54
|
+
- Run IDs: `hd-<MMDD>-<SEQ_CODE>` where SEQ_CODE is a 2-char encoding: `01`–`99` for 1–99, then `A0`–`ZZ` for 100–1035
|
|
55
|
+
- Automatic migration: on startup, if `~/.ds-cli/` exists and `~/.handoff/` doesn't, the entire directory is renamed and `dscli.db` becomes `handoff.db`
|
|
56
|
+
|
|
57
|
+
### Backend resolution (`cli/backend.py`)
|
|
58
|
+
|
|
59
|
+
Functions that set environment variables and build CLI argument lists from resolved backend configs. Two backend types:
|
|
60
|
+
|
|
61
|
+
- **claude**: `claude -p <prompt> --output-format stream-json ...` against any Anthropic-compatible endpoint. Wrapped in `script -q /dev/null` (PTY) because `claude -p` changes behavior when stdout is not a TTY. Fresh runs use `session_id_flags` (`--session-id {session_id}`); continuations use `continue_id_flags` (`--resume {session_id}`).
|
|
62
|
+
- **codex**: `codex exec --json ...` using the local codex login state. No PTY needed. Fresh runs use `session_flags` alone (codex assigns the thread id); continuations use `continue_id_flags` (`codex exec resume --json {session_id} {prompt}`) because `codex exec resume` rejects `--sandbox`/`-C`.
|
|
63
|
+
|
|
64
|
+
Placeholder substitution supports `{model}`, `{prompt}`, `{session_id}`, `{system_prompt}`, `{pro_model}`, `{cwd}`, `{home}`. `build_args()` produces the backend CLI invocation; `build_resume_args()` builds the interactive reopen invocation (no `-p`/prompt). `wrap_with_pty()` wraps in `script -q /dev/null` for claude-type backends.
|
|
65
|
+
|
|
66
|
+
### Execution pipeline (`cli/stream.py`)
|
|
67
|
+
|
|
68
|
+
`execute_run()` — the core of `run`:
|
|
69
|
+
1. Spawns the backend CLI (with PTY wrapper if claude-type) as a subprocess, stdout captured
|
|
70
|
+
2. Routes each line to a type-specific stream parser (`ClaudeStreamParser` or `CodexStreamParser`), which emits `("progress", text)` and `("session", id)` events
|
|
71
|
+
3. For each JSONL line: writes line to `.jsonl` file, handles progress events to stderr + `.out.txt`, persists the real session id when the backend reports it (codex `thread.started`)
|
|
72
|
+
4. On result: extracts result text → writes `.result.md`; default mode also prints the result text to stdout
|
|
73
|
+
5. `run` prints `RESULT=<abs-path-to-result.md>` to both stdout and stderr at startup; stderr carries progress and a final `RESULT=` marker
|
|
74
|
+
|
|
75
|
+
Parser contract:
|
|
76
|
+
- `ClaudeStreamParser` — parses `claude --output-format stream-json` JSONL via `jsonl_parser`; handles assistant plan dedup, result extraction
|
|
77
|
+
- `CodexStreamParser` — parses `codex exec --json` experimental event JSONL; session from `thread.started`, progress from `item.*` events, result from last `agent_message` at `turn.completed`
|
|
78
|
+
|
|
79
|
+
### Resume / continuation (`cli/commands/resume.py`)
|
|
80
|
+
|
|
81
|
+
`handoff resume <seq>` unifies "reopen a past conversation". It resolves the target via `find_run()` (by seq or run-id; empty → latest) and reads the row's `session_id`.
|
|
82
|
+
- **No prompt input** → interactive: `build_resume_args()` + `os.execvp` into the backend CLI (the old `go` behavior).
|
|
83
|
+
- **With prompt input** (`-`/heredoc, `--text`, or a file arg after the selector) → non-interactive continuation: calls `run._execute(..., resume_session_id=session_id)`, which allocates a *new* run row (new run_id/seq/files) carrying the parent's `session_id`, then runs the normal `execute_run` pipeline.
|
|
84
|
+
|
|
85
|
+
Because `--resume` does not fork, the session_id is stable, so the **original seq stays a valid handle** for every later turn. Flags: `--pro` / `--cwd`. An explicit `--backend` that differs from the saved backend is rejected (a conversation's session id only means something to the CLI that created it).
|
|
86
|
+
|
|
87
|
+
### TUI (`cli/tui.py`)
|
|
88
|
+
|
|
89
|
+
Textual-based interactive listing for `handoff list`. Renders a scrollable `DataTable` of runs, supports detail view (shows prompt + parsed JSONL event stream), resume (`G`), and copy session UUID (`C` → pbcopy). Auto-refreshes via a `set_interval(POLL_INTERVAL=2.0s, …)` timer that re-queries the DB (`refresh_fn` passed from `cmd_list`); a lightweight `run_id:status` fingerprint gates rebuilds, the cursor is preserved by `run_id`, and rebuilds are deferred (`_dirty` + `_on_screen_resume`) while the detail view is on top so the user isn't kicked back. The DB connection stays open for the app's lifetime in TUI mode.
|
|
90
|
+
|
|
91
|
+
### Skill/subagent files
|
|
92
|
+
|
|
93
|
+
All files live in `cli/skills/` (distributed with the wheel):
|
|
94
|
+
- `handoff-ds/SKILL.md` — Claude Code skill for DeepSeek backend (`--backend deepseek`)
|
|
95
|
+
- `handoff-codex/SKILL.md` — Claude Code skill for Codex backend (`--backend codex`)
|
|
96
|
+
- `handoff-opus/SKILL.md` — Claude Code skill for Opus backend (`--backend opus`)
|
|
97
|
+
- `handoff-ds.toml` — Codex subagent that forwards prompt files via `handoff run --backend deepseek <prompt-file> >/dev/null`
|
|
98
|
+
|
|
99
|
+
`handoff init` creates hard/soft links from `cli/skills/` into `~/.codex/agents/` and `~/.claude/skills/`.
|
|
100
|
+
|
|
101
|
+
### Mechanism layer (`cli/backend_types.yaml`)
|
|
102
|
+
|
|
103
|
+
Defines two backend types as part of the program's behavioural contract — NOT overridable by user config:
|
|
104
|
+
|
|
105
|
+
- `claude`: PTY-wrapped `claude -p --output-format stream-json` with `--session-id` (fresh) / `--resume` (continuation) flags
|
|
106
|
+
- `codex`: `codex exec --json --sandbox workspace-write` with `codex exec resume` (continuation) flags; resumes reject `--sandbox`/`-C` so `continue_id_flags` omits them
|
|
107
|
+
|
|
108
|
+
Also carries the built-in `system_prompt` (direct execution, no confirmation-seeking) — the one value users CAN override. Three backends are shipped in `user_config_template.yaml`: `deepseek` (claude type, DeepSeek API, model `deepseek-v4-flash`, pro `deepseek-v4-pro[1m]`), `opus` (claude type, local claude login, model `claude-opus-4-8`), `codex` (codex type, local codex login, model `gpt-5.5`).
|
|
109
|
+
|
|
110
|
+
## Key constraints
|
|
111
|
+
|
|
112
|
+
- `--backend` flag picks a backend (bundled: `deepseek`, `opus`, `codex`); `--pro` uses the backend's `pro_model`
|
|
113
|
+
- Resume must stay on the conversation's original backend (explicit `--backend` mismatch is an error)
|
|
114
|
+
- `ensure_backend_token_ready()` blocks execution for claude-type backends whose token is still a placeholder (`<...>`) or empty; backends without `ANTHROPIC_AUTH_TOKEN` env (opus, codex) are skipped
|
|
115
|
+
- Custom backends of type `claude` must carry a `model` field
|
|
116
|
+
- Max 1035 runs per day (ZZ seq_code limit)
|
|
117
|
+
- Statuses: `running`, `success`, `error`, `interrupted`
|
|
118
|
+
- Legacy `~/.ds-cli/` is auto-migrated to `~/.handoff/` on first run; old `ds-` prefix run IDs are not renamed but remain valid for lookup
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# handoff
|
|
4
|
+
|
|
5
|
+
**Your coding agents should be delegating to each other.**
|
|
6
|
+
|
|
7
|
+
Hand work off to DeepSeek from inside Claude Code / Codex and save your flagship quota;
|
|
8
|
+
hand a hard problem off to Codex / Opus from inside a DeepSeek session and borrow a brain.
|
|
9
|
+
No tool-switching, no lost context.
|
|
10
|
+
|
|
11
|
+
[](https://pypi.org/project/handoff-cli/)
|
|
12
|
+
[](https://pypi.org/project/handoff-cli/)
|
|
13
|
+
|
|
14
|
+
**English** · [简体中文](README.zh-CN.md)
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<!-- assets/claude-code.jpg — ~720px wide — hero shot: one-sentence dispatch in Claude Code, main session only echoes RESULT=, agent reads .result.md and reports back -->
|
|
19
|
+
<img src="assets/claude-code.jpg" width="720" alt="Handing a task off to DeepSeek from Claude Code">
|
|
20
|
+
|
|
21
|
+
## Why handoff
|
|
22
|
+
|
|
23
|
+
If you juggle more than one coding agent, these will sound familiar:
|
|
24
|
+
|
|
25
|
+
- 💸 **"My Claude / Codex subscription never lasts the week"** — You say: *"Take the 3 tasks above and run them on 3 parallel `/handoff-ds`."* DeepSeek does execution work fast and cheap; save the flagship quota for decisions.
|
|
26
|
+
- 🤔 **"DeepSeek is stuck — I'd like a second opinion from Codex"** — You say: *"Ask `/handoff-codex` what it thinks."* No new terminal, no re-explaining the background; the answer lands back in your current session.
|
|
27
|
+
- 🔁 **"Pick up where the last dispatched task left off"** — You say: *"Resume that `/handoff-ds` session and do item 2."* Every file it changed, everything it read and concluded is still there.
|
|
28
|
+
- 🔄 **"Trying another model means a fresh session and retelling the whole story"** — Don't switch. Stay in the session you know; handoff brokers the task in the middle and brings the result back.
|
|
29
|
+
|
|
30
|
+
And that's the entire user interface: **one sentence inside your agent session.**
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uv tool install handoff-cli
|
|
36
|
+
handoff init # creates the config, links skill / agent files
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Provide your DeepSeek token either way: set the `DEEPSEEK_API_KEY` environment variable, or put it in `~/.handoff/config.yaml`.
|
|
40
|
+
|
|
41
|
+
Then go back to Claude Code and say:
|
|
42
|
+
|
|
43
|
+
> Make a plan, then have `/handoff-ds` execute it.
|
|
44
|
+
|
|
45
|
+
The task runs in the background without blocking your session; when it finishes, the agent reads the result and reports back.
|
|
46
|
+
|
|
47
|
+
Upgrade with `uv tool upgrade handoff-cli`.
|
|
48
|
+
|
|
49
|
+
<details>
|
|
50
|
+
<summary>No uv / installing from source?</summary>
|
|
51
|
+
|
|
52
|
+
<br>
|
|
53
|
+
|
|
54
|
+
`pipx install handoff-cli` or `pip install handoff-cli` work just as well. From source:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
git clone https://github.com/dazuiba/handoff && cd handoff
|
|
58
|
+
uv tool install -e .
|
|
59
|
+
handoff init
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
</details>
|
|
63
|
+
|
|
64
|
+
## Who you can hand work off to
|
|
65
|
+
|
|
66
|
+
| Prompt / agent | Delegates to | Under the hood | Best for |
|
|
67
|
+
| --- | --- | --- | --- |
|
|
68
|
+
| `/handoff-ds` | DeepSeek V4 | `claude -p` (DeepSeek's Anthropic-compatible endpoint) | Execution work: writing code, running tests, refactors, bulk edits |
|
|
69
|
+
| `/handoff-codex` | Codex (GPT-5.5) | `codex exec` | Heavy reasoning, second opinions, gnarly debugging |
|
|
70
|
+
| `/handoff-opus` | Claude Opus | `claude -p` | Decisions that deserve the top model |
|
|
71
|
+
|
|
72
|
+
> Codex has no slash commands; use the subagent of the same name instead — say "have `handoff-ds` execute the task above."
|
|
73
|
+
|
|
74
|
+
All three targets work out of the box: opus / codex reuse your local logins with zero config; deepseek only needs a token.
|
|
75
|
+
|
|
76
|
+
## After a task is dispatched
|
|
77
|
+
|
|
78
|
+
Dispatching and resuming are the AI's job (`handoff run` / `handoff resume` under the hood). These two are for you — browse the list, watch the progress:
|
|
79
|
+
|
|
80
|
+
<table>
|
|
81
|
+
<tr>
|
|
82
|
+
<td width="50%" valign="top">
|
|
83
|
+
|
|
84
|
+
**`handoff list`** — interactive TUI over your full task history. Read the full prompt, live status, and final result; press `G` on a row to reload that conversation and keep chatting.
|
|
85
|
+
|
|
86
|
+
</td>
|
|
87
|
+
<td width="50%" valign="top">
|
|
88
|
+
|
|
89
|
+
**`handoff tail <run-id>`** — follow a task's output stream live, like looking over its shoulder.
|
|
90
|
+
|
|
91
|
+
</td>
|
|
92
|
+
</tr>
|
|
93
|
+
<tr>
|
|
94
|
+
<td valign="top">
|
|
95
|
+
|
|
96
|
+
<!-- assets/list-tui.jpg — ~480px wide — TUI list + detail view, highlight G/C shortcuts -->
|
|
97
|
+
<img src="assets/list-tui.jpg" width="100%" alt="handoff list interactive TUI">
|
|
98
|
+
|
|
99
|
+
</td>
|
|
100
|
+
<td valign="top">
|
|
101
|
+
|
|
102
|
+
<!-- assets/tail.jpg — ~480px wide — handoff tail live stream -->
|
|
103
|
+
<img src="assets/tail.jpg" width="100%" alt="handoff tail live follow">
|
|
104
|
+
|
|
105
|
+
</td>
|
|
106
|
+
</tr>
|
|
107
|
+
</table>
|
|
108
|
+
|
|
109
|
+
Inside Claude Code there's also a zero-cost option: expand the background shell and the live progress stream is right there — rendered in the shell view, never burning your main session's context.
|
|
110
|
+
|
|
111
|
+
<details>
|
|
112
|
+
<summary><b>Dispatching tasks in parallel</b></summary>
|
|
113
|
+
|
|
114
|
+
<br>
|
|
115
|
+
|
|
116
|
+
Have your agent fire off several tasks in a single message; each runs and completes independently. handoff auto-increments run numbers so they never collide.
|
|
117
|
+
|
|
118
|
+
<!-- assets/parallel.jpg — ~621px wide — 2–3 background tasks from one message, each with its own RESULT= path -->
|
|
119
|
+
<img src="assets/parallel.jpg" width="621" alt="Parallel dispatch">
|
|
120
|
+
|
|
121
|
+
</details>
|
|
122
|
+
|
|
123
|
+
## How it works
|
|
124
|
+
|
|
125
|
+
1. Your agent hands the whole task to handoff, which runs it **in the background** — your session never blocks.
|
|
126
|
+
2. handoff launches the matching CLI (`claude -p` / `codex exec`) in an isolated context and streams the full output to disk.
|
|
127
|
+
3. The main session receives exactly one line: `RESULT=<path-to-result-file>`. Progress goes to the background shell view and `.out.txt` — **never** into your main context.
|
|
128
|
+
4. On completion the agent gets notified, reads `.result.md`, and reports back to you.
|
|
129
|
+
5. The `RESULT=` path encodes the run_id (e.g. `hd-0611-03`) — a stable handle for resuming: every follow-up round points at the same conversation.
|
|
130
|
+
|
|
131
|
+
<details>
|
|
132
|
+
<summary><b>Why it pays off: the math</b></summary>
|
|
133
|
+
|
|
134
|
+
<br>
|
|
135
|
+
|
|
136
|
+
For **transactional work** — writing code, running tests — DeepSeek V4 holds its own against Sonnet-class models at a fraction of the price. What's genuinely scarce, and worth a subscription, is the judgment of the one or two models at the very top (Opus / GPT-5.5).
|
|
137
|
+
|
|
138
|
+
| Option | Relative cost for the same work |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| Claude Sonnet | 1× (baseline) |
|
|
141
|
+
| DeepSeek official API | **1/3** |
|
|
142
|
+
| [OpenCode Go](https://opencode.ai/go?ref=D5926WCTD8) (includes DeepSeek V4) | **1/18** |
|
|
143
|
+
|
|
144
|
+
Let the flagship model communicate, decompose, and review; hand all execution off. A $20 subscription directing $5 of compute gets you ~$200 worth of work.
|
|
145
|
+
|
|
146
|
+
</details>
|
|
147
|
+
|
|
148
|
+
## Configuration
|
|
149
|
+
|
|
150
|
+
`handoff init` writes a complete `~/.handoff/config.yaml` — three backends, ready to go. The first one is the default. Only DeepSeek needs a token; opus and codex reuse your local logins with zero config.
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
# ~/.handoff/config.yaml — handoff init generates this for you
|
|
154
|
+
backends:
|
|
155
|
+
deepseek: # ← first = default
|
|
156
|
+
type: claude
|
|
157
|
+
model: deepseek-v4-flash
|
|
158
|
+
pro_model: "deepseek-v4-pro[1m]"
|
|
159
|
+
env:
|
|
160
|
+
ANTHROPIC_BASE_URL: https://api.deepseek.com/anthropic
|
|
161
|
+
ANTHROPIC_AUTH_TOKEN: "${DEEPSEEK_API_KEY}" # set in shell, or replace with sk-...
|
|
162
|
+
ANTHROPIC_MODEL: "{model}"
|
|
163
|
+
|
|
164
|
+
opus: # local claude login — zero config
|
|
165
|
+
type: claude
|
|
166
|
+
...
|
|
167
|
+
|
|
168
|
+
codex: # local codex login — zero config
|
|
169
|
+
type: codex
|
|
170
|
+
...
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Want another Anthropic-compatible endpoint? Add a block under `backends`:
|
|
174
|
+
|
|
175
|
+
```yaml
|
|
176
|
+
backends:
|
|
177
|
+
kimi:
|
|
178
|
+
type: claude
|
|
179
|
+
model: kimi-k3
|
|
180
|
+
env:
|
|
181
|
+
ANTHROPIC_BASE_URL: https://api.moonshot.cn/anthropic
|
|
182
|
+
ANTHROPIC_AUTH_TOKEN: "${MOONSHOT_API_KEY}"
|
|
183
|
+
ANTHROPIC_MODEL: "{model}"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
The env block is entirely yours — every key=value you set is exported before the CLI launches. `{model}` substitutes the resolved model name, `${ENV_VAR}` expands from your shell.
|
|
187
|
+
|
|
188
|
+
Run `handoff env` to see where everything lives. Full details: **[configuration docs →](docs/configuration.zh-CN.md)**.
|
|
189
|
+
|
|
190
|
+
## More
|
|
191
|
+
|
|
192
|
+
- **[CLI reference →](docs/cli-reference.zh-CN.md)** — full usage of `run` / `resume` / `list` / `tail` / `env` / `init`, run-id encoding, on-disk file layout.
|
|
193
|
+
- **[Configuration →](docs/configuration.zh-CN.md)** — mechanism vs data layers, env block, `${ENV}` interpolation, include, custom backends.
|
|
194
|
+
- **[Design notes →](docs/design.zh-CN.md)** — why Claude Code uses background shells while Codex uses a subagent; the RESULT= protocol.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# handoff
|
|
4
|
+
|
|
5
|
+
**你的 coding agent 们,该互相派活了。**
|
|
6
|
+
|
|
7
|
+
在 Claude Code / Codex 里把活儿 handoff 给 DeepSeek,省下旗舰额度;
|
|
8
|
+
在 DeepSeek 会话里把难题 handoff 给 Codex / Opus,借个脑子。
|
|
9
|
+
不用切来切去,也不丢上下文。
|
|
10
|
+
|
|
11
|
+
[](https://pypi.org/project/handoff-cli/)
|
|
12
|
+
[](https://pypi.org/project/handoff-cli/)
|
|
13
|
+
|
|
14
|
+
[English](README.md) · **简体中文**
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<!-- assets/claude-code.jpg — 建议 720 宽 — 主演示图:Claude Code 里一句话派发,主会话只回显 RESULT=,完成后读 .result.md 汇报 -->
|
|
19
|
+
<img src="assets/claude-code.jpg" width="720" alt="在 Claude Code 里把任务 handoff 给 DeepSeek">
|
|
20
|
+
|
|
21
|
+
## 为什么需要 handoff
|
|
22
|
+
|
|
23
|
+
如果你同时用着几家 coding agent,这些场景你一定眼熟:
|
|
24
|
+
|
|
25
|
+
- 💸 **「Claude / Codex 订阅额度不经用」** — 你说:*「把上面 3 个任务,开 3 个 `/handoff-ds` 并行做」*。执行性工作 DeepSeek 干得又快又便宜,旗舰额度留给决策。
|
|
26
|
+
- 🤔 **「DeepSeek 干活时卡住了,想听听 Codex 的意见」** — 你说:*「问问 `/handoff-codex` 的意见」*。不用开新终端、复述半天背景,答案带回当前会话。
|
|
27
|
+
- 🔁 **「想接着上次派出去的活儿继续」** — 你说:*「接着刚才那个 `/handoff-ds` 的会话,继续做第 2 项」*。之前改过的文件、读过的代码、得出的结论全都还在。
|
|
28
|
+
- 🔄 **「想换个模型干活,就得重开会话、重述一遍背景」** — 不用换。你始终留在熟悉的会话里,handoff 在中间转交任务、拿回结果。
|
|
29
|
+
|
|
30
|
+
这就是全部用户界面:**在你的 agent 会话里说一句话**。
|
|
31
|
+
|
|
32
|
+
## 快速开始
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uv tool install handoff-cli
|
|
36
|
+
handoff init # 初始化配置,链接 skill / agent 文件
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
填上 DeepSeek 的 token(二选一):设置环境变量 `DEEPSEEK_API_KEY`,或写进 `~/.handoff/config.yaml`。
|
|
40
|
+
|
|
41
|
+
然后回到 Claude Code,对它说:
|
|
42
|
+
|
|
43
|
+
> 制定计划,让 `/handoff-ds` 执行上述任务。
|
|
44
|
+
|
|
45
|
+
任务进入后台执行,不阻塞你的会话;完成后 agent 自动读取结果、向你汇报。
|
|
46
|
+
|
|
47
|
+
更新:`uv tool upgrade handoff-cli`。
|
|
48
|
+
|
|
49
|
+
<details>
|
|
50
|
+
<summary>没有 uv / 想从源码装?</summary>
|
|
51
|
+
|
|
52
|
+
<br>
|
|
53
|
+
|
|
54
|
+
`pipx install handoff-cli` 或 `pip install handoff-cli` 同样可用。源码安装:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
git clone https://github.com/dazuiba/handoff && cd handoff
|
|
58
|
+
uv tool install -e .
|
|
59
|
+
handoff init
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
</details>
|
|
63
|
+
|
|
64
|
+
## 可以把活儿派给谁
|
|
65
|
+
|
|
66
|
+
| 提示词 / agent | 派给谁 | 底层 | 适合 |
|
|
67
|
+
| --- | --- | --- | --- |
|
|
68
|
+
| `/handoff-ds` | DeepSeek V4 | `claude -p`(DeepSeek Anthropic 端点) | 写代码、跑测试、重构、批量修改等执行性工作 |
|
|
69
|
+
| `/handoff-codex` | Codex (GPT-5.5) | `codex exec` | 复杂推理、第二意见、疑难调试 |
|
|
70
|
+
| `/handoff-opus` | Claude Opus | `claude -p` | 需要顶级模型出马的关键决策 |
|
|
71
|
+
|
|
72
|
+
> Codex 里没有 slash 命令,对应的是同名 subagent:说「让 `handoff-ds` 执行上述任务」即可。
|
|
73
|
+
|
|
74
|
+
三个目标开箱即用:opus / codex 走你本机的登录态,零配置;deepseek 只需 token。
|
|
75
|
+
|
|
76
|
+
## 任务派出去之后
|
|
77
|
+
|
|
78
|
+
派发和续接是 AI 的事(背后是 `handoff run` / `handoff resume`);下面这些给你——看列表、盯进度:
|
|
79
|
+
|
|
80
|
+
<table>
|
|
81
|
+
<tr>
|
|
82
|
+
<td width="50%" valign="top">
|
|
83
|
+
|
|
84
|
+
**`handoff list`** — 交互式 TUI,浏览全部历史任务。看 prompt 全文、实时状态、最终结果;选中按 `G` 直接把那次会话重新加载进来接着聊。
|
|
85
|
+
|
|
86
|
+
</td>
|
|
87
|
+
<td width="50%" valign="top">
|
|
88
|
+
|
|
89
|
+
**`handoff tail <run-id>`** — 实时跟踪某条任务的输出流,相当于盯着它干活。
|
|
90
|
+
|
|
91
|
+
</td>
|
|
92
|
+
</tr>
|
|
93
|
+
<tr>
|
|
94
|
+
<td valign="top">
|
|
95
|
+
|
|
96
|
+
<!-- assets/list-tui.jpg — 建议 ~480 宽 — TUI 列表 + 详情视图,圈出 G/C 快捷键 -->
|
|
97
|
+
<img src="assets/list-tui.jpg" width="100%" alt="handoff list 交互式 TUI">
|
|
98
|
+
|
|
99
|
+
</td>
|
|
100
|
+
<td valign="top">
|
|
101
|
+
|
|
102
|
+
<!-- assets/tail.jpg — 建议 ~480 宽 — handoff tail 实时输出流 -->
|
|
103
|
+
<img src="assets/tail.jpg" width="100%" alt="handoff tail 实时跟踪">
|
|
104
|
+
|
|
105
|
+
</td>
|
|
106
|
+
</tr>
|
|
107
|
+
</table>
|
|
108
|
+
|
|
109
|
+
在 Claude Code 里还有个零成本选项:展开那条后台 shell,实时进度流就在那里——走 shell view,不烧主会话上下文。
|
|
110
|
+
|
|
111
|
+
<details>
|
|
112
|
+
<summary><b>并行派发多个任务</b></summary>
|
|
113
|
+
|
|
114
|
+
<br>
|
|
115
|
+
|
|
116
|
+
在同一条消息里让 agent 派出多个任务,各自独立执行、独立完成通知。handoff 自动递增 run 序号,互不干扰。
|
|
117
|
+
|
|
118
|
+
<!-- assets/parallel.jpg — 建议 621 宽 — 同一条消息派发 2~3 个后台任务,各自拿到不同 RESULT= 路径 -->
|
|
119
|
+
<img src="assets/parallel.jpg" width="621" alt="并行派发多任务">
|
|
120
|
+
|
|
121
|
+
</details>
|
|
122
|
+
|
|
123
|
+
## 它是怎么工作的
|
|
124
|
+
|
|
125
|
+
1. 你的 agent 把任务整包交给 handoff,**后台执行**,会话不阻塞。
|
|
126
|
+
2. handoff 在独立上下文里拉起对应的 CLI(`claude -p` / `codex exec`),完整输出流式落盘。
|
|
127
|
+
3. 主会话只拿到一行 `RESULT=<结果文件路径>`;执行进度打在后台 shell view 和 `.out.txt`,**不进**主会话上下文。
|
|
128
|
+
4. 完成后 agent 收到通知,读取 `.result.md`,向你汇报。
|
|
129
|
+
5. `RESULT=` 路径里编码着 run_id(如 `hd-0611-03`)——它是续接的稳定句柄,多轮续接始终指向同一个会话。
|
|
130
|
+
|
|
131
|
+
<details>
|
|
132
|
+
<summary><b>为什么值得:一笔账</b></summary>
|
|
133
|
+
|
|
134
|
+
<br>
|
|
135
|
+
|
|
136
|
+
在写代码、跑测试这类**事务性工作**上,DeepSeek V4 不输 Sonnet 级模型,价格只有零头。真正稀缺、值得为之付订阅费的,是顶端那一两个模型(Opus / GPT-5.5)的判断力。
|
|
137
|
+
|
|
138
|
+
| 方案 | 相对单价(干同样的活) |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| Claude Sonnet | 1×(基准) |
|
|
141
|
+
| DeepSeek 官方 API | **1/3** |
|
|
142
|
+
| [OpenCode Go](https://opencode.ai/go?ref=D5926WCTD8)(含 DeepSeek V4) | **1/18** |
|
|
143
|
+
|
|
144
|
+
旗舰模型负责沟通、拆解、验收;执行全部 handoff 出去。$20 的订阅指挥 $5 的算力,干出 ~$200 的活。
|
|
145
|
+
|
|
146
|
+
</details>
|
|
147
|
+
|
|
148
|
+
## 配置
|
|
149
|
+
|
|
150
|
+
`handoff init` 生成一份完整的 `~/.handoff/config.yaml`——三个目标,开箱即用。第一个是默认目标。只有 DeepSeek 需要填 token;opus 和 codex 走本机登录态,零配置。
|
|
151
|
+
|
|
152
|
+
```yaml
|
|
153
|
+
# ~/.handoff/config.yaml —— handoff init 帮你生成
|
|
154
|
+
backends:
|
|
155
|
+
deepseek: # ← 第一个 = 默认
|
|
156
|
+
type: claude
|
|
157
|
+
model: deepseek-v4-flash
|
|
158
|
+
pro_model: "deepseek-v4-pro[1m]"
|
|
159
|
+
env:
|
|
160
|
+
ANTHROPIC_BASE_URL: https://api.deepseek.com/anthropic
|
|
161
|
+
ANTHROPIC_AUTH_TOKEN: "${DEEPSEEK_API_KEY}" # 设环境变量,或直接写 sk-...
|
|
162
|
+
ANTHROPIC_MODEL: "{model}"
|
|
163
|
+
|
|
164
|
+
opus: # 本机 claude 登录态,零配置
|
|
165
|
+
type: claude
|
|
166
|
+
...
|
|
167
|
+
|
|
168
|
+
codex: # 本机 codex 登录态,零配置
|
|
169
|
+
type: codex
|
|
170
|
+
...
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
想接入其他 anthropic 兼容端点?在 `backends` 下加一段即可:
|
|
174
|
+
|
|
175
|
+
```yaml
|
|
176
|
+
backends:
|
|
177
|
+
kimi:
|
|
178
|
+
type: claude
|
|
179
|
+
model: kimi-k3
|
|
180
|
+
env:
|
|
181
|
+
ANTHROPIC_BASE_URL: https://api.moonshot.cn/anthropic
|
|
182
|
+
ANTHROPIC_AUTH_TOKEN: "${MOONSHOT_API_KEY}"
|
|
183
|
+
ANTHROPIC_MODEL: "{model}"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
env 块全由你书写——你设的每个 key=value 都会在拉起 CLI 前导出为环境变量。`{model}` 替换为解析出的模型名,`${ENV_VAR}` 从当前 shell 展开。
|
|
187
|
+
|
|
188
|
+
运行 `handoff env` 看一下各路径在哪。完整细节见 **[配置文档 →](docs/configuration.zh-CN.md)**。
|
|
189
|
+
|
|
190
|
+
## 更多
|
|
191
|
+
|
|
192
|
+
- **[命令参考 →](docs/cli-reference.zh-CN.md)** — `run` / `resume` / `list` / `tail` / `env` / `init` 全部用法,run id 编码与落盘文件布局。
|
|
193
|
+
- **[配置文档 →](docs/configuration.zh-CN.md)** — 机制与数据两层、env 块、`${ENV}` 插值、include、自定义目标。
|
|
194
|
+
- **[设计说明 →](docs/design.zh-CN.md)** — 为什么 Claude Code 用后台 shell、Codex 用 subagent;RESULT= 协议细节。
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# TODO
|
|
2
|
+
|
|
3
|
+
ds-cli → handoff 迁移的未完成项(2026-06-11 整理)。已完成的四阶段计划存档于 `plans/archive/handoff/`。
|
|
4
|
+
|
|
5
|
+
## 1. 发布
|
|
6
|
+
|
|
7
|
+
- [ ] `uv build && uv publish`(需 PyPI token;包名 `handoff-cli`,命令 `handoff`)
|
|
8
|
+
- [ ] 发布后核对:README 顶部两个 badge 渲染正常;干净机器 `uv tool install handoff-cli && handoff init` 全流程
|
|
9
|
+
- [ ] 旧 Homebrew tap 的 ds-cli formula 加 `deprecate!`,指向 PyPI 安装方式
|
|
10
|
+
|
|
11
|
+
## 2. README 截图(4 张,新拍,旧图全是 ds-cli 时代的)
|
|
12
|
+
|
|
13
|
+
占位注释里写了每张的内容要求与宽度,README.md 与 README.zh-CN.md 共用:
|
|
14
|
+
|
|
15
|
+
- [ ] `assets/claude-code.jpg`(720 宽)— 主演示:一句话派发 → 只回显 RESULT= → 读 .result.md 汇报
|
|
16
|
+
- [ ] `assets/list-tui.jpg`(~480)— TUI 列表 + 详情,圈出 G/C 快捷键
|
|
17
|
+
- [ ] `assets/tail.jpg`(~480)— tail 实时输出流
|
|
18
|
+
- [ ] `assets/parallel.jpg`(621)— 同一条消息派发多任务、各自 RESULT=
|
|
19
|
+
|
|
20
|
+
## 3. 本机切换与旧装置退役
|
|
21
|
+
|
|
22
|
+
现状(2026-06-11):`~/.ds-cli` 已被迁移收编进 `~/.handoff`(历史数据完好);全局 `handoff`
|
|
23
|
+
指向 `/Users/sam/dev/github/handoff` clone 的 editable 安装;`~/.handoff/config.yaml` 被
|
|
24
|
+
init 重置为模板(旧 opencode 配置在 `config.yaml.bak`,需要的话手动合回,opencode 段落可参考
|
|
25
|
+
docs/configuration.zh-CN.md 的示例)。
|
|
26
|
+
|
|
27
|
+
- [ ] 把 opencode 配置从 `config.yaml.bak` 合回 `config.yaml`(或填 DEEPSEEK_API_KEY 走官方 API)
|
|
28
|
+
- [ ] 实战验证三个 skill:在 Claude Code 真实派发 `/handoff-ds`、`/handoff-codex`、`/handoff-opus` 各一次
|
|
29
|
+
- [ ] Codex 侧验证 `handoff-ds` subagent(`~/.codex/agents/handoff-ds.toml`,prompt-file 机制未实战跑过)
|
|
30
|
+
- [ ] 验证交互式 codex 续接(`handoff resume <codex-seq>` 无 prompt → `codex resume <id>`,从未人工测过)
|
|
31
|
+
- [ ] 退役死装置:`~/.claude/skills/ds-cli/`(其依赖的 `~/.ds-cli` 已不存在,skill 已不可用)、
|
|
32
|
+
`~/bin/ds-cli`、旧 checkout `/Users/sam/dev/github/ds-cli`(merge 后即冗余);
|
|
33
|
+
`acpx` / `headless` 两个 skill 与 handoff 功能重叠,考虑收敛
|
|
34
|
+
|
|
35
|
+
## 4. 文档遗留
|
|
36
|
+
|
|
37
|
+
- [ ] 英文文档缺口:README.md(英文主版)的"More"链接指向 `docs/*.zh-CN.md` 中文文档。决定翻译英文版 docs 还是在链接处注明 Chinese-only
|
|
38
|
+
- [ ] `docs/configuration.zh-CN.md`、`docs/design.zh-CN.md` 按"并列多维内容用 table 而非 section"标准复查(cli-reference 已按此重构)
|
|
39
|
+
- [ ] `configuration.zh-CN.md` system_prompt 一节仍提到死键名 `type_defaults`,措辞改为"机制层字段"
|
|
40
|
+
|
|
41
|
+
## 5. 近期要求的复查项(落地状态备忘)
|
|
42
|
+
|
|
43
|
+
- [ ] 「不对用户说"后端"」红线:当前全文档 0 命中;新增文案(截图说明、skill description 等)时保持
|
|
44
|
+
- [ ] `default_backend` 配置键已彻底移除(用户 config 出现会被警告忽略);代码内部仍有 `Config.default_backend` 访问器(语义=backends 第一个条目),名字如介意可重命名
|
|
45
|
+
- [ ] `~/.handoff/config.yaml.bak-pre-0.3` / `.bak-pre-0.4` 两个备份,确认稳定后删除
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|