@tiny-fish/cli 0.42.0 → 0.42.1-next.336
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.d.ts +4 -2
- package/dist/commands/connect.js +34 -7
- package/dist/commands/upgrade.js +3 -3
- package/dist/lib/doctor-repairs.js +21 -12
- package/dist/lib/harness-spec.d.ts +1 -1
- package/dist/lib/install-root.d.ts +2 -0
- package/dist/lib/install-root.js +5 -1
- package/dist/lib/skill-install.d.ts +1 -11
- package/dist/lib/skill-install.js +20 -117
- package/package.json +1 -1
|
@@ -23,14 +23,16 @@ export interface NativeConnectOptions {
|
|
|
23
23
|
/** Targeted connects only: --all keeps its per-harness failed row. */
|
|
24
24
|
fallbackWhenMissing?: boolean;
|
|
25
25
|
}
|
|
26
|
-
/** Skill install only: no key handoff
|
|
27
|
-
export declare function connectOpenClaw(options: Omit<NativeConnectOptions, 'keyAuthOnly' | '
|
|
26
|
+
/** Skill install only: no key handoff or post-install callback to honour. */
|
|
27
|
+
export declare function connectOpenClaw(options: Omit<NativeConnectOptions, 'keyAuthOnly' | 'onPostInstallFailed'>): Promise<void>;
|
|
28
28
|
/** Writes mcp.json directly — no `cursor mcp add` exists. */
|
|
29
29
|
export declare function connectCursor(options: {
|
|
30
30
|
apiKey?: string;
|
|
31
31
|
mcpUrl: string;
|
|
32
32
|
attemptId?: string;
|
|
33
33
|
verbose?: boolean;
|
|
34
|
+
authTimeoutMs?: number;
|
|
35
|
+
onPostInstallFailed?: () => void;
|
|
34
36
|
}): Promise<void>;
|
|
35
37
|
export declare function launchAgent(client: AgentClient): Promise<void>;
|
|
36
38
|
export declare function connectHarness(harness: AgentClient, options: NativeConnectOptions): Promise<ConnectAuthMode | undefined>;
|
package/dist/commands/connect.js
CHANGED
|
@@ -451,7 +451,7 @@ function trackPostInstallFailure(displayName, state, telemetry, error) {
|
|
|
451
451
|
errLine(`The ${displayName} MCP connection succeeded, but a finishing step ` +
|
|
452
452
|
`(${state.stage}) failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
453
453
|
}
|
|
454
|
-
/** Skill install only: no key handoff
|
|
454
|
+
/** Skill install only: no key handoff or post-install callback to honour. */
|
|
455
455
|
export async function connectOpenClaw(options) {
|
|
456
456
|
const telemetry = createConnectTelemetry(options.mcpUrl, 'openclaw', options);
|
|
457
457
|
// Skill install, no MCP server — never degraded, and false keeps it inside a `= false` filter.
|
|
@@ -483,7 +483,10 @@ export async function connectOpenClaw(options) {
|
|
|
483
483
|
telemetry.track('checkpoint', { phase: 'skill_installed' });
|
|
484
484
|
// Stores the key before probing, so a passed --api-key never reaches an interactive login.
|
|
485
485
|
state.stage = 'authentication';
|
|
486
|
-
|
|
486
|
+
// A set timeout means headless; never shell an interactive login.
|
|
487
|
+
ensureCliAuthenticated('openclaw', options.apiKey, {
|
|
488
|
+
interactiveLogin: options.authTimeoutMs === undefined,
|
|
489
|
+
});
|
|
487
490
|
telemetry.track('checkpoint', { phase: 'authenticated' });
|
|
488
491
|
saveConnectContext('openclaw', telemetry.attemptId);
|
|
489
492
|
// For openclaw the skill install and auth ARE functional; only the walkthrough is cosmetic.
|
|
@@ -586,6 +589,28 @@ const CONFIG_FILE_HARNESSES = {
|
|
|
586
589
|
},
|
|
587
590
|
},
|
|
588
591
|
};
|
|
592
|
+
/** Cosmetic and post-settle: a skill failure must not fail the connection. */
|
|
593
|
+
function installSkillPostSettle(spec, displayName, state, telemetry, options) {
|
|
594
|
+
if (!spec.skillTarget.skillAgent)
|
|
595
|
+
return false;
|
|
596
|
+
state.stage = 'skill_install';
|
|
597
|
+
try {
|
|
598
|
+
installWebSkill(spec.skillTarget, { verbose: options.verbose ?? false });
|
|
599
|
+
telemetry.track('checkpoint', { phase: 'skill_installed' });
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
catch (error) {
|
|
603
|
+
trackPostInstallFailure(displayName, state, telemetry, error);
|
|
604
|
+
return true;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
/** Prints last: the outro must own the final line. */
|
|
608
|
+
function reportPostInstallFailure(harness, failed, onPostInstallFailed) {
|
|
609
|
+
if (!failed)
|
|
610
|
+
return;
|
|
611
|
+
errLine(finishSetupHint(harness));
|
|
612
|
+
onPostInstallFailed?.();
|
|
613
|
+
}
|
|
589
614
|
/** Writes the harness's MCP config file itself; no `mcp add` exists. */
|
|
590
615
|
async function connectConfigFileHarness(harness, options) {
|
|
591
616
|
const spec = CONFIG_FILE_HARNESSES[harness];
|
|
@@ -630,12 +655,12 @@ async function connectConfigFileHarness(harness, options) {
|
|
|
630
655
|
await runGuarded(state, telemetry, async () => {
|
|
631
656
|
telemetry.track('started');
|
|
632
657
|
telemetry.track('checkpoint', { phase: 'prerequisite_ok' });
|
|
633
|
-
state.stage = 'skill_install';
|
|
634
|
-
installWebSkill(spec.skillTarget, { verbose: options.verbose ?? false });
|
|
635
|
-
telemetry.track('checkpoint', { phase: 'skill_installed' });
|
|
636
658
|
// Auth first: a key lets the config carry an X-API-Key header, removing the sign-in hand-back.
|
|
637
659
|
state.stage = 'authentication';
|
|
638
|
-
|
|
660
|
+
// A set timeout means headless; never shell an interactive login.
|
|
661
|
+
ensureCliAuthenticated(harness, options.apiKey, {
|
|
662
|
+
interactiveLogin: options.authTimeoutMs === undefined,
|
|
663
|
+
});
|
|
639
664
|
telemetry.track('checkpoint', { phase: 'authenticated' });
|
|
640
665
|
const resolvedKey = validatedApiKey(options.apiKey);
|
|
641
666
|
// ensureCliAuthenticated leaves a key stored, and `auth status` exits 0 on a malformed one.
|
|
@@ -662,6 +687,8 @@ async function connectConfigFileHarness(harness, options) {
|
|
|
662
687
|
}
|
|
663
688
|
telemetry.track('checkpoint', { phase: 'registered' });
|
|
664
689
|
saveConnectContext(harness, telemetry.attemptId);
|
|
690
|
+
settle(state, telemetry, 'completed', { authMode: resolvedKey ? AuthMode.ApiKey : 'deferred' });
|
|
691
|
+
const postInstallFailed = installSkillPostSettle(spec, displayName, state, telemetry, options);
|
|
665
692
|
if (resolvedKey) {
|
|
666
693
|
// Deeplink would embed the key in a URL (process args, LaunchServices logs) — reload instead.
|
|
667
694
|
const verify = await verifyMcpAuth(resolvedKey);
|
|
@@ -681,7 +708,7 @@ async function connectConfigFileHarness(harness, options) {
|
|
|
681
708
|
const note = spec.postConnectNote?.();
|
|
682
709
|
if (note)
|
|
683
710
|
errLine(note);
|
|
684
|
-
|
|
711
|
+
reportPostInstallFailure(harness, postInstallFailed, options.onPostInstallFailed);
|
|
685
712
|
});
|
|
686
713
|
}
|
|
687
714
|
/** Writes mcp.json directly — no `cursor mcp add` exists. */
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -118,10 +118,10 @@ export async function runUpgradeCommand(verbose = false, scope = 'both') {
|
|
|
118
118
|
export function registerUpgrade(program) {
|
|
119
119
|
program
|
|
120
120
|
.command('upgrade')
|
|
121
|
-
.description('Update the TinyFish CLI and
|
|
122
|
-
.option('--verbose', 'Print the full npm
|
|
121
|
+
.description('Update the TinyFish CLI and refresh its bundled use-tinyfish skill')
|
|
122
|
+
.option('--verbose', 'Print the full npm output instead of only on failure')
|
|
123
123
|
.option('--cli-only', 'Update only the TinyFish CLI, leaving the skill untouched')
|
|
124
|
-
.option('--skill-only',
|
|
124
|
+
.option('--skill-only', "Rewrite the skill from the installed CLI's bundle, skipping npm")
|
|
125
125
|
.action(async (options) => {
|
|
126
126
|
if (options.cliOnly && options.skillOnly) {
|
|
127
127
|
throw new Error('Pass either --cli-only or --skill-only, not both.');
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import spawn from 'cross-spawn';
|
|
2
2
|
import { connectHarness } from '../commands/connect.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { OAUTH_SIGN_IN_TIMEOUT_MS } from './connect-all-auth.js';
|
|
4
|
+
import { Registered } from './harness-detect.js';
|
|
5
5
|
import { detectHumanInitiated } from './harness.js';
|
|
6
6
|
import { errLine } from './output.js';
|
|
7
|
-
// Config-file repairs are local writes; the rest need browser sign-in.
|
|
8
|
-
const UNATTENDED_SAFE = new Set(ALL_HARNESSES.filter((harness) => harnessSpec(harness).cliWritesConfig));
|
|
9
7
|
export function repairsFor(checks, statuses) {
|
|
10
8
|
const repairs = [];
|
|
11
9
|
// A revoked-but-well-formed key passes the credential check and fails the call; both need login.
|
|
@@ -38,8 +36,8 @@ export function repairsFor(checks, statuses) {
|
|
|
38
36
|
action: 'connect',
|
|
39
37
|
harness: status.harness,
|
|
40
38
|
command: `tinyfish connect ${status.harness}`,
|
|
41
|
-
//
|
|
42
|
-
unattended_safe:
|
|
39
|
+
// Every harness takes a key, so a live one is the only gate.
|
|
40
|
+
unattended_safe: authCallPassed,
|
|
43
41
|
});
|
|
44
42
|
}
|
|
45
43
|
// connect hermes reinstalls the plugin; a second row would double it.
|
|
@@ -50,7 +48,7 @@ export function repairsFor(checks, statuses) {
|
|
|
50
48
|
action: 'connect',
|
|
51
49
|
harness: 'hermes',
|
|
52
50
|
command: 'tinyfish connect hermes',
|
|
53
|
-
unattended_safe:
|
|
51
|
+
unattended_safe: authCallPassed,
|
|
54
52
|
});
|
|
55
53
|
}
|
|
56
54
|
return repairs;
|
|
@@ -72,11 +70,20 @@ function runAuthLogin() {
|
|
|
72
70
|
}
|
|
73
71
|
}
|
|
74
72
|
/** Dispatches on `action`, never on a null harness: that conflation made the credential fix a no-op. */
|
|
75
|
-
async function runRepair(repair,
|
|
73
|
+
async function runRepair(repair, options, unattended) {
|
|
76
74
|
if (repair.action === 'auth-login')
|
|
77
75
|
return runAuthLogin();
|
|
78
|
-
if (repair.harness)
|
|
79
|
-
|
|
76
|
+
if (!repair.harness)
|
|
77
|
+
return;
|
|
78
|
+
await connectHarness(repair.harness, {
|
|
79
|
+
mcpUrl: options.mcpUrl,
|
|
80
|
+
launch: false,
|
|
81
|
+
// Unattended: refuse a keyless install, bound the spawns.
|
|
82
|
+
keyAuthOnly: unattended || undefined,
|
|
83
|
+
authTimeoutMs: unattended ? OAUTH_SIGN_IN_TIMEOUT_MS : undefined,
|
|
84
|
+
// Doctor is the CLI; reinstalling it to fix a harness is circular.
|
|
85
|
+
cliInstall: unattended ? false : undefined,
|
|
86
|
+
});
|
|
80
87
|
}
|
|
81
88
|
export async function applyRepairs(repairs, options) {
|
|
82
89
|
const interactive = detectHumanInitiated();
|
|
@@ -94,12 +101,14 @@ export async function applyRepairs(repairs, options) {
|
|
|
94
101
|
outcomes.push({
|
|
95
102
|
repair,
|
|
96
103
|
status: 'skipped',
|
|
97
|
-
reason:
|
|
104
|
+
reason: repair.action === 'auth-login'
|
|
105
|
+
? 'needs a browser sign-in, which has no unattended path'
|
|
106
|
+
: 'credential missing or failing; run tinyfish auth login',
|
|
98
107
|
});
|
|
99
108
|
continue;
|
|
100
109
|
}
|
|
101
110
|
try {
|
|
102
|
-
await runRepair(repair, options
|
|
111
|
+
await runRepair(repair, options, !interactive);
|
|
103
112
|
outcomes.push({ repair, status: 'repaired' });
|
|
104
113
|
}
|
|
105
114
|
catch (e) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Our skill-path slugs, inherited from the `skills` CLI era. */
|
|
2
2
|
export type SkillAgent = 'claude-code' | 'codex' | 'command-code' | 'cursor' | 'hermes-agent' | 'opencode' | 'pi';
|
|
3
3
|
export interface HarnessSupportCheck {
|
|
4
4
|
args: string[];
|
|
@@ -11,5 +11,7 @@ export type InstallRoot = {
|
|
|
11
11
|
*/
|
|
12
12
|
export declare function resolveInstallRoot(packageRoot: string, platform: typeof process.platform, onDisk?: (candidate: string) => boolean): InstallRoot | null;
|
|
13
13
|
export declare function installRoot(): InstallRoot | null;
|
|
14
|
+
/** This package's own dir under a prefix's node_modules. */
|
|
15
|
+
export declare function installedPackageDir(nodeModules: string): string;
|
|
14
16
|
/** The version on disk under a prefix, which is the only account of what `@latest` resolved to. */
|
|
15
17
|
export declare function readInstalledVersion(nodeModules: string): string | null;
|
package/dist/lib/install-root.js
CHANGED
|
@@ -37,10 +37,14 @@ export function installRoot() {
|
|
|
37
37
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
38
38
|
return resolveInstallRoot(packageRoot, process.platform);
|
|
39
39
|
}
|
|
40
|
+
/** This package's own dir under a prefix's node_modules. */
|
|
41
|
+
export function installedPackageDir(nodeModules) {
|
|
42
|
+
return path.join(nodeModules, ...TINYFISH_CLI_PACKAGE.split('/'));
|
|
43
|
+
}
|
|
40
44
|
/** The version on disk under a prefix, which is the only account of what `@latest` resolved to. */
|
|
41
45
|
export function readInstalledVersion(nodeModules) {
|
|
42
46
|
try {
|
|
43
|
-
const manifest = fs.readFileSync(path.join(nodeModules
|
|
47
|
+
const manifest = fs.readFileSync(path.join(installedPackageDir(nodeModules), 'package.json'), 'utf8');
|
|
44
48
|
return JSON.parse(manifest).version ?? null;
|
|
45
49
|
}
|
|
46
50
|
catch {
|
|
@@ -1,18 +1,8 @@
|
|
|
1
1
|
import { type InstallOptions } from './cli-install.js';
|
|
2
2
|
import { type SkillAgent } from './connect-clients.js';
|
|
3
|
-
export declare const SKILLS_CLI_PACKAGE = "skills@1.5.15";
|
|
4
3
|
export declare function installWebSkill(client: {
|
|
5
4
|
skillAgent?: SkillAgent;
|
|
6
5
|
displayName: string;
|
|
7
6
|
}, { verbose }: Pick<InstallOptions, 'verbose'>): void;
|
|
8
|
-
/**
|
|
9
|
-
* Refresh the skill we installed, for the harnesses we installed it in.
|
|
10
|
-
*
|
|
11
|
-
* Reinstalling beats `skills update`: it reports failure through the exit code instead of
|
|
12
|
-
* only in prose, it repairs a copy whose lock entry lost its hash, and it cannot spread the
|
|
13
|
-
* skill to agents the user never connected (`update` re-adds with no --agent, which targets
|
|
14
|
-
* every agent on the machine).
|
|
15
|
-
*
|
|
16
|
-
* Returns whether a skill was actually rewritten.
|
|
17
|
-
*/
|
|
7
|
+
/** Rewrite the vendored skill where connect recorded installs; true iff bytes changed. */
|
|
18
8
|
export declare function updateWebSkill({ verbose }: InstallOptions): boolean;
|
|
@@ -2,55 +2,13 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import spawn from 'cross-spawn';
|
|
4
4
|
import { loadConfig } from './auth.js';
|
|
5
|
-
import { captureStdio, capturedOutput, replay
|
|
5
|
+
import { captureStdio, capturedOutput, replay } from './cli-install.js';
|
|
6
6
|
import { CURSOR_SKILL_TARGET, NATIVE_MCP_CLIENTS, OPENCLAW, openclawSkillInstallArgs, } from './connect-clients.js';
|
|
7
7
|
import { ConnectInterruptedError, ConnectStepError, probeSupportVariant, throwIfInterrupted, } from './connect-runtime.js';
|
|
8
|
-
import {
|
|
8
|
+
import { WEB_SKILL_NAME } from './constants.js';
|
|
9
|
+
import { installedPackageDir, installRoot } from './install-root.js';
|
|
9
10
|
import { errLine } from './output.js';
|
|
10
|
-
import { skillTargetDir } from './skill-paths.js';
|
|
11
11
|
import { clearSkillLockEntry, writeWebSkill } from './skill-vendor.js';
|
|
12
|
-
// Supports Hermes without node:util.styleText, so the installer still runs on Node 20.11.
|
|
13
|
-
export const SKILLS_CLI_PACKAGE = 'skills@1.5.15';
|
|
14
|
-
const TINYFISH_WEB_SKILL_SOURCE = 'tinyfish-io/tinyfish-cookbook';
|
|
15
|
-
const TINYFISH_WEB_SKILL = 'use-tinyfish';
|
|
16
|
-
/** `skills add` is an unconditional overwrite, so it doubles as the refresh path. */
|
|
17
|
-
function skillAddArgs(skillAgents) {
|
|
18
|
-
return [
|
|
19
|
-
'-y',
|
|
20
|
-
SKILLS_CLI_PACKAGE,
|
|
21
|
-
'add',
|
|
22
|
-
TINYFISH_WEB_SKILL_SOURCE,
|
|
23
|
-
'--skill',
|
|
24
|
-
TINYFISH_WEB_SKILL,
|
|
25
|
-
'--global',
|
|
26
|
-
'--agent',
|
|
27
|
-
...skillAgents,
|
|
28
|
-
'--yes',
|
|
29
|
-
];
|
|
30
|
-
}
|
|
31
|
-
// npx resolves from PATH, which a global npm install may not have exported yet.
|
|
32
|
-
function skillSpawnEnv() {
|
|
33
|
-
return {
|
|
34
|
-
...process.env,
|
|
35
|
-
// `skills` gates its logo art on agent detection.
|
|
36
|
-
AI_AGENT: CLI_AGENT_IDENTITY,
|
|
37
|
-
PATH: `${path.dirname(process.execPath)}${path.delimiter}${process.env.PATH ?? ''}`,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
// The `skills` update flow exits 0 even when it fails, so status alone proves nothing.
|
|
41
|
-
// Safe to match on because SKILLS_CLI_PACKAGE is pinned.
|
|
42
|
-
const SKILL_UPDATE_FAILURE_PATTERN = /Failed to (?:update|check|fetch)/;
|
|
43
|
-
const SKILL_NOT_INSTALLED_PATTERN = /No installed skills found matching/;
|
|
44
|
-
// Nothing was rewritten, so nothing needs an agent restart.
|
|
45
|
-
const SKILL_ALREADY_CURRENT_PATTERN = /All global skills are up to date/;
|
|
46
|
-
// A lock entry with no recorded hash is untrackable, so `skills` reports it as skipped rather
|
|
47
|
-
// than failed. Left undetected that reads as "up to date" while nothing was refreshed.
|
|
48
|
-
const SKILL_UNCHECKABLE_PATTERN = /cannot be checked automatically/;
|
|
49
|
-
/** `add` exits 0 on per-agent failure, so the file it should have written is the verdict. */
|
|
50
|
-
function skillOnDisk(agent) {
|
|
51
|
-
// SKILL.md, not the dir: `skills` mkdirs before it copies, so a failed copy leaves one.
|
|
52
|
-
return fs.existsSync(path.join(skillTargetDir(agent), 'SKILL.md'));
|
|
53
|
-
}
|
|
54
12
|
export function installWebSkill(client, { verbose }) {
|
|
55
13
|
if (!client.skillAgent)
|
|
56
14
|
return;
|
|
@@ -58,16 +16,16 @@ export function installWebSkill(client, { verbose }) {
|
|
|
58
16
|
writeWebSkill([client.skillAgent]);
|
|
59
17
|
clearSkillLockEntry({ verbose });
|
|
60
18
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
*/
|
|
19
|
+
// The running process predates the install it just ran, so its own bundle is stale.
|
|
20
|
+
function freshBundleDir() {
|
|
21
|
+
const root = installRoot();
|
|
22
|
+
if (!root)
|
|
23
|
+
return undefined;
|
|
24
|
+
const dir = path.join(installedPackageDir(root.nodeModules), 'skill', WEB_SKILL_NAME);
|
|
25
|
+
// A failed or pre-bundle install has none; fall back to our own copy.
|
|
26
|
+
return fs.existsSync(path.join(dir, 'SKILL.md')) ? dir : undefined;
|
|
27
|
+
}
|
|
28
|
+
/** Rewrite the vendored skill where connect recorded installs; true iff bytes changed. */
|
|
71
29
|
export function updateWebSkill({ verbose }) {
|
|
72
30
|
// One entry per `tinyfish connect <client>`, persisted by PF-3169.
|
|
73
31
|
const connected = loadConfig().connect ?? {};
|
|
@@ -77,16 +35,17 @@ export function updateWebSkill({ verbose }) {
|
|
|
77
35
|
...(connected['cursor'] ? [CURSOR_SKILL_TARGET.skillAgent] : []),
|
|
78
36
|
].filter((agent) => agent !== undefined);
|
|
79
37
|
const hasOpenClaw = Boolean(connected['openclaw']);
|
|
80
|
-
//
|
|
81
|
-
// skill-less recorded install (Grok) may still have one from before, so refresh anyway
|
|
82
|
-
// — but there having been none is then expected, not an upgrade failure.
|
|
38
|
+
// No recorded skill install means no target dirs; connect is the install path.
|
|
83
39
|
if (skillAgents.length === 0 && !hasOpenClaw) {
|
|
84
|
-
|
|
40
|
+
errLine('No use-tinyfish skill to refresh. Run `tinyfish connect <client>` to install it.');
|
|
41
|
+
return false;
|
|
85
42
|
}
|
|
86
43
|
let refreshed = false;
|
|
87
44
|
if (skillAgents.length > 0) {
|
|
88
|
-
|
|
89
|
-
|
|
45
|
+
if (verbose)
|
|
46
|
+
errLine(`Refreshing the TinyFish web skill for ${skillAgents.join(', ')}...`);
|
|
47
|
+
refreshed = writeWebSkill(skillAgents, { sourceDir: freshBundleDir() });
|
|
48
|
+
clearSkillLockEntry({ verbose });
|
|
90
49
|
}
|
|
91
50
|
// OpenClaw keeps its skill under its own CLI, so it needs its own refresh.
|
|
92
51
|
if (hasOpenClaw)
|
|
@@ -131,59 +90,3 @@ function reinstallOpenClawSkill(verbose) {
|
|
|
131
90
|
}
|
|
132
91
|
return true;
|
|
133
92
|
}
|
|
134
|
-
function reinstallWebSkill(skillAgents, verbose) {
|
|
135
|
-
if (verbose)
|
|
136
|
-
errLine(`Refreshing the TinyFish web skill for ${skillAgents.join(', ')}...`);
|
|
137
|
-
const result = spawn.sync('npx', skillAddArgs(skillAgents), {
|
|
138
|
-
encoding: 'utf8',
|
|
139
|
-
env: skillSpawnEnv(),
|
|
140
|
-
maxBuffer: STEP_MAX_BUFFER,
|
|
141
|
-
timeout: SKILL_INSTALL_TIMEOUT_MS,
|
|
142
|
-
});
|
|
143
|
-
const output = capturedOutput(result);
|
|
144
|
-
// Quiet capture stays (#4434); the on-disk check replaced prose matching.
|
|
145
|
-
const addFailed = Boolean(result.error) || result.status !== 0;
|
|
146
|
-
const missing = addFailed ? undefined : skillAgents.find((agent) => !skillOnDisk(agent));
|
|
147
|
-
const failed = addFailed || missing !== undefined;
|
|
148
|
-
if (failed)
|
|
149
|
-
throwIfInterrupted(result);
|
|
150
|
-
if (verbose || failed)
|
|
151
|
-
replay(output);
|
|
152
|
-
if (failed) {
|
|
153
|
-
// Upgrade telemetry carries no detail, so the agent has to ride the message.
|
|
154
|
-
const scope = missing ? ` for ${missing}` : '';
|
|
155
|
-
throw new Error(`Could not refresh the TinyFish web skill${scope}`, { cause: result.error });
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
/** Fallback when no skill-bearing harness is recorded: refresh whatever `skills` tracks. */
|
|
159
|
-
function refreshWebSkillByHash(verbose, untrackedIsExpected = false) {
|
|
160
|
-
if (verbose)
|
|
161
|
-
errLine('Refreshing the TinyFish web skill...');
|
|
162
|
-
const result = spawn.sync('npx', ['-y', SKILLS_CLI_PACKAGE, 'update', TINYFISH_WEB_SKILL, '--global', '--yes'], {
|
|
163
|
-
encoding: 'utf8',
|
|
164
|
-
env: skillSpawnEnv(),
|
|
165
|
-
maxBuffer: STEP_MAX_BUFFER,
|
|
166
|
-
timeout: SKILL_INSTALL_TIMEOUT_MS,
|
|
167
|
-
});
|
|
168
|
-
// Piped so prose-only failures can be matched; skills exits 0 anyway.
|
|
169
|
-
const output = capturedOutput(result);
|
|
170
|
-
const unchecked = SKILL_UNCHECKABLE_PATTERN.test(output);
|
|
171
|
-
const failed = Boolean(result.error) || result.status !== 0 || SKILL_UPDATE_FAILURE_PATTERN.test(output);
|
|
172
|
-
if (failed)
|
|
173
|
-
throwIfInterrupted(result);
|
|
174
|
-
if (verbose || failed || unchecked)
|
|
175
|
-
replay(output);
|
|
176
|
-
if (failed)
|
|
177
|
-
throw new Error('Could not refresh the TinyFish web skill', { cause: result.error });
|
|
178
|
-
if (unchecked) {
|
|
179
|
-
errLine('Run `tinyfish connect <client>` to reinstall the skill and restore update tracking.');
|
|
180
|
-
if (untrackedIsExpected)
|
|
181
|
-
return false;
|
|
182
|
-
throw new Error('The TinyFish web skill cannot be checked for updates');
|
|
183
|
-
}
|
|
184
|
-
if (SKILL_NOT_INSTALLED_PATTERN.test(output)) {
|
|
185
|
-
errLine('No use-tinyfish skill installed. Run `tinyfish connect <client>` to add it.');
|
|
186
|
-
return false;
|
|
187
|
-
}
|
|
188
|
-
return !SKILL_ALREADY_CURRENT_PATTERN.test(output);
|
|
189
|
-
}
|