@tiny-fish/cli 0.25.1-next.216 → 0.25.2-next.220
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/dist/commands/connect.js
CHANGED
|
@@ -88,9 +88,7 @@ async function connectNativeMcpClient(client, options) {
|
|
|
88
88
|
const addArgs = keyedAdd
|
|
89
89
|
? keyedAdd.spec.addArgs(mcpUrl.toString(), keyedAdd.key)
|
|
90
90
|
: client.addArgs(mcpUrl.toString(), options.mcpUrl);
|
|
91
|
-
const addResult = spawn.sync(client.command, addArgs, {
|
|
92
|
-
stdio: "inherit",
|
|
93
|
-
});
|
|
91
|
+
const addResult = spawn.sync(client.command, addArgs, { stdio: "inherit" });
|
|
94
92
|
if (addResult.error || addResult.status !== 0) {
|
|
95
93
|
throw spawnStepError(`Could not add TinyFish to ${client.displayName}`, addResult);
|
|
96
94
|
}
|
|
@@ -111,18 +109,19 @@ async function connectNativeMcpClient(client, options) {
|
|
|
111
109
|
// A probed client signs in again only when its inline OAuth is known to have been abandoned.
|
|
112
110
|
const retryLogin = client.probeAuthenticated ? addSignedIn === false : true;
|
|
113
111
|
const pendingLogin = !useKeyAuth && retryLogin ? loginArgs : undefined;
|
|
112
|
+
let signInDeferred = !useKeyAuth && !addSignedIn && !pendingLogin;
|
|
114
113
|
if (pendingLogin) {
|
|
115
114
|
state.stage = "client_oauth";
|
|
116
115
|
errLine("Signing in to TinyFish...");
|
|
117
|
-
const loginResult = spawn.sync(client.command, pendingLogin, {
|
|
118
|
-
stdio: "inherit",
|
|
119
|
-
});
|
|
116
|
+
const loginResult = spawn.sync(client.command, pendingLogin, { stdio: "inherit" });
|
|
120
117
|
if (loginResult.error || loginResult.status !== 0) {
|
|
121
118
|
throw spawnStepError(`Could not authenticate TinyFish in ${client.displayName}`, loginResult);
|
|
122
119
|
}
|
|
123
|
-
|
|
120
|
+
if (client.probeAuthenticated?.() === false)
|
|
121
|
+
signInDeferred = true;
|
|
122
|
+
else
|
|
123
|
+
telemetry.track("checkpoint", { phase: "oauth_done" });
|
|
124
124
|
}
|
|
125
|
-
const signInDeferred = !useKeyAuth && !addSignedIn && !pendingLogin;
|
|
126
125
|
const authMode = useKeyAuth
|
|
127
126
|
? AuthMode.ApiKey
|
|
128
127
|
: signInDeferred
|
|
@@ -212,7 +211,10 @@ function runCliInstallStep(options, telemetry) {
|
|
|
212
211
|
throw firstTake ? outcome.error : new HoistedFailureReportedError(outcome.error.message);
|
|
213
212
|
}
|
|
214
213
|
if (firstTake) {
|
|
215
|
-
telemetry.track("checkpoint", {
|
|
214
|
+
telemetry.track("checkpoint", {
|
|
215
|
+
phase: "cli_installed",
|
|
216
|
+
stageDurationMs: outcome.durationMs,
|
|
217
|
+
});
|
|
216
218
|
}
|
|
217
219
|
return;
|
|
218
220
|
}
|
|
@@ -231,7 +233,7 @@ function trackPostInstallFailure(displayName, state, telemetry, error) {
|
|
|
231
233
|
if (!(error instanceof HoistedFailureReportedError)) {
|
|
232
234
|
telemetry.track("post_install_failed", {
|
|
233
235
|
failedStage: state.stage,
|
|
234
|
-
|
|
236
|
+
failureReason: error instanceof ConnectStepError ? error.failureReason : "unexpected_error",
|
|
235
237
|
});
|
|
236
238
|
}
|
|
237
239
|
errLine(`The ${displayName} MCP connection succeeded, but a finishing step ` +
|
|
@@ -53,5 +53,5 @@ export declare const OPENCODE: NativeMcpClient;
|
|
|
53
53
|
/** Every native MCP client. Derive from this, so a new harness cannot be missed. */
|
|
54
54
|
export declare const NATIVE_MCP_CLIENTS: readonly NativeMcpClient[];
|
|
55
55
|
export declare const OPENCLAW: SupportedCommand;
|
|
56
|
-
export declare function launchNativeMcpClient(client: NativeMcpClient):
|
|
57
|
-
export declare function launchOpenClawWalkthrough():
|
|
56
|
+
export declare function launchNativeMcpClient(client: NativeMcpClient): void;
|
|
57
|
+
export declare function launchOpenClawWalkthrough(): void;
|
|
@@ -192,6 +192,13 @@ export const GROK = {
|
|
|
192
192
|
removals: [{ args: ["mcp", "remove", "tinyfish"], label: "TinyFish registration from Grok" }],
|
|
193
193
|
addArgs: grokAddArgs,
|
|
194
194
|
};
|
|
195
|
+
// Handover: spawn errors are ours; the exit is the user's.
|
|
196
|
+
function handOverTerminal(command, args, displayName) {
|
|
197
|
+
const result = spawn.sync(command, args, { stdio: "inherit" });
|
|
198
|
+
if (result.error) {
|
|
199
|
+
throw spawnStepError(`Could not launch ${displayName}`, result);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
195
202
|
function launchHermesWalkthrough() {
|
|
196
203
|
const seedResult = spawn.sync("hermes", ["chat", "-Q", "-q", DEFAULT_ONBOARDING_PROMPT], {
|
|
197
204
|
encoding: "utf8",
|
|
@@ -205,15 +212,7 @@ function launchHermesWalkthrough() {
|
|
|
205
212
|
if (!sessionId) {
|
|
206
213
|
throw new Error("Hermes did not return a walkthrough session ID");
|
|
207
214
|
}
|
|
208
|
-
|
|
209
|
-
if (result.error) {
|
|
210
|
-
throw spawnStepError("Could not launch Hermes", result);
|
|
211
|
-
}
|
|
212
|
-
if (result.signal === "SIGINT" || result.signal === "SIGTERM")
|
|
213
|
-
return;
|
|
214
|
-
if (result.status !== 0) {
|
|
215
|
-
throw spawnStepError(`Hermes walkthrough exited with status ${result.status ?? "unknown"}`, result);
|
|
216
|
-
}
|
|
215
|
+
handOverTerminal("hermes", ["--resume", sessionId], "Hermes");
|
|
217
216
|
}
|
|
218
217
|
export const HERMES = {
|
|
219
218
|
command: "hermes",
|
|
@@ -235,17 +234,7 @@ export const HERMES = {
|
|
|
235
234
|
// would be read as a folder. The `--prompt` flag seeds the interactive TUI with a first message
|
|
236
235
|
// (opencode.ai/docs/cli), so the onboarding guide fires just like the other agents.
|
|
237
236
|
function launchOpencode() {
|
|
238
|
-
|
|
239
|
-
stdio: "inherit",
|
|
240
|
-
});
|
|
241
|
-
if (result.error) {
|
|
242
|
-
throw spawnStepError("Could not launch OpenCode", result);
|
|
243
|
-
}
|
|
244
|
-
if (result.signal === "SIGINT" || result.signal === "SIGTERM")
|
|
245
|
-
return;
|
|
246
|
-
if (result.status !== 0) {
|
|
247
|
-
throw spawnStepError(`OpenCode exited with status ${result.status ?? "unknown"}`, result);
|
|
248
|
-
}
|
|
237
|
+
handOverTerminal("opencode", ["--prompt", DEFAULT_ONBOARDING_PROMPT], "OpenCode");
|
|
249
238
|
}
|
|
250
239
|
export const OPENCODE = {
|
|
251
240
|
command: "opencode",
|
|
@@ -288,31 +277,11 @@ export function launchNativeMcpClient(client) {
|
|
|
288
277
|
errLine(`Starting the TinyFish walkthrough in ${client.displayName}...`);
|
|
289
278
|
if (client.launchWalkthrough) {
|
|
290
279
|
client.launchWalkthrough();
|
|
291
|
-
return
|
|
292
|
-
}
|
|
293
|
-
const result = spawn.sync(client.command, [DEFAULT_ONBOARDING_PROMPT], { stdio: "inherit" });
|
|
294
|
-
if (result.error) {
|
|
295
|
-
throw spawnStepError(`Could not launch ${client.displayName}`, result);
|
|
296
|
-
}
|
|
297
|
-
if (result.signal === "SIGINT" || result.signal === "SIGTERM")
|
|
298
|
-
return false;
|
|
299
|
-
if (result.status !== 0) {
|
|
300
|
-
throw spawnStepError(`${client.displayName} walkthrough exited with status ${result.status ?? "unknown"}`, result);
|
|
280
|
+
return;
|
|
301
281
|
}
|
|
302
|
-
|
|
282
|
+
handOverTerminal(client.command, [DEFAULT_ONBOARDING_PROMPT], client.displayName);
|
|
303
283
|
}
|
|
304
284
|
export function launchOpenClawWalkthrough() {
|
|
305
285
|
errLine("Starting the TinyFish walkthrough in OpenClaw...");
|
|
306
|
-
|
|
307
|
-
stdio: "inherit",
|
|
308
|
-
});
|
|
309
|
-
if (result.error) {
|
|
310
|
-
throw spawnStepError("Could not launch OpenClaw", result);
|
|
311
|
-
}
|
|
312
|
-
if (result.signal === "SIGINT" || result.signal === "SIGTERM")
|
|
313
|
-
return false;
|
|
314
|
-
if (result.status !== 0) {
|
|
315
|
-
throw spawnStepError(`OpenClaw walkthrough exited with status ${result.status ?? "unknown"}`, result);
|
|
316
|
-
}
|
|
317
|
-
return true;
|
|
286
|
+
handOverTerminal("openclaw", ["chat", "--message", OPENCLAW_ONBOARDING_PROMPT], "OpenClaw");
|
|
318
287
|
}
|
|
@@ -3,11 +3,11 @@ export declare const NON_INTERACTIVE_TIMEOUT_MS = 10000;
|
|
|
3
3
|
export type AgentClient = "claude-code" | "codex" | "cursor" | "grok" | "hermes" | "openclaw" | "opencode";
|
|
4
4
|
type ConnectStage = "started" | "checkpoint" | "completed" | "failed" | "aborted" | "post_install_failed";
|
|
5
5
|
type ConnectFailureStage = "prerequisite_check" | "registration_cleanup" | "registration" | "registration_or_authentication" | "client_oauth" | "cli_install" | "skill_install" | "authentication" | "walkthrough_launch";
|
|
6
|
-
export declare const CONNECT_FAILURE_REASONS: readonly ["harness_not_installed", "harness_command_unsupported", "harness_too_old", "command_not_found", "timeout", "spawn_error", "nonzero_exit", "invalid_config"];
|
|
6
|
+
export declare const CONNECT_FAILURE_REASONS: readonly ["harness_not_installed", "harness_command_unsupported", "harness_too_old", "command_not_found", "timeout", "spawn_error", "nonzero_exit", "invalid_config", "user_abandoned", "process_terminated", "unexpected_error"];
|
|
7
7
|
type ConnectFailureReason = (typeof CONNECT_FAILURE_REASONS)[number];
|
|
8
8
|
export type ConnectAuthMode = Exclude<AuthMode, AuthMode.Unknown> | "deferred";
|
|
9
9
|
type ConnectCheckpoint = "prerequisite_ok" | "registered" | "oauth_done" | "cli_installed" | "skill_installed" | "authenticated";
|
|
10
|
-
/** Ctrl+C
|
|
10
|
+
/** Ctrl+C killed a setup child — abandonment, not error. */
|
|
11
11
|
export declare class ConnectInterruptedError extends Error {
|
|
12
12
|
}
|
|
13
13
|
/** A classified step failure; runGuarded forwards the reason. */
|
|
@@ -17,8 +17,11 @@ export const CONNECT_FAILURE_REASONS = [
|
|
|
17
17
|
"spawn_error",
|
|
18
18
|
"nonzero_exit",
|
|
19
19
|
"invalid_config",
|
|
20
|
+
"user_abandoned",
|
|
21
|
+
"process_terminated",
|
|
22
|
+
"unexpected_error",
|
|
20
23
|
];
|
|
21
|
-
/** Ctrl+C
|
|
24
|
+
/** Ctrl+C killed a setup child — abandonment, not error. */
|
|
22
25
|
export class ConnectInterruptedError extends Error {
|
|
23
26
|
}
|
|
24
27
|
/** A classified step failure; runGuarded forwards the reason. */
|
|
@@ -41,7 +44,7 @@ export function throwIfInterrupted(result) {
|
|
|
41
44
|
const code = result.error?.code;
|
|
42
45
|
if (code && NOT_INTERRUPTIONS.has(code))
|
|
43
46
|
return;
|
|
44
|
-
if (result.signal === "SIGINT"
|
|
47
|
+
if (result.signal === "SIGINT") {
|
|
45
48
|
throw new ConnectInterruptedError("Setup interrupted");
|
|
46
49
|
}
|
|
47
50
|
}
|
|
@@ -75,9 +78,12 @@ export function settle(state, telemetry, stage, detail) {
|
|
|
75
78
|
// Raw-mode children still surface via throwIfInterrupted; cooked-mode stages
|
|
76
79
|
// (npm/npx/OAuth waits) land here.
|
|
77
80
|
function installConnectSignalGuard(state, telemetry) {
|
|
78
|
-
return installSignalGuard(async () => {
|
|
81
|
+
return installSignalGuard(async (signal) => {
|
|
79
82
|
if (!state.settled) {
|
|
80
|
-
settle(state, telemetry, "aborted", {
|
|
83
|
+
settle(state, telemetry, "aborted", {
|
|
84
|
+
failedStage: state.stage,
|
|
85
|
+
failureReason: signal === "SIGINT" ? "user_abandoned" : "process_terminated",
|
|
86
|
+
});
|
|
81
87
|
errLine("Setup interrupted — run the command again to finish.");
|
|
82
88
|
}
|
|
83
89
|
// Settled spans the whole post-install phase; the `completed` POST may still be in flight.
|
|
@@ -92,16 +98,18 @@ export async function runGuarded(state, telemetry, body) {
|
|
|
92
98
|
}
|
|
93
99
|
catch (error) {
|
|
94
100
|
if (error instanceof ConnectInterruptedError) {
|
|
95
|
-
settle(state, telemetry, "aborted", {
|
|
101
|
+
settle(state, telemetry, "aborted", {
|
|
102
|
+
failedStage: state.stage,
|
|
103
|
+
failureReason: "user_abandoned",
|
|
104
|
+
});
|
|
96
105
|
errLine("Setup interrupted — run the command again to finish.");
|
|
97
106
|
process.exitCode = 130;
|
|
98
107
|
return;
|
|
99
108
|
}
|
|
100
109
|
settle(state, telemetry, "failed", {
|
|
101
110
|
failedStage: state.stage,
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
: {}),
|
|
111
|
+
failureReason: error instanceof ConnectStepError ? error.failureReason : "unexpected_error",
|
|
112
|
+
...(error instanceof ConnectStepError ? { harnessVersion: error.harnessVersion } : {}),
|
|
105
113
|
});
|
|
106
114
|
throw error;
|
|
107
115
|
}
|
|
@@ -146,7 +154,11 @@ export function requireCommandSupport(client) {
|
|
|
146
154
|
harnessVersion: probeHarnessVersion(client.command),
|
|
147
155
|
});
|
|
148
156
|
}
|
|
149
|
-
return {
|
|
157
|
+
return {
|
|
158
|
+
optionalSupported,
|
|
159
|
+
keyAuthSupported,
|
|
160
|
+
harnessVersion: probeHarnessVersion(client.command),
|
|
161
|
+
};
|
|
150
162
|
}
|
|
151
163
|
/** Best-effort; the server rejects non-printable characters and >32 chars. */
|
|
152
164
|
function probeHarnessVersion(command) {
|