handoff-cli 0.3.0__tar.gz → 0.3.4__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.4/CLAUDE.md +45 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/PKG-INFO +1 -1
- handoff_cli-0.3.4/README.md +207 -0
- handoff_cli-0.3.4/README.zh-CN.md +209 -0
- handoff_cli-0.3.4/cli/__init__.py +8 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/backend.py +34 -17
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/commands/init.py +45 -34
- handoff_cli-0.3.4/cli/commands/new.py +88 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/commands/resume.py +30 -19
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/commands/run.py +106 -21
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/core.py +110 -15
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/jsonl_viewer.py +33 -5
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/main.py +13 -8
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/skills/handoff-codex/SKILL.md +16 -10
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/skills/handoff-ds/SKILL.md +16 -10
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/skills/handoff-ds.toml +7 -7
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/skills/handoff-opus/SKILL.md +16 -10
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/user_config_template.yaml +12 -6
- {handoff_cli-0.3.0 → handoff_cli-0.3.4/docs}/TODO.md +2 -2
- handoff_cli-0.3.4/docs/assets/handoff-hero.jpg +0 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/docs/cli-reference.zh-CN.md +4 -20
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/docs/design.zh-CN.md +1 -1
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/pyproject.toml +1 -1
- handoff_cli-0.3.0/CLAUDE.md +0 -118
- handoff_cli-0.3.0/README.md +0 -194
- handoff_cli-0.3.0/README.zh-CN.md +0 -194
- handoff_cli-0.3.0/cli/__init__.py +0 -3
- handoff_cli-0.3.0/plans/archive/ds-cli-tail-go-cli-commands-go-py-magical-toucan.md +0 -159
- handoff_cli-0.3.0/plans/archive/handoff/00-overview.md +0 -53
- handoff_cli-0.3.0/plans/archive/handoff/01-rename-packaging.md +0 -78
- handoff_cli-0.3.0/plans/archive/handoff/02-multi-backend.md +0 -152
- handoff_cli-0.3.0/plans/archive/handoff/03-skills-docs-cleanup.md +0 -73
- handoff_cli-0.3.0/plans/archive/handoff/04-config-split-env.md +0 -159
- handoff_cli-0.3.0/plans/archive/readme-zh-cn-md-readme-md-screenshot-golden-church.md +0 -70
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/.github/workflows/publish.yml +0 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/.gitignore +0 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/backend_types.yaml +0 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/commands/__init__.py +0 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/commands/env.py +0 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/commands/list.py +0 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/commands/tail.py +0 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/config.py +0 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/jsonl_parser.py +0 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/stream.py +0 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/cli/tui.py +0 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4/docs}/assets/claude-code.jpg +0 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4/docs}/assets/codex.jpg +0 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4/docs}/assets/list-tui.jpg +0 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4/docs}/assets/parallel.jpg +0 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4/docs}/assets/shell.jpg +0 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4/docs}/assets/tail.jpg +0 -0
- {handoff_cli-0.3.0 → handoff_cli-0.3.4}/docs/configuration.zh-CN.md +0 -0
|
@@ -0,0 +1,45 @@
|
|
|
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 or Codex subagent, rarely typing `handoff` directly.
|
|
8
|
+
|
|
9
|
+
## File map
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
cli/
|
|
13
|
+
├── main.py # Entry point: parses argv[1], dispatches to cli/commands/<cmd>.py
|
|
14
|
+
├── config.py # Loads ~/.handoff/config.yaml (user config), deep-merges with backend_types.yaml (mechanism)
|
|
15
|
+
├── backend.py # Resolves backend config → CLI args + env vars for claude/codex subprocess
|
|
16
|
+
├── core.py # SQLite state (~/.handoff/runs/handoff.db), run ID allocation, legacy migration
|
|
17
|
+
├── stream.py # execute_run(): spawn backend, parse JSONL stream, write .out.txt / .result.md
|
|
18
|
+
├── tui.py # Textual TUI for `handoff list` (DataTable, detail view, auto-refresh)
|
|
19
|
+
├── jsonl_parser.py # Low-level JSONL line parser
|
|
20
|
+
├── jsonl_viewer.py # JSONL log viewer (used by TUI detail panel)
|
|
21
|
+
├── backend_types.yaml # Mechanism contract: how each backend TYPE is launched (command, PTY, flags). NOT user-overridable.
|
|
22
|
+
├── user_config_template.yaml# Template for `handoff init` → ~/.handoff/config.yaml
|
|
23
|
+
├── commands/
|
|
24
|
+
│ ├── run.py # `handoff run` — execute a prompt against a backend
|
|
25
|
+
│ ├── new.py # `handoff new` — pre-allocate run_id, print prompt file path
|
|
26
|
+
│ ├── list.py # `handoff list` / `handoff ls` — TUI or plain-text listing
|
|
27
|
+
│ ├── resume.py # `handoff resume` — reopen or continue a past conversation
|
|
28
|
+
│ ├── tail.py # `handoff tail` — live-tail a run's output stream
|
|
29
|
+
│ ├── init.py # `handoff init` — create config, symlink skill/agent files
|
|
30
|
+
│ └── env.py # `handoff env` — print config/data paths
|
|
31
|
+
└── skills/
|
|
32
|
+
├── handoff-ds/SKILL.md # Claude Code skill → deepseek backend
|
|
33
|
+
├── handoff-codex/SKILL.md# Claude Code skill → codex backend
|
|
34
|
+
├── handoff-opus/SKILL.md # Claude Code skill → opus backend
|
|
35
|
+
└── handoff-ds.toml # Codex subagent definition → deepseek backend
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## How to release
|
|
39
|
+
|
|
40
|
+
1. Bump `version` in `pyproject.toml`
|
|
41
|
+
2. Commit: `git commit -m "release: vX.Y.Z"`
|
|
42
|
+
3. Tag: `git tag vX.Y.Z`
|
|
43
|
+
4. Push: `git push && git push --tags`
|
|
44
|
+
|
|
45
|
+
Pushing a `v*` tag triggers `.github/workflows/publish.yml` → builds with `uv build`, publishes to PyPI, creates a GitHub Release.
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="docs/assets/handoff-hero.jpg" width="100%" alt="hero">
|
|
3
|
+
|
|
4
|
+
# With **Handoff**, your coding agents can finally work together.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
| coding agent | → Hand off to | Why |
|
|
8
|
+
| :-- | :-- | :-- |
|
|
9
|
+
| Claude Code / Codex | **DeepSeek** | Execution work is fast and cheap; save the expensive quota for decisions |
|
|
10
|
+
| DeepSeek | **Codex / Opus** | Borrow a brain for hard problems, bring the answer back to your session |
|
|
11
|
+
|
|
12
|
+
No tool-switching, no lost context.
|
|
13
|
+
|
|
14
|
+
**English** · [简体中文](README.zh-CN.md)
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
## Why handoff
|
|
19
|
+
|
|
20
|
+
If you use more than one coding agent, these will sound familiar:
|
|
21
|
+
|
|
22
|
+
- 💸 **"Claude / Codex: the $20 plan never lasts. The $100 plan costs too much."**<br>
|
|
23
|
+
— Just say: *"Give this task to `/handoff-ds`."* DeepSeek does the work fast and cheap. Save your expensive quota for decisions.
|
|
24
|
+
- 🤔 **"DeepSeek is stuck. I want a second opinion from Codex."**<br>
|
|
25
|
+
— Just say: *"Ask `/handoff-codex` what it thinks."* No new terminal. No re-explaining. The answer comes back to your current session.
|
|
26
|
+
- 🔁 **"I want to continue that task from before."**<br>
|
|
27
|
+
— Just say: *"Resume that `/handoff-ds` session."* Everything is still there: the files it changed, the code it read, what it concluded.
|
|
28
|
+
- 🔄 **"A new model means a new session. I have to explain everything again."**<br>
|
|
29
|
+
— Don't switch. Stay in your session. handoff passes the task over, then brings the result back.
|
|
30
|
+
|
|
31
|
+
**Do the math:** for transactional work — writing code, running tests — DeepSeek V4 matches Sonnet-class models at a fraction of the price. What is really scarce, and worth a subscription, is the judgment of the one or two models at the very top (Opus / GPT-5.5).
|
|
32
|
+
|
|
33
|
+
| Option | Relative cost for the same work |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| Claude Sonnet | 1× (baseline) |
|
|
36
|
+
| DeepSeek official API | **1/3** |
|
|
37
|
+
| [OpenCode Go](https://opencode.ai/go?ref=D5926WCTD8) (includes DeepSeek V4) | **1/18** |
|
|
38
|
+
|
|
39
|
+
Let the top model talk to you, split the work, and review; hand all execution off — **a $20 subscription directing $5 of compute gets you ~$200 worth of work.** That's all there is to it: one sentence inside your agent session.
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
### 1. Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
uv tool install handoff-cli
|
|
47
|
+
handoff init # creates the config, links skill / agent files
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Upgrade with `uv tool upgrade handoff-cli`.
|
|
51
|
+
|
|
52
|
+
### 2. Set your token
|
|
53
|
+
|
|
54
|
+
opus / codex reuse your local claude / codex logins — zero config. **Only DeepSeek needs a token.**
|
|
55
|
+
|
|
56
|
+
For DeepSeek compute we recommend the [OpenCode Go plan](https://opencode.ai/go?ref=D5926WCTD8) (lowest cost, includes DeepSeek V4). Once you have a key, edit `~/.handoff/config.yaml` and change just the `ANTHROPIC_AUTH_TOKEN` line:
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
# ~/.handoff/config.yaml — handoff init generates this for you
|
|
60
|
+
backends:
|
|
61
|
+
deepseek: # ← first = default
|
|
62
|
+
type: claude
|
|
63
|
+
model: deepseek-v4-flash
|
|
64
|
+
pro_model: "deepseek-v4-pro[1m]"
|
|
65
|
+
env:
|
|
66
|
+
ANTHROPIC_BASE_URL: https://api.deepseek.com/anthropic
|
|
67
|
+
ANTHROPIC_AUTH_TOKEN: "sk-..." # ← change this. Local proxy setup: https://github.com/iTzFaisal/oc-cc-proxy
|
|
68
|
+
ANTHROPIC_MODEL: "{model}"
|
|
69
|
+
|
|
70
|
+
opus: # local claude login — zero config
|
|
71
|
+
type: claude
|
|
72
|
+
...
|
|
73
|
+
codex: # local codex login — zero config
|
|
74
|
+
type: codex
|
|
75
|
+
...
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 3. Dispatch your first task
|
|
79
|
+
|
|
80
|
+
Go back to Claude Code and say:
|
|
81
|
+
|
|
82
|
+
> Make a plan, then hand it to `/handoff-ds`.
|
|
83
|
+
|
|
84
|
+
The task runs in the background; your session is never blocked. When it finishes, the agent reads the result and reports back.
|
|
85
|
+
|
|
86
|
+
### 4. Who you can hand work off to
|
|
87
|
+
|
|
88
|
+
| What you say | From | Hands off to | Best for |
|
|
89
|
+
| --- | --- | --- | --- |
|
|
90
|
+
| `/handoff-ds` | Claude Code | DeepSeek V4 | Execution work: writing code, running tests, refactors, bulk edits |
|
|
91
|
+
| `handoff-ds` (subagent) | Codex | DeepSeek V4 | Same as above — use this when you're inside Codex |
|
|
92
|
+
| `/handoff-codex` | Claude Code | Codex (GPT-5.5) | Heavy reasoning, second opinions, hard bugs |
|
|
93
|
+
| `/handoff-opus` | Claude Code | Claude Opus | Decisions that deserve the top model |
|
|
94
|
+
|
|
95
|
+
> Codex has no slash commands, so that row is the subagent of the same name — say "have `handoff-ds` execute the task above."
|
|
96
|
+
|
|
97
|
+
### 5. Watch progress / browse history
|
|
98
|
+
|
|
99
|
+
Inside Claude Code, expand the background shell and the live progress is right there — it renders in the shell view and uses none of your main session's context. To browse history or follow a task on its own, use `handoff list` and `handoff tail` (see the FAQ below).
|
|
100
|
+
|
|
101
|
+
## FAQ
|
|
102
|
+
|
|
103
|
+
<details>
|
|
104
|
+
<summary><b>How do I browse the task list or watch a task's progress?</b></summary>
|
|
105
|
+
|
|
106
|
+
<br>
|
|
107
|
+
|
|
108
|
+
Dispatching and resuming are the AI's job (`handoff run` / `handoff resume` under the hood). These two commands are for you — browse the list, watch the progress:
|
|
109
|
+
|
|
110
|
+
<table>
|
|
111
|
+
<tr>
|
|
112
|
+
<td width="50%" valign="top">
|
|
113
|
+
|
|
114
|
+
**`handoff list` / `handoff ls`** — interactive TUI over your full task history. See the full prompt, live status, and final result; press `G` on a row to reload that conversation and keep chatting.
|
|
115
|
+
|
|
116
|
+
</td>
|
|
117
|
+
<td width="50%" valign="top">
|
|
118
|
+
|
|
119
|
+
**`handoff tail <run-id>`** — follow a task's output stream live, like looking over its shoulder.
|
|
120
|
+
|
|
121
|
+
</td>
|
|
122
|
+
</tr>
|
|
123
|
+
<tr>
|
|
124
|
+
<td valign="top">
|
|
125
|
+
|
|
126
|
+
<!-- docs/assets/list-tui.jpg — ~480px wide — TUI list + detail view, highlight G/C shortcuts -->
|
|
127
|
+
<img src="docs/assets/list-tui.jpg" width="100%" alt="handoff list interactive TUI">
|
|
128
|
+
|
|
129
|
+
</td>
|
|
130
|
+
<td valign="top">
|
|
131
|
+
|
|
132
|
+
<!-- docs/assets/tail.jpg — ~480px wide — handoff tail live stream -->
|
|
133
|
+
<img src="docs/assets/tail.jpg" width="100%" alt="handoff tail live follow">
|
|
134
|
+
|
|
135
|
+
</td>
|
|
136
|
+
</tr>
|
|
137
|
+
</table>
|
|
138
|
+
|
|
139
|
+
</details>
|
|
140
|
+
|
|
141
|
+
<details>
|
|
142
|
+
<summary><b>Can I dispatch several tasks at once?</b></summary>
|
|
143
|
+
|
|
144
|
+
<br>
|
|
145
|
+
|
|
146
|
+
Yes. Have your agent send off several tasks in one message. Each runs on its own and reports back on its own — they never get in each other's way.
|
|
147
|
+
|
|
148
|
+
<!-- docs/assets/parallel.jpg — ~621px wide — 2–3 background tasks from one message, each with its own RESULT= path -->
|
|
149
|
+
<img src="docs/assets/parallel.jpg" width="621" alt="Parallel dispatch">
|
|
150
|
+
|
|
151
|
+
</details>
|
|
152
|
+
|
|
153
|
+
<details>
|
|
154
|
+
<summary><b>No uv / installing from source?</b></summary>
|
|
155
|
+
|
|
156
|
+
<br>
|
|
157
|
+
|
|
158
|
+
`pipx install handoff-cli` or `pip install handoff-cli` work just as well. From source:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
git clone https://github.com/dazuiba/handoff && cd handoff
|
|
162
|
+
uv tool install -e .
|
|
163
|
+
handoff init
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
</details>
|
|
167
|
+
|
|
168
|
+
<details>
|
|
169
|
+
<summary><b>How do I add a custom backend / what goes in the env block?</b></summary>
|
|
170
|
+
|
|
171
|
+
<br>
|
|
172
|
+
|
|
173
|
+
Add one more entry under `backends` — any Anthropic-compatible endpoint works:
|
|
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. Run `handoff env` to see where everything lives. Full details: **[configuration docs (Chinese) →](docs/configuration.zh-CN.md)**.
|
|
187
|
+
|
|
188
|
+
</details>
|
|
189
|
+
|
|
190
|
+
<details>
|
|
191
|
+
<summary><b>How does it actually work?</b></summary>
|
|
192
|
+
|
|
193
|
+
<br>
|
|
194
|
+
|
|
195
|
+
1. Your agent hands the whole task to handoff, which runs it **in the background** — your session never blocks.
|
|
196
|
+
2. handoff launches the matching CLI (`claude -p` / `codex exec`) in an isolated context and streams the full output to disk.
|
|
197
|
+
3. The main session receives exactly one line: `RESULT=<path-to-result-file>`. Progress goes to the background shell view — **never** into your main context.
|
|
198
|
+
4. On completion the agent gets notified, reads the result file, and reports back to you.
|
|
199
|
+
5. The `RESULT=` path is also a stable handle for the conversation: every follow-up round resumes the same session.
|
|
200
|
+
|
|
201
|
+
</details>
|
|
202
|
+
|
|
203
|
+
**More docs**
|
|
204
|
+
|
|
205
|
+
- **[CLI reference (Chinese) →](docs/cli-reference.zh-CN.md)** — full usage of `run` / `resume` / `list` / `tail` / `env` / `init`, run-id encoding, on-disk file layout.
|
|
206
|
+
- **[Configuration (Chinese) →](docs/configuration.zh-CN.md)** — mechanism vs data layers, env block, `${ENV}` interpolation, include, custom backends.
|
|
207
|
+
- **[Design notes (Chinese) →](docs/design.zh-CN.md)** — why Claude Code uses background shells while Codex uses a subagent; the RESULT= protocol.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="docs/assets/handoff-hero.jpg" width="100%" alt="hero">
|
|
3
|
+
|
|
4
|
+
# 有了 **Handoff**,你手里的几个 coding agent 终于可以互相协作了。
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
| coding agent | → 派给 | 为什么 |
|
|
10
|
+
| :-- | :-- | :-- |
|
|
11
|
+
| Claude Code / Codex | **DeepSeek** | 执行性工作又快又便宜,旗舰额度留给决策 |
|
|
12
|
+
| DeepSeek | **Codex / Opus** | 难题借个脑子,答案带回当前会话 |
|
|
13
|
+
|
|
14
|
+
不用切来切去,也不丢上下文。
|
|
15
|
+
|
|
16
|
+
[English](README.md) · **简体中文**
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
## 为什么需要 handoff
|
|
21
|
+
|
|
22
|
+
如果你同时用着几家 coding agent,这些场景你一定眼熟:
|
|
23
|
+
|
|
24
|
+
- 💸 **「Claude / Codex 订阅:$20 的量太少不经用,$100 的又太贵用不起」**<br>
|
|
25
|
+
— 只需一句:*「把这个任务交给 `/handoff-ds` 做」*。执行性工作 DeepSeek 干得又快又便宜,旗舰额度留给决策。
|
|
26
|
+
- 🤔 **「DeepSeek 干活时卡住了,想听听 Codex 的意见」**<br>
|
|
27
|
+
— 只需一句:*「问问 `/handoff-codex` 的意见」*。不用开新终端、复述半天背景,答案带回当前会话。
|
|
28
|
+
- 🔁 **「想接着上次派出去的活儿继续」**<br>
|
|
29
|
+
— 只需一句:*「接着刚才那个 `/handoff-ds` 的会话继续」*。之前改过的文件、读过的代码、得出的结论全都还在。
|
|
30
|
+
- 🔄 **「想换个模型干活,就得重开会话、重述一遍背景」**<br>
|
|
31
|
+
— 不用换。你始终留在熟悉的会话里,handoff 在中间转交任务、拿回结果。
|
|
32
|
+
|
|
33
|
+
**算笔账就明白了**:写代码、跑测试这类事务性工作,DeepSeek V4 不输 Sonnet 级模型,价格只有零头。真正稀缺、值得付订阅费的,是顶端那一两个模型(Opus / GPT-5.5)的判断力。
|
|
34
|
+
|
|
35
|
+
| 方案 | 相对单价(干同样的活) |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| Claude Sonnet | 1×(基准) |
|
|
38
|
+
| DeepSeek 官方 API | **1/3** |
|
|
39
|
+
| [OpenCode Go](https://opencode.ai/go?ref=D5926WCTD8)(含 DeepSeek V4) | **1/18** |
|
|
40
|
+
|
|
41
|
+
旗舰模型负责沟通、拆解、验收;执行全部 handoff 出去——**$20 的订阅指挥 $5 的算力,干出 ~$200 的活**。这就是 handoff 的全部用法:在你的 agent 会话里说一句话。
|
|
42
|
+
|
|
43
|
+
## 快速开始
|
|
44
|
+
|
|
45
|
+
### 1. 安装
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv tool install handoff-cli
|
|
49
|
+
handoff init # 初始化配置,链接 skill / agent 文件
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
更新:`uv tool upgrade handoff-cli`。
|
|
53
|
+
|
|
54
|
+
### 2. 配 token
|
|
55
|
+
|
|
56
|
+
opus / codex 走你本机的 claude / codex 登录态,零配置;**只有 DeepSeek 需要填一个 token**。
|
|
57
|
+
|
|
58
|
+
DeepSeek 算力推荐走 [OpenCode Go 套餐](https://opencode.ai/go?ref=D5926WCTD8)(单价最低,含 DeepSeek V4)。拿到 key 后,编辑 `~/.handoff/config.yaml`,只改 `ANTHROPIC_AUTH_TOKEN` 这一行:
|
|
59
|
+
|
|
60
|
+
```yaml
|
|
61
|
+
# ~/.handoff/config.yaml —— handoff init 帮你生成
|
|
62
|
+
backends:
|
|
63
|
+
deepseek: # ← 第一个 = 默认
|
|
64
|
+
type: claude
|
|
65
|
+
model: deepseek-v4-flash
|
|
66
|
+
pro_model: "deepseek-v4-pro[1m]"
|
|
67
|
+
env:
|
|
68
|
+
ANTHROPIC_BASE_URL: https://api.deepseek.com/anthropic
|
|
69
|
+
ANTHROPIC_AUTH_TOKEN: "sk-..." # ← 改这里。本地代理设置见 https://github.com/iTzFaisal/oc-cc-proxy
|
|
70
|
+
ANTHROPIC_MODEL: "{model}"
|
|
71
|
+
|
|
72
|
+
opus: # 本机 claude 登录态,零配置
|
|
73
|
+
type: claude
|
|
74
|
+
...
|
|
75
|
+
codex: # 本机 codex 登录态,零配置
|
|
76
|
+
type: codex
|
|
77
|
+
...
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 3. 派第一个活
|
|
81
|
+
|
|
82
|
+
回到 Claude Code,对它说:
|
|
83
|
+
|
|
84
|
+
> 制定计划,交给 `/handoff-ds` 执行。
|
|
85
|
+
|
|
86
|
+
任务进入后台执行,不阻塞你的会话;完成后 agent 自动读取结果、向你汇报。
|
|
87
|
+
|
|
88
|
+
### 4. 能派给谁
|
|
89
|
+
|
|
90
|
+
| 你怎么说 | 从 | 派给 | 适合 |
|
|
91
|
+
| --- | --- | --- | --- |
|
|
92
|
+
| `/handoff-ds` | Claude Code | DeepSeek V4 | 写代码、跑测试、重构、批量修改等执行性工作 |
|
|
93
|
+
| `handoff-ds`(subagent) | Codex | DeepSeek V4 | 同上——你人在 Codex 里时走这条 |
|
|
94
|
+
| `/handoff-codex` | Claude Code | Codex (GPT-5.5) | 复杂推理、第二意见、疑难调试 |
|
|
95
|
+
| `/handoff-opus` | Claude Code | Claude Opus | 需要顶级模型出马的关键决策 |
|
|
96
|
+
|
|
97
|
+
> Codex 里没有 slash 命令,所以那行是同名 subagent:说「让 `handoff-ds` 执行上述任务」即可。
|
|
98
|
+
|
|
99
|
+
### 5. 盯进度 / 看历史
|
|
100
|
+
|
|
101
|
+
在 Claude Code 里展开那条后台 shell,实时进度流就在那里——走 shell view,不烧主会话上下文。想单独浏览历史、实时跟踪,用 `handoff list` 和 `handoff tail`(详见下方 FAQ)。
|
|
102
|
+
|
|
103
|
+
## FAQ
|
|
104
|
+
|
|
105
|
+
<details>
|
|
106
|
+
<summary><b>怎么看任务列表、盯某条任务的进度?</b></summary>
|
|
107
|
+
|
|
108
|
+
<br>
|
|
109
|
+
|
|
110
|
+
派发和续接是 AI 的事(背后是 `handoff run` / `handoff resume`);下面这两个命令给你——看列表、盯进度:
|
|
111
|
+
|
|
112
|
+
<table>
|
|
113
|
+
<tr>
|
|
114
|
+
<td width="50%" valign="top">
|
|
115
|
+
|
|
116
|
+
**`handoff list` / `handoff ls`** — 交互式 TUI,浏览全部历史任务。看 prompt 全文、实时状态、最终结果;选中按 `G` 直接把那次会话重新加载进来接着聊。
|
|
117
|
+
|
|
118
|
+
</td>
|
|
119
|
+
<td width="50%" valign="top">
|
|
120
|
+
|
|
121
|
+
**`handoff tail <run-id>`** — 实时跟踪某条任务的输出流,相当于盯着它干活。
|
|
122
|
+
|
|
123
|
+
</td>
|
|
124
|
+
</tr>
|
|
125
|
+
<tr>
|
|
126
|
+
<td valign="top">
|
|
127
|
+
|
|
128
|
+
<!-- docs/assets/list-tui.jpg — 建议 ~480 宽 — TUI 列表 + 详情视图,圈出 G/C 快捷键 -->
|
|
129
|
+
<img src="docs/assets/list-tui.jpg" width="100%" alt="handoff list 交互式 TUI">
|
|
130
|
+
|
|
131
|
+
</td>
|
|
132
|
+
<td valign="top">
|
|
133
|
+
|
|
134
|
+
<!-- docs/assets/tail.jpg — 建议 ~480 宽 — handoff tail 实时输出流 -->
|
|
135
|
+
<img src="docs/assets/tail.jpg" width="100%" alt="handoff tail 实时跟踪">
|
|
136
|
+
|
|
137
|
+
</td>
|
|
138
|
+
</tr>
|
|
139
|
+
</table>
|
|
140
|
+
|
|
141
|
+
</details>
|
|
142
|
+
|
|
143
|
+
<details>
|
|
144
|
+
<summary><b>能同时派发多个任务吗?</b></summary>
|
|
145
|
+
|
|
146
|
+
<br>
|
|
147
|
+
|
|
148
|
+
可以。在同一条消息里让 agent 派出多个任务,各自独立执行、独立完成通知,互不干扰。
|
|
149
|
+
|
|
150
|
+
<!-- docs/assets/parallel.jpg — 建议 621 宽 — 同一条消息派发 2~3 个后台任务,各自拿到不同 RESULT= 路径 -->
|
|
151
|
+
<img src="docs/assets/parallel.jpg" width="621" alt="并行派发多任务">
|
|
152
|
+
|
|
153
|
+
</details>
|
|
154
|
+
|
|
155
|
+
<details>
|
|
156
|
+
<summary><b>没有 uv / 想从源码装?</b></summary>
|
|
157
|
+
|
|
158
|
+
<br>
|
|
159
|
+
|
|
160
|
+
`pipx install handoff-cli` 或 `pip install handoff-cli` 同样可用。源码安装:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
git clone https://github.com/dazuiba/handoff && cd handoff
|
|
164
|
+
uv tool install -e .
|
|
165
|
+
handoff init
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
</details>
|
|
169
|
+
|
|
170
|
+
<details>
|
|
171
|
+
<summary><b>怎么加一个自定义后端 / env 块怎么写?</b></summary>
|
|
172
|
+
|
|
173
|
+
<br>
|
|
174
|
+
|
|
175
|
+
`backends` 下再加一项即可,任何 Anthropic 兼容端点都行:
|
|
176
|
+
|
|
177
|
+
```yaml
|
|
178
|
+
backends:
|
|
179
|
+
kimi:
|
|
180
|
+
type: claude
|
|
181
|
+
model: kimi-k3
|
|
182
|
+
env:
|
|
183
|
+
ANTHROPIC_BASE_URL: https://api.moonshot.cn/anthropic
|
|
184
|
+
ANTHROPIC_AUTH_TOKEN: "${MOONSHOT_API_KEY}"
|
|
185
|
+
ANTHROPIC_MODEL: "{model}"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
env 块完全由你定义——你设的每个 key=value 都会在拉起 CLI 前导出为环境变量。`{model}` 替换为解析出的模型名,`${ENV_VAR}` 从当前 shell 展开。运行 `handoff env` 看各路径在哪。完整细节见 **[配置文档 →](docs/configuration.zh-CN.md)**。
|
|
189
|
+
|
|
190
|
+
</details>
|
|
191
|
+
|
|
192
|
+
<details>
|
|
193
|
+
<summary><b>它到底是怎么工作的?</b></summary>
|
|
194
|
+
|
|
195
|
+
<br>
|
|
196
|
+
|
|
197
|
+
1. 你的 agent 把任务整包交给 handoff,**后台执行**,会话不阻塞。
|
|
198
|
+
2. handoff 在独立上下文里拉起对应的 CLI(`claude -p` / `codex exec`),完整输出流式落盘。
|
|
199
|
+
3. 主会话只拿到一行 `RESULT=<结果文件路径>`;执行进度打在后台 shell view,**不进**主会话上下文。
|
|
200
|
+
4. 完成后 agent 收到通知,读取结果文件,向你汇报。
|
|
201
|
+
5. `RESULT=` 路径同时是这个会话的稳定句柄——之后每轮续接都指向同一个会话。
|
|
202
|
+
|
|
203
|
+
</details>
|
|
204
|
+
|
|
205
|
+
**更多文档**
|
|
206
|
+
|
|
207
|
+
- **[命令参考 →](docs/cli-reference.zh-CN.md)** — `run` / `resume` / `list` / `tail` / `env` / `init` 全部用法,run id 编码与落盘文件布局。
|
|
208
|
+
- **[配置文档 →](docs/configuration.zh-CN.md)** — 机制与数据两层、env 块、`${ENV}` 插值、include、自定义后端。
|
|
209
|
+
- **[设计说明 →](docs/design.zh-CN.md)** — 为什么 Claude Code 用后台 shell、Codex 用 subagent;RESULT= 协议细节。
|
|
@@ -28,6 +28,7 @@ Placeholder substitution:
|
|
|
28
28
|
from __future__ import annotations
|
|
29
29
|
|
|
30
30
|
import os
|
|
31
|
+
import shlex
|
|
31
32
|
import sys
|
|
32
33
|
from typing import Optional
|
|
33
34
|
|
|
@@ -80,6 +81,26 @@ _CLAUDE_HERMETIC_VARS = (
|
|
|
80
81
|
)
|
|
81
82
|
|
|
82
83
|
|
|
84
|
+
def resolved_backend_env(backend: dict, model: str, pro_model: str = "") -> tuple[list[str], dict[str, str]]:
|
|
85
|
+
"""Return env vars cleared and set for this backend after placeholder expansion."""
|
|
86
|
+
ctx = _base_ctx(backend, model=model, pro_model=pro_model)
|
|
87
|
+
unset_keys = list(_CLAUDE_HERMETIC_VARS) if backend_type(backend) == "claude" else []
|
|
88
|
+
|
|
89
|
+
resolved_env: dict[str, str] = {}
|
|
90
|
+
env_map = backend.get("env", {})
|
|
91
|
+
for key, val in env_map.items():
|
|
92
|
+
resolved = _resolve_env_val(val, ctx)
|
|
93
|
+
if resolved == "":
|
|
94
|
+
continue
|
|
95
|
+
resolved_env[key] = str(resolved)
|
|
96
|
+
|
|
97
|
+
if backend_type(backend) == "claude" and "CLAUDE_CONFIG_DIR" not in resolved_env:
|
|
98
|
+
config_dir = os.environ.get("CLAUDE_CONFIG_DIR") or os.path.expanduser("~/.claude")
|
|
99
|
+
resolved_env["CLAUDE_CONFIG_DIR"] = config_dir
|
|
100
|
+
|
|
101
|
+
return unset_keys, resolved_env
|
|
102
|
+
|
|
103
|
+
|
|
83
104
|
def set_backend_env(backend: dict, model: str, pro_model: str = ""):
|
|
84
105
|
"""Set environment variables for the backend CLI.
|
|
85
106
|
|
|
@@ -88,26 +109,22 @@ def set_backend_env(backend: dict, model: str, pro_model: str = ""):
|
|
|
88
109
|
known ANTHROPIC_*/model vars are cleared first, so only the backend's own
|
|
89
110
|
env block takes effect.
|
|
90
111
|
"""
|
|
91
|
-
|
|
112
|
+
unset_keys, resolved_env = resolved_backend_env(backend, model, pro_model)
|
|
92
113
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
114
|
+
for key in unset_keys:
|
|
115
|
+
os.environ.pop(key, None)
|
|
116
|
+
|
|
117
|
+
for key, value in resolved_env.items():
|
|
118
|
+
os.environ[key] = value
|
|
96
119
|
|
|
97
|
-
env_map = backend.get("env", {})
|
|
98
|
-
for key, val in env_map.items():
|
|
99
|
-
resolved = _resolve_env_val(val, ctx)
|
|
100
|
-
# an empty value (e.g. a model-less legacy backend resolving
|
|
101
|
-
# ANTHROPIC_MODEL="{model}") must not be exported — an empty env var
|
|
102
|
-
# breaks the CLI where an unset one would not
|
|
103
|
-
if resolved == "":
|
|
104
|
-
continue
|
|
105
|
-
os.environ[key] = resolved
|
|
106
120
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
121
|
+
def format_shell_command(cwd: str, cmd: list[str], unset_keys: list[str], set_env: dict[str, str]) -> str:
|
|
122
|
+
"""Render a shell command that reproduces the backend invocation."""
|
|
123
|
+
parts = [f"cd {shlex.quote(cwd)}", "&&", "env"]
|
|
124
|
+
parts.extend(f"-u {shlex.quote(key)}" for key in unset_keys)
|
|
125
|
+
parts.extend(f"{key}={shlex.quote(value)}" for key, value in set_env.items())
|
|
126
|
+
parts.extend(shlex.quote(arg) for arg in cmd)
|
|
127
|
+
return " ".join(parts)
|
|
111
128
|
|
|
112
129
|
|
|
113
130
|
def build_args(
|