@tiny-fish/cli 0.39.1-next.309 → 0.39.1-next.311

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.
@@ -108,6 +108,9 @@ function createSdkClient(apiKey, timeout, baseUrl) {
108
108
  ...(timeout !== undefined ? { timeout } : {}),
109
109
  });
110
110
  }
111
+ function sdk(apiKey, call) {
112
+ return call(createSdkClient(apiKey)).catch(rethrowSdkError);
113
+ }
111
114
  function rethrowSdkError(error) {
112
115
  if (error instanceof APIStatusError) {
113
116
  throw new ApiError(error.statusCode, error.message);
@@ -136,14 +139,8 @@ const batchGetResponseSchema = z.object({
136
139
  data: z.array(runSchema),
137
140
  not_found: z.array(z.string()).nullable(),
138
141
  });
139
- const batchCancelResultSchema = z.object({
140
- run_id: z.string(),
141
- status: z.string(),
142
- cancelled_at: z.string().nullable(),
143
- message: z.string().nullable(),
144
- });
145
142
  const batchCancelResponseSchema = z.object({
146
- results: z.array(batchCancelResultSchema),
143
+ results: z.array(cancelRunResponseSchema),
147
144
  not_found: z.array(z.string()).nullable(),
148
145
  });
149
146
  const runStepSchema = z.object({
@@ -219,27 +216,21 @@ async function* parseSseStream(stream) {
219
216
  reader.releaseLock();
220
217
  }
221
218
  }
222
- export async function runSync(req, apiKey) {
223
- try {
224
- const response = await createSdkClient(apiKey).post('/v1/automation/run', {
219
+ export function runSync(req, apiKey) {
220
+ return sdk(apiKey, async (client) => {
221
+ const response = await client.post('/v1/automation/run', {
225
222
  json: req,
226
223
  });
227
224
  return parseWithSchema(agentRunResponseSchema, response, 'Invalid agent run response');
228
- }
229
- catch (error) {
230
- rethrowSdkError(error);
231
- }
225
+ });
232
226
  }
233
- export async function runAsync(req, apiKey) {
234
- try {
235
- const response = await createSdkClient(apiKey).post('/v1/automation/run-async', {
227
+ export function runAsync(req, apiKey) {
228
+ return sdk(apiKey, async (client) => {
229
+ const response = await client.post('/v1/automation/run-async', {
236
230
  json: req,
237
231
  });
238
232
  return parseWithSchema(agentRunAsyncResponseSchema, response, 'Invalid async run response');
239
- }
240
- catch (error) {
241
- rethrowSdkError(error);
242
- }
233
+ });
243
234
  }
244
235
  export async function* runStream(req, apiKey, signal) {
245
236
  let stream = null;
@@ -268,110 +259,75 @@ export async function* runStream(req, apiKey, signal) {
268
259
  }
269
260
  }
270
261
  }
271
- export async function listRuns(opts, apiKey, timeout, baseUrl) {
272
- try {
273
- return await createSdkClient(apiKey, timeout, baseUrl).runs.list(opts);
274
- }
275
- catch (error) {
276
- rethrowSdkError(error);
277
- }
262
+ export function listRuns(opts, apiKey, timeout, baseUrl) {
263
+ return createSdkClient(apiKey, timeout, baseUrl).runs.list(opts).catch(rethrowSdkError);
278
264
  }
279
- export async function getRun(runId, apiKey) {
280
- try {
281
- return await createSdkClient(apiKey).runs.get(runId);
282
- }
283
- catch (error) {
284
- rethrowSdkError(error);
285
- }
265
+ export function getRun(runId, apiKey) {
266
+ return sdk(apiKey, (client) => client.runs.get(runId));
286
267
  }
287
- export async function getRunSteps(runId, apiKey) {
288
- try {
268
+ export function getRunSteps(runId, apiKey) {
269
+ return sdk(apiKey, async (client) => {
289
270
  // The public GET /v1/runs/:id route returns the base run payload plus `steps`;
290
271
  // the installed SDK runSchema only models the base run, so steps need this local schema.
291
- const response = await createSdkClient(apiKey).get(`/v1/runs/${encodeURIComponent(runId)}`, { params: { screenshots: 'none' } });
272
+ const response = await client.get(`/v1/runs/${encodeURIComponent(runId)}`, {
273
+ params: { screenshots: 'none' },
274
+ });
292
275
  return parseWithSchema(runStepsResponseSchema, response, 'Invalid run steps response');
293
- }
294
- catch (error) {
295
- rethrowSdkError(error);
296
- }
276
+ });
297
277
  }
298
- export async function searchQuery(params, apiKey) {
299
- try {
300
- const response = await createSdkClient(apiKey).search.query(params);
278
+ export function searchQuery(params, apiKey) {
279
+ return sdk(apiKey, async (client) => {
280
+ const response = await client.search.query(params);
301
281
  return searchQueryResponseSchema.parse(response);
302
- }
303
- catch (error) {
304
- rethrowSdkError(error);
305
- }
282
+ });
306
283
  }
307
- export async function fetchContentGet(params, apiKey) {
308
- try {
309
- const response = await createSdkClient(apiKey).fetch.getContents(params);
284
+ export function fetchContentGet(params, apiKey) {
285
+ return sdk(apiKey, async (client) => {
286
+ const response = await client.fetch.getContents(params);
310
287
  return fetchResponseSchema.parse(response);
311
- }
312
- catch (error) {
313
- rethrowSdkError(error);
314
- }
288
+ });
315
289
  }
316
- export async function browserSessionCreate(params, apiKey) {
317
- try {
318
- const response = await createSdkClient(apiKey).post('/v1/browser', { json: params });
290
+ export function browserSessionCreate(params, apiKey) {
291
+ return sdk(apiKey, async (client) => {
292
+ const response = await client.post('/v1/browser', { json: params });
319
293
  return browserSessionSchema.parse(response);
320
- }
321
- catch (error) {
322
- rethrowSdkError(error);
323
- }
294
+ });
324
295
  }
325
- export async function cancelRun(runId, apiKey) {
326
- try {
327
- const response = await createSdkClient(apiKey).post(`/v1/runs/${encodeURIComponent(runId)}/cancel`, { json: {} });
296
+ export function cancelRun(runId, apiKey) {
297
+ return sdk(apiKey, async (client) => {
298
+ const response = await client.post(`/v1/runs/${encodeURIComponent(runId)}/cancel`, {
299
+ json: {},
300
+ });
328
301
  return parseWithSchema(cancelRunResponseSchema, response, 'Invalid cancel run response');
329
- }
330
- catch (error) {
331
- rethrowSdkError(error);
332
- }
302
+ });
333
303
  }
334
304
  // ── Wallet ──────────────────────────────────────────────────────────────────
335
- export async function getWallet(apiKey) {
336
- try {
337
- return await createSdkClient(apiKey).wallet.get();
338
- }
339
- catch (error) {
340
- rethrowSdkError(error);
341
- }
305
+ export function getWallet(apiKey) {
306
+ return sdk(apiKey, (client) => client.wallet.get());
342
307
  }
343
- export async function submitBatch(req, apiKey) {
344
- try {
345
- const response = await createSdkClient(apiKey).post('/v1/automation/run-batch', {
308
+ export function submitBatch(req, apiKey) {
309
+ return sdk(apiKey, async (client) => {
310
+ const response = await client.post('/v1/automation/run-batch', {
346
311
  json: req,
347
312
  });
348
313
  return parseWithSchema(batchRunResponseSchema, response, 'Invalid batch run response');
349
- }
350
- catch (error) {
351
- rethrowSdkError(error);
352
- }
314
+ });
353
315
  }
354
- export async function getBatchRuns(runIds, apiKey) {
355
- try {
356
- const response = await createSdkClient(apiKey).post('/v1/runs/batch', {
316
+ export function getBatchRuns(runIds, apiKey) {
317
+ return sdk(apiKey, async (client) => {
318
+ const response = await client.post('/v1/runs/batch', {
357
319
  json: { run_ids: runIds },
358
320
  });
359
321
  return parseWithSchema(batchGetResponseSchema, response, 'Invalid batch get response');
360
- }
361
- catch (error) {
362
- rethrowSdkError(error);
363
- }
322
+ });
364
323
  }
365
- export async function cancelBatchRuns(runIds, apiKey) {
366
- try {
367
- const response = await createSdkClient(apiKey).post('/v1/runs/batch/cancel', {
324
+ export function cancelBatchRuns(runIds, apiKey) {
325
+ return sdk(apiKey, async (client) => {
326
+ const response = await client.post('/v1/runs/batch/cancel', {
368
327
  json: { run_ids: runIds },
369
328
  });
370
329
  return parseWithSchema(batchCancelResponseSchema, response, 'Invalid batch cancel response');
371
- }
372
- catch (error) {
373
- rethrowSdkError(error);
374
- }
330
+ });
375
331
  }
376
332
  const profileCreateResponseSchema = z.object({
377
333
  id: z.string(),
@@ -384,25 +340,19 @@ const profileUploadResponseSchema = z.object({
384
340
  domains_updated: z.array(z.string()),
385
341
  domains_failed: z.array(z.string()).optional(),
386
342
  });
387
- export async function profileCreate(name, apiKey) {
388
- try {
389
- const response = await createSdkClient(apiKey).post('/v1/profiles', {
343
+ export function profileCreate(name, apiKey) {
344
+ return sdk(apiKey, async (client) => {
345
+ const response = await client.post('/v1/profiles', {
390
346
  json: { name },
391
347
  });
392
348
  return parseWithSchema(profileCreateResponseSchema, response, 'Invalid profile create response');
393
- }
394
- catch (error) {
395
- rethrowSdkError(error);
396
- }
349
+ });
397
350
  }
398
- export async function profileUpload(profileId, cookies, apiKey) {
399
- try {
400
- const response = await createSdkClient(apiKey).post(`/v1/profiles/${encodeURIComponent(profileId)}/upload`, { json: { cookies } });
351
+ export function profileUpload(profileId, cookies, apiKey) {
352
+ return sdk(apiKey, async (client) => {
353
+ const response = await client.post(`/v1/profiles/${encodeURIComponent(profileId)}/upload`, { json: { cookies } });
401
354
  return parseWithSchema(profileUploadResponseSchema, response, 'Invalid profile upload response');
402
- }
403
- catch (error) {
404
- rethrowSdkError(error);
405
- }
355
+ });
406
356
  }
407
357
  // ── Vault ───────────────────────────────────────────────────────────────────
408
358
  // /v1/vault/* returns camelCase fields (unlike the snake_case rest of the API).
@@ -451,52 +401,37 @@ const vaultItemsSyncResponseSchema = z.object({
451
401
  removed: z.number(),
452
402
  }),
453
403
  });
454
- export async function vaultConnections(apiKey) {
455
- try {
456
- const response = await createSdkClient(apiKey).get('/v1/vault/connections');
404
+ export function vaultConnections(apiKey) {
405
+ return sdk(apiKey, async (client) => {
406
+ const response = await client.get('/v1/vault/connections');
457
407
  return parseWithSchema(vaultConnectionsListResponseSchema, response, 'Invalid vault connections response');
458
- }
459
- catch (error) {
460
- rethrowSdkError(error);
461
- }
408
+ });
462
409
  }
463
- export async function vaultConnect(req, apiKey) {
464
- try {
465
- const response = await createSdkClient(apiKey).post('/v1/vault/connections', {
410
+ export function vaultConnect(req, apiKey) {
411
+ return sdk(apiKey, async (client) => {
412
+ const response = await client.post('/v1/vault/connections', {
466
413
  json: req,
467
414
  });
468
415
  return parseWithSchema(vaultConnectResponseSchema, response, 'Invalid vault connect response');
469
- }
470
- catch (error) {
471
- rethrowSdkError(error);
472
- }
416
+ });
473
417
  }
474
- export async function vaultDisconnect(connectionId, apiKey) {
475
- try {
476
- const response = await createSdkClient(apiKey).del(`/v1/vault/connections/${encodeURIComponent(connectionId)}`);
418
+ export function vaultDisconnect(connectionId, apiKey) {
419
+ return sdk(apiKey, async (client) => {
420
+ const response = await client.del(`/v1/vault/connections/${encodeURIComponent(connectionId)}`);
477
421
  return parseWithSchema(vaultDisconnectResponseSchema, response, 'Invalid vault disconnect response');
478
- }
479
- catch (error) {
480
- rethrowSdkError(error);
481
- }
422
+ });
482
423
  }
483
- export async function vaultItems(apiKey) {
484
- try {
485
- const response = await createSdkClient(apiKey).get('/v1/vault/items');
424
+ export function vaultItems(apiKey) {
425
+ return sdk(apiKey, async (client) => {
426
+ const response = await client.get('/v1/vault/items');
486
427
  return parseWithSchema(vaultItemsListResponseSchema, response, 'Invalid vault items response');
487
- }
488
- catch (error) {
489
- rethrowSdkError(error);
490
- }
428
+ });
491
429
  }
492
- export async function vaultSync(apiKey) {
493
- try {
494
- const response = await createSdkClient(apiKey).post('/v1/vault/items/sync', {
430
+ export function vaultSync(apiKey) {
431
+ return sdk(apiKey, async (client) => {
432
+ const response = await client.post('/v1/vault/items/sync', {
495
433
  json: {},
496
434
  });
497
435
  return parseWithSchema(vaultItemsSyncResponseSchema, response, 'Invalid vault sync response');
498
- }
499
- catch (error) {
500
- rethrowSdkError(error);
501
- }
436
+ });
502
437
  }
@@ -19,7 +19,7 @@ const NO_BROWSER_REASONS = new Set([
19
19
  // Derived: these block `connect` on a browser sign-in when keyless.
20
20
  const BROWSER_SIGN_IN = new Set(ALL_HARNESSES.filter((harness) => {
21
21
  const native = NATIVE_BY_HARNESS.get(harness);
22
- return !!native?.loginArgs || !!native?.oauthInAdd;
22
+ return !!native?.loginArgs;
23
23
  }));
24
24
  // Derived: these refuse a keyless install, so no sign-in helps.
25
25
  const KEY_REQUIRED = new Set(ALL_HARNESSES.filter((harness) => !!NATIVE_BY_HARNESS.get(harness)?.keyRequired));
@@ -7,7 +7,6 @@ import { installTinyFishCli, } from './cli-install.js';
7
7
  import { authGate, mapConnectError, OAUTH_SIGN_IN_TIMEOUT_MS } from './connect-all-auth.js';
8
8
  import { actionable, computeExitCode, reloadHint, renderSummary } from './connect-all-summary.js';
9
9
  import { planText, uninstallHarness, uninstallPlanText } from './connect-all-uninstall.js';
10
- import { NATIVE_BY_HARNESS } from './connect-clients.js';
11
10
  import { createStdinPrompt, runCliFallback } from './connect-fallback.js';
12
11
  import { gateApiKey } from './connect-preflight.js';
13
12
  import { ConnectInterruptedError } from './connect-runtime.js';
@@ -19,21 +18,6 @@ import { errLine, outLine, setErrIndent } from './output.js';
19
18
  import { sendSetupCompleted, sendSetupStarted, } from './setup-telemetry.js';
20
19
  import { verifyMcpAuth, verifyMcpHealth } from './verify.js';
21
20
  const STEP_INDENT = ' ';
22
- // Derived: a native add carries the key, or the CLI holds it itself.
23
- const KEY_AUTH_CAPABLE = new Set(ALL_HARNESSES.filter((harness) => !!NATIVE_BY_HARNESS.get(harness)?.keyAuth || harnessSpec(harness).keyHeldByCli));
24
- async function runHarnessConnect(harness, opts, extras) {
25
- return connectHarness(harness, {
26
- apiKey: opts.apiKey,
27
- mcpUrl: opts.mcpUrl,
28
- launch: false,
29
- keyAuthOnly: extras.keyAuthOnly,
30
- authTimeoutMs: extras.authTimeoutMs,
31
- hoistedCliInstall: extras.hoistedCliInstall,
32
- onPostInstallFailed: extras.onPostInstallFailed,
33
- verbose: opts.verbose,
34
- deferOutro: extras.deferOutro,
35
- });
36
- }
37
21
  /** Undefined means Ctrl+C: the caller stops the whole run. */
38
22
  function runHoistedCliInstall(verbose) {
39
23
  const startedAt = Date.now();
@@ -90,8 +74,8 @@ async function processHarness(detection, opts, prompt, oauthBudget, hoistedCliIn
90
74
  return settled;
91
75
  const isTTY = detectHumanInitiated();
92
76
  const apiKey = validatedApiKey(opts.apiKey);
93
- // A key only leaves the own-OAuth set where the harness can actually carry one.
94
- const keyed = !!apiKey && KEY_AUTH_CAPABLE.has(harness);
77
+ // All harnesses carry a key; one that cannot fails harness_too_old.
78
+ const keyed = !!apiKey;
95
79
  const fixCommand = `tinyfish connect ${harness}`;
96
80
  const gate = await authGate(base, oauthBudget, fixCommand, { prompt, isTTY, keyed });
97
81
  if (gate.result)
@@ -103,7 +87,11 @@ async function processHarness(detection, opts, prompt, oauthBudget, hoistedCliIn
103
87
  let postInstallFailed = false;
104
88
  try {
105
89
  // A keyed install too old for key auth fails, no browser.
106
- authMode = await runHarnessConnect(harness, opts, {
90
+ authMode = await connectHarness(harness, {
91
+ apiKey: opts.apiKey,
92
+ mcpUrl: opts.mcpUrl,
93
+ launch: false,
94
+ verbose: opts.verbose,
107
95
  keyAuthOnly: keyed && !isTTY,
108
96
  authTimeoutMs: headlessAuth ? OAUTH_SIGN_IN_TIMEOUT_MS : undefined,
109
97
  hoistedCliInstall,
@@ -337,10 +325,6 @@ export async function runConnectAll(opts) {
337
325
  const prompt = isTTY ? createStdinPrompt() : undefined;
338
326
  const detections = detectInstalledHarnesses();
339
327
  const gateKey = validatedApiKey(opts.apiKey);
340
- // Keyed installs first: working tools land before sign-ins can block.
341
- if (gateKey) {
342
- detections.sort((a, b) => Number(KEY_AUTH_CAPABLE.has(b.harness)) - Number(KEY_AUTH_CAPABLE.has(a.harness)));
343
- }
344
328
  const pick = await resolvePick(detections, opts, prompt);
345
329
  if (pick.cancelled) {
346
330
  // Before summary and telemetry: an explicit nothing is not a setup.
@@ -349,10 +333,7 @@ export async function runConnectAll(opts) {
349
333
  }
350
334
  const { deselected, mode } = pick;
351
335
  // Mirrors processHarness's gate: headless, only keyless CLI-login harnesses skip.
352
- const willAttempt = (harness) => !deselected.has(harness) &&
353
- (isTTY ||
354
- !harnessSpec(harness).signInViaCliLogin ||
355
- (!!gateKey && KEY_AUTH_CAPABLE.has(harness)));
336
+ const willAttempt = (harness) => !deselected.has(harness) && (isTTY || !harnessSpec(harness).signInViaCliLogin || !!gateKey);
356
337
  const hoisted = hoistCliIfNeeded(detections, opts, willAttempt);
357
338
  if (hoisted === 'interrupted') {
358
339
  errLine('Setup interrupted — run the command again to finish.');
@@ -18,7 +18,7 @@ export declare const CURSOR_SKILL_TARGET: {
18
18
  readonly displayName: string;
19
19
  };
20
20
  /** The harness reads the key from its own store. */
21
- export interface SeededInstall {
21
+ interface SeededInstall {
22
22
  /** Pins the add child at the home the key landed in. */
23
23
  env: typeof process.env;
24
24
  /** Answers prompts `mcp add` still asks once the key is seeded. */
@@ -45,8 +45,6 @@ interface BaseMcpClient extends SupportedCommand, Pick<HarnessSpec, 'skillAgent'
45
45
  seedKey?: (apiKey: string) => SeededInstall;
46
46
  };
47
47
  loginArgs?: string[];
48
- /** Keyless `mcp add` itself blocks on the browser OAuth. */
49
- oauthInAdd?: boolean;
50
48
  removals: {
51
49
  args: string[];
52
50
  label: string;
@@ -69,17 +67,16 @@ interface BaseMcpClient extends SupportedCommand, Pick<HarnessSpec, 'skillAgent'
69
67
  };
70
68
  }
71
69
  /** Keyless installs use this argv; the harness signs itself in. */
72
- export interface OauthCapableMcpClient extends BaseMcpClient {
70
+ interface OauthCapableMcpClient extends BaseMcpClient {
73
71
  keyRequired?: false;
74
72
  addArgs: (mcpUrl: string) => string[];
75
73
  }
76
74
  /** No sign-in exists here, so connect refuses a keyless install (Hermes). */
77
- export interface KeyRequiredMcpClient extends BaseMcpClient {
75
+ interface KeyRequiredMcpClient extends BaseMcpClient {
78
76
  keyRequired: true;
79
77
  keyAuth: NonNullable<BaseMcpClient['keyAuth']>;
80
78
  /** Restoring any of these three would restore the OAuth fallback. */
81
79
  addArgs?: never;
82
- oauthInAdd?: never;
83
80
  loginArgs?: never;
84
81
  }
85
82
  export type NativeMcpClient = OauthCapableMcpClient | KeyRequiredMcpClient;
@@ -4,7 +4,7 @@ export declare const SEARCH_RETRY = "tinyfish search query --pretty -- \"<your q
4
4
  export declare const DEFAULT_VERIFICATION_QUERY = "today's top Hacker News story";
5
5
  export declare const MALFORMED_ENV_KEY_MESSAGE = "TINYFISH_API_KEY is set but malformed; it will override any stored key. Fix or unset it, then re-run.";
6
6
  export declare const MALFORMED_STORED_KEY_MESSAGE = "The stored TinyFish API key is malformed. Run: tinyfish auth login";
7
- export interface CliFallbackHooks {
7
+ interface CliFallbackHooks {
8
8
  onStepStart?: (stage: ConnectFailureStage) => void;
9
9
  onStepDone?: (phase: ConnectCheckpoint) => void;
10
10
  }
@@ -22,3 +22,4 @@ export interface CliFallbackOptions {
22
22
  export declare function createStdinPrompt(): Prompt;
23
23
  /** Steps print retry lines and return outcomes; only interrupts throw. */
24
24
  export declare function runCliFallback(opts: CliFallbackOptions): Promise<CliFallbackOutcome>;
25
+ export {};
@@ -1,11 +1,11 @@
1
1
  import type { Readable } from 'node:stream';
2
2
  import { type Harness, type HarnessDetection } from './harness-detect.js';
3
- export type PickSelection = Harness[] | 'cancel';
3
+ type PickSelection = Harness[] | 'cancel';
4
4
  export interface PickResult {
5
5
  selection: PickSelection;
6
6
  mode: 'pick' | 'pick_numeric';
7
7
  }
8
- export type PickerStdin = Readable & {
8
+ type PickerStdin = Readable & {
9
9
  setRawMode?: (raw: boolean) => unknown;
10
10
  };
11
11
  export interface PickerIo {
@@ -14,3 +14,4 @@ export interface PickerIo {
14
14
  prompt?: (question: string) => Promise<string>;
15
15
  }
16
16
  export declare function pickHarnesses(detections: HarnessDetection[], connectedBefore: ReadonlySet<string>, io?: PickerIo): Promise<PickResult>;
17
+ export {};
@@ -49,8 +49,6 @@ export function stageDurationOf(error) {
49
49
  const value = error?.stageDurationMs;
50
50
  return typeof value === 'number' ? value : undefined;
51
51
  }
52
- class PrerequisiteError extends ConnectStepError {
53
- }
54
52
  /** A bounded sign-in wait elapsed — unfinished, not broken. */
55
53
  export class SignInTimeoutError extends ConnectStepError {
56
54
  constructor(message, cause) {
@@ -190,7 +188,7 @@ function throwProbeFailure(client, result) {
190
188
  `${probeTimeoutMs(client) / 1000}s. Retry.`, 'timeout', { cause: result.error });
191
189
  }
192
190
  // It ran and exited non-zero, so the version is still answerable.
193
- throw new PrerequisiteError(client.supportCheck.unavailableMessage, 'harness_command_unsupported', { cause: result.error, harnessVersion: probeHarnessVersion(client) });
191
+ throw new ConnectStepError(client.supportCheck.unavailableMessage, 'harness_command_unsupported', { cause: result.error, harnessVersion: probeHarnessVersion(client) });
194
192
  }
195
193
  function supportProbeOutput(client) {
196
194
  const result = spawn.sync(client.command, client.supportCheck.args, {
@@ -201,7 +199,7 @@ function supportProbeOutput(client) {
201
199
  });
202
200
  // 127 is the shell's not-found code; a wrapper shim with no real binary behind it exits that.
203
201
  if (commandNotFound(result.error) || result.status === 127) {
204
- throw new PrerequisiteError(`${client.displayName} is not installed or not available on PATH.`, 'harness_not_installed', { cause: result.error });
202
+ throw new ConnectStepError(`${client.displayName} is not installed or not available on PATH.`, 'harness_not_installed', { cause: result.error });
205
203
  }
206
204
  if (result.error || result.status !== 0) {
207
205
  throwProbeFailure(client, result);
@@ -218,7 +216,7 @@ function requireEssential(client, output) {
218
216
  if (client.supportCheck.patterns.every((pattern) => pattern.test(output)))
219
217
  return;
220
218
  debugProbeOutput(client, output);
221
- throw new PrerequisiteError(client.supportCheck.unavailableMessage, 'harness_too_old', {
219
+ throw new ConnectStepError(client.supportCheck.unavailableMessage, 'harness_too_old', {
222
220
  harnessVersion: probeHarnessVersion(client),
223
221
  });
224
222
  }
@@ -235,7 +233,7 @@ function resolveVariant(client, output) {
235
233
  return undefined;
236
234
  // Unacknowledged policy warnings can block the install; fail before it does.
237
235
  debugProbeOutput(client, output);
238
- throw new PrerequisiteError(`${client.displayName} lists \`${unknown}\`, which this TinyFish CLI does not recognise. ` +
236
+ throw new ConnectStepError(`${client.displayName} lists \`${unknown}\`, which this TinyFish CLI does not recognise. ` +
239
237
  'Upgrade the TinyFish CLI with `tinyfish upgrade` and retry.', 'harness_too_old', { harnessVersion: probeHarnessVersion(client) });
240
238
  }
241
239
  /** Refresh paths need the advertised alternative without connect's version probe. */
@@ -67,17 +67,17 @@ export interface HarnessSpec {
67
67
  /** Printed after a successful connect, on both the launch and non-launch paths. */
68
68
  postConnectNote?: string;
69
69
  /** Sign-in is the CLI's own interactive `tinyfish auth login`. */
70
- signInViaCliLogin: boolean;
70
+ signInViaCliLogin?: true;
71
71
  /** Stored-key auth verification works against this harness's config. */
72
- canVerifyAuth: boolean;
72
+ canVerifyAuth?: true;
73
73
  /** The CLI holds or writes the key; no harness `mcp add` carries it. */
74
- keyHeldByCli: boolean;
74
+ keyHeldByCli?: true;
75
75
  /** Connect writes the MCP config file; no harness binary is spawned. */
76
- cliWritesConfig: boolean;
76
+ cliWritesConfig?: true;
77
77
  /** Binary resolves the config path; absent binary, nothing reads it. */
78
78
  configPathFromBinary?: true;
79
79
  /** `mcp add` succeeds unauthenticated; OAuth lands at first tool use. */
80
- authDeferredAtInstall: boolean;
80
+ authDeferredAtInstall?: true;
81
81
  }
82
82
  /** One entry per harness; every per-harness list derives from it. */
83
83
  export declare const HARNESS_SPECS: {
@@ -110,11 +110,6 @@ export declare const HARNESS_SPECS: {
110
110
  args: string[];
111
111
  label: string;
112
112
  }[];
113
- signInViaCliLogin: false;
114
- canVerifyAuth: false;
115
- keyHeldByCli: false;
116
- cliWritesConfig: false;
117
- authDeferredAtInstall: false;
118
113
  };
119
114
  codex: {
120
115
  command: string;
@@ -136,10 +131,6 @@ export declare const HARNESS_SPECS: {
136
131
  deferred: string;
137
132
  };
138
133
  loginArgs: string[];
139
- signInViaCliLogin: false;
140
- canVerifyAuth: false;
141
- keyHeldByCli: false;
142
- cliWritesConfig: false;
143
134
  authDeferredAtInstall: true;
144
135
  };
145
136
  cursor: {
@@ -148,11 +139,9 @@ export declare const HARNESS_SPECS: {
148
139
  configDir: string;
149
140
  reloadAction: string;
150
141
  skillAgent: "cursor";
151
- signInViaCliLogin: false;
152
142
  canVerifyAuth: true;
153
143
  keyHeldByCli: true;
154
144
  cliWritesConfig: true;
155
- authDeferredAtInstall: false;
156
145
  };
157
146
  grok: {
158
147
  command: string;
@@ -175,11 +164,6 @@ export declare const HARNESS_SPECS: {
175
164
  viaEnv: true;
176
165
  };
177
166
  signInHint: string;
178
- signInViaCliLogin: false;
179
- canVerifyAuth: false;
180
- keyHeldByCli: false;
181
- cliWritesConfig: false;
182
- authDeferredAtInstall: false;
183
167
  };
184
168
  hermes: {
185
169
  command: string;
@@ -201,11 +185,7 @@ export declare const HARNESS_SPECS: {
201
185
  label: string;
202
186
  };
203
187
  postConnectNote: string;
204
- signInViaCliLogin: false;
205
- canVerifyAuth: false;
206
188
  keyHeldByCli: true;
207
- cliWritesConfig: false;
208
- authDeferredAtInstall: false;
209
189
  };
210
190
  omp: {
211
191
  command: string;
@@ -213,11 +193,9 @@ export declare const HARNESS_SPECS: {
213
193
  configDir: string;
214
194
  reloadAction: string;
215
195
  configPathFromBinary: true;
216
- signInViaCliLogin: false;
217
196
  canVerifyAuth: true;
218
197
  keyHeldByCli: true;
219
198
  cliWritesConfig: true;
220
- authDeferredAtInstall: false;
221
199
  };
222
200
  openclaw: {
223
201
  command: string;
@@ -235,8 +213,6 @@ export declare const HARNESS_SPECS: {
235
213
  signInViaCliLogin: true;
236
214
  canVerifyAuth: true;
237
215
  keyHeldByCli: true;
238
- cliWritesConfig: false;
239
- authDeferredAtInstall: false;
240
216
  };
241
217
  opencode: {
242
218
  command: string;
@@ -259,11 +235,6 @@ export declare const HARNESS_SPECS: {
259
235
  loginArgs: string[];
260
236
  removals: never[];
261
237
  postConnectNote: string;
262
- signInViaCliLogin: false;
263
- canVerifyAuth: false;
264
- keyHeldByCli: false;
265
- cliWritesConfig: false;
266
- authDeferredAtInstall: false;
267
238
  };
268
239
  };
269
240
  /** Derived from spec keys; one entry extends every union. */