@tiny-fish/cli 0.31.1-next.271 → 0.31.1-next.272

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.
@@ -665,13 +665,14 @@ function parseMcpUrlOption(options) {
665
665
  }
666
666
  return { mcpUrl, attemptId };
667
667
  }
668
- async function connectAllPath(options, mcpUrl) {
668
+ async function connectAllPath(options, mcpUrl, attemptId) {
669
669
  if (options.launch) {
670
670
  throw new Error('--launch is not supported with --all. Connect a single client to launch it.');
671
671
  }
672
672
  const exitCode = await runConnectAll({
673
673
  apiKey: options.apiKey,
674
674
  mcpUrl,
675
+ attemptId,
675
676
  dryRun: options.dryRun ?? false,
676
677
  uninstall: options.uninstall ?? false,
677
678
  noLaunch: options.skipLaunch === true,
@@ -681,7 +682,7 @@ async function connectAllPath(options, mcpUrl) {
681
682
  if (!process.exitCode)
682
683
  process.exitCode = exitCode;
683
684
  }
684
- async function connectPickPath(options, mcpUrl) {
685
+ async function connectPickPath(options, mcpUrl, attemptId) {
685
686
  if (options.launch) {
686
687
  throw new Error('--launch is not supported with --all. Connect a single client to launch it.');
687
688
  }
@@ -692,6 +693,7 @@ async function connectPickPath(options, mcpUrl) {
692
693
  const exitCode = await runConnectAll({
693
694
  apiKey: options.apiKey,
694
695
  mcpUrl,
696
+ attemptId,
695
697
  dryRun: false,
696
698
  uninstall: false,
697
699
  pick: humanInitiated,
@@ -743,14 +745,14 @@ export function registerConnect(program) {
743
745
  if (client) {
744
746
  throw new Error('Pass either a client or --all, not both.');
745
747
  }
746
- await connectAllPath(options, mcpUrl);
748
+ await connectAllPath(options, mcpUrl, attemptId);
747
749
  return;
748
750
  }
749
751
  if (options.dryRun || options.uninstall || options.skipLaunch) {
750
752
  throw new Error('--dry-run, --uninstall and --skip-launch are only supported with --all.');
751
753
  }
752
754
  if (!client) {
753
- await connectPickPath(options, mcpUrl);
755
+ await connectPickPath(options, mcpUrl, attemptId);
754
756
  return;
755
757
  }
756
758
  await connectSingleClient(client, options, mcpUrl, attemptId);
@@ -31,6 +31,8 @@ export interface ConnectAllOptions {
31
31
  pick?: boolean;
32
32
  /** Inherit subprocess stdio instead of replaying it only on failure. */
33
33
  verbose?: boolean;
34
+ /** Setup-page id riding in on --url; joins "copied the command" to this run. */
35
+ attemptId?: string;
34
36
  }
35
37
  export declare function runConnectAll(opts: ConnectAllOptions): Promise<number>;
36
38
  export declare const FIRST_TASK_PROMPT: string;
@@ -14,7 +14,7 @@ import { commandOnPath, detectInstalledHarnesses, HARNESS_COMMANDS, HARNESS_DISP
14
14
  import { detectHumanInitiated } from './harness.js';
15
15
  import { emitNotice } from './notice.js';
16
16
  import { errLine, outLine, setErrIndent } from './output.js';
17
- import { sendSetupCompleted } from './setup-telemetry.js';
17
+ import { sendSetupCompleted, sendSetupStarted, } from './setup-telemetry.js';
18
18
  import { verifyMcpAuth, verifyMcpHealth } from './verify.js';
19
19
  const STEP_INDENT = ' ';
20
20
  // Derived: a native add carries the key, or the CLI holds it itself.
@@ -315,6 +315,12 @@ async function finishRun(results, opts, isTTY) {
315
315
  }
316
316
  export async function runConnectAll(opts) {
317
317
  const isTTY = detectHumanInitiated();
318
+ // Ahead of detection and the hoisted install; either can end the process silently.
319
+ // Not on the picker path: an explicit cancel is not a setup.
320
+ if (!opts.uninstall && !opts.dryRun && !opts.pick) {
321
+ // validatedApiKey falls back to env/config; opts.apiKey alone splits the run's two ends.
322
+ await sendSetupStarted(opts.mcpUrl, opts.attemptId, validatedApiKey(opts.apiKey));
323
+ }
318
324
  // Open interface holds stdin raw; hermes' input() then wedges.
319
325
  const prompt = isTTY ? createStdinPrompt() : undefined;
320
326
  const detections = detectInstalledHarnesses();
@@ -372,7 +378,7 @@ export async function runConnectAll(opts) {
372
378
  verify_depth: r.verifyDepth ?? null,
373
379
  verify_ok: r.verifyOk ?? null,
374
380
  }));
375
- await sendSetupCompleted(opts.mcpUrl, harnessTelemetry, validatedApiKey(opts.apiKey), mode);
381
+ await sendSetupCompleted(opts.mcpUrl, harnessTelemetry, validatedApiKey(opts.apiKey), mode, opts.attemptId);
376
382
  // After the summary and before the launch, which is where the user is reading.
377
383
  emitNotice();
378
384
  }
@@ -40,6 +40,8 @@ declare const harnessResultSchema: z.ZodObject<{
40
40
  }>>;
41
41
  verify_ok: z.ZodNullable<z.ZodBoolean>;
42
42
  }, z.core.$strip>;
43
+ /** First act of a `--all` run; everything upstream of it stays invisible. */
44
+ export declare function sendSetupStarted(mcpUrl: string, attemptId?: string, apiKey?: string): Promise<void>;
43
45
  export declare const setupCompletedPayloadSchema: z.ZodObject<{
44
46
  harnesses: z.ZodArray<z.ZodObject<{
45
47
  harness: z.ZodEnum<{
@@ -65,6 +67,7 @@ export declare const setupCompletedPayloadSchema: z.ZodObject<{
65
67
  pick: "pick";
66
68
  pick_numeric: "pick_numeric";
67
69
  }>>;
70
+ connect_attempt_id: z.ZodOptional<z.ZodUUID>;
68
71
  }, z.core.$strip>;
69
72
  export type SetupCompletedPayload = z.infer<typeof setupCompletedPayloadSchema>;
70
73
  export type HarnessResult = z.infer<typeof harnessResultSchema>;
@@ -74,7 +77,7 @@ export type UpgradeOutcome = 'updated' | 'failed' | 'interrupted';
74
77
  export type UpgradeScope = 'both' | 'cli' | 'skill';
75
78
  /** `toVersion` is null when the run was interrupted or the installed version could not be read. */
76
79
  export declare function sendUpgradeCompleted(fromVersion: string, toVersion: string | null, outcome: UpgradeOutcome, scope: UpgradeScope, apiKey?: string): Promise<void>;
77
- export declare function sendSetupCompleted(mcpUrl: string, harnesses: HarnessResult[], apiKey?: string, mode?: SetupMode): Promise<void>;
80
+ export declare function sendSetupCompleted(mcpUrl: string, harnesses: HarnessResult[], apiKey?: string, mode?: SetupMode, attemptId?: string): Promise<void>;
78
81
  export declare const DOCTOR_HARNESS_SCOPES: readonly ["all", "claude-code", "codex", "cursor", "grok", "hermes", "openclaw", "opencode"];
79
82
  export declare const doctorCompletedPayloadSchema: z.ZodObject<{
80
83
  schema_version: z.ZodInt;
@@ -101,6 +101,25 @@ const harnessResultSchema = z.object({
101
101
  verify_depth: z.enum(['health', 'health+auth']).nullable(),
102
102
  verify_ok: z.boolean().nullable(),
103
103
  });
104
+ /** First act of a `--all` run; everything upstream of it stays invisible. */
105
+ export async function sendSetupStarted(mcpUrl, attemptId, apiKey) {
106
+ if (telemetryDisabled())
107
+ return;
108
+ // Route 400s a bad id and postConnectEvent never retries 4xx, losing the whole event.
109
+ const stamped = attemptId && z.uuid().safeParse(attemptId).success ? attemptId : undefined;
110
+ await postConnectEvent({
111
+ endpoint: new URL('/api/cli/connect-event', mcpUrl).toString(),
112
+ body: JSON.stringify({
113
+ event: 'setup_started',
114
+ cli_version: CLI_VERSION,
115
+ runtime_platform: process.platform,
116
+ ...(stamped ? { connect_attempt_id: stamped } : {}),
117
+ }),
118
+ attempts: 1,
119
+ label: 'setup',
120
+ apiKey,
121
+ });
122
+ }
104
123
  export const setupCompletedPayloadSchema = z.object({
105
124
  harnesses: z
106
125
  .array(harnessResultSchema)
@@ -111,6 +130,7 @@ export const setupCompletedPayloadSchema = z.object({
111
130
  cli_version: z.string(),
112
131
  // Segments picker runs from --all runs; the server field shipped first.
113
132
  mode: z.enum(['all', 'pick', 'pick_numeric']).optional(),
133
+ connect_attempt_id: z.uuid().optional(),
114
134
  });
115
135
  /** `toVersion` is null when the run was interrupted or the installed version could not be read. */
116
136
  export async function sendUpgradeCompleted(fromVersion, toVersion, outcome, scope, apiKey) {
@@ -128,7 +148,7 @@ export async function sendUpgradeCompleted(fromVersion, toVersion, outcome, scop
128
148
  apiKey,
129
149
  });
130
150
  }
131
- export async function sendSetupCompleted(mcpUrl, harnesses, apiKey, mode) {
151
+ export async function sendSetupCompleted(mcpUrl, harnesses, apiKey, mode, attemptId) {
132
152
  // Ahead of the parse so an opt-out run prints no telemetry debug.
133
153
  if (telemetryDisabled())
134
154
  return;
@@ -140,6 +160,10 @@ export async function sendSetupCompleted(mcpUrl, harnesses, apiKey, mode) {
140
160
  harnesses,
141
161
  cli_version: CLI_VERSION,
142
162
  ...(mode ? { mode } : {}),
163
+ // Guarded here, not only at the route: a parse throw drops the whole event, not one field.
164
+ ...(attemptId && z.uuid().safeParse(attemptId).success
165
+ ? { connect_attempt_id: attemptId }
166
+ : {}),
143
167
  });
144
168
  endpoint = new URL('/api/cli/connect-event', mcpUrl).toString();
145
169
  body = JSON.stringify({ event: 'setup_completed', ...payload });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiny-fish/cli",
3
- "version": "0.31.1-next.271",
3
+ "version": "0.31.1-next.272",
4
4
  "description": "TinyFish CLI — run web automations from your terminal",
5
5
  "type": "module",
6
6
  "bin": {