@tiny-fish/cli 0.29.1-next.239 → 0.29.1-next.241
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/lib/connect-all.js +47 -11
- package/dist/lib/connect-runtime.js +2 -1
- package/package.json +1 -1
package/dist/lib/connect-all.js
CHANGED
|
@@ -26,6 +26,8 @@ const NO_BROWSER_REASONS = new Set([
|
|
|
26
26
|
"harness_not_installed",
|
|
27
27
|
"command_not_found",
|
|
28
28
|
]);
|
|
29
|
+
// Naming a harness the user does not have is the noise PF-3708 cut.
|
|
30
|
+
const HIDDEN_ONCE_SOMETHING_WORKED = new Set(["deselected", "not_detected", "not_installed"]);
|
|
29
31
|
const STEP_INDENT = " ";
|
|
30
32
|
// Must undercut agent tool-call budgets (Claude Code Bash defaults to 120s).
|
|
31
33
|
const OAUTH_SIGN_IN_TIMEOUT_MS = 90_000;
|
|
@@ -140,11 +142,11 @@ function uninstallHarness(harness, configPath) {
|
|
|
140
142
|
async function processHarness(detection, opts, prompt, oauthBudget, hoistedCliInstall, deferOutro) {
|
|
141
143
|
const { harness, configPath, detected } = detection;
|
|
142
144
|
const base = { harness, detected, configPath, installed: false };
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
const noWork = noWorkOutcome(detection, opts.uninstall);
|
|
146
|
+
if (noWork === "not_detected")
|
|
147
|
+
return { ...base, outcome: noWork };
|
|
148
|
+
if (noWork)
|
|
149
|
+
return { ...base, outcome: noWork, fixCommand: `tinyfish connect ${harness}` };
|
|
148
150
|
// dryRun first: --dry-run --uninstall must plan, never reach removeCursorMcpServer().
|
|
149
151
|
if (opts.dryRun) {
|
|
150
152
|
const plan = opts.uninstall
|
|
@@ -210,6 +212,10 @@ async function processHarness(detection, opts, prompt, oauthBudget, hoistedCliIn
|
|
|
210
212
|
if (spentBrowserSlot && e instanceof ConnectStepError && NO_BROWSER_REASONS.has(e.failureReason)) {
|
|
211
213
|
oauthBudget.spent = false;
|
|
212
214
|
}
|
|
215
|
+
// Detection saw a name on PATH; the probe proved nothing is behind it.
|
|
216
|
+
if (e instanceof ConnectStepError && e.failureReason === "harness_not_installed") {
|
|
217
|
+
return { ...base, detected: false, outcome: "not_installed" };
|
|
218
|
+
}
|
|
213
219
|
// Both reasons carry the same "flag not listed" message; the upgrade nudge is in it.
|
|
214
220
|
if (e instanceof ConnectStepError && TOO_OLD_REASONS.has(e.failureReason)) {
|
|
215
221
|
return {
|
|
@@ -245,6 +251,12 @@ async function processHarness(detection, opts, prompt, oauthBudget, hoistedCliIn
|
|
|
245
251
|
}
|
|
246
252
|
function summaryLine(result, uninstall) {
|
|
247
253
|
const name = DISPLAY_NAMES[result.harness];
|
|
254
|
+
if (result.outcome === "not_installed") {
|
|
255
|
+
const next = uninstall
|
|
256
|
+
? "nothing to remove"
|
|
257
|
+
: `Install ${name}, then run: tinyfish connect ${result.harness}`;
|
|
258
|
+
return `- ${name} — not installed (\`${HARNESS_COMMANDS[result.harness]}\` is on PATH but does not run). ${next}`;
|
|
259
|
+
}
|
|
248
260
|
if (!result.detected) {
|
|
249
261
|
const next = uninstall
|
|
250
262
|
? "nothing to remove"
|
|
@@ -268,9 +280,6 @@ function summaryLine(result, uninstall) {
|
|
|
268
280
|
? `… ${name} — ${result.errorMessage}. Finish it in a terminal: ${result.fixCommand}`
|
|
269
281
|
: `… ${name} — needs a browser sign-in. Run in a terminal: ${result.fixCommand}`;
|
|
270
282
|
}
|
|
271
|
-
if (result.outcome === "deselected") {
|
|
272
|
-
return `… ${name} — skipped, not selected. Fix: ${result.fixCommand}`;
|
|
273
|
-
}
|
|
274
283
|
if (result.outcome === "uninstall_pointer") {
|
|
275
284
|
return `… ${name} — not removed automatically (config is owned by ${name}). Run: ${result.fixCommand}`;
|
|
276
285
|
}
|
|
@@ -307,6 +316,28 @@ function summaryLine(result, uninstall) {
|
|
|
307
316
|
}
|
|
308
317
|
return `✓ ${name} — installed, ${verifyLabel}`;
|
|
309
318
|
}
|
|
319
|
+
/** Absent and unpicked harnesses are noise once something real happened. */
|
|
320
|
+
function renderSummary(results, uninstall) {
|
|
321
|
+
if (!results.some(actionable))
|
|
322
|
+
return results.map((r) => summaryLine(r, uninstall));
|
|
323
|
+
// Ids, not display names: they double as the `connect` argument.
|
|
324
|
+
const skipped = results.filter((r) => r.outcome === "deselected").map((r) => r.harness);
|
|
325
|
+
const lines = results
|
|
326
|
+
.filter((r) => !HIDDEN_ONCE_SOMETHING_WORKED.has(r.outcome))
|
|
327
|
+
.map((r) => summaryLine(r, uninstall));
|
|
328
|
+
if (skipped.length === 0)
|
|
329
|
+
return lines;
|
|
330
|
+
lines.push("", `Skipped (not selected): ${skipped.join(", ")}`);
|
|
331
|
+
return lines;
|
|
332
|
+
}
|
|
333
|
+
/** The states processHarness settles without work; stepTotal must agree with it. */
|
|
334
|
+
function noWorkOutcome(d, uninstall) {
|
|
335
|
+
if (!d.detected)
|
|
336
|
+
return "not_detected";
|
|
337
|
+
if (!uninstall && isStaleConfig(d))
|
|
338
|
+
return "stale_config";
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
310
341
|
/** A config-writing connect needs no binary; every other harness spawns one. */
|
|
311
342
|
function isStaleConfig(detection) {
|
|
312
343
|
return (detection.detected &&
|
|
@@ -475,7 +506,9 @@ export async function runConnectAll(opts) {
|
|
|
475
506
|
const outro = new Set();
|
|
476
507
|
const results = [];
|
|
477
508
|
const oauthBudget = { spent: false };
|
|
478
|
-
|
|
509
|
+
// Shares processHarness's no-work states so stepTotal cannot desync.
|
|
510
|
+
const stepped = (d) => !deselected.has(d.harness) && !noWorkOutcome(d, opts.uninstall);
|
|
511
|
+
const stepTotal = detections.filter(stepped).length;
|
|
479
512
|
let step = 0;
|
|
480
513
|
for (const detection of detections) {
|
|
481
514
|
const { harness } = detection;
|
|
@@ -488,6 +521,9 @@ export async function runConnectAll(opts) {
|
|
|
488
521
|
});
|
|
489
522
|
continue;
|
|
490
523
|
}
|
|
524
|
+
// The pad loop below records these; announcing them is the noise this command had.
|
|
525
|
+
if (!stepped(detection))
|
|
526
|
+
continue;
|
|
491
527
|
step += 1;
|
|
492
528
|
errLine(`step ${step} of ${stepTotal} — ${DISPLAY_NAMES[harness]}`);
|
|
493
529
|
let result;
|
|
@@ -533,8 +569,8 @@ export async function runConnectAll(opts) {
|
|
|
533
569
|
}
|
|
534
570
|
outLine("");
|
|
535
571
|
outLine("TinyFish setup summary:");
|
|
536
|
-
for (const
|
|
537
|
-
outLine(
|
|
572
|
+
for (const line of renderSummary(results, opts.uninstall))
|
|
573
|
+
outLine(line);
|
|
538
574
|
// Before the launch handover: anything printed after it is never seen.
|
|
539
575
|
for (const line of outro)
|
|
540
576
|
errLine(line);
|
|
@@ -137,7 +137,8 @@ export function requireCommandSupport(client) {
|
|
|
137
137
|
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1" },
|
|
138
138
|
timeout: NON_INTERACTIVE_TIMEOUT_MS,
|
|
139
139
|
});
|
|
140
|
-
|
|
140
|
+
// 127 is the shell's not-found code; a wrapper shim with no real binary behind it exits that.
|
|
141
|
+
if (commandNotFound(result.error) || result.status === 127) {
|
|
141
142
|
throw new PrerequisiteError(`${client.displayName} is not installed or not available on PATH.`, "harness_not_installed", { cause: result.error });
|
|
142
143
|
}
|
|
143
144
|
if (result.error || result.status !== 0) {
|