@tiny-fish/cli 0.25.2-next.221 → 0.25.2-next.222
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/connect.js
CHANGED
|
@@ -63,11 +63,12 @@ async function connectNativeMcpClient(client, options) {
|
|
|
63
63
|
const useKeyAuth = !!keyedAdd;
|
|
64
64
|
if (options.keyAuthOnly && !useKeyAuth) {
|
|
65
65
|
// No keyAuth at all is structural (Hermes); a missing flag is just an old install.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
`needs a terminal. Update it, then re-run: tinyfish connect ${client.connectClient}
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
if (client.keyAuth) {
|
|
67
|
+
throw new ConnectStepError(`This ${client.displayName} install cannot take a TinyFish API key, and signing in ` +
|
|
68
|
+
`needs a terminal. Update it, then re-run: tinyfish connect ${client.connectClient}`, "harness_too_old");
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`${client.displayName} cannot take a TinyFish API key, and signing in needs a ` +
|
|
71
|
+
`terminal. Run: tinyfish connect ${client.connectClient}`);
|
|
71
72
|
}
|
|
72
73
|
telemetry.track("checkpoint", { phase: "prerequisite_ok" });
|
|
73
74
|
state.stage = "registration_cleanup";
|
package/dist/lib/connect-all.js
CHANGED
|
@@ -17,6 +17,11 @@ import { verifyMcpAuth, verifyMcpHealth } from "./verify.js";
|
|
|
17
17
|
// `codex mcp add` succeeds unauthenticated; OAuth lands at first tool use.
|
|
18
18
|
// TODO(PF-3580): connect now probes Codex sign-in, so this over-warns a signed-in install.
|
|
19
19
|
const AUTH_DEFERRED_AT_INSTALL = new Set(["codex"]);
|
|
20
|
+
// A probe that exits non-zero is an install so old the subcommand is missing entirely.
|
|
21
|
+
const TOO_OLD_REASONS = new Set([
|
|
22
|
+
"harness_too_old",
|
|
23
|
+
"harness_command_unsupported",
|
|
24
|
+
]);
|
|
20
25
|
const AUTH_CHECK_CAPABLE = new Set(["cursor", "openclaw"]);
|
|
21
26
|
// Descriptor-derived so a harness that cannot carry a key is skipped, not failed, when headless.
|
|
22
27
|
// Cursor and OpenClaw hold the key themselves rather than handing it to a harness `mcp add`.
|
|
@@ -186,6 +191,15 @@ async function processHarness(detection, opts, prompt, hoistedCliInstall) {
|
|
|
186
191
|
});
|
|
187
192
|
}
|
|
188
193
|
catch (e) {
|
|
194
|
+
// Both reasons carry the same "flag not listed" message; the upgrade nudge is in it.
|
|
195
|
+
if (e instanceof ConnectStepError && TOO_OLD_REASONS.has(e.failureReason)) {
|
|
196
|
+
return {
|
|
197
|
+
...base,
|
|
198
|
+
outcome: "harness_too_old",
|
|
199
|
+
errorMessage: e.message,
|
|
200
|
+
fixCommand: `tinyfish connect ${harness}`,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
189
203
|
return {
|
|
190
204
|
...base,
|
|
191
205
|
outcome: "failed",
|
|
@@ -250,6 +264,9 @@ function summaryLine(result, uninstall) {
|
|
|
250
264
|
if (result.outcome === "interrupted") {
|
|
251
265
|
return `✗ ${name} — interrupted before finishing. Fix: ${result.fixCommand}`;
|
|
252
266
|
}
|
|
267
|
+
if (result.outcome === "harness_too_old") {
|
|
268
|
+
return `… ${name} — needs a newer ${name}: ${result.errorMessage} Fix, once upgraded: ${result.fixCommand}`;
|
|
269
|
+
}
|
|
253
270
|
if (result.outcome === "failed") {
|
|
254
271
|
return `✗ ${name} — install failed: ${result.errorMessage}. Fix: ${result.fixCommand}`;
|
|
255
272
|
}
|
|
@@ -283,7 +300,10 @@ function computeExitCode(results) {
|
|
|
283
300
|
if (detected.length === 0)
|
|
284
301
|
return 1;
|
|
285
302
|
// Only real install attempts count toward the success/failure denominator.
|
|
286
|
-
const attempted = detected.filter((r) => r.outcome === "installed" ||
|
|
303
|
+
const attempted = detected.filter((r) => r.outcome === "installed" ||
|
|
304
|
+
r.outcome === "failed" ||
|
|
305
|
+
r.outcome === "harness_too_old" ||
|
|
306
|
+
r.outcome === "interrupted");
|
|
287
307
|
if (attempted.length === 0)
|
|
288
308
|
return 0;
|
|
289
309
|
return attempted.some((r) => r.outcome === "installed") ? 0 : 1;
|
|
@@ -4,7 +4,7 @@ export type AgentClient = "claude-code" | "codex" | "cursor" | "grok" | "hermes"
|
|
|
4
4
|
type ConnectStage = "started" | "checkpoint" | "completed" | "failed" | "aborted" | "post_install_failed";
|
|
5
5
|
type ConnectFailureStage = "prerequisite_check" | "registration_cleanup" | "registration" | "registration_or_authentication" | "client_oauth" | "cli_install" | "skill_install" | "authentication" | "walkthrough_launch";
|
|
6
6
|
export declare const CONNECT_FAILURE_REASONS: readonly ["harness_not_installed", "harness_command_unsupported", "harness_too_old", "command_not_found", "timeout", "spawn_error", "nonzero_exit", "invalid_config", "user_abandoned", "process_terminated", "unexpected_error"];
|
|
7
|
-
type ConnectFailureReason = (typeof CONNECT_FAILURE_REASONS)[number];
|
|
7
|
+
export type ConnectFailureReason = (typeof CONNECT_FAILURE_REASONS)[number];
|
|
8
8
|
export type ConnectAuthMode = Exclude<AuthMode, AuthMode.Unknown> | "deferred";
|
|
9
9
|
type ConnectCheckpoint = "prerequisite_ok" | "registered" | "oauth_done" | "cli_installed" | "skill_installed" | "authenticated";
|
|
10
10
|
/** Ctrl+C killed a setup child — abandonment, not error. */
|
|
@@ -8,7 +8,6 @@ import { commandNotFound, spawnStepError, throwIfInterrupted } from "./connect-r
|
|
|
8
8
|
import { CLI_AGENT_IDENTITY } from "./constants.js";
|
|
9
9
|
import { errLine } from "./output.js";
|
|
10
10
|
// Supports Hermes without node:util.styleText, so the installer still runs on Node 20.11.
|
|
11
|
-
// A bump must re-verify SKILL_AGENT_DISPLAY_NAMES against `skills list --json`.
|
|
12
11
|
const SKILLS_CLI_PACKAGE = "skills@1.5.15";
|
|
13
12
|
const TINYFISH_WEB_SKILL_SOURCE = "tinyfish-io/tinyfish-cookbook";
|
|
14
13
|
const TINYFISH_WEB_SKILL = "use-tinyfish";
|
|
@@ -45,35 +44,23 @@ const SKILL_ALREADY_CURRENT_PATTERN = /All global skills are up to date/;
|
|
|
45
44
|
// A lock entry with no recorded hash is untrackable, so `skills` reports it as skipped rather
|
|
46
45
|
// than failed. Left undetected that reads as "up to date" while nothing was refreshed.
|
|
47
46
|
const SKILL_UNCHECKABLE_PATTERN = /cannot be checked automatically/;
|
|
48
|
-
|
|
49
|
-
const SKILL_AGENT_DISPLAY_NAMES = {
|
|
50
|
-
"claude-code": "Claude Code",
|
|
51
|
-
codex: "Codex",
|
|
52
|
-
cursor: "Cursor",
|
|
53
|
-
"hermes-agent": "Hermes Agent",
|
|
54
|
-
opencode: "OpenCode",
|
|
55
|
-
};
|
|
56
|
-
const skillListSchema = z.array(z.object({ name: z.string(), agents: z.array(z.string()) }));
|
|
47
|
+
const skillListSchema = z.array(z.object({ name: z.string() }));
|
|
57
48
|
// `add` exits 0 on per-skill failure; the piped list is the verdict.
|
|
58
|
-
function failedListVerdict(
|
|
49
|
+
function failedListVerdict() {
|
|
59
50
|
const list = spawn.sync("npx", ["-y", SKILLS_CLI_PACKAGE, "list", "--global", "--json"], {
|
|
60
51
|
encoding: "utf8",
|
|
61
52
|
env: skillSpawnEnv(),
|
|
62
53
|
maxBuffer: STEP_MAX_BUFFER,
|
|
63
54
|
timeout: SKILL_INSTALL_TIMEOUT_MS,
|
|
64
55
|
});
|
|
65
|
-
const verified = !list.error && list.status === 0 &&
|
|
56
|
+
const verified = !list.error && list.status === 0 && skillListed(list.stdout ?? "");
|
|
66
57
|
return verified ? null : list;
|
|
67
58
|
}
|
|
68
|
-
//
|
|
69
|
-
|
|
59
|
+
// Entry presence is the whole verdict: `agents` names the harnesses `skills` detects,
|
|
60
|
+
// so an install for a harness with no config dir yet lists none (PF-3680).
|
|
61
|
+
function skillListed(stdout) {
|
|
70
62
|
const parsed = skillListSchema.safeParse(parseJson(stdout));
|
|
71
|
-
|
|
72
|
-
return false;
|
|
73
|
-
const agents = parsed.data
|
|
74
|
-
.filter((skill) => skill.name === TINYFISH_WEB_SKILL)
|
|
75
|
-
.flatMap((skill) => skill.agents);
|
|
76
|
-
return skillAgents.every((agent) => agents.includes(SKILL_AGENT_DISPLAY_NAMES[agent]));
|
|
63
|
+
return parsed.success && parsed.data.some((skill) => skill.name === TINYFISH_WEB_SKILL);
|
|
77
64
|
}
|
|
78
65
|
function parseJson(text) {
|
|
79
66
|
try {
|
|
@@ -93,7 +80,7 @@ export function installWebSkill(client) {
|
|
|
93
80
|
env: skillSpawnEnv(),
|
|
94
81
|
timeout: SKILL_INSTALL_TIMEOUT_MS,
|
|
95
82
|
});
|
|
96
|
-
const failed = add.error || add.status !== 0 ? add : failedListVerdict(
|
|
83
|
+
const failed = add.error || add.status !== 0 ? add : failedListVerdict();
|
|
97
84
|
if (failed) {
|
|
98
85
|
throw spawnStepError(`Could not install the TinyFish web skill in ${client.displayName}`, failed);
|
|
99
86
|
}
|
|
@@ -162,7 +149,7 @@ function reinstallWebSkill(skillAgents, verbose) {
|
|
|
162
149
|
const output = capturedOutput(result);
|
|
163
150
|
// Quiet capture stays (#4434); the list verdict replaced prose matching.
|
|
164
151
|
const addFailed = Boolean(result.error) || result.status !== 0;
|
|
165
|
-
const failedList = addFailed ? null : failedListVerdict(
|
|
152
|
+
const failedList = addFailed ? null : failedListVerdict();
|
|
166
153
|
const failed = addFailed || failedList !== null;
|
|
167
154
|
if (failed)
|
|
168
155
|
throwIfInterrupted(failedList ?? result);
|