@tiny-fish/cli 0.25.1 → 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.
@@ -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
- telemetry.track("checkpoint", { phase: "oauth_done" });
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", { phase: "cli_installed", stageDurationMs: outcome.durationMs });
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
- ...(error instanceof ConnectStepError ? { failureReason: error.failureReason } : {}),
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 ` +
@@ -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/SIGTERM killed a setup child — abandonment, not error. */
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/SIGTERM killed a setup child — abandonment, not error. */
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" || result.signal === "SIGTERM") {
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", { failedStage: state.stage });
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", { failedStage: state.stage });
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
- ...(error instanceof ConnectStepError
103
- ? { failureReason: error.failureReason, harnessVersion: error.harnessVersion }
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 { optionalSupported, keyAuthSupported, harnessVersion: probeHarnessVersion(client.command) };
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiny-fish/cli",
3
- "version": "0.25.1",
3
+ "version": "0.25.2-next.220",
4
4
  "description": "TinyFish CLI — run web automations from your terminal",
5
5
  "type": "module",
6
6
  "bin": {