@tiny-fish/cli 0.35.1-next.291 → 0.35.1-next.292

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.
@@ -23,11 +23,6 @@ export interface NativeConnectOptions {
23
23
  /** Targeted connects only: --all keeps its per-harness failed row. */
24
24
  fallbackWhenMissing?: boolean;
25
25
  }
26
- export declare function connectClaudeCode(options: NativeConnectOptions): Promise<ConnectAuthMode | undefined>;
27
- export declare function connectCodex(options: NativeConnectOptions): Promise<ConnectAuthMode | undefined>;
28
- export declare function connectGrok(options: NativeConnectOptions): Promise<ConnectAuthMode | undefined>;
29
- export declare function connectHermes(options: NativeConnectOptions): Promise<ConnectAuthMode | undefined>;
30
- export declare function connectOpencode(options: NativeConnectOptions): Promise<ConnectAuthMode | undefined>;
31
26
  export declare function connectOpenClaw(options: {
32
27
  apiKey?: string;
33
28
  mcpUrl: string;
@@ -50,4 +45,5 @@ export declare function connectCursor(options: {
50
45
  verbose?: boolean;
51
46
  }): Promise<void>;
52
47
  export declare function launchAgent(client: AgentClient): Promise<void>;
48
+ export declare function connectHarness(harness: AgentClient, options: NativeConnectOptions): Promise<ConnectAuthMode | undefined>;
53
49
  export declare function registerConnect(program: Command): void;
@@ -1,6 +1,6 @@
1
1
  import spawn from 'cross-spawn';
2
2
  import { CONNECT_SOURCE, persistApiKeyToEnvironment, saveConnectContext, validateKeyFormat, validatedApiKey, } from '../lib/auth.js';
3
- import { CLAUDE_CODE, CODEX, CURSOR_SKILL_TARGET, GROK, HERMES, OPENCLAW, OPENCLAW_SKILL_INSTALL_ARGS, OPENCODE, launchNativeMcpClient, launchOpenClawWalkthrough, openExternalUrl, } from '../lib/connect-clients.js';
3
+ import { CURSOR_SKILL_TARGET, OPENCLAW, OPENCLAW_SKILL_INSTALL_ARGS, NATIVE_BY_HARNESS, launchNativeMcpClient, launchOpenClawWalkthrough, openExternalUrl, } from '../lib/connect-clients.js';
4
4
  import { captureStdio, capturedOutput, HoistedFailureReportedError, installTinyFishCli, replay, SKILL_INSTALL_TIMEOUT_MS, STEP_MAX_BUFFER, } from '../lib/cli-install.js';
5
5
  import { ensureCliAuthenticated } from '../lib/connect-auth.js';
6
6
  import { createStdinPrompt, runCliFallback } from '../lib/connect-fallback.js';
@@ -8,7 +8,7 @@ import { installWebSkill } from '../lib/skill-install.js';
8
8
  import { ConnectInterruptedError, ConnectStepError, createConnectTelemetry, finishSetupCommand, requireCommandSupport, runGuarded, settle, SignInTimeoutError, spawnStepError, stageDurationOf, withStageDuration, spawnRemoval, } from '../lib/connect-runtime.js';
9
9
  import { cursorInstallDeeplink, cursorMcpPath, readCursorTinyfishEntry, writeCursorMcpConfig, } from '../lib/cursor-config.js';
10
10
  import { runConnectAll } from '../lib/connect-all.js';
11
- import { AuthMode, RELOAD_ACTION } from '../lib/harness-detect.js';
11
+ import { ALL_HARNESSES, AuthMode, RELOAD_ACTION } from '../lib/harness-detect.js';
12
12
  import { detectHumanInitiated } from '../lib/harness.js';
13
13
  import { TINYFISH_API_KEY_VAR } from '../lib/constants.js';
14
14
  import { emitNotice } from '../lib/notice.js';
@@ -442,21 +442,6 @@ function trackPostInstallFailure(displayName, state, telemetry, error) {
442
442
  errLine(`The ${displayName} MCP connection succeeded, but a finishing step ` +
443
443
  `(${state.stage}) failed: ${error instanceof Error ? error.message : String(error)}`);
444
444
  }
445
- export async function connectClaudeCode(options) {
446
- return connectNativeMcpClient(CLAUDE_CODE, options);
447
- }
448
- export async function connectCodex(options) {
449
- return connectNativeMcpClient(CODEX, options);
450
- }
451
- export async function connectGrok(options) {
452
- return connectNativeMcpClient(GROK, options);
453
- }
454
- export async function connectHermes(options) {
455
- return connectNativeMcpClient(HERMES, options);
456
- }
457
- export async function connectOpencode(options) {
458
- return connectNativeMcpClient(OPENCODE, options);
459
- }
460
445
  export async function connectOpenClaw(options) {
461
446
  const telemetry = createConnectTelemetry(options.mcpUrl, 'openclaw', options);
462
447
  // Skill install, no MCP server — never degraded, and false keeps it inside a `= false` filter.
@@ -620,38 +605,46 @@ function launchCursorDeeplink(mcpUrl) {
620
605
  const result = openExternalUrl(cursorInstallDeeplink(mcpUrl));
621
606
  return !result.error && result.status === 0;
622
607
  }
623
- // A new client fails to compile until wired here.
624
- const AGENT_LAUNCHERS = {
625
- 'claude-code': () => launchNativeMcpClient(CLAUDE_CODE),
626
- codex: () => launchNativeMcpClient(CODEX),
627
- grok: () => launchNativeMcpClient(GROK),
628
- hermes: () => launchNativeMcpClient(HERMES),
629
- opencode: () => launchNativeMcpClient(OPENCODE),
608
+ /** Total over the non-native harnesses: a new one fails to compile until wired here. */
609
+ const LAUNCH_OVERRIDES = {
630
610
  openclaw: launchOpenClawWalkthrough,
631
611
  // Cursor has no chat CLI to launch.
632
612
  cursor: () => {
633
613
  throw new Error('Cursor has no launchable walkthrough. Open Cursor and start chatting.');
634
614
  },
635
615
  };
616
+ const isNonNative = (harness) => harness in LAUNCH_OVERRIDES;
636
617
  export async function launchAgent(client) {
637
- await AGENT_LAUNCHERS[client]();
638
- }
639
- // A new client fails to compile until wired here.
640
- const CLIENT_CONNECTORS = {
641
- 'claude-code': connectClaudeCode,
642
- codex: connectCodex,
643
- grok: connectGrok,
644
- hermes: connectHermes,
645
- opencode: connectOpencode,
646
- openclaw: connectOpenClaw,
647
- // `connect cursor --launch` still connects; it just cannot launch after.
618
+ if (isNonNative(client))
619
+ return void (await LAUNCH_OVERRIDES[client]());
620
+ await launchNativeMcpClient(requireNative(client));
621
+ }
622
+ /** Total over the non-native harnesses; native harnesses use the descriptor engine. */
623
+ const CONNECTOR_OVERRIDES = {
624
+ openclaw: async (options) => {
625
+ await connectOpenClaw(options);
626
+ return undefined;
627
+ },
648
628
  cursor: async (options) => {
649
629
  await connectCursor(options);
630
+ // `connect cursor --launch` still connects; it just cannot launch after.
650
631
  if (options.launch) {
651
632
  errLine('Cursor has no launchable walkthrough. Open Cursor and start chatting.');
652
633
  }
634
+ return undefined;
653
635
  },
654
636
  };
637
+ function requireNative(harness) {
638
+ const client = NATIVE_BY_HARNESS.get(harness);
639
+ if (!client)
640
+ throw new Error(`No native MCP descriptor for ${harness}`);
641
+ return client;
642
+ }
643
+ export async function connectHarness(harness, options) {
644
+ if (isNonNative(harness))
645
+ return CONNECTOR_OVERRIDES[harness](options);
646
+ return connectNativeMcpClient(requireNative(harness), options);
647
+ }
655
648
  function parseMcpUrlOption(options) {
656
649
  let mcpUrl = options.url ?? DEFAULT_MCP_URL;
657
650
  if (mcpUrl.startsWith('-')) {
@@ -729,13 +722,10 @@ async function connectSingleClient(client, options, mcpUrl, attemptId) {
729
722
  fallbackWhenMissing: true,
730
723
  verbose: options.verbose ?? false,
731
724
  };
732
- const connector = CLIENT_CONNECTORS[client];
733
- if (!connector) {
734
- throw new Error('Unsupported client: ' +
735
- client +
736
- '. Supported clients: claude-code, codex, cursor, grok, hermes, openclaw, opencode');
725
+ if (!ALL_HARNESSES.includes(client)) {
726
+ throw new Error('Unsupported client: ' + client + '. Supported clients: ' + ALL_HARNESSES.join(', '));
737
727
  }
738
- await connector(connectOptions);
728
+ await connectHarness(client, connectOptions);
739
729
  }
740
730
  export function registerConnect(program) {
741
731
  program
@@ -11,7 +11,7 @@ import { verifyMcpAuth, verifyMcpHealth } from '../lib/verify.js';
11
11
  import { resolveHermesHome } from '../lib/hermes-env.js';
12
12
  import { hermesWebBackendsOurs, readHermesPluginStatus, } from '../lib/hermes-plugin.js';
13
13
  import { DEFAULT_MCP_URL, endpointOf, isDefaultEndpoint } from '../lib/mcp-endpoint.js';
14
- import { connectClaudeCode, connectCodex, connectCursor, connectGrok, connectHermes, connectOpenClaw, connectOpencode, } from './connect.js';
14
+ import { connectHarness } from './connect.js';
15
15
  // A green auth mode with a red auth call proves nothing, so the call's verdict gates the claim.
16
16
  function provesHarnessReach(status, keyAuth, healthOk, mcpUrl) {
17
17
  // An endpoint doctor could not reach proves nothing about a harness pointed at it.
@@ -375,21 +375,12 @@ function runAuthLogin() {
375
375
  throw new Error(`\`tinyfish auth login\` exited ${result.status ?? 'unknown'}`);
376
376
  }
377
377
  }
378
- const CONNECTORS = {
379
- 'claude-code': (opts) => connectClaudeCode({ ...opts, launch: false }),
380
- codex: (opts) => connectCodex({ ...opts, launch: false }),
381
- cursor: (opts) => connectCursor(opts),
382
- grok: (opts) => connectGrok({ ...opts, launch: false }),
383
- hermes: (opts) => connectHermes({ ...opts, launch: false }),
384
- openclaw: (opts) => connectOpenClaw({ ...opts, launch: false }),
385
- opencode: (opts) => connectOpencode({ ...opts, launch: false }),
386
- };
387
378
  /** Dispatches on `action`, never on a null harness: that conflation made the credential fix a no-op. */
388
379
  async function runRepair(repair, mcpUrl) {
389
380
  if (repair.action === 'auth-login')
390
381
  return runAuthLogin();
391
382
  if (repair.harness)
392
- await CONNECTORS[repair.harness]({ mcpUrl });
383
+ await connectHarness(repair.harness, { mcpUrl, launch: false });
393
384
  }
394
385
  export async function applyRepairs(repairs, options) {
395
386
  const interactive = detectHumanInitiated();
@@ -3,19 +3,22 @@ import { getApiKey, saveConfig } from '../lib/auth.js';
3
3
  import { fetchContentGet, searchQuery } from '../lib/client.js';
4
4
  import { BASE_URL, TINYFISH_API_KEY_VAR } from '../lib/constants.js';
5
5
  import { errLine, handleApiError, outLine } from '../lib/output.js';
6
- import { connectClaudeCode, connectCodex, connectHermes, connectOpenClaw, connectOpencode, launchAgent, } from './connect.js';
6
+ import { connectHarness, launchAgent } from './connect.js';
7
+ import { HARNESS_DISPLAY_NAMES } from '../lib/harness-detect.js';
7
8
  const EXAMPLE_QUERIES = [
8
9
  'What are recent changes in the AI dev stack? Give me the top 5 changes that matter, why they matter, and any action needed.',
9
10
  'Find a new-grad software engineering role posted in the last 24 hours and fetch the job description.',
10
11
  "Analyse today's market and recommend the best investments for $5,000 with expected returns and risks.",
11
12
  ];
12
- const AGENTS = [
13
- { id: 'claude-code', label: 'Claude Code', connect: connectClaudeCode },
14
- { id: 'codex', label: 'Codex', connect: connectCodex },
15
- { id: 'hermes', label: 'Hermes', connect: connectHermes },
16
- { id: 'openclaw', label: 'OpenClaw', connect: connectOpenClaw },
17
- { id: 'opencode', label: 'OpenCode', connect: connectOpencode },
13
+ /** Onboarding's own shortlist, not every harness; order is the menu order. */
14
+ const AGENT_IDS = [
15
+ 'claude-code',
16
+ 'codex',
17
+ 'hermes',
18
+ 'openclaw',
19
+ 'opencode',
18
20
  ];
21
+ const AGENTS = AGENT_IDS.map((id) => ({ id, label: HARNESS_DISPLAY_NAMES[id] }));
19
22
  function cleanFetchedContent(text) {
20
23
  const cleaned = text
21
24
  .replace(/!\[[^\]]*\]\([^)]+\)/g, '')
@@ -82,16 +85,12 @@ const page = await client.fetch.getContents({ urls: [topUrl], format: "markdown"
82
85
  console.log(page.results[0]?.text);`;
83
86
  }
84
87
  async function connectAgent(id) {
85
- const agent = AGENTS.find((candidate) => candidate.id === id);
86
- if (!agent)
87
- return;
88
88
  const mcpUrl = new URL('/mcp', BASE_URL).toString();
89
- if (id === 'openclaw') {
90
- await connectOpenClaw({ mcpUrl, launch: false, cliInstall: false });
91
- }
92
- else {
93
- await agent.connect({ mcpUrl, launch: false });
94
- }
89
+ await connectHarness(id, {
90
+ mcpUrl,
91
+ launch: false,
92
+ ...(id === 'openclaw' ? { cliInstall: false } : {}),
93
+ });
95
94
  }
96
95
  function parseSelectedAgent(value, selected) {
97
96
  if (!value.trim())
@@ -1,4 +1,5 @@
1
- import { HARNESS_CAPABILITIES, NATIVE_BY_HARNESS } from './connect-clients.js';
1
+ import { NATIVE_BY_HARNESS } from './connect-clients.js';
2
+ import { harnessSpec } from './harness-spec.js';
2
3
  import { ConnectInterruptedError, ConnectStepError, SignInTimeoutError, } from './connect-runtime.js';
3
4
  import { ALL_HARNESSES, HARNESS_DISPLAY_NAMES as DISPLAY_NAMES, } from './harness-detect.js';
4
5
  import { errLine } from './output.js';
@@ -25,7 +26,7 @@ const KEY_REQUIRED = new Set(ALL_HARNESSES.filter((harness) => !!NATIVE_BY_HARNE
25
26
  function headlessGate(base, oauthBudget, fixCommand) {
26
27
  const { harness } = base;
27
28
  // A CLI-login sign-in is interactive-only — unboundable headless.
28
- if (HARNESS_CAPABILITIES[harness].signInViaCliLogin) {
29
+ if (harnessSpec(harness).signInViaCliLogin) {
29
30
  return {
30
31
  result: { ...base, outcome: 'auth_pending', fixCommand },
31
32
  headlessAuth: false,
@@ -1,5 +1,5 @@
1
- import { HARNESS_CAPABILITIES } from './connect-clients.js';
2
1
  import { finishSetupCommand } from './connect-runtime.js';
2
+ import { harnessSpec } from './harness-spec.js';
3
3
  import { HARNESS_COMMANDS, HARNESS_DISPLAY_NAMES as DISPLAY_NAMES, RELOAD_ACTION, } from './harness-detect.js';
4
4
  // Naming a harness the user does not have is the noise PF-3708 cut.
5
5
  const HIDDEN_ONCE_SOMETHING_WORKED = new Set([
@@ -83,7 +83,7 @@ function installedSummaryLine(result) {
83
83
  `Fix: ${finishSetupCommand(result.harness)}`);
84
84
  }
85
85
  // A keyed install has no sign-in left, so the deferred-auth warning would contradict it.
86
- const authDeferred = HARNESS_CAPABILITIES[result.harness].authDeferredAtInstall && !result.keyAuthed;
86
+ const authDeferred = harnessSpec(result.harness).authDeferredAtInstall && !result.keyAuthed;
87
87
  const verifyLabel = result.verifyDepth ? `verified: ${result.verifyDepth}` : 'not verified';
88
88
  if (authDeferred || result.verifyOk === false) {
89
89
  const reason = authDeferred
@@ -1,12 +1,13 @@
1
1
  import * as readline from 'node:readline/promises';
2
+ import { harnessSpec } from './harness-spec.js';
2
3
  import spawn from 'cross-spawn';
3
- import { connectClaudeCode, connectCodex, connectCursor, connectGrok, connectHermes, connectOpenClaw, connectOpencode, } from '../commands/connect.js';
4
+ import { connectHarness } from '../commands/connect.js';
4
5
  import { loadConfig, validatedApiKey } from './auth.js';
5
6
  import { installTinyFishCli, } from './cli-install.js';
6
7
  import { authGate, mapConnectError, OAUTH_SIGN_IN_TIMEOUT_MS } from './connect-all-auth.js';
7
8
  import { actionable, computeExitCode, reloadHint, renderSummary } from './connect-all-summary.js';
8
9
  import { planText, uninstallHarness, uninstallPlanText } from './connect-all-uninstall.js';
9
- import { HARNESS_CAPABILITIES, NATIVE_BY_HARNESS } from './connect-clients.js';
10
+ import { NATIVE_BY_HARNESS } from './connect-clients.js';
10
11
  import { createStdinPrompt, runCliFallback } from './connect-fallback.js';
11
12
  import { ConnectInterruptedError } from './connect-runtime.js';
12
13
  import { pickHarnesses } from './connect-picker.js';
@@ -18,26 +19,9 @@ import { sendSetupCompleted, sendSetupStarted, } from './setup-telemetry.js';
18
19
  import { verifyMcpAuth, verifyMcpHealth } from './verify.js';
19
20
  const STEP_INDENT = ' ';
20
21
  // Derived: a native add carries the key, or the CLI holds it itself.
21
- const KEY_AUTH_CAPABLE = new Set(ALL_HARNESSES.filter((harness) => !!NATIVE_BY_HARNESS.get(harness)?.keyAuth || HARNESS_CAPABILITIES[harness].keyHeldByCli));
22
- // A new harness fails to compile until wired here.
23
- const HARNESS_CONNECTORS = {
24
- 'claude-code': connectClaudeCode,
25
- codex: connectCodex,
26
- grok: connectGrok,
27
- hermes: connectHermes,
28
- opencode: connectOpencode,
29
- // Without the key the child shells an interactive `tinyfish auth login`.
30
- openclaw: async (base) => {
31
- await connectOpenClaw(base);
32
- return undefined;
33
- },
34
- cursor: async (base) => {
35
- await connectCursor({ apiKey: base.apiKey, mcpUrl: base.mcpUrl, verbose: base.verbose });
36
- return undefined;
37
- },
38
- };
22
+ const KEY_AUTH_CAPABLE = new Set(ALL_HARNESSES.filter((harness) => !!NATIVE_BY_HARNESS.get(harness)?.keyAuth || harnessSpec(harness).keyHeldByCli));
39
23
  async function runHarnessConnect(harness, opts, extras) {
40
- return HARNESS_CONNECTORS[harness]({
24
+ return connectHarness(harness, {
41
25
  apiKey: opts.apiKey,
42
26
  mcpUrl: opts.mcpUrl,
43
27
  launch: false,
@@ -70,7 +54,7 @@ function runHoistedCliInstall(verbose) {
70
54
  async function verifyHarness(harness, mcpUrl, keyed, apiKey) {
71
55
  const health = await verifyMcpHealth(mcpUrl);
72
56
  // The own-OAuth harnesses are auth-checkable only in key mode (key-authed config).
73
- const capable = HARNESS_CAPABILITIES[harness].canVerifyAuth || keyed;
57
+ const capable = harnessSpec(harness).canVerifyAuth || keyed;
74
58
  if (!capable || !health.ok)
75
59
  return health;
76
60
  if (!apiKey)
@@ -158,9 +142,7 @@ function noWorkOutcome(d, uninstall) {
158
142
  }
159
143
  /** A config-writing connect needs no binary; every other harness spawns one. */
160
144
  function isStaleConfig(detection) {
161
- return (detection.detected &&
162
- !detection.binaryOnPath &&
163
- !HARNESS_CAPABILITIES[detection.harness].cliWritesConfig);
145
+ return (detection.detected && !detection.binaryOnPath && !harnessSpec(detection.harness).cliWritesConfig);
164
146
  }
165
147
  async function resolvePick(detections, opts, prompt) {
166
148
  let deselected = new Set();
@@ -187,7 +169,7 @@ function hoistCliIfNeeded(detections, opts, willAttempt) {
187
169
  !opts.uninstall &&
188
170
  detections.some((d) => d.detected &&
189
171
  d.binaryOnPath &&
190
- !HARNESS_CAPABILITIES[d.harness].cliWritesConfig &&
172
+ !harnessSpec(d.harness).cliWritesConfig &&
191
173
  willAttempt(d.harness));
192
174
  if (!cliNeeded)
193
175
  return undefined;
@@ -339,7 +321,7 @@ export async function runConnectAll(opts) {
339
321
  // Mirrors processHarness's gate: headless, only keyless CLI-login harnesses skip.
340
322
  const willAttempt = (harness) => !deselected.has(harness) &&
341
323
  (isTTY ||
342
- !HARNESS_CAPABILITIES[harness].signInViaCliLogin ||
324
+ !harnessSpec(harness).signInViaCliLogin ||
343
325
  (!!gateKey && KEY_AUTH_CAPABLE.has(harness)));
344
326
  const hoisted = hoistCliIfNeeded(detections, opts, willAttempt);
345
327
  if (hoisted === 'interrupted') {
@@ -1,5 +1,4 @@
1
1
  import spawn from 'cross-spawn';
2
- import { type Harness } from './harness-detect.js';
3
2
  import { type AgentClient, type SupportedCommand } from './connect-runtime.js';
4
3
  import { type HarnessSpec, type SkillAgent } from './harness-spec.js';
5
4
  export declare const OPENCLAW_SKILL_INSTALL_ARGS: readonly string[];
@@ -97,15 +96,7 @@ export declare function hermesRegistrationEnabled(home: string): {
97
96
  export declare const NATIVE_MCP_CLIENTS: readonly NativeMcpClient[];
98
97
  /** Native descriptor by harness id; Cursor and OpenClaw have none. */
99
98
  export declare const NATIVE_BY_HARNESS: Map<"openclaw" | "grok" | "cursor" | "codex" | "hermes" | "opencode" | "claude-code", NativeMcpClient>;
100
- export declare const CLAUDE_CODE: NativeMcpClient;
101
- export declare const CODEX: NativeMcpClient;
102
- export declare const GROK: NativeMcpClient;
103
- export declare const HERMES: NativeMcpClient;
104
- export declare const OPENCODE: NativeMcpClient;
105
99
  export declare const OPENCLAW: SupportedCommand;
106
- /** Traits with no native descriptor to live on (Cursor and OpenClaw included). */
107
- export type HarnessCapabilities = Pick<HarnessSpec, 'signInViaCliLogin' | 'canVerifyAuth' | 'keyHeldByCli' | 'cliWritesConfig' | 'authDeferredAtInstall'>;
108
- export declare const HARNESS_CAPABILITIES: Record<Harness, HarnessCapabilities>;
109
100
  /** "printed" = handed to the user to paste; the walkthrough was never started for them. */
110
101
  export type WalkthroughOutcome = 'launched' | 'printed';
111
102
  /** Awaited before the handover; track+flush must land pre-block. */
@@ -5,7 +5,7 @@ import { harnessConfigPath } from './harness-detect.js';
5
5
  import { errLine, sanitizeLine, warnLine } from './output.js';
6
6
  import { ConnectStepError, spawnStepError, } from './connect-runtime.js';
7
7
  import { TINYFISH_API_KEY_VAR } from './constants.js';
8
- import { ALL_HARNESSES, HARNESS_SPECS, NATIVE_HARNESSES, harnessSpec, } from './harness-spec.js';
8
+ import { HARNESS_SPECS, NATIVE_HARNESSES, harnessSpec, } from './harness-spec.js';
9
9
  import { HERMES_KEY_VAR, captureHermesKeyRestore, hermesEnvPath, resolveHermesHome, writeHermesKey, } from './hermes-env.js';
10
10
  import { hermesConfigPath, readHermesEntry } from './hermes-config.js';
11
11
  import { installHermesPlugin, setHermesWebBackends } from './hermes-plugin.js';
@@ -254,35 +254,11 @@ function toNativeClient(harness) {
254
254
  export const NATIVE_MCP_CLIENTS = NATIVE_HARNESSES.map(toNativeClient);
255
255
  /** Native descriptor by harness id; Cursor and OpenClaw have none. */
256
256
  export const NATIVE_BY_HARNESS = new Map(NATIVE_MCP_CLIENTS.map((client) => [client.connectClient, client]));
257
- function nativeClient(harness) {
258
- const client = NATIVE_BY_HARNESS.get(harness);
259
- if (!client)
260
- throw new Error(`${harness} is not a native MCP client`);
261
- return client;
262
- }
263
- export const CLAUDE_CODE = nativeClient('claude-code');
264
- export const CODEX = nativeClient('codex');
265
- export const GROK = nativeClient('grok');
266
- export const HERMES = nativeClient('hermes');
267
- export const OPENCODE = nativeClient('opencode');
268
257
  export const OPENCLAW = {
269
258
  command: HARNESS_SPECS.openclaw.command,
270
259
  displayName: HARNESS_SPECS.openclaw.displayName,
271
260
  supportCheck: HARNESS_SPECS.openclaw.supportCheck,
272
261
  };
273
- export const HARNESS_CAPABILITIES = Object.fromEntries(ALL_HARNESSES.map((harness) => {
274
- const spec = harnessSpec(harness);
275
- return [
276
- harness,
277
- {
278
- signInViaCliLogin: spec.signInViaCliLogin,
279
- canVerifyAuth: spec.canVerifyAuth,
280
- keyHeldByCli: spec.keyHeldByCli,
281
- cliWritesConfig: spec.cliWritesConfig,
282
- authDeferredAtInstall: spec.authDeferredAtInstall,
283
- },
284
- ];
285
- }));
286
262
  function printPromptForHandoff(displayName, command, prompt, reason) {
287
263
  // "set up", not "connected": sign-in can still be deferred here.
288
264
  errLine(`TinyFish is set up in ${displayName}.`);
@@ -247,5 +247,9 @@ export declare const HARNESS_SPECS: {
247
247
  export type Harness = keyof typeof HARNESS_SPECS;
248
248
  export declare const ALL_HARNESSES: readonly Harness[];
249
249
  export declare function harnessSpec(harness: Harness): HarnessSpec;
250
+ /** The rest of the union: every one of these must supply a connect and launch override. */
251
+ export type NonNativeHarness = {
252
+ [K in Harness]: 'urlStyle' extends keyof (typeof HARNESS_SPECS)[K] ? never : K;
253
+ }[Harness];
250
254
  /** Native MCP harnesses carry add-generation fields; the rest override connect. */
251
255
  export declare const NATIVE_HARNESSES: ("openclaw" | "grok" | "cursor" | "codex" | "hermes" | "opencode" | "claude-code")[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiny-fish/cli",
3
- "version": "0.35.1-next.291",
3
+ "version": "0.35.1-next.292",
4
4
  "description": "TinyFish CLI — run web automations from your terminal",
5
5
  "type": "module",
6
6
  "bin": {