@tiny-fish/cli 0.30.1-next.252 → 0.30.1-next.253

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.
@@ -50,5 +50,5 @@ export declare function connectCursor(options: {
50
50
  attemptId?: string;
51
51
  verbose?: boolean;
52
52
  }): Promise<void>;
53
- export declare function launchAgent(client: AgentClient): void;
53
+ export declare function launchAgent(client: AgentClient): Promise<void>;
54
54
  export declare function registerConnect(program: Command): void;
@@ -204,7 +204,7 @@ async function connectNativeMcpClient(client, options) {
204
204
  state.authMode = authMode;
205
205
  // Registration/OAuth make MCP work; later steps are cosmetic and must not fail the attempt.
206
206
  settle(state, telemetry, "completed", { authMode });
207
- runPostInstallSteps(client, options, state, telemetry, signInDeferred);
207
+ await runPostInstallSteps(client, options, state, telemetry, signInDeferred);
208
208
  });
209
209
  return state.authMode;
210
210
  }
@@ -220,13 +220,13 @@ function connectedLine(client, signInDeferred) {
220
220
  `${RELOAD_ACTION[client.connectClient]} to pick up the tools.`);
221
221
  }
222
222
  // Runs after `completed` settled: steps fail alone, the rest still run.
223
- function runPostInstallSteps(client, options, state, telemetry, signInDeferred) {
223
+ async function runPostInstallSteps(client, options, state, telemetry, signInDeferred) {
224
224
  let failed = false;
225
225
  // Modelled on upgrade's attempt(); interrupts rethrow and abandon remaining steps.
226
- const attempt = (stage, run) => {
226
+ const attempt = async (stage, run) => {
227
227
  state.stage = stage;
228
228
  try {
229
- run();
229
+ await run();
230
230
  }
231
231
  catch (error) {
232
232
  if (error instanceof ConnectInterruptedError)
@@ -237,17 +237,17 @@ function runPostInstallSteps(client, options, state, telemetry, signInDeferred)
237
237
  };
238
238
  try {
239
239
  if (options.cliInstall !== false) {
240
- attempt("cli_install", () => runCliInstallStep(options, telemetry));
240
+ await attempt("cli_install", () => runCliInstallStep(options, telemetry));
241
241
  }
242
242
  if (client.skillAgent) {
243
- attempt("skill_install", () => {
243
+ await attempt("skill_install", () => {
244
244
  installWebSkill(client, { verbose: options.verbose ?? false });
245
245
  telemetry.track("checkpoint", { phase: "skill_installed" });
246
246
  });
247
247
  }
248
248
  // Before auth: a failed sign-in must not lose the harness record.
249
249
  saveConnectContext(client.connectClient, telemetry.attemptId, state.authMode);
250
- attempt("authentication", () => {
250
+ await attempt("authentication", () => {
251
251
  // A set timeout means headless; never shell an interactive login.
252
252
  ensureCliAuthenticated(client.connectClient, options.apiKey, {
253
253
  interactiveLogin: options.authTimeoutMs === undefined,
@@ -262,11 +262,15 @@ function runPostInstallSteps(client, options, state, telemetry, signInDeferred)
262
262
  errLine(connectedLine(client, signInDeferred));
263
263
  }
264
264
  else {
265
- attempt("walkthrough_launch", () => {
265
+ await attempt("walkthrough_launch", async () => {
266
266
  // Before the handover, or the only sign-in instructions scroll past under the agent.
267
267
  if (signInDeferred && client.signInHint)
268
268
  errLine(client.signInHint);
269
- launchNativeMcpClient(client);
269
+ await launchNativeMcpClient(client, async (outcome) => {
270
+ telemetry.track("checkpoint", { phase: walkthroughPhase(outcome) });
271
+ // Flushed pre-block; the session-long handover would strand the delivery.
272
+ await telemetry.flush();
273
+ });
270
274
  });
271
275
  }
272
276
  }
@@ -300,6 +304,9 @@ function runCliInstallStep(options, telemetry) {
300
304
  installTinyFishCli({ verbose: options.verbose ?? false, announce: true });
301
305
  telemetry.track("checkpoint", { phase: "cli_installed" });
302
306
  }
307
+ function walkthroughPhase(outcome) {
308
+ return outcome === "printed" ? "walkthrough_prompt_printed" : "walkthrough_launched";
309
+ }
303
310
  function finishSetupHint(connectClient) {
304
311
  return `Finish setup with: ${finishSetupCommand(connectClient)}`;
305
312
  }
@@ -392,7 +399,11 @@ export async function connectOpenClaw(options) {
392
399
  }
393
400
  state.stage = "walkthrough_launch";
394
401
  try {
395
- launchOpenClawWalkthrough();
402
+ await launchOpenClawWalkthrough(async (outcome) => {
403
+ telemetry.track("checkpoint", { phase: walkthroughPhase(outcome) });
404
+ // Flushed pre-block; the session-long handover would strand the delivery.
405
+ await telemetry.flush();
406
+ });
396
407
  }
397
408
  catch (error) {
398
409
  trackPostInstallFailure("OpenClaw", state, telemetry, error);
@@ -511,8 +522,8 @@ const AGENT_LAUNCHERS = {
511
522
  throw new Error("Cursor has no launchable walkthrough. Open Cursor and start chatting.");
512
523
  },
513
524
  };
514
- export function launchAgent(client) {
515
- AGENT_LAUNCHERS[client]();
525
+ export async function launchAgent(client) {
526
+ await AGENT_LAUNCHERS[client]();
516
527
  }
517
528
  // A new client fails to compile until wired here.
518
529
  const CLIENT_CONNECTORS = {
@@ -187,7 +187,7 @@ async function runAgentSteps(interactive) {
187
187
  if (!launchId)
188
188
  return;
189
189
  try {
190
- launchAgent(launchId);
190
+ await launchAgent(launchId);
191
191
  }
192
192
  catch (error) {
193
193
  errLine(` Could not open ${agentLabel(launchId)}: ${errorMessage(error)}`);
@@ -45,7 +45,10 @@ export interface NativeMcpClient extends SupportedCommand {
45
45
  label: string;
46
46
  }[];
47
47
  addArgs: (mcpUrl: string, oauthResource: string) => string[];
48
+ /** Blocking terminal walkthrough; runs only behind the TTY gate. */
48
49
  launchWalkthrough?: () => void;
50
+ /** Own window, never inherits stdio; reports its own outcome. */
51
+ detachedLaunch?: () => WalkthroughOutcome;
49
52
  /** Printed after a successful connect, on both the launch and non-launch paths. */
50
53
  postConnectNote?: string;
51
54
  }
@@ -80,5 +83,10 @@ export interface HarnessCapabilities {
80
83
  }
81
84
  /** Required booleans: a new harness or new flag fails to compile undecided. */
82
85
  export declare const HARNESS_CAPABILITIES: Record<Harness, HarnessCapabilities>;
83
- export declare function launchNativeMcpClient(client: NativeMcpClient): void;
84
- export declare function launchOpenClawWalkthrough(): void;
86
+ /** "printed" = handed to the user to paste; the walkthrough was never started for them. */
87
+ export type WalkthroughOutcome = "launched" | "printed";
88
+ /** Awaited before the handover; track+flush must land pre-block. */
89
+ type OnWalkthroughDecided = (outcome: WalkthroughOutcome) => void | Promise<void>;
90
+ export declare function launchNativeMcpClient(client: NativeMcpClient, onDecided?: OnWalkthroughDecided): Promise<WalkthroughOutcome>;
91
+ export declare function launchOpenClawWalkthrough(onDecided?: OnWalkthroughDecided): Promise<WalkthroughOutcome>;
92
+ export {};
@@ -1,5 +1,6 @@
1
1
  import spawn from "cross-spawn";
2
2
  import { SKILL_INSTALL_TIMEOUT_MS } from "./cli-install.js";
3
+ import { detectHumanInitiated } from "./harness.js";
3
4
  import { harnessConfigPath } from "./harness-detect.js";
4
5
  import { errLine, sanitizeLine } from "./output.js";
5
6
  import { spawnStepError, } from "./connect-runtime.js";
@@ -74,9 +75,13 @@ export function openclawSkillUninstall() {
74
75
  timeoutMs: SKILL_INSTALL_TIMEOUT_MS,
75
76
  };
76
77
  }
77
- export const DEFAULT_ONBOARDING_PROMPT = "I just connected TinyFish. Call the guide_next_step tool now to begin. Then follow its " +
78
- "instructions one step at a time. Ask for my input and wait for my reply before each subsequent " +
79
- "TinyFish tool call.";
78
+ export const DEFAULT_ONBOARDING_PROMPT = "I just connected TinyFish. Use the MCP server named `tinyfish` for every TinyFish tool call; " +
79
+ "if another TinyFish server is also registered, still use `tinyfish` and do not ask me which. " +
80
+ "Start by asking me what I want to look up on the web, and wait for my reply. Then call " +
81
+ "`guide_next_step` and follow its instructions one step at a time, asking for my input and " +
82
+ "waiting for my reply before each subsequent TinyFish tool call. If a tool call comes back " +
83
+ "blocked or not approved, tell me which tool needs approval and how to grant it, then wait for " +
84
+ "me instead of ending the walkthrough.";
80
85
  export const OPENCLAW_ONBOARDING_PROMPT = "I just installed the TinyFish skill. Guide me through it interactively: first ask what topic I " +
81
86
  "want to search and wait for my reply before using TinyFish Search. Show me the results, ask " +
82
87
  "which result I want to read, and wait before using TinyFish Fetch. Then ask what browser task " +
@@ -152,9 +157,12 @@ function launchCodexWalkthrough() {
152
157
  deepLink.searchParams.set("path", process.cwd());
153
158
  const result = openExternalUrl(deepLink.toString());
154
159
  if (result.error || result.status !== 0) {
155
- throw spawnStepError("Could not open Codex", result);
160
+ // Headless box: nothing can open a window; recover like the gated clients.
161
+ printPromptForHandoff("Codex", "codex", DEFAULT_ONBOARDING_PROMPT, "Codex could not be opened here (no `codex://` handler)");
162
+ return "printed";
156
163
  }
157
164
  errLine("Codex opened with the TinyFish walkthrough ready. Send the prompt to start.");
165
+ return "launched";
158
166
  }
159
167
  export const CODEX = {
160
168
  command: "codex",
@@ -173,7 +181,7 @@ export const CODEX = {
173
181
  envVar: TINYFISH_API_KEY_VAR,
174
182
  addArgs: (mcpUrl) => [...urlAddArgs(mcpUrl), "--bearer-token-env-var", TINYFISH_API_KEY_VAR],
175
183
  },
176
- launchWalkthrough: launchCodexWalkthrough,
184
+ detachedLaunch: launchCodexWalkthrough,
177
185
  // Retry only: running it after a finished inline OAuth is a second browser hop.
178
186
  loginArgs: ["mcp", "login", "tinyfish"],
179
187
  removals: [{ args: ["mcp", "remove", "tinyfish"], label: "TinyFish registration from Codex" }],
@@ -350,15 +358,40 @@ export const HARNESS_CAPABILITIES = {
350
358
  authDeferredAtInstall: false,
351
359
  },
352
360
  };
353
- export function launchNativeMcpClient(client) {
354
- errLine(`Starting the TinyFish walkthrough in ${client.displayName}...`);
355
- if (client.launchWalkthrough) {
356
- client.launchWalkthrough();
357
- return;
361
+ function printPromptForHandoff(displayName, command, prompt, reason) {
362
+ // "set up", not "connected": sign-in can still be deferred here.
363
+ errLine(`TinyFish is set up in ${displayName}.`);
364
+ errLine(`${reason}, so TinyFish did not launch it. Start ${displayName} (\`${command}\`) yourself ` +
365
+ `and paste this first message:`);
366
+ errLine("");
367
+ errLine(prompt);
368
+ }
369
+ /** One gate + one decide-before-block path for every terminal handover. */
370
+ async function deliverWalkthrough(displayName, command, prompt, handover, onDecided) {
371
+ // Inherited-stdio handover: a non-TTY parent cannot approve tool calls.
372
+ if (!detectHumanInitiated()) {
373
+ await onDecided?.("printed");
374
+ printPromptForHandoff(displayName, command, prompt, `This terminal cannot answer ${displayName}'s tool-approval prompts`);
375
+ return "printed";
376
+ }
377
+ // Decided and delivered first: the handover blocks for the whole session.
378
+ await onDecided?.("launched");
379
+ errLine(`Starting the TinyFish walkthrough in ${displayName}...`);
380
+ handover();
381
+ return "launched";
382
+ }
383
+ export async function launchNativeMcpClient(client, onDecided) {
384
+ if (client.detachedLaunch) {
385
+ // Detached: the open attempt never blocks, so decide after it.
386
+ errLine(`Starting the TinyFish walkthrough in ${client.displayName}...`);
387
+ const outcome = client.detachedLaunch();
388
+ await onDecided?.(outcome);
389
+ return outcome;
358
390
  }
359
- handOverTerminal(client.command, [DEFAULT_ONBOARDING_PROMPT], client.displayName);
391
+ return deliverWalkthrough(client.displayName, client.command, DEFAULT_ONBOARDING_PROMPT, () => client.launchWalkthrough
392
+ ? client.launchWalkthrough()
393
+ : handOverTerminal(client.command, [DEFAULT_ONBOARDING_PROMPT], client.displayName), onDecided);
360
394
  }
361
- export function launchOpenClawWalkthrough() {
362
- errLine("Starting the TinyFish walkthrough in OpenClaw...");
363
- handOverTerminal("openclaw", ["chat", "--message", OPENCLAW_ONBOARDING_PROMPT], "OpenClaw");
395
+ export function launchOpenClawWalkthrough(onDecided) {
396
+ return deliverWalkthrough("OpenClaw", "openclaw", OPENCLAW_ONBOARDING_PROMPT, () => handOverTerminal("openclaw", ["chat", "--message", OPENCLAW_ONBOARDING_PROMPT], "OpenClaw"), onDecided);
364
397
  }
@@ -6,7 +6,7 @@ export type ConnectFailureStage = "prerequisite_check" | "registration_cleanup"
6
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
- export type ConnectCheckpoint = "prerequisite_ok" | "registered" | "oauth_done" | "cli_installed" | "skill_installed" | "authenticated" | "cli_verified";
9
+ export type ConnectCheckpoint = "prerequisite_ok" | "registered" | "oauth_done" | "cli_installed" | "skill_installed" | "authenticated" | "cli_verified" | "walkthrough_launched" | "walkthrough_prompt_printed";
10
10
  export type CliFallbackOutcome = "cli_verified" | "cli_install_failed" | "no_credentials" | "invalid_credentials" | "authentication_failed" | "verification_failed" | "abandoned";
11
11
  /** Ctrl+C killed a setup child — abandonment, not error. */
12
12
  export declare class ConnectInterruptedError extends Error {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiny-fish/cli",
3
- "version": "0.30.1-next.252",
3
+ "version": "0.30.1-next.253",
4
4
  "description": "TinyFish CLI — run web automations from your terminal",
5
5
  "type": "module",
6
6
  "bin": {