@tiny-fish/cli 0.39.1-next.309 → 0.39.1-next.311
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 +2 -14
- package/dist/commands/connect.js +23 -56
- package/dist/commands/profile.d.ts +0 -3
- package/dist/commands/profile.js +7 -22
- package/dist/commands/run.js +20 -40
- package/dist/commands/runs.js +24 -58
- package/dist/lib/auth.d.ts +1 -3
- package/dist/lib/auth.js +1 -1
- package/dist/lib/claude-config.d.ts +0 -4
- package/dist/lib/claude-config.js +4 -4
- package/dist/lib/cli-install.d.ts +0 -1
- package/dist/lib/cli-install.js +0 -1
- package/dist/lib/client.js +82 -147
- package/dist/lib/connect-all-auth.js +1 -1
- package/dist/lib/connect-all.js +8 -27
- package/dist/lib/connect-clients.d.ts +3 -6
- package/dist/lib/connect-fallback.d.ts +2 -1
- package/dist/lib/connect-picker.d.ts +3 -2
- package/dist/lib/connect-runtime.js +4 -6
- package/dist/lib/harness-spec.d.ts +5 -34
- package/dist/lib/harness-spec.js +1 -30
- package/dist/lib/hermes-env.d.ts +0 -1
- package/dist/lib/hermes-env.js +1 -3
- package/dist/lib/hermes-plugin.js +1 -9
- package/dist/lib/output.d.ts +2 -0
- package/dist/lib/output.js +9 -0
- package/dist/lib/registration-detect.d.ts +0 -1
- package/dist/lib/registration-detect.js +36 -84
- package/dist/lib/setup-telemetry.d.ts +3 -7
- package/dist/lib/setup-telemetry.js +5 -5
- package/dist/lib/skill-install.js +1 -9
- package/dist/lib/types.d.ts +4 -3
- package/dist/program.js +2 -3
- package/package.json +1 -2
package/dist/lib/harness-spec.js
CHANGED
|
@@ -82,11 +82,6 @@ export const HARNESS_SPECS = {
|
|
|
82
82
|
label: 'local TinyFish registration',
|
|
83
83
|
},
|
|
84
84
|
],
|
|
85
|
-
signInViaCliLogin: false,
|
|
86
|
-
canVerifyAuth: false,
|
|
87
|
-
keyHeldByCli: false,
|
|
88
|
-
cliWritesConfig: false,
|
|
89
|
-
authDeferredAtInstall: false,
|
|
90
85
|
},
|
|
91
86
|
codex: {
|
|
92
87
|
command: 'codex',
|
|
@@ -110,10 +105,6 @@ export const HARNESS_SPECS = {
|
|
|
110
105
|
// `--oauth-resource` duplicates the resource Codex discovers; Clerk rejects both.
|
|
111
106
|
// `mcp add` still runs OAuth inline, so `loginArgs` stays a retry.
|
|
112
107
|
loginArgs: ['mcp', 'login', 'tinyfish'],
|
|
113
|
-
signInViaCliLogin: false,
|
|
114
|
-
canVerifyAuth: false,
|
|
115
|
-
keyHeldByCli: false,
|
|
116
|
-
cliWritesConfig: false,
|
|
117
108
|
// TODO(PF-3580): connect now probes Codex sign-in, so this over-warns a signed-in install.
|
|
118
109
|
authDeferredAtInstall: true,
|
|
119
110
|
},
|
|
@@ -123,11 +114,9 @@ export const HARNESS_SPECS = {
|
|
|
123
114
|
configDir: '.cursor',
|
|
124
115
|
reloadAction: 'reload the window',
|
|
125
116
|
skillAgent: 'cursor',
|
|
126
|
-
signInViaCliLogin: false,
|
|
127
117
|
canVerifyAuth: true,
|
|
128
118
|
keyHeldByCli: true,
|
|
129
119
|
cliWritesConfig: true,
|
|
130
|
-
authDeferredAtInstall: false,
|
|
131
120
|
},
|
|
132
121
|
grok: {
|
|
133
122
|
command: 'grok',
|
|
@@ -148,11 +137,6 @@ export const HARNESS_SPECS = {
|
|
|
148
137
|
// Grok expands `${VAR}` in headers at load, so the key stays out of argv and config.toml.
|
|
149
138
|
header: { name: 'Authorization', sep: ': ', valuePrefix: 'Bearer ', viaEnv: true },
|
|
150
139
|
signInHint: GROK_SIGN_IN_HINT,
|
|
151
|
-
signInViaCliLogin: false,
|
|
152
|
-
canVerifyAuth: false,
|
|
153
|
-
keyHeldByCli: false,
|
|
154
|
-
cliWritesConfig: false,
|
|
155
|
-
authDeferredAtInstall: false,
|
|
156
140
|
},
|
|
157
141
|
hermes: {
|
|
158
142
|
command: 'hermes',
|
|
@@ -175,12 +159,8 @@ export const HARNESS_SPECS = {
|
|
|
175
159
|
label: 'TinyFish web plugin from Hermes',
|
|
176
160
|
},
|
|
177
161
|
postConnectNote: HERMES_RESTART_NOTE,
|
|
178
|
-
|
|
179
|
-
// Stays false: keyed already covers auth; true breaks keyless installs.
|
|
180
|
-
canVerifyAuth: false,
|
|
162
|
+
// No canVerifyAuth: keyed already covers auth; true breaks keyless installs.
|
|
181
163
|
keyHeldByCli: true,
|
|
182
|
-
cliWritesConfig: false,
|
|
183
|
-
authDeferredAtInstall: false,
|
|
184
164
|
},
|
|
185
165
|
omp: {
|
|
186
166
|
command: 'omp',
|
|
@@ -188,11 +168,9 @@ export const HARNESS_SPECS = {
|
|
|
188
168
|
configDir: '.omp',
|
|
189
169
|
reloadAction: 'restart it',
|
|
190
170
|
configPathFromBinary: true,
|
|
191
|
-
signInViaCliLogin: false,
|
|
192
171
|
canVerifyAuth: true,
|
|
193
172
|
keyHeldByCli: true,
|
|
194
173
|
cliWritesConfig: true,
|
|
195
|
-
authDeferredAtInstall: false,
|
|
196
174
|
},
|
|
197
175
|
openclaw: {
|
|
198
176
|
command: 'openclaw',
|
|
@@ -212,8 +190,6 @@ export const HARNESS_SPECS = {
|
|
|
212
190
|
signInViaCliLogin: true,
|
|
213
191
|
canVerifyAuth: true,
|
|
214
192
|
keyHeldByCli: true,
|
|
215
|
-
cliWritesConfig: false,
|
|
216
|
-
authDeferredAtInstall: false,
|
|
217
193
|
},
|
|
218
194
|
opencode: {
|
|
219
195
|
command: 'opencode',
|
|
@@ -236,11 +212,6 @@ export const HARNESS_SPECS = {
|
|
|
236
212
|
// `opencode mcp add` upserts by name and ships no `mcp remove`.
|
|
237
213
|
removals: [],
|
|
238
214
|
postConnectNote: OPENCODE_MODEL_NOTE,
|
|
239
|
-
signInViaCliLogin: false,
|
|
240
|
-
canVerifyAuth: false,
|
|
241
|
-
keyHeldByCli: false,
|
|
242
|
-
cliWritesConfig: false,
|
|
243
|
-
authDeferredAtInstall: false,
|
|
244
215
|
},
|
|
245
216
|
};
|
|
246
217
|
export const ALL_HARNESSES = Object.keys(HARNESS_SPECS);
|
package/dist/lib/hermes-env.d.ts
CHANGED
package/dist/lib/hermes-env.js
CHANGED
|
@@ -7,14 +7,12 @@ import { HARNESS_PROBE_TIMEOUT_MS } from './constants.js';
|
|
|
7
7
|
import { sanitizeLine } from './output.js';
|
|
8
8
|
// display_hermes_home() abbreviates under $HOME, so the value is not always absolute.
|
|
9
9
|
const HERMES_HOME_LINE = /^\s*hermes_home:\s*(\S.*?)\s*$/m;
|
|
10
|
-
// One value: the next cold-start bump must move both probes together.
|
|
11
|
-
export const HERMES_PROBE_TIMEOUT_MS = HARNESS_PROBE_TIMEOUT_MS;
|
|
12
10
|
/** Asking Hermes beats reimplementing its profile override, which we'd drift from. */
|
|
13
11
|
export function resolveHermesHome() {
|
|
14
12
|
const result = spawn.sync('hermes', ['dump'], {
|
|
15
13
|
encoding: 'utf8',
|
|
16
14
|
env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1' },
|
|
17
|
-
timeout:
|
|
15
|
+
timeout: HARNESS_PROBE_TIMEOUT_MS,
|
|
18
16
|
});
|
|
19
17
|
if (result.error || result.status !== 0)
|
|
20
18
|
return undefined;
|
|
@@ -7,7 +7,7 @@ import { z } from 'zod';
|
|
|
7
7
|
import { capturedOutput, replay, SKILL_INSTALL_TIMEOUT_MS, STEP_MAX_BUFFER, } from './cli-install.js';
|
|
8
8
|
import { commandNotFound, ConnectStepError, spawnStepError, throwIfInterrupted, } from './connect-runtime.js';
|
|
9
9
|
import { HARNESS_PROBE_TIMEOUT_MS } from './constants.js';
|
|
10
|
-
import { errLine } from './output.js';
|
|
10
|
+
import { errLine, parseJson } from './output.js';
|
|
11
11
|
// Bump per CLI release.
|
|
12
12
|
export const HERMES_PLUGIN_SHA = '496cd63fefd982bbaa8a85ce78ef2270d700984f';
|
|
13
13
|
// Bump with the SHA; the source is `hermes/plugin.yaml` at that commit.
|
|
@@ -127,14 +127,6 @@ const hermesPluginStatusSchema = z.looseObject({
|
|
|
127
127
|
provider_available: z.boolean().nullish(),
|
|
128
128
|
web_backend_configured: z.boolean(),
|
|
129
129
|
});
|
|
130
|
-
function parseJson(text) {
|
|
131
|
-
try {
|
|
132
|
-
return JSON.parse(text);
|
|
133
|
-
}
|
|
134
|
-
catch {
|
|
135
|
-
return undefined;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
130
|
/** `status --json` always exits 0; a non-zero exit means the subcommand is absent. */
|
|
139
131
|
export function readHermesPluginStatus(home) {
|
|
140
132
|
const result = spawn.sync('hermes', ['tinyfish', 'status', '--json'], {
|
package/dist/lib/output.d.ts
CHANGED
|
@@ -16,3 +16,5 @@ export declare function errLine(line: string): void;
|
|
|
16
16
|
export declare function warnLine(line: string): void;
|
|
17
17
|
export declare function err(data: unknown): void;
|
|
18
18
|
export declare function handleApiError(e: unknown): never;
|
|
19
|
+
/** Undefined, not a throw: callers hand the value to a Zod safeParse. */
|
|
20
|
+
export declare function parseJson(text: string): unknown;
|
package/dist/lib/output.js
CHANGED
|
@@ -70,3 +70,12 @@ export function handleApiError(e) {
|
|
|
70
70
|
}
|
|
71
71
|
process.exit(1);
|
|
72
72
|
}
|
|
73
|
+
/** Undefined, not a throw: callers hand the value to a Zod safeParse. */
|
|
74
|
+
export function parseJson(text) {
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(text);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -20,7 +20,6 @@ export interface RegistrationStatus {
|
|
|
20
20
|
recordedAuthMode?: RecordedAuthMode;
|
|
21
21
|
reason?: string;
|
|
22
22
|
}
|
|
23
|
-
export declare function codexOauthCompleted(): boolean | undefined;
|
|
24
23
|
/** Per-harness sign-in probes; connect composes these rather than the descriptor carrying them. */
|
|
25
24
|
export declare const SIGN_IN_PROBES: Partial<Record<AgentClient, () => boolean | undefined>>;
|
|
26
25
|
/** Never throws; a failed probe is `unknown` with a reason, so no caller can read it as healthy. */
|
|
@@ -11,6 +11,15 @@ import { readOmpTinyfishEntry } from './omp-config.js';
|
|
|
11
11
|
import { readHermesKey, resolveHermesHome } from './hermes-env.js';
|
|
12
12
|
import { readHermesEntry } from './hermes-config.js';
|
|
13
13
|
import { detectInstalledHarnesses, harnessConfigPath, harnessDisplayPath, AuthMode, Registered, } from './harness-detect.js';
|
|
14
|
+
const NOT_REGISTERED = Object.freeze({
|
|
15
|
+
registered: Registered.No,
|
|
16
|
+
authMode: AuthMode.Unknown,
|
|
17
|
+
});
|
|
18
|
+
const unverified = (reason) => ({
|
|
19
|
+
registered: Registered.Unknown,
|
|
20
|
+
authMode: AuthMode.Unknown,
|
|
21
|
+
reason,
|
|
22
|
+
});
|
|
14
23
|
// A list, verified against codex 0.146. A name-keyed map was accepted here too, but every
|
|
15
24
|
// field is optional, so that arm parsed *any* object-of-objects: a wrapper like
|
|
16
25
|
// `{"servers": {...}}` decoded as one entry named `servers` and reported TinyFish absent.
|
|
@@ -151,15 +160,11 @@ function parseMcpGet(output) {
|
|
|
151
160
|
function fromMcpGet(command) {
|
|
152
161
|
const probe = runProbe(command, ['mcp', 'get', 'tinyfish']);
|
|
153
162
|
if (probe.outcome === 'unavailable') {
|
|
154
|
-
return
|
|
163
|
+
return unverified(probe.reason);
|
|
155
164
|
}
|
|
156
165
|
if (probe.exitCode !== 0) {
|
|
157
166
|
if (UNSUPPORTED_SUBCOMMAND.test(probe.output)) {
|
|
158
|
-
return {
|
|
159
|
-
registered: Registered.Unknown,
|
|
160
|
-
authMode: AuthMode.Unknown,
|
|
161
|
-
reason: `\`${command} mcp get\` is unsupported by this version`,
|
|
162
|
-
};
|
|
167
|
+
return unverified(`\`${command} mcp get\` is unsupported by this version`);
|
|
163
168
|
}
|
|
164
169
|
// The roster is free; `mcp list` health-checks every server.
|
|
165
170
|
const scoped = tinyfishServerName(probe.output);
|
|
@@ -178,13 +183,9 @@ function fromMcpGet(command) {
|
|
|
178
183
|
return plugin;
|
|
179
184
|
// A broken CLI also exits nonzero, and reading that as absence earns a spurious repair.
|
|
180
185
|
if (!NO_SUCH_SERVER.test(probe.output)) {
|
|
181
|
-
return {
|
|
182
|
-
registered: Registered.Unknown,
|
|
183
|
-
authMode: AuthMode.Unknown,
|
|
184
|
-
reason: `\`${command} mcp get\` failed without reporting the server as absent`,
|
|
185
|
-
};
|
|
186
|
+
return unverified(`\`${command} mcp get\` failed without reporting the server as absent`);
|
|
186
187
|
}
|
|
187
|
-
return
|
|
188
|
+
return NOT_REGISTERED;
|
|
188
189
|
}
|
|
189
190
|
return parseMcpGet(probe.output);
|
|
190
191
|
}
|
|
@@ -218,7 +219,7 @@ function readCodexEntry() {
|
|
|
218
219
|
}
|
|
219
220
|
// `mcp add` exits 0 whether or not its inline OAuth finished, so `not_logged_in` is the only
|
|
220
221
|
// evidence of an abandoned sign-in; every other state, older Codex included, is unknown.
|
|
221
|
-
|
|
222
|
+
function codexOauthCompleted() {
|
|
222
223
|
const read = readCodexEntry();
|
|
223
224
|
if ('reason' in read)
|
|
224
225
|
return undefined;
|
|
@@ -255,10 +256,10 @@ function envKeyVerdict(envVar) {
|
|
|
255
256
|
function probeCodex() {
|
|
256
257
|
const read = readCodexEntry();
|
|
257
258
|
if ('reason' in read)
|
|
258
|
-
return
|
|
259
|
+
return unverified(read.reason);
|
|
259
260
|
const entry = read.entry;
|
|
260
261
|
if (!entry)
|
|
261
|
-
return
|
|
262
|
+
return NOT_REGISTERED;
|
|
262
263
|
const codexUrl = entry.transport?.url;
|
|
263
264
|
// Read from this entry: codex emits `bearer_token_env_var` on every HTTP server, so testing
|
|
264
265
|
// the whole payload reports api-key for TinyFish because some other server carries a key.
|
|
@@ -279,14 +280,10 @@ function probeCodex() {
|
|
|
279
280
|
function probeCursor() {
|
|
280
281
|
const entry = readCursorTinyfishEntry();
|
|
281
282
|
if (entry.error) {
|
|
282
|
-
return
|
|
283
|
-
registered: Registered.Unknown,
|
|
284
|
-
authMode: AuthMode.Unknown,
|
|
285
|
-
reason: 'mcp.json exists but could not be read or parsed',
|
|
286
|
-
};
|
|
283
|
+
return unverified('mcp.json exists but could not be read or parsed');
|
|
287
284
|
}
|
|
288
285
|
if (!entry.present)
|
|
289
|
-
return
|
|
286
|
+
return NOT_REGISTERED;
|
|
290
287
|
return {
|
|
291
288
|
registered: Registered.Yes,
|
|
292
289
|
authMode: entry.hasApiKeyHeader ? AuthMode.ApiKey : AuthMode.Unknown,
|
|
@@ -298,21 +295,13 @@ function probeCursor() {
|
|
|
298
295
|
function probeOmp() {
|
|
299
296
|
const entry = readOmpTinyfishEntry();
|
|
300
297
|
if (!entry) {
|
|
301
|
-
return
|
|
302
|
-
registered: Registered.Unknown,
|
|
303
|
-
authMode: AuthMode.Unknown,
|
|
304
|
-
reason: '`omp config path` did not report a config directory',
|
|
305
|
-
};
|
|
298
|
+
return unverified('`omp config path` did not report a config directory');
|
|
306
299
|
}
|
|
307
300
|
if (entry.error) {
|
|
308
|
-
return
|
|
309
|
-
registered: Registered.Unknown,
|
|
310
|
-
authMode: AuthMode.Unknown,
|
|
311
|
-
reason: 'mcp.json exists but could not be read or parsed',
|
|
312
|
-
};
|
|
301
|
+
return unverified('mcp.json exists but could not be read or parsed');
|
|
313
302
|
}
|
|
314
303
|
if (!entry.present)
|
|
315
|
-
return
|
|
304
|
+
return NOT_REGISTERED;
|
|
316
305
|
// A hand-written `${VAR}` header resolves like Codex's env-var key.
|
|
317
306
|
const keyVerdict = entry.keyTemplateVar
|
|
318
307
|
? envKeyVerdict(entry.keyTemplateVar)
|
|
@@ -342,34 +331,22 @@ function fromMcpList(command, output, registeredMode, urlPattern) {
|
|
|
342
331
|
};
|
|
343
332
|
}
|
|
344
333
|
if (!MCP_LIST_HEADER.test(output)) {
|
|
345
|
-
return {
|
|
346
|
-
registered: Registered.Unknown,
|
|
347
|
-
authMode: AuthMode.Unknown,
|
|
348
|
-
reason: `could not interpret \`${command} mcp list\` output`,
|
|
349
|
-
};
|
|
334
|
+
return unverified(`could not interpret \`${command} mcp list\` output`);
|
|
350
335
|
}
|
|
351
|
-
return
|
|
336
|
+
return NOT_REGISTERED;
|
|
352
337
|
}
|
|
353
338
|
// `hermes mcp list` only pretty-prints this file, so connect's gate reads it too.
|
|
354
339
|
function probeHermes() {
|
|
355
340
|
const home = resolveHermesHome();
|
|
356
341
|
if (!home) {
|
|
357
|
-
return
|
|
358
|
-
registered: Registered.Unknown,
|
|
359
|
-
authMode: AuthMode.Unknown,
|
|
360
|
-
reason: '`hermes dump` did not report a home directory',
|
|
361
|
-
};
|
|
342
|
+
return unverified('`hermes dump` did not report a home directory');
|
|
362
343
|
}
|
|
363
344
|
const entry = readHermesEntry(home);
|
|
364
345
|
if (entry.state === 'unreadable' || entry.state === 'unparseable') {
|
|
365
|
-
return {
|
|
366
|
-
registered: Registered.Unknown,
|
|
367
|
-
authMode: AuthMode.Unknown,
|
|
368
|
-
reason: `Hermes' config.yaml could not be ${entry.state === 'unreadable' ? 'read' : 'parsed'}`,
|
|
369
|
-
};
|
|
346
|
+
return unverified(`Hermes' config.yaml could not be ${entry.state === 'unreadable' ? 'read' : 'parsed'}`);
|
|
370
347
|
}
|
|
371
348
|
if (entry.state === 'absent')
|
|
372
|
-
return
|
|
349
|
+
return NOT_REGISTERED;
|
|
373
350
|
// `mcp add` saves a disabled entry on a failed connect, which connect refuses too.
|
|
374
351
|
const enabled = entry.state === 'enabled' ? {} : { connected: false };
|
|
375
352
|
// The .env key is Hermes-wide; the header template ties it here.
|
|
@@ -393,16 +370,12 @@ function probeOpenClaw() {
|
|
|
393
370
|
entries = fs.readdirSync(skillsDir);
|
|
394
371
|
}
|
|
395
372
|
catch {
|
|
396
|
-
return {
|
|
397
|
-
registered: Registered.Unknown,
|
|
398
|
-
authMode: AuthMode.Unknown,
|
|
399
|
-
reason: `no global skills directory at ${harnessDisplayPath('openclaw')}/skills; OpenClaw layout is unverified`,
|
|
400
|
-
};
|
|
373
|
+
return unverified(`no global skills directory at ${harnessDisplayPath('openclaw')}/skills; OpenClaw layout is unverified`);
|
|
401
374
|
}
|
|
402
375
|
// A bare substring also matched a skill merely named `not-tinyfish-thing`.
|
|
403
376
|
return entries.some((name) => SKILL_DIR_IS_TINYFISH.test(name))
|
|
404
377
|
? { registered: Registered.Yes, authMode: AuthMode.ApiKey, usesCliKey: true }
|
|
405
|
-
:
|
|
378
|
+
: NOT_REGISTERED;
|
|
406
379
|
}
|
|
407
380
|
// `opencode mcp list` prints no headers, so a key-authed registration is indistinguishable.
|
|
408
381
|
function probeOpencode() {
|
|
@@ -410,7 +383,7 @@ function probeOpencode() {
|
|
|
410
383
|
const probe = runProbe('opencode', args);
|
|
411
384
|
if (probe.outcome !== 'ran' || probe.exitCode !== 0) {
|
|
412
385
|
const reason = probeReason('opencode', args, probe);
|
|
413
|
-
return
|
|
386
|
+
return unverified(reason);
|
|
414
387
|
}
|
|
415
388
|
return fromMcpList('opencode', probe.output, AuthMode.Unknown, OPENCODE_ROW_URL);
|
|
416
389
|
}
|
|
@@ -440,11 +413,7 @@ function fromGrokPlugin() {
|
|
|
440
413
|
// repair that double-registers a working one. Anything but a list is unknown, not absent.
|
|
441
414
|
const shape = grokPluginSchema.safeParse(listed);
|
|
442
415
|
if (!shape.success) {
|
|
443
|
-
return
|
|
444
|
-
registered: Registered.Unknown,
|
|
445
|
-
authMode: AuthMode.Unknown,
|
|
446
|
-
reason: '`grok plugin list --json` returned an unexpected shape',
|
|
447
|
-
};
|
|
416
|
+
return unverified('`grok plugin list --json` returned an unexpected shape');
|
|
448
417
|
}
|
|
449
418
|
if (!shape.data.some((plugin) => plugin.name === 'tinyfish'))
|
|
450
419
|
return undefined;
|
|
@@ -477,36 +446,24 @@ function probeGrok() {
|
|
|
477
446
|
const args = ['mcp', 'list', '--json'];
|
|
478
447
|
const probe = runProbe('grok', args);
|
|
479
448
|
if (probe.outcome === 'unavailable') {
|
|
480
|
-
return
|
|
449
|
+
return unverified(probe.reason);
|
|
481
450
|
}
|
|
482
451
|
if (probe.exitCode !== 0) {
|
|
483
452
|
if (UNSUPPORTED_SUBCOMMAND.test(probe.output)) {
|
|
484
|
-
return
|
|
485
|
-
registered: Registered.Unknown,
|
|
486
|
-
authMode: AuthMode.Unknown,
|
|
487
|
-
reason: 'this Grok build has no `mcp list --json`',
|
|
488
|
-
};
|
|
453
|
+
return unverified('this Grok build has no `mcp list --json`');
|
|
489
454
|
}
|
|
490
|
-
return
|
|
491
|
-
registered: Registered.Unknown,
|
|
492
|
-
authMode: AuthMode.Unknown,
|
|
493
|
-
reason: probeReason('grok', args, probe),
|
|
494
|
-
};
|
|
455
|
+
return unverified(probeReason('grok', args, probe));
|
|
495
456
|
}
|
|
496
457
|
let entries;
|
|
497
458
|
try {
|
|
498
459
|
entries = grokListSchema.parse(JSON.parse(probe.stdout));
|
|
499
460
|
}
|
|
500
461
|
catch {
|
|
501
|
-
return
|
|
502
|
-
registered: Registered.Unknown,
|
|
503
|
-
authMode: AuthMode.Unknown,
|
|
504
|
-
reason: '`grok mcp list --json` did not return the expected shape',
|
|
505
|
-
};
|
|
462
|
+
return unverified('`grok mcp list --json` did not return the expected shape');
|
|
506
463
|
}
|
|
507
464
|
const entry = entries.find((server) => server.name === 'tinyfish');
|
|
508
465
|
if (!entry)
|
|
509
|
-
return fromGrokPlugin() ??
|
|
466
|
+
return fromGrokPlugin() ?? NOT_REGISTERED;
|
|
510
467
|
return {
|
|
511
468
|
registered: Registered.Yes,
|
|
512
469
|
...grokKeyAuth(entry.headers ?? {}),
|
|
@@ -549,12 +506,7 @@ export function detectRegistrations(harnesses) {
|
|
|
549
506
|
if (process.env['TINYFISH_DEBUG']) {
|
|
550
507
|
errLine(`probe ${detection.harness} threw: ${e instanceof Error ? e.message : String(e)}`);
|
|
551
508
|
}
|
|
552
|
-
return {
|
|
553
|
-
...base,
|
|
554
|
-
registered: Registered.Unknown,
|
|
555
|
-
authMode: AuthMode.Unknown,
|
|
556
|
-
reason: 'the probe failed unexpectedly',
|
|
557
|
-
};
|
|
509
|
+
return { ...base, ...unverified('the probe failed unexpectedly') };
|
|
558
510
|
}
|
|
559
511
|
});
|
|
560
512
|
}
|
|
@@ -6,9 +6,6 @@ import { AuthMode, Registered } from './harness-detect.js';
|
|
|
6
6
|
* opts out — a privacy switch must not fail closed on `true`.
|
|
7
7
|
*/
|
|
8
8
|
export declare function telemetryDisabled(): boolean;
|
|
9
|
-
export declare function isTrustedTelemetryOrigin(endpoint: string): boolean;
|
|
10
|
-
/** SHA-256 lookup key only, and only to trusted origins — the raw key never rides telemetry. */
|
|
11
|
-
export declare function telemetryHeaders(endpoint: string, apiKey?: string): Record<string, string>;
|
|
12
9
|
/**
|
|
13
10
|
* The one POST to `/api/cli/connect-event`, and the only place its security-relevant
|
|
14
11
|
* properties live: trusted-origin key gating, `redirect: "error"`, the 2s cap, notice
|
|
@@ -84,7 +81,7 @@ export declare const setupCompletedPayloadSchema: z.ZodObject<{
|
|
|
84
81
|
}>>;
|
|
85
82
|
connect_attempt_id: z.ZodOptional<z.ZodUUID>;
|
|
86
83
|
}, z.core.$strip>;
|
|
87
|
-
|
|
84
|
+
type SetupCompletedPayload = z.infer<typeof setupCompletedPayloadSchema>;
|
|
88
85
|
export type HarnessResult = z.infer<typeof harnessResultSchema>;
|
|
89
86
|
export type SetupMode = NonNullable<SetupCompletedPayload['mode']>;
|
|
90
87
|
export type UpgradeOutcome = 'updated' | 'failed' | 'interrupted';
|
|
@@ -93,12 +90,11 @@ export type UpgradeScope = 'both' | 'cli' | 'skill';
|
|
|
93
90
|
/** `toVersion` is null when the run was interrupted or the installed version could not be read. */
|
|
94
91
|
export declare function sendUpgradeCompleted(fromVersion: string, toVersion: string | null, outcome: UpgradeOutcome, scope: UpgradeScope, apiKey?: string): Promise<void>;
|
|
95
92
|
export declare function sendSetupCompleted(mcpUrl: string, harnesses: HarnessResult[], apiKey?: string, mode?: SetupMode, attemptId?: string): Promise<void>;
|
|
96
|
-
export declare const DOCTOR_HARNESS_SCOPES: readonly ["all", ...("openclaw" | "omp" | "grok" | "cursor" | "codex" | "hermes" | "opencode" | "claude-code")[]];
|
|
97
93
|
export declare const DOCTOR_ERROR_CLASSES: readonly ["command_not_found", "timeout", "spawn_error", "nonzero_exit", "unexpected_error"];
|
|
98
94
|
export type DoctorErrorClass = (typeof DOCTOR_ERROR_CLASSES)[number];
|
|
99
95
|
/** Derived, not read: the catch is generic, so only the code is trustworthy. */
|
|
100
96
|
export declare function doctorErrorClass(error: unknown): DoctorErrorClass;
|
|
101
|
-
|
|
97
|
+
declare const doctorCompletedPayloadSchema: z.ZodObject<{
|
|
102
98
|
outcome: z.ZodLiteral<"completed">;
|
|
103
99
|
schema_version: z.ZodInt;
|
|
104
100
|
cli_version: z.ZodString;
|
|
@@ -163,7 +159,7 @@ export declare const doctorCompletedPayloadSchema: z.ZodObject<{
|
|
|
163
159
|
hermes_plugin_version: z.ZodOptional<z.ZodString>;
|
|
164
160
|
hermes_plugin_expected_version: z.ZodOptional<z.ZodString>;
|
|
165
161
|
}, z.core.$strip>;
|
|
166
|
-
|
|
162
|
+
declare const doctorCouldNotRunPayloadSchema: z.ZodObject<{
|
|
167
163
|
outcome: z.ZodLiteral<"could_not_run">;
|
|
168
164
|
error_class: z.ZodEnum<{
|
|
169
165
|
timeout: "timeout";
|
|
@@ -20,7 +20,7 @@ export function telemetryDisabled() {
|
|
|
20
20
|
// `--url` accepts any http(s) URL, so a user-supplied origin must never receive the key.
|
|
21
21
|
const TRUSTED_TELEMETRY_HOST_SUFFIXES = ['.tinyfish.ai', '.tinyfish.io'];
|
|
22
22
|
const TRUSTED_TELEMETRY_LOOPBACK_HOSTS = ['localhost', '127.0.0.1', '[::1]'];
|
|
23
|
-
|
|
23
|
+
function isTrustedTelemetryOrigin(endpoint) {
|
|
24
24
|
let url;
|
|
25
25
|
try {
|
|
26
26
|
url = new URL(endpoint);
|
|
@@ -38,7 +38,7 @@ export function isTrustedTelemetryOrigin(endpoint) {
|
|
|
38
38
|
return TRUSTED_TELEMETRY_HOST_SUFFIXES.some((suffix) => host === suffix.slice(1) || host.endsWith(suffix));
|
|
39
39
|
}
|
|
40
40
|
/** SHA-256 lookup key only, and only to trusted origins — the raw key never rides telemetry. */
|
|
41
|
-
|
|
41
|
+
function telemetryHeaders(endpoint, apiKey) {
|
|
42
42
|
const headers = { 'content-type': 'application/json' };
|
|
43
43
|
if (apiKey && validateKeyFormat(apiKey) && isTrustedTelemetryOrigin(endpoint)) {
|
|
44
44
|
headers['x-api-key-lookup'] = createHash('sha256').update(apiKey).digest('hex');
|
|
@@ -210,7 +210,7 @@ export async function sendSetupCompleted(mcpUrl, harnesses, apiKey, mode, attemp
|
|
|
210
210
|
}
|
|
211
211
|
await postConnectEvent({ endpoint, body, attempts: 2, label: 'setup', apiKey });
|
|
212
212
|
}
|
|
213
|
-
|
|
213
|
+
const DOCTOR_HARNESS_SCOPES = ['all', ...ALL_HARNESSES];
|
|
214
214
|
// Mirrors the route's cap; drops what the route would 400 un-retried.
|
|
215
215
|
const versionSchema = z
|
|
216
216
|
.string()
|
|
@@ -237,7 +237,7 @@ export function doctorErrorClass(error) {
|
|
|
237
237
|
return 'timeout';
|
|
238
238
|
return code ? 'spawn_error' : 'unexpected_error';
|
|
239
239
|
}
|
|
240
|
-
|
|
240
|
+
const doctorCompletedPayloadSchema = z.object({
|
|
241
241
|
outcome: z.literal('completed'),
|
|
242
242
|
schema_version: z.int().positive(),
|
|
243
243
|
cli_version: z.string().max(32),
|
|
@@ -274,7 +274,7 @@ export const doctorCompletedPayloadSchema = z.object({
|
|
|
274
274
|
hermes_plugin_expected_version: versionSchema.optional(),
|
|
275
275
|
});
|
|
276
276
|
// Detection threw, so there is no report to aggregate: enumerated outcome and timing only.
|
|
277
|
-
|
|
277
|
+
const doctorCouldNotRunPayloadSchema = z.object({
|
|
278
278
|
outcome: z.literal('could_not_run'),
|
|
279
279
|
error_class: z.enum(DOCTOR_ERROR_CLASSES),
|
|
280
280
|
schema_version: z.int().positive(),
|
|
@@ -6,7 +6,7 @@ import { captureStdio, capturedOutput, replay, SKILL_INSTALL_TIMEOUT_MS, STEP_MA
|
|
|
6
6
|
import { CURSOR_SKILL_TARGET, NATIVE_MCP_CLIENTS, OPENCLAW, openclawSkillInstallArgs, } from './connect-clients.js';
|
|
7
7
|
import { ConnectInterruptedError, ConnectStepError, probeSupportVariant, spawnFailureDetail, spawnStepError, throwIfInterrupted, } from './connect-runtime.js';
|
|
8
8
|
import { CLI_AGENT_IDENTITY } from './constants.js';
|
|
9
|
-
import { errLine } from './output.js';
|
|
9
|
+
import { errLine, parseJson } from './output.js';
|
|
10
10
|
// Supports Hermes without node:util.styleText, so the installer still runs on Node 20.11.
|
|
11
11
|
const SKILLS_CLI_PACKAGE = 'skills@1.5.15';
|
|
12
12
|
const TINYFISH_WEB_SKILL_SOURCE = 'tinyfish-io/tinyfish-cookbook';
|
|
@@ -66,14 +66,6 @@ function skillListed(stdout) {
|
|
|
66
66
|
const parsed = skillListSchema.safeParse(parseJson(stdout));
|
|
67
67
|
return parsed.success && parsed.data.some((skill) => skill.name === TINYFISH_WEB_SKILL);
|
|
68
68
|
}
|
|
69
|
-
function parseJson(text) {
|
|
70
|
-
try {
|
|
71
|
-
return JSON.parse(text);
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
return undefined;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
69
|
export function installWebSkill(client, { verbose }) {
|
|
78
70
|
if (!client.skillAgent)
|
|
79
71
|
return;
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -21,14 +21,14 @@ export interface CliAgentRunParams extends AgentRunParams {
|
|
|
21
21
|
use_vault?: boolean;
|
|
22
22
|
credential_item_ids?: string[];
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
type VaultProvider = '1password' | 'bitwarden';
|
|
25
25
|
export interface VaultConnection {
|
|
26
26
|
id: string;
|
|
27
27
|
provider: VaultProvider;
|
|
28
28
|
connectionStatus: string;
|
|
29
29
|
lastValidatedAt: string | null;
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
interface VaultFieldMetadata {
|
|
32
32
|
fieldId: string;
|
|
33
33
|
label: string;
|
|
34
34
|
type: 'STRING' | 'CONCEALED' | 'OTP';
|
|
@@ -119,7 +119,7 @@ export interface BatchGetResponse {
|
|
|
119
119
|
data: Run[];
|
|
120
120
|
not_found: string[] | null;
|
|
121
121
|
}
|
|
122
|
-
|
|
122
|
+
interface BatchCancelResult {
|
|
123
123
|
run_id: string;
|
|
124
124
|
status: string;
|
|
125
125
|
cancelled_at: string | null;
|
|
@@ -157,3 +157,4 @@ export interface ProfileUploadResponse {
|
|
|
157
157
|
domains_updated: string[];
|
|
158
158
|
domains_failed?: string[];
|
|
159
159
|
}
|
|
160
|
+
export {};
|
package/dist/program.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createRequire } from 'module';
|
|
2
1
|
import { Command } from 'commander';
|
|
2
|
+
import { CLI_VERSION } from './lib/constants.js';
|
|
3
3
|
import { registerAuth } from './commands/auth.js';
|
|
4
4
|
import { registerBatch } from './commands/batch.js';
|
|
5
5
|
import { registerFetch } from './commands/fetch.js';
|
|
@@ -18,12 +18,11 @@ import { registerOnboard } from './commands/onboard.js';
|
|
|
18
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)('../package.json');
|
|
22
21
|
const program = new Command();
|
|
23
22
|
program
|
|
24
23
|
.name('tinyfish')
|
|
25
24
|
.description('TinyFish CLI — run web automations from your terminal or agent.')
|
|
26
|
-
.version(
|
|
25
|
+
.version(CLI_VERSION, '-V, --version', 'Show version')
|
|
27
26
|
.helpOption('-h, --help', 'Show help')
|
|
28
27
|
.addHelpCommand(false)
|
|
29
28
|
.enablePositionalOptions()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiny-fish/cli",
|
|
3
|
-
"version": "0.39.1-next.
|
|
3
|
+
"version": "0.39.1-next.311",
|
|
4
4
|
"description": "TinyFish CLI — run web automations from your terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -37,7 +37,6 @@
|
|
|
37
37
|
"@tiny-fish/sdk": "^0.5.0",
|
|
38
38
|
"commander": "^12.0.0",
|
|
39
39
|
"cross-spawn": "^7.0.6",
|
|
40
|
-
"globals": "^17.4.0",
|
|
41
40
|
"tldts": "^6.1.86",
|
|
42
41
|
"which": "^2.0.2",
|
|
43
42
|
"yaml": "^2.9.0",
|