@tiny-fish/cli 0.29.1-next.237 → 0.29.1-next.239
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.
|
@@ -2,7 +2,7 @@ import { Command } from "commander";
|
|
|
2
2
|
import { type TakeHoistedCliInstall } from "../lib/cli-install.js";
|
|
3
3
|
import { type AgentClient, type ConnectAuthMode } from "../lib/connect-runtime.js";
|
|
4
4
|
export declare const DEFAULT_MCP_URL = "https://agent.tinyfish.ai/mcp";
|
|
5
|
-
interface NativeConnectOptions {
|
|
5
|
+
export interface NativeConnectOptions {
|
|
6
6
|
apiKey?: string;
|
|
7
7
|
mcpUrl: string;
|
|
8
8
|
launch: boolean;
|
|
@@ -48,4 +48,3 @@ export declare function connectCursor(options: {
|
|
|
48
48
|
}): Promise<void>;
|
|
49
49
|
export declare function launchAgent(client: AgentClient): void;
|
|
50
50
|
export declare function registerConnect(program: Command): void;
|
|
51
|
-
export {};
|
package/dist/commands/connect.js
CHANGED
|
@@ -446,34 +446,38 @@ function launchCursorDeeplink(mcpUrl) {
|
|
|
446
446
|
const result = openExternalUrl(cursorInstallDeeplink(mcpUrl));
|
|
447
447
|
return !result.error && result.status === 0;
|
|
448
448
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
else if (client === "opencode") {
|
|
460
|
-
launchNativeMcpClient(OPENCODE);
|
|
461
|
-
}
|
|
462
|
-
else if (client === "grok") {
|
|
463
|
-
launchNativeMcpClient(GROK);
|
|
464
|
-
}
|
|
465
|
-
else if (client === "openclaw") {
|
|
466
|
-
launchOpenClawWalkthrough();
|
|
467
|
-
}
|
|
468
|
-
else if (client === "cursor") {
|
|
469
|
-
// Cursor has no chat CLI to launch.
|
|
449
|
+
// A new client fails to compile until wired here.
|
|
450
|
+
const AGENT_LAUNCHERS = {
|
|
451
|
+
"claude-code": () => launchNativeMcpClient(CLAUDE_CODE),
|
|
452
|
+
codex: () => launchNativeMcpClient(CODEX),
|
|
453
|
+
grok: () => launchNativeMcpClient(GROK),
|
|
454
|
+
hermes: () => launchNativeMcpClient(HERMES),
|
|
455
|
+
opencode: () => launchNativeMcpClient(OPENCODE),
|
|
456
|
+
openclaw: launchOpenClawWalkthrough,
|
|
457
|
+
// Cursor has no chat CLI to launch.
|
|
458
|
+
cursor: () => {
|
|
470
459
|
throw new Error("Cursor has no launchable walkthrough. Open Cursor and start chatting.");
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
460
|
+
},
|
|
461
|
+
};
|
|
462
|
+
export function launchAgent(client) {
|
|
463
|
+
AGENT_LAUNCHERS[client]();
|
|
476
464
|
}
|
|
465
|
+
// A new client fails to compile until wired here.
|
|
466
|
+
const CLIENT_CONNECTORS = {
|
|
467
|
+
"claude-code": connectClaudeCode,
|
|
468
|
+
codex: connectCodex,
|
|
469
|
+
grok: connectGrok,
|
|
470
|
+
hermes: connectHermes,
|
|
471
|
+
opencode: connectOpencode,
|
|
472
|
+
openclaw: connectOpenClaw,
|
|
473
|
+
// `connect cursor --launch` still connects; it just cannot launch after.
|
|
474
|
+
cursor: async (options) => {
|
|
475
|
+
await connectCursor(options);
|
|
476
|
+
if (options.launch) {
|
|
477
|
+
errLine("Cursor has no launchable walkthrough. Open Cursor and start chatting.");
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
};
|
|
477
481
|
export function registerConnect(program) {
|
|
478
482
|
program
|
|
479
483
|
.command("connect")
|
|
@@ -568,31 +572,9 @@ export function registerConnect(program) {
|
|
|
568
572
|
keyAuthOnly: !detectHumanInitiated() && !!validatedApiKey(options.apiKey),
|
|
569
573
|
verbose: options.verbose ?? false,
|
|
570
574
|
};
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
else if (client === "codex") {
|
|
575
|
-
await connectCodex(connectOptions);
|
|
576
|
-
}
|
|
577
|
-
else if (client === "grok") {
|
|
578
|
-
await connectGrok(connectOptions);
|
|
579
|
-
}
|
|
580
|
-
else if (client === "hermes") {
|
|
581
|
-
await connectHermes(connectOptions);
|
|
582
|
-
}
|
|
583
|
-
else if (client === "opencode") {
|
|
584
|
-
await connectOpencode(connectOptions);
|
|
585
|
-
}
|
|
586
|
-
else if (client === "openclaw") {
|
|
587
|
-
await connectOpenClaw(connectOptions);
|
|
588
|
-
}
|
|
589
|
-
else if (client === "cursor") {
|
|
590
|
-
// `connect <client> --launch` is the shape every doc uses; connect anyway, then say
|
|
591
|
-
// why nothing launched rather than throwing away a working install.
|
|
592
|
-
await connectCursor(connectOptions);
|
|
593
|
-
if (connectOptions.launch) {
|
|
594
|
-
errLine("Cursor has no launchable walkthrough. Open Cursor and start chatting.");
|
|
595
|
-
}
|
|
575
|
+
const connector = CLIENT_CONNECTORS[client];
|
|
576
|
+
if (connector) {
|
|
577
|
+
await connector(connectOptions);
|
|
596
578
|
}
|
|
597
579
|
else {
|
|
598
580
|
throw new Error("Unsupported client: " +
|
package/dist/lib/connect-all.js
CHANGED
|
@@ -4,7 +4,7 @@ import { connectClaudeCode, connectCodex, connectCursor, connectGrok, connectHer
|
|
|
4
4
|
import { loadConfig, validatedApiKey } from "./auth.js";
|
|
5
5
|
import { TINYFISH_API_KEY_VAR } from "./constants.js";
|
|
6
6
|
import { installTinyFishCli, } from "./cli-install.js";
|
|
7
|
-
import {
|
|
7
|
+
import { HARNESS_CAPABILITIES, NATIVE_BY_HARNESS } from "./connect-clients.js";
|
|
8
8
|
import { ensureCliAuthenticated, TINYFISH_CLI_NOT_FOUND_MESSAGE } from "./connect-auth.js";
|
|
9
9
|
import { commandNotFound, ConnectInterruptedError, ConnectStepError, SignInTimeoutError, finishSetupCommand, throwIfInterrupted, } from "./connect-runtime.js";
|
|
10
10
|
import { pickHarnesses } from "./connect-picker.js";
|
|
@@ -15,9 +15,6 @@ import { removeCursorMcpServer, planCursorWrite } from "./cursor-config.js";
|
|
|
15
15
|
import { errLine, outLine, setErrIndent } from "./output.js";
|
|
16
16
|
import { sendSetupCompleted } from "./setup-telemetry.js";
|
|
17
17
|
import { verifyMcpAuth, verifyMcpHealth } from "./verify.js";
|
|
18
|
-
// `codex mcp add` succeeds unauthenticated; OAuth lands at first tool use.
|
|
19
|
-
// TODO(PF-3580): connect now probes Codex sign-in, so this over-warns a signed-in install.
|
|
20
|
-
const AUTH_DEFERRED_AT_INSTALL = new Set(["codex"]);
|
|
21
18
|
// A probe that exits non-zero is an install so old the subcommand is missing entirely.
|
|
22
19
|
const TOO_OLD_REASONS = new Set([
|
|
23
20
|
"harness_too_old",
|
|
@@ -29,26 +26,16 @@ const NO_BROWSER_REASONS = new Set([
|
|
|
29
26
|
"harness_not_installed",
|
|
30
27
|
"command_not_found",
|
|
31
28
|
]);
|
|
32
|
-
const AUTH_CHECK_CAPABLE = new Set(["cursor", "openclaw"]);
|
|
33
29
|
const STEP_INDENT = " ";
|
|
34
30
|
// Must undercut agent tool-call budgets (Claude Code Bash defaults to 120s).
|
|
35
31
|
const OAUTH_SIGN_IN_TIMEOUT_MS = 90_000;
|
|
36
32
|
// Derived: these block `connect` on a browser sign-in when keyless.
|
|
37
|
-
const BROWSER_SIGN_IN = new Set(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
const KEY_AUTH_CAPABLE = new Set([
|
|
44
|
-
...NATIVE_MCP_CLIENTS.filter((client) => !!client.keyAuth)
|
|
45
|
-
.map((client) => client.connectClient)
|
|
46
|
-
.filter((client) => ALL_HARNESSES.includes(client))
|
|
47
|
-
// Second map only narrows connectClient to Harness; the filter above is the real gate.
|
|
48
|
-
.map((client) => client),
|
|
49
|
-
"cursor",
|
|
50
|
-
"openclaw",
|
|
51
|
-
]);
|
|
33
|
+
const BROWSER_SIGN_IN = new Set(ALL_HARNESSES.filter((harness) => {
|
|
34
|
+
const native = NATIVE_BY_HARNESS.get(harness);
|
|
35
|
+
return !!native?.loginArgs || !!native?.oauthInAdd;
|
|
36
|
+
}));
|
|
37
|
+
// Derived: a native add carries the key, or the CLI holds it itself.
|
|
38
|
+
const KEY_AUTH_CAPABLE = new Set(ALL_HARNESSES.filter((harness) => !!NATIVE_BY_HARNESS.get(harness)?.keyAuth || HARNESS_CAPABILITIES[harness].keyHeldByCli));
|
|
52
39
|
// Real removal commands — harness id is not the binary name.
|
|
53
40
|
const UNINSTALL_POINTERS = {
|
|
54
41
|
"claude-code": "claude mcp remove tinyfish --scope user",
|
|
@@ -59,8 +46,25 @@ const UNINSTALL_POINTERS = {
|
|
|
59
46
|
// OpenCode ships no `mcp remove`, so the only removal is editing the config it wrote.
|
|
60
47
|
opencode: "remove the tinyfish entry from ~/.config/opencode/opencode.json",
|
|
61
48
|
};
|
|
49
|
+
// A new harness fails to compile until wired here.
|
|
50
|
+
const HARNESS_CONNECTORS = {
|
|
51
|
+
"claude-code": connectClaudeCode,
|
|
52
|
+
codex: connectCodex,
|
|
53
|
+
grok: connectGrok,
|
|
54
|
+
hermes: connectHermes,
|
|
55
|
+
opencode: connectOpencode,
|
|
56
|
+
// Without the key the child shells an interactive `tinyfish auth login`.
|
|
57
|
+
openclaw: async (base) => {
|
|
58
|
+
await connectOpenClaw(base);
|
|
59
|
+
return undefined;
|
|
60
|
+
},
|
|
61
|
+
cursor: async (base) => {
|
|
62
|
+
await connectCursor({ apiKey: base.apiKey, mcpUrl: base.mcpUrl, verbose: base.verbose });
|
|
63
|
+
return undefined;
|
|
64
|
+
},
|
|
65
|
+
};
|
|
62
66
|
async function runHarnessConnect(harness, opts, keyAuthOnly, authTimeoutMs, hoistedCliInstall, onPostInstallFailed, deferOutro) {
|
|
63
|
-
|
|
67
|
+
return HARNESS_CONNECTORS[harness]({
|
|
64
68
|
apiKey: opts.apiKey,
|
|
65
69
|
mcpUrl: opts.mcpUrl,
|
|
66
70
|
launch: false,
|
|
@@ -70,35 +74,7 @@ async function runHarnessConnect(harness, opts, keyAuthOnly, authTimeoutMs, hois
|
|
|
70
74
|
onPostInstallFailed,
|
|
71
75
|
verbose: opts.verbose,
|
|
72
76
|
deferOutro,
|
|
73
|
-
};
|
|
74
|
-
if (harness === "claude-code")
|
|
75
|
-
return connectClaudeCode(base);
|
|
76
|
-
if (harness === "codex")
|
|
77
|
-
return connectCodex(base);
|
|
78
|
-
if (harness === "hermes")
|
|
79
|
-
return connectHermes(base);
|
|
80
|
-
if (harness === "openclaw") {
|
|
81
|
-
// Without the key the child shells an interactive `tinyfish auth login`.
|
|
82
|
-
await connectOpenClaw({
|
|
83
|
-
apiKey: opts.apiKey,
|
|
84
|
-
mcpUrl: opts.mcpUrl,
|
|
85
|
-
launch: false,
|
|
86
|
-
hoistedCliInstall,
|
|
87
|
-
verbose: opts.verbose,
|
|
88
|
-
deferOutro,
|
|
89
|
-
});
|
|
90
|
-
return undefined;
|
|
91
|
-
}
|
|
92
|
-
if (harness === "opencode")
|
|
93
|
-
return connectOpencode(base);
|
|
94
|
-
if (harness === "grok")
|
|
95
|
-
return connectGrok(base);
|
|
96
|
-
if (harness === "cursor") {
|
|
97
|
-
await connectCursor({ apiKey: opts.apiKey, mcpUrl: opts.mcpUrl, verbose: opts.verbose });
|
|
98
|
-
return undefined;
|
|
99
|
-
}
|
|
100
|
-
// Explicit: a new harness reaching an untested branch must fail, not write Cursor's config.
|
|
101
|
-
throw new Error(`No installer is wired for ${harness}`);
|
|
77
|
+
});
|
|
102
78
|
}
|
|
103
79
|
/** Undefined means Ctrl+C: the caller stops the whole run. */
|
|
104
80
|
function runHoistedCliInstall(verbose) {
|
|
@@ -117,7 +93,7 @@ function runHoistedCliInstall(verbose) {
|
|
|
117
93
|
async function verifyHarness(harness, mcpUrl, keyed, apiKey) {
|
|
118
94
|
const health = await verifyMcpHealth(mcpUrl);
|
|
119
95
|
// The own-OAuth harnesses are auth-checkable only in key mode (key-authed config).
|
|
120
|
-
const capable =
|
|
96
|
+
const capable = HARNESS_CAPABILITIES[harness].canVerifyAuth || keyed;
|
|
121
97
|
if (!capable || !health.ok)
|
|
122
98
|
return health;
|
|
123
99
|
if (!apiKey)
|
|
@@ -186,9 +162,10 @@ async function processHarness(detection, opts, prompt, oauthBudget, hoistedCliIn
|
|
|
186
162
|
let headlessAuth = false;
|
|
187
163
|
let spentBrowserSlot = false;
|
|
188
164
|
if (!isTTY && !keyed) {
|
|
189
|
-
//
|
|
190
|
-
if (harness
|
|
165
|
+
// A CLI-login sign-in is interactive-only — unboundable headless.
|
|
166
|
+
if (HARNESS_CAPABILITIES[harness].signInViaCliLogin) {
|
|
191
167
|
return { ...base, outcome: "auth_pending", fixCommand };
|
|
168
|
+
}
|
|
192
169
|
if (BROWSER_SIGN_IN.has(harness)) {
|
|
193
170
|
// One browser sign-in per run; the rest wait for a re-run.
|
|
194
171
|
if (oauthBudget.spent)
|
|
@@ -320,7 +297,7 @@ function summaryLine(result, uninstall) {
|
|
|
320
297
|
`Fix: ${finishSetupCommand(result.harness)}`);
|
|
321
298
|
}
|
|
322
299
|
// A keyed install has no sign-in left, so the deferred-auth warning would contradict it.
|
|
323
|
-
const authDeferred =
|
|
300
|
+
const authDeferred = HARNESS_CAPABILITIES[result.harness].authDeferredAtInstall && !result.keyAuthed;
|
|
324
301
|
const verifyLabel = result.verifyDepth ? `verified: ${result.verifyDepth}` : "not verified";
|
|
325
302
|
if (authDeferred || result.verifyOk === false) {
|
|
326
303
|
const reason = authDeferred
|
|
@@ -330,10 +307,11 @@ function summaryLine(result, uninstall) {
|
|
|
330
307
|
}
|
|
331
308
|
return `✓ ${name} — installed, ${verifyLabel}`;
|
|
332
309
|
}
|
|
333
|
-
/**
|
|
334
|
-
// TODO(PF-3668): 4th cursor carve-out here; fold them into one harness capability table.
|
|
310
|
+
/** A config-writing connect needs no binary; every other harness spawns one. */
|
|
335
311
|
function isStaleConfig(detection) {
|
|
336
|
-
return detection.detected &&
|
|
312
|
+
return (detection.detected &&
|
|
313
|
+
!detection.binaryOnPath &&
|
|
314
|
+
!HARNESS_CAPABILITIES[detection.harness].cliWritesConfig);
|
|
337
315
|
}
|
|
338
316
|
/** A stale config dir is a detection signal with nothing behind it. */
|
|
339
317
|
function actionable(result) {
|
|
@@ -465,13 +443,18 @@ export async function runConnectAll(opts) {
|
|
|
465
443
|
const chosen = new Set(pick.selection);
|
|
466
444
|
deselected = new Set(offerable.filter((d) => !chosen.has(d.harness)).map((d) => d.harness));
|
|
467
445
|
}
|
|
468
|
-
// Mirrors processHarness's gate: headless, only keyless
|
|
446
|
+
// Mirrors processHarness's gate: headless, only keyless CLI-login harnesses skip.
|
|
469
447
|
const willAttempt = (harness) => !deselected.has(harness) &&
|
|
470
|
-
(isTTY ||
|
|
471
|
-
|
|
448
|
+
(isTTY ||
|
|
449
|
+
!HARNESS_CAPABILITIES[harness].signInViaCliLogin ||
|
|
450
|
+
(!!gateKey && KEY_AUTH_CAPABLE.has(harness)));
|
|
451
|
+
// Machine-global, so once per invocation; config-writing connects never install it.
|
|
472
452
|
const cliNeeded = !opts.dryRun &&
|
|
473
453
|
!opts.uninstall &&
|
|
474
|
-
detections.some((d) => d.detected &&
|
|
454
|
+
detections.some((d) => d.detected &&
|
|
455
|
+
d.binaryOnPath &&
|
|
456
|
+
!HARNESS_CAPABILITIES[d.harness].cliWritesConfig &&
|
|
457
|
+
willAttempt(d.harness));
|
|
475
458
|
let takeHoistedCli;
|
|
476
459
|
if (cliNeeded) {
|
|
477
460
|
errLine("First: the TinyFish CLI, shared by every harness.");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import spawn from "cross-spawn";
|
|
2
|
+
import type { Harness } from "./harness-detect.js";
|
|
2
3
|
import { type AgentClient, type SupportedCommand } from "./connect-runtime.js";
|
|
3
4
|
export declare const OPENCLAW_SKILL_INSTALL_ARGS: readonly string[];
|
|
4
5
|
export declare const DEFAULT_ONBOARDING_PROMPT: string;
|
|
@@ -54,6 +55,23 @@ export declare const HERMES: NativeMcpClient;
|
|
|
54
55
|
export declare const OPENCODE: NativeMcpClient;
|
|
55
56
|
/** Every native MCP client. Derive from this, so a new harness cannot be missed. */
|
|
56
57
|
export declare const NATIVE_MCP_CLIENTS: readonly NativeMcpClient[];
|
|
58
|
+
/** Native descriptor by harness id; Cursor and OpenClaw have none. */
|
|
59
|
+
export declare const NATIVE_BY_HARNESS: Map<"openclaw" | "grok" | "cursor" | "codex" | "hermes" | "opencode" | "claude-code", NativeMcpClient>;
|
|
57
60
|
export declare const OPENCLAW: SupportedCommand;
|
|
61
|
+
/** Traits with no native descriptor to live on (Cursor and OpenClaw included). */
|
|
62
|
+
export interface HarnessCapabilities {
|
|
63
|
+
/** Sign-in is the CLI's own interactive `tinyfish auth login`. */
|
|
64
|
+
signInViaCliLogin: boolean;
|
|
65
|
+
/** Stored-key auth verification works against this harness's config. */
|
|
66
|
+
canVerifyAuth: boolean;
|
|
67
|
+
/** The CLI holds or writes the key; no harness `mcp add` carries it. */
|
|
68
|
+
keyHeldByCli: boolean;
|
|
69
|
+
/** Connect writes the MCP config file; no harness binary is spawned. */
|
|
70
|
+
cliWritesConfig: boolean;
|
|
71
|
+
/** `mcp add` succeeds unauthenticated; OAuth lands at first tool use. */
|
|
72
|
+
authDeferredAtInstall: boolean;
|
|
73
|
+
}
|
|
74
|
+
/** Required booleans: a new harness or new flag fails to compile undecided. */
|
|
75
|
+
export declare const HARNESS_CAPABILITIES: Record<Harness, HarnessCapabilities>;
|
|
58
76
|
export declare function launchNativeMcpClient(client: NativeMcpClient): void;
|
|
59
77
|
export declare function launchOpenClawWalkthrough(): void;
|
|
@@ -267,6 +267,8 @@ export const NATIVE_MCP_CLIENTS = [
|
|
|
267
267
|
HERMES,
|
|
268
268
|
OPENCODE,
|
|
269
269
|
];
|
|
270
|
+
/** Native descriptor by harness id; Cursor and OpenClaw have none. */
|
|
271
|
+
export const NATIVE_BY_HARNESS = new Map(NATIVE_MCP_CLIENTS.map((client) => [client.connectClient, client]));
|
|
270
272
|
export const OPENCLAW = {
|
|
271
273
|
command: "openclaw",
|
|
272
274
|
displayName: "OpenClaw",
|
|
@@ -276,6 +278,59 @@ export const OPENCLAW = {
|
|
|
276
278
|
unavailableMessage: OPENCLAW_SKILL_UNAVAILABLE_MESSAGE,
|
|
277
279
|
},
|
|
278
280
|
};
|
|
281
|
+
/** Required booleans: a new harness or new flag fails to compile undecided. */
|
|
282
|
+
export const HARNESS_CAPABILITIES = {
|
|
283
|
+
"claude-code": {
|
|
284
|
+
signInViaCliLogin: false,
|
|
285
|
+
canVerifyAuth: false,
|
|
286
|
+
keyHeldByCli: false,
|
|
287
|
+
cliWritesConfig: false,
|
|
288
|
+
authDeferredAtInstall: false,
|
|
289
|
+
},
|
|
290
|
+
codex: {
|
|
291
|
+
signInViaCliLogin: false,
|
|
292
|
+
canVerifyAuth: false,
|
|
293
|
+
keyHeldByCli: false,
|
|
294
|
+
cliWritesConfig: false,
|
|
295
|
+
// TODO(PF-3580): connect now probes Codex sign-in, so this over-warns a signed-in install.
|
|
296
|
+
authDeferredAtInstall: true,
|
|
297
|
+
},
|
|
298
|
+
cursor: {
|
|
299
|
+
signInViaCliLogin: false,
|
|
300
|
+
canVerifyAuth: true,
|
|
301
|
+
keyHeldByCli: true,
|
|
302
|
+
cliWritesConfig: true,
|
|
303
|
+
authDeferredAtInstall: false,
|
|
304
|
+
},
|
|
305
|
+
grok: {
|
|
306
|
+
signInViaCliLogin: false,
|
|
307
|
+
canVerifyAuth: false,
|
|
308
|
+
keyHeldByCli: false,
|
|
309
|
+
cliWritesConfig: false,
|
|
310
|
+
authDeferredAtInstall: false,
|
|
311
|
+
},
|
|
312
|
+
hermes: {
|
|
313
|
+
signInViaCliLogin: false,
|
|
314
|
+
canVerifyAuth: false,
|
|
315
|
+
keyHeldByCli: false,
|
|
316
|
+
cliWritesConfig: false,
|
|
317
|
+
authDeferredAtInstall: false,
|
|
318
|
+
},
|
|
319
|
+
openclaw: {
|
|
320
|
+
signInViaCliLogin: true,
|
|
321
|
+
canVerifyAuth: true,
|
|
322
|
+
keyHeldByCli: true,
|
|
323
|
+
cliWritesConfig: false,
|
|
324
|
+
authDeferredAtInstall: false,
|
|
325
|
+
},
|
|
326
|
+
opencode: {
|
|
327
|
+
signInViaCliLogin: false,
|
|
328
|
+
canVerifyAuth: false,
|
|
329
|
+
keyHeldByCli: false,
|
|
330
|
+
cliWritesConfig: false,
|
|
331
|
+
authDeferredAtInstall: false,
|
|
332
|
+
},
|
|
333
|
+
};
|
|
279
334
|
export function launchNativeMcpClient(client) {
|
|
280
335
|
errLine(`Starting the TinyFish walkthrough in ${client.displayName}...`);
|
|
281
336
|
if (client.launchWalkthrough) {
|
|
@@ -3,7 +3,7 @@ import * as path from "path";
|
|
|
3
3
|
import spawn from "cross-spawn";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { loadConfig, matchesCliKey } from "./auth.js";
|
|
6
|
-
import {
|
|
6
|
+
import { NATIVE_BY_HARNESS } from "./connect-clients.js";
|
|
7
7
|
import { errLine } from "./output.js";
|
|
8
8
|
import { readCursorTinyfishEntry } from "./cursor-config.js";
|
|
9
9
|
import { detectInstalledHarnesses, harnessConfigPath, harnessDisplayPath, AuthMode, Registered, } from "./harness-detect.js";
|
|
@@ -227,7 +227,6 @@ export const SIGN_IN_PROBES = {
|
|
|
227
227
|
};
|
|
228
228
|
// This reaches the pasted report, so its shape is checked first.
|
|
229
229
|
const ENV_VAR_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
230
|
-
const NATIVE_BY_HARNESS = new Map(NATIVE_MCP_CLIENTS.map((client) => [client.connectClient, client]));
|
|
231
230
|
/** Undefined unless the descriptor routes this harness's key through a variable. */
|
|
232
231
|
function declaredKeyEnvVar(harness) {
|
|
233
232
|
return NATIVE_BY_HARNESS.get(harness)?.keyAuth?.envVar;
|