@tiny-fish/cli 0.30.2-next.256 → 0.30.2-next.258
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/auth.d.ts +1 -1
- package/dist/commands/auth.js +52 -52
- package/dist/commands/batch.d.ts +1 -1
- package/dist/commands/batch.js +54 -46
- package/dist/commands/browser.d.ts +1 -1
- package/dist/commands/browser.js +14 -14
- package/dist/commands/config-claude.d.ts +1 -1
- package/dist/commands/config-claude.js +22 -22
- package/dist/commands/connect.d.ts +3 -3
- package/dist/commands/connect.js +150 -150
- package/dist/commands/doctor.d.ts +4 -4
- package/dist/commands/doctor.js +104 -104
- package/dist/commands/fetch.d.ts +1 -1
- package/dist/commands/fetch.js +24 -27
- package/dist/commands/onboard.d.ts +1 -1
- package/dist/commands/onboard.js +50 -50
- package/dist/commands/profile.d.ts +3 -3
- package/dist/commands/profile.js +45 -45
- package/dist/commands/run.d.ts +1 -1
- package/dist/commands/run.js +78 -65
- package/dist/commands/runs.d.ts +1 -1
- package/dist/commands/runs.js +63 -50
- package/dist/commands/search.d.ts +1 -1
- package/dist/commands/search.js +16 -16
- package/dist/commands/upgrade.d.ts +2 -2
- package/dist/commands/upgrade.js +36 -36
- package/dist/commands/vault.d.ts +1 -1
- package/dist/commands/vault.js +48 -45
- package/dist/commands/wallet.d.ts +1 -1
- package/dist/commands/wallet.js +14 -14
- package/dist/index.js +2 -2
- package/dist/lib/auth.d.ts +5 -5
- package/dist/lib/auth.js +56 -56
- package/dist/lib/batch-store.d.ts +1 -1
- package/dist/lib/batch-store.js +4 -4
- package/dist/lib/chrome-cookies.d.ts +1 -1
- package/dist/lib/chrome-cookies.js +24 -24
- package/dist/lib/claude-config.js +37 -37
- package/dist/lib/cli-install.js +12 -12
- package/dist/lib/client.d.ts +2 -2
- package/dist/lib/client.js +61 -61
- package/dist/lib/connect-all.js +140 -134
- package/dist/lib/connect-auth.d.ts +2 -2
- package/dist/lib/connect-auth.js +15 -15
- package/dist/lib/connect-clients.d.ts +6 -6
- package/dist/lib/connect-clients.js +143 -143
- package/dist/lib/connect-fallback.d.ts +1 -1
- package/dist/lib/connect-fallback.js +38 -38
- package/dist/lib/connect-picker.d.ts +4 -4
- package/dist/lib/connect-picker.js +28 -28
- package/dist/lib/connect-runtime.d.ts +7 -7
- package/dist/lib/connect-runtime.js +61 -61
- package/dist/lib/constants.js +13 -13
- package/dist/lib/cursor-config.d.ts +1 -1
- package/dist/lib/cursor-config.js +30 -30
- package/dist/lib/doctor-report.d.ts +2 -2
- package/dist/lib/doctor-report.js +22 -22
- package/dist/lib/harness-detect.js +39 -39
- package/dist/lib/harness.js +9 -9
- package/dist/lib/hermes-env.js +26 -23
- package/dist/lib/install-root.js +12 -12
- package/dist/lib/notice.d.ts +1 -1
- package/dist/lib/notice.js +12 -12
- package/dist/lib/output.js +14 -14
- package/dist/lib/registration-detect.d.ts +3 -3
- package/dist/lib/registration-detect.js +82 -82
- package/dist/lib/setup-telemetry.d.ts +4 -4
- package/dist/lib/setup-telemetry.js +34 -34
- package/dist/lib/signals.js +1 -1
- package/dist/lib/skill-install.d.ts +3 -3
- package/dist/lib/skill-install.js +40 -40
- package/dist/lib/types.d.ts +8 -8
- package/dist/lib/verify.d.ts +1 -1
- package/dist/lib/verify.js +28 -28
- package/dist/program.d.ts +1 -1
- package/dist/program.js +28 -28
- package/package.json +18 -9
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { createHash } from
|
|
2
|
-
import { z } from
|
|
3
|
-
import { validateKeyFormat } from
|
|
4
|
-
import { BASE_URL, CLI_VERSION } from
|
|
5
|
-
import { ALL_HARNESSES } from
|
|
6
|
-
import { captureNotice } from
|
|
7
|
-
import { errLine } from
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { validateKeyFormat } from './auth.js';
|
|
4
|
+
import { BASE_URL, CLI_VERSION } from './constants.js';
|
|
5
|
+
import { ALL_HARNESSES } from './harness-detect.js';
|
|
6
|
+
import { captureNotice } from './notice.js';
|
|
7
|
+
import { errLine } from './output.js';
|
|
8
8
|
const CONNECT_EVENT_TIMEOUT_MS = 2_000;
|
|
9
9
|
/**
|
|
10
10
|
* `TINYFISH_NO_TELEMETRY` suppresses ALL CLI-emitted network analytics
|
|
@@ -12,13 +12,13 @@ const CONNECT_EVENT_TIMEOUT_MS = 2_000;
|
|
|
12
12
|
* opts out — a privacy switch must not fail closed on `true`.
|
|
13
13
|
*/
|
|
14
14
|
export function telemetryDisabled() {
|
|
15
|
-
const value = (process.env[
|
|
16
|
-
return value !==
|
|
15
|
+
const value = (process.env['TINYFISH_NO_TELEMETRY'] ?? '').trim().toLowerCase();
|
|
16
|
+
return value !== '' && value !== '0' && value !== 'false';
|
|
17
17
|
}
|
|
18
18
|
// TinyFish-owned domains plus loopback are the only origins the API key may be sent to.
|
|
19
19
|
// `--url` accepts any http(s) URL, so a user-supplied origin must never receive the key.
|
|
20
|
-
const TRUSTED_TELEMETRY_HOST_SUFFIXES = [
|
|
21
|
-
const TRUSTED_TELEMETRY_LOOPBACK_HOSTS = [
|
|
20
|
+
const TRUSTED_TELEMETRY_HOST_SUFFIXES = ['.tinyfish.ai', '.tinyfish.io'];
|
|
21
|
+
const TRUSTED_TELEMETRY_LOOPBACK_HOSTS = ['localhost', '127.0.0.1', '[::1]'];
|
|
22
22
|
export function isTrustedTelemetryOrigin(endpoint) {
|
|
23
23
|
let url;
|
|
24
24
|
try {
|
|
@@ -32,15 +32,15 @@ export function isTrustedTelemetryOrigin(endpoint) {
|
|
|
32
32
|
// key is never sent over cleartext.
|
|
33
33
|
if (TRUSTED_TELEMETRY_LOOPBACK_HOSTS.includes(host))
|
|
34
34
|
return true;
|
|
35
|
-
if (url.protocol !==
|
|
35
|
+
if (url.protocol !== 'https:')
|
|
36
36
|
return false;
|
|
37
37
|
return TRUSTED_TELEMETRY_HOST_SUFFIXES.some((suffix) => host === suffix.slice(1) || host.endsWith(suffix));
|
|
38
38
|
}
|
|
39
39
|
/** SHA-256 lookup key only, and only to trusted origins — the raw key never rides telemetry. */
|
|
40
40
|
export function telemetryHeaders(endpoint, apiKey) {
|
|
41
|
-
const headers = {
|
|
41
|
+
const headers = { 'content-type': 'application/json' };
|
|
42
42
|
if (apiKey && validateKeyFormat(apiKey) && isTrustedTelemetryOrigin(endpoint)) {
|
|
43
|
-
headers[
|
|
43
|
+
headers['x-api-key-lookup'] = createHash('sha256').update(apiKey).digest('hex');
|
|
44
44
|
}
|
|
45
45
|
return headers;
|
|
46
46
|
}
|
|
@@ -56,7 +56,7 @@ export async function postConnectEvent(options) {
|
|
|
56
56
|
return;
|
|
57
57
|
// Swallows its own write failure so callers can treat this function as total.
|
|
58
58
|
const debug = (line) => {
|
|
59
|
-
if (!process.env[
|
|
59
|
+
if (!process.env['TINYFISH_DEBUG'])
|
|
60
60
|
return;
|
|
61
61
|
try {
|
|
62
62
|
errLine(`${label} telemetry ${line}`);
|
|
@@ -70,10 +70,10 @@ export async function postConnectEvent(options) {
|
|
|
70
70
|
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
71
71
|
try {
|
|
72
72
|
const response = await fetch(endpoint, {
|
|
73
|
-
method:
|
|
73
|
+
method: 'POST',
|
|
74
74
|
headers,
|
|
75
75
|
body,
|
|
76
|
-
redirect:
|
|
76
|
+
redirect: 'error',
|
|
77
77
|
signal: AbortSignal.timeout(CONNECT_EVENT_TIMEOUT_MS),
|
|
78
78
|
});
|
|
79
79
|
// Before the ok branch so a rejected event still yields a notice; gated on origin so a
|
|
@@ -98,7 +98,7 @@ const harnessResultSchema = z.object({
|
|
|
98
98
|
harness: z.enum(ALL_HARNESSES),
|
|
99
99
|
detected: z.boolean(),
|
|
100
100
|
installed: z.boolean(),
|
|
101
|
-
verify_depth: z.enum([
|
|
101
|
+
verify_depth: z.enum(['health', 'health+auth']).nullable(),
|
|
102
102
|
verify_ok: z.boolean().nullable(),
|
|
103
103
|
});
|
|
104
104
|
export const setupCompletedPayloadSchema = z.object({
|
|
@@ -106,25 +106,25 @@ export const setupCompletedPayloadSchema = z.object({
|
|
|
106
106
|
.array(harnessResultSchema)
|
|
107
107
|
.length(ALL_HARNESSES.length)
|
|
108
108
|
.refine((results) => new Set(results.map((r) => r.harness)).size === ALL_HARNESSES.length, {
|
|
109
|
-
message:
|
|
109
|
+
message: 'expected one result per harness',
|
|
110
110
|
}),
|
|
111
111
|
cli_version: z.string(),
|
|
112
112
|
// Segments picker runs from --all runs; the server field shipped first.
|
|
113
|
-
mode: z.enum([
|
|
113
|
+
mode: z.enum(['all', 'pick', 'pick_numeric']).optional(),
|
|
114
114
|
});
|
|
115
115
|
/** `toVersion` is null when the run was interrupted or the installed version could not be read. */
|
|
116
116
|
export async function sendUpgradeCompleted(fromVersion, toVersion, outcome, scope, apiKey) {
|
|
117
117
|
await postConnectEvent({
|
|
118
|
-
endpoint: new URL(
|
|
118
|
+
endpoint: new URL('/api/cli/connect-event', BASE_URL).toString(),
|
|
119
119
|
body: JSON.stringify({
|
|
120
|
-
event:
|
|
120
|
+
event: 'upgrade_completed',
|
|
121
121
|
from_version: fromVersion,
|
|
122
122
|
to_version: toVersion,
|
|
123
123
|
outcome,
|
|
124
124
|
scope,
|
|
125
125
|
}),
|
|
126
126
|
attempts: 1,
|
|
127
|
-
label:
|
|
127
|
+
label: 'upgrade',
|
|
128
128
|
apiKey,
|
|
129
129
|
});
|
|
130
130
|
}
|
|
@@ -141,18 +141,18 @@ export async function sendSetupCompleted(mcpUrl, harnesses, apiKey, mode) {
|
|
|
141
141
|
cli_version: CLI_VERSION,
|
|
142
142
|
...(mode ? { mode } : {}),
|
|
143
143
|
});
|
|
144
|
-
endpoint = new URL(
|
|
145
|
-
body = JSON.stringify({ event:
|
|
144
|
+
endpoint = new URL('/api/cli/connect-event', mcpUrl).toString();
|
|
145
|
+
body = JSON.stringify({ event: 'setup_completed', ...payload });
|
|
146
146
|
}
|
|
147
147
|
catch (e) {
|
|
148
|
-
if (process.env[
|
|
148
|
+
if (process.env['TINYFISH_DEBUG']) {
|
|
149
149
|
errLine(`setup telemetry skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
150
150
|
}
|
|
151
151
|
return;
|
|
152
152
|
}
|
|
153
|
-
await postConnectEvent({ endpoint, body, attempts: 2, label:
|
|
153
|
+
await postConnectEvent({ endpoint, body, attempts: 2, label: 'setup', apiKey });
|
|
154
154
|
}
|
|
155
|
-
export const DOCTOR_HARNESS_SCOPES = [
|
|
155
|
+
export const DOCTOR_HARNESS_SCOPES = ['all', ...ALL_HARNESSES];
|
|
156
156
|
// Aggregate only: per-harness rows and check `detail` strings stay local, so nothing
|
|
157
157
|
// user-authored or credential-shaped can ride a doctor run to PostHog.
|
|
158
158
|
// Caps mirror the route's schema: a payload the server would 400 is dropped here instead,
|
|
@@ -168,10 +168,10 @@ export const doctorCompletedPayloadSchema = z.object({
|
|
|
168
168
|
checks_skip: z.int().nonnegative().max(256),
|
|
169
169
|
/** Offered by the pre-repair report; the verdicts describe the machine after it. */
|
|
170
170
|
repairs_offered: z.int().nonnegative().max(256),
|
|
171
|
-
repair_actions: z.array(z.enum([
|
|
171
|
+
repair_actions: z.array(z.enum(['connect', 'auth-login'])).max(8),
|
|
172
172
|
repairs_attempted: z.int().nonnegative().max(256),
|
|
173
173
|
repairs_succeeded: z.int().nonnegative().max(256),
|
|
174
|
-
fix_mode: z.enum([
|
|
174
|
+
fix_mode: z.enum(['none', 'fix', 'fix_yes']),
|
|
175
175
|
/** "all" unless --harness scoped the run; the counts above cover only that scope. */
|
|
176
176
|
harness_scope: z.enum(DOCTOR_HARNESS_SCOPES),
|
|
177
177
|
is_human_initiated: z.boolean(),
|
|
@@ -184,15 +184,15 @@ export async function sendDoctorCompleted(mcpUrl, payload, apiKey) {
|
|
|
184
184
|
try {
|
|
185
185
|
// Parse inside try — a schema violation must not fail a diagnosis that already ran.
|
|
186
186
|
const parsed = doctorCompletedPayloadSchema.parse(payload);
|
|
187
|
-
endpoint = new URL(
|
|
188
|
-
body = JSON.stringify({ event:
|
|
187
|
+
endpoint = new URL('/api/cli/connect-event', mcpUrl).toString();
|
|
188
|
+
body = JSON.stringify({ event: 'doctor_completed', ...parsed });
|
|
189
189
|
}
|
|
190
190
|
catch (e) {
|
|
191
|
-
if (process.env[
|
|
191
|
+
if (process.env['TINYFISH_DEBUG']) {
|
|
192
192
|
errLine(`doctor telemetry skipped: ${e instanceof Error ? e.message : String(e)}`);
|
|
193
193
|
}
|
|
194
194
|
return;
|
|
195
195
|
}
|
|
196
196
|
// One attempt: doctor just probed this origin, so a stale keep-alive socket is not the risk.
|
|
197
|
-
await postConnectEvent({ endpoint, body, attempts: 1, label:
|
|
197
|
+
await postConnectEvent({ endpoint, body, attempts: 1, label: 'doctor', apiKey });
|
|
198
198
|
}
|
package/dist/lib/signals.js
CHANGED
|
@@ -7,7 +7,7 @@ export const SIGNAL_EXIT_CODES = { SIGHUP: 129, SIGINT: 130, SIGTERM: 143 };
|
|
|
7
7
|
*/
|
|
8
8
|
export function installSignalGuard(onSignal) {
|
|
9
9
|
let handling = false;
|
|
10
|
-
const handlers = [
|
|
10
|
+
const handlers = ['SIGINT', 'SIGTERM', 'SIGHUP'].map((signal) => {
|
|
11
11
|
const handler = () => {
|
|
12
12
|
const exit = () => process.exit(SIGNAL_EXIT_CODES[signal]);
|
|
13
13
|
if (handling)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { type InstallOptions } from
|
|
2
|
-
import { type SkillAgent } from
|
|
1
|
+
import { type InstallOptions } from './cli-install.js';
|
|
2
|
+
import { type SkillAgent } from './connect-clients.js';
|
|
3
3
|
export declare function installWebSkill(client: {
|
|
4
4
|
skillAgent?: SkillAgent;
|
|
5
5
|
displayName: string;
|
|
6
|
-
}, { verbose }: Pick<InstallOptions,
|
|
6
|
+
}, { verbose }: Pick<InstallOptions, 'verbose'>): void;
|
|
7
7
|
/**
|
|
8
8
|
* Refresh the skill we installed, for the harnesses we installed it in.
|
|
9
9
|
*
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import * as path from
|
|
2
|
-
import spawn from
|
|
3
|
-
import { z } from
|
|
4
|
-
import { loadConfig } from
|
|
5
|
-
import { captureStdio, capturedOutput, replay, SKILL_INSTALL_TIMEOUT_MS, STEP_MAX_BUFFER, } from
|
|
6
|
-
import { CURSOR_SKILL_TARGET, NATIVE_MCP_CLIENTS, OPENCLAW_SKILL_INSTALL_ARGS, } from
|
|
7
|
-
import { commandNotFound, spawnStepError, throwIfInterrupted } from
|
|
8
|
-
import { CLI_AGENT_IDENTITY } from
|
|
9
|
-
import { errLine } from
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import spawn from 'cross-spawn';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { loadConfig } from './auth.js';
|
|
5
|
+
import { captureStdio, capturedOutput, replay, SKILL_INSTALL_TIMEOUT_MS, STEP_MAX_BUFFER, } from './cli-install.js';
|
|
6
|
+
import { CURSOR_SKILL_TARGET, NATIVE_MCP_CLIENTS, OPENCLAW_SKILL_INSTALL_ARGS, } from './connect-clients.js';
|
|
7
|
+
import { commandNotFound, spawnStepError, throwIfInterrupted } from './connect-runtime.js';
|
|
8
|
+
import { CLI_AGENT_IDENTITY } from './constants.js';
|
|
9
|
+
import { errLine } from './output.js';
|
|
10
10
|
// Supports Hermes without node:util.styleText, so the installer still runs on Node 20.11.
|
|
11
|
-
const SKILLS_CLI_PACKAGE =
|
|
12
|
-
const TINYFISH_WEB_SKILL_SOURCE =
|
|
13
|
-
const TINYFISH_WEB_SKILL =
|
|
11
|
+
const SKILLS_CLI_PACKAGE = 'skills@1.5.15';
|
|
12
|
+
const TINYFISH_WEB_SKILL_SOURCE = 'tinyfish-io/tinyfish-cookbook';
|
|
13
|
+
const TINYFISH_WEB_SKILL = 'use-tinyfish';
|
|
14
14
|
/** `skills add` is an unconditional overwrite, so it doubles as the refresh path. */
|
|
15
15
|
function skillAddArgs(skillAgents) {
|
|
16
16
|
return [
|
|
17
|
-
|
|
17
|
+
'-y',
|
|
18
18
|
SKILLS_CLI_PACKAGE,
|
|
19
|
-
|
|
19
|
+
'add',
|
|
20
20
|
TINYFISH_WEB_SKILL_SOURCE,
|
|
21
|
-
|
|
21
|
+
'--skill',
|
|
22
22
|
TINYFISH_WEB_SKILL,
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
'--global',
|
|
24
|
+
'--agent',
|
|
25
25
|
...skillAgents,
|
|
26
|
-
|
|
26
|
+
'--yes',
|
|
27
27
|
];
|
|
28
28
|
}
|
|
29
29
|
// npx resolves from PATH, which a global npm install may not have exported yet.
|
|
@@ -32,7 +32,7 @@ function skillSpawnEnv() {
|
|
|
32
32
|
...process.env,
|
|
33
33
|
// `skills` gates its logo art on agent detection.
|
|
34
34
|
AI_AGENT: CLI_AGENT_IDENTITY,
|
|
35
|
-
PATH: `${path.dirname(process.execPath)}${path.delimiter}${process.env.PATH ??
|
|
35
|
+
PATH: `${path.dirname(process.execPath)}${path.delimiter}${process.env.PATH ?? ''}`,
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
// The `skills` update flow exits 0 even when it fails, so status alone proves nothing.
|
|
@@ -47,13 +47,13 @@ const SKILL_UNCHECKABLE_PATTERN = /cannot be checked automatically/;
|
|
|
47
47
|
const skillListSchema = z.array(z.object({ name: z.string() }));
|
|
48
48
|
// `add` exits 0 on per-skill failure; the piped list is the verdict.
|
|
49
49
|
function failedListVerdict() {
|
|
50
|
-
const list = spawn.sync(
|
|
51
|
-
encoding:
|
|
50
|
+
const list = spawn.sync('npx', ['-y', SKILLS_CLI_PACKAGE, 'list', '--global', '--json'], {
|
|
51
|
+
encoding: 'utf8',
|
|
52
52
|
env: skillSpawnEnv(),
|
|
53
53
|
maxBuffer: STEP_MAX_BUFFER,
|
|
54
54
|
timeout: SKILL_INSTALL_TIMEOUT_MS,
|
|
55
55
|
});
|
|
56
|
-
const verified = !list.error && list.status === 0 && skillListed(list.stdout ??
|
|
56
|
+
const verified = !list.error && list.status === 0 && skillListed(list.stdout ?? '');
|
|
57
57
|
return verified ? null : list;
|
|
58
58
|
}
|
|
59
59
|
// Entry presence is the whole verdict: `agents` names the harnesses `skills` detects,
|
|
@@ -74,7 +74,7 @@ export function installWebSkill(client, { verbose }) {
|
|
|
74
74
|
if (!client.skillAgent)
|
|
75
75
|
return;
|
|
76
76
|
errLine(`Installing the TinyFish web skill in ${client.displayName}...`);
|
|
77
|
-
const add = spawn.sync(
|
|
77
|
+
const add = spawn.sync('npx', skillAddArgs([client.skillAgent]), {
|
|
78
78
|
...captureStdio(verbose),
|
|
79
79
|
env: skillSpawnEnv(),
|
|
80
80
|
});
|
|
@@ -103,9 +103,9 @@ export function updateWebSkill({ verbose }) {
|
|
|
103
103
|
const skillAgents = [
|
|
104
104
|
...NATIVE_MCP_CLIENTS.filter((client) => connected[client.connectClient]).map((client) => client.skillAgent),
|
|
105
105
|
// Cursor is not a NativeMcpClient — it writes its own MCP config.
|
|
106
|
-
...(connected[
|
|
106
|
+
...(connected['cursor'] ? [CURSOR_SKILL_TARGET.skillAgent] : []),
|
|
107
107
|
].filter((agent) => agent !== undefined);
|
|
108
|
-
const hasOpenClaw = Boolean(connected[
|
|
108
|
+
const hasOpenClaw = Boolean(connected['openclaw']);
|
|
109
109
|
// Installs predating PF-3169 recorded nothing; ask `skills` to refresh what it tracks. A
|
|
110
110
|
// skill-less recorded install (Grok) may still have one from before, so refresh anyway
|
|
111
111
|
// — but there having been none is then expected, not an upgrade failure.
|
|
@@ -124,26 +124,26 @@ export function updateWebSkill({ verbose }) {
|
|
|
124
124
|
}
|
|
125
125
|
function reinstallOpenClawSkill(verbose) {
|
|
126
126
|
if (verbose)
|
|
127
|
-
errLine(
|
|
128
|
-
const result = spawn.sync(
|
|
127
|
+
errLine('Refreshing the TinyFish skill in OpenClaw...');
|
|
128
|
+
const result = spawn.sync('openclaw', OPENCLAW_SKILL_INSTALL_ARGS, captureStdio(verbose));
|
|
129
129
|
// OpenClaw may have been removed since connect; a stale context entry must not fail an
|
|
130
130
|
// upgrade that otherwise succeeded.
|
|
131
131
|
if (commandNotFound(result.error)) {
|
|
132
|
-
errLine(
|
|
132
|
+
errLine('OpenClaw is not on PATH, so its TinyFish skill was left unchanged.');
|
|
133
133
|
return false;
|
|
134
134
|
}
|
|
135
135
|
if (result.error || result.status !== 0) {
|
|
136
136
|
throwIfInterrupted(result);
|
|
137
137
|
replay(capturedOutput(result));
|
|
138
|
-
throw new Error(
|
|
138
|
+
throw new Error('Could not refresh the TinyFish skill in OpenClaw', { cause: result.error });
|
|
139
139
|
}
|
|
140
140
|
return true;
|
|
141
141
|
}
|
|
142
142
|
function reinstallWebSkill(skillAgents, verbose) {
|
|
143
143
|
if (verbose)
|
|
144
|
-
errLine(`Refreshing the TinyFish web skill for ${skillAgents.join(
|
|
145
|
-
const result = spawn.sync(
|
|
146
|
-
encoding:
|
|
144
|
+
errLine(`Refreshing the TinyFish web skill for ${skillAgents.join(', ')}...`);
|
|
145
|
+
const result = spawn.sync('npx', skillAddArgs(skillAgents), {
|
|
146
|
+
encoding: 'utf8',
|
|
147
147
|
env: skillSpawnEnv(),
|
|
148
148
|
maxBuffer: STEP_MAX_BUFFER,
|
|
149
149
|
timeout: SKILL_INSTALL_TIMEOUT_MS,
|
|
@@ -158,7 +158,7 @@ function reinstallWebSkill(skillAgents, verbose) {
|
|
|
158
158
|
if (verbose || failed)
|
|
159
159
|
replay(output);
|
|
160
160
|
if (failed) {
|
|
161
|
-
throw new Error(
|
|
161
|
+
throw new Error('Could not refresh the TinyFish web skill', {
|
|
162
162
|
cause: (failedList ?? result).error,
|
|
163
163
|
});
|
|
164
164
|
}
|
|
@@ -166,9 +166,9 @@ function reinstallWebSkill(skillAgents, verbose) {
|
|
|
166
166
|
/** Fallback when no skill-bearing harness is recorded: refresh whatever `skills` tracks. */
|
|
167
167
|
function refreshWebSkillByHash(verbose, untrackedIsExpected = false) {
|
|
168
168
|
if (verbose)
|
|
169
|
-
errLine(
|
|
170
|
-
const result = spawn.sync(
|
|
171
|
-
encoding:
|
|
169
|
+
errLine('Refreshing the TinyFish web skill...');
|
|
170
|
+
const result = spawn.sync('npx', ['-y', SKILLS_CLI_PACKAGE, 'update', TINYFISH_WEB_SKILL, '--global', '--yes'], {
|
|
171
|
+
encoding: 'utf8',
|
|
172
172
|
env: skillSpawnEnv(),
|
|
173
173
|
maxBuffer: STEP_MAX_BUFFER,
|
|
174
174
|
timeout: SKILL_INSTALL_TIMEOUT_MS,
|
|
@@ -182,15 +182,15 @@ function refreshWebSkillByHash(verbose, untrackedIsExpected = false) {
|
|
|
182
182
|
if (verbose || failed || unchecked)
|
|
183
183
|
replay(output);
|
|
184
184
|
if (failed)
|
|
185
|
-
throw new Error(
|
|
185
|
+
throw new Error('Could not refresh the TinyFish web skill', { cause: result.error });
|
|
186
186
|
if (unchecked) {
|
|
187
|
-
errLine(
|
|
187
|
+
errLine('Run `tinyfish connect <client>` to reinstall the skill and restore update tracking.');
|
|
188
188
|
if (untrackedIsExpected)
|
|
189
189
|
return false;
|
|
190
|
-
throw new Error(
|
|
190
|
+
throw new Error('The TinyFish web skill cannot be checked for updates');
|
|
191
191
|
}
|
|
192
192
|
if (SKILL_NOT_INSTALLED_PATTERN.test(output)) {
|
|
193
|
-
errLine(
|
|
193
|
+
errLine('No use-tinyfish skill installed. Run `tinyfish connect <client>` to add it.');
|
|
194
194
|
return false;
|
|
195
195
|
}
|
|
196
196
|
return !SKILL_ALREADY_CURRENT_PATTERN.test(output);
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { AgentRunParams, BrowserProfile, Run, RunStatus } from
|
|
1
|
+
import type { AgentRunParams, BrowserProfile, Run, RunStatus } from '@tiny-fish/sdk';
|
|
2
2
|
export type OutputSchema = Record<string, unknown>;
|
|
3
3
|
export interface CliAgentRunParams extends AgentRunParams {
|
|
4
4
|
output_schema?: OutputSchema;
|
|
5
5
|
agent_config?: {
|
|
6
|
-
mode?:
|
|
7
|
-
cursor_style?:
|
|
6
|
+
mode?: 'default' | 'strict';
|
|
7
|
+
cursor_style?: 'fish' | 'standard' | 'green-pointer';
|
|
8
8
|
max_steps?: number;
|
|
9
9
|
max_duration_seconds?: number;
|
|
10
10
|
};
|
|
@@ -21,7 +21,7 @@ export interface CliAgentRunParams extends AgentRunParams {
|
|
|
21
21
|
use_vault?: boolean;
|
|
22
22
|
credential_item_ids?: string[];
|
|
23
23
|
}
|
|
24
|
-
export type VaultProvider =
|
|
24
|
+
export type VaultProvider = '1password' | 'bitwarden';
|
|
25
25
|
export interface VaultConnection {
|
|
26
26
|
id: string;
|
|
27
27
|
provider: VaultProvider;
|
|
@@ -31,7 +31,7 @@ export interface VaultConnection {
|
|
|
31
31
|
export interface VaultFieldMetadata {
|
|
32
32
|
fieldId: string;
|
|
33
33
|
label: string;
|
|
34
|
-
type:
|
|
34
|
+
type: 'STRING' | 'CONCEALED' | 'OTP';
|
|
35
35
|
}
|
|
36
36
|
export interface VaultItem {
|
|
37
37
|
itemId: string;
|
|
@@ -43,10 +43,10 @@ export interface VaultItem {
|
|
|
43
43
|
hasTotp: boolean;
|
|
44
44
|
}
|
|
45
45
|
export type VaultConnectRequest = {
|
|
46
|
-
provider:
|
|
46
|
+
provider: '1password';
|
|
47
47
|
token: string;
|
|
48
48
|
} | {
|
|
49
|
-
provider:
|
|
49
|
+
provider: 'bitwarden';
|
|
50
50
|
clientId: string;
|
|
51
51
|
clientSecret: string;
|
|
52
52
|
masterPassword: string;
|
|
@@ -93,7 +93,7 @@ export interface RunStepsResponse {
|
|
|
93
93
|
}
|
|
94
94
|
export interface CliBrowserSessionCreateParams {
|
|
95
95
|
url?: string;
|
|
96
|
-
browser_profile?:
|
|
96
|
+
browser_profile?: 'lite' | 'stealth';
|
|
97
97
|
}
|
|
98
98
|
export interface CancelRunResponse {
|
|
99
99
|
run_id: string;
|
package/dist/lib/verify.d.ts
CHANGED
package/dist/lib/verify.js
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
import { listRuns } from
|
|
2
|
-
import { ApiError } from
|
|
1
|
+
import { listRuns } from './client.js';
|
|
2
|
+
import { ApiError } from './output.js';
|
|
3
3
|
const VERIFY_TIMEOUT_MS = 5_000;
|
|
4
4
|
// `fetch` collapses every network fault to "fetch failed" and hides the cause on `e.cause`.
|
|
5
5
|
function networkCode(e) {
|
|
6
6
|
const cause = e?.cause;
|
|
7
7
|
switch (cause?.code) {
|
|
8
|
-
case
|
|
9
|
-
return
|
|
10
|
-
case
|
|
11
|
-
return
|
|
12
|
-
case
|
|
13
|
-
return
|
|
14
|
-
case
|
|
15
|
-
case
|
|
16
|
-
case
|
|
17
|
-
return
|
|
8
|
+
case 'ECONNREFUSED':
|
|
9
|
+
return 'connection refused';
|
|
10
|
+
case 'ENOTFOUND':
|
|
11
|
+
return 'host not found';
|
|
12
|
+
case 'ETIMEDOUT':
|
|
13
|
+
return 'connection timed out';
|
|
14
|
+
case 'CERT_HAS_EXPIRED':
|
|
15
|
+
case 'UNABLE_TO_VERIFY_LEAF_SIGNATURE':
|
|
16
|
+
case 'DEPTH_ZERO_SELF_SIGNED_CERT':
|
|
17
|
+
return 'TLS verification failed';
|
|
18
18
|
default:
|
|
19
|
-
return e instanceof Error && e.name ===
|
|
20
|
-
?
|
|
21
|
-
:
|
|
19
|
+
return e instanceof Error && e.name === 'TimeoutError'
|
|
20
|
+
? 'request timed out'
|
|
21
|
+
: 'could not reach the endpoint';
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
function authCode(e) {
|
|
25
25
|
if (!(e instanceof ApiError))
|
|
26
|
-
return
|
|
26
|
+
return 'the authenticated call failed';
|
|
27
27
|
if (e.status === 401)
|
|
28
|
-
return
|
|
28
|
+
return 'the API key is invalid or expired';
|
|
29
29
|
if (e.status === 403)
|
|
30
|
-
return
|
|
30
|
+
return 'the API key is not allowed to do this';
|
|
31
31
|
if (e.status === 429)
|
|
32
|
-
return
|
|
33
|
-
return e.status >= 500 ?
|
|
32
|
+
return 'rate limited';
|
|
33
|
+
return e.status >= 500 ? 'TinyFish returned a server error' : `the API returned HTTP ${e.status}`;
|
|
34
34
|
}
|
|
35
35
|
/** Reachability check. Verify failure is a warning, never install failure. */
|
|
36
36
|
export async function verifyMcpHealth(mcpUrl) {
|
|
37
37
|
try {
|
|
38
38
|
const response = await fetch(mcpUrl, {
|
|
39
|
-
method:
|
|
39
|
+
method: 'GET',
|
|
40
40
|
// Redirect (captive portal, typo'd --url) is not healthy.
|
|
41
|
-
redirect:
|
|
41
|
+
redirect: 'error',
|
|
42
42
|
signal: AbortSignal.timeout(VERIFY_TIMEOUT_MS),
|
|
43
43
|
});
|
|
44
44
|
// MCP rejects bare GET with 4xx — still proves endpoint routed.
|
|
45
45
|
if (response.status >= 500) {
|
|
46
46
|
return {
|
|
47
|
-
depth:
|
|
47
|
+
depth: 'health',
|
|
48
48
|
ok: false,
|
|
49
49
|
reason: `endpoint returned HTTP ${response.status}`,
|
|
50
50
|
code: `the endpoint returned HTTP ${response.status}`,
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
return { depth:
|
|
53
|
+
return { depth: 'health', ok: true };
|
|
54
54
|
}
|
|
55
55
|
catch (e) {
|
|
56
56
|
return {
|
|
57
|
-
depth:
|
|
57
|
+
depth: 'health',
|
|
58
58
|
ok: false,
|
|
59
|
-
reason: e instanceof Error ? e.message :
|
|
59
|
+
reason: e instanceof Error ? e.message : 'network error reaching MCP endpoint',
|
|
60
60
|
code: networkCode(e),
|
|
61
61
|
};
|
|
62
62
|
}
|
|
@@ -66,11 +66,11 @@ export async function verifyMcpAuth(apiKey) {
|
|
|
66
66
|
try {
|
|
67
67
|
// Bound it: the SDK default is 600s, and a warning-only check must never hang an install.
|
|
68
68
|
await listRuns({ limit: 1 }, apiKey, VERIFY_TIMEOUT_MS);
|
|
69
|
-
return { depth:
|
|
69
|
+
return { depth: 'health+auth', ok: true };
|
|
70
70
|
}
|
|
71
71
|
catch (e) {
|
|
72
72
|
return {
|
|
73
|
-
depth:
|
|
73
|
+
depth: 'health+auth',
|
|
74
74
|
ok: false,
|
|
75
75
|
reason: e instanceof Error ? e.message : String(e),
|
|
76
76
|
code: authCode(e),
|
package/dist/program.d.ts
CHANGED
package/dist/program.js
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import { createRequire } from
|
|
2
|
-
import { Command } from
|
|
3
|
-
import { registerAuth } from
|
|
4
|
-
import { registerBatch } from
|
|
5
|
-
import { registerFetch } from
|
|
6
|
-
import { registerBrowser } from
|
|
7
|
-
import { registerProfile } from
|
|
8
|
-
import { registerVault } from
|
|
9
|
-
import { registerWallet } from
|
|
10
|
-
import { registerRun } from
|
|
11
|
-
import { registerRuns } from
|
|
12
|
-
import { registerConfigureClaude } from
|
|
13
|
-
import { registerSearch } from
|
|
14
|
-
import { registerConnect } from
|
|
15
|
-
import { registerDoctor } from
|
|
16
|
-
import { registerUpgrade } from
|
|
17
|
-
import { registerOnboard } from
|
|
18
|
-
import { installNoticeEmit } from
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { registerAuth } from './commands/auth.js';
|
|
4
|
+
import { registerBatch } from './commands/batch.js';
|
|
5
|
+
import { registerFetch } from './commands/fetch.js';
|
|
6
|
+
import { registerBrowser } from './commands/browser.js';
|
|
7
|
+
import { registerProfile } from './commands/profile.js';
|
|
8
|
+
import { registerVault } from './commands/vault.js';
|
|
9
|
+
import { registerWallet } from './commands/wallet.js';
|
|
10
|
+
import { registerRun } from './commands/run.js';
|
|
11
|
+
import { registerRuns } from './commands/runs.js';
|
|
12
|
+
import { registerConfigureClaude } from './commands/config-claude.js';
|
|
13
|
+
import { registerSearch } from './commands/search.js';
|
|
14
|
+
import { registerConnect } from './commands/connect.js';
|
|
15
|
+
import { registerDoctor } from './commands/doctor.js';
|
|
16
|
+
import { registerUpgrade } from './commands/upgrade.js';
|
|
17
|
+
import { registerOnboard } from './commands/onboard.js';
|
|
18
|
+
import { installNoticeEmit } from './lib/notice.js';
|
|
19
19
|
/** Split from index.ts so tests can drive the real program instead of hand-assembling one. */
|
|
20
20
|
export function buildProgram() {
|
|
21
|
-
const { version } = createRequire(import.meta.url)(
|
|
21
|
+
const { version } = createRequire(import.meta.url)('../package.json');
|
|
22
22
|
const program = new Command();
|
|
23
23
|
program
|
|
24
|
-
.name(
|
|
25
|
-
.description(
|
|
26
|
-
.version(version,
|
|
27
|
-
.helpOption(
|
|
24
|
+
.name('tinyfish')
|
|
25
|
+
.description('TinyFish CLI — run web automations from your terminal or agent.')
|
|
26
|
+
.version(version, '-V, --version', 'Show version')
|
|
27
|
+
.helpOption('-h, --help', 'Show help')
|
|
28
28
|
.addHelpCommand(false)
|
|
29
29
|
.enablePositionalOptions()
|
|
30
|
-
.option(
|
|
31
|
-
.hook(
|
|
30
|
+
.option('--debug', 'Print HTTP requests and responses to stderr (or set TINYFISH_DEBUG=1)')
|
|
31
|
+
.hook('preAction', (cmd) => {
|
|
32
32
|
if (cmd.opts().debug) {
|
|
33
|
-
process.env[
|
|
33
|
+
process.env['TINYFISH_DEBUG'] = '1';
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
36
|
registerAuth(program);
|
|
@@ -44,8 +44,8 @@ export function buildProgram() {
|
|
|
44
44
|
registerVault(program);
|
|
45
45
|
registerWallet(program);
|
|
46
46
|
const agentCmd = program
|
|
47
|
-
.command(
|
|
48
|
-
.description(
|
|
47
|
+
.command('agent')
|
|
48
|
+
.description('Agent automation commands')
|
|
49
49
|
.enablePositionalOptions();
|
|
50
50
|
const runCmd = registerRun(agentCmd);
|
|
51
51
|
registerRuns(runCmd);
|