@tiny-fish/cli 0.26.1-next.223 → 0.26.1-next.226

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.
@@ -5,7 +5,7 @@ import { HoistedFailureReportedError, installTinyFishCli, SKILL_INSTALL_TIMEOUT_
5
5
  import { ensureCliAuthenticated } from "../lib/connect-auth.js";
6
6
  import { installWebSkill } from "../lib/skill-install.js";
7
7
  import { ConnectInterruptedError, ConnectStepError, createConnectTelemetry, finishSetupCommand, requireCommandSupport, runGuarded, settle, spawnStepError, throwIfInterrupted, NON_INTERACTIVE_TIMEOUT_MS, } from "../lib/connect-runtime.js";
8
- import { cursorInstallDeeplink, cursorMcpPath, writeCursorMcpConfig, } from "../lib/cursor-config.js";
8
+ import { cursorInstallDeeplink, cursorMcpPath, readCursorTinyfishEntry, writeCursorMcpConfig, } from "../lib/cursor-config.js";
9
9
  import { runConnectAll } from "../lib/connect-all.js";
10
10
  import { AuthMode } from "../lib/harness-detect.js";
11
11
  import { detectHumanInitiated } from "../lib/harness.js";
@@ -67,8 +67,8 @@ async function connectNativeMcpClient(client, options) {
67
67
  throw new ConnectStepError(`This ${client.displayName} install cannot take a TinyFish API key, and signing in ` +
68
68
  `needs a terminal. Update it, then re-run: tinyfish connect ${client.connectClient}`, "harness_too_old");
69
69
  }
70
- throw new Error(`${client.displayName} cannot take a TinyFish API key, and signing in needs a ` +
71
- `terminal. Run: tinyfish connect ${client.connectClient}`);
70
+ throw new ConnectStepError(`${client.displayName} cannot take a TinyFish API key, and signing in needs a ` +
71
+ `terminal. Run: tinyfish connect ${client.connectClient}`, "key_auth_unsupported");
72
72
  }
73
73
  telemetry.track("checkpoint", { phase: "prerequisite_ok" });
74
74
  state.stage = "registration_cleanup";
@@ -319,7 +319,36 @@ export async function connectOpenClaw(options) {
319
319
  }
320
320
  /** Writes mcp.json directly — no `cursor mcp add` exists. */
321
321
  export async function connectCursor(options) {
322
- const telemetry = createConnectTelemetry(options.mcpUrl, "cursor", options);
322
+ let fallbackAttemptId;
323
+ const registeredUrl = readCursorTinyfishEntry().url;
324
+ if (registeredUrl) {
325
+ try {
326
+ const registered = new URL(registeredUrl);
327
+ const requested = new URL(options.mcpUrl);
328
+ const registeredId = registered.searchParams.get("connect_attempt_id");
329
+ const registeredSource = registered.searchParams.get("source");
330
+ const registeredClient = registered.searchParams.get("client");
331
+ for (const key of ["source", "client", "connect_attempt_id"]) {
332
+ registered.searchParams.delete(key);
333
+ requested.searchParams.delete(key);
334
+ }
335
+ registered.searchParams.sort();
336
+ requested.searchParams.sort();
337
+ if (registered.toString() === requested.toString() &&
338
+ registeredSource === CONNECT_SOURCE &&
339
+ registeredClient === "cursor" &&
340
+ z.uuid().safeParse(registeredId).success) {
341
+ fallbackAttemptId = registeredId ?? undefined;
342
+ }
343
+ }
344
+ catch {
345
+ // A hand-edited invalid URL is repaired below with a fresh attempt id.
346
+ }
347
+ }
348
+ const telemetry = createConnectTelemetry(options.mcpUrl, "cursor", {
349
+ ...options,
350
+ fallbackAttemptId,
351
+ });
323
352
  // Never degraded: no sign-in command to be missing. False, not absent, so a `= false`
324
353
  // filter still catches Cursor.
325
354
  const state = {
@@ -347,7 +376,7 @@ export async function connectCursor(options) {
347
376
  const mcpUrl = new URL(options.mcpUrl);
348
377
  mcpUrl.searchParams.set("source", CONNECT_SOURCE);
349
378
  mcpUrl.searchParams.set("client", "cursor");
350
- // Attempt id rides telemetry only; persisting it would break idempotency.
379
+ mcpUrl.searchParams.set("connect_attempt_id", telemetry.attemptId);
351
380
  const result = writeCursorMcpConfig(mcpUrl.toString(), resolvedKey);
352
381
  if (result.status === "corrupt_skip") {
353
382
  throw new ConnectStepError(`Could not update ${cursorMcpPath()}: existing file could not be read or is not valid JSON (${result.error}). ` +
@@ -3,7 +3,7 @@ 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", "user_abandoned", "process_terminated", "unexpected_error"];
6
+ export declare const CONNECT_FAILURE_REASONS: readonly ["harness_not_installed", "harness_command_unsupported", "harness_too_old", "key_auth_unsupported", "command_not_found", "timeout", "spawn_error", "nonzero_exit", "invalid_config", "user_abandoned", "process_terminated", "unexpected_error"];
7
7
  export 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";
@@ -84,5 +84,6 @@ export declare function requireCommandSupport(client: SupportedCommand): {
84
84
  export declare function createConnectTelemetry(mcpUrl: string, client: AgentClient, opts?: {
85
85
  apiKey?: string;
86
86
  attemptId?: string;
87
+ fallbackAttemptId?: string;
87
88
  }): ConnectTelemetry;
88
89
  export {};
@@ -12,6 +12,7 @@ export const CONNECT_FAILURE_REASONS = [
12
12
  "harness_not_installed",
13
13
  "harness_command_unsupported",
14
14
  "harness_too_old",
15
+ "key_auth_unsupported",
15
16
  "command_not_found",
16
17
  "timeout",
17
18
  "spawn_error",
@@ -192,7 +193,7 @@ export function createConnectTelemetry(mcpUrl, client, opts) {
192
193
  const seeded = takeSeedAttemptId();
193
194
  // A setup-page id (via --url, the env var, or one parked by login) joins "copied the
194
195
  // command" to this install attempt.
195
- const attemptId = opts?.attemptId ?? seeded ?? randomUUID();
196
+ const attemptId = opts?.attemptId ?? seeded ?? opts?.fallbackAttemptId ?? randomUUID();
196
197
  const endpoint = new URL("/api/cli/connect-event", mcpUrl).toString();
197
198
  const pending = [];
198
199
  let lastTrackAt;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiny-fish/cli",
3
- "version": "0.26.1-next.223",
3
+ "version": "0.26.1-next.226",
4
4
  "description": "TinyFish CLI — run web automations from your terminal",
5
5
  "type": "module",
6
6
  "bin": {