talking-stick 0.1.4 → 0.3.0
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 +56 -43
- package/dist/cli/event-stream.js +124 -0
- package/dist/cli/install-commands.js +76 -36
- package/dist/cli/msg-commands.js +81 -0
- package/dist/cli/output.js +5 -3
- package/dist/cli/registry.js +24 -33
- package/dist/cli/room-commands.js +13 -2
- package/dist/cli/startup-maintenance.js +27 -1
- package/dist/cli.js +2 -2
- package/dist/commands.js +15 -0
- package/dist/config.js +4 -1
- package/dist/db.js +7 -0
- package/dist/identity.js +4 -4
- package/dist/index.js +2 -2
- package/dist/install-audit.js +21 -0
- package/dist/install-migration.js +84 -0
- package/dist/install.js +0 -69
- package/dist/service.js +161 -4
- package/dist/update-migration.js +135 -0
- package/docs/plans/2026-05-04-diff-walker-design.md +585 -0
- package/docs/plans/2026-05-05-cli-only-coordination.md +224 -0
- package/docs/plans/out-of-band-signaling-implementation.md +854 -0
- package/docs/plans/out-of-band-signaling.md +255 -176
- package/docs/receive-consumer-contract.md +32 -0
- package/docs/releases/0.2.0.md +85 -0
- package/docs/releases/0.3.0.md +77 -0
- package/docs/talking-stick-plan.md +3 -2
- package/package.json +4 -3
- package/scripts/postinstall-mcp-cleanup.cjs +25 -0
- package/skills/talking-stick/SKILL.md +131 -88
- package/dist/mcp-server.js +0 -212
- package/dist/server.js +0 -3
package/dist/cli/registry.js
CHANGED
|
@@ -1,19 +1,10 @@
|
|
|
1
|
-
import { runStdioServer } from "../index.js";
|
|
2
1
|
import { runGuardCommand } from "./guardian.js";
|
|
3
|
-
import { runInstallCommand,
|
|
2
|
+
import { runInstallCommand, runMcpMigrationCommand, runSelfUpdateCommand, runUninstallCommand } from "./install-commands.js";
|
|
3
|
+
import { handleMsgCommand } from "./msg-commands.js";
|
|
4
4
|
import { handleNotesCommand } from "./notes-commands.js";
|
|
5
5
|
import { handleEventsCommand, handleJoinCommand, handleKickCommand, handleLeaveCommand, handleListCommand, handleStateCommand, handleWhoAmICommand } from "./room-commands.js";
|
|
6
6
|
import { handleAssignCommand, handlePassCommand, handleReleaseCommand, handleTakeCommand, handleWaitCommand } from "./turn-commands.js";
|
|
7
7
|
export const COMMAND_REGISTRY = [
|
|
8
|
-
{
|
|
9
|
-
name: "mcp",
|
|
10
|
-
needsRuntime: false,
|
|
11
|
-
startupMaintenance: false,
|
|
12
|
-
internal: true,
|
|
13
|
-
usage: "tt mcp",
|
|
14
|
-
description: "Run the MCP server over stdio.",
|
|
15
|
-
handler: () => runStdioServer()
|
|
16
|
-
},
|
|
17
8
|
{
|
|
18
9
|
name: "guard",
|
|
19
10
|
needsRuntime: false,
|
|
@@ -29,7 +20,7 @@ export const COMMAND_REGISTRY = [
|
|
|
29
20
|
startupMaintenance: false,
|
|
30
21
|
internal: false,
|
|
31
22
|
usage: "tt install <harness...> | --all [--print] [--copy] [--link]",
|
|
32
|
-
description: "Install Talking Stick
|
|
23
|
+
description: "Install the Talking Stick skill and remove stale MCP registrations.",
|
|
33
24
|
handler: ({ parsed }) => runInstallCommand(parsed)
|
|
34
25
|
},
|
|
35
26
|
{
|
|
@@ -38,35 +29,26 @@ export const COMMAND_REGISTRY = [
|
|
|
38
29
|
startupMaintenance: false,
|
|
39
30
|
internal: false,
|
|
40
31
|
usage: "tt uninstall <harness...> | --all [--print]",
|
|
41
|
-
description: "Remove Talking Stick
|
|
32
|
+
description: "Remove the Talking Stick skill and stale MCP registrations.",
|
|
42
33
|
handler: ({ parsed }) => runUninstallCommand(parsed)
|
|
43
34
|
},
|
|
44
35
|
{
|
|
45
|
-
name: "
|
|
36
|
+
name: "self-update",
|
|
46
37
|
needsRuntime: false,
|
|
47
38
|
startupMaintenance: false,
|
|
48
39
|
internal: false,
|
|
49
|
-
usage: "tt
|
|
50
|
-
description: "
|
|
51
|
-
handler: ({ parsed }) =>
|
|
40
|
+
usage: "tt self-update [--print] [--manager npm|pnpm|yarn|bun]",
|
|
41
|
+
description: "Update the globally installed tt package.",
|
|
42
|
+
handler: ({ parsed, cliEntryUrl }) => runSelfUpdateCommand(parsed, cliEntryUrl)
|
|
52
43
|
},
|
|
53
44
|
{
|
|
54
|
-
name: "
|
|
45
|
+
name: "migrate-mcp",
|
|
55
46
|
needsRuntime: false,
|
|
56
47
|
startupMaintenance: false,
|
|
57
|
-
internal:
|
|
58
|
-
usage: "tt
|
|
59
|
-
description: "Remove
|
|
60
|
-
handler: ({ parsed }) =>
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
name: "self-update",
|
|
64
|
-
needsRuntime: false,
|
|
65
|
-
startupMaintenance: true,
|
|
66
|
-
internal: false,
|
|
67
|
-
usage: "tt self-update [--print] [--manager npm|pnpm|yarn|bun]",
|
|
68
|
-
description: "Update the globally installed tt package.",
|
|
69
|
-
handler: ({ parsed, cliEntryUrl }) => runSelfUpdateCommand(parsed, cliEntryUrl)
|
|
48
|
+
internal: true,
|
|
49
|
+
usage: "tt migrate-mcp [--reason update|first-run|uninstall|manual] [--quiet]",
|
|
50
|
+
description: "Remove stale Talking Stick MCP registrations.",
|
|
51
|
+
handler: ({ parsed }) => runMcpMigrationCommand(parsed)
|
|
70
52
|
},
|
|
71
53
|
{
|
|
72
54
|
name: "whoami",
|
|
@@ -127,16 +109,25 @@ export const COMMAND_REGISTRY = [
|
|
|
127
109
|
needsRuntime: true,
|
|
128
110
|
startupMaintenance: true,
|
|
129
111
|
internal: false,
|
|
130
|
-
usage: "tt events [path] [--after N] [--limit N]",
|
|
112
|
+
usage: "tt events [path] [--after N] [--limit N] [--wait|--follow] [--event TYPE[,TYPE]] [--target self|any|agent]",
|
|
131
113
|
description: "Show room events.",
|
|
132
114
|
handler: ({ runtime, parsed }) => handleEventsCommand(requireRuntime(runtime), parsed)
|
|
133
115
|
},
|
|
116
|
+
{
|
|
117
|
+
name: "msg",
|
|
118
|
+
needsRuntime: true,
|
|
119
|
+
startupMaintenance: true,
|
|
120
|
+
internal: false,
|
|
121
|
+
usage: "tt msg <send|recv> [...]",
|
|
122
|
+
description: "Send or receive transient messages on a room's event stream.",
|
|
123
|
+
handler: ({ runtime, parsed }) => handleMsgCommand(requireRuntime(runtime), parsed)
|
|
124
|
+
},
|
|
134
125
|
{
|
|
135
126
|
name: "wait",
|
|
136
127
|
needsRuntime: true,
|
|
137
128
|
startupMaintenance: true,
|
|
138
129
|
internal: false,
|
|
139
|
-
usage: "tt wait [path] [--timeout
|
|
130
|
+
usage: "tt wait [path] [--timeout 110s]",
|
|
140
131
|
description: "Wait until this agent can claim the stick.",
|
|
141
132
|
handler: ({ runtime, parsed, cliEntryUrl }) => handleWaitCommand(requireRuntime(runtime), parsed, false, cliEntryUrl)
|
|
142
133
|
},
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { deriveCliIdentity, resolveCliIdentity } from "./identity.js";
|
|
2
2
|
import { removeCliSession, removeCliSessionsForRoom, resolveCliSessionPath } from "../index.js";
|
|
3
3
|
import { stopGuardian } from "./guardian.js";
|
|
4
|
-
import { getStringOption, hasOption, parseOptionalInteger } from "./parser.js";
|
|
4
|
+
import { getStringOption, hasOption, normalizeBooleanFlag, parseOptionalInteger } from "./parser.js";
|
|
5
5
|
import { formatRelativeTime, printResult } from "./output.js";
|
|
6
|
+
import { parseEventTypeFilter, runEventStream } from "./event-stream.js";
|
|
6
7
|
import { resolveSessionForReads, upsertSessionFromJoin } from "./session.js";
|
|
7
8
|
export function handleListCommand(runtime, parsed) {
|
|
8
9
|
const contextPath = parsed.positionals[0] ?? process.cwd();
|
|
@@ -126,9 +127,19 @@ export function handleStateCommand(runtime, parsed) {
|
|
|
126
127
|
return lines.join("\n");
|
|
127
128
|
});
|
|
128
129
|
}
|
|
129
|
-
export function handleEventsCommand(runtime, parsed) {
|
|
130
|
+
export async function handleEventsCommand(runtime, parsed) {
|
|
131
|
+
normalizeBooleanFlag(parsed, "wait");
|
|
132
|
+
normalizeBooleanFlag(parsed, "follow");
|
|
130
133
|
const identity = deriveCliIdentity(parsed);
|
|
131
134
|
const session = resolveSessionForReads(runtime, parsed, identity);
|
|
135
|
+
if (hasOption(parsed, "wait") || hasOption(parsed, "follow")) {
|
|
136
|
+
await runEventStream(runtime, parsed, identity, session.room_id, {
|
|
137
|
+
event_type: parseEventTypeFilter(getStringOption(parsed, "event")),
|
|
138
|
+
default_target: "self",
|
|
139
|
+
force_tail_cursor: false
|
|
140
|
+
});
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
132
143
|
const events = runtime.commands.getRoomEvents({
|
|
133
144
|
room_id: session.room_id,
|
|
134
145
|
agent_id: identity.agent_id,
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import { syncInstalledSkills } from "../skill-install.js";
|
|
2
|
+
import { runFirstRunMcpMigration } from "../update-migration.js";
|
|
3
|
+
import { detectInstallSource, resolveCurrentBinaryPath } from "../self-update.js";
|
|
2
4
|
import { isKnownHarnessCliEnv } from "./identity.js";
|
|
3
5
|
import { getCommand } from "./registry.js";
|
|
4
|
-
export function runStartupMaintenance(parsed, env = process.env) {
|
|
6
|
+
export async function runStartupMaintenance(parsed, cliEntryUrl, env = process.env) {
|
|
7
|
+
if (shouldRunFirstRunMcpMigration(parsed, cliEntryUrl, env)) {
|
|
8
|
+
try {
|
|
9
|
+
await runFirstRunMcpMigration({
|
|
10
|
+
installOptions: { env }
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// Startup cleanup is best-effort. Explicit install, uninstall, and
|
|
15
|
+
// self-update paths surface cleanup failures directly.
|
|
16
|
+
}
|
|
17
|
+
}
|
|
5
18
|
if (!shouldAutoSyncInstalledSkills(parsed, env)) {
|
|
6
19
|
return;
|
|
7
20
|
}
|
|
@@ -13,6 +26,19 @@ export function runStartupMaintenance(parsed, env = process.env) {
|
|
|
13
26
|
// unrelated tt command fail.
|
|
14
27
|
}
|
|
15
28
|
}
|
|
29
|
+
export function shouldRunFirstRunMcpMigration(parsed, cliEntryUrl, env = process.env) {
|
|
30
|
+
if (env.TALKING_STICK_DISABLE_MCP_MIGRATION?.trim()) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const command = getCommand(parsed.name);
|
|
34
|
+
if (!command?.startupMaintenance) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const source = detectInstallSource({
|
|
38
|
+
binaryPath: resolveCurrentBinaryPath(cliEntryUrl)
|
|
39
|
+
});
|
|
40
|
+
return source !== "dev" && source !== "unknown";
|
|
41
|
+
}
|
|
16
42
|
export function shouldAutoSyncInstalledSkills(parsed, env = process.env) {
|
|
17
43
|
if (env.TALKING_STICK_DISABLE_SKILL_SYNC?.trim()) {
|
|
18
44
|
return false;
|
package/dist/cli.js
CHANGED
|
@@ -11,10 +11,10 @@ import { runStartupMaintenance } from "./cli/startup-maintenance.js";
|
|
|
11
11
|
export { checkGuardianLiveness } from "./cli/guardian.js";
|
|
12
12
|
export { parseHandoffJson } from "./cli/handoff.js";
|
|
13
13
|
export { formatRelativeTime, shouldUseJson } from "./cli/output.js";
|
|
14
|
-
export { shouldAutoSyncInstalledSkills } from "./cli/startup-maintenance.js";
|
|
14
|
+
export { shouldAutoSyncInstalledSkills, shouldRunFirstRunMcpMigration } from "./cli/startup-maintenance.js";
|
|
15
15
|
export async function runCli(argv = process.argv.slice(2)) {
|
|
16
16
|
const parsed = parseCommand(argv);
|
|
17
|
-
runStartupMaintenance(parsed);
|
|
17
|
+
await runStartupMaintenance(parsed, import.meta.url);
|
|
18
18
|
if (!parsed.name || parsed.name === "help" || parsed.name === "--help") {
|
|
19
19
|
printHelp();
|
|
20
20
|
return;
|
package/dist/commands.js
CHANGED
|
@@ -82,6 +82,21 @@ export class TalkingStickCommands {
|
|
|
82
82
|
getRoomEvents(input) {
|
|
83
83
|
return this.service.getRoomEvents(input);
|
|
84
84
|
}
|
|
85
|
+
sendMessage(identity, input) {
|
|
86
|
+
return this.service.sendMessage({
|
|
87
|
+
agent_id: identity.agent_id,
|
|
88
|
+
room_id: input.room_id,
|
|
89
|
+
body: input.body,
|
|
90
|
+
to_agent_id: input.to_agent_id,
|
|
91
|
+
delivery_hint: input.delivery_hint
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
waitForEvents(input) {
|
|
95
|
+
return this.service.waitForEvents(input);
|
|
96
|
+
}
|
|
97
|
+
getLatestEventSeq(input) {
|
|
98
|
+
return this.service.getLatestEventSeq(input);
|
|
99
|
+
}
|
|
85
100
|
addNote(identity, input) {
|
|
86
101
|
return this.service.addNote({
|
|
87
102
|
agent_id: identity.agent_id,
|
package/dist/config.js
CHANGED
|
@@ -4,8 +4,11 @@ export const defaultPolicy = {
|
|
|
4
4
|
ownerLeaseTtlMs: 45 * 60 * 1000,
|
|
5
5
|
heartbeatIntervalMs: 5 * 60 * 1000,
|
|
6
6
|
claimTtlMs: 20 * 60 * 1000,
|
|
7
|
-
waitForTurnMaxWaitMs:
|
|
7
|
+
waitForTurnMaxWaitMs: 110 * 1000,
|
|
8
8
|
waitForTurnPollMs: 250,
|
|
9
|
+
waitForEventsMaxWaitMs: 110 * 1000,
|
|
10
|
+
waitForEventsPollMs: 250,
|
|
11
|
+
waitForEventsBatchLimit: 100,
|
|
9
12
|
presenceTtlMs: 4 * 60 * 60 * 1000,
|
|
10
13
|
waiterGraceMs: 10 * 1000,
|
|
11
14
|
idleRoomTtlMs: 7 * 24 * 60 * 60 * 1000
|
package/dist/db.js
CHANGED
|
@@ -96,6 +96,13 @@ const migrations = [
|
|
|
96
96
|
up: `
|
|
97
97
|
ALTER TABLE room_members ADD COLUMN last_wait_at TEXT;
|
|
98
98
|
`
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: 5,
|
|
102
|
+
name: "room_events_payload_json",
|
|
103
|
+
up: `
|
|
104
|
+
ALTER TABLE room_events ADD COLUMN payload_json TEXT;
|
|
105
|
+
`
|
|
99
106
|
}
|
|
100
107
|
];
|
|
101
108
|
export function resolveDatabasePath(options = {}) {
|
package/dist/identity.js
CHANGED
|
@@ -127,13 +127,13 @@ function harnessAgentId(harness, sessionId, hostId, username) {
|
|
|
127
127
|
function resolveHarnessSessionId(signal, env, parentPid, parentInspection, username, hostId, inspector) {
|
|
128
128
|
if (signal.sessionId)
|
|
129
129
|
return `harness:${signal.sessionId}`;
|
|
130
|
-
const terminalId = resolveTerminalSessionId(env);
|
|
131
|
-
if (terminalId)
|
|
132
|
-
return terminalId;
|
|
133
130
|
const harnessRoot = findHarnessRootInAncestry(signal.harness, parentPid, parentInspection, inspector);
|
|
134
131
|
if (harnessRoot) {
|
|
135
132
|
return `pid:${harnessRoot.pid}@${harnessRoot.startTime}`;
|
|
136
133
|
}
|
|
134
|
+
const terminalId = resolveTerminalSessionId(env);
|
|
135
|
+
if (terminalId)
|
|
136
|
+
return terminalId;
|
|
137
137
|
if (parentInspection?.startTime) {
|
|
138
138
|
return `pid:${parentPid}@${parentInspection.startTime}`;
|
|
139
139
|
}
|
|
@@ -224,7 +224,7 @@ function detectHarnessSignal(env) {
|
|
|
224
224
|
if (env.CLAUDECODE === "1") {
|
|
225
225
|
return {
|
|
226
226
|
harness: "claude",
|
|
227
|
-
sessionId:
|
|
227
|
+
sessionId: nonEmpty(env.CLAUDE_CODE_SESSION_ID),
|
|
228
228
|
pidHint: parsePositiveInteger(env.CMUX_CLAUDE_PID)
|
|
229
229
|
};
|
|
230
230
|
}
|
package/dist/index.js
CHANGED
|
@@ -4,9 +4,9 @@ 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 {
|
|
8
|
-
export { SUPPORTED_HARNESSES, MissingHarnessError, detectHarness, parseHarnessList, planInstall, planUninstall, resolveHarnessConfigDir, resolveOpencodeConfigDir, resolveOpencodeConfigPath, runAction, skipAction } from "./install.js";
|
|
7
|
+
export { SUPPORTED_HARNESSES, MissingHarnessError, detectHarness, parseHarnessList, planUninstall, resolveHarnessConfigDir, resolveOpencodeConfigDir, resolveOpencodeConfigPath, runAction, skipAction } from "./install.js";
|
|
9
8
|
export { DEFAULT_SKILL_NAME, planSkillInstall, planSkillUninstall, resolveBundledSkillPath, resolveSkillTargetPath, syncInstalledSkills } from "./skill-install.js";
|
|
9
|
+
export { readPackageVersion, readUpdateMigrationState, resolveUpdateMigrationStatePath, runFirstRunMcpMigration, runStaleMcpCleanup, writeUpdateMigrationState } from "./update-migration.js";
|
|
10
10
|
export { createSystemProcessInspector, terminateKnownProcess } from "./process-utils.js";
|
|
11
11
|
export { clearCliSessionLease, findCliSessionByRoom, findCliSessionForContextPath, readCliSessions, removeCliSession, removeCliSessionsForRoom, resolveCliSessionPath, upsertCliSession, upsertJoinedCliSession, writeCliSessions } from "./session-store.js";
|
|
12
12
|
export { TalkingStickService, createDefaultProcessLivenessChecker } from "./service.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export class FileAuditLog {
|
|
4
|
+
filePath;
|
|
5
|
+
constructor(filePath) {
|
|
6
|
+
this.filePath = filePath;
|
|
7
|
+
}
|
|
8
|
+
append(entry) {
|
|
9
|
+
const fullEntry = { ts: entry.ts ?? new Date().toISOString(), ...entry };
|
|
10
|
+
fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
|
|
11
|
+
fs.appendFileSync(this.filePath, `${JSON.stringify(fullEntry)}\n`, "utf8");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class NoopAuditLog {
|
|
15
|
+
append() {
|
|
16
|
+
// intentionally blank
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function defaultAuditLogPath(dataDir) {
|
|
20
|
+
return path.join(dataDir, "update-migrations.log");
|
|
21
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { SUPPORTED_HARNESSES, planUninstall, runAction } from "./install.js";
|
|
2
|
+
import { NoopAuditLog } from "./install-audit.js";
|
|
3
|
+
export async function removeStaleMcpRegistrations(options) {
|
|
4
|
+
const audit = options.audit ?? new NoopAuditLog();
|
|
5
|
+
const strict = options.strict ?? true;
|
|
6
|
+
const harnesses = options.harnesses === undefined || options.harnesses === "all"
|
|
7
|
+
? [...SUPPORTED_HARNESSES]
|
|
8
|
+
: options.harnesses;
|
|
9
|
+
const installOptions = {
|
|
10
|
+
skipMissing: true,
|
|
11
|
+
...(options.installOptions ?? {})
|
|
12
|
+
};
|
|
13
|
+
const results = [];
|
|
14
|
+
for (const harness of harnesses) {
|
|
15
|
+
const result = await removeOneHarness(harness, installOptions, strict);
|
|
16
|
+
results.push(result);
|
|
17
|
+
audit.append({
|
|
18
|
+
reason: options.reason,
|
|
19
|
+
package_version_from: options.packageVersionFrom,
|
|
20
|
+
package_version_to: options.packageVersionTo,
|
|
21
|
+
harness,
|
|
22
|
+
config_path: result.configPath,
|
|
23
|
+
action: result.action,
|
|
24
|
+
server_name: result.serverName,
|
|
25
|
+
detail: result.message
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return results.map(({ harness, action, message }) => ({ harness, action, message }));
|
|
29
|
+
}
|
|
30
|
+
async function removeOneHarness(harness, installOptions, strict) {
|
|
31
|
+
const action = planUninstall(harness, installOptions);
|
|
32
|
+
if (action.kind === "skip") {
|
|
33
|
+
return {
|
|
34
|
+
harness,
|
|
35
|
+
action: "skipped",
|
|
36
|
+
message: action.message,
|
|
37
|
+
serverName: installOptions.serverName ?? "talking-stick"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (action.kind === "file-patch") {
|
|
41
|
+
const state = action.inspect ? action.inspect() : "unknown";
|
|
42
|
+
const serverName = action.serverName ?? "talking-stick";
|
|
43
|
+
if (state === "absent") {
|
|
44
|
+
return {
|
|
45
|
+
harness,
|
|
46
|
+
action: "absent",
|
|
47
|
+
message: `${harness}: no Talking Stick MCP entry to remove`,
|
|
48
|
+
configPath: action.filePath,
|
|
49
|
+
serverName
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (strict && state !== "present") {
|
|
53
|
+
return {
|
|
54
|
+
harness,
|
|
55
|
+
action: "preserved",
|
|
56
|
+
message: `${harness}: hand-edited entry left alone (state=${state})`,
|
|
57
|
+
configPath: action.filePath,
|
|
58
|
+
serverName
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const installResult = await runAction(action, installOptions);
|
|
63
|
+
return mapInstallResult(harness, action, installResult);
|
|
64
|
+
}
|
|
65
|
+
function mapInstallResult(harness, action, result) {
|
|
66
|
+
let serverName = "talking-stick";
|
|
67
|
+
if ("serverName" in action && typeof action.serverName === "string") {
|
|
68
|
+
serverName = action.serverName;
|
|
69
|
+
}
|
|
70
|
+
const configPath = action.kind === "file-patch" ? action.filePath : undefined;
|
|
71
|
+
if (!result.ok) {
|
|
72
|
+
return { harness, action: "failed", message: result.message, configPath, serverName };
|
|
73
|
+
}
|
|
74
|
+
switch (result.status) {
|
|
75
|
+
case "already_absent":
|
|
76
|
+
return { harness, action: "absent", message: result.message, configPath, serverName };
|
|
77
|
+
case "removed":
|
|
78
|
+
return { harness, action: "removed", message: result.message, configPath, serverName };
|
|
79
|
+
case "skipped":
|
|
80
|
+
return { harness, action: "skipped", message: result.message, configPath, serverName };
|
|
81
|
+
default:
|
|
82
|
+
return { harness, action: "failed", message: result.message, configPath, serverName };
|
|
83
|
+
}
|
|
84
|
+
}
|
package/dist/install.js
CHANGED
|
@@ -115,75 +115,6 @@ function resolveHarnessConfigDirFromResolved(harness, resolved) {
|
|
|
115
115
|
throw new Error(`Unknown harness: ${harness}`);
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
-
export function planInstall(harness, options = {}) {
|
|
119
|
-
const resolved = resolveOptions(options);
|
|
120
|
-
const [serverBin, ...serverArgs] = resolved.serverCommand;
|
|
121
|
-
if (!serverBin)
|
|
122
|
-
throw new Error("serverCommand must include at least the binary");
|
|
123
|
-
switch (harness) {
|
|
124
|
-
case "claude-code":
|
|
125
|
-
if (resolved.skipMissing && !resolved.hooks.which("claude")) {
|
|
126
|
-
return skipAction(harness, "claude not on PATH");
|
|
127
|
-
}
|
|
128
|
-
return {
|
|
129
|
-
kind: "exec",
|
|
130
|
-
harness,
|
|
131
|
-
command: "claude",
|
|
132
|
-
args: ["mcp", "add", "-s", "user", resolved.serverName, "--", serverBin, ...serverArgs],
|
|
133
|
-
description: `claude mcp add -s user ${resolved.serverName} -- ${resolved.serverCommand.join(" ")}`,
|
|
134
|
-
operation: "install",
|
|
135
|
-
serverName: resolved.serverName,
|
|
136
|
-
serverCommand: resolved.serverCommand
|
|
137
|
-
};
|
|
138
|
-
case "codex":
|
|
139
|
-
if (resolved.skipMissing && !resolved.hooks.which("codex")) {
|
|
140
|
-
return skipAction(harness, "codex not on PATH");
|
|
141
|
-
}
|
|
142
|
-
return {
|
|
143
|
-
kind: "exec",
|
|
144
|
-
harness,
|
|
145
|
-
command: "codex",
|
|
146
|
-
args: ["mcp", "add", resolved.serverName, "--", serverBin, ...serverArgs],
|
|
147
|
-
description: `codex mcp add ${resolved.serverName} -- ${resolved.serverCommand.join(" ")}`,
|
|
148
|
-
operation: "install",
|
|
149
|
-
serverName: resolved.serverName,
|
|
150
|
-
serverCommand: resolved.serverCommand
|
|
151
|
-
};
|
|
152
|
-
case "gemini":
|
|
153
|
-
if (resolved.skipMissing && !resolved.hooks.which("gemini")) {
|
|
154
|
-
return skipAction(harness, "gemini not on PATH");
|
|
155
|
-
}
|
|
156
|
-
return {
|
|
157
|
-
kind: "exec",
|
|
158
|
-
harness,
|
|
159
|
-
command: "gemini",
|
|
160
|
-
args: ["mcp", "add", "-s", "user", "-t", "stdio", resolved.serverName, serverBin, ...serverArgs],
|
|
161
|
-
description: `gemini mcp add -s user -t stdio ${resolved.serverName} ${resolved.serverCommand.join(" ")}`,
|
|
162
|
-
operation: "install",
|
|
163
|
-
serverName: resolved.serverName,
|
|
164
|
-
serverCommand: resolved.serverCommand
|
|
165
|
-
};
|
|
166
|
-
case "opencode": {
|
|
167
|
-
const filePath = resolveOpencodeConfigPath(options);
|
|
168
|
-
const configDir = path.dirname(filePath);
|
|
169
|
-
if (resolved.skipMissing && !resolved.hooks.pathExists(configDir)) {
|
|
170
|
-
return skipAction(harness, `opencode config directory not found: ${configDir}`);
|
|
171
|
-
}
|
|
172
|
-
return {
|
|
173
|
-
kind: "file-patch",
|
|
174
|
-
harness,
|
|
175
|
-
filePath,
|
|
176
|
-
description: `merge mcp.${resolved.serverName} into ${filePath}`,
|
|
177
|
-
operation: "install",
|
|
178
|
-
serverName: resolved.serverName,
|
|
179
|
-
inspect: () => inspectOpencodeConfig(filePath, resolved),
|
|
180
|
-
apply: () => patchOpencodeConfig(filePath, resolved, "install")
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
default:
|
|
184
|
-
throw new Error(`Unknown harness: ${harness}`);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
118
|
export function planUninstall(harness, options = {}) {
|
|
188
119
|
const resolved = resolveOptions(options);
|
|
189
120
|
switch (harness) {
|