@tiny-fish/cli 0.11.1-next.132 → 0.12.1-next.135
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/README.md +20 -0
- package/dist/commands/connect.d.ts +14 -0
- package/dist/commands/connect.js +126 -12
- package/dist/commands/upgrade.d.ts +3 -0
- package/dist/commands/upgrade.js +46 -0
- package/dist/index.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -64,6 +64,26 @@ npx -y @tiny-fish/cli@latest connect openclaw --launch
|
|
|
64
64
|
OpenClaw must already be installed. The command installs the TinyFish skill for all local OpenClaw
|
|
65
65
|
agents and guides you through Search, Fetch, and Agent one step at a time.
|
|
66
66
|
|
|
67
|
+
## Upgrading
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
tinyfish upgrade
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Brings both halves of an install current: the global `@tiny-fish/cli` package and the global
|
|
74
|
+
`use-tinyfish` skill. Use this rather than remembering `npm i -g @tiny-fish/cli@latest` and
|
|
75
|
+
`skills update -g` separately.
|
|
76
|
+
|
|
77
|
+
The skill step refreshes only the TinyFish skill, and only for the agents you ran
|
|
78
|
+
`tinyfish connect` against. Other global skills, and agents you never connected, are left alone.
|
|
79
|
+
The refresh overwrites your copy of the skill, including any local edits to it. Restart your
|
|
80
|
+
agent afterward to pick it up.
|
|
81
|
+
|
|
82
|
+
If you never ran `tinyfish connect` on this machine, there is no record of which agent to
|
|
83
|
+
refresh, so the command falls back to updating a tracked `use-tinyfish` skill if one exists. When
|
|
84
|
+
it reports that the skill is missing or cannot be checked, run `tinyfish connect <client>` to
|
|
85
|
+
reinstall it.
|
|
86
|
+
|
|
67
87
|
## Authentication
|
|
68
88
|
|
|
69
89
|
Get your API key from [agent.tinyfish.ai](https://agent.tinyfish.ai).
|
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
export declare const UPGRADE_HINT = "Run `tinyfish upgrade` any time to update the CLI and skill.";
|
|
2
3
|
export declare const DEFAULT_ONBOARDING_PROMPT: string;
|
|
3
4
|
export declare const OPENCLAW_ONBOARDING_PROMPT: string;
|
|
4
5
|
export type AgentClient = "claude-code" | "codex" | "hermes" | "openclaw";
|
|
6
|
+
/** Ctrl+C/SIGTERM killed a setup child — abandonment, not error. */
|
|
7
|
+
export declare class ConnectInterruptedError extends Error {
|
|
8
|
+
}
|
|
9
|
+
export declare function installTinyFishCli(): void;
|
|
10
|
+
/**
|
|
11
|
+
* Refresh the skill we installed, for the harnesses we installed it in.
|
|
12
|
+
*
|
|
13
|
+
* Reinstalling beats `skills update`: it reports failure through the exit code instead of
|
|
14
|
+
* only in prose, it repairs a copy whose lock entry lost its hash, and it cannot spread the
|
|
15
|
+
* skill to agents the user never connected (`update` re-adds with no --agent, which targets
|
|
16
|
+
* every agent on the machine).
|
|
17
|
+
*/
|
|
18
|
+
export declare function updateWebSkill(): void;
|
|
5
19
|
export declare function connectClaudeCode(options: {
|
|
6
20
|
apiKey?: string;
|
|
7
21
|
mcpUrl: string;
|
package/dist/commands/connect.js
CHANGED
|
@@ -20,11 +20,21 @@ const OPENCLAW_SKILL_UNAVAILABLE_MESSAGE = "This OpenClaw installation does not
|
|
|
20
20
|
"retry.";
|
|
21
21
|
const TINYFISH_CLI_NOT_FOUND_MESSAGE = "TinyFish CLI installed but is not available on PATH. Open a new terminal and retry.";
|
|
22
22
|
const TINYFISH_CLI_PACKAGE = "@tiny-fish/cli@latest";
|
|
23
|
+
export const UPGRADE_HINT = "Run `tinyfish upgrade` any time to update the CLI and skill.";
|
|
23
24
|
// Supports Hermes without node:util.styleText, so the installer still runs on Node 20.11.
|
|
24
25
|
const SKILLS_CLI_PACKAGE = "skills@1.5.15";
|
|
25
26
|
const TINYFISH_WEB_SKILL_SOURCE = "tinyfish-io/tinyfish-cookbook";
|
|
26
27
|
const TINYFISH_WEB_SKILL = "use-tinyfish";
|
|
27
28
|
const OPENCLAW_SKILL = "@tinyfish/tinyfish";
|
|
29
|
+
// --force makes this an unconditional overwrite, so the same call installs and refreshes.
|
|
30
|
+
const OPENCLAW_SKILL_INSTALL_ARGS = [
|
|
31
|
+
"skills",
|
|
32
|
+
"install",
|
|
33
|
+
OPENCLAW_SKILL,
|
|
34
|
+
"--global",
|
|
35
|
+
"--force",
|
|
36
|
+
"--acknowledge-clawhub-risk",
|
|
37
|
+
];
|
|
28
38
|
export const DEFAULT_ONBOARDING_PROMPT = "I just connected TinyFish. Call the guide_next_step tool now to begin. Then follow its " +
|
|
29
39
|
"instructions one step at a time. Ask for my input and wait for my reply before each subsequent " +
|
|
30
40
|
"TinyFish tool call.";
|
|
@@ -33,9 +43,13 @@ export const OPENCLAW_ONBOARDING_PROMPT = "I just installed the TinyFish skill.
|
|
|
33
43
|
"which result I want to read, and wait before using TinyFish Fetch. Then ask what browser task " +
|
|
34
44
|
"I want to complete and wait before using TinyFish Agent. Never skip ahead or choose for me.";
|
|
35
45
|
/** Ctrl+C/SIGTERM killed a setup child — abandonment, not error. */
|
|
36
|
-
class ConnectInterruptedError extends Error {
|
|
46
|
+
export class ConnectInterruptedError extends Error {
|
|
37
47
|
}
|
|
38
48
|
function throwIfInterrupted(result) {
|
|
49
|
+
// spawn.sync kills a timed-out child with SIGTERM, so signal alone would misread a slow
|
|
50
|
+
// network as the user walking away. A timeout is a failure and must report as one.
|
|
51
|
+
if (result.error?.code === "ETIMEDOUT")
|
|
52
|
+
return;
|
|
39
53
|
if (result.signal === "SIGINT" || result.signal === "SIGTERM") {
|
|
40
54
|
throw new ConnectInterruptedError("Setup interrupted");
|
|
41
55
|
}
|
|
@@ -314,7 +328,7 @@ function requireCommandSupport(client) {
|
|
|
314
328
|
function commandNotFound(error) {
|
|
315
329
|
return error?.code === "ENOENT";
|
|
316
330
|
}
|
|
317
|
-
function installTinyFishCli() {
|
|
331
|
+
export function installTinyFishCli() {
|
|
318
332
|
errLine("Installing the TinyFish CLI...");
|
|
319
333
|
const result = spawn.sync("npm", ["install", "--global", TINYFISH_CLI_PACKAGE], {
|
|
320
334
|
stdio: "inherit",
|
|
@@ -325,9 +339,9 @@ function installTinyFishCli() {
|
|
|
325
339
|
throw new Error("Could not install the TinyFish CLI", { cause: result.error });
|
|
326
340
|
}
|
|
327
341
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
342
|
+
/** `skills add` is an unconditional overwrite, so it doubles as the refresh path. */
|
|
343
|
+
function skillAddArgs(skillAgents) {
|
|
344
|
+
return [
|
|
331
345
|
"-y",
|
|
332
346
|
SKILLS_CLI_PACKAGE,
|
|
333
347
|
"add",
|
|
@@ -336,13 +350,21 @@ function installWebSkill(client) {
|
|
|
336
350
|
TINYFISH_WEB_SKILL,
|
|
337
351
|
"--global",
|
|
338
352
|
"--agent",
|
|
339
|
-
|
|
353
|
+
...skillAgents,
|
|
340
354
|
"--yes",
|
|
341
|
-
]
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
355
|
+
];
|
|
356
|
+
}
|
|
357
|
+
// npx resolves from PATH, which a global npm install may not have exported yet.
|
|
358
|
+
function skillSpawnEnv() {
|
|
359
|
+
return {
|
|
360
|
+
...process.env,
|
|
361
|
+
PATH: `${path.dirname(process.execPath)}${path.delimiter}${process.env.PATH ?? ""}`,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
function installWebSkill(client) {
|
|
365
|
+
errLine(`Installing the TinyFish web skill in ${client.displayName}...`);
|
|
366
|
+
const result = spawn.sync("npx", skillAddArgs([client.skillAgent]), {
|
|
367
|
+
env: skillSpawnEnv(),
|
|
346
368
|
stdio: "inherit",
|
|
347
369
|
timeout: SKILL_INSTALL_TIMEOUT_MS,
|
|
348
370
|
});
|
|
@@ -353,6 +375,94 @@ function installWebSkill(client) {
|
|
|
353
375
|
});
|
|
354
376
|
}
|
|
355
377
|
}
|
|
378
|
+
// The `skills` update flow exits 0 even when it fails, so status alone proves nothing.
|
|
379
|
+
// Safe to match on because SKILLS_CLI_PACKAGE is pinned.
|
|
380
|
+
const SKILL_UPDATE_FAILURE_PATTERN = /Failed to (?:update|check|fetch)/;
|
|
381
|
+
const SKILL_NOT_INSTALLED_PATTERN = /No installed skills found matching/;
|
|
382
|
+
// A lock entry with no recorded hash is untrackable, so `skills` reports it as skipped rather
|
|
383
|
+
// than failed. Left undetected that reads as "up to date" while nothing was refreshed.
|
|
384
|
+
const SKILL_UNCHECKABLE_PATTERN = /cannot be checked automatically/;
|
|
385
|
+
// `skills add` exits non-zero on a fatal error, but per-skill install failures only print.
|
|
386
|
+
const SKILL_INSTALL_FAILURE_PATTERN = /Failed to install/;
|
|
387
|
+
/**
|
|
388
|
+
* Refresh the skill we installed, for the harnesses we installed it in.
|
|
389
|
+
*
|
|
390
|
+
* Reinstalling beats `skills update`: it reports failure through the exit code instead of
|
|
391
|
+
* only in prose, it repairs a copy whose lock entry lost its hash, and it cannot spread the
|
|
392
|
+
* skill to agents the user never connected (`update` re-adds with no --agent, which targets
|
|
393
|
+
* every agent on the machine).
|
|
394
|
+
*/
|
|
395
|
+
export function updateWebSkill() {
|
|
396
|
+
// One entry per `tinyfish connect <client>`, persisted by PF-3169.
|
|
397
|
+
const connected = loadConfig().connect ?? {};
|
|
398
|
+
const skillAgents = [CLAUDE_CODE, CODEX, HERMES]
|
|
399
|
+
.filter((client) => connected[client.connectClient])
|
|
400
|
+
.map((client) => client.skillAgent);
|
|
401
|
+
const hasOpenClaw = Boolean(connected["openclaw"]);
|
|
402
|
+
// Installs predating PF-3169 recorded nothing; ask `skills` to refresh what it tracks.
|
|
403
|
+
if (skillAgents.length === 0 && !hasOpenClaw) {
|
|
404
|
+
refreshWebSkillByHash();
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (skillAgents.length > 0)
|
|
408
|
+
reinstallWebSkill(skillAgents);
|
|
409
|
+
// OpenClaw keeps its skill under its own CLI, so it needs its own refresh.
|
|
410
|
+
if (hasOpenClaw)
|
|
411
|
+
reinstallOpenClawSkill();
|
|
412
|
+
}
|
|
413
|
+
function reinstallOpenClawSkill() {
|
|
414
|
+
errLine("Refreshing the TinyFish skill in OpenClaw...");
|
|
415
|
+
const result = spawn.sync("openclaw", OPENCLAW_SKILL_INSTALL_ARGS, { stdio: "inherit" });
|
|
416
|
+
// OpenClaw may have been removed since connect; a stale context entry must not fail an
|
|
417
|
+
// upgrade that otherwise succeeded.
|
|
418
|
+
if (commandNotFound(result.error)) {
|
|
419
|
+
errLine("OpenClaw is not on PATH, so its TinyFish skill was left unchanged.");
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
if (result.error || result.status !== 0) {
|
|
423
|
+
throwIfInterrupted(result);
|
|
424
|
+
throw new Error("Could not refresh the TinyFish skill in OpenClaw", { cause: result.error });
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
function reinstallWebSkill(skillAgents) {
|
|
428
|
+
errLine(`Refreshing the TinyFish web skill for ${skillAgents.join(", ")}...`);
|
|
429
|
+
const result = spawn.sync("npx", skillAddArgs(skillAgents), {
|
|
430
|
+
encoding: "utf8",
|
|
431
|
+
env: skillSpawnEnv(),
|
|
432
|
+
timeout: SKILL_INSTALL_TIMEOUT_MS,
|
|
433
|
+
});
|
|
434
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
435
|
+
if (output.trim())
|
|
436
|
+
errLine(output.trimEnd());
|
|
437
|
+
if (result.error || result.status !== 0 || SKILL_INSTALL_FAILURE_PATTERN.test(output)) {
|
|
438
|
+
throwIfInterrupted(result);
|
|
439
|
+
throw new Error("Could not refresh the TinyFish web skill", { cause: result.error });
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
/** Fallback when no harness is recorded: ask `skills` to refresh whatever it tracks. */
|
|
443
|
+
function refreshWebSkillByHash() {
|
|
444
|
+
errLine("Refreshing the TinyFish web skill...");
|
|
445
|
+
const result = spawn.sync("npx", ["-y", SKILLS_CLI_PACKAGE, "update", TINYFISH_WEB_SKILL, "--global", "--yes"], {
|
|
446
|
+
encoding: "utf8",
|
|
447
|
+
env: skillSpawnEnv(),
|
|
448
|
+
timeout: SKILL_INSTALL_TIMEOUT_MS,
|
|
449
|
+
});
|
|
450
|
+
// Piped rather than inherited so the output can be inspected; echoed back for the user.
|
|
451
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
452
|
+
if (output.trim())
|
|
453
|
+
errLine(output.trimEnd());
|
|
454
|
+
if (result.error || result.status !== 0 || SKILL_UPDATE_FAILURE_PATTERN.test(output)) {
|
|
455
|
+
throwIfInterrupted(result);
|
|
456
|
+
throw new Error("Could not refresh the TinyFish web skill", { cause: result.error });
|
|
457
|
+
}
|
|
458
|
+
if (SKILL_UNCHECKABLE_PATTERN.test(output)) {
|
|
459
|
+
errLine("Run `tinyfish connect <client>` to reinstall the skill and restore update tracking.");
|
|
460
|
+
throw new Error("The TinyFish web skill cannot be checked for updates");
|
|
461
|
+
}
|
|
462
|
+
if (SKILL_NOT_INSTALLED_PATTERN.test(output)) {
|
|
463
|
+
errLine("No use-tinyfish skill installed. Run `tinyfish connect <client>` to add it.");
|
|
464
|
+
}
|
|
465
|
+
}
|
|
356
466
|
function ensureCliAuthenticated(source, apiKey) {
|
|
357
467
|
const envKey = apiKey ?? process.env["TINYFISH_API_KEY"];
|
|
358
468
|
if (envKey) {
|
|
@@ -487,6 +597,7 @@ async function connectNativeMcpClient(client, options) {
|
|
|
487
597
|
// Persist once here (skill installed + authenticated) so both success paths, launch
|
|
488
598
|
// or not, record the install; buildConnectHeaders reads it on subsequent requests.
|
|
489
599
|
saveConnectContext(client.connectClient, telemetry.attemptId);
|
|
600
|
+
errLine(UPGRADE_HINT);
|
|
490
601
|
if (!options.launch) {
|
|
491
602
|
errLine(`TinyFish is connected. Open ${client.displayName} to start using it.`);
|
|
492
603
|
settle(state, telemetry, "completed");
|
|
@@ -529,7 +640,9 @@ export async function connectOpenClaw(options) {
|
|
|
529
640
|
}
|
|
530
641
|
state.stage = "skill_install";
|
|
531
642
|
errLine("Installing the TinyFish skill in OpenClaw...");
|
|
532
|
-
const skillInstallResult = spawn.sync("openclaw",
|
|
643
|
+
const skillInstallResult = spawn.sync("openclaw", OPENCLAW_SKILL_INSTALL_ARGS, {
|
|
644
|
+
stdio: "inherit",
|
|
645
|
+
});
|
|
533
646
|
if (skillInstallResult.error || skillInstallResult.status !== 0) {
|
|
534
647
|
throwIfInterrupted(skillInstallResult);
|
|
535
648
|
throw new Error("Could not install the TinyFish skill in OpenClaw", {
|
|
@@ -562,6 +675,7 @@ export async function connectOpenClaw(options) {
|
|
|
562
675
|
}
|
|
563
676
|
telemetry.track("checkpoint", { phase: "authenticated" });
|
|
564
677
|
saveConnectContext("openclaw", telemetry.attemptId);
|
|
678
|
+
errLine(UPGRADE_HINT);
|
|
565
679
|
if (!options.launch) {
|
|
566
680
|
errLine("TinyFish is connected. Open OpenClaw to start using it.");
|
|
567
681
|
settle(state, telemetry, "completed");
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { errLine } from "../lib/output.js";
|
|
2
|
+
import { ConnectInterruptedError, installTinyFishCli, updateWebSkill } from "./connect.js";
|
|
3
|
+
const INTERRUPTED_EXIT_CODE = 130;
|
|
4
|
+
function attempt(label, run) {
|
|
5
|
+
try {
|
|
6
|
+
run();
|
|
7
|
+
errLine(`✓ ${label}: done`);
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
// Ctrl+C is abandonment, not failure — neither step should be reported as broken.
|
|
12
|
+
if (error instanceof ConnectInterruptedError)
|
|
13
|
+
throw error;
|
|
14
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
15
|
+
errLine(`✗ ${label}: ${message}`);
|
|
16
|
+
return message;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function runUpgrade() {
|
|
20
|
+
errLine("Upgrading TinyFish...");
|
|
21
|
+
try {
|
|
22
|
+
// Both steps always run: neither artifact should stay stale because the other failed.
|
|
23
|
+
const failures = [
|
|
24
|
+
attempt("update the TinyFish CLI", installTinyFishCli),
|
|
25
|
+
attempt("refresh the TinyFish web skill", updateWebSkill),
|
|
26
|
+
].filter((failure) => failure !== undefined);
|
|
27
|
+
if (failures.length > 0)
|
|
28
|
+
throw new Error(`tinyfish upgrade failed: ${failures.join("; ")}`);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (!(error instanceof ConnectInterruptedError))
|
|
32
|
+
throw error;
|
|
33
|
+
errLine("Upgrade interrupted — run `tinyfish upgrade` again to finish.");
|
|
34
|
+
process.exitCode = INTERRUPTED_EXIT_CODE;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
errLine("TinyFish is up to date. Restart your agent to pick up the refreshed skill.");
|
|
38
|
+
}
|
|
39
|
+
export function registerUpgrade(program) {
|
|
40
|
+
program
|
|
41
|
+
.command("upgrade")
|
|
42
|
+
.description("Update the TinyFish CLI and the use-tinyfish skill to the latest versions")
|
|
43
|
+
.action(() => {
|
|
44
|
+
runUpgrade();
|
|
45
|
+
});
|
|
46
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { registerRuns } from "./commands/runs.js";
|
|
|
12
12
|
import { registerConfigureClaude } from "./commands/config-claude.js";
|
|
13
13
|
import { registerSearch } from "./commands/search.js";
|
|
14
14
|
import { registerConnect } from "./commands/connect.js";
|
|
15
|
+
import { registerUpgrade } from "./commands/upgrade.js";
|
|
15
16
|
import { registerOnboard } from "./commands/onboard.js";
|
|
16
17
|
const { version } = createRequire(import.meta.url)("../package.json");
|
|
17
18
|
const program = new Command();
|
|
@@ -31,6 +32,7 @@ program
|
|
|
31
32
|
registerAuth(program);
|
|
32
33
|
registerOnboard(program);
|
|
33
34
|
registerConnect(program);
|
|
35
|
+
registerUpgrade(program);
|
|
34
36
|
registerConfigureClaude(program);
|
|
35
37
|
registerBrowser(program);
|
|
36
38
|
registerProfile(program);
|