talking-stick 0.4.11 → 0.4.13
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.
- package/README.md +15 -12
- package/dist/cli/install-commands.js +108 -19
- package/dist/cli/output.js +2 -2
- package/dist/cli/parser.js +1 -0
- package/dist/cli/registry.js +1 -1
- package/dist/harness-model.js +44 -0
- package/dist/identity.js +11 -8
- package/dist/index.js +3 -2
- package/dist/install-migration.js +7 -1
- package/dist/install.js +20 -1
- package/dist/instructions.js +14 -5
- package/dist/skill-install.js +200 -25
- package/dist/update-migration.js +11 -1
- package/docs/plans/2026-06-13-antigravity-agents-skill-install.md +793 -0
- package/docs/releases/0.4.12.md +17 -0
- package/docs/releases/0.4.13.md +25 -0
- package/package.json +1 -1
- package/skills/talking-stick/SKILL.md +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A CLI coordination tool that lets multiple AI coding agents share a single workspace without stepping on each other. One agent holds the stick at a time; handoffs carry structured context so the next agent doesn't have to re-derive it.
|
|
4
4
|
|
|
5
|
-
**Version:** 0.4.
|
|
5
|
+
**Version:** 0.4.12. Multi-process-safe (SQLite WAL), liveness-aware, no daemon. Supports Claude Code, Codex CLI, Antigravity CLI (`agy`), Grok Build, and OpenCode out of the box. Gemini CLI identity is retained for existing sessions, but Gemini skill installation is deprecated in favor of Antigravity and the shared agents skill directory. Two agents in the same room can also chat out-of-band — without passing the stick — via `tt msg send/recv`.
|
|
6
6
|
|
|
7
7
|
## Quickstart
|
|
8
8
|
|
|
@@ -84,6 +84,8 @@ tt self-update
|
|
|
84
84
|
tt uninstall --all
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
Single-harness uninstalls for shared-reading harnesses leave `~/.agents/skills/talking-stick` in place because Codex, Antigravity, Grok, and OpenCode share that one skill location. Use `tt uninstall agents` or `tt uninstall --shared` to remove only the shared skill target.
|
|
88
|
+
|
|
87
89
|
## What it gives your agent
|
|
88
90
|
|
|
89
91
|
Once installed, each agent harness has a skill that tells it to coordinate through the `tt` CLI:
|
|
@@ -163,17 +165,18 @@ For harnesses that only notice completed subprocesses, run `tt events --wait --a
|
|
|
163
165
|
|
|
164
166
|
`tt install` installs or refreshes the bundled `talking-stick` skill. It does not add MCP servers. During install, uninstall, package update, and first run after an installed package version changes, `tt` removes stale MCP registrations written by older Talking Stick releases.
|
|
165
167
|
|
|
166
|
-
- Claude Code: copied or linked into `~/.claude/skills/talking-stick`
|
|
167
|
-
- Codex: copied or linked into `~/.
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
|
|
168
|
+
- Claude Code: copied or linked into `~/.claude/skills/talking-stick` because Claude Code does not read `~/.agents/skills`
|
|
169
|
+
- Codex, Antigravity (`agy`), Grok Build, and OpenCode: copied or linked once into the shared `~/.agents/skills/talking-stick`
|
|
170
|
+
- Grok Build: also installs a trusted global session hook at `~/.grok/hooks/talking-stick-session.json`
|
|
171
|
+
- Gemini CLI: deprecated for skill installation; `tt install gemini` prints a deprecation notice and runs cleanup only
|
|
172
|
+
|
|
173
|
+
By default, `tt install` links the bundled skill so local updates are picked up immediately. Pass `--copy` if you want a standalone snapshot.
|
|
171
174
|
|
|
172
|
-
|
|
175
|
+
For harnesses that previously had proprietary skill copies, `tt` prunes duplicate `talking-stick` entries conservatively: it removes only symlinks that resolve to the bundled Talking Stick skill, and preserves copied directories, foreign symlinks, or hand-authored entries. OpenCode cleanup checks both `~/.config/opencode/skills/talking-stick` (honoring `XDG_CONFIG_HOME`) and the older `~/.opencode/skills/talking-stick` location.
|
|
173
176
|
|
|
174
|
-
Stale MCP cleanup is strict for OpenCode JSON entries: it removes only the canonical `mcp.talking-stick` value with `["tt", "mcp"]` and leaves hand-edited entries alone. Claude Code, Codex, and Gemini cleanup uses their own `mcp remove` commands when the old server name exists. Grok Build
|
|
177
|
+
Stale MCP cleanup is strict for OpenCode JSON entries: it removes only the canonical `mcp.talking-stick` value with `["tt", "mcp"]` and leaves hand-edited entries alone. Claude Code, Codex, and Gemini cleanup uses their own `mcp remove` commands when the old server name exists. Grok Build and Antigravity have no Talking Stick MCP registration path; install is native skill plus hook/shared skill only. Every cleanup run appends JSONL audit entries to `${TALKING_STICK_DATA_DIR}/update-migrations.log`.
|
|
175
178
|
|
|
176
|
-
Human CLI invocations also perform a silent best-effort sync for already-installed file-based skills in Claude Code
|
|
179
|
+
Human CLI invocations also perform a silent best-effort sync for already-installed file-based skills in Claude Code and the shared `~/.agents/skills/talking-stick` target. If the installed skill is a copy, it is refreshed from the bundled skill; if it is a stale symlink, it is relinked. Missing skill installs are skipped. Gemini skill sync is deprecated; use Antigravity/shared install instead.
|
|
177
180
|
|
|
178
181
|
## Human CLI
|
|
179
182
|
|
|
@@ -190,7 +193,7 @@ tt state [path] # full room state
|
|
|
190
193
|
tt events [path] [--after N] [--limit N] [--wait|--follow] [--event TYPE[,TYPE]] [--target self|any|agent] # room event log; --wait/--follow long-polls
|
|
191
194
|
tt msg send <recipient|room> <body...> [--interrupt] [--stdin] [--path DIR] # send an OOB message
|
|
192
195
|
tt msg recv [--wait|--follow] [--from agent] [--after N] [--target self|any|agent] [--path DIR] # receive OOB messages
|
|
193
|
-
tt instructions show [path] [--harness claude|codex|gemini|grok|opencode|all] [--scope effective|bundled|user|project] # show collaboration prompt
|
|
196
|
+
tt instructions show [path] [--harness claude|codex|antigravity|gemini|grok|opencode|all] [--scope effective|bundled|user|project] # show collaboration prompt
|
|
194
197
|
tt instructions edit [path] [--user|--project] # edit user or project prompt
|
|
195
198
|
tt instructions reset [path] (--user|--project) # delete a user or project prompt
|
|
196
199
|
tt release [path] --status TEXT --next-action TEXT # normal handoff
|
|
@@ -201,7 +204,7 @@ tt takeover [path] [--reason TEXT] # alias for take
|
|
|
201
204
|
tt notes add <body> [--turn N] [--path DIR] [--stdin] # leave an async note
|
|
202
205
|
tt notes list [--all] [--after ID] [--limit N] [--path DIR] # read notes
|
|
203
206
|
tt install <harness...> | --all [--print] [--copy] [--link] # install skill and clean stale MCP entries
|
|
204
|
-
tt uninstall <harness
|
|
207
|
+
tt uninstall <harness...|agents> | --all | --shared [--print] # remove skill and stale MCP entries
|
|
205
208
|
tt self-update [--print] [--manager npm|pnpm|yarn|bun] # update to the latest published tt
|
|
206
209
|
```
|
|
207
210
|
|
|
@@ -217,7 +220,7 @@ By default, `tt` behaves like a human CLI and resolves to `human:<username>` onl
|
|
|
217
220
|
|
|
218
221
|
Harness-aware CLI identity is resolved before the human fallback:
|
|
219
222
|
|
|
220
|
-
- Known harness environment markers such as `CLAUDECODE=1`, `CODEX_THREAD_ID`, `GEMINI_CLI=1`, `CMUX_AGENT_LAUNCH_KIND=grok`, or `OPENCODE=1` make `tt` derive a harness-style identity automatically. The cmux Grok marker is optional; Grok Build also works without cmux by walking process ancestry for a `grok` root process.
|
|
223
|
+
- Known harness environment markers such as `CLAUDECODE=1`, `CODEX_THREAD_ID`, `ANTIGRAVITY_AGENT=1`, `ANTIGRAVITY_CONVERSATION_ID`, `ANTIGRAVITY_TRAJECTORY_ID`, `GEMINI_CLI=1`, `CMUX_AGENT_LAUNCH_KIND=grok`, or `OPENCODE=1` make `tt` derive a harness-style identity automatically. Antigravity uses `ANTIGRAVITY_CONVERSATION_ID` as the preferred session anchor, falling back to `ANTIGRAVITY_TRAJECTORY_ID` and then `agy` process ancestry. The cmux Grok marker is optional; Grok Build also works without cmux by walking process ancestry for a `grok` root process.
|
|
221
224
|
- Grok Build's installed hook records hook-only `GROK_SESSION_ID` context into `${TALKING_STICK_DATA_DIR}/grok-sessions.jsonl`, letting later Grok-launched `tt` calls upgrade from process identity to the real Grok session id. `GROK_SESSION_ID` by itself is not treated as a normal shell marker, and the hook is not required for basic Grok detection.
|
|
222
225
|
- Set `TT_HARNESS_AGENT_ID=<agent-id>` if the harness wants to export the exact agent id directly.
|
|
223
226
|
- Set `TT_HARNESS_EXPORT=1` only when you need ancestry-based harness detection without a known harness environment marker.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { SUPPORTED_HARNESSES, detectHarness, parseHarnessList, planGrokSessionHookInstall, planGrokSessionHookUninstall, planUninstall, runAction } from "../install.js";
|
|
3
|
-
import { planSkillInstall, planSkillUninstall } from "../skill-install.js";
|
|
2
|
+
import { SUPPORTED_HARNESSES, detectHarness, isDeprecatedHarness, parseHarnessList, planGrokSessionHookInstall, planGrokSessionHookUninstall, planUninstall, runAction } from "../install.js";
|
|
3
|
+
import { removeDuplicateSkillInstalls, planSkillInstall, planSkillUninstall, planSharedSkillUninstall, resolveDuplicateSkillTargetPaths } from "../skill-install.js";
|
|
4
4
|
import { resolveDataDir } from "../config.js";
|
|
5
5
|
import { FileAuditLog, defaultAuditLogPath } from "../install-audit.js";
|
|
6
6
|
import { removeStaleMcpRegistrations } from "../install-migration.js";
|
|
@@ -15,32 +15,44 @@ export async function runInstallCommand(parsed) {
|
|
|
15
15
|
skipMissing: true
|
|
16
16
|
};
|
|
17
17
|
if (dryRun) {
|
|
18
|
-
|
|
18
|
+
printDeprecatedHarnessNotices(harnesses);
|
|
19
|
+
for (const action of dedupeInstallActions(planInstallActions(harnesses, installOptions))) {
|
|
19
20
|
printActionPlan(action);
|
|
20
21
|
}
|
|
21
22
|
for (const action of planCleanupActions(harnesses, installOptions)) {
|
|
22
23
|
printActionPlan(action);
|
|
23
24
|
}
|
|
25
|
+
printDuplicateSkillCleanupPlan(harnesses, installOptions);
|
|
24
26
|
return;
|
|
25
27
|
}
|
|
26
|
-
|
|
28
|
+
printDeprecatedHarnessNotices(harnesses);
|
|
29
|
+
const results = await runSkillInstallActions(dedupeInstallActions(planInstallActions(harnesses, installOptions)), installOptions);
|
|
27
30
|
reportInstallResults(results, "install");
|
|
28
31
|
reportCleanupResults(await runCleanup(harnesses, "manual", installOptions), "install");
|
|
29
32
|
printInstructionHint(results);
|
|
30
33
|
}
|
|
31
34
|
export async function runUninstallCommand(parsed) {
|
|
32
|
-
const harnesses =
|
|
35
|
+
const { harnesses, removeShared } = selectUninstallTargets(parsed);
|
|
33
36
|
const dryRun = hasOption(parsed, "print");
|
|
34
37
|
const installOptions = { skipMissing: true };
|
|
35
|
-
const actions =
|
|
38
|
+
const actions = [
|
|
39
|
+
...planUninstallActions(harnesses, installOptions),
|
|
40
|
+
...(removeShared ? [planSharedSkillUninstall(installOptions)] : [])
|
|
41
|
+
];
|
|
36
42
|
if (dryRun) {
|
|
43
|
+
printDeprecatedHarnessNotices(harnesses);
|
|
37
44
|
for (const action of actions) {
|
|
38
45
|
printActionPlan(action);
|
|
39
46
|
}
|
|
47
|
+
printSharedSkillLeftHint(harnesses, removeShared);
|
|
40
48
|
return;
|
|
41
49
|
}
|
|
42
50
|
const results = (await Promise.all(harnesses.map((harness) => runSkillUninstall(harness, installOptions)))).flat();
|
|
51
|
+
if (removeShared) {
|
|
52
|
+
results.push(await runAction(planSharedSkillUninstall(installOptions), installOptions));
|
|
53
|
+
}
|
|
43
54
|
reportInstallResults(results, "uninstall");
|
|
55
|
+
printSharedSkillLeftHint(harnesses, removeShared);
|
|
44
56
|
reportCleanupResults(await runCleanup(harnesses, "uninstall", installOptions), "uninstall");
|
|
45
57
|
}
|
|
46
58
|
export async function runInstallSkillCommand(parsed) {
|
|
@@ -48,21 +60,25 @@ export async function runInstallSkillCommand(parsed) {
|
|
|
48
60
|
const dryRun = hasOption(parsed, "print");
|
|
49
61
|
const link = resolveSkillInstallLinkMode(parsed);
|
|
50
62
|
const installOptions = { link, skipMissing: true };
|
|
51
|
-
const actions = harnesses.map((harness) => planSkillInstall(harness, installOptions));
|
|
63
|
+
const actions = dedupeInstallActions(harnesses.map((harness) => planSkillInstall(harness, installOptions)));
|
|
52
64
|
if (dryRun) {
|
|
53
65
|
for (const action of actions) {
|
|
54
66
|
printActionPlan(action);
|
|
55
67
|
}
|
|
56
68
|
return;
|
|
57
69
|
}
|
|
70
|
+
printDeprecatedHarnessNotices(harnesses);
|
|
58
71
|
const results = await Promise.all(actions.map((action) => runAction(action, installOptions)));
|
|
59
72
|
reportInstallResults(results, "install");
|
|
60
73
|
}
|
|
61
74
|
export async function runUninstallSkillCommand(parsed) {
|
|
62
|
-
const harnesses =
|
|
75
|
+
const { harnesses, removeShared } = selectUninstallTargets(parsed);
|
|
63
76
|
const dryRun = hasOption(parsed, "print");
|
|
64
77
|
const installOptions = { skipMissing: true };
|
|
65
|
-
const actions =
|
|
78
|
+
const actions = [
|
|
79
|
+
...harnesses.map((harness) => planSkillUninstall(harness, installOptions)),
|
|
80
|
+
...(removeShared ? [planSharedSkillUninstall(installOptions)] : [])
|
|
81
|
+
];
|
|
66
82
|
if (dryRun) {
|
|
67
83
|
for (const action of actions) {
|
|
68
84
|
printActionPlan(action);
|
|
@@ -71,6 +87,7 @@ export async function runUninstallSkillCommand(parsed) {
|
|
|
71
87
|
}
|
|
72
88
|
const results = await Promise.all(actions.map((action) => runAction(action, installOptions)));
|
|
73
89
|
reportInstallResults(results, "uninstall");
|
|
90
|
+
printSharedSkillLeftHint(harnesses, removeShared);
|
|
74
91
|
}
|
|
75
92
|
export async function runSelfUpdateCommand(parsed, cliEntryUrl) {
|
|
76
93
|
const dryRun = hasOption(parsed, "print");
|
|
@@ -137,6 +154,27 @@ function resolveSkillInstallLinkMode(parsed) {
|
|
|
137
154
|
function planInstallActions(harnesses, installOptions) {
|
|
138
155
|
return harnesses.flatMap((harness) => planInstallActionsForHarness(harness, installOptions));
|
|
139
156
|
}
|
|
157
|
+
function dedupeInstallActions(actions) {
|
|
158
|
+
const seen = new Set();
|
|
159
|
+
const result = [];
|
|
160
|
+
for (const action of actions) {
|
|
161
|
+
const key = installActionDedupeKey(action);
|
|
162
|
+
if (seen.has(key))
|
|
163
|
+
continue;
|
|
164
|
+
seen.add(key);
|
|
165
|
+
result.push(action);
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
function installActionDedupeKey(action) {
|
|
170
|
+
if (action.kind === "file-patch") {
|
|
171
|
+
return `${action.kind}:${action.operation ?? "op"}:${action.filePath}`;
|
|
172
|
+
}
|
|
173
|
+
if (action.kind === "exec") {
|
|
174
|
+
return `${action.kind}:${action.operation ?? "op"}:${action.command}:${action.args.join("\0")}`;
|
|
175
|
+
}
|
|
176
|
+
return `${action.kind}:${action.harness}:${action.message}`;
|
|
177
|
+
}
|
|
140
178
|
function planUninstallActions(harnesses, installOptions) {
|
|
141
179
|
return harnesses.flatMap((harness) => [
|
|
142
180
|
planSkillUninstall(harness, {
|
|
@@ -157,8 +195,7 @@ function planUninstallActions(harnesses, installOptions) {
|
|
|
157
195
|
function planCleanupActions(harnesses, installOptions) {
|
|
158
196
|
return harnesses.map((harness) => planUninstall(harness, installOptions));
|
|
159
197
|
}
|
|
160
|
-
async function
|
|
161
|
-
const actions = planInstallActionsForHarness(harness, installOptions);
|
|
198
|
+
async function runSkillInstallActions(actions, installOptions) {
|
|
162
199
|
return Promise.all(actions.map((action) => runAction(action, installOptions)));
|
|
163
200
|
}
|
|
164
201
|
async function runSkillUninstall(harness, installOptions) {
|
|
@@ -186,16 +223,24 @@ function planInstallActionsForHarness(harness, installOptions) {
|
|
|
186
223
|
}
|
|
187
224
|
async function runCleanup(harnesses, reason, installOptions) {
|
|
188
225
|
const dataDir = resolveDataDir();
|
|
189
|
-
|
|
226
|
+
const audit = new FileAuditLog(defaultAuditLogPath(dataDir));
|
|
227
|
+
const mcpResults = await removeStaleMcpRegistrations({
|
|
190
228
|
harnesses,
|
|
191
229
|
reason,
|
|
192
|
-
audit
|
|
230
|
+
audit,
|
|
193
231
|
installOptions
|
|
194
232
|
});
|
|
233
|
+
const skillResults = removeDuplicateSkillInstalls({
|
|
234
|
+
harnesses,
|
|
235
|
+
reason,
|
|
236
|
+
audit,
|
|
237
|
+
...installOptions
|
|
238
|
+
});
|
|
239
|
+
return [...mcpResults, ...skillResults];
|
|
195
240
|
}
|
|
196
241
|
function selectHarnesses(parsed) {
|
|
197
242
|
if (hasOption(parsed, "all")) {
|
|
198
|
-
const detected = SUPPORTED_HARNESSES.filter((harness) => detectHarness(harness).detected);
|
|
243
|
+
const detected = SUPPORTED_HARNESSES.filter((harness) => !isDeprecatedHarness(harness) && detectHarness(harness).detected);
|
|
199
244
|
return [...detected];
|
|
200
245
|
}
|
|
201
246
|
if (parsed.positionals.length === 0) {
|
|
@@ -203,6 +248,38 @@ function selectHarnesses(parsed) {
|
|
|
203
248
|
}
|
|
204
249
|
return parseHarnessList(parsed.positionals);
|
|
205
250
|
}
|
|
251
|
+
function selectUninstallTargets(parsed) {
|
|
252
|
+
if (hasOption(parsed, "all")) {
|
|
253
|
+
return { harnesses: [...SUPPORTED_HARNESSES], removeShared: true };
|
|
254
|
+
}
|
|
255
|
+
const removeShared = hasOption(parsed, "shared") || parsed.positionals.includes("agents");
|
|
256
|
+
const harnessTokens = parsed.positionals.filter((value) => value !== "agents");
|
|
257
|
+
if (harnessTokens.length === 0) {
|
|
258
|
+
if (removeShared)
|
|
259
|
+
return { harnesses: [], removeShared: true };
|
|
260
|
+
throw new Error(`Specify at least one harness (${SUPPORTED_HARNESSES.join(", ")}, agents) or pass --all to target every installed one.`);
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
harnesses: parseHarnessList(harnessTokens),
|
|
264
|
+
removeShared
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function printDeprecatedHarnessNotices(harnesses) {
|
|
268
|
+
if (!harnesses.includes("gemini"))
|
|
269
|
+
return;
|
|
270
|
+
process.stdout.write("[gemini] deprecated: Gemini CLI skill install is deprecated; use `tt install antigravity`.\n");
|
|
271
|
+
}
|
|
272
|
+
function printSharedSkillLeftHint(harnesses, removeShared) {
|
|
273
|
+
if (removeShared)
|
|
274
|
+
return;
|
|
275
|
+
if (!harnesses.some((harness) => harness === "codex" ||
|
|
276
|
+
harness === "grok" ||
|
|
277
|
+
harness === "opencode" ||
|
|
278
|
+
harness === "antigravity")) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
process.stdout.write("Left ~/.agents/skills/talking-stick (shared with other agents). Run `tt uninstall --all` or `tt uninstall agents` to remove the shared skill.\n");
|
|
282
|
+
}
|
|
206
283
|
function printActionPlan(action) {
|
|
207
284
|
if (action.kind === "skip") {
|
|
208
285
|
return;
|
|
@@ -213,6 +290,13 @@ function printActionPlan(action) {
|
|
|
213
290
|
}
|
|
214
291
|
process.stdout.write(`[${action.harness}] ${action.description}\n`);
|
|
215
292
|
}
|
|
293
|
+
function printDuplicateSkillCleanupPlan(harnesses, installOptions) {
|
|
294
|
+
for (const harness of harnesses) {
|
|
295
|
+
for (const targetPath of resolveDuplicateSkillTargetPaths(harness, installOptions)) {
|
|
296
|
+
process.stdout.write(`[${harness}] remove duplicate skill symlink ${targetPath} if it points at bundled skill\n`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
216
300
|
function runInheritIo(command, args) {
|
|
217
301
|
return new Promise((resolve, reject) => {
|
|
218
302
|
const child = spawn(command, args, { stdio: "inherit", shell: false });
|
|
@@ -238,21 +322,26 @@ function reportInstallResults(results, mode) {
|
|
|
238
322
|
throw new Error(`${mode} completed with failures.`);
|
|
239
323
|
}
|
|
240
324
|
}
|
|
241
|
-
function printInstructionHint(results) {
|
|
242
|
-
|
|
325
|
+
export function printInstructionHint(results) {
|
|
326
|
+
const changed = new Set(["added", "updated", "ok"]);
|
|
327
|
+
if (!results.some((result) => result.ok && changed.has(result.status))) {
|
|
243
328
|
return;
|
|
244
329
|
}
|
|
245
330
|
process.stdout.write("Customize collaboration instructions with: tt instructions edit\n");
|
|
246
331
|
}
|
|
247
|
-
function reportCleanupResults(results, mode) {
|
|
332
|
+
export function reportCleanupResults(results, mode) {
|
|
248
333
|
let anyFailed = false;
|
|
249
334
|
for (const result of results) {
|
|
250
|
-
|
|
335
|
+
if (result.action === "absent" || result.action === "skipped") {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const label = result.target_type === "skill" ? "skill-cleanup" : "mcp-cleanup";
|
|
339
|
+
process.stdout.write(`[${result.harness}] ${label} ${result.action}: ${result.message}\n`);
|
|
251
340
|
if (result.action === "failed")
|
|
252
341
|
anyFailed = true;
|
|
253
342
|
}
|
|
254
343
|
if (anyFailed) {
|
|
255
|
-
throw new Error(`${mode} completed with
|
|
344
|
+
throw new Error(`${mode} completed with cleanup failures.`);
|
|
256
345
|
}
|
|
257
346
|
}
|
|
258
347
|
function formatInstallStatus(status) {
|
package/dist/cli/output.js
CHANGED
|
@@ -164,7 +164,7 @@ Commands:
|
|
|
164
164
|
tt events [path] [--after N] [--limit N] [--wait|--follow] [--event TYPE[,TYPE]] [--target self|any|agent]
|
|
165
165
|
tt msg send <recipient|room> <body...> [--interrupt] [--stdin] [--path DIR]
|
|
166
166
|
tt msg recv [--wait|--follow] [--from agent] [--after N] [--target self|any|agent] [--path DIR]
|
|
167
|
-
tt instructions show [path] [--harness claude|codex|gemini|grok|opencode|all] [--scope effective|bundled|user|project]
|
|
167
|
+
tt instructions show [path] [--harness claude|codex|antigravity|gemini|grok|opencode|all] [--scope effective|bundled|user|project]
|
|
168
168
|
tt instructions edit [path] [--user|--project]
|
|
169
169
|
tt instructions reset [path] (--user|--project)
|
|
170
170
|
tt release [path] (--status TEXT --next-action TEXT | --stdin)
|
|
@@ -175,7 +175,7 @@ Commands:
|
|
|
175
175
|
tt notes add <body> [--turn N] [--path DIR] [--stdin]
|
|
176
176
|
tt notes list [--all] [--after NOTE_ID] [--limit N] [--path DIR]
|
|
177
177
|
tt install <harness...> | --all [--print] [--copy] [--link]
|
|
178
|
-
tt uninstall <harness
|
|
178
|
+
tt uninstall <harness...|agents> | --all | --shared [--print]
|
|
179
179
|
tt self-update [--print] [--manager npm|pnpm|yarn|bun]
|
|
180
180
|
|
|
181
181
|
Harnesses: ${SUPPORTED_HARNESSES.join(", ")}
|
package/dist/cli/parser.js
CHANGED
package/dist/cli/registry.js
CHANGED
|
@@ -39,7 +39,7 @@ export const COMMAND_REGISTRY = [
|
|
|
39
39
|
needsRuntime: false,
|
|
40
40
|
startupMaintenance: false,
|
|
41
41
|
internal: false,
|
|
42
|
-
usage: "tt uninstall <harness
|
|
42
|
+
usage: "tt uninstall <harness...|agents> | --all | --shared [--print]",
|
|
43
43
|
description: "Remove the Talking Stick skill and stale MCP registrations.",
|
|
44
44
|
handler: ({ parsed }) => runUninstallCommand(parsed)
|
|
45
45
|
},
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const HARNESS_SKILL_MODELS = {
|
|
2
|
+
"claude-code": {
|
|
3
|
+
identity: "claude",
|
|
4
|
+
commandLabels: ["claude", "claude-code"],
|
|
5
|
+
skillLoadingModel: "proprietary"
|
|
6
|
+
},
|
|
7
|
+
codex: {
|
|
8
|
+
identity: "codex",
|
|
9
|
+
commandLabels: ["codex"],
|
|
10
|
+
skillLoadingModel: "shared+proprietary"
|
|
11
|
+
},
|
|
12
|
+
antigravity: {
|
|
13
|
+
identity: "antigravity",
|
|
14
|
+
commandLabels: ["agy", "antigravity"],
|
|
15
|
+
skillLoadingModel: "shared"
|
|
16
|
+
},
|
|
17
|
+
grok: {
|
|
18
|
+
identity: "grok",
|
|
19
|
+
commandLabels: ["grok"],
|
|
20
|
+
skillLoadingModel: "shared+proprietary"
|
|
21
|
+
},
|
|
22
|
+
opencode: {
|
|
23
|
+
identity: "opencode",
|
|
24
|
+
commandLabels: ["opencode"],
|
|
25
|
+
skillLoadingModel: "shared+proprietary"
|
|
26
|
+
},
|
|
27
|
+
gemini: {
|
|
28
|
+
identity: "gemini",
|
|
29
|
+
commandLabels: ["gemini"],
|
|
30
|
+
skillLoadingModel: "deprecated",
|
|
31
|
+
deprecated: true
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
export const SUPPORTED_HARNESSES = Object.keys(HARNESS_SKILL_MODELS);
|
|
35
|
+
export const FILE_SKILL_HARNESSES = SUPPORTED_HARNESSES.filter((harness) => HARNESS_SKILL_MODELS[harness].skillLoadingModel !== "deprecated");
|
|
36
|
+
export const HARNESS_CLI_HARNESSES = Array.from(new Set(SUPPORTED_HARNESSES.map((harness) => HARNESS_SKILL_MODELS[harness].identity)));
|
|
37
|
+
export const HARNESS_COMMAND_MAPPING = Object.fromEntries(SUPPORTED_HARNESSES.flatMap((harness) => HARNESS_SKILL_MODELS[harness].commandLabels.map((label) => [
|
|
38
|
+
label,
|
|
39
|
+
HARNESS_SKILL_MODELS[harness].identity
|
|
40
|
+
])));
|
|
41
|
+
export function isDeprecatedHarness(harness) {
|
|
42
|
+
const model = HARNESS_SKILL_MODELS[harness];
|
|
43
|
+
return model.deprecated === true;
|
|
44
|
+
}
|
package/dist/identity.js
CHANGED
|
@@ -2,6 +2,7 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { findGrokSessionRecord, resolveGrokSessionLogPath } from "./grok-session-store.js";
|
|
5
|
+
import { HARNESS_COMMAND_MAPPING } from "./harness-model.js";
|
|
5
6
|
import { resolveContextPath } from "./path-resolution.js";
|
|
6
7
|
import { createSystemProcessInspector } from "./process-utils.js";
|
|
7
8
|
const HARNESS_CLI_EXPORT_ENV = "TT_HARNESS_EXPORT";
|
|
@@ -233,14 +234,6 @@ function deriveHarnessDisplayName(agentId) {
|
|
|
233
234
|
const prefix = agentId.split(":")[0]?.trim();
|
|
234
235
|
return prefix && prefix.length > 0 ? prefix : "harness";
|
|
235
236
|
}
|
|
236
|
-
const HARNESS_COMMAND_MAPPING = {
|
|
237
|
-
claude: "claude",
|
|
238
|
-
"claude-code": "claude",
|
|
239
|
-
codex: "codex",
|
|
240
|
-
gemini: "gemini",
|
|
241
|
-
grok: "grok",
|
|
242
|
-
opencode: "opencode"
|
|
243
|
-
};
|
|
244
237
|
function detectHarnessViaAncestry(pid, inspector, maxDepth = 10) {
|
|
245
238
|
let currentPid = pid;
|
|
246
239
|
for (let i = 0; i < maxDepth; i++) {
|
|
@@ -279,6 +272,16 @@ function detectHarnessSignal(env) {
|
|
|
279
272
|
pidHint: null
|
|
280
273
|
};
|
|
281
274
|
}
|
|
275
|
+
if (env.ANTIGRAVITY_AGENT === "1" ||
|
|
276
|
+
nonEmpty(env.ANTIGRAVITY_CONVERSATION_ID) ||
|
|
277
|
+
nonEmpty(env.ANTIGRAVITY_TRAJECTORY_ID)) {
|
|
278
|
+
return {
|
|
279
|
+
harness: "antigravity",
|
|
280
|
+
sessionId: nonEmpty(env.ANTIGRAVITY_CONVERSATION_ID) ??
|
|
281
|
+
nonEmpty(env.ANTIGRAVITY_TRAJECTORY_ID),
|
|
282
|
+
pidHint: null
|
|
283
|
+
};
|
|
284
|
+
}
|
|
282
285
|
if (env.GEMINI_CLI === "1") {
|
|
283
286
|
return { harness: "gemini", sessionId: null, pidHint: null };
|
|
284
287
|
}
|
package/dist/index.js
CHANGED
|
@@ -4,10 +4,11 @@ export { applyPragmas, assertLocalFilesystem, detectFilesystemType, migrate, ope
|
|
|
4
4
|
export { ProtocolError, isProtocolError } from "./errors.js";
|
|
5
5
|
export { deriveHarnessCliIdentity, deriveHumanCliIdentity, deriveMcpHarnessIdentity } from "./identity.js";
|
|
6
6
|
export { ancestorPaths, canonicalizeContextPath, resolveContextPath, resolveWorkspaceRoot } from "./path-resolution.js";
|
|
7
|
-
export { DEFAULT_MAX_INSTRUCTION_FILE_BYTES, DEFAULT_INSTRUCTIONS_MARKDOWN, editInstructions, extractHarnessInstructions, normalizeInstructionHarness, parseInstructionScope, resetInstructions, resolveInstructionHarness, resolveInstructionPaths, showInstructions } from "./instructions.js";
|
|
7
|
+
export { DEFAULT_MAX_INSTRUCTION_FILE_BYTES, DEFAULT_INSTRUCTIONS_MARKDOWN, HARNESS_ALIASES, INSTRUCTION_HARNESSES, editInstructions, extractHarnessInstructions, normalizeInstructionHarness, parseInstructionScope, resetInstructions, resolveInstructionHarness, resolveInstructionPaths, showInstructions } from "./instructions.js";
|
|
8
8
|
export { SUPPORTED_HARNESSES, buildGrokSessionHookConfig, DEFAULT_GROK_SESSION_HOOK_COMMAND, GROK_SESSION_HOOK_EVENTS, GROK_SESSION_HOOK_FILE, MissingHarnessError, detectHarness, parseHarnessList, planGrokSessionHookInstall, planGrokSessionHookUninstall, planUninstall, resolveGrokSessionHookPath, resolveHarnessConfigDir, resolveOpencodeConfigDir, resolveOpencodeConfigPath, runAction, skipAction } from "./install.js";
|
|
9
9
|
export { DEFAULT_GROK_SESSION_RECORD_MAX_AGE_MS, appendGrokSessionRecord, findGrokSessionRecord, isGrokSessionEndEvent, readGrokSessionRecords, resolveGrokSessionLogPath } from "./grok-session-store.js";
|
|
10
|
-
export { DEFAULT_SKILL_NAME, planSkillInstall, planSkillUninstall, resolveBundledSkillPath, resolveSkillTargetPath, syncInstalledSkills } from "./skill-install.js";
|
|
10
|
+
export { FILE_SKILL_HARNESSES, DEFAULT_SKILL_NAME, removeDuplicateSkillInstalls, planSkillInstall, planSkillUninstall, planSharedSkillUninstall, resolveBundledSkillPath, resolveDuplicateSkillTargetPaths, resolveLegacyOpencodeSkillTargetPath, resolvePrimarySkillTargetPath, resolveSharedAgentsSkillsDir, resolveSharedSkillTargetPath, resolveSkillTargetPath, skillLoadingModel, syncInstalledSkills } from "./skill-install.js";
|
|
11
|
+
export { HARNESS_CLI_HARNESSES, HARNESS_COMMAND_MAPPING, HARNESS_SKILL_MODELS, isDeprecatedHarness } from "./harness-model.js";
|
|
11
12
|
export { readPackageVersion, readUpdateMigrationState, resolveUpdateMigrationStatePath, runFirstRunMcpMigration, runStaleMcpCleanup, writeUpdateMigrationState } from "./update-migration.js";
|
|
12
13
|
export { createSystemProcessInspector, terminateKnownProcess } from "./process-utils.js";
|
|
13
14
|
export { clearCliSessionLease, findCliSessionByRoom, findCliSessionForContextPath, readCliSessions, removeCliSession, removeCliSessionsForRoom, resolveCliSessionPath, upsertCliSession, upsertJoinedCliSession, writeCliSessions } from "./session-store.js";
|
|
@@ -20,12 +20,18 @@ export async function removeStaleMcpRegistrations(options) {
|
|
|
20
20
|
package_version_to: options.packageVersionTo,
|
|
21
21
|
harness,
|
|
22
22
|
config_path: result.configPath,
|
|
23
|
+
target_type: "mcp",
|
|
23
24
|
action: result.action,
|
|
24
25
|
server_name: result.serverName,
|
|
25
26
|
detail: result.message
|
|
26
27
|
});
|
|
27
28
|
}
|
|
28
|
-
return results.map(({ harness, action, message }) => ({
|
|
29
|
+
return results.map(({ harness, action, message }) => ({
|
|
30
|
+
harness,
|
|
31
|
+
action,
|
|
32
|
+
message,
|
|
33
|
+
target_type: "mcp"
|
|
34
|
+
}));
|
|
29
35
|
}
|
|
30
36
|
async function removeOneHarness(harness, installOptions, strict) {
|
|
31
37
|
const action = planUninstall(harness, installOptions);
|
package/dist/install.js
CHANGED
|
@@ -3,7 +3,8 @@ import fs from "node:fs";
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { writeFileAtomic } from "./atomic-write.js";
|
|
6
|
-
|
|
6
|
+
import { SUPPORTED_HARNESSES } from "./harness-model.js";
|
|
7
|
+
export { SUPPORTED_HARNESSES, isDeprecatedHarness } from "./harness-model.js";
|
|
7
8
|
export const DEFAULT_SERVER_NAME = "talking-stick";
|
|
8
9
|
// Legacy MCP command retained only to identify stale config entries for removal.
|
|
9
10
|
export const DEFAULT_SERVER_COMMAND = ["tt", "mcp"];
|
|
@@ -127,6 +128,8 @@ function resolveHarnessConfigDirFromResolved(harness, resolved) {
|
|
|
127
128
|
return path.join(resolved.homeDir, ".claude");
|
|
128
129
|
case "codex":
|
|
129
130
|
return path.join(resolved.homeDir, ".codex");
|
|
131
|
+
case "antigravity":
|
|
132
|
+
return path.join(resolved.homeDir, ".agents");
|
|
130
133
|
case "gemini":
|
|
131
134
|
return path.join(resolved.homeDir, ".gemini");
|
|
132
135
|
case "grok":
|
|
@@ -166,6 +169,8 @@ export function planUninstall(harness, options = {}) {
|
|
|
166
169
|
operation: "uninstall",
|
|
167
170
|
serverName: resolved.serverName
|
|
168
171
|
};
|
|
172
|
+
case "antigravity":
|
|
173
|
+
return skipAction(harness, "legacy Talking Stick cleanup is not applicable for antigravity");
|
|
169
174
|
case "gemini":
|
|
170
175
|
if (resolved.skipMissing && !resolved.hooks.which("gemini")) {
|
|
171
176
|
return skipAction(harness, "gemini not on PATH");
|
|
@@ -225,6 +230,7 @@ export function planGrokSessionHookInstall(options = {}) {
|
|
|
225
230
|
harness: "grok",
|
|
226
231
|
filePath,
|
|
227
232
|
description: `write Grok session hook ${filePath}`,
|
|
233
|
+
operation: "install",
|
|
228
234
|
inspect: () => inspectGrokSessionHook(filePath, resolved),
|
|
229
235
|
apply: () => writeGrokSessionHook(filePath, resolved)
|
|
230
236
|
};
|
|
@@ -381,6 +387,12 @@ export function detectHarness(harness, options = {}) {
|
|
|
381
387
|
return { harness, detected: true, evidence: configDir };
|
|
382
388
|
return { harness, detected: false, evidence: "codex not on PATH and no config directory" };
|
|
383
389
|
}
|
|
390
|
+
case "antigravity": {
|
|
391
|
+
const bin = resolved.hooks.which("agy");
|
|
392
|
+
if (bin)
|
|
393
|
+
return { harness, detected: true, evidence: bin };
|
|
394
|
+
return { harness, detected: false, evidence: "agy not on PATH" };
|
|
395
|
+
}
|
|
384
396
|
case "gemini": {
|
|
385
397
|
const bin = resolved.hooks.which("gemini");
|
|
386
398
|
if (bin)
|
|
@@ -569,6 +581,8 @@ export async function runAction(action, options = {}) {
|
|
|
569
581
|
}
|
|
570
582
|
}
|
|
571
583
|
async function inspectExecAction(action, resolved) {
|
|
584
|
+
if (action.inspect)
|
|
585
|
+
return action.inspect();
|
|
572
586
|
if (!action.operation || !action.serverName)
|
|
573
587
|
return "unknown";
|
|
574
588
|
if (action.harness === "gemini") {
|
|
@@ -615,6 +629,9 @@ function formatMcpActionMessage(action, status, fallback) {
|
|
|
615
629
|
break;
|
|
616
630
|
}
|
|
617
631
|
}
|
|
632
|
+
if (action.kind === "exec" && status === "already_present") {
|
|
633
|
+
return "skill is already installed.";
|
|
634
|
+
}
|
|
618
635
|
return fallback ?? "ok";
|
|
619
636
|
}
|
|
620
637
|
const target = `MCP server '${action.serverName}'`;
|
|
@@ -642,6 +659,8 @@ function mcpConfigLocation(action) {
|
|
|
642
659
|
return "Claude Code user config";
|
|
643
660
|
case "codex":
|
|
644
661
|
return "Codex global config";
|
|
662
|
+
case "antigravity":
|
|
663
|
+
return "Antigravity shared config";
|
|
645
664
|
case "gemini":
|
|
646
665
|
return "Gemini user config";
|
|
647
666
|
case "grok":
|
package/dist/instructions.js
CHANGED
|
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { resolveDataDir } from "./config.js";
|
|
5
|
+
import { HARNESS_CLI_HARNESSES } from "./harness-model.js";
|
|
5
6
|
import { resolveContextPath } from "./path-resolution.js";
|
|
6
7
|
export const DEFAULT_MAX_INSTRUCTION_FILE_BYTES = 256 * 1024;
|
|
7
8
|
export const DEFAULT_INSTRUCTIONS_MARKDOWN = `# Talking Stick collaboration instructions
|
|
@@ -12,7 +13,7 @@ On freshly invoked multi-agent tasks, give peers a short window to join before d
|
|
|
12
13
|
|
|
13
14
|
Use phase names in handoffs when they clarify the work: draft, adversarial review, convergence, implementation, implementation review, test review, and release. These phases are vocabulary, not protocol state.
|
|
14
15
|
|
|
15
|
-
Claude and Codex are peers of comparable capability; neither outranks the other. Split work evenly between them rather than routing by stereotype, and have all models plan, implement, and evaluate together: any harness can draft, review, converge, implement, or release.
|
|
16
|
+
Claude and Codex are peers of comparable capability; neither outranks the other. Split work evenly between them rather than routing by stereotype, and have all models plan, implement, and evaluate together: any harness can draft, review, converge, implement, or release. Antigravity and OpenCode start with conservative local guidance until project dogfood says otherwise.
|
|
16
17
|
|
|
17
18
|
For multi-agent design work, prefer independent read-only drafts first, then adversarial review and convergence. Do not impose a draft file structure on the workspace by default. If scratch draft files are useful, delete superseded pre-convergence drafts after the converged plan exists unless the operator asks to keep them.
|
|
18
19
|
|
|
@@ -26,9 +27,9 @@ Take a full, even share of planning, implementation, and evaluation. Watch for s
|
|
|
26
27
|
|
|
27
28
|
Take a full, even share of planning, implementation, and evaluation. Watch for over-indexing on mechanics when the operator still needs to decide direction. Make the next phase explicit in the handoff.
|
|
28
29
|
|
|
29
|
-
##
|
|
30
|
+
## Antigravity
|
|
30
31
|
|
|
31
|
-
Use broad context review and exploration conservatively until the project has stronger
|
|
32
|
+
Use broad context review and exploration conservatively until the project has stronger Antigravity-specific dogfood. Keep handoffs concrete and do not assume responsibility that the operator assigned to another harness.
|
|
32
33
|
|
|
33
34
|
## Grok
|
|
34
35
|
|
|
@@ -38,11 +39,17 @@ Use Grok Build as a first-class local coding harness. Keep coordination safety a
|
|
|
38
39
|
|
|
39
40
|
Use terminal-native local exploration and implementation conservatively until the project has stronger OpenCode-specific dogfood. Keep coordination safety ahead of speed.
|
|
40
41
|
`;
|
|
41
|
-
const
|
|
42
|
+
export const INSTRUCTION_HARNESSES = [
|
|
43
|
+
...HARNESS_CLI_HARNESSES,
|
|
44
|
+
"all"
|
|
45
|
+
];
|
|
46
|
+
export const HARNESS_ALIASES = {
|
|
42
47
|
all: "all",
|
|
43
48
|
base: "all",
|
|
44
49
|
claude: "claude",
|
|
45
50
|
"claude-code": "claude",
|
|
51
|
+
antigravity: "antigravity",
|
|
52
|
+
agy: "antigravity",
|
|
46
53
|
codex: "codex",
|
|
47
54
|
gemini: "gemini",
|
|
48
55
|
grok: "grok",
|
|
@@ -114,7 +121,7 @@ export function resolveInstructionHarness(explicitHarness, identity) {
|
|
|
114
121
|
export function normalizeInstructionHarness(value) {
|
|
115
122
|
const normalized = HARNESS_ALIASES[normalizeKey(value)];
|
|
116
123
|
if (!normalized) {
|
|
117
|
-
throw new Error(`--harness must be one of claude, codex, gemini, grok, opencode, all (got ${value}).`);
|
|
124
|
+
throw new Error(`--harness must be one of claude, codex, antigravity, gemini, grok, opencode, all (got ${value}).`);
|
|
118
125
|
}
|
|
119
126
|
return normalized;
|
|
120
127
|
}
|
|
@@ -219,6 +226,8 @@ function parseHarnessHeader(line) {
|
|
|
219
226
|
return "claude";
|
|
220
227
|
if (key.startsWith("codex"))
|
|
221
228
|
return "codex";
|
|
229
|
+
if (key.startsWith("antigravity") || key === "agy")
|
|
230
|
+
return "antigravity";
|
|
222
231
|
if (key.startsWith("gemini"))
|
|
223
232
|
return "gemini";
|
|
224
233
|
if (key.startsWith("grok"))
|