@tiny-fish/cli 0.50.1-next.397 → 0.50.1-next.400
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.
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Connect installs the CLI for the skills; only a missing or older PATH copy needs one. */
|
|
2
|
+
export declare function cliInstallNeeded(): boolean;
|
|
3
|
+
/** SemVer order: a prerelease sorts before its release (`0.51.0-next.3` < `0.51.0`). */
|
|
4
|
+
export declare function isOlder(installed: string, current: string): boolean;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import spawn from 'cross-spawn';
|
|
3
|
+
import which from 'which';
|
|
4
|
+
import { CLI_VERSION } from './constants.js';
|
|
5
|
+
import { installRoot } from './install-root.js';
|
|
6
|
+
const VERSION_PROBE_TIMEOUT_MS = 10_000;
|
|
7
|
+
const SEMVER = /^\d+\.\d+\.\d+(?:-\S+)?$/;
|
|
8
|
+
/** Connect installs the CLI for the skills; only a missing or older PATH copy needs one. */
|
|
9
|
+
export function cliInstallNeeded() {
|
|
10
|
+
// Running from the global prefix means PATH already has this exact version.
|
|
11
|
+
if (installRoot())
|
|
12
|
+
return false;
|
|
13
|
+
// npx puts its own cache bin first on PATH, and that copy vanishes after the run.
|
|
14
|
+
const candidates = (which.sync('tinyfish', { all: true, nothrow: true }) ?? []).filter((candidate) => !candidate.split(path.sep).includes('_npx') && dirOnPath(candidate));
|
|
15
|
+
if (candidates.length === 0)
|
|
16
|
+
return true;
|
|
17
|
+
const installed = pathCliVersion(candidates[0]);
|
|
18
|
+
if (!installed)
|
|
19
|
+
return true;
|
|
20
|
+
return isOlder(installed, CLI_VERSION);
|
|
21
|
+
}
|
|
22
|
+
/** SemVer order: a prerelease sorts before its release (`0.51.0-next.3` < `0.51.0`). */
|
|
23
|
+
export function isOlder(installed, current) {
|
|
24
|
+
const [installedCore, installedPre] = installed.split(/-(.*)/);
|
|
25
|
+
const [currentCore, currentPre] = current.split(/-(.*)/);
|
|
26
|
+
const order = installedCore.localeCompare(currentCore, 'en', { numeric: true });
|
|
27
|
+
if (order !== 0)
|
|
28
|
+
return order < 0;
|
|
29
|
+
if (!installedPre || !currentPre)
|
|
30
|
+
return Boolean(installedPre) && !currentPre;
|
|
31
|
+
return installedPre.localeCompare(currentPre, 'en', { numeric: true }) < 0;
|
|
32
|
+
}
|
|
33
|
+
// which searches the cwd first on Windows; a skill run elsewhere would miss that copy.
|
|
34
|
+
function dirOnPath(candidate) {
|
|
35
|
+
const key = (dir) => {
|
|
36
|
+
const resolved = path.resolve(dir);
|
|
37
|
+
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
38
|
+
};
|
|
39
|
+
const dirs = (process.env['PATH'] ?? '').split(path.delimiter).filter(Boolean).map(key);
|
|
40
|
+
return dirs.includes(key(path.dirname(candidate)));
|
|
41
|
+
}
|
|
42
|
+
function pathCliVersion(binary) {
|
|
43
|
+
const result = spawn.sync(binary, ['--version'], {
|
|
44
|
+
encoding: 'utf8',
|
|
45
|
+
timeout: VERSION_PROBE_TIMEOUT_MS,
|
|
46
|
+
});
|
|
47
|
+
if (result.error || result.status !== 0)
|
|
48
|
+
return undefined;
|
|
49
|
+
// Unparseable output stays undefined, which installs: today's behaviour.
|
|
50
|
+
return result.stdout
|
|
51
|
+
?.split(/\r?\n/)
|
|
52
|
+
.map((line) => line.trim())
|
|
53
|
+
.find((line) => SEMVER.test(line));
|
|
54
|
+
}
|
|
@@ -99,7 +99,8 @@ function probeVersion() {
|
|
|
99
99
|
});
|
|
100
100
|
if (result.error || result.status !== 0)
|
|
101
101
|
return false;
|
|
102
|
-
|
|
102
|
+
// pi 0.73 prints its version to stderr.
|
|
103
|
+
return /^\d+\.\d+\.\d+/.test((result.stdout || result.stderr || '').trim());
|
|
103
104
|
}
|
|
104
105
|
/** `skills` hardcodes the default dir, so an override leaves the skill where pi will not look. */
|
|
105
106
|
export function piSkillDirMismatch() {
|
package/dist/lib/connect-all.js
CHANGED
|
@@ -4,6 +4,7 @@ import spawn from 'cross-spawn';
|
|
|
4
4
|
import { connectHarness } from './connect-harness.js';
|
|
5
5
|
import { loadConfig, validatedApiKey } from './auth.js';
|
|
6
6
|
import { installTinyFishCli, } from './cli-install.js';
|
|
7
|
+
import { cliInstallNeeded } from './cli-presence.js';
|
|
7
8
|
import { authGate, mapConnectError, OAUTH_SIGN_IN_TIMEOUT_MS } from './connect-all-auth.js';
|
|
8
9
|
import { actionable, computeExitCode, reloadHint, renderSummary } from './connect-all-summary.js';
|
|
9
10
|
import { planText, uninstallHarness, uninstallPlanText } from './connect-all-uninstall.js';
|
|
@@ -167,8 +168,12 @@ function hoistCliIfNeeded(detections, opts, willAttempt) {
|
|
|
167
168
|
willAttempt(d.harness));
|
|
168
169
|
if (!cliNeeded)
|
|
169
170
|
return undefined;
|
|
170
|
-
|
|
171
|
-
|
|
171
|
+
// A skip still reports cli_installed; the route rejects unknown phases.
|
|
172
|
+
let outcome = { ok: true, durationMs: 0 };
|
|
173
|
+
if (cliInstallNeeded()) {
|
|
174
|
+
errLine('First: the TinyFish CLI, shared by every harness.');
|
|
175
|
+
outcome = runHoistedCliInstall(opts.verbose ?? false);
|
|
176
|
+
}
|
|
172
177
|
if (!outcome)
|
|
173
178
|
return 'interrupted';
|
|
174
179
|
let taken = false;
|
|
@@ -2,6 +2,7 @@ import * as readline from 'node:readline/promises';
|
|
|
2
2
|
import spawn from 'cross-spawn';
|
|
3
3
|
import { resolvedApiKey, validateKeyFormat } from './auth.js';
|
|
4
4
|
import { installTinyFishCli } from './cli-install.js';
|
|
5
|
+
import { cliInstallNeeded } from './cli-presence.js';
|
|
5
6
|
import { ensureCliAuthenticated, TINYFISH_CLI_NOT_FOUND_MESSAGE } from './connect-auth.js';
|
|
6
7
|
import { TINYFISH_API_KEY_VAR } from './constants.js';
|
|
7
8
|
import { commandNotFound, ConnectInterruptedError, ConnectStepError, throwIfInterrupted, } from './connect-runtime.js';
|
|
@@ -50,6 +51,8 @@ export function createStdinPrompt() {
|
|
|
50
51
|
};
|
|
51
52
|
}
|
|
52
53
|
function installStep(opts) {
|
|
54
|
+
if (!cliInstallNeeded())
|
|
55
|
+
return null;
|
|
53
56
|
try {
|
|
54
57
|
installTinyFishCli({ verbose: opts.verbose, announce: true });
|
|
55
58
|
return null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { verifyMcpHealth } from './verify.js';
|
|
2
2
|
import { installTinyFishCli } from './cli-install.js';
|
|
3
|
+
import { cliInstallNeeded } from './cli-presence.js';
|
|
3
4
|
import { createStdinPrompt, runCliFallback } from './connect-fallback.js';
|
|
4
5
|
import { detectHumanInitiated } from './harness.js';
|
|
5
6
|
import { errLine } from './output.js';
|
|
@@ -77,7 +78,8 @@ export function runCliInstallStep(options, telemetry) {
|
|
|
77
78
|
takeHoistedCliInstall(options.hoistedCliInstall, telemetry);
|
|
78
79
|
return;
|
|
79
80
|
}
|
|
80
|
-
|
|
81
|
+
if (cliInstallNeeded())
|
|
82
|
+
installTinyFishCli({ verbose: options.verbose ?? false, announce: true });
|
|
81
83
|
telemetry.track('checkpoint', { phase: 'cli_installed' });
|
|
82
84
|
}
|
|
83
85
|
export function walkthroughPhase(outcome) {
|