@tiny-fish/cli 0.15.1-next.161 → 0.16.0

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.
@@ -1,9 +1,9 @@
1
1
  import type { Command } from "commander";
2
2
  import { type DoctorRepair, type DoctorReport } from "../lib/doctor-report.js";
3
- import { type FiveHarness } from "../lib/harness-detect.js";
3
+ import { type Harness } from "../lib/harness-detect.js";
4
4
  export interface DoctorOptions {
5
5
  mcpUrl: string;
6
- harness?: FiveHarness;
6
+ harness?: Harness;
7
7
  fix?: boolean;
8
8
  yes?: boolean;
9
9
  }
@@ -2,12 +2,12 @@ import spawn from "cross-spawn";
2
2
  import { apiKeyStatus } from "../lib/auth.js";
3
3
  import { CLI_VERSION } from "../lib/constants.js";
4
4
  import { DOCTOR_COULD_NOT_RUN, DOCTOR_SCHEMA_VERSION, doctorReportSchema, exitCodeFor, renderPretty, } from "../lib/doctor-report.js";
5
- import { FIVE_HARNESSES } from "../lib/harness-detect.js";
5
+ import { ALL_HARNESSES } from "../lib/harness-detect.js";
6
6
  import { detectHumanInitiated } from "../lib/harness.js";
7
7
  import { err, errLine, out, outLine } from "../lib/output.js";
8
8
  import { detectRegistrations } from "../lib/registration-detect.js";
9
9
  import { verifyMcpAuth, verifyMcpHealth } from "../lib/verify.js";
10
- import { connectClaudeCode, connectCodex, connectCursor, connectHermes, connectOpenClaw, DEFAULT_MCP_URL, } from "./connect.js";
10
+ import { connectClaudeCode, connectCodex, connectCursor, connectHermes, connectOpenClaw, connectOpencode, DEFAULT_MCP_URL, } from "./connect.js";
11
11
  // A green auth mode with a red auth call proves nothing, so the call's verdict gates the claim.
12
12
  function provesHarnessReach(status, authCallPassed, healthOk, mcpUrl) {
13
13
  // An endpoint doctor could not reach proves nothing about a harness pointed at it.
@@ -70,7 +70,9 @@ function registrationVerdict(status, mcpUrl) {
70
70
  detail: `registered, but points at ${elsewhere} rather than ${endpointOf(mcpUrl)}`,
71
71
  };
72
72
  }
73
- return { status: "pass", detail: `registered, auth mode ${status.authMode}` };
73
+ // A probe that had to explain itself to reach `yes` is the only thing that explains `unknown`.
74
+ const because = status.reason ? ` (${status.reason})` : "";
75
+ return { status: "pass", detail: `registered, auth mode ${status.authMode}${because}` };
74
76
  }
75
77
  // Connected once and now absent is a broken install; never connected is just not set up.
76
78
  return status.connectedBefore
@@ -204,6 +206,7 @@ const CONNECTORS = {
204
206
  cursor: (opts) => connectCursor(opts),
205
207
  hermes: (opts) => connectHermes({ ...opts, launch: false }),
206
208
  openclaw: (opts) => connectOpenClaw({ ...opts, launch: false }),
209
+ opencode: (opts) => connectOpencode({ ...opts, launch: false }),
207
210
  };
208
211
  /** Dispatches on `action`, never on a null harness: that conflation made the credential fix a no-op. */
209
212
  async function runRepair(repair, mcpUrl) {
@@ -256,12 +259,12 @@ export function registerDoctor(program) {
256
259
  .command("doctor")
257
260
  .description("Diagnose your TinyFish setup")
258
261
  .option("--pretty", "Human-readable check list instead of JSON")
259
- .option("--harness <name>", `Restrict to one harness (${FIVE_HARNESSES.join(", ")})`)
262
+ .option("--harness <name>", `Restrict to one harness (${ALL_HARNESSES.join(", ")})`)
260
263
  .option("--url <mcpUrl>", "MCP endpoint override")
261
264
  .option("--fix", "Repair what can be repaired by re-running tinyfish connect")
262
265
  .option("--yes", "Allow --fix to run without a terminal (unattended-safe repairs only)")
263
266
  .action(async (options) => {
264
- if (options.harness && !FIVE_HARNESSES.includes(options.harness)) {
267
+ if (options.harness && !ALL_HARNESSES.includes(options.harness)) {
265
268
  err({ error: `Unknown --harness value: ${options.harness}` });
266
269
  process.exitCode = DOCTOR_COULD_NOT_RUN;
267
270
  return;
@@ -1,8 +1,8 @@
1
1
  import * as readline from "node:readline/promises";
2
2
  import spawn from "cross-spawn";
3
- import { connectClaudeCode, connectCodex, connectCursor, connectHermes, connectOpenClaw, } from "../commands/connect.js";
3
+ import { connectClaudeCode, connectCodex, connectCursor, connectHermes, connectOpenClaw, connectOpencode, } from "../commands/connect.js";
4
4
  import { validatedApiKey } from "./auth.js";
5
- import { detectInstalledHarnesses, FIVE_HARNESSES, } from "./harness-detect.js";
5
+ import { detectInstalledHarnesses, ALL_HARNESSES, } from "./harness-detect.js";
6
6
  import { detectHumanInitiated } from "./harness.js";
7
7
  import { emitNotice } from "./notice.js";
8
8
  import { removeCursorMcpServer, planCursorWrite } from "./cursor-config.js";
@@ -15,12 +15,13 @@ const DISPLAY_NAMES = {
15
15
  cursor: "Cursor",
16
16
  hermes: "Hermes",
17
17
  openclaw: "OpenClaw",
18
+ opencode: "OpenCode",
18
19
  };
19
20
  // `codex mcp add` succeeds unauthenticated; OAuth lands at first tool use.
20
21
  const AUTH_DEFERRED_AT_INSTALL = new Set(["codex"]);
21
22
  // Harnesses that drive their own OAuth at install — a stored TinyFish key says nothing
22
23
  // about their session, so it must never unlock a headless run for them.
23
- const HARNESS_OWN_AUTH = new Set(["claude-code", "codex", "hermes"]);
24
+ const HARNESS_OWN_AUTH = new Set(["claude-code", "codex", "hermes", "opencode"]);
24
25
  const AUTH_CHECK_CAPABLE = new Set(["cursor", "openclaw"]);
25
26
  // Real removal commands — harness id is not the binary name.
26
27
  const UNINSTALL_POINTERS = {
@@ -28,6 +29,8 @@ const UNINSTALL_POINTERS = {
28
29
  codex: "codex mcp remove tinyfish",
29
30
  hermes: "hermes mcp remove tinyfish",
30
31
  openclaw: "openclaw skills uninstall @tinyfish/tinyfish --global",
32
+ // OpenCode ships no `mcp remove`, so the only removal is editing the config it wrote.
33
+ opencode: "remove the tinyfish entry from ~/.config/opencode/opencode.json",
31
34
  };
32
35
  async function runHarnessConnect(harness, opts) {
33
36
  const base = { apiKey: opts.apiKey, mcpUrl: opts.mcpUrl, launch: false };
@@ -39,6 +42,8 @@ async function runHarnessConnect(harness, opts) {
39
42
  return connectHermes(base);
40
43
  if (harness === "openclaw")
41
44
  return connectOpenClaw({ mcpUrl: opts.mcpUrl, launch: false });
45
+ if (harness === "opencode")
46
+ return connectOpencode(base);
42
47
  return connectCursor({ apiKey: opts.apiKey, mcpUrl: opts.mcpUrl });
43
48
  }
44
49
  async function verifyHarness(harness, mcpUrl, apiKey) {
@@ -262,7 +267,7 @@ export async function runConnectAll(opts) {
262
267
  outLine(summaryLine(result, opts.uninstall));
263
268
  if (results.every((r) => !r.detected)) {
264
269
  outLine("");
265
- outLine("No supported harness found. Install one of: " + FIVE_HARNESSES.join(", "));
270
+ outLine("No supported harness found. Install one of: " + ALL_HARNESSES.join(", "));
266
271
  if (!opts.uninstall)
267
272
  outLine("Then run: npx @tiny-fish/cli connect --all");
268
273
  return 1;
@@ -22,6 +22,7 @@ declare const doctorCheckSchema: z.ZodObject<{
22
22
  cursor: "cursor";
23
23
  codex: "codex";
24
24
  hermes: "hermes";
25
+ opencode: "opencode";
25
26
  "claude-code": "claude-code";
26
27
  }>>;
27
28
  }, z.core.$strip>;
@@ -31,6 +32,7 @@ declare const doctorHarnessSchema: z.ZodObject<{
31
32
  cursor: "cursor";
32
33
  codex: "codex";
33
34
  hermes: "hermes";
35
+ opencode: "opencode";
34
36
  "claude-code": "claude-code";
35
37
  }>;
36
38
  detected: z.ZodBoolean;
@@ -57,6 +59,7 @@ declare const doctorRepairSchema: z.ZodObject<{
57
59
  cursor: "cursor";
58
60
  codex: "codex";
59
61
  hermes: "hermes";
62
+ opencode: "opencode";
60
63
  "claude-code": "claude-code";
61
64
  }>>;
62
65
  command: z.ZodString;
@@ -81,6 +84,7 @@ export declare const doctorReportSchema: z.ZodObject<{
81
84
  cursor: "cursor";
82
85
  codex: "codex";
83
86
  hermes: "hermes";
87
+ opencode: "opencode";
84
88
  "claude-code": "claude-code";
85
89
  }>>;
86
90
  }, z.core.$strip>>;
@@ -90,6 +94,7 @@ export declare const doctorReportSchema: z.ZodObject<{
90
94
  cursor: "cursor";
91
95
  codex: "codex";
92
96
  hermes: "hermes";
97
+ opencode: "opencode";
93
98
  "claude-code": "claude-code";
94
99
  }>;
95
100
  detected: z.ZodBoolean;
@@ -116,6 +121,7 @@ export declare const doctorReportSchema: z.ZodObject<{
116
121
  cursor: "cursor";
117
122
  codex: "codex";
118
123
  hermes: "hermes";
124
+ opencode: "opencode";
119
125
  "claude-code": "claude-code";
120
126
  }>>;
121
127
  command: z.ZodString;
@@ -1,9 +1,9 @@
1
1
  import { z } from "zod";
2
- import { FIVE_HARNESSES } from "./harness-detect.js";
2
+ import { ALL_HARNESSES } from "./harness-detect.js";
3
3
  /** Bumped whenever a consumer could misread the payload; the cookbook skill releases separately. */
4
4
  export const DOCTOR_SCHEMA_VERSION = 1;
5
5
  const checkStatusSchema = z.enum(["pass", "fail", "warn", "skip"]);
6
- const harnessSchema = z.enum(FIVE_HARNESSES);
6
+ const harnessSchema = z.enum(ALL_HARNESSES);
7
7
  const doctorCheckSchema = z.object({
8
8
  id: z.string(),
9
9
  title: z.string(),
@@ -1,10 +1,10 @@
1
- export declare const FIVE_HARNESSES: readonly ["claude-code", "codex", "cursor", "hermes", "openclaw"];
2
- export type FiveHarness = (typeof FIVE_HARNESSES)[number];
3
- export declare function harnessConfigPath(harness: FiveHarness): string;
1
+ export declare const ALL_HARNESSES: readonly ["claude-code", "codex", "cursor", "hermes", "openclaw", "opencode"];
2
+ export type Harness = (typeof ALL_HARNESSES)[number];
3
+ export declare function harnessConfigPath(harness: Harness): string;
4
4
  /** For reason strings that name a location; keeps them in sync with the table above. */
5
- export declare function harnessDisplayPath(harness: FiveHarness): string;
5
+ export declare function harnessDisplayPath(harness: Harness): string;
6
6
  export interface HarnessDetection {
7
- harness: FiveHarness;
7
+ harness: Harness;
8
8
  detected: boolean;
9
9
  configPath: string;
10
10
  }
@@ -1,7 +1,14 @@
1
1
  import * as fs from "fs";
2
2
  import * as os from "os";
3
3
  import * as path from "path";
4
- export const FIVE_HARNESSES = ["claude-code", "codex", "cursor", "hermes", "openclaw"];
4
+ export const ALL_HARNESSES = [
5
+ "claude-code",
6
+ "codex",
7
+ "cursor",
8
+ "hermes",
9
+ "openclaw",
10
+ "opencode",
11
+ ];
5
12
  // Presence detection only — dir existence means "installed", nothing more.
6
13
  const CONFIG_DIRS = {
7
14
  "claude-code": ".claude",
@@ -9,6 +16,7 @@ const CONFIG_DIRS = {
9
16
  cursor: ".cursor",
10
17
  hermes: ".hermes",
11
18
  openclaw: ".openclaw",
19
+ opencode: ".config/opencode",
12
20
  };
13
21
  export function harnessConfigPath(harness) {
14
22
  return path.join(os.homedir(), CONFIG_DIRS[harness]); // nosemgrep: path-join-resolve-traversal -- fixed dir names under os.homedir()
@@ -18,7 +26,7 @@ export function harnessDisplayPath(harness) {
18
26
  return `~/${CONFIG_DIRS[harness]}`;
19
27
  }
20
28
  export function detectInstalledHarnesses() {
21
- return FIVE_HARNESSES.map((harness) => {
29
+ return ALL_HARNESSES.map((harness) => {
22
30
  const configPath = harnessConfigPath(harness);
23
31
  let detected;
24
32
  try {
@@ -1,8 +1,8 @@
1
- import { type FiveHarness } from "./harness-detect.js";
1
+ import { type Harness } from "./harness-detect.js";
2
2
  export type Registered = "yes" | "no" | "unknown";
3
3
  export type AuthMode = "api-key" | "oauth" | "unknown";
4
4
  export interface RegistrationStatus {
5
- harness: FiveHarness;
5
+ harness: Harness;
6
6
  detected: boolean;
7
7
  registered: Registered;
8
8
  authMode: AuthMode;
@@ -12,4 +12,4 @@ export interface RegistrationStatus {
12
12
  reason?: string;
13
13
  }
14
14
  /** Never throws; a failed probe is `unknown` with a reason, so no caller can read it as healthy. */
15
- export declare function detectRegistrations(harnesses?: FiveHarness[]): RegistrationStatus[];
15
+ export declare function detectRegistrations(harnesses?: Harness[]): RegistrationStatus[];
@@ -24,6 +24,10 @@ const UNSUPPORTED_SUBCOMMAND = /unknown command|unrecognized subcommand|invalid
24
24
  // `claude mcp get` exits 1 for a usage error too, so only this message is evidence of absence.
25
25
  const NO_SUCH_SERVER = /no (?:mcp )?server named/i;
26
26
  const API_KEY_HEADER = /x-api-key/i;
27
+ // OpenCode colours its list even through a pipe, putting `\x1b[34m` between the line start and
28
+ // the server name, so an unstripped probe reads a registered harness as absent.
29
+ // eslint-disable-next-line no-control-regex
30
+ const ANSI_SGR = /\x1b\[[0-9;]*m/g;
27
31
  // The report is pasted in public, so every reason is authored here. A spawn error's own
28
32
  // message quotes absolute paths, which is how a home directory reaches the payload.
29
33
  function spawnFailureReason(command, code) {
@@ -34,7 +38,13 @@ function spawnFailureReason(command, code) {
34
38
  return `could not run \`${command}\``;
35
39
  }
36
40
  function runProbe(command, args) {
37
- const result = spawn.sync(command, args, { encoding: "utf8", timeout: PROBE_TIMEOUT_MS });
41
+ const result = spawn.sync(command, args, {
42
+ encoding: "utf8",
43
+ timeout: PROBE_TIMEOUT_MS,
44
+ // Belt and braces only: agents export FORCE_COLOR, and `NO_COLOR` does not fully silence
45
+ // OpenCode, so the strip below stays load-bearing rather than a fallback.
46
+ env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0" },
47
+ });
38
48
  if (result.error) {
39
49
  const code = result.error.code;
40
50
  // A timeout arrives as ETIMEDOUT here, not only as a signal, so it must be named before
@@ -48,12 +58,36 @@ function runProbe(command, args) {
48
58
  if (result.signal) {
49
59
  return { outcome: "unavailable", reason: `\`${command}\` was killed by ${result.signal}` };
50
60
  }
51
- const stdout = result.stdout ?? "";
61
+ // Both streams, since a colour code inside a JSON payload breaks the parse just as it
62
+ // breaks an anchored match. Valid JSON never carries a raw ESC byte, so this cannot corrupt it.
63
+ const stdout = (result.stdout ?? "").replace(ANSI_SGR, "");
52
64
  return {
53
65
  outcome: "ran",
54
66
  exitCode: result.status ?? 1,
55
67
  stdout,
56
- output: `${stdout}${result.stderr ?? ""}`,
68
+ output: `${stdout}${(result.stderr ?? "").replace(ANSI_SGR, "")}`,
69
+ };
70
+ }
71
+ // A plugin registers its server as `plugin:<plugin>:<server>`, which `mcp get tinyfish` misses.
72
+ // The optional prefix must end in `:`, so `not-tinyfish` is not a match.
73
+ const MCP_LIST_TINYFISH_LINE = /^((?:\S*:)?tinyfish):\s+(https?:\S+)/m;
74
+ // A bare substring also matches `not-tinyfish` and any row quoting a tinyfish URL, so the name
75
+ // is required to be the first word on its line, after whatever status glyph the CLI prints.
76
+ const LISTED_AS_TINYFISH = /^[^\w]*tinyfish(?![\w-])/m;
77
+ /** The plugin ships its own MCP server, so a working install can carry no `tinyfish` entry. */
78
+ function fromPluginRegistration(command) {
79
+ const probe = runProbe(command, ["mcp", "list"]);
80
+ if (probe.outcome === "unavailable" || probe.exitCode !== 0)
81
+ return undefined;
82
+ const match = MCP_LIST_TINYFISH_LINE.exec(probe.output);
83
+ if (!match)
84
+ return undefined;
85
+ // `mcp list` prints no headers, so the credential behind it is unproven, never assumed.
86
+ return {
87
+ registered: "yes",
88
+ authMode: "unknown",
89
+ registeredUrl: match[2],
90
+ reason: `registered as ${match[1]}, which \`mcp get tinyfish\` does not resolve`,
57
91
  };
58
92
  }
59
93
  // claude-code only. Codex cannot use this: its `mcp get` exits 0 on a missing server.
@@ -70,6 +104,10 @@ function fromMcpGet(command, keyAuthPattern) {
70
104
  reason: `\`${command} mcp get\` is unsupported by this version`,
71
105
  };
72
106
  }
107
+ // Positive evidence from `mcp list` outranks anything inferred from a failed `mcp get`.
108
+ const plugin = fromPluginRegistration(command);
109
+ if (plugin)
110
+ return plugin;
73
111
  // A broken CLI also exits nonzero, and reading that as absence earns a spurious repair.
74
112
  if (!NO_SUCH_SERVER.test(probe.output)) {
75
113
  return {
@@ -162,6 +200,26 @@ function probeCursor() {
162
200
  ...(entry.url ? { registeredUrl: entry.url } : {}),
163
201
  };
164
202
  }
203
+ // Both list commands print this header in every state, including "no servers configured".
204
+ // Exit 0 with output we cannot recognise is not evidence of absence: reporting `no` there would
205
+ // earn a connect repair off a parse failure, which is the bug class this command exists to kill.
206
+ const MCP_LIST_HEADER = /mcp servers/i;
207
+ // OpenCode prints the endpoint on the line under the server row; Hermes truncates its column.
208
+ const OPENCODE_ROW_URL = /^[^\w]*tinyfish(?![\w-]).*\n[^\w]*(https?:\S+)/m;
209
+ function fromMcpList(command, output, registeredMode, urlPattern) {
210
+ if (LISTED_AS_TINYFISH.test(output)) {
211
+ const url = urlPattern?.exec(output)?.[1];
212
+ return { registered: "yes", authMode: registeredMode, ...(url ? { registeredUrl: url } : {}) };
213
+ }
214
+ if (!MCP_LIST_HEADER.test(output)) {
215
+ return {
216
+ registered: "unknown",
217
+ authMode: "unknown",
218
+ reason: `could not interpret \`${command} mcp list\` output`,
219
+ };
220
+ }
221
+ return { registered: "no", authMode: "unknown" };
222
+ }
165
223
  // Hermes registers with `--auth oauth` and has no key path today, so mode is fixed.
166
224
  function probeHermes() {
167
225
  const probe = runProbe("hermes", ["mcp", "list"]);
@@ -175,10 +233,9 @@ function probeHermes() {
175
233
  reason: `\`hermes mcp list\` exited ${probe.exitCode}`,
176
234
  };
177
235
  }
178
- return probe.output.includes("tinyfish")
179
- ? { registered: "yes", authMode: "oauth" }
180
- : { registered: "no", authMode: "unknown" };
236
+ return fromMcpList("hermes", probe.output, "oauth");
181
237
  }
238
+ const SKILL_DIR_IS_TINYFISH = /^(?:@tinyfish[/_-])?tinyfish(?![\w-])/i;
182
239
  // OpenClaw installs a skill, not an MCP server; the skill shells the CLI, so auth is the CLI key.
183
240
  function probeOpenClaw() {
184
241
  const skillsDir = path.join(harnessConfigPath("openclaw"), "skills");
@@ -193,16 +250,33 @@ function probeOpenClaw() {
193
250
  reason: `no global skills directory at ${harnessDisplayPath("openclaw")}/skills; OpenClaw layout is unverified`,
194
251
  };
195
252
  }
196
- return entries.some((name) => /tinyfish/i.test(name))
253
+ // A bare substring also matched a skill merely named `not-tinyfish-thing`.
254
+ return entries.some((name) => SKILL_DIR_IS_TINYFISH.test(name))
197
255
  ? { registered: "yes", authMode: "api-key" }
198
256
  : { registered: "no", authMode: "unknown" };
199
257
  }
258
+ // Connect registers with `mcp add --url` then `mcp auth`, so there is no key path today.
259
+ function probeOpencode() {
260
+ const probe = runProbe("opencode", ["mcp", "list"]);
261
+ if (probe.outcome === "unavailable") {
262
+ return { registered: "unknown", authMode: "unknown", reason: probe.reason };
263
+ }
264
+ if (probe.exitCode !== 0) {
265
+ return {
266
+ registered: "unknown",
267
+ authMode: "unknown",
268
+ reason: `\`opencode mcp list\` exited ${probe.exitCode}`,
269
+ };
270
+ }
271
+ return fromMcpList("opencode", probe.output, "oauth", OPENCODE_ROW_URL);
272
+ }
200
273
  const PROBES = {
201
274
  "claude-code": () => fromMcpGet("claude", API_KEY_HEADER),
202
275
  codex: probeCodex,
203
276
  cursor: probeCursor,
204
277
  hermes: probeHermes,
205
278
  openclaw: probeOpenClaw,
279
+ opencode: probeOpencode,
206
280
  };
207
281
  /** Never throws; a failed probe is `unknown` with a reason, so no caller can read it as healthy. */
208
282
  export function detectRegistrations(harnesses) {
@@ -28,6 +28,7 @@ declare const harnessResultSchema: z.ZodObject<{
28
28
  cursor: "cursor";
29
29
  codex: "codex";
30
30
  hermes: "hermes";
31
+ opencode: "opencode";
31
32
  "claude-code": "claude-code";
32
33
  }>;
33
34
  detected: z.ZodBoolean;
@@ -45,6 +46,7 @@ export declare const setupCompletedPayloadSchema: z.ZodObject<{
45
46
  cursor: "cursor";
46
47
  codex: "codex";
47
48
  hermes: "hermes";
49
+ opencode: "opencode";
48
50
  "claude-code": "claude-code";
49
51
  }>;
50
52
  detected: z.ZodBoolean;
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { z } from "zod";
3
3
  import { validateKeyFormat } from "./auth.js";
4
4
  import { BASE_URL, CLI_VERSION } from "./constants.js";
5
- import { FIVE_HARNESSES } from "./harness-detect.js";
5
+ import { ALL_HARNESSES } from "./harness-detect.js";
6
6
  import { captureNotice } from "./notice.js";
7
7
  import { errLine } from "./output.js";
8
8
  const CONNECT_EVENT_TIMEOUT_MS = 2_000;
@@ -95,7 +95,7 @@ export async function postConnectEvent(options) {
95
95
  }
96
96
  }
97
97
  const harnessResultSchema = z.object({
98
- harness: z.enum(FIVE_HARNESSES),
98
+ harness: z.enum(ALL_HARNESSES),
99
99
  detected: z.boolean(),
100
100
  installed: z.boolean(),
101
101
  verify_depth: z.enum(["health", "health+auth"]).nullable(),
@@ -104,8 +104,8 @@ const harnessResultSchema = z.object({
104
104
  export const setupCompletedPayloadSchema = z.object({
105
105
  harnesses: z
106
106
  .array(harnessResultSchema)
107
- .length(FIVE_HARNESSES.length)
108
- .refine((results) => new Set(results.map((r) => r.harness)).size === FIVE_HARNESSES.length, {
107
+ .length(ALL_HARNESSES.length)
108
+ .refine((results) => new Set(results.map((r) => r.harness)).size === ALL_HARNESSES.length, {
109
109
  message: "expected one result per harness",
110
110
  }),
111
111
  cli_version: z.string(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiny-fish/cli",
3
- "version": "0.15.1-next.161",
3
+ "version": "0.16.0",
4
4
  "description": "TinyFish CLI — run web automations from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,7 +32,7 @@
32
32
  "prepublishOnly": "npm run build"
33
33
  },
34
34
  "dependencies": {
35
- "@tiny-fish/sdk": "^0.3.0",
35
+ "@tiny-fish/sdk": "^0.4.0",
36
36
  "chrome-cookies-secure": "^3.0.2",
37
37
  "commander": "^12.0.0",
38
38
  "cross-spawn": "^7.0.6",