@tiny-fish/cli 0.39.1-next.309 → 0.40.0
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 +109 -75
- package/dist/commands/doctor.js +3 -1
- 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-summary.js +4 -1
- package/dist/lib/connect-all-uninstall.js +12 -1
- package/dist/lib/connect-all.d.ts +4 -2
- package/dist/lib/connect-all.js +21 -32
- package/dist/lib/connect-clients.d.ts +11 -8
- package/dist/lib/connect-clients.js +53 -13
- 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/doctor-checks.d.ts +1 -0
- package/dist/lib/doctor-checks.js +38 -3
- package/dist/lib/doctor-report.d.ts +6 -0
- package/dist/lib/harness-detect.d.ts +2 -0
- package/dist/lib/harness-detect.js +11 -3
- package/dist/lib/harness-spec.d.ts +20 -39
- package/dist/lib/harness-spec.js +12 -31
- package/dist/lib/harness.js +2 -0
- package/dist/lib/hermes-config.d.ts +3 -0
- package/dist/lib/hermes-config.js +8 -4
- package/dist/lib/hermes-env.d.ts +7 -2
- package/dist/lib/hermes-env.js +7 -6
- package/dist/lib/hermes-plugin.d.ts +4 -0
- package/dist/lib/hermes-plugin.js +17 -9
- package/dist/lib/output.d.ts +2 -0
- package/dist/lib/output.js +9 -0
- package/dist/lib/pi-config.d.ts +26 -0
- package/dist/lib/pi-config.js +111 -0
- package/dist/lib/registration-detect.d.ts +0 -1
- package/dist/lib/registration-detect.js +60 -81
- package/dist/lib/setup-telemetry.d.ts +13 -7
- package/dist/lib/setup-telemetry.js +5 -5
- package/dist/lib/skill-install.d.ts +1 -0
- package/dist/lib/skill-install.js +43 -44
- package/dist/lib/types.d.ts +4 -3
- package/dist/program.js +2 -3
- package/package.json +1 -2
package/dist/commands/runs.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
1
2
|
import { RunStatus } from '@tiny-fish/sdk';
|
|
2
3
|
import { getApiKey } from '../lib/auth.js';
|
|
3
4
|
import { cancelRun, getRun, getRunSteps, listRuns } from '../lib/client.js';
|
|
@@ -16,6 +17,7 @@ function formatStep(step, index) {
|
|
|
16
17
|
const meta = [step.status, step.duration].filter(Boolean).join(', ');
|
|
17
18
|
return `${String(index + 1).padStart(2, ' ')}. ${action}${meta ? ` (${meta})` : ''}`;
|
|
18
19
|
}
|
|
20
|
+
const MAX_TIMER_MS = 2 ** 31 - 1;
|
|
19
21
|
function parsePositiveInt(value, flag, defaultMs, minMs = 1) {
|
|
20
22
|
if (value === undefined)
|
|
21
23
|
return defaultMs;
|
|
@@ -27,56 +29,24 @@ function parsePositiveInt(value, flag, defaultMs, minMs = 1) {
|
|
|
27
29
|
process.exit(1);
|
|
28
30
|
}
|
|
29
31
|
const parsed = Number(value);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
// Node timers take a signed 32-bit delay; larger overflows to 1ms or throws.
|
|
33
|
+
if (parsed < minMs || parsed > MAX_TIMER_MS) {
|
|
34
|
+
err({
|
|
35
|
+
error: `Invalid ${flag} "${value}". Must be an integer between ${minMs} and ${MAX_TIMER_MS}ms.`,
|
|
36
|
+
});
|
|
32
37
|
process.exit(1);
|
|
33
38
|
}
|
|
34
39
|
return parsed;
|
|
35
40
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
if (signal.aborted) {
|
|
44
|
-
reject(new Error('watch aborted'));
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
const timer = setTimeout(() => reject(new Error('watch deadline exceeded')), remaining);
|
|
48
|
-
const onAbort = () => {
|
|
49
|
-
clearTimeout(timer);
|
|
50
|
-
reject(new Error('watch aborted'));
|
|
51
|
-
};
|
|
52
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
53
|
-
promise.then((v) => {
|
|
54
|
-
clearTimeout(timer);
|
|
55
|
-
signal.removeEventListener('abort', onAbort);
|
|
56
|
-
resolve(v);
|
|
57
|
-
}, (e) => {
|
|
58
|
-
clearTimeout(timer);
|
|
59
|
-
signal.removeEventListener('abort', onAbort);
|
|
60
|
-
reject(e);
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
function sleep(ms, signal) {
|
|
65
|
-
return new Promise((resolve, reject) => {
|
|
66
|
-
if (signal.aborted) {
|
|
67
|
-
reject(new Error('aborted'));
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const timer = setTimeout(() => {
|
|
71
|
-
signal.removeEventListener('abort', onAbort);
|
|
72
|
-
resolve();
|
|
73
|
-
}, ms);
|
|
74
|
-
const onAbort = () => {
|
|
75
|
-
clearTimeout(timer);
|
|
76
|
-
reject(new Error('aborted'));
|
|
77
|
-
};
|
|
78
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
41
|
+
/** The SDK calls take no signal, so the race is the only cancel. */
|
|
42
|
+
function withSignal(promise, signal) {
|
|
43
|
+
if (signal.aborted)
|
|
44
|
+
return Promise.reject(signal.reason);
|
|
45
|
+
const done = new AbortController();
|
|
46
|
+
const aborted = new Promise((_, reject) => {
|
|
47
|
+
signal.addEventListener('abort', () => reject(signal.reason), { signal: done.signal });
|
|
79
48
|
});
|
|
49
|
+
return Promise.race([promise, aborted]).finally(() => done.abort());
|
|
80
50
|
}
|
|
81
51
|
async function runsListAction(opts) {
|
|
82
52
|
// Validate --status
|
|
@@ -193,18 +163,20 @@ async function runsWatchAction(runId, opts) {
|
|
|
193
163
|
};
|
|
194
164
|
process.once('SIGINT', onSigint);
|
|
195
165
|
const deadline = Date.now() + timeoutMs;
|
|
166
|
+
const timedOut = AbortSignal.timeout(timeoutMs);
|
|
167
|
+
const signal = AbortSignal.any([controller.signal, timedOut]);
|
|
196
168
|
const seen = new Set();
|
|
197
169
|
try {
|
|
198
|
-
const initial = await
|
|
170
|
+
const initial = await withSignal(getRun(runId, apiKey), signal);
|
|
199
171
|
if (TERMINAL_STATUSES.includes(initial.status)) {
|
|
200
172
|
err({
|
|
201
173
|
error: `Run ${runId} is already ${initial.status}; nothing to watch. Use 'tinyfish agent run steps ${runId}' for the trace.`,
|
|
202
174
|
});
|
|
203
175
|
process.exit(1);
|
|
204
176
|
}
|
|
205
|
-
while (!
|
|
177
|
+
while (!signal.aborted) {
|
|
206
178
|
// One API call per tick — the steps endpoint also returns the run's current status.
|
|
207
|
-
const tick = await
|
|
179
|
+
const tick = await withSignal(getRunSteps(runId, apiKey), signal);
|
|
208
180
|
let nextIndex = seen.size;
|
|
209
181
|
for (const step of tick.steps) {
|
|
210
182
|
if (seen.has(step.id))
|
|
@@ -228,22 +200,16 @@ async function runsWatchAction(runId, opts) {
|
|
|
228
200
|
err({ error: `Watch timed out after ${timeoutMs}ms (run still ${tick.status}).` });
|
|
229
201
|
process.exit(1);
|
|
230
202
|
}
|
|
231
|
-
|
|
232
|
-
await sleep(intervalMs, controller.signal);
|
|
233
|
-
}
|
|
234
|
-
catch {
|
|
235
|
-
return; // aborted
|
|
236
|
-
}
|
|
203
|
+
await sleep(intervalMs, undefined, { signal });
|
|
237
204
|
}
|
|
238
205
|
}
|
|
239
206
|
catch (e) {
|
|
240
|
-
if (
|
|
207
|
+
if (timedOut.aborted) {
|
|
241
208
|
err({ error: `Watch timed out after ${timeoutMs}ms.` });
|
|
242
209
|
process.exit(1);
|
|
243
210
|
}
|
|
244
|
-
if (
|
|
245
|
-
return; // SIGINT handler already
|
|
246
|
-
}
|
|
211
|
+
if (controller.signal.aborted)
|
|
212
|
+
return; // SIGINT handler already exited 130
|
|
247
213
|
handleApiError(e);
|
|
248
214
|
}
|
|
249
215
|
finally {
|
package/dist/lib/auth.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare const CONNECT_SOURCE = "tinyfish_cli";
|
|
|
6
6
|
export declare const CONNECT_ATTEMPT_ENV = "TINYFISH_CONNECT_ATTEMPT_ID";
|
|
7
7
|
/** Mirrors ConnectAuthMode; duplicated to keep auth.ts free of connect-runtime imports. */
|
|
8
8
|
export type RecordedAuthMode = 'api-key' | 'oauth' | 'deferred';
|
|
9
|
-
|
|
9
|
+
interface ConnectEntry {
|
|
10
10
|
attempt_id: string;
|
|
11
11
|
auth_mode?: RecordedAuthMode;
|
|
12
12
|
}
|
|
@@ -43,8 +43,6 @@ interface EnvironmentOptions {
|
|
|
43
43
|
}
|
|
44
44
|
export declare function persistApiKeyToEnvironment(apiKey: string, options?: EnvironmentOptions): void;
|
|
45
45
|
export declare function clearConfig(): boolean;
|
|
46
|
-
/** The one rule that is ours to enforce: the key becomes an HTTP header, so CR/LF cannot pass. */
|
|
47
|
-
export declare function isHeaderSafe(key: string): boolean;
|
|
48
46
|
export declare function validateKeyFormat(key: string): boolean;
|
|
49
47
|
export declare function maskKey(key: string): string;
|
|
50
48
|
export type KeySource = 'explicit' | 'env' | 'config' | 'none';
|
package/dist/lib/auth.js
CHANGED
|
@@ -236,7 +236,7 @@ export function clearConfig() {
|
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
/** The one rule that is ours to enforce: the key becomes an HTTP header, so CR/LF cannot pass. */
|
|
239
|
-
|
|
239
|
+
function isHeaderSafe(key) {
|
|
240
240
|
for (let i = 0; i < key.length; i += 1) {
|
|
241
241
|
if (key.charCodeAt(i) < 0x20)
|
|
242
242
|
return false;
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
export declare function claudeDir(): string;
|
|
2
1
|
export declare function claudeSettingsPath(): string;
|
|
3
2
|
export declare function claudeMdPath(): string;
|
|
4
3
|
export declare const TINYFISH_PERMISSION = "Bash(tinyfish:*)";
|
|
5
4
|
export declare const CLAUDE_MD_MARKER = "<!-- tinyfish-configure-claude -->";
|
|
6
|
-
export declare const CLAUDE_MD_BLOCK = "<!-- tinyfish-configure-claude -->\n## WebSearch or Search\nWhen looking to use Search or WebSearch, always use the TinyFish Web Search tool, run `tinyfish search query \"<query>\"`\nOnly fall back to native Search when you get rate limited by tinyfish (for a minute, then you can try using tinyfish again)\n\n## WebFetch or Fetch\nWhen looking to use Fetch or WebFetch, always use the TinyFish Fetch tool, run `tinyfish fetch content get \"<url>\"`\nOnly fall back to native Fetch when you get rate limited by tinyfish (for a minute, then you can try using tinyfish again)\n<!-- tinyfish-configure-claude -->";
|
|
7
5
|
export declare function readSettingsJson(): Record<string, unknown>;
|
|
8
6
|
export declare function writeSettingsJson(settings: Record<string, unknown>): void;
|
|
9
7
|
export declare function mergeSettings(existing: Record<string, unknown>): Record<string, unknown>;
|
|
10
8
|
export declare function removeFromSettings(existing: Record<string, unknown>): Record<string, unknown>;
|
|
11
9
|
export declare function readClaudeMd(): string;
|
|
12
10
|
export declare function writeClaudeMd(content: string): void;
|
|
13
|
-
export declare function hasMarker(content: string): boolean;
|
|
14
|
-
export declare function hasLegacyBlock(content: string): boolean;
|
|
15
11
|
export declare function mergeClaudeMd(existing: string): {
|
|
16
12
|
content: string;
|
|
17
13
|
replaced: boolean;
|
|
@@ -2,7 +2,7 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as os from 'os';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
// ─── Path helpers ─────────────────────────────────────────────────────────────
|
|
5
|
-
|
|
5
|
+
function claudeDir() {
|
|
6
6
|
return path.join(os.homedir(), '.claude');
|
|
7
7
|
}
|
|
8
8
|
export function claudeSettingsPath() {
|
|
@@ -40,7 +40,7 @@ const TINYFISH_SUBAGENT_HOOK = {
|
|
|
40
40
|
],
|
|
41
41
|
};
|
|
42
42
|
export const CLAUDE_MD_MARKER = '<!-- tinyfish-configure-claude -->';
|
|
43
|
-
|
|
43
|
+
const CLAUDE_MD_BLOCK = `${CLAUDE_MD_MARKER}
|
|
44
44
|
## WebSearch or Search
|
|
45
45
|
When looking to use Search or WebSearch, always use the TinyFish Web Search tool, run \`tinyfish search query "<query>"\`
|
|
46
46
|
Only fall back to native Search when you get rate limited by tinyfish (for a minute, then you can try using tinyfish again)
|
|
@@ -159,10 +159,10 @@ function escapeRegExp(s) {
|
|
|
159
159
|
}
|
|
160
160
|
const MARKER_REGEX = new RegExp(`${escapeRegExp(CLAUDE_MD_MARKER)}[\\s\\S]*?${escapeRegExp(CLAUDE_MD_MARKER)}`, 'm');
|
|
161
161
|
const LEGACY_REGEX = /## Web(?:Search|Seach) or Search[\s\S]*?(?:tinyfish fetch[^\n]*\n(?:[^\n]*tinyfish[^\n]*\n)*[^\n]*)\n*/m;
|
|
162
|
-
|
|
162
|
+
function hasMarker(content) {
|
|
163
163
|
return content.includes(CLAUDE_MD_MARKER);
|
|
164
164
|
}
|
|
165
|
-
|
|
165
|
+
function hasLegacyBlock(content) {
|
|
166
166
|
return !content.includes(CLAUDE_MD_MARKER) && LEGACY_REGEX.test(content);
|
|
167
167
|
}
|
|
168
168
|
export function mergeClaudeMd(existing) {
|
|
@@ -20,7 +20,6 @@ export type TakeHoistedCliInstall = () => {
|
|
|
20
20
|
outcome: HoistedCliInstall;
|
|
21
21
|
firstTake: boolean;
|
|
22
22
|
};
|
|
23
|
-
export { HoistedFailureReportedError } from './connect-runtime.js';
|
|
24
23
|
export declare function captureStdio(verbose: boolean): {
|
|
25
24
|
stdio: "inherit";
|
|
26
25
|
timeout: number;
|
package/dist/lib/cli-install.js
CHANGED
|
@@ -7,7 +7,6 @@ export const SKILL_INSTALL_TIMEOUT_MS = 120_000;
|
|
|
7
7
|
const TINYFISH_CLI_INSTALL_SPEC = `${TINYFISH_CLI_PACKAGE}@latest`;
|
|
8
8
|
// spawnSync caps piped output at 1 MiB by default.
|
|
9
9
|
export const STEP_MAX_BUFFER = 10 * 1024 * 1024;
|
|
10
|
-
export { HoistedFailureReportedError } from './connect-runtime.js';
|
|
11
10
|
export function captureStdio(verbose) {
|
|
12
11
|
return verbose
|
|
13
12
|
? { stdio: 'inherit', timeout: SKILL_INSTALL_TIMEOUT_MS }
|
package/dist/lib/client.js
CHANGED
|
@@ -108,6 +108,9 @@ function createSdkClient(apiKey, timeout, baseUrl) {
|
|
|
108
108
|
...(timeout !== undefined ? { timeout } : {}),
|
|
109
109
|
});
|
|
110
110
|
}
|
|
111
|
+
function sdk(apiKey, call) {
|
|
112
|
+
return call(createSdkClient(apiKey)).catch(rethrowSdkError);
|
|
113
|
+
}
|
|
111
114
|
function rethrowSdkError(error) {
|
|
112
115
|
if (error instanceof APIStatusError) {
|
|
113
116
|
throw new ApiError(error.statusCode, error.message);
|
|
@@ -136,14 +139,8 @@ const batchGetResponseSchema = z.object({
|
|
|
136
139
|
data: z.array(runSchema),
|
|
137
140
|
not_found: z.array(z.string()).nullable(),
|
|
138
141
|
});
|
|
139
|
-
const batchCancelResultSchema = z.object({
|
|
140
|
-
run_id: z.string(),
|
|
141
|
-
status: z.string(),
|
|
142
|
-
cancelled_at: z.string().nullable(),
|
|
143
|
-
message: z.string().nullable(),
|
|
144
|
-
});
|
|
145
142
|
const batchCancelResponseSchema = z.object({
|
|
146
|
-
results: z.array(
|
|
143
|
+
results: z.array(cancelRunResponseSchema),
|
|
147
144
|
not_found: z.array(z.string()).nullable(),
|
|
148
145
|
});
|
|
149
146
|
const runStepSchema = z.object({
|
|
@@ -219,27 +216,21 @@ async function* parseSseStream(stream) {
|
|
|
219
216
|
reader.releaseLock();
|
|
220
217
|
}
|
|
221
218
|
}
|
|
222
|
-
export
|
|
223
|
-
|
|
224
|
-
const response = await
|
|
219
|
+
export function runSync(req, apiKey) {
|
|
220
|
+
return sdk(apiKey, async (client) => {
|
|
221
|
+
const response = await client.post('/v1/automation/run', {
|
|
225
222
|
json: req,
|
|
226
223
|
});
|
|
227
224
|
return parseWithSchema(agentRunResponseSchema, response, 'Invalid agent run response');
|
|
228
|
-
}
|
|
229
|
-
catch (error) {
|
|
230
|
-
rethrowSdkError(error);
|
|
231
|
-
}
|
|
225
|
+
});
|
|
232
226
|
}
|
|
233
|
-
export
|
|
234
|
-
|
|
235
|
-
const response = await
|
|
227
|
+
export function runAsync(req, apiKey) {
|
|
228
|
+
return sdk(apiKey, async (client) => {
|
|
229
|
+
const response = await client.post('/v1/automation/run-async', {
|
|
236
230
|
json: req,
|
|
237
231
|
});
|
|
238
232
|
return parseWithSchema(agentRunAsyncResponseSchema, response, 'Invalid async run response');
|
|
239
|
-
}
|
|
240
|
-
catch (error) {
|
|
241
|
-
rethrowSdkError(error);
|
|
242
|
-
}
|
|
233
|
+
});
|
|
243
234
|
}
|
|
244
235
|
export async function* runStream(req, apiKey, signal) {
|
|
245
236
|
let stream = null;
|
|
@@ -268,110 +259,75 @@ export async function* runStream(req, apiKey, signal) {
|
|
|
268
259
|
}
|
|
269
260
|
}
|
|
270
261
|
}
|
|
271
|
-
export
|
|
272
|
-
|
|
273
|
-
return await createSdkClient(apiKey, timeout, baseUrl).runs.list(opts);
|
|
274
|
-
}
|
|
275
|
-
catch (error) {
|
|
276
|
-
rethrowSdkError(error);
|
|
277
|
-
}
|
|
262
|
+
export function listRuns(opts, apiKey, timeout, baseUrl) {
|
|
263
|
+
return createSdkClient(apiKey, timeout, baseUrl).runs.list(opts).catch(rethrowSdkError);
|
|
278
264
|
}
|
|
279
|
-
export
|
|
280
|
-
|
|
281
|
-
return await createSdkClient(apiKey).runs.get(runId);
|
|
282
|
-
}
|
|
283
|
-
catch (error) {
|
|
284
|
-
rethrowSdkError(error);
|
|
285
|
-
}
|
|
265
|
+
export function getRun(runId, apiKey) {
|
|
266
|
+
return sdk(apiKey, (client) => client.runs.get(runId));
|
|
286
267
|
}
|
|
287
|
-
export
|
|
288
|
-
|
|
268
|
+
export function getRunSteps(runId, apiKey) {
|
|
269
|
+
return sdk(apiKey, async (client) => {
|
|
289
270
|
// The public GET /v1/runs/:id route returns the base run payload plus `steps`;
|
|
290
271
|
// the installed SDK runSchema only models the base run, so steps need this local schema.
|
|
291
|
-
const response = await
|
|
272
|
+
const response = await client.get(`/v1/runs/${encodeURIComponent(runId)}`, {
|
|
273
|
+
params: { screenshots: 'none' },
|
|
274
|
+
});
|
|
292
275
|
return parseWithSchema(runStepsResponseSchema, response, 'Invalid run steps response');
|
|
293
|
-
}
|
|
294
|
-
catch (error) {
|
|
295
|
-
rethrowSdkError(error);
|
|
296
|
-
}
|
|
276
|
+
});
|
|
297
277
|
}
|
|
298
|
-
export
|
|
299
|
-
|
|
300
|
-
const response = await
|
|
278
|
+
export function searchQuery(params, apiKey) {
|
|
279
|
+
return sdk(apiKey, async (client) => {
|
|
280
|
+
const response = await client.search.query(params);
|
|
301
281
|
return searchQueryResponseSchema.parse(response);
|
|
302
|
-
}
|
|
303
|
-
catch (error) {
|
|
304
|
-
rethrowSdkError(error);
|
|
305
|
-
}
|
|
282
|
+
});
|
|
306
283
|
}
|
|
307
|
-
export
|
|
308
|
-
|
|
309
|
-
const response = await
|
|
284
|
+
export function fetchContentGet(params, apiKey) {
|
|
285
|
+
return sdk(apiKey, async (client) => {
|
|
286
|
+
const response = await client.fetch.getContents(params);
|
|
310
287
|
return fetchResponseSchema.parse(response);
|
|
311
|
-
}
|
|
312
|
-
catch (error) {
|
|
313
|
-
rethrowSdkError(error);
|
|
314
|
-
}
|
|
288
|
+
});
|
|
315
289
|
}
|
|
316
|
-
export
|
|
317
|
-
|
|
318
|
-
const response = await
|
|
290
|
+
export function browserSessionCreate(params, apiKey) {
|
|
291
|
+
return sdk(apiKey, async (client) => {
|
|
292
|
+
const response = await client.post('/v1/browser', { json: params });
|
|
319
293
|
return browserSessionSchema.parse(response);
|
|
320
|
-
}
|
|
321
|
-
catch (error) {
|
|
322
|
-
rethrowSdkError(error);
|
|
323
|
-
}
|
|
294
|
+
});
|
|
324
295
|
}
|
|
325
|
-
export
|
|
326
|
-
|
|
327
|
-
const response = await
|
|
296
|
+
export function cancelRun(runId, apiKey) {
|
|
297
|
+
return sdk(apiKey, async (client) => {
|
|
298
|
+
const response = await client.post(`/v1/runs/${encodeURIComponent(runId)}/cancel`, {
|
|
299
|
+
json: {},
|
|
300
|
+
});
|
|
328
301
|
return parseWithSchema(cancelRunResponseSchema, response, 'Invalid cancel run response');
|
|
329
|
-
}
|
|
330
|
-
catch (error) {
|
|
331
|
-
rethrowSdkError(error);
|
|
332
|
-
}
|
|
302
|
+
});
|
|
333
303
|
}
|
|
334
304
|
// ── Wallet ──────────────────────────────────────────────────────────────────
|
|
335
|
-
export
|
|
336
|
-
|
|
337
|
-
return await createSdkClient(apiKey).wallet.get();
|
|
338
|
-
}
|
|
339
|
-
catch (error) {
|
|
340
|
-
rethrowSdkError(error);
|
|
341
|
-
}
|
|
305
|
+
export function getWallet(apiKey) {
|
|
306
|
+
return sdk(apiKey, (client) => client.wallet.get());
|
|
342
307
|
}
|
|
343
|
-
export
|
|
344
|
-
|
|
345
|
-
const response = await
|
|
308
|
+
export function submitBatch(req, apiKey) {
|
|
309
|
+
return sdk(apiKey, async (client) => {
|
|
310
|
+
const response = await client.post('/v1/automation/run-batch', {
|
|
346
311
|
json: req,
|
|
347
312
|
});
|
|
348
313
|
return parseWithSchema(batchRunResponseSchema, response, 'Invalid batch run response');
|
|
349
|
-
}
|
|
350
|
-
catch (error) {
|
|
351
|
-
rethrowSdkError(error);
|
|
352
|
-
}
|
|
314
|
+
});
|
|
353
315
|
}
|
|
354
|
-
export
|
|
355
|
-
|
|
356
|
-
const response = await
|
|
316
|
+
export function getBatchRuns(runIds, apiKey) {
|
|
317
|
+
return sdk(apiKey, async (client) => {
|
|
318
|
+
const response = await client.post('/v1/runs/batch', {
|
|
357
319
|
json: { run_ids: runIds },
|
|
358
320
|
});
|
|
359
321
|
return parseWithSchema(batchGetResponseSchema, response, 'Invalid batch get response');
|
|
360
|
-
}
|
|
361
|
-
catch (error) {
|
|
362
|
-
rethrowSdkError(error);
|
|
363
|
-
}
|
|
322
|
+
});
|
|
364
323
|
}
|
|
365
|
-
export
|
|
366
|
-
|
|
367
|
-
const response = await
|
|
324
|
+
export function cancelBatchRuns(runIds, apiKey) {
|
|
325
|
+
return sdk(apiKey, async (client) => {
|
|
326
|
+
const response = await client.post('/v1/runs/batch/cancel', {
|
|
368
327
|
json: { run_ids: runIds },
|
|
369
328
|
});
|
|
370
329
|
return parseWithSchema(batchCancelResponseSchema, response, 'Invalid batch cancel response');
|
|
371
|
-
}
|
|
372
|
-
catch (error) {
|
|
373
|
-
rethrowSdkError(error);
|
|
374
|
-
}
|
|
330
|
+
});
|
|
375
331
|
}
|
|
376
332
|
const profileCreateResponseSchema = z.object({
|
|
377
333
|
id: z.string(),
|
|
@@ -384,25 +340,19 @@ const profileUploadResponseSchema = z.object({
|
|
|
384
340
|
domains_updated: z.array(z.string()),
|
|
385
341
|
domains_failed: z.array(z.string()).optional(),
|
|
386
342
|
});
|
|
387
|
-
export
|
|
388
|
-
|
|
389
|
-
const response = await
|
|
343
|
+
export function profileCreate(name, apiKey) {
|
|
344
|
+
return sdk(apiKey, async (client) => {
|
|
345
|
+
const response = await client.post('/v1/profiles', {
|
|
390
346
|
json: { name },
|
|
391
347
|
});
|
|
392
348
|
return parseWithSchema(profileCreateResponseSchema, response, 'Invalid profile create response');
|
|
393
|
-
}
|
|
394
|
-
catch (error) {
|
|
395
|
-
rethrowSdkError(error);
|
|
396
|
-
}
|
|
349
|
+
});
|
|
397
350
|
}
|
|
398
|
-
export
|
|
399
|
-
|
|
400
|
-
const response = await
|
|
351
|
+
export function profileUpload(profileId, cookies, apiKey) {
|
|
352
|
+
return sdk(apiKey, async (client) => {
|
|
353
|
+
const response = await client.post(`/v1/profiles/${encodeURIComponent(profileId)}/upload`, { json: { cookies } });
|
|
401
354
|
return parseWithSchema(profileUploadResponseSchema, response, 'Invalid profile upload response');
|
|
402
|
-
}
|
|
403
|
-
catch (error) {
|
|
404
|
-
rethrowSdkError(error);
|
|
405
|
-
}
|
|
355
|
+
});
|
|
406
356
|
}
|
|
407
357
|
// ── Vault ───────────────────────────────────────────────────────────────────
|
|
408
358
|
// /v1/vault/* returns camelCase fields (unlike the snake_case rest of the API).
|
|
@@ -451,52 +401,37 @@ const vaultItemsSyncResponseSchema = z.object({
|
|
|
451
401
|
removed: z.number(),
|
|
452
402
|
}),
|
|
453
403
|
});
|
|
454
|
-
export
|
|
455
|
-
|
|
456
|
-
const response = await
|
|
404
|
+
export function vaultConnections(apiKey) {
|
|
405
|
+
return sdk(apiKey, async (client) => {
|
|
406
|
+
const response = await client.get('/v1/vault/connections');
|
|
457
407
|
return parseWithSchema(vaultConnectionsListResponseSchema, response, 'Invalid vault connections response');
|
|
458
|
-
}
|
|
459
|
-
catch (error) {
|
|
460
|
-
rethrowSdkError(error);
|
|
461
|
-
}
|
|
408
|
+
});
|
|
462
409
|
}
|
|
463
|
-
export
|
|
464
|
-
|
|
465
|
-
const response = await
|
|
410
|
+
export function vaultConnect(req, apiKey) {
|
|
411
|
+
return sdk(apiKey, async (client) => {
|
|
412
|
+
const response = await client.post('/v1/vault/connections', {
|
|
466
413
|
json: req,
|
|
467
414
|
});
|
|
468
415
|
return parseWithSchema(vaultConnectResponseSchema, response, 'Invalid vault connect response');
|
|
469
|
-
}
|
|
470
|
-
catch (error) {
|
|
471
|
-
rethrowSdkError(error);
|
|
472
|
-
}
|
|
416
|
+
});
|
|
473
417
|
}
|
|
474
|
-
export
|
|
475
|
-
|
|
476
|
-
const response = await
|
|
418
|
+
export function vaultDisconnect(connectionId, apiKey) {
|
|
419
|
+
return sdk(apiKey, async (client) => {
|
|
420
|
+
const response = await client.del(`/v1/vault/connections/${encodeURIComponent(connectionId)}`);
|
|
477
421
|
return parseWithSchema(vaultDisconnectResponseSchema, response, 'Invalid vault disconnect response');
|
|
478
|
-
}
|
|
479
|
-
catch (error) {
|
|
480
|
-
rethrowSdkError(error);
|
|
481
|
-
}
|
|
422
|
+
});
|
|
482
423
|
}
|
|
483
|
-
export
|
|
484
|
-
|
|
485
|
-
const response = await
|
|
424
|
+
export function vaultItems(apiKey) {
|
|
425
|
+
return sdk(apiKey, async (client) => {
|
|
426
|
+
const response = await client.get('/v1/vault/items');
|
|
486
427
|
return parseWithSchema(vaultItemsListResponseSchema, response, 'Invalid vault items response');
|
|
487
|
-
}
|
|
488
|
-
catch (error) {
|
|
489
|
-
rethrowSdkError(error);
|
|
490
|
-
}
|
|
428
|
+
});
|
|
491
429
|
}
|
|
492
|
-
export
|
|
493
|
-
|
|
494
|
-
const response = await
|
|
430
|
+
export function vaultSync(apiKey) {
|
|
431
|
+
return sdk(apiKey, async (client) => {
|
|
432
|
+
const response = await client.post('/v1/vault/items/sync', {
|
|
495
433
|
json: {},
|
|
496
434
|
});
|
|
497
435
|
return parseWithSchema(vaultItemsSyncResponseSchema, response, 'Invalid vault sync response');
|
|
498
|
-
}
|
|
499
|
-
catch (error) {
|
|
500
|
-
rethrowSdkError(error);
|
|
501
|
-
}
|
|
436
|
+
});
|
|
502
437
|
}
|
|
@@ -19,7 +19,7 @@ const NO_BROWSER_REASONS = new Set([
|
|
|
19
19
|
// Derived: these block `connect` on a browser sign-in when keyless.
|
|
20
20
|
const BROWSER_SIGN_IN = new Set(ALL_HARNESSES.filter((harness) => {
|
|
21
21
|
const native = NATIVE_BY_HARNESS.get(harness);
|
|
22
|
-
return !!native?.loginArgs
|
|
22
|
+
return !!native?.loginArgs;
|
|
23
23
|
}));
|
|
24
24
|
// Derived: these refuse a keyless install, so no sign-in helps.
|
|
25
25
|
const KEY_REQUIRED = new Set(ALL_HARNESSES.filter((harness) => !!NATIVE_BY_HARNESS.get(harness)?.keyRequired));
|
|
@@ -9,7 +9,7 @@ const HIDDEN_ONCE_SOMETHING_WORKED = new Set([
|
|
|
9
9
|
]);
|
|
10
10
|
/** A stale config dir is a detection signal with nothing behind it. */
|
|
11
11
|
export function actionable(result) {
|
|
12
|
-
return result.detected && result.outcome !== 'stale_config';
|
|
12
|
+
return (result.detected && result.outcome !== 'stale_config' && result.outcome !== 'needs_first_run');
|
|
13
13
|
}
|
|
14
14
|
function absentSummaryLine(result, uninstall) {
|
|
15
15
|
const name = DISPLAY_NAMES[result.harness];
|
|
@@ -29,6 +29,9 @@ function skippedSummaryLine(result) {
|
|
|
29
29
|
if (result.outcome === 'stale_config') {
|
|
30
30
|
return `… ${name} — config at ${result.configPath} but no \`${HARNESS_COMMANDS[result.harness]}\` on PATH, skipped. Install ${name}, then run: ${result.fixCommand}`;
|
|
31
31
|
}
|
|
32
|
+
if (result.outcome === 'needs_first_run') {
|
|
33
|
+
return `… ${name} — installed but never run, so ${result.configPath} does not exist yet, skipped. Run \`${HARNESS_COMMANDS[result.harness]}\` once, then: ${result.fixCommand}`;
|
|
34
|
+
}
|
|
32
35
|
if (result.outcome === 'not_reached') {
|
|
33
36
|
return `… ${name} — not reached, the run stopped early. Fix: ${result.fixCommand}`;
|
|
34
37
|
}
|
|
@@ -3,6 +3,7 @@ import { NATIVE_BY_HARNESS, openclawSkillUninstall } from './connect-clients.js'
|
|
|
3
3
|
import { ConnectInterruptedError, spawnRemoval } from './connect-runtime.js';
|
|
4
4
|
import { removeCursorMcpServer, planCursorWrite } from './cursor-config.js';
|
|
5
5
|
import { planOmpWrite, removeOmpMcpServer } from './omp-config.js';
|
|
6
|
+
import { piMcpPath, planPiWrite, removePiMcpServer } from './pi-config.js';
|
|
6
7
|
import { HERMES_KEY_VAR, hermesEnvPath, removeHermesKey, resolveHermesHome } from './hermes-env.js';
|
|
7
8
|
import { clearHermesWebBackends, HERMES_PLUGIN_SHA } from './hermes-plugin.js';
|
|
8
9
|
import { HARNESS_DISPLAY_NAMES as DISPLAY_NAMES } from './harness-detect.js';
|
|
@@ -14,6 +15,8 @@ export function planText(harness, mcpUrl, apiKey) {
|
|
|
14
15
|
return planCursorWrite(mcpUrl, apiKey);
|
|
15
16
|
if (harness === 'omp')
|
|
16
17
|
return planOmpWrite(mcpUrl, apiKey);
|
|
18
|
+
if (harness === 'pi')
|
|
19
|
+
return planPiWrite(mcpUrl, apiKey);
|
|
17
20
|
// Only the keyed path seeds the .env; a keyless Hermes install writes nothing.
|
|
18
21
|
if (harness === 'hermes' && apiKey) {
|
|
19
22
|
return (`would run \`tinyfish connect hermes\` (harness-owned MCP write; the CLI writes ` +
|
|
@@ -29,6 +32,9 @@ export function uninstallPlanText(harness) {
|
|
|
29
32
|
if (harness === 'omp') {
|
|
30
33
|
return "would remove the tinyfish entry from omp's mcp.json (located via `omp config path`)";
|
|
31
34
|
}
|
|
35
|
+
if (harness === 'pi') {
|
|
36
|
+
return `would remove the tinyfish entry from ${piMcpPath()}`;
|
|
37
|
+
}
|
|
32
38
|
if (!uninstallRuns(harness)) {
|
|
33
39
|
return `would print \`${uninstallPointer(harness)}\` (no removal command exists)`;
|
|
34
40
|
}
|
|
@@ -62,7 +68,8 @@ function uninstallPointer(harness) {
|
|
|
62
68
|
}
|
|
63
69
|
/** `mcp remove` clears neither the key nor the backends we wrote. */
|
|
64
70
|
function clearHermesLocalState() {
|
|
65
|
-
const
|
|
71
|
+
const resolved = resolveHermesHome();
|
|
72
|
+
const home = typeof resolved === 'string' ? resolved : undefined;
|
|
66
73
|
const advisories = [clearHermesEnvKey(home), clearHermesBackends(home)].filter((advisory) => advisory !== undefined);
|
|
67
74
|
return advisories.length > 0 ? advisories.join(' ') : undefined;
|
|
68
75
|
}
|
|
@@ -186,6 +193,10 @@ const MCP_JSON_UNINSTALLS = {
|
|
|
186
193
|
remove: removeOmpMcpServer,
|
|
187
194
|
fix: "Fix omp's mcp.json (at `omp config path`) by hand, then re-run: tinyfish connect --all --uninstall",
|
|
188
195
|
},
|
|
196
|
+
pi: {
|
|
197
|
+
remove: removePiMcpServer,
|
|
198
|
+
fix: `Fix ${piMcpPath()} by hand, then re-run: tinyfish connect --all --uninstall`,
|
|
199
|
+
},
|
|
189
200
|
};
|
|
190
201
|
function uninstallMcpJson(detection, spec) {
|
|
191
202
|
let result;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type Harness } from './harness-detect.js';
|
|
1
|
+
import { type Harness, type HarnessDetection } from './harness-detect.js';
|
|
2
2
|
import { type VerifyDepth } from './verify.js';
|
|
3
|
-
export type Outcome = 'not_detected' | 'not_installed' | 'stale_config' | 'installed' | 'failed' | 'harness_too_old' | 'interrupted' | 'auth_pending' | 'no_tty_auth_skip' | 'uninstalled' | 'uninstall_noop' | 'uninstall_pointer' | 'dry_run' | 'not_reached' | 'deselected';
|
|
3
|
+
export type Outcome = 'not_detected' | 'not_installed' | 'stale_config' | 'needs_first_run' | 'installed' | 'failed' | 'harness_too_old' | 'interrupted' | 'auth_pending' | 'no_tty_auth_skip' | 'uninstalled' | 'uninstall_noop' | 'uninstall_pointer' | 'dry_run' | 'not_reached' | 'deselected';
|
|
4
4
|
export type HarnessResultBase = {
|
|
5
5
|
harness: Harness;
|
|
6
6
|
detected: boolean;
|
|
@@ -34,5 +34,7 @@ export interface ConnectAllOptions {
|
|
|
34
34
|
/** Setup-page id riding in on --url; joins "copied the command" to this run. */
|
|
35
35
|
attemptId?: string;
|
|
36
36
|
}
|
|
37
|
+
/** The harness has never created its dir, so connecting would build one it does not own. */
|
|
38
|
+
export declare function needsFirstRun(detection: HarnessDetection): boolean;
|
|
37
39
|
export declare function runConnectAll(opts: ConnectAllOptions): Promise<number>;
|
|
38
40
|
export declare const FIRST_TASK_PROMPT: string;
|