@tiny-fish/cli 0.30.2-next.256 → 0.30.2-next.258

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.
Files changed (77) hide show
  1. package/dist/commands/auth.d.ts +1 -1
  2. package/dist/commands/auth.js +52 -52
  3. package/dist/commands/batch.d.ts +1 -1
  4. package/dist/commands/batch.js +54 -46
  5. package/dist/commands/browser.d.ts +1 -1
  6. package/dist/commands/browser.js +14 -14
  7. package/dist/commands/config-claude.d.ts +1 -1
  8. package/dist/commands/config-claude.js +22 -22
  9. package/dist/commands/connect.d.ts +3 -3
  10. package/dist/commands/connect.js +150 -150
  11. package/dist/commands/doctor.d.ts +4 -4
  12. package/dist/commands/doctor.js +104 -104
  13. package/dist/commands/fetch.d.ts +1 -1
  14. package/dist/commands/fetch.js +24 -27
  15. package/dist/commands/onboard.d.ts +1 -1
  16. package/dist/commands/onboard.js +50 -50
  17. package/dist/commands/profile.d.ts +3 -3
  18. package/dist/commands/profile.js +45 -45
  19. package/dist/commands/run.d.ts +1 -1
  20. package/dist/commands/run.js +78 -65
  21. package/dist/commands/runs.d.ts +1 -1
  22. package/dist/commands/runs.js +63 -50
  23. package/dist/commands/search.d.ts +1 -1
  24. package/dist/commands/search.js +16 -16
  25. package/dist/commands/upgrade.d.ts +2 -2
  26. package/dist/commands/upgrade.js +36 -36
  27. package/dist/commands/vault.d.ts +1 -1
  28. package/dist/commands/vault.js +48 -45
  29. package/dist/commands/wallet.d.ts +1 -1
  30. package/dist/commands/wallet.js +14 -14
  31. package/dist/index.js +2 -2
  32. package/dist/lib/auth.d.ts +5 -5
  33. package/dist/lib/auth.js +56 -56
  34. package/dist/lib/batch-store.d.ts +1 -1
  35. package/dist/lib/batch-store.js +4 -4
  36. package/dist/lib/chrome-cookies.d.ts +1 -1
  37. package/dist/lib/chrome-cookies.js +24 -24
  38. package/dist/lib/claude-config.js +37 -37
  39. package/dist/lib/cli-install.js +12 -12
  40. package/dist/lib/client.d.ts +2 -2
  41. package/dist/lib/client.js +61 -61
  42. package/dist/lib/connect-all.js +140 -134
  43. package/dist/lib/connect-auth.d.ts +2 -2
  44. package/dist/lib/connect-auth.js +15 -15
  45. package/dist/lib/connect-clients.d.ts +6 -6
  46. package/dist/lib/connect-clients.js +143 -143
  47. package/dist/lib/connect-fallback.d.ts +1 -1
  48. package/dist/lib/connect-fallback.js +38 -38
  49. package/dist/lib/connect-picker.d.ts +4 -4
  50. package/dist/lib/connect-picker.js +28 -28
  51. package/dist/lib/connect-runtime.d.ts +7 -7
  52. package/dist/lib/connect-runtime.js +61 -61
  53. package/dist/lib/constants.js +13 -13
  54. package/dist/lib/cursor-config.d.ts +1 -1
  55. package/dist/lib/cursor-config.js +30 -30
  56. package/dist/lib/doctor-report.d.ts +2 -2
  57. package/dist/lib/doctor-report.js +22 -22
  58. package/dist/lib/harness-detect.js +39 -39
  59. package/dist/lib/harness.js +9 -9
  60. package/dist/lib/hermes-env.js +26 -23
  61. package/dist/lib/install-root.js +12 -12
  62. package/dist/lib/notice.d.ts +1 -1
  63. package/dist/lib/notice.js +12 -12
  64. package/dist/lib/output.js +14 -14
  65. package/dist/lib/registration-detect.d.ts +3 -3
  66. package/dist/lib/registration-detect.js +82 -82
  67. package/dist/lib/setup-telemetry.d.ts +4 -4
  68. package/dist/lib/setup-telemetry.js +34 -34
  69. package/dist/lib/signals.js +1 -1
  70. package/dist/lib/skill-install.d.ts +3 -3
  71. package/dist/lib/skill-install.js +40 -40
  72. package/dist/lib/types.d.ts +8 -8
  73. package/dist/lib/verify.d.ts +1 -1
  74. package/dist/lib/verify.js +28 -28
  75. package/dist/program.d.ts +1 -1
  76. package/dist/program.js +28 -28
  77. package/package.json +18 -9
@@ -1,4 +1,4 @@
1
- import type { Command } from "commander";
1
+ import type { Command } from 'commander';
2
2
  /** Test-only: the capture slot and the once-per-invocation latch are module state. */
3
3
  export declare function resetNoticeState(): void;
4
4
  /** Records the first notice seen; printing is deferred so it lands at the end of a command. */
@@ -1,10 +1,10 @@
1
- import { CLI_VERSION } from "./constants.js";
2
- import { detectHumanInitiated } from "./harness.js";
3
- import { err, sanitizeLine, warnLine } from "./output.js";
4
- const NOTICE_HEADER = "x-tf-notice";
5
- const LEVEL_HEADER = "x-tf-notice-level";
1
+ import { CLI_VERSION } from './constants.js';
2
+ import { detectHumanInitiated } from './harness.js';
3
+ import { err, sanitizeLine, warnLine } from './output.js';
4
+ const NOTICE_HEADER = 'x-tf-notice';
5
+ const LEVEL_HEADER = 'x-tf-notice-level';
6
6
  const MAX_LENGTH = 300;
7
- const VALID_LEVELS = new Set(["required", "recommended"]);
7
+ const VALID_LEVELS = new Set(['required', 'recommended']);
8
8
  let captured;
9
9
  let printed = false;
10
10
  /** Test-only: the capture slot and the once-per-invocation latch are module state. */
@@ -16,9 +16,9 @@ export function resetNoticeState() {
16
16
  export function captureNotice(headers) {
17
17
  if (captured)
18
18
  return;
19
- if (typeof headers?.get !== "function")
19
+ if (typeof headers?.get !== 'function')
20
20
  return;
21
- const message = sanitizeLine(headers.get(NOTICE_HEADER) ?? "")
21
+ const message = sanitizeLine(headers.get(NOTICE_HEADER) ?? '')
22
22
  .trim()
23
23
  .slice(0, MAX_LENGTH);
24
24
  if (!message)
@@ -29,7 +29,7 @@ export function captureNotice(headers) {
29
29
  }
30
30
  /** Built here, not by the server, so nothing it sends can reach an agent's context as instructions. */
31
31
  function agentMessage(level) {
32
- return level === "required"
32
+ return level === 'required'
33
33
  ? `This TinyFish CLI (${CLI_VERSION}) is below the required minimum. Run: tinyfish upgrade`
34
34
  : `A TinyFish CLI update is available (you have ${CLI_VERSION}). Run: tinyfish upgrade`;
35
35
  }
@@ -53,7 +53,7 @@ export function suppressNotice() {
53
53
  /** postAction covers clean completions, the exit hook covers the process.exit and rejection paths, the latch keeps it to one. */
54
54
  export function installNoticeEmit(program) {
55
55
  const backstop = () => emitNotice();
56
- program.hook("postAction", () => emitNotice());
57
- process.once("exit", backstop);
58
- return () => process.removeListener("exit", backstop);
56
+ program.hook('postAction', () => emitNotice());
57
+ process.once('exit', backstop);
58
+ return () => process.removeListener('exit', backstop);
59
59
  }
@@ -1,4 +1,4 @@
1
- import { stripVTControlCharacters } from "node:util";
1
+ import { stripVTControlCharacters } from 'node:util';
2
2
  export class ApiError extends Error {
3
3
  status;
4
4
  code;
@@ -6,21 +6,21 @@ export class ApiError extends Error {
6
6
  super(message);
7
7
  this.status = status;
8
8
  this.code = code;
9
- this.name = "ApiError";
9
+ this.name = 'ApiError';
10
10
  }
11
11
  }
12
12
  export function out(data) {
13
- process.stdout.write(JSON.stringify(data) + "\n");
13
+ process.stdout.write(JSON.stringify(data) + '\n');
14
14
  }
15
15
  /** The only writer for untrusted text: strips ANSI and C0/C1 controls. */
16
16
  export function sanitizeLine(text) {
17
17
  // eslint-disable-next-line no-control-regex
18
- return stripVTControlCharacters(text).replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, "");
18
+ return stripVTControlCharacters(text).replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, '');
19
19
  }
20
20
  export function outLine(line) {
21
- process.stdout.write(sanitizeLine(line) + "\n");
21
+ process.stdout.write(sanitizeLine(line) + '\n');
22
22
  }
23
- let errIndent = "";
23
+ let errIndent = '';
24
24
  /** `connect --all` nests a harness's own lines under its step header. */
25
25
  export function setErrIndent(prefix) {
26
26
  errIndent = prefix;
@@ -28,20 +28,20 @@ export function setErrIndent(prefix) {
28
28
  export function errLine(line) {
29
29
  const text = sanitizeLine(line);
30
30
  // Per line, not once: a replayed capture carries newlines.
31
- process.stderr.write((errIndent ? text.replace(/^(?=.)/gm, errIndent) : text) + "\n");
31
+ process.stderr.write((errIndent ? text.replace(/^(?=.)/gm, errIndent) : text) + '\n');
32
32
  }
33
- const WARN_ON = "\x1b[33m";
34
- const WARN_OFF = "\x1b[39m";
33
+ const WARN_ON = '\x1b[33m';
34
+ const WARN_OFF = '\x1b[39m';
35
35
  /** Advisory stderr line, yellow on a terminal and plain everywhere else so piped output stays clean. */
36
36
  export function warnLine(line) {
37
37
  const text = sanitizeLine(line);
38
- const colour = Boolean(process.stderr.isTTY) && !process.env["NO_COLOR"];
38
+ const colour = Boolean(process.stderr.isTTY) && !process.env['NO_COLOR'];
39
39
  process.stderr.write(colour ? `${WARN_ON}${text}${WARN_OFF}\n` : `${text}\n`);
40
40
  }
41
41
  export function err(data) {
42
- const payload = typeof data === "string" ? { error: data } : data;
43
- const serialized = JSON.stringify(payload) ?? "undefined";
44
- process.stderr.write(sanitizeLine(serialized) + "\n");
42
+ const payload = typeof data === 'string' ? { error: data } : data;
43
+ const serialized = JSON.stringify(payload) ?? 'undefined';
44
+ process.stderr.write(sanitizeLine(serialized) + '\n');
45
45
  }
46
46
  export function handleApiError(e) {
47
47
  if (e instanceof ApiError) {
@@ -50,7 +50,7 @@ export function handleApiError(e) {
50
50
  payload.code = e.code;
51
51
  if (e.status === 401)
52
52
  payload.hint =
53
- "Clear the TINYFISH_API_KEY environment variable, run `tinyfish auth login`, then try again.";
53
+ 'Clear the TINYFISH_API_KEY environment variable, run `tinyfish auth login`, then try again.';
54
54
  err(payload);
55
55
  }
56
56
  else if (e instanceof Error) {
@@ -1,6 +1,6 @@
1
- import { type RecordedAuthMode } from "./auth.js";
2
- import type { AgentClient } from "./connect-runtime.js";
3
- import { AuthMode, type Harness, Registered } from "./harness-detect.js";
1
+ import { type RecordedAuthMode } from './auth.js';
2
+ import type { AgentClient } from './connect-runtime.js';
3
+ import { AuthMode, type Harness, Registered } from './harness-detect.js';
4
4
  export interface RegistrationStatus {
5
5
  harness: Harness;
6
6
  detected: boolean;
@@ -1,13 +1,13 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import spawn from "cross-spawn";
4
- import { z } from "zod";
5
- import { loadConfig, matchesCliKey } from "./auth.js";
6
- import { NATIVE_BY_HARNESS } from "./connect-clients.js";
7
- import { HARNESS_PROBE_TIMEOUT_MS } from "./constants.js";
8
- import { errLine } from "./output.js";
9
- import { readCursorTinyfishEntry } from "./cursor-config.js";
10
- import { detectInstalledHarnesses, harnessConfigPath, harnessDisplayPath, AuthMode, Registered, } from "./harness-detect.js";
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import spawn from 'cross-spawn';
4
+ import { z } from 'zod';
5
+ import { loadConfig, matchesCliKey } from './auth.js';
6
+ import { NATIVE_BY_HARNESS } from './connect-clients.js';
7
+ import { HARNESS_PROBE_TIMEOUT_MS } from './constants.js';
8
+ import { errLine } from './output.js';
9
+ import { readCursorTinyfishEntry } from './cursor-config.js';
10
+ import { detectInstalledHarnesses, harnessConfigPath, harnessDisplayPath, AuthMode, Registered, } from './harness-detect.js';
11
11
  // A list, verified against codex 0.146. A name-keyed map was accepted here too, but every
12
12
  // field is optional, so that arm parsed *any* object-of-objects: a wrapper like
13
13
  // `{"servers": {...}}` decoded as one entry named `servers` and reported TinyFish absent.
@@ -33,50 +33,50 @@ const ANSI_SGR = /\x1b\[[0-9;]*m/g;
33
33
  // The report is pasted in public, so every reason is authored here. A spawn error's own
34
34
  // message quotes absolute paths, which is how a home directory reaches the payload.
35
35
  function spawnFailureReason(command, code) {
36
- if (code === "ENOENT")
36
+ if (code === 'ENOENT')
37
37
  return `\`${command}\` is not on PATH`;
38
- if (code === "EACCES")
38
+ if (code === 'EACCES')
39
39
  return `\`${command}\` is not executable`;
40
40
  return `could not run \`${command}\``;
41
41
  }
42
42
  function runProbe(command, args) {
43
43
  const result = spawn.sync(command, args, {
44
- encoding: "utf8",
44
+ encoding: 'utf8',
45
45
  timeout: HARNESS_PROBE_TIMEOUT_MS,
46
46
  // Belt and braces only: agents export FORCE_COLOR, and `NO_COLOR` does not fully silence
47
47
  // OpenCode, so the strip below stays load-bearing rather than a fallback.
48
- env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" },
48
+ env: { ...process.env, NO_COLOR: '1', FORCE_COLOR: '0' },
49
49
  });
50
50
  if (result.error) {
51
51
  const code = result.error.code;
52
52
  // A timeout arrives as ETIMEDOUT here, not only as a signal, so it must be named before
53
53
  // the generic spawn failure; otherwise a timeout reports "could not run".
54
- if (code === "ETIMEDOUT") {
55
- return { outcome: "unavailable", reason: `\`${command} ${args.join(" ")}\` timed out` };
54
+ if (code === 'ETIMEDOUT') {
55
+ return { outcome: 'unavailable', reason: `\`${command} ${args.join(' ')}\` timed out` };
56
56
  }
57
- return { outcome: "unavailable", reason: spawnFailureReason(command, code) };
57
+ return { outcome: 'unavailable', reason: spawnFailureReason(command, code) };
58
58
  }
59
59
  // Reached only when something else killed the child, so this is a crash, not the timeout.
60
60
  if (result.signal) {
61
- return { outcome: "unavailable", reason: `\`${command}\` was killed by ${result.signal}` };
61
+ return { outcome: 'unavailable', reason: `\`${command}\` was killed by ${result.signal}` };
62
62
  }
63
63
  // Both streams, since a colour code inside a JSON payload breaks the parse just as it
64
64
  // breaks an anchored match. Valid JSON never carries a raw ESC byte, so this cannot corrupt it.
65
- const stdout = (result.stdout ?? "").replace(ANSI_SGR, "");
65
+ const stdout = (result.stdout ?? '').replace(ANSI_SGR, '');
66
66
  return {
67
- outcome: "ran",
67
+ outcome: 'ran',
68
68
  exitCode: result.status ?? 1,
69
69
  stdout,
70
- output: `${stdout}${(result.stderr ?? "").replace(ANSI_SGR, "")}`,
70
+ output: `${stdout}${(result.stderr ?? '').replace(ANSI_SGR, '')}`,
71
71
  };
72
72
  }
73
73
  /** Undefined when the probe ran and exited zero. */
74
74
  function probeReason(command, args, probe) {
75
- if (probe.outcome === "unavailable")
75
+ if (probe.outcome === 'unavailable')
76
76
  return probe.reason;
77
77
  if (probe.exitCode === 0)
78
78
  return undefined;
79
- return `\`${command} ${args.join(" ")}\` exited ${probe.exitCode}`;
79
+ return `\`${command} ${args.join(' ')}\` exited ${probe.exitCode}`;
80
80
  }
81
81
  // A plugin registers its server as `plugin:<plugin>:<server>`, which `mcp get tinyfish` misses.
82
82
  // The optional prefix must end in `:`, so `not-tinyfish` is not a match.
@@ -99,13 +99,13 @@ function connectionState(status) {
99
99
  }
100
100
  /** The plugin ships its own MCP server, so a working install can carry no `tinyfish` entry. */
101
101
  function fromPluginRegistration(command) {
102
- const probe = runProbe(command, ["mcp", "list"]);
103
- if (probe.outcome === "unavailable" || probe.exitCode !== 0)
102
+ const probe = runProbe(command, ['mcp', 'list']);
103
+ if (probe.outcome === 'unavailable' || probe.exitCode !== 0)
104
104
  return undefined;
105
105
  const match = MCP_LIST_TINYFISH_LINE.exec(probe.output);
106
106
  if (!match)
107
107
  return undefined;
108
- const connected = connectionState(match[3] ?? "");
108
+ const connected = connectionState(match[3] ?? '');
109
109
  // `mcp list` prints no headers, so the credential behind it is unproven, never assumed.
110
110
  return {
111
111
  registered: Registered.Yes,
@@ -119,7 +119,7 @@ function fromPluginRegistration(command) {
119
119
  function tinyfishServerName(output) {
120
120
  const listed = CONFIGURED_SERVERS.exec(output)?.[1];
121
121
  return listed
122
- ?.split(",")
122
+ ?.split(',')
123
123
  .map((name) => name.trim())
124
124
  .find((name) => SERVER_NAMED_TINYFISH.test(name));
125
125
  }
@@ -140,8 +140,8 @@ function parseMcpGet(output) {
140
140
  }
141
141
  // claude-code only. Codex cannot use this: its `mcp get` exits 0 on a missing server.
142
142
  function fromMcpGet(command) {
143
- const probe = runProbe(command, ["mcp", "get", "tinyfish"]);
144
- if (probe.outcome === "unavailable") {
143
+ const probe = runProbe(command, ['mcp', 'get', 'tinyfish']);
144
+ if (probe.outcome === 'unavailable') {
145
145
  return { registered: Registered.Unknown, authMode: AuthMode.Unknown, reason: probe.reason };
146
146
  }
147
147
  if (probe.exitCode !== 0) {
@@ -155,8 +155,8 @@ function fromMcpGet(command) {
155
155
  // The roster is free; `mcp list` health-checks every server.
156
156
  const scoped = tinyfishServerName(probe.output);
157
157
  if (scoped) {
158
- const rescoped = runProbe(command, ["mcp", "get", scoped]);
159
- if (rescoped.outcome === "ran" && rescoped.exitCode === 0) {
158
+ const rescoped = runProbe(command, ['mcp', 'get', scoped]);
159
+ if (rescoped.outcome === 'ran' && rescoped.exitCode === 0) {
160
160
  return {
161
161
  ...parseMcpGet(rescoped.output),
162
162
  reason: `registered as ${scoped}`,
@@ -182,13 +182,13 @@ function fromMcpGet(command) {
182
182
  // `codex mcp get <missing>` prints an error and exits 0, so presence must never be read from
183
183
  // its exit code; `mcp list --json` is the only trustworthy read-back.
184
184
  function readCodexEntry() {
185
- const args = ["mcp", "list", "--json"];
186
- const probe = runProbe("codex", args);
187
- if (probe.outcome === "unavailable")
185
+ const args = ['mcp', 'list', '--json'];
186
+ const probe = runProbe('codex', args);
187
+ if (probe.outcome === 'unavailable')
188
188
  return { reason: probe.reason };
189
189
  // Presence is never inferred from exit code 0, but a nonzero exit means the output cannot
190
190
  // be trusted either — that is `unknown`, not "no server".
191
- const failure = probeReason("codex", args, probe);
191
+ const failure = probeReason('codex', args, probe);
192
192
  if (failure)
193
193
  return { reason: failure };
194
194
  let servers;
@@ -198,25 +198,25 @@ function readCodexEntry() {
198
198
  servers = JSON.parse(probe.stdout);
199
199
  }
200
200
  catch {
201
- return { reason: "`codex mcp list --json` returned unparseable output" };
201
+ return { reason: '`codex mcp list --json` returned unparseable output' };
202
202
  }
203
203
  // A scalar or a wrapper object decodes fine and then silently reports "no server", which
204
204
  // would suggest a spurious repair. Anything but a list is unknown.
205
205
  const shape = codexListSchema.safeParse(servers);
206
206
  if (!shape.success)
207
- return { reason: "`codex mcp list --json` returned an unexpected shape" };
208
- return { entry: shape.data.find((s) => s.name === "tinyfish") };
207
+ return { reason: '`codex mcp list --json` returned an unexpected shape' };
208
+ return { entry: shape.data.find((s) => s.name === 'tinyfish') };
209
209
  }
210
210
  // `mcp add` exits 0 whether or not its inline OAuth finished, so `not_logged_in` is the only
211
211
  // evidence of an abandoned sign-in; every other state, older Codex included, is unknown.
212
212
  export function codexOauthCompleted() {
213
213
  const read = readCodexEntry();
214
- if ("reason" in read)
214
+ if ('reason' in read)
215
215
  return undefined;
216
216
  const status = read.entry?.auth_status;
217
- if (status === "o_auth")
217
+ if (status === 'o_auth')
218
218
  return true;
219
- if (status === "not_logged_in")
219
+ if (status === 'not_logged_in')
220
220
  return false;
221
221
  return undefined;
222
222
  }
@@ -233,7 +233,7 @@ function declaredKeyEnvVar(harness) {
233
233
  /** Codex stores a variable name; resolving it only answers whether it is the CLI's key. */
234
234
  function envKeyVerdict(envVar) {
235
235
  if (!ENV_VAR_NAME.test(envVar)) {
236
- return { keyReason: "its key comes from an unusable variable name" };
236
+ return { keyReason: 'its key comes from an unusable variable name' };
237
237
  }
238
238
  if (matchesCliKey(process.env[envVar]))
239
239
  return { keyMatchesCliKey: true };
@@ -245,7 +245,7 @@ function envKeyVerdict(envVar) {
245
245
  }
246
246
  function probeCodex() {
247
247
  const read = readCodexEntry();
248
- if ("reason" in read)
248
+ if ('reason' in read)
249
249
  return { registered: Registered.Unknown, authMode: AuthMode.Unknown, reason: read.reason };
250
250
  const entry = read.entry;
251
251
  if (!entry)
@@ -253,14 +253,10 @@ function probeCodex() {
253
253
  const codexUrl = entry.transport?.url;
254
254
  // Read from this entry: codex emits `bearer_token_env_var` on every HTTP server, so testing
255
255
  // the whole payload reports api-key for TinyFish because some other server carries a key.
256
- const envVar = entry.auth_status === "o_auth" ? undefined : entry.transport?.bearer_token_env_var;
256
+ const envVar = entry.auth_status === 'o_auth' ? undefined : entry.transport?.bearer_token_env_var;
257
257
  return {
258
258
  registered: Registered.Yes,
259
- authMode: entry.auth_status === "o_auth"
260
- ? AuthMode.OAuth
261
- : envVar
262
- ? AuthMode.ApiKey
263
- : AuthMode.Unknown,
259
+ authMode: entry.auth_status === 'o_auth' ? AuthMode.OAuth : envVar ? AuthMode.ApiKey : AuthMode.Unknown,
264
260
  ...(codexUrl ? { registeredUrl: codexUrl } : {}),
265
261
  ...(envVar ? envKeyVerdict(envVar) : {}),
266
262
  };
@@ -271,7 +267,7 @@ function probeCursor() {
271
267
  return {
272
268
  registered: Registered.Unknown,
273
269
  authMode: AuthMode.Unknown,
274
- reason: "mcp.json exists but could not be read or parsed",
270
+ reason: 'mcp.json exists but could not be read or parsed',
275
271
  };
276
272
  }
277
273
  if (!entry.present)
@@ -292,7 +288,11 @@ const OPENCODE_ROW_URL = /^[^\w]*tinyfish(?![\w-]).*\n[^\w]*(https?:\S+)/m;
292
288
  function fromMcpList(command, output, registeredMode, urlPattern) {
293
289
  if (LISTED_AS_TINYFISH.test(output)) {
294
290
  const url = urlPattern?.exec(output)?.[1];
295
- return { registered: Registered.Yes, authMode: registeredMode, ...(url ? { registeredUrl: url } : {}) };
291
+ return {
292
+ registered: Registered.Yes,
293
+ authMode: registeredMode,
294
+ ...(url ? { registeredUrl: url } : {}),
295
+ };
296
296
  }
297
297
  if (!MCP_LIST_HEADER.test(output)) {
298
298
  return {
@@ -305,18 +305,18 @@ function fromMcpList(command, output, registeredMode, urlPattern) {
305
305
  }
306
306
  // connect only ever writes an OAuth Hermes entry, so `oauth` is a fact, not a detection gap.
307
307
  function probeHermes() {
308
- const args = ["mcp", "list"];
309
- const probe = runProbe("hermes", args);
310
- if (probe.outcome !== "ran" || probe.exitCode !== 0) {
311
- const reason = probeReason("hermes", args, probe);
308
+ const args = ['mcp', 'list'];
309
+ const probe = runProbe('hermes', args);
310
+ if (probe.outcome !== 'ran' || probe.exitCode !== 0) {
311
+ const reason = probeReason('hermes', args, probe);
312
312
  return { registered: Registered.Unknown, authMode: AuthMode.Unknown, reason };
313
313
  }
314
- return fromMcpList("hermes", probe.output, AuthMode.OAuth);
314
+ return fromMcpList('hermes', probe.output, AuthMode.OAuth);
315
315
  }
316
316
  const SKILL_DIR_IS_TINYFISH = /^(?:@tinyfish[/_-])?tinyfish(?![\w-])/i;
317
317
  // OpenClaw installs a skill, not an MCP server; the skill shells the CLI, so auth is the CLI key.
318
318
  function probeOpenClaw() {
319
- const skillsDir = path.join(harnessConfigPath("openclaw"), "skills");
319
+ const skillsDir = path.join(harnessConfigPath('openclaw'), 'skills');
320
320
  let entries;
321
321
  try {
322
322
  entries = fs.readdirSync(skillsDir);
@@ -325,7 +325,7 @@ function probeOpenClaw() {
325
325
  return {
326
326
  registered: Registered.Unknown,
327
327
  authMode: AuthMode.Unknown,
328
- reason: `no global skills directory at ${harnessDisplayPath("openclaw")}/skills; OpenClaw layout is unverified`,
328
+ reason: `no global skills directory at ${harnessDisplayPath('openclaw')}/skills; OpenClaw layout is unverified`,
329
329
  };
330
330
  }
331
331
  // A bare substring also matched a skill merely named `not-tinyfish-thing`.
@@ -335,13 +335,13 @@ function probeOpenClaw() {
335
335
  }
336
336
  // `opencode mcp list` prints no headers, so a key-authed registration is indistinguishable.
337
337
  function probeOpencode() {
338
- const args = ["mcp", "list"];
339
- const probe = runProbe("opencode", args);
340
- if (probe.outcome !== "ran" || probe.exitCode !== 0) {
341
- const reason = probeReason("opencode", args, probe);
338
+ const args = ['mcp', 'list'];
339
+ const probe = runProbe('opencode', args);
340
+ if (probe.outcome !== 'ran' || probe.exitCode !== 0) {
341
+ const reason = probeReason('opencode', args, probe);
342
342
  return { registered: Registered.Unknown, authMode: AuthMode.Unknown, reason };
343
343
  }
344
- return fromMcpList("opencode", probe.output, AuthMode.Unknown, OPENCODE_ROW_URL);
344
+ return fromMcpList('opencode', probe.output, AuthMode.Unknown, OPENCODE_ROW_URL);
345
345
  }
346
346
  // Verified against grok 0.0.60: a list. Header values stay `unknown`; only names are read.
347
347
  const grokEntrySchema = z.looseObject({
@@ -354,8 +354,8 @@ const grokListSchema = z.array(grokEntrySchema);
354
354
  const grokPluginSchema = z.array(z.looseObject({ name: z.string().optional() }));
355
355
  /** The Grok plugin ships its own MCP server, and `grok mcp list` does not show it. */
356
356
  function fromGrokPlugin() {
357
- const probe = runProbe("grok", ["plugin", "list", "--json"]);
358
- if (probe.outcome === "unavailable" || probe.exitCode !== 0)
357
+ const probe = runProbe('grok', ['plugin', 'list', '--json']);
358
+ if (probe.outcome === 'unavailable' || probe.exitCode !== 0)
359
359
  return undefined;
360
360
  let listed;
361
361
  try {
@@ -372,16 +372,16 @@ function fromGrokPlugin() {
372
372
  return {
373
373
  registered: Registered.Unknown,
374
374
  authMode: AuthMode.Unknown,
375
- reason: "`grok plugin list --json` returned an unexpected shape",
375
+ reason: '`grok plugin list --json` returned an unexpected shape',
376
376
  };
377
377
  }
378
- if (!shape.data.some((plugin) => plugin.name === "tinyfish"))
378
+ if (!shape.data.some((plugin) => plugin.name === 'tinyfish'))
379
379
  return undefined;
380
380
  // The plugin manifest carries no headers, so its only credential path is the browser sign-in.
381
381
  return {
382
382
  registered: Registered.Yes,
383
383
  authMode: AuthMode.OAuth,
384
- reason: "registered by the TinyFish Grok plugin",
384
+ reason: 'registered by the TinyFish Grok plugin',
385
385
  };
386
386
  }
387
387
  // Grok inherits our env, so a header we wrote round-trips to our own value.
@@ -391,21 +391,21 @@ function grokWroteIt(value, declared) {
391
391
  }
392
392
  /** The descriptor names the variable; envKeyVerdict owns every wording for it. */
393
393
  function grokKeyAuth(headers) {
394
- const authorization = Object.entries(headers).find(([name]) => name.toLowerCase() === "authorization");
394
+ const authorization = Object.entries(headers).find(([name]) => name.toLowerCase() === 'authorization');
395
395
  if (!authorization)
396
396
  return { authMode: AuthMode.OAuth };
397
- const declared = declaredKeyEnvVar("grok");
398
- const value = typeof authorization[1] === "string" ? authorization[1] : "";
397
+ const declared = declaredKeyEnvVar('grok');
398
+ const value = typeof authorization[1] === 'string' ? authorization[1] : '';
399
399
  if (!declared || !grokWroteIt(value, declared)) {
400
- return { authMode: AuthMode.ApiKey, keyReason: "it holds a key this CLI cannot verify" };
400
+ return { authMode: AuthMode.ApiKey, keyReason: 'it holds a key this CLI cannot verify' };
401
401
  }
402
402
  return { authMode: AuthMode.ApiKey, ...envKeyVerdict(declared) };
403
403
  }
404
404
  // Grok has no `mcp get`; `mcp list --json` is the only machine-readable view.
405
405
  function probeGrok() {
406
- const args = ["mcp", "list", "--json"];
407
- const probe = runProbe("grok", args);
408
- if (probe.outcome === "unavailable") {
406
+ const args = ['mcp', 'list', '--json'];
407
+ const probe = runProbe('grok', args);
408
+ if (probe.outcome === 'unavailable') {
409
409
  return { registered: Registered.Unknown, authMode: AuthMode.Unknown, reason: probe.reason };
410
410
  }
411
411
  if (probe.exitCode !== 0) {
@@ -413,13 +413,13 @@ function probeGrok() {
413
413
  return {
414
414
  registered: Registered.Unknown,
415
415
  authMode: AuthMode.Unknown,
416
- reason: "this Grok build has no `mcp list --json`",
416
+ reason: 'this Grok build has no `mcp list --json`',
417
417
  };
418
418
  }
419
419
  return {
420
420
  registered: Registered.Unknown,
421
421
  authMode: AuthMode.Unknown,
422
- reason: probeReason("grok", args, probe),
422
+ reason: probeReason('grok', args, probe),
423
423
  };
424
424
  }
425
425
  let entries;
@@ -430,10 +430,10 @@ function probeGrok() {
430
430
  return {
431
431
  registered: Registered.Unknown,
432
432
  authMode: AuthMode.Unknown,
433
- reason: "`grok mcp list --json` did not return the expected shape",
433
+ reason: '`grok mcp list --json` did not return the expected shape',
434
434
  };
435
435
  }
436
- const entry = entries.find((server) => server.name === "tinyfish");
436
+ const entry = entries.find((server) => server.name === 'tinyfish');
437
437
  if (!entry)
438
438
  return fromGrokPlugin() ?? { registered: Registered.No, authMode: AuthMode.Unknown };
439
439
  return {
@@ -443,7 +443,7 @@ function probeGrok() {
443
443
  };
444
444
  }
445
445
  const PROBES = {
446
- "claude-code": () => fromMcpGet("claude"),
446
+ 'claude-code': () => fromMcpGet('claude'),
447
447
  codex: probeCodex,
448
448
  cursor: probeCursor,
449
449
  grok: probeGrok,
@@ -474,14 +474,14 @@ export function detectRegistrations(harnesses) {
474
474
  catch (e) {
475
475
  // An arbitrary throw is the one message we cannot author, so it never ships. Full
476
476
  // fidelity stays on stderr behind --debug, which is not the pasted artifact.
477
- if (process.env["TINYFISH_DEBUG"]) {
477
+ if (process.env['TINYFISH_DEBUG']) {
478
478
  errLine(`probe ${detection.harness} threw: ${e instanceof Error ? e.message : String(e)}`);
479
479
  }
480
480
  return {
481
481
  ...base,
482
482
  registered: Registered.Unknown,
483
483
  authMode: AuthMode.Unknown,
484
- reason: "the probe failed unexpectedly",
484
+ reason: 'the probe failed unexpectedly',
485
485
  };
486
486
  }
487
487
  });
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import { z } from 'zod';
2
2
  /**
3
3
  * `TINYFISH_NO_TELEMETRY` suppresses ALL CLI-emitted network analytics
4
4
  * (setup_completed, connect-event, update check). Any value except `0`/`false`
@@ -68,10 +68,10 @@ export declare const setupCompletedPayloadSchema: z.ZodObject<{
68
68
  }, z.core.$strip>;
69
69
  export type SetupCompletedPayload = z.infer<typeof setupCompletedPayloadSchema>;
70
70
  export type HarnessResult = z.infer<typeof harnessResultSchema>;
71
- export type SetupMode = NonNullable<SetupCompletedPayload["mode"]>;
72
- export type UpgradeOutcome = "updated" | "failed" | "interrupted";
71
+ export type SetupMode = NonNullable<SetupCompletedPayload['mode']>;
72
+ export type UpgradeOutcome = 'updated' | 'failed' | 'interrupted';
73
73
  /** Step selection; "updated" means every selected step ran. */
74
- export type UpgradeScope = "both" | "cli" | "skill";
74
+ export type UpgradeScope = 'both' | 'cli' | 'skill';
75
75
  /** `toVersion` is null when the run was interrupted or the installed version could not be read. */
76
76
  export declare function sendUpgradeCompleted(fromVersion: string, toVersion: string | null, outcome: UpgradeOutcome, scope: UpgradeScope, apiKey?: string): Promise<void>;
77
77
  export declare function sendSetupCompleted(mcpUrl: string, harnesses: HarnessResult[], apiKey?: string, mode?: SetupMode): Promise<void>;