@tiny-fish/cli 0.30.2-next.257 → 0.30.2-next.259
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.
- package/dist/commands/auth.d.ts +1 -1
- package/dist/commands/auth.js +52 -52
- package/dist/commands/batch.d.ts +1 -1
- package/dist/commands/batch.js +54 -46
- package/dist/commands/browser.d.ts +1 -1
- package/dist/commands/browser.js +14 -14
- package/dist/commands/config-claude.d.ts +1 -1
- package/dist/commands/config-claude.js +22 -22
- package/dist/commands/connect.d.ts +3 -3
- package/dist/commands/connect.js +150 -150
- package/dist/commands/doctor.d.ts +4 -4
- package/dist/commands/doctor.js +104 -104
- package/dist/commands/fetch.d.ts +1 -1
- package/dist/commands/fetch.js +24 -27
- package/dist/commands/onboard.d.ts +1 -1
- package/dist/commands/onboard.js +50 -50
- package/dist/commands/profile.d.ts +3 -3
- package/dist/commands/profile.js +45 -45
- package/dist/commands/run.d.ts +1 -1
- package/dist/commands/run.js +78 -65
- package/dist/commands/runs.d.ts +1 -1
- package/dist/commands/runs.js +63 -50
- package/dist/commands/search.d.ts +1 -1
- package/dist/commands/search.js +16 -16
- package/dist/commands/upgrade.d.ts +2 -2
- package/dist/commands/upgrade.js +36 -36
- package/dist/commands/vault.d.ts +1 -1
- package/dist/commands/vault.js +48 -45
- package/dist/commands/wallet.d.ts +1 -1
- package/dist/commands/wallet.js +14 -14
- package/dist/index.js +2 -2
- package/dist/lib/auth.d.ts +5 -5
- package/dist/lib/auth.js +56 -56
- package/dist/lib/batch-store.d.ts +1 -1
- package/dist/lib/batch-store.js +4 -4
- package/dist/lib/chrome-cookies.d.ts +1 -1
- package/dist/lib/chrome-cookies.js +24 -24
- package/dist/lib/claude-config.js +37 -37
- package/dist/lib/cli-install.js +12 -12
- package/dist/lib/client.d.ts +2 -2
- package/dist/lib/client.js +61 -61
- package/dist/lib/connect-all.js +140 -134
- package/dist/lib/connect-auth.d.ts +2 -2
- package/dist/lib/connect-auth.js +15 -15
- package/dist/lib/connect-clients.d.ts +6 -6
- package/dist/lib/connect-clients.js +143 -143
- package/dist/lib/connect-fallback.d.ts +1 -1
- package/dist/lib/connect-fallback.js +38 -38
- package/dist/lib/connect-picker.d.ts +4 -4
- package/dist/lib/connect-picker.js +28 -28
- package/dist/lib/connect-runtime.d.ts +7 -7
- package/dist/lib/connect-runtime.js +61 -61
- package/dist/lib/constants.js +13 -13
- package/dist/lib/cursor-config.d.ts +1 -1
- package/dist/lib/cursor-config.js +30 -30
- package/dist/lib/doctor-report.d.ts +2 -2
- package/dist/lib/doctor-report.js +22 -22
- package/dist/lib/harness-detect.js +39 -39
- package/dist/lib/harness.js +9 -9
- package/dist/lib/hermes-env.js +26 -23
- package/dist/lib/install-root.js +12 -12
- package/dist/lib/notice.d.ts +1 -1
- package/dist/lib/notice.js +12 -12
- package/dist/lib/output.js +14 -14
- package/dist/lib/registration-detect.d.ts +3 -3
- package/dist/lib/registration-detect.js +82 -82
- package/dist/lib/setup-telemetry.d.ts +4 -4
- package/dist/lib/setup-telemetry.js +34 -34
- package/dist/lib/signals.js +1 -1
- package/dist/lib/skill-install.d.ts +3 -3
- package/dist/lib/skill-install.js +40 -40
- package/dist/lib/types.d.ts +8 -8
- package/dist/lib/verify.d.ts +1 -1
- package/dist/lib/verify.js +28 -28
- package/dist/program.d.ts +1 -1
- package/dist/program.js +28 -28
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type AgentClient } from
|
|
1
|
+
import { type AgentClient } from './connect-runtime.js';
|
|
2
2
|
export declare const TINYFISH_CLI_NOT_FOUND_MESSAGE = "TinyFish CLI installed but is not available on PATH. Open a new terminal and retry.";
|
|
3
|
-
export declare function ensureCliAuthenticated(source: AgentClient |
|
|
3
|
+
export declare function ensureCliAuthenticated(source: AgentClient | 'cli', apiKey?: string, opts?: {
|
|
4
4
|
interactiveLogin?: boolean;
|
|
5
5
|
}): void;
|
package/dist/lib/connect-auth.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import spawn from
|
|
2
|
-
import { TINYFISH_API_KEY_VAR } from
|
|
3
|
-
import { persistApiKeyToEnvironment, validateKeyFormat, writeConfig } from
|
|
4
|
-
import { commandNotFound, ConnectStepError, spawnStepError, throwIfInterrupted, NON_INTERACTIVE_TIMEOUT_MS, } from
|
|
5
|
-
import { errLine } from
|
|
6
|
-
export const TINYFISH_CLI_NOT_FOUND_MESSAGE =
|
|
1
|
+
import spawn from 'cross-spawn';
|
|
2
|
+
import { TINYFISH_API_KEY_VAR } from './constants.js';
|
|
3
|
+
import { persistApiKeyToEnvironment, validateKeyFormat, writeConfig } from './auth.js';
|
|
4
|
+
import { commandNotFound, ConnectStepError, spawnStepError, throwIfInterrupted, NON_INTERACTIVE_TIMEOUT_MS, } from './connect-runtime.js';
|
|
5
|
+
import { errLine } from './output.js';
|
|
6
|
+
export const TINYFISH_CLI_NOT_FOUND_MESSAGE = 'TinyFish CLI installed but is not available on PATH. Open a new terminal and retry.';
|
|
7
7
|
export function ensureCliAuthenticated(source, apiKey, opts) {
|
|
8
8
|
const envKey = apiKey ?? process.env[TINYFISH_API_KEY_VAR];
|
|
9
9
|
if (envKey) {
|
|
10
10
|
if (!validateKeyFormat(envKey)) {
|
|
11
|
-
throw new ConnectStepError(
|
|
11
|
+
throw new ConnectStepError('TINYFISH_API_KEY has invalid format', 'invalid_config');
|
|
12
12
|
}
|
|
13
13
|
// Throwing variant: a write failure must reach connect's catch/flush, not exit(1) past it.
|
|
14
14
|
writeConfig(envKey);
|
|
15
15
|
persistApiKeyToEnvironment(envKey);
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
|
-
const authStatus = spawn.sync(
|
|
19
|
-
stdio:
|
|
18
|
+
const authStatus = spawn.sync('tinyfish', ['auth', 'status'], {
|
|
19
|
+
stdio: 'ignore',
|
|
20
20
|
timeout: NON_INTERACTIVE_TIMEOUT_MS,
|
|
21
21
|
});
|
|
22
22
|
if (commandNotFound(authStatus.error)) {
|
|
@@ -28,19 +28,19 @@ export function ensureCliAuthenticated(source, apiKey, opts) {
|
|
|
28
28
|
// "not signed in" and drop the user into an interactive login they never asked for.
|
|
29
29
|
throwIfInterrupted(authStatus);
|
|
30
30
|
if (authStatus.error) {
|
|
31
|
-
throw spawnStepError(
|
|
31
|
+
throw spawnStepError('Could not read TinyFish CLI auth status', authStatus);
|
|
32
32
|
}
|
|
33
33
|
if (opts?.interactiveLogin === false) {
|
|
34
|
-
throw new Error(
|
|
34
|
+
throw new Error('The TinyFish CLI is not signed in. Run: tinyfish auth login');
|
|
35
35
|
}
|
|
36
|
-
errLine(
|
|
37
|
-
const loginResult = spawn.sync(
|
|
38
|
-
stdio:
|
|
36
|
+
errLine('Signing in to the TinyFish CLI...');
|
|
37
|
+
const loginResult = spawn.sync('tinyfish', ['auth', 'login', '--source', source], {
|
|
38
|
+
stdio: 'inherit',
|
|
39
39
|
});
|
|
40
40
|
if (commandNotFound(loginResult.error)) {
|
|
41
41
|
throw spawnStepError(TINYFISH_CLI_NOT_FOUND_MESSAGE, loginResult);
|
|
42
42
|
}
|
|
43
43
|
if (loginResult.error || loginResult.status !== 0) {
|
|
44
|
-
throw spawnStepError(
|
|
44
|
+
throw spawnStepError('Could not authenticate the TinyFish CLI', loginResult);
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import spawn from
|
|
2
|
-
import { type Harness } from
|
|
3
|
-
import { type AgentClient, type SupportedCommand } from
|
|
1
|
+
import spawn from 'cross-spawn';
|
|
2
|
+
import { type Harness } from './harness-detect.js';
|
|
3
|
+
import { type AgentClient, type SupportedCommand } from './connect-runtime.js';
|
|
4
4
|
export declare const OPENCLAW_SKILL_INSTALL_ARGS: readonly string[];
|
|
5
5
|
/** `openclaw skills` has no uninstall; ClawHub removes from the global dir. */
|
|
6
6
|
export declare function openclawSkillUninstall(): {
|
|
@@ -12,14 +12,14 @@ export declare function openclawSkillUninstall(): {
|
|
|
12
12
|
export declare const DEFAULT_ONBOARDING_PROMPT: string;
|
|
13
13
|
export declare const OPENCLAW_ONBOARDING_PROMPT: string;
|
|
14
14
|
/** The `skills` CLI's own agent names — not ours, and not every harness has one. */
|
|
15
|
-
export type SkillAgent =
|
|
15
|
+
export type SkillAgent = 'claude-code' | 'codex' | 'cursor' | 'hermes-agent' | 'opencode';
|
|
16
16
|
/** Cursor writes its own MCP config, so it has no descriptor to carry this. */
|
|
17
17
|
export declare const CURSOR_SKILL_TARGET: {
|
|
18
18
|
readonly skillAgent: "cursor";
|
|
19
19
|
readonly displayName: "Cursor";
|
|
20
20
|
};
|
|
21
21
|
export interface NativeMcpClient extends SupportedCommand {
|
|
22
|
-
connectClient: Exclude<AgentClient,
|
|
22
|
+
connectClient: Exclude<AgentClient, 'openclaw'>;
|
|
23
23
|
/** Omitted where the `skills` CLI has no agent for this harness (Grok ships its own plugin). */
|
|
24
24
|
skillAgent?: SkillAgent;
|
|
25
25
|
/** Capturable: this `mcp add` never runs OAuth inline; unset stays inherited. */
|
|
@@ -84,7 +84,7 @@ export interface HarnessCapabilities {
|
|
|
84
84
|
/** Required booleans: a new harness or new flag fails to compile undecided. */
|
|
85
85
|
export declare const HARNESS_CAPABILITIES: Record<Harness, HarnessCapabilities>;
|
|
86
86
|
/** "printed" = handed to the user to paste; the walkthrough was never started for them. */
|
|
87
|
-
export type WalkthroughOutcome =
|
|
87
|
+
export type WalkthroughOutcome = 'launched' | 'printed';
|
|
88
88
|
/** Awaited before the handover; track+flush must land pre-block. */
|
|
89
89
|
type OnWalkthroughDecided = (outcome: WalkthroughOutcome) => void | Promise<void>;
|
|
90
90
|
export declare function launchNativeMcpClient(client: NativeMcpClient, onDecided?: OnWalkthroughDecided): Promise<WalkthroughOutcome>;
|
|
@@ -1,136 +1,136 @@
|
|
|
1
|
-
import spawn from
|
|
2
|
-
import { SKILL_INSTALL_TIMEOUT_MS } from
|
|
3
|
-
import { detectHumanInitiated } from
|
|
4
|
-
import { harnessConfigPath } from
|
|
5
|
-
import { errLine, sanitizeLine } from
|
|
6
|
-
import { spawnStepError
|
|
7
|
-
import { TINYFISH_API_KEY_VAR } from
|
|
1
|
+
import spawn from 'cross-spawn';
|
|
2
|
+
import { SKILL_INSTALL_TIMEOUT_MS } from './cli-install.js';
|
|
3
|
+
import { detectHumanInitiated } from './harness.js';
|
|
4
|
+
import { harnessConfigPath } from './harness-detect.js';
|
|
5
|
+
import { errLine, sanitizeLine } from './output.js';
|
|
6
|
+
import { spawnStepError } from './connect-runtime.js';
|
|
7
|
+
import { TINYFISH_API_KEY_VAR } from './constants.js';
|
|
8
8
|
const HERMES_SEED_TIMEOUT_MS = 120_000;
|
|
9
9
|
// An old install is only one reason a flag can go unseen, so no message asserts the cause.
|
|
10
|
-
const SUPPORT_CHECK_DEBUG_HINT =
|
|
11
|
-
const MCP_ADD_UNAVAILABLE_MESSAGE =
|
|
12
|
-
|
|
10
|
+
const SUPPORT_CHECK_DEBUG_HINT = ' Set TINYFISH_DEBUG=1 and retry to print the help output TinyFish read.';
|
|
11
|
+
const MCP_ADD_UNAVAILABLE_MESSAGE = 'Could not confirm this Claude Code installation exposes `claude mcp add`: `claude mcp ' +
|
|
12
|
+
'--help` did not list it. Update Claude Code with `claude update` and retry.' +
|
|
13
13
|
SUPPORT_CHECK_DEBUG_HINT;
|
|
14
14
|
// `claude mcp login` only exists upstream from Claude Code 2.1.186; older installs still register.
|
|
15
|
-
const CLAUDE_CODE_KEYED_FALLBACK_NOTE =
|
|
16
|
-
|
|
17
|
-
const CLAUDE_CODE_DEFERRED_AUTH_NOTE =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const CODEX_OAUTH_RESOURCE_UNAVAILABLE_MESSAGE =
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
const CLAUDE_CODE_KEYED_FALLBACK_NOTE = 'Using your stored TinyFish API key: this Claude Code predates one-command sign-in ' +
|
|
16
|
+
'(`claude mcp login`), so no browser sign-in is needed.';
|
|
17
|
+
const CLAUDE_CODE_DEFERRED_AUTH_NOTE = 'TinyFish is registered in Claude Code but not signed in: this Claude Code predates ' +
|
|
18
|
+
'one-command sign-in (`claude mcp login`). Open Claude Code and run `/mcp` to sign in to ' +
|
|
19
|
+
'TinyFish. `claude update` gets you one-command sign-in. Already have a TinyFish API key? ' +
|
|
20
|
+
'Run `tinyfish connect claude-code --api-key <key>` instead.';
|
|
21
|
+
const CODEX_OAUTH_RESOURCE_UNAVAILABLE_MESSAGE = 'Could not confirm this Codex installation supports one-command MCP authentication: `codex ' +
|
|
22
|
+
'mcp add --help` did not list `--oauth-resource`. Update Codex and retry, or add TinyFish ' +
|
|
23
|
+
'manually with `codex mcp add`.' +
|
|
24
24
|
SUPPORT_CHECK_DEBUG_HINT;
|
|
25
|
-
const HERMES_OAUTH_UNAVAILABLE_MESSAGE =
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
const HERMES_OAUTH_UNAVAILABLE_MESSAGE = 'Could not confirm this Hermes installation supports one-command MCP authentication: `hermes ' +
|
|
26
|
+
'mcp add --help` did not list `--auth oauth`. Update Hermes and retry, or add TinyFish ' +
|
|
27
|
+
'manually with `hermes mcp add`.' +
|
|
28
28
|
SUPPORT_CHECK_DEBUG_HINT;
|
|
29
|
-
const GROK_HTTP_UNAVAILABLE_MESSAGE =
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
const GROK_HTTP_UNAVAILABLE_MESSAGE = 'Could not confirm this Grok installation supports remote MCP servers: `grok mcp add --help` ' +
|
|
30
|
+
'did not list `--transport`. Update Grok and retry, or add TinyFish manually with ' +
|
|
31
|
+
'`grok mcp add`.' +
|
|
32
32
|
SUPPORT_CHECK_DEBUG_HINT;
|
|
33
33
|
// Grok has no `mcp login`; sign-in only happens inside the TUI. No key nudge here: a revoked
|
|
34
34
|
// key leaves Grok with no OAuth fallback, and doctor cannot spot that yet (PF-3618).
|
|
35
|
-
const GROK_SIGN_IN_HINT =
|
|
36
|
-
|
|
37
|
-
const OPENCLAW_SKILL_UNAVAILABLE_MESSAGE =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
const GROK_SIGN_IN_HINT = 'TinyFish is registered in Grok. Open Grok, run `/mcps`, select tinyfish, and press `i` to ' +
|
|
36
|
+
'sign in.';
|
|
37
|
+
const OPENCLAW_SKILL_UNAVAILABLE_MESSAGE = 'Could not confirm this OpenClaw installation supports global skill installation: `openclaw ' +
|
|
38
|
+
'skills install --help` did not list `--global` and `--acknowledge-clawhub-risk`. Update ' +
|
|
39
|
+
'OpenClaw and retry, or install manually with `openclaw skills install @tinyfish/tinyfish ' +
|
|
40
|
+
'--global --acknowledge-clawhub-risk`.' +
|
|
41
41
|
SUPPORT_CHECK_DEBUG_HINT;
|
|
42
|
-
const OPENCODE_MCP_ADD_UNAVAILABLE_MESSAGE =
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
const OPENCODE_MCP_ADD_UNAVAILABLE_MESSAGE = 'Could not confirm this OpenCode installation supports one-command MCP setup: `opencode mcp ' +
|
|
43
|
+
'add --help` did not list `--url`. Update OpenCode and retry, or add TinyFish manually with ' +
|
|
44
|
+
'`opencode mcp add`.' +
|
|
45
45
|
SUPPORT_CHECK_DEBUG_HINT;
|
|
46
46
|
// OpenCode is model-agnostic (unlike Claude Code / Codex), so a fresh install can default to a
|
|
47
47
|
// model with no tool support — TinyFish runs entirely on tool calls, so it would fail there.
|
|
48
|
-
const OPENCODE_MODEL_NOTE =
|
|
48
|
+
const OPENCODE_MODEL_NOTE = 'Note: TinyFish runs on tool calls, so OpenCode needs a model that supports tool use. Image-only ' +
|
|
49
49
|
'models (e.g. Nano Banana Pro) will show "No endpoints found that support tool use" — switch ' +
|
|
50
50
|
"OpenCode's model if the walkthrough can't start.";
|
|
51
|
-
const OPENCLAW_SKILL =
|
|
51
|
+
const OPENCLAW_SKILL = '@tinyfish/tinyfish';
|
|
52
52
|
// --force makes this an unconditional overwrite, so the same call installs and refreshes.
|
|
53
53
|
export const OPENCLAW_SKILL_INSTALL_ARGS = [
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
'skills',
|
|
55
|
+
'install',
|
|
56
56
|
OPENCLAW_SKILL,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
'--global',
|
|
58
|
+
'--force',
|
|
59
|
+
'--acknowledge-clawhub-risk',
|
|
60
60
|
];
|
|
61
61
|
/** `openclaw skills` has no uninstall; ClawHub removes from the global dir. */
|
|
62
62
|
export function openclawSkillUninstall() {
|
|
63
63
|
return {
|
|
64
|
-
command:
|
|
64
|
+
command: 'npx',
|
|
65
65
|
args: [
|
|
66
|
-
|
|
66
|
+
'-y',
|
|
67
67
|
// Pinned: npx would otherwise execute whatever is newest at run time.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
harnessConfigPath(
|
|
71
|
-
|
|
68
|
+
'clawhub@0.23.3',
|
|
69
|
+
'--workdir',
|
|
70
|
+
harnessConfigPath('openclaw'),
|
|
71
|
+
'uninstall',
|
|
72
72
|
OPENCLAW_SKILL,
|
|
73
|
-
|
|
73
|
+
'--yes',
|
|
74
74
|
],
|
|
75
75
|
timeoutMs: SKILL_INSTALL_TIMEOUT_MS,
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
|
-
export const DEFAULT_ONBOARDING_PROMPT =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
export const OPENCLAW_ONBOARDING_PROMPT =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
78
|
+
export const DEFAULT_ONBOARDING_PROMPT = 'I just connected TinyFish. Use the MCP server named `tinyfish` for every TinyFish tool call; ' +
|
|
79
|
+
'if another TinyFish server is also registered, still use `tinyfish` and do not ask me which. ' +
|
|
80
|
+
'Start by asking me what I want to look up on the web, and wait for my reply. Then call ' +
|
|
81
|
+
'`guide_next_step` and follow its instructions one step at a time, asking for my input and ' +
|
|
82
|
+
'waiting for my reply before each subsequent TinyFish tool call. If a tool call comes back ' +
|
|
83
|
+
'blocked or not approved, tell me which tool needs approval and how to grant it, then wait for ' +
|
|
84
|
+
'me instead of ending the walkthrough.';
|
|
85
|
+
export const OPENCLAW_ONBOARDING_PROMPT = 'I just installed the TinyFish skill. Guide me through it interactively: first ask what topic I ' +
|
|
86
|
+
'want to search and wait for my reply before using TinyFish Search. Show me the results, ask ' +
|
|
87
|
+
'which result I want to read, and wait before using TinyFish Fetch. Then ask what browser task ' +
|
|
88
|
+
'I want to complete and wait before using TinyFish Agent. Never skip ahead or choose for me.';
|
|
89
89
|
/** Cursor writes its own MCP config, so it has no descriptor to carry this. */
|
|
90
|
-
export const CURSOR_SKILL_TARGET = { skillAgent:
|
|
90
|
+
export const CURSOR_SKILL_TARGET = { skillAgent: 'cursor', displayName: 'Cursor' };
|
|
91
91
|
const HEADER_FLAG = /(?:^|\s)--header(?:[\s<=]|$)/m;
|
|
92
92
|
const claudeAddArgs = (mcpUrl) => [
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
'mcp',
|
|
94
|
+
'add',
|
|
95
|
+
'--scope',
|
|
96
|
+
'user',
|
|
97
|
+
'--transport',
|
|
98
|
+
'http',
|
|
99
|
+
'tinyfish',
|
|
100
100
|
mcpUrl,
|
|
101
101
|
];
|
|
102
|
-
const urlAddArgs = (mcpUrl) => [
|
|
102
|
+
const urlAddArgs = (mcpUrl) => ['mcp', 'add', 'tinyfish', '--url', mcpUrl];
|
|
103
103
|
// OpenCode takes `--header NAME=VALUE`, not Claude Code's `NAME: VALUE`.
|
|
104
104
|
const headerKeyAuth = {
|
|
105
|
-
addArgs: (mcpUrl, apiKey) => [...urlAddArgs(mcpUrl),
|
|
105
|
+
addArgs: (mcpUrl, apiKey) => [...urlAddArgs(mcpUrl), '--header', `X-API-Key=${apiKey}`],
|
|
106
106
|
};
|
|
107
107
|
export const CLAUDE_CODE = {
|
|
108
|
-
command:
|
|
109
|
-
connectClient:
|
|
110
|
-
skillAgent:
|
|
108
|
+
command: 'claude',
|
|
109
|
+
connectClient: 'claude-code',
|
|
110
|
+
skillAgent: 'claude-code',
|
|
111
111
|
nonInteractiveAdd: true,
|
|
112
|
-
displayName:
|
|
112
|
+
displayName: 'Claude Code',
|
|
113
113
|
supportCheck: {
|
|
114
|
-
args: [
|
|
114
|
+
args: ['mcp', '--help'],
|
|
115
115
|
patterns: [/^\s*add(?:\s|\[)/m],
|
|
116
116
|
optionalPatterns: [/^\s*login(?:\s|\[)/m],
|
|
117
117
|
keyAuthPattern: HEADER_FLAG,
|
|
118
118
|
unavailableMessage: MCP_ADD_UNAVAILABLE_MESSAGE,
|
|
119
119
|
},
|
|
120
120
|
keyAuth: {
|
|
121
|
-
addArgs: (mcpUrl, apiKey) => [...claudeAddArgs(mcpUrl),
|
|
121
|
+
addArgs: (mcpUrl, apiKey) => [...claudeAddArgs(mcpUrl), '--header', `X-API-Key: ${apiKey}`],
|
|
122
122
|
},
|
|
123
|
-
loginArgs: [
|
|
123
|
+
loginArgs: ['mcp', 'login', 'tinyfish'],
|
|
124
124
|
degradedAuthNotes: {
|
|
125
125
|
keyed: CLAUDE_CODE_KEYED_FALLBACK_NOTE,
|
|
126
126
|
deferred: CLAUDE_CODE_DEFERRED_AUTH_NOTE,
|
|
127
127
|
},
|
|
128
128
|
// Project scope is shared in .mcp.json; a user setup command must not rewrite it.
|
|
129
129
|
removals: [
|
|
130
|
-
{ args: [
|
|
130
|
+
{ args: ['mcp', 'remove', 'tinyfish', '--scope', 'user'], label: 'user TinyFish registration' },
|
|
131
131
|
{
|
|
132
|
-
args: [
|
|
133
|
-
label:
|
|
132
|
+
args: ['mcp', 'remove', 'tinyfish', '--scope', 'local'],
|
|
133
|
+
label: 'local TinyFish registration',
|
|
134
134
|
},
|
|
135
135
|
],
|
|
136
136
|
addArgs: (mcpUrl) => claudeAddArgs(mcpUrl),
|
|
@@ -141,36 +141,36 @@ export const CLAUDE_CODE = {
|
|
|
141
141
|
* quotes back out. Untested on Windows.
|
|
142
142
|
*/
|
|
143
143
|
export function openExternalUrl(url) {
|
|
144
|
-
if (process.platform ===
|
|
145
|
-
return spawn.sync(
|
|
146
|
-
if (process.platform ===
|
|
147
|
-
return spawn.sync(
|
|
148
|
-
stdio:
|
|
144
|
+
if (process.platform === 'darwin')
|
|
145
|
+
return spawn.sync('open', [url], { stdio: 'ignore' });
|
|
146
|
+
if (process.platform === 'win32') {
|
|
147
|
+
return spawn.sync('cmd', ['/c', 'start', '""', `"${url}"`], {
|
|
148
|
+
stdio: 'ignore',
|
|
149
149
|
windowsVerbatimArguments: true,
|
|
150
150
|
});
|
|
151
151
|
}
|
|
152
|
-
return spawn.sync(
|
|
152
|
+
return spawn.sync('xdg-open', [url], { stdio: 'ignore' });
|
|
153
153
|
}
|
|
154
154
|
function launchCodexWalkthrough() {
|
|
155
|
-
const deepLink = new URL(
|
|
156
|
-
deepLink.searchParams.set(
|
|
157
|
-
deepLink.searchParams.set(
|
|
155
|
+
const deepLink = new URL('codex://new');
|
|
156
|
+
deepLink.searchParams.set('prompt', DEFAULT_ONBOARDING_PROMPT);
|
|
157
|
+
deepLink.searchParams.set('path', process.cwd());
|
|
158
158
|
const result = openExternalUrl(deepLink.toString());
|
|
159
159
|
if (result.error || result.status !== 0) {
|
|
160
160
|
// Headless box: nothing can open a window; recover like the gated clients.
|
|
161
|
-
printPromptForHandoff(
|
|
162
|
-
return
|
|
161
|
+
printPromptForHandoff('Codex', 'codex', DEFAULT_ONBOARDING_PROMPT, 'Codex could not be opened here (no `codex://` handler)');
|
|
162
|
+
return 'printed';
|
|
163
163
|
}
|
|
164
|
-
errLine(
|
|
165
|
-
return
|
|
164
|
+
errLine('Codex opened with the TinyFish walkthrough ready. Send the prompt to start.');
|
|
165
|
+
return 'launched';
|
|
166
166
|
}
|
|
167
167
|
export const CODEX = {
|
|
168
|
-
command:
|
|
169
|
-
connectClient:
|
|
170
|
-
skillAgent:
|
|
171
|
-
displayName:
|
|
168
|
+
command: 'codex',
|
|
169
|
+
connectClient: 'codex',
|
|
170
|
+
skillAgent: 'codex',
|
|
171
|
+
displayName: 'Codex',
|
|
172
172
|
supportCheck: {
|
|
173
|
-
args: [
|
|
173
|
+
args: ['mcp', 'add', '--help'],
|
|
174
174
|
patterns: [/--oauth-resource(?:\s|<)/],
|
|
175
175
|
keyAuthPattern: /(?:^|\s)--bearer-token-env-var(?:[\s<=]|$)/m,
|
|
176
176
|
unavailableMessage: CODEX_OAUTH_RESOURCE_UNAVAILABLE_MESSAGE,
|
|
@@ -179,29 +179,29 @@ export const CODEX = {
|
|
|
179
179
|
// Omitting `--oauth-resource` is what keeps `mcp add` from running OAuth.
|
|
180
180
|
keyAuth: {
|
|
181
181
|
envVar: TINYFISH_API_KEY_VAR,
|
|
182
|
-
addArgs: (mcpUrl) => [...urlAddArgs(mcpUrl),
|
|
182
|
+
addArgs: (mcpUrl) => [...urlAddArgs(mcpUrl), '--bearer-token-env-var', TINYFISH_API_KEY_VAR],
|
|
183
183
|
},
|
|
184
184
|
detachedLaunch: launchCodexWalkthrough,
|
|
185
185
|
// Retry only: running it after a finished inline OAuth is a second browser hop.
|
|
186
|
-
loginArgs: [
|
|
187
|
-
removals: [{ args: [
|
|
188
|
-
addArgs: (mcpUrl, oauthResource) => [...urlAddArgs(mcpUrl),
|
|
186
|
+
loginArgs: ['mcp', 'login', 'tinyfish'],
|
|
187
|
+
removals: [{ args: ['mcp', 'remove', 'tinyfish'], label: 'TinyFish registration from Codex' }],
|
|
188
|
+
addArgs: (mcpUrl, oauthResource) => [...urlAddArgs(mcpUrl), '--oauth-resource', oauthResource],
|
|
189
189
|
};
|
|
190
190
|
const grokAddArgs = (mcpUrl) => [
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
191
|
+
'mcp',
|
|
192
|
+
'add',
|
|
193
|
+
'--transport',
|
|
194
|
+
'http',
|
|
195
|
+
'tinyfish',
|
|
196
196
|
mcpUrl,
|
|
197
197
|
];
|
|
198
198
|
export const GROK = {
|
|
199
|
-
command:
|
|
200
|
-
connectClient:
|
|
199
|
+
command: 'grok',
|
|
200
|
+
connectClient: 'grok',
|
|
201
201
|
nonInteractiveAdd: true,
|
|
202
|
-
displayName:
|
|
202
|
+
displayName: 'Grok',
|
|
203
203
|
supportCheck: {
|
|
204
|
-
args: [
|
|
204
|
+
args: ['mcp', 'add', '--help'],
|
|
205
205
|
patterns: [/--transport(?:[\s<=]|$)/m],
|
|
206
206
|
keyAuthPattern: HEADER_FLAG,
|
|
207
207
|
unavailableMessage: GROK_HTTP_UNAVAILABLE_MESSAGE,
|
|
@@ -212,67 +212,67 @@ export const GROK = {
|
|
|
212
212
|
envVar: TINYFISH_API_KEY_VAR,
|
|
213
213
|
addArgs: (mcpUrl) => [
|
|
214
214
|
...grokAddArgs(mcpUrl),
|
|
215
|
-
|
|
215
|
+
'--header',
|
|
216
216
|
`Authorization: Bearer \${${TINYFISH_API_KEY_VAR}}`,
|
|
217
217
|
],
|
|
218
218
|
},
|
|
219
219
|
signInHint: GROK_SIGN_IN_HINT,
|
|
220
|
-
removals: [{ args: [
|
|
220
|
+
removals: [{ args: ['mcp', 'remove', 'tinyfish'], label: 'TinyFish registration from Grok' }],
|
|
221
221
|
addArgs: grokAddArgs,
|
|
222
222
|
};
|
|
223
223
|
// Handover: spawn errors are ours; the exit is the user's.
|
|
224
224
|
function handOverTerminal(command, args, displayName) {
|
|
225
|
-
const result = spawn.sync(command, args, { stdio:
|
|
225
|
+
const result = spawn.sync(command, args, { stdio: 'inherit' });
|
|
226
226
|
if (result.error) {
|
|
227
227
|
throw spawnStepError(`Could not launch ${displayName}`, result);
|
|
228
228
|
}
|
|
229
229
|
}
|
|
230
230
|
function launchHermesWalkthrough() {
|
|
231
|
-
const seedResult = spawn.sync(
|
|
232
|
-
encoding:
|
|
231
|
+
const seedResult = spawn.sync('hermes', ['chat', '-Q', '-q', DEFAULT_ONBOARDING_PROMPT], {
|
|
232
|
+
encoding: 'utf8',
|
|
233
233
|
timeout: HERMES_SEED_TIMEOUT_MS,
|
|
234
234
|
});
|
|
235
235
|
if (seedResult.error || seedResult.status !== 0) {
|
|
236
|
-
throw spawnStepError(
|
|
236
|
+
throw spawnStepError('Could not start the TinyFish walkthrough in Hermes', seedResult);
|
|
237
237
|
}
|
|
238
238
|
// A colourised id would carry its trailing escape sequence into `--resume` (PF-3452).
|
|
239
|
-
const sessionId = sanitizeLine(seedResult.stderr ??
|
|
239
|
+
const sessionId = sanitizeLine(seedResult.stderr ?? '').match(/session_id:\s*([^\s]+)/i)?.[1];
|
|
240
240
|
if (!sessionId) {
|
|
241
|
-
throw new Error(
|
|
241
|
+
throw new Error('Hermes did not return a walkthrough session ID');
|
|
242
242
|
}
|
|
243
|
-
handOverTerminal(
|
|
243
|
+
handOverTerminal('hermes', ['--resume', sessionId], 'Hermes');
|
|
244
244
|
}
|
|
245
245
|
export const HERMES = {
|
|
246
|
-
command:
|
|
247
|
-
connectClient:
|
|
248
|
-
skillAgent:
|
|
249
|
-
displayName:
|
|
246
|
+
command: 'hermes',
|
|
247
|
+
connectClient: 'hermes',
|
|
248
|
+
skillAgent: 'hermes-agent',
|
|
249
|
+
displayName: 'Hermes',
|
|
250
250
|
supportCheck: {
|
|
251
|
-
args: [
|
|
251
|
+
args: ['mcp', 'add', '--help'],
|
|
252
252
|
patterns: [/--auth\s+\{[^}]*oauth[^}]*\}/],
|
|
253
253
|
unavailableMessage: HERMES_OAUTH_UNAVAILABLE_MESSAGE,
|
|
254
254
|
},
|
|
255
255
|
// No `keyAuth`: `hermes mcp add` has no --header, and `--auth header` prompts interactively.
|
|
256
|
-
removals: [{ args: [
|
|
256
|
+
removals: [{ args: ['mcp', 'remove', 'tinyfish'], label: 'TinyFish registration from Hermes' }],
|
|
257
257
|
// Hermes completes OAuth while adding the server; a separate `mcp login` would authenticate twice.
|
|
258
258
|
oauthInAdd: true,
|
|
259
|
-
addArgs: (mcpUrl) => [...urlAddArgs(mcpUrl),
|
|
259
|
+
addArgs: (mcpUrl) => [...urlAddArgs(mcpUrl), '--auth', 'oauth'],
|
|
260
260
|
launchWalkthrough: launchHermesWalkthrough,
|
|
261
261
|
};
|
|
262
262
|
// OpenCode's TUI takes a positional as a project directory, so a bare `opencode "<prompt>"`
|
|
263
263
|
// would be read as a folder. The `--prompt` flag seeds the interactive TUI with a first message
|
|
264
264
|
// (opencode.ai/docs/cli), so the onboarding guide fires just like the other agents.
|
|
265
265
|
function launchOpencode() {
|
|
266
|
-
handOverTerminal(
|
|
266
|
+
handOverTerminal('opencode', ['--prompt', DEFAULT_ONBOARDING_PROMPT], 'OpenCode');
|
|
267
267
|
}
|
|
268
268
|
export const OPENCODE = {
|
|
269
|
-
command:
|
|
270
|
-
connectClient:
|
|
271
|
-
skillAgent:
|
|
269
|
+
command: 'opencode',
|
|
270
|
+
connectClient: 'opencode',
|
|
271
|
+
skillAgent: 'opencode',
|
|
272
272
|
nonInteractiveAdd: true,
|
|
273
|
-
displayName:
|
|
273
|
+
displayName: 'OpenCode',
|
|
274
274
|
supportCheck: {
|
|
275
|
-
args: [
|
|
275
|
+
args: ['mcp', 'add', '--help'],
|
|
276
276
|
patterns: [/--url(?:\s|$)/m],
|
|
277
277
|
keyAuthPattern: HEADER_FLAG,
|
|
278
278
|
unavailableMessage: OPENCODE_MCP_ADD_UNAVAILABLE_MESSAGE,
|
|
@@ -282,7 +282,7 @@ export const OPENCODE = {
|
|
|
282
282
|
removals: [],
|
|
283
283
|
// OpenCode writes ~/.config/opencode/opencode.json itself; `mcp auth` is the separate OAuth step.
|
|
284
284
|
addArgs: (mcpUrl) => urlAddArgs(mcpUrl),
|
|
285
|
-
loginArgs: [
|
|
285
|
+
loginArgs: ['mcp', 'auth', 'tinyfish'],
|
|
286
286
|
launchWalkthrough: launchOpencode,
|
|
287
287
|
postConnectNote: OPENCODE_MODEL_NOTE,
|
|
288
288
|
};
|
|
@@ -297,17 +297,17 @@ export const NATIVE_MCP_CLIENTS = [
|
|
|
297
297
|
/** Native descriptor by harness id; Cursor and OpenClaw have none. */
|
|
298
298
|
export const NATIVE_BY_HARNESS = new Map(NATIVE_MCP_CLIENTS.map((client) => [client.connectClient, client]));
|
|
299
299
|
export const OPENCLAW = {
|
|
300
|
-
command:
|
|
301
|
-
displayName:
|
|
300
|
+
command: 'openclaw',
|
|
301
|
+
displayName: 'OpenClaw',
|
|
302
302
|
supportCheck: {
|
|
303
|
-
args: [
|
|
303
|
+
args: ['skills', 'install', '--help'],
|
|
304
304
|
patterns: [/--global(?:\s|$)/, /--acknowledge-clawhub-risk(?:\s|$)/],
|
|
305
305
|
unavailableMessage: OPENCLAW_SKILL_UNAVAILABLE_MESSAGE,
|
|
306
306
|
},
|
|
307
307
|
};
|
|
308
308
|
/** Required booleans: a new harness or new flag fails to compile undecided. */
|
|
309
309
|
export const HARNESS_CAPABILITIES = {
|
|
310
|
-
|
|
310
|
+
'claude-code': {
|
|
311
311
|
signInViaCliLogin: false,
|
|
312
312
|
canVerifyAuth: false,
|
|
313
313
|
keyHeldByCli: false,
|
|
@@ -363,22 +363,22 @@ function printPromptForHandoff(displayName, command, prompt, reason) {
|
|
|
363
363
|
errLine(`TinyFish is set up in ${displayName}.`);
|
|
364
364
|
errLine(`${reason}, so TinyFish did not launch it. Start ${displayName} (\`${command}\`) yourself ` +
|
|
365
365
|
`and paste this first message:`);
|
|
366
|
-
errLine(
|
|
366
|
+
errLine('');
|
|
367
367
|
errLine(prompt);
|
|
368
368
|
}
|
|
369
369
|
/** One gate + one decide-before-block path for every terminal handover. */
|
|
370
370
|
async function deliverWalkthrough(displayName, command, prompt, handover, onDecided) {
|
|
371
371
|
// Inherited-stdio handover: a non-TTY parent cannot approve tool calls.
|
|
372
372
|
if (!detectHumanInitiated()) {
|
|
373
|
-
await onDecided?.(
|
|
373
|
+
await onDecided?.('printed');
|
|
374
374
|
printPromptForHandoff(displayName, command, prompt, `This terminal cannot answer ${displayName}'s tool-approval prompts`);
|
|
375
|
-
return
|
|
375
|
+
return 'printed';
|
|
376
376
|
}
|
|
377
377
|
// Decided and delivered first: the handover blocks for the whole session.
|
|
378
|
-
await onDecided?.(
|
|
378
|
+
await onDecided?.('launched');
|
|
379
379
|
errLine(`Starting the TinyFish walkthrough in ${displayName}...`);
|
|
380
380
|
handover();
|
|
381
|
-
return
|
|
381
|
+
return 'launched';
|
|
382
382
|
}
|
|
383
383
|
export async function launchNativeMcpClient(client, onDecided) {
|
|
384
384
|
if (client.detachedLaunch) {
|
|
@@ -393,5 +393,5 @@ export async function launchNativeMcpClient(client, onDecided) {
|
|
|
393
393
|
: handOverTerminal(client.command, [DEFAULT_ONBOARDING_PROMPT], client.displayName), onDecided);
|
|
394
394
|
}
|
|
395
395
|
export function launchOpenClawWalkthrough(onDecided) {
|
|
396
|
-
return deliverWalkthrough(
|
|
396
|
+
return deliverWalkthrough('OpenClaw', 'openclaw', OPENCLAW_ONBOARDING_PROMPT, () => handOverTerminal('openclaw', ['chat', '--message', OPENCLAW_ONBOARDING_PROMPT], 'OpenClaw'), onDecided);
|
|
397
397
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CliFallbackOutcome, type ConnectCheckpoint, type ConnectFailureStage } from
|
|
1
|
+
import { type CliFallbackOutcome, type ConnectCheckpoint, type ConnectFailureStage } from './connect-runtime.js';
|
|
2
2
|
export type Prompt = (question: string) => Promise<string>;
|
|
3
3
|
export declare const SEARCH_RETRY = "tinyfish search query --pretty -- \"<your query>\"";
|
|
4
4
|
export declare const DEFAULT_VERIFICATION_QUERY = "today's top Hacker News story";
|