@tiny-fish/cli 0.23.0 → 0.23.1-next.203
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -1
- package/dist/commands/run.js +78 -9
- package/dist/lib/client.js +3 -0
- package/dist/lib/connect-all.js +80 -0
- package/dist/lib/connect-install.d.ts +1 -1
- package/dist/lib/types.d.ts +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -142,7 +142,18 @@ tinyfish agent run "Check checkout" \
|
|
|
142
142
|
--url https://example.com \
|
|
143
143
|
--browser-profile stealth \
|
|
144
144
|
--mode strict \
|
|
145
|
-
--max-steps 75
|
|
145
|
+
--max-steps 75 \
|
|
146
|
+
--max-duration-seconds 300 \
|
|
147
|
+
--cursor-style standard
|
|
148
|
+
|
|
149
|
+
# Capture run artifacts, send a webhook, and use a saved profile
|
|
150
|
+
tinyfish agent run "Check checkout" \
|
|
151
|
+
--url https://example.com \
|
|
152
|
+
--capture-screenshots \
|
|
153
|
+
--capture-html \
|
|
154
|
+
--capture-recording \
|
|
155
|
+
--webhook-url https://example.com/tinyfish-webhook \
|
|
156
|
+
--use-profile
|
|
146
157
|
|
|
147
158
|
# Wait for result without streaming
|
|
148
159
|
tinyfish agent run "Find the pricing page" --url https://example.com --sync
|
|
@@ -163,6 +174,15 @@ tinyfish agent run "Extract the product price" \
|
|
|
163
174
|
--output-schema-file ./schemas/product-price.json
|
|
164
175
|
```
|
|
165
176
|
|
|
177
|
+
### Agent option flags
|
|
178
|
+
|
|
179
|
+
- `--cursor-style`, `--max-duration-seconds`, `--mode`, `--max-steps` — agent behavior, sent as `agent_config`
|
|
180
|
+
- `--capture-elements`, `--capture-snapshots`, `--capture-screenshots`, `--capture-recording`, `--capture-html` — artifacts to capture, sent as `capture_config`
|
|
181
|
+
- `--webhook-url` — HTTPS URL notified on run lifecycle events; validated locally
|
|
182
|
+
- `--use-profile`, `--profile-id` — start from a Browser Context Profile; `--profile-id` requires `--use-profile`
|
|
183
|
+
|
|
184
|
+
`--capture-elements` and `--capture-recording` are gated capabilities: an account without them enabled gets a 403 saying the capability is not enabled. The other capture flags are ungated.
|
|
185
|
+
|
|
166
186
|
### Structured output
|
|
167
187
|
|
|
168
188
|
Use `--output-schema <json>` for short inline schemas and `--output-schema-file <path>` for schemas you want to reuse, review, or keep in source control.
|
package/dist/commands/run.js
CHANGED
|
@@ -7,6 +7,7 @@ import { err, errLine, handleApiError, out, outLine } from "../lib/output.js";
|
|
|
7
7
|
import { BASE_URL } from "../lib/constants.js";
|
|
8
8
|
const VALID_BROWSER_PROFILES = ["lite", "stealth"];
|
|
9
9
|
const VALID_MODES = ["default", "strict"];
|
|
10
|
+
const VALID_CURSOR_STYLES = ["fish", "standard", "green-pointer"];
|
|
10
11
|
const MAX_STEPS_LIMIT = 500;
|
|
11
12
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
12
13
|
function parseSessionId(value) {
|
|
@@ -69,19 +70,36 @@ function parseChoice(value, valid, flag) {
|
|
|
69
70
|
err({ error: `Invalid ${flag} "${value}". Must be one of: ${valid.join(", ")}` });
|
|
70
71
|
process.exit(1);
|
|
71
72
|
}
|
|
72
|
-
function
|
|
73
|
+
function parsePositiveInteger(value, flag, max) {
|
|
73
74
|
if (value === undefined)
|
|
74
75
|
return undefined;
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
const parsed = Number(value);
|
|
77
|
+
if (!/^\d+$/.test(value) || parsed < 1 || (max !== undefined && parsed > max)) {
|
|
78
|
+
err({
|
|
79
|
+
error: max === undefined
|
|
80
|
+
? `Invalid ${flag} "${value}". Must be a positive integer.`
|
|
81
|
+
: `${flag} must be between 1 and ${max}.`,
|
|
82
|
+
});
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
return parsed;
|
|
86
|
+
}
|
|
87
|
+
function parseHttpsUrl(value, flag) {
|
|
88
|
+
if (value === undefined)
|
|
89
|
+
return undefined;
|
|
90
|
+
let parsed;
|
|
91
|
+
try {
|
|
92
|
+
parsed = new URL(value);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
err({ error: `Invalid ${flag} "${value}". Provide a valid URL.` });
|
|
77
96
|
process.exit(1);
|
|
78
97
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
err({ error: `--max-steps must be between 1 and ${MAX_STEPS_LIMIT}.` });
|
|
98
|
+
if (parsed.protocol !== "https:") {
|
|
99
|
+
err({ error: `${flag} must use HTTPS.` });
|
|
82
100
|
process.exit(1);
|
|
83
101
|
}
|
|
84
|
-
return
|
|
102
|
+
return value;
|
|
85
103
|
}
|
|
86
104
|
async function loadOutputSchema(opts) {
|
|
87
105
|
if (opts.outputSchema !== undefined && opts.outputSchemaFile !== undefined) {
|
|
@@ -114,7 +132,18 @@ export function registerRun(agentCmd) {
|
|
|
114
132
|
.option("--output-schema-file <path>", "Path to a JSON file containing the output schema")
|
|
115
133
|
.option("--browser-profile <profile>", "Browser profile for execution (lite|stealth)")
|
|
116
134
|
.option("--max-steps <n>", `Maximum tool-call steps before stopping (1-${MAX_STEPS_LIMIT})`)
|
|
135
|
+
.option("--max-duration-seconds <n>", "Maximum wall-clock seconds before stopping")
|
|
117
136
|
.option("--mode <mode>", "Agent behavior mode (default|strict)")
|
|
137
|
+
.option("--cursor-style <style>", "Cursor style (fish|standard|green-pointer)")
|
|
138
|
+
.option("--capture-elements", "Capture page element metadata (gated capability; returns 403 if not enabled for your account)")
|
|
139
|
+
.option("--capture-snapshots", "Capture page snapshots")
|
|
140
|
+
.option("--capture-screenshots", "Capture screenshots")
|
|
141
|
+
.option("--no-capture-screenshots", "Disable screenshot capture")
|
|
142
|
+
.option("--capture-recording", "Capture a browser recording (gated capability; returns 403 if not enabled for your account)")
|
|
143
|
+
.option("--capture-html", "Capture HTML snapshots")
|
|
144
|
+
.option("--webhook-url <url>", "HTTPS webhook URL for run lifecycle events")
|
|
145
|
+
.option("--use-profile", "Use an attached browser profile")
|
|
146
|
+
.option("--profile-id <id>", "Browser Context Profile ID to start from (requires --use-profile)")
|
|
118
147
|
.option("--session-id <uuid>", "Caller-provided UUID for idempotency / parallel-safe --sync")
|
|
119
148
|
.option("--use-vault", "Consume vault credentials in this run")
|
|
120
149
|
.option("--credential-item-id <id>", "Scope vault to a specific credential item ID (repeat for multiple; get IDs from `vault item list`; requires --use-vault)", (v, acc) => [...acc, v], [])
|
|
@@ -161,18 +190,58 @@ export function registerRun(agentCmd) {
|
|
|
161
190
|
err({ error: "--credential-item-id requires --use-vault" });
|
|
162
191
|
process.exit(1);
|
|
163
192
|
}
|
|
193
|
+
// The server rejects profile_id without use_profile (400); fail fast here.
|
|
194
|
+
if (opts.profileId !== undefined && !opts.useProfile) {
|
|
195
|
+
err({ error: "--profile-id requires --use-profile" });
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
164
198
|
const outputSchema = await loadOutputSchema(opts);
|
|
165
199
|
const browserProfile = parseChoice(opts.browserProfile, VALID_BROWSER_PROFILES, "--browser-profile");
|
|
166
200
|
const mode = parseChoice(opts.mode, VALID_MODES, "--mode");
|
|
167
|
-
const
|
|
201
|
+
const cursorStyle = parseChoice(opts.cursorStyle, VALID_CURSOR_STYLES, "--cursor-style");
|
|
202
|
+
const maxSteps = parsePositiveInteger(opts.maxSteps, "--max-steps", MAX_STEPS_LIMIT);
|
|
203
|
+
const maxDurationSeconds = parsePositiveInteger(opts.maxDurationSeconds, "--max-duration-seconds");
|
|
168
204
|
const sessionId = parseSessionId(opts.sessionId);
|
|
205
|
+
const webhookUrl = parseHttpsUrl(opts.webhookUrl, "--webhook-url");
|
|
206
|
+
const agentConfig = mode !== undefined ||
|
|
207
|
+
cursorStyle !== undefined ||
|
|
208
|
+
maxSteps !== undefined ||
|
|
209
|
+
maxDurationSeconds !== undefined
|
|
210
|
+
? {
|
|
211
|
+
...(mode !== undefined ? { mode } : {}),
|
|
212
|
+
...(cursorStyle !== undefined ? { cursor_style: cursorStyle } : {}),
|
|
213
|
+
...(maxSteps !== undefined ? { max_steps: maxSteps } : {}),
|
|
214
|
+
...(maxDurationSeconds !== undefined
|
|
215
|
+
? { max_duration_seconds: maxDurationSeconds }
|
|
216
|
+
: {}),
|
|
217
|
+
}
|
|
218
|
+
: undefined;
|
|
219
|
+
const captureConfig = opts.captureElements !== undefined ||
|
|
220
|
+
opts.captureSnapshots !== undefined ||
|
|
221
|
+
opts.captureScreenshots !== undefined ||
|
|
222
|
+
opts.captureRecording !== undefined ||
|
|
223
|
+
opts.captureHtml !== undefined
|
|
224
|
+
? {
|
|
225
|
+
...(opts.captureElements !== undefined ? { elements: opts.captureElements } : {}),
|
|
226
|
+
...(opts.captureSnapshots !== undefined ? { snapshots: opts.captureSnapshots } : {}),
|
|
227
|
+
...(opts.captureScreenshots !== undefined
|
|
228
|
+
? { screenshots: opts.captureScreenshots }
|
|
229
|
+
: {}),
|
|
230
|
+
...(opts.captureRecording !== undefined ? { recording: opts.captureRecording } : {}),
|
|
231
|
+
...(opts.captureHtml !== undefined ? { html: opts.captureHtml } : {}),
|
|
232
|
+
}
|
|
233
|
+
: undefined;
|
|
169
234
|
const apiKey = getApiKey();
|
|
170
235
|
const req = {
|
|
171
236
|
goal,
|
|
172
237
|
url: normalizedUrl,
|
|
173
238
|
...(outputSchema !== undefined ? { output_schema: outputSchema } : {}),
|
|
174
239
|
...(browserProfile !== undefined ? { browser_profile: browserProfile } : {}),
|
|
175
|
-
...(
|
|
240
|
+
...(agentConfig !== undefined ? { agent_config: agentConfig } : {}),
|
|
241
|
+
...(captureConfig !== undefined ? { capture_config: captureConfig } : {}),
|
|
242
|
+
...(webhookUrl !== undefined ? { webhook_url: webhookUrl } : {}),
|
|
243
|
+
...(opts.useProfile !== undefined ? { use_profile: opts.useProfile } : {}),
|
|
244
|
+
...(opts.profileId !== undefined ? { profile_id: opts.profileId } : {}),
|
|
176
245
|
...(sessionId !== undefined ? { session_id: sessionId } : {}),
|
|
177
246
|
...(opts.useVault ? { use_vault: true } : {}),
|
|
178
247
|
...(hasCredentialItemIds ? { credential_item_ids: opts.credentialItemId } : {}),
|
package/dist/lib/client.js
CHANGED
|
@@ -54,6 +54,9 @@ class TinyFishCliClient extends TinyFish {
|
|
|
54
54
|
headers["X-TF-Client-Version"] = clientVersion;
|
|
55
55
|
// Agent-vs-human signal, independent of harness detection or skill install.
|
|
56
56
|
headers["X-TF-Human-Initiated"] = detectHumanInitiated() ? "true" : "false";
|
|
57
|
+
if (process.env["TF_SETUP_VERIFICATION"] === "1") {
|
|
58
|
+
headers["X-TF-Setup-Verification"] = "true";
|
|
59
|
+
}
|
|
57
60
|
// Reuse the already-resolved client_name so connect_client can never diverge from it
|
|
58
61
|
// (same sanitize + precedence, not a second raw read of TF_CLIENT_NAME).
|
|
59
62
|
const connectCaller = clientName || null;
|
package/dist/lib/connect-all.js
CHANGED
|
@@ -3,6 +3,8 @@ import spawn from "cross-spawn";
|
|
|
3
3
|
import { connectClaudeCode, connectCodex, connectCursor, connectHermes, connectOpenClaw, connectOpencode, } from "../commands/connect.js";
|
|
4
4
|
import { validatedApiKey } from "./auth.js";
|
|
5
5
|
import { NATIVE_MCP_CLIENTS } from "./connect-clients.js";
|
|
6
|
+
import { ensureCliAuthenticated, installTinyFishCli, TINYFISH_CLI_NOT_FOUND_MESSAGE, } from "./connect-install.js";
|
|
7
|
+
import { commandNotFound, ConnectInterruptedError, ConnectStepError, throwIfInterrupted, } from "./connect-runtime.js";
|
|
6
8
|
import { detectInstalledHarnesses, ALL_HARNESSES, } from "./harness-detect.js";
|
|
7
9
|
import { detectHumanInitiated } from "./harness.js";
|
|
8
10
|
import { emitNotice } from "./notice.js";
|
|
@@ -234,6 +236,72 @@ function computeExitCode(results) {
|
|
|
234
236
|
return 0;
|
|
235
237
|
return attempted.some((r) => r.outcome === "installed") ? 0 : 1;
|
|
236
238
|
}
|
|
239
|
+
const SEARCH_RETRY = 'tinyfish search query --pretty -- "<your query>"';
|
|
240
|
+
function searchRetry(query) {
|
|
241
|
+
if (process.platform === "win32")
|
|
242
|
+
return SEARCH_RETRY;
|
|
243
|
+
return `tinyfish search query --pretty -- '${query.replaceAll("'", "'\\''")}'`;
|
|
244
|
+
}
|
|
245
|
+
async function runCliFallback(opts, prompt) {
|
|
246
|
+
try {
|
|
247
|
+
installTinyFishCli({ verbose: true });
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
if (error instanceof ConnectInterruptedError)
|
|
251
|
+
throw error;
|
|
252
|
+
errLine(error instanceof Error ? error.message : String(error));
|
|
253
|
+
outLine("Retry: npx @tiny-fish/cli connect --all");
|
|
254
|
+
return 1;
|
|
255
|
+
}
|
|
256
|
+
const apiKey = opts.apiKey ?? process.env["TINYFISH_API_KEY"];
|
|
257
|
+
if (!prompt && !apiKey) {
|
|
258
|
+
outLine("Retry in a terminal: npx @tiny-fish/cli connect --all");
|
|
259
|
+
return 1;
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
ensureCliAuthenticated("cli", apiKey);
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
if (error instanceof ConnectInterruptedError)
|
|
266
|
+
throw error;
|
|
267
|
+
errLine(error instanceof Error ? error.message : String(error));
|
|
268
|
+
outLine(error instanceof ConnectStepError && error.failureReason === "command_not_found"
|
|
269
|
+
? "Retry in a new terminal: tinyfish auth login"
|
|
270
|
+
: "Retry: tinyfish auth login");
|
|
271
|
+
return 1;
|
|
272
|
+
}
|
|
273
|
+
if (!prompt) {
|
|
274
|
+
outLine(`Retry in a terminal: ${SEARCH_RETRY}`);
|
|
275
|
+
return 1;
|
|
276
|
+
}
|
|
277
|
+
let query;
|
|
278
|
+
try {
|
|
279
|
+
query = (await prompt("What would you like to search? ")).trim();
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
outLine(`Retry: ${SEARCH_RETRY}`);
|
|
283
|
+
return 1;
|
|
284
|
+
}
|
|
285
|
+
if (!query) {
|
|
286
|
+
outLine(`Retry: ${SEARCH_RETRY}`);
|
|
287
|
+
return 1;
|
|
288
|
+
}
|
|
289
|
+
const search = spawn.sync("tinyfish", ["search", "query", "--pretty", "--", query], {
|
|
290
|
+
stdio: "inherit",
|
|
291
|
+
env: { ...process.env, TF_SETUP_VERIFICATION: "1" },
|
|
292
|
+
});
|
|
293
|
+
if (commandNotFound(search.error)) {
|
|
294
|
+
errLine(TINYFISH_CLI_NOT_FOUND_MESSAGE);
|
|
295
|
+
outLine(`Retry in a new terminal: ${searchRetry(query)}`);
|
|
296
|
+
return 1;
|
|
297
|
+
}
|
|
298
|
+
throwIfInterrupted(search);
|
|
299
|
+
if (search.error || search.status !== 0) {
|
|
300
|
+
outLine(`Retry: ${searchRetry(query)}`);
|
|
301
|
+
return 1;
|
|
302
|
+
}
|
|
303
|
+
return 0;
|
|
304
|
+
}
|
|
237
305
|
export async function runConnectAll(opts) {
|
|
238
306
|
const isTTY = detectHumanInitiated();
|
|
239
307
|
let stdinClosed = false;
|
|
@@ -300,6 +368,18 @@ export async function runConnectAll(opts) {
|
|
|
300
368
|
for (const result of results)
|
|
301
369
|
outLine(summaryLine(result, opts.uninstall));
|
|
302
370
|
if (results.every((r) => !r.detected)) {
|
|
371
|
+
if (!opts.dryRun && !opts.uninstall) {
|
|
372
|
+
try {
|
|
373
|
+
return await runCliFallback(opts, prompt);
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
if (!(error instanceof ConnectInterruptedError))
|
|
377
|
+
throw error;
|
|
378
|
+
errLine("Setup interrupted — run the command again to finish.");
|
|
379
|
+
process.exitCode = 130;
|
|
380
|
+
return 130;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
303
383
|
outLine("");
|
|
304
384
|
outLine("No supported harness found. Install one of: " + ALL_HARNESSES.join(", "));
|
|
305
385
|
if (!opts.uninstall)
|
|
@@ -20,4 +20,4 @@ export declare function installWebSkill(client: NativeMcpClient): void;
|
|
|
20
20
|
* Returns whether a skill was actually rewritten.
|
|
21
21
|
*/
|
|
22
22
|
export declare function updateWebSkill({ verbose }: InstallOptions): boolean;
|
|
23
|
-
export declare function ensureCliAuthenticated(source: AgentClient, apiKey?: string): void;
|
|
23
|
+
export declare function ensureCliAuthenticated(source: AgentClient | "cli", apiKey?: string): void;
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -4,8 +4,18 @@ export interface CliAgentRunParams extends AgentRunParams {
|
|
|
4
4
|
output_schema?: OutputSchema;
|
|
5
5
|
agent_config?: {
|
|
6
6
|
mode?: "default" | "strict";
|
|
7
|
+
cursor_style?: "fish" | "standard" | "green-pointer";
|
|
7
8
|
max_steps?: number;
|
|
9
|
+
max_duration_seconds?: number;
|
|
8
10
|
};
|
|
11
|
+
capture_config?: {
|
|
12
|
+
elements?: boolean;
|
|
13
|
+
snapshots?: boolean;
|
|
14
|
+
screenshots?: boolean;
|
|
15
|
+
recording?: boolean;
|
|
16
|
+
html?: boolean;
|
|
17
|
+
};
|
|
18
|
+
webhook_url?: string;
|
|
9
19
|
browser_profile?: BrowserProfile;
|
|
10
20
|
session_id?: string;
|
|
11
21
|
use_vault?: boolean;
|