@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.
@@ -82,11 +82,6 @@ export const HARNESS_SPECS = {
82
82
  label: 'local TinyFish registration',
83
83
  },
84
84
  ],
85
- signInViaCliLogin: false,
86
- canVerifyAuth: false,
87
- keyHeldByCli: false,
88
- cliWritesConfig: false,
89
- authDeferredAtInstall: false,
90
85
  },
91
86
  codex: {
92
87
  command: 'codex',
@@ -110,10 +105,6 @@ export const HARNESS_SPECS = {
110
105
  // `--oauth-resource` duplicates the resource Codex discovers; Clerk rejects both.
111
106
  // `mcp add` still runs OAuth inline, so `loginArgs` stays a retry.
112
107
  loginArgs: ['mcp', 'login', 'tinyfish'],
113
- signInViaCliLogin: false,
114
- canVerifyAuth: false,
115
- keyHeldByCli: false,
116
- cliWritesConfig: false,
117
108
  // TODO(PF-3580): connect now probes Codex sign-in, so this over-warns a signed-in install.
118
109
  authDeferredAtInstall: true,
119
110
  },
@@ -123,11 +114,9 @@ export const HARNESS_SPECS = {
123
114
  configDir: '.cursor',
124
115
  reloadAction: 'reload the window',
125
116
  skillAgent: 'cursor',
126
- signInViaCliLogin: false,
127
117
  canVerifyAuth: true,
128
118
  keyHeldByCli: true,
129
119
  cliWritesConfig: true,
130
- authDeferredAtInstall: false,
131
120
  },
132
121
  grok: {
133
122
  command: 'grok',
@@ -148,11 +137,6 @@ export const HARNESS_SPECS = {
148
137
  // Grok expands `${VAR}` in headers at load, so the key stays out of argv and config.toml.
149
138
  header: { name: 'Authorization', sep: ': ', valuePrefix: 'Bearer ', viaEnv: true },
150
139
  signInHint: GROK_SIGN_IN_HINT,
151
- signInViaCliLogin: false,
152
- canVerifyAuth: false,
153
- keyHeldByCli: false,
154
- cliWritesConfig: false,
155
- authDeferredAtInstall: false,
156
140
  },
157
141
  hermes: {
158
142
  command: 'hermes',
@@ -175,12 +159,8 @@ export const HARNESS_SPECS = {
175
159
  label: 'TinyFish web plugin from Hermes',
176
160
  },
177
161
  postConnectNote: HERMES_RESTART_NOTE,
178
- signInViaCliLogin: false,
179
- // Stays false: keyed already covers auth; true breaks keyless installs.
180
- canVerifyAuth: false,
162
+ // No canVerifyAuth: keyed already covers auth; true breaks keyless installs.
181
163
  keyHeldByCli: true,
182
- cliWritesConfig: false,
183
- authDeferredAtInstall: false,
184
164
  },
185
165
  omp: {
186
166
  command: 'omp',
@@ -188,11 +168,9 @@ export const HARNESS_SPECS = {
188
168
  configDir: '.omp',
189
169
  reloadAction: 'restart it',
190
170
  configPathFromBinary: true,
191
- signInViaCliLogin: false,
192
171
  canVerifyAuth: true,
193
172
  keyHeldByCli: true,
194
173
  cliWritesConfig: true,
195
- authDeferredAtInstall: false,
196
174
  },
197
175
  openclaw: {
198
176
  command: 'openclaw',
@@ -212,8 +190,6 @@ export const HARNESS_SPECS = {
212
190
  signInViaCliLogin: true,
213
191
  canVerifyAuth: true,
214
192
  keyHeldByCli: true,
215
- cliWritesConfig: false,
216
- authDeferredAtInstall: false,
217
193
  },
218
194
  opencode: {
219
195
  command: 'opencode',
@@ -236,11 +212,6 @@ export const HARNESS_SPECS = {
236
212
  // `opencode mcp add` upserts by name and ships no `mcp remove`.
237
213
  removals: [],
238
214
  postConnectNote: OPENCODE_MODEL_NOTE,
239
- signInViaCliLogin: false,
240
- canVerifyAuth: false,
241
- keyHeldByCli: false,
242
- cliWritesConfig: false,
243
- authDeferredAtInstall: false,
244
215
  },
245
216
  };
246
217
  export const ALL_HARNESSES = Object.keys(HARNESS_SPECS);
@@ -1,4 +1,3 @@
1
- export declare const HERMES_PROBE_TIMEOUT_MS = 15000;
2
1
  /** Asking Hermes beats reimplementing its profile override, which we'd drift from. */
3
2
  export declare function resolveHermesHome(): string | undefined;
4
3
  export declare const HERMES_KEY_VAR = "MCP_TINYFISH_API_KEY";
@@ -7,14 +7,12 @@ import { HARNESS_PROBE_TIMEOUT_MS } from './constants.js';
7
7
  import { sanitizeLine } from './output.js';
8
8
  // display_hermes_home() abbreviates under $HOME, so the value is not always absolute.
9
9
  const HERMES_HOME_LINE = /^\s*hermes_home:\s*(\S.*?)\s*$/m;
10
- // One value: the next cold-start bump must move both probes together.
11
- export const HERMES_PROBE_TIMEOUT_MS = HARNESS_PROBE_TIMEOUT_MS;
12
10
  /** Asking Hermes beats reimplementing its profile override, which we'd drift from. */
13
11
  export function resolveHermesHome() {
14
12
  const result = spawn.sync('hermes', ['dump'], {
15
13
  encoding: 'utf8',
16
14
  env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1' },
17
- timeout: HERMES_PROBE_TIMEOUT_MS,
15
+ timeout: HARNESS_PROBE_TIMEOUT_MS,
18
16
  });
19
17
  if (result.error || result.status !== 0)
20
18
  return undefined;
@@ -7,7 +7,7 @@ import { z } from 'zod';
7
7
  import { capturedOutput, replay, SKILL_INSTALL_TIMEOUT_MS, STEP_MAX_BUFFER, } from './cli-install.js';
8
8
  import { commandNotFound, ConnectStepError, spawnStepError, throwIfInterrupted, } from './connect-runtime.js';
9
9
  import { HARNESS_PROBE_TIMEOUT_MS } from './constants.js';
10
- import { errLine } from './output.js';
10
+ import { errLine, parseJson } from './output.js';
11
11
  // Bump per CLI release.
12
12
  export const HERMES_PLUGIN_SHA = '496cd63fefd982bbaa8a85ce78ef2270d700984f';
13
13
  // Bump with the SHA; the source is `hermes/plugin.yaml` at that commit.
@@ -127,14 +127,6 @@ const hermesPluginStatusSchema = z.looseObject({
127
127
  provider_available: z.boolean().nullish(),
128
128
  web_backend_configured: z.boolean(),
129
129
  });
130
- function parseJson(text) {
131
- try {
132
- return JSON.parse(text);
133
- }
134
- catch {
135
- return undefined;
136
- }
137
- }
138
130
  /** `status --json` always exits 0; a non-zero exit means the subcommand is absent. */
139
131
  export function readHermesPluginStatus(home) {
140
132
  const result = spawn.sync('hermes', ['tinyfish', 'status', '--json'], {
@@ -16,3 +16,5 @@ export declare function errLine(line: string): void;
16
16
  export declare function warnLine(line: string): void;
17
17
  export declare function err(data: unknown): void;
18
18
  export declare function handleApiError(e: unknown): never;
19
+ /** Undefined, not a throw: callers hand the value to a Zod safeParse. */
20
+ export declare function parseJson(text: string): unknown;
@@ -70,3 +70,12 @@ export function handleApiError(e) {
70
70
  }
71
71
  process.exit(1);
72
72
  }
73
+ /** Undefined, not a throw: callers hand the value to a Zod safeParse. */
74
+ export function parseJson(text) {
75
+ try {
76
+ return JSON.parse(text);
77
+ }
78
+ catch {
79
+ return undefined;
80
+ }
81
+ }
@@ -20,7 +20,6 @@ export interface RegistrationStatus {
20
20
  recordedAuthMode?: RecordedAuthMode;
21
21
  reason?: string;
22
22
  }
23
- export declare function codexOauthCompleted(): boolean | undefined;
24
23
  /** Per-harness sign-in probes; connect composes these rather than the descriptor carrying them. */
25
24
  export declare const SIGN_IN_PROBES: Partial<Record<AgentClient, () => boolean | undefined>>;
26
25
  /** Never throws; a failed probe is `unknown` with a reason, so no caller can read it as healthy. */
@@ -11,6 +11,15 @@ import { readOmpTinyfishEntry } from './omp-config.js';
11
11
  import { readHermesKey, resolveHermesHome } from './hermes-env.js';
12
12
  import { readHermesEntry } from './hermes-config.js';
13
13
  import { detectInstalledHarnesses, harnessConfigPath, harnessDisplayPath, AuthMode, Registered, } from './harness-detect.js';
14
+ const NOT_REGISTERED = Object.freeze({
15
+ registered: Registered.No,
16
+ authMode: AuthMode.Unknown,
17
+ });
18
+ const unverified = (reason) => ({
19
+ registered: Registered.Unknown,
20
+ authMode: AuthMode.Unknown,
21
+ reason,
22
+ });
14
23
  // A list, verified against codex 0.146. A name-keyed map was accepted here too, but every
15
24
  // field is optional, so that arm parsed *any* object-of-objects: a wrapper like
16
25
  // `{"servers": {...}}` decoded as one entry named `servers` and reported TinyFish absent.
@@ -151,15 +160,11 @@ function parseMcpGet(output) {
151
160
  function fromMcpGet(command) {
152
161
  const probe = runProbe(command, ['mcp', 'get', 'tinyfish']);
153
162
  if (probe.outcome === 'unavailable') {
154
- return { registered: Registered.Unknown, authMode: AuthMode.Unknown, reason: probe.reason };
163
+ return unverified(probe.reason);
155
164
  }
156
165
  if (probe.exitCode !== 0) {
157
166
  if (UNSUPPORTED_SUBCOMMAND.test(probe.output)) {
158
- return {
159
- registered: Registered.Unknown,
160
- authMode: AuthMode.Unknown,
161
- reason: `\`${command} mcp get\` is unsupported by this version`,
162
- };
167
+ return unverified(`\`${command} mcp get\` is unsupported by this version`);
163
168
  }
164
169
  // The roster is free; `mcp list` health-checks every server.
165
170
  const scoped = tinyfishServerName(probe.output);
@@ -178,13 +183,9 @@ function fromMcpGet(command) {
178
183
  return plugin;
179
184
  // A broken CLI also exits nonzero, and reading that as absence earns a spurious repair.
180
185
  if (!NO_SUCH_SERVER.test(probe.output)) {
181
- return {
182
- registered: Registered.Unknown,
183
- authMode: AuthMode.Unknown,
184
- reason: `\`${command} mcp get\` failed without reporting the server as absent`,
185
- };
186
+ return unverified(`\`${command} mcp get\` failed without reporting the server as absent`);
186
187
  }
187
- return { registered: Registered.No, authMode: AuthMode.Unknown };
188
+ return NOT_REGISTERED;
188
189
  }
189
190
  return parseMcpGet(probe.output);
190
191
  }
@@ -218,7 +219,7 @@ function readCodexEntry() {
218
219
  }
219
220
  // `mcp add` exits 0 whether or not its inline OAuth finished, so `not_logged_in` is the only
220
221
  // evidence of an abandoned sign-in; every other state, older Codex included, is unknown.
221
- export function codexOauthCompleted() {
222
+ function codexOauthCompleted() {
222
223
  const read = readCodexEntry();
223
224
  if ('reason' in read)
224
225
  return undefined;
@@ -255,10 +256,10 @@ function envKeyVerdict(envVar) {
255
256
  function probeCodex() {
256
257
  const read = readCodexEntry();
257
258
  if ('reason' in read)
258
- return { registered: Registered.Unknown, authMode: AuthMode.Unknown, reason: read.reason };
259
+ return unverified(read.reason);
259
260
  const entry = read.entry;
260
261
  if (!entry)
261
- return { registered: Registered.No, authMode: AuthMode.Unknown };
262
+ return NOT_REGISTERED;
262
263
  const codexUrl = entry.transport?.url;
263
264
  // Read from this entry: codex emits `bearer_token_env_var` on every HTTP server, so testing
264
265
  // the whole payload reports api-key for TinyFish because some other server carries a key.
@@ -279,14 +280,10 @@ function probeCodex() {
279
280
  function probeCursor() {
280
281
  const entry = readCursorTinyfishEntry();
281
282
  if (entry.error) {
282
- return {
283
- registered: Registered.Unknown,
284
- authMode: AuthMode.Unknown,
285
- reason: 'mcp.json exists but could not be read or parsed',
286
- };
283
+ return unverified('mcp.json exists but could not be read or parsed');
287
284
  }
288
285
  if (!entry.present)
289
- return { registered: Registered.No, authMode: AuthMode.Unknown };
286
+ return NOT_REGISTERED;
290
287
  return {
291
288
  registered: Registered.Yes,
292
289
  authMode: entry.hasApiKeyHeader ? AuthMode.ApiKey : AuthMode.Unknown,
@@ -298,21 +295,13 @@ function probeCursor() {
298
295
  function probeOmp() {
299
296
  const entry = readOmpTinyfishEntry();
300
297
  if (!entry) {
301
- return {
302
- registered: Registered.Unknown,
303
- authMode: AuthMode.Unknown,
304
- reason: '`omp config path` did not report a config directory',
305
- };
298
+ return unverified('`omp config path` did not report a config directory');
306
299
  }
307
300
  if (entry.error) {
308
- return {
309
- registered: Registered.Unknown,
310
- authMode: AuthMode.Unknown,
311
- reason: 'mcp.json exists but could not be read or parsed',
312
- };
301
+ return unverified('mcp.json exists but could not be read or parsed');
313
302
  }
314
303
  if (!entry.present)
315
- return { registered: Registered.No, authMode: AuthMode.Unknown };
304
+ return NOT_REGISTERED;
316
305
  // A hand-written `${VAR}` header resolves like Codex's env-var key.
317
306
  const keyVerdict = entry.keyTemplateVar
318
307
  ? envKeyVerdict(entry.keyTemplateVar)
@@ -342,34 +331,22 @@ function fromMcpList(command, output, registeredMode, urlPattern) {
342
331
  };
343
332
  }
344
333
  if (!MCP_LIST_HEADER.test(output)) {
345
- return {
346
- registered: Registered.Unknown,
347
- authMode: AuthMode.Unknown,
348
- reason: `could not interpret \`${command} mcp list\` output`,
349
- };
334
+ return unverified(`could not interpret \`${command} mcp list\` output`);
350
335
  }
351
- return { registered: Registered.No, authMode: AuthMode.Unknown };
336
+ return NOT_REGISTERED;
352
337
  }
353
338
  // `hermes mcp list` only pretty-prints this file, so connect's gate reads it too.
354
339
  function probeHermes() {
355
340
  const home = resolveHermesHome();
356
341
  if (!home) {
357
- return {
358
- registered: Registered.Unknown,
359
- authMode: AuthMode.Unknown,
360
- reason: '`hermes dump` did not report a home directory',
361
- };
342
+ return unverified('`hermes dump` did not report a home directory');
362
343
  }
363
344
  const entry = readHermesEntry(home);
364
345
  if (entry.state === 'unreadable' || entry.state === 'unparseable') {
365
- return {
366
- registered: Registered.Unknown,
367
- authMode: AuthMode.Unknown,
368
- reason: `Hermes' config.yaml could not be ${entry.state === 'unreadable' ? 'read' : 'parsed'}`,
369
- };
346
+ return unverified(`Hermes' config.yaml could not be ${entry.state === 'unreadable' ? 'read' : 'parsed'}`);
370
347
  }
371
348
  if (entry.state === 'absent')
372
- return { registered: Registered.No, authMode: AuthMode.Unknown };
349
+ return NOT_REGISTERED;
373
350
  // `mcp add` saves a disabled entry on a failed connect, which connect refuses too.
374
351
  const enabled = entry.state === 'enabled' ? {} : { connected: false };
375
352
  // The .env key is Hermes-wide; the header template ties it here.
@@ -393,16 +370,12 @@ function probeOpenClaw() {
393
370
  entries = fs.readdirSync(skillsDir);
394
371
  }
395
372
  catch {
396
- return {
397
- registered: Registered.Unknown,
398
- authMode: AuthMode.Unknown,
399
- reason: `no global skills directory at ${harnessDisplayPath('openclaw')}/skills; OpenClaw layout is unverified`,
400
- };
373
+ return unverified(`no global skills directory at ${harnessDisplayPath('openclaw')}/skills; OpenClaw layout is unverified`);
401
374
  }
402
375
  // A bare substring also matched a skill merely named `not-tinyfish-thing`.
403
376
  return entries.some((name) => SKILL_DIR_IS_TINYFISH.test(name))
404
377
  ? { registered: Registered.Yes, authMode: AuthMode.ApiKey, usesCliKey: true }
405
- : { registered: Registered.No, authMode: AuthMode.Unknown };
378
+ : NOT_REGISTERED;
406
379
  }
407
380
  // `opencode mcp list` prints no headers, so a key-authed registration is indistinguishable.
408
381
  function probeOpencode() {
@@ -410,7 +383,7 @@ function probeOpencode() {
410
383
  const probe = runProbe('opencode', args);
411
384
  if (probe.outcome !== 'ran' || probe.exitCode !== 0) {
412
385
  const reason = probeReason('opencode', args, probe);
413
- return { registered: Registered.Unknown, authMode: AuthMode.Unknown, reason };
386
+ return unverified(reason);
414
387
  }
415
388
  return fromMcpList('opencode', probe.output, AuthMode.Unknown, OPENCODE_ROW_URL);
416
389
  }
@@ -440,11 +413,7 @@ function fromGrokPlugin() {
440
413
  // repair that double-registers a working one. Anything but a list is unknown, not absent.
441
414
  const shape = grokPluginSchema.safeParse(listed);
442
415
  if (!shape.success) {
443
- return {
444
- registered: Registered.Unknown,
445
- authMode: AuthMode.Unknown,
446
- reason: '`grok plugin list --json` returned an unexpected shape',
447
- };
416
+ return unverified('`grok plugin list --json` returned an unexpected shape');
448
417
  }
449
418
  if (!shape.data.some((plugin) => plugin.name === 'tinyfish'))
450
419
  return undefined;
@@ -477,36 +446,24 @@ function probeGrok() {
477
446
  const args = ['mcp', 'list', '--json'];
478
447
  const probe = runProbe('grok', args);
479
448
  if (probe.outcome === 'unavailable') {
480
- return { registered: Registered.Unknown, authMode: AuthMode.Unknown, reason: probe.reason };
449
+ return unverified(probe.reason);
481
450
  }
482
451
  if (probe.exitCode !== 0) {
483
452
  if (UNSUPPORTED_SUBCOMMAND.test(probe.output)) {
484
- return {
485
- registered: Registered.Unknown,
486
- authMode: AuthMode.Unknown,
487
- reason: 'this Grok build has no `mcp list --json`',
488
- };
453
+ return unverified('this Grok build has no `mcp list --json`');
489
454
  }
490
- return {
491
- registered: Registered.Unknown,
492
- authMode: AuthMode.Unknown,
493
- reason: probeReason('grok', args, probe),
494
- };
455
+ return unverified(probeReason('grok', args, probe));
495
456
  }
496
457
  let entries;
497
458
  try {
498
459
  entries = grokListSchema.parse(JSON.parse(probe.stdout));
499
460
  }
500
461
  catch {
501
- return {
502
- registered: Registered.Unknown,
503
- authMode: AuthMode.Unknown,
504
- reason: '`grok mcp list --json` did not return the expected shape',
505
- };
462
+ return unverified('`grok mcp list --json` did not return the expected shape');
506
463
  }
507
464
  const entry = entries.find((server) => server.name === 'tinyfish');
508
465
  if (!entry)
509
- return fromGrokPlugin() ?? { registered: Registered.No, authMode: AuthMode.Unknown };
466
+ return fromGrokPlugin() ?? NOT_REGISTERED;
510
467
  return {
511
468
  registered: Registered.Yes,
512
469
  ...grokKeyAuth(entry.headers ?? {}),
@@ -549,12 +506,7 @@ export function detectRegistrations(harnesses) {
549
506
  if (process.env['TINYFISH_DEBUG']) {
550
507
  errLine(`probe ${detection.harness} threw: ${e instanceof Error ? e.message : String(e)}`);
551
508
  }
552
- return {
553
- ...base,
554
- registered: Registered.Unknown,
555
- authMode: AuthMode.Unknown,
556
- reason: 'the probe failed unexpectedly',
557
- };
509
+ return { ...base, ...unverified('the probe failed unexpectedly') };
558
510
  }
559
511
  });
560
512
  }
@@ -6,9 +6,6 @@ import { AuthMode, Registered } from './harness-detect.js';
6
6
  * opts out — a privacy switch must not fail closed on `true`.
7
7
  */
8
8
  export declare function telemetryDisabled(): boolean;
9
- export declare function isTrustedTelemetryOrigin(endpoint: string): boolean;
10
- /** SHA-256 lookup key only, and only to trusted origins — the raw key never rides telemetry. */
11
- export declare function telemetryHeaders(endpoint: string, apiKey?: string): Record<string, string>;
12
9
  /**
13
10
  * The one POST to `/api/cli/connect-event`, and the only place its security-relevant
14
11
  * properties live: trusted-origin key gating, `redirect: "error"`, the 2s cap, notice
@@ -84,7 +81,7 @@ export declare const setupCompletedPayloadSchema: z.ZodObject<{
84
81
  }>>;
85
82
  connect_attempt_id: z.ZodOptional<z.ZodUUID>;
86
83
  }, z.core.$strip>;
87
- export type SetupCompletedPayload = z.infer<typeof setupCompletedPayloadSchema>;
84
+ type SetupCompletedPayload = z.infer<typeof setupCompletedPayloadSchema>;
88
85
  export type HarnessResult = z.infer<typeof harnessResultSchema>;
89
86
  export type SetupMode = NonNullable<SetupCompletedPayload['mode']>;
90
87
  export type UpgradeOutcome = 'updated' | 'failed' | 'interrupted';
@@ -93,12 +90,11 @@ export type UpgradeScope = 'both' | 'cli' | 'skill';
93
90
  /** `toVersion` is null when the run was interrupted or the installed version could not be read. */
94
91
  export declare function sendUpgradeCompleted(fromVersion: string, toVersion: string | null, outcome: UpgradeOutcome, scope: UpgradeScope, apiKey?: string): Promise<void>;
95
92
  export declare function sendSetupCompleted(mcpUrl: string, harnesses: HarnessResult[], apiKey?: string, mode?: SetupMode, attemptId?: string): Promise<void>;
96
- export declare const DOCTOR_HARNESS_SCOPES: readonly ["all", ...("openclaw" | "omp" | "grok" | "cursor" | "codex" | "hermes" | "opencode" | "claude-code")[]];
97
93
  export declare const DOCTOR_ERROR_CLASSES: readonly ["command_not_found", "timeout", "spawn_error", "nonzero_exit", "unexpected_error"];
98
94
  export type DoctorErrorClass = (typeof DOCTOR_ERROR_CLASSES)[number];
99
95
  /** Derived, not read: the catch is generic, so only the code is trustworthy. */
100
96
  export declare function doctorErrorClass(error: unknown): DoctorErrorClass;
101
- export declare const doctorCompletedPayloadSchema: z.ZodObject<{
97
+ declare const doctorCompletedPayloadSchema: z.ZodObject<{
102
98
  outcome: z.ZodLiteral<"completed">;
103
99
  schema_version: z.ZodInt;
104
100
  cli_version: z.ZodString;
@@ -163,7 +159,7 @@ export declare const doctorCompletedPayloadSchema: z.ZodObject<{
163
159
  hermes_plugin_version: z.ZodOptional<z.ZodString>;
164
160
  hermes_plugin_expected_version: z.ZodOptional<z.ZodString>;
165
161
  }, z.core.$strip>;
166
- export declare const doctorCouldNotRunPayloadSchema: z.ZodObject<{
162
+ declare const doctorCouldNotRunPayloadSchema: z.ZodObject<{
167
163
  outcome: z.ZodLiteral<"could_not_run">;
168
164
  error_class: z.ZodEnum<{
169
165
  timeout: "timeout";
@@ -20,7 +20,7 @@ export function telemetryDisabled() {
20
20
  // `--url` accepts any http(s) URL, so a user-supplied origin must never receive the key.
21
21
  const TRUSTED_TELEMETRY_HOST_SUFFIXES = ['.tinyfish.ai', '.tinyfish.io'];
22
22
  const TRUSTED_TELEMETRY_LOOPBACK_HOSTS = ['localhost', '127.0.0.1', '[::1]'];
23
- export function isTrustedTelemetryOrigin(endpoint) {
23
+ function isTrustedTelemetryOrigin(endpoint) {
24
24
  let url;
25
25
  try {
26
26
  url = new URL(endpoint);
@@ -38,7 +38,7 @@ export function isTrustedTelemetryOrigin(endpoint) {
38
38
  return TRUSTED_TELEMETRY_HOST_SUFFIXES.some((suffix) => host === suffix.slice(1) || host.endsWith(suffix));
39
39
  }
40
40
  /** SHA-256 lookup key only, and only to trusted origins — the raw key never rides telemetry. */
41
- export function telemetryHeaders(endpoint, apiKey) {
41
+ function telemetryHeaders(endpoint, apiKey) {
42
42
  const headers = { 'content-type': 'application/json' };
43
43
  if (apiKey && validateKeyFormat(apiKey) && isTrustedTelemetryOrigin(endpoint)) {
44
44
  headers['x-api-key-lookup'] = createHash('sha256').update(apiKey).digest('hex');
@@ -210,7 +210,7 @@ export async function sendSetupCompleted(mcpUrl, harnesses, apiKey, mode, attemp
210
210
  }
211
211
  await postConnectEvent({ endpoint, body, attempts: 2, label: 'setup', apiKey });
212
212
  }
213
- export const DOCTOR_HARNESS_SCOPES = ['all', ...ALL_HARNESSES];
213
+ const DOCTOR_HARNESS_SCOPES = ['all', ...ALL_HARNESSES];
214
214
  // Mirrors the route's cap; drops what the route would 400 un-retried.
215
215
  const versionSchema = z
216
216
  .string()
@@ -237,7 +237,7 @@ export function doctorErrorClass(error) {
237
237
  return 'timeout';
238
238
  return code ? 'spawn_error' : 'unexpected_error';
239
239
  }
240
- export const doctorCompletedPayloadSchema = z.object({
240
+ const doctorCompletedPayloadSchema = z.object({
241
241
  outcome: z.literal('completed'),
242
242
  schema_version: z.int().positive(),
243
243
  cli_version: z.string().max(32),
@@ -274,7 +274,7 @@ export const doctorCompletedPayloadSchema = z.object({
274
274
  hermes_plugin_expected_version: versionSchema.optional(),
275
275
  });
276
276
  // Detection threw, so there is no report to aggregate: enumerated outcome and timing only.
277
- export const doctorCouldNotRunPayloadSchema = z.object({
277
+ const doctorCouldNotRunPayloadSchema = z.object({
278
278
  outcome: z.literal('could_not_run'),
279
279
  error_class: z.enum(DOCTOR_ERROR_CLASSES),
280
280
  schema_version: z.int().positive(),
@@ -6,7 +6,7 @@ import { captureStdio, capturedOutput, replay, SKILL_INSTALL_TIMEOUT_MS, STEP_MA
6
6
  import { CURSOR_SKILL_TARGET, NATIVE_MCP_CLIENTS, OPENCLAW, openclawSkillInstallArgs, } from './connect-clients.js';
7
7
  import { ConnectInterruptedError, ConnectStepError, probeSupportVariant, spawnFailureDetail, spawnStepError, throwIfInterrupted, } from './connect-runtime.js';
8
8
  import { CLI_AGENT_IDENTITY } from './constants.js';
9
- import { errLine } from './output.js';
9
+ import { errLine, parseJson } from './output.js';
10
10
  // Supports Hermes without node:util.styleText, so the installer still runs on Node 20.11.
11
11
  const SKILLS_CLI_PACKAGE = 'skills@1.5.15';
12
12
  const TINYFISH_WEB_SKILL_SOURCE = 'tinyfish-io/tinyfish-cookbook';
@@ -66,14 +66,6 @@ function skillListed(stdout) {
66
66
  const parsed = skillListSchema.safeParse(parseJson(stdout));
67
67
  return parsed.success && parsed.data.some((skill) => skill.name === TINYFISH_WEB_SKILL);
68
68
  }
69
- function parseJson(text) {
70
- try {
71
- return JSON.parse(text);
72
- }
73
- catch {
74
- return undefined;
75
- }
76
- }
77
69
  export function installWebSkill(client, { verbose }) {
78
70
  if (!client.skillAgent)
79
71
  return;
@@ -21,14 +21,14 @@ export interface CliAgentRunParams extends AgentRunParams {
21
21
  use_vault?: boolean;
22
22
  credential_item_ids?: string[];
23
23
  }
24
- export type VaultProvider = '1password' | 'bitwarden';
24
+ type VaultProvider = '1password' | 'bitwarden';
25
25
  export interface VaultConnection {
26
26
  id: string;
27
27
  provider: VaultProvider;
28
28
  connectionStatus: string;
29
29
  lastValidatedAt: string | null;
30
30
  }
31
- export interface VaultFieldMetadata {
31
+ interface VaultFieldMetadata {
32
32
  fieldId: string;
33
33
  label: string;
34
34
  type: 'STRING' | 'CONCEALED' | 'OTP';
@@ -119,7 +119,7 @@ export interface BatchGetResponse {
119
119
  data: Run[];
120
120
  not_found: string[] | null;
121
121
  }
122
- export interface BatchCancelResult {
122
+ interface BatchCancelResult {
123
123
  run_id: string;
124
124
  status: string;
125
125
  cancelled_at: string | null;
@@ -157,3 +157,4 @@ export interface ProfileUploadResponse {
157
157
  domains_updated: string[];
158
158
  domains_failed?: string[];
159
159
  }
160
+ export {};
package/dist/program.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createRequire } from 'module';
2
1
  import { Command } from 'commander';
2
+ import { CLI_VERSION } from './lib/constants.js';
3
3
  import { registerAuth } from './commands/auth.js';
4
4
  import { registerBatch } from './commands/batch.js';
5
5
  import { registerFetch } from './commands/fetch.js';
@@ -18,12 +18,11 @@ import { registerOnboard } from './commands/onboard.js';
18
18
  import { installNoticeEmit } from './lib/notice.js';
19
19
  /** Split from index.ts so tests can drive the real program instead of hand-assembling one. */
20
20
  export function buildProgram() {
21
- const { version } = createRequire(import.meta.url)('../package.json');
22
21
  const program = new Command();
23
22
  program
24
23
  .name('tinyfish')
25
24
  .description('TinyFish CLI — run web automations from your terminal or agent.')
26
- .version(version, '-V, --version', 'Show version')
25
+ .version(CLI_VERSION, '-V, --version', 'Show version')
27
26
  .helpOption('-h, --help', 'Show help')
28
27
  .addHelpCommand(false)
29
28
  .enablePositionalOptions()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiny-fish/cli",
3
- "version": "0.39.1-next.309",
3
+ "version": "0.39.1-next.311",
4
4
  "description": "TinyFish CLI — run web automations from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,7 +37,6 @@
37
37
  "@tiny-fish/sdk": "^0.5.0",
38
38
  "commander": "^12.0.0",
39
39
  "cross-spawn": "^7.0.6",
40
- "globals": "^17.4.0",
41
40
  "tldts": "^6.1.86",
42
41
  "which": "^2.0.2",
43
42
  "yaml": "^2.9.0",