@tiny-fish/cli 0.1.5-next.47 → 0.1.5-next.50
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 +18 -0
- package/dist/commands/browser.js +14 -1
- package/dist/commands/run.js +47 -0
- package/dist/commands/runs.js +167 -1
- package/dist/lib/client.d.ts +4 -3
- package/dist/lib/client.js +94 -8
- package/dist/lib/types.d.ts +26 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -36,6 +36,13 @@ tinyfish agent run "Find the pricing page" --url https://example.com
|
|
|
36
36
|
# Human-readable output
|
|
37
37
|
tinyfish agent run "Find the pricing page" --url https://example.com --pretty
|
|
38
38
|
|
|
39
|
+
# Use a browser profile and stricter agent limits
|
|
40
|
+
tinyfish agent run "Check checkout" \
|
|
41
|
+
--url https://example.com \
|
|
42
|
+
--browser-profile stealth \
|
|
43
|
+
--mode strict \
|
|
44
|
+
--max-steps 75
|
|
45
|
+
|
|
39
46
|
# Wait for result without streaming
|
|
40
47
|
tinyfish agent run "Find the pricing page" --url https://example.com --sync
|
|
41
48
|
|
|
@@ -85,10 +92,19 @@ tinyfish agent run list --status RUNNING --pretty
|
|
|
85
92
|
# Inspect a run
|
|
86
93
|
tinyfish agent run get <run_id> --pretty
|
|
87
94
|
|
|
95
|
+
# Print the step-by-step trace
|
|
96
|
+
tinyfish agent run steps <run_id> --pretty
|
|
97
|
+
|
|
98
|
+
# Watch a run live (polls the steps endpoint; exits when the run finishes)
|
|
99
|
+
tinyfish agent run watch <run_id> --pretty
|
|
100
|
+
tinyfish agent run watch <run_id> --pretty --interval 5000 --timeout 600000
|
|
101
|
+
|
|
88
102
|
# Cancel a run
|
|
89
103
|
tinyfish agent run cancel <run_id> --pretty
|
|
90
104
|
```
|
|
91
105
|
|
|
106
|
+
**Important token scope note:** CLI run commands are scoped to the user's personal API key (`TINYFISH_API_KEY` or `tinyfish auth`). MCP tool calls are scoped to the MCP token. These are different run namespaces, so `tinyfish agent run get <mcp_run_id>` returning 404 is expected behavior, not a CLI bug.
|
|
107
|
+
|
|
92
108
|
### Search
|
|
93
109
|
|
|
94
110
|
```bash
|
|
@@ -139,6 +155,8 @@ Errors are JSON to stderr with exit code 1. Ctrl+C during a streaming run cancel
|
|
|
139
155
|
|
|
140
156
|
`--output-schema` and `--output-schema-file` are mutually exclusive. Both inputs must parse to a top-level JSON object. The CLI only validates that outer shape locally; the API performs full schema validation and may return errors such as `INVALID_INPUT` or feature-flag access failures.
|
|
141
157
|
|
|
158
|
+
`--browser-profile` accepts `lite` or `stealth`. `--mode` accepts `default` or `strict`. `--max-steps` accepts values from 1 to 500. `--session-id <uuid>` is a caller-provided UUID that lets concurrent `--sync` calls stay idempotent.
|
|
159
|
+
|
|
142
160
|
### Debug
|
|
143
161
|
|
|
144
162
|
```bash
|
package/dist/commands/browser.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { getApiKey } from "../lib/auth.js";
|
|
2
2
|
import { browserSessionCreate } from "../lib/client.js";
|
|
3
|
-
import { handleApiError, out, outLine } from "../lib/output.js";
|
|
3
|
+
import { err, handleApiError, out, outLine } from "../lib/output.js";
|
|
4
|
+
const VALID_BROWSER_PROFILES = ["lite", "stealth"];
|
|
5
|
+
function parseBrowserProfile(value) {
|
|
6
|
+
if (value === undefined)
|
|
7
|
+
return undefined;
|
|
8
|
+
if (VALID_BROWSER_PROFILES.includes(value)) {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
err({ error: `Invalid --browser-profile "${value}". Must be one of: lite, stealth` });
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
4
14
|
function printPrettyBrowserSession(session) {
|
|
5
15
|
outLine("Browser session created");
|
|
6
16
|
outLine(`Session ID: ${session.session_id}`);
|
|
@@ -20,12 +30,15 @@ export function registerBrowser(program) {
|
|
|
20
30
|
.command("create")
|
|
21
31
|
.description("Create a remote browser session")
|
|
22
32
|
.option("--url <value>", "Open a URL after session creation")
|
|
33
|
+
.option("--browser-profile <profile>", "Browser profile for execution (lite|stealth)")
|
|
23
34
|
.option("--pretty", "Human-readable output")
|
|
24
35
|
.action(async (opts) => {
|
|
25
36
|
const apiKey = getApiKey();
|
|
37
|
+
const browserProfile = parseBrowserProfile(opts.browserProfile);
|
|
26
38
|
try {
|
|
27
39
|
const response = await browserSessionCreate({
|
|
28
40
|
...(opts.url !== undefined ? { url: opts.url } : {}),
|
|
41
|
+
...(browserProfile !== undefined ? { browser_profile: browserProfile } : {}),
|
|
29
42
|
}, apiKey);
|
|
30
43
|
if (opts.pretty) {
|
|
31
44
|
printPrettyBrowserSession(response);
|
package/dist/commands/run.js
CHANGED
|
@@ -5,6 +5,19 @@ import { getApiKey } from "../lib/auth.js";
|
|
|
5
5
|
import { cancelRun, runAsync, runStream, runSync } from "../lib/client.js";
|
|
6
6
|
import { err, errLine, handleApiError, out, outLine } from "../lib/output.js";
|
|
7
7
|
import { BASE_URL } from "../lib/constants.js";
|
|
8
|
+
const VALID_BROWSER_PROFILES = ["lite", "stealth"];
|
|
9
|
+
const VALID_MODES = ["default", "strict"];
|
|
10
|
+
const MAX_STEPS_LIMIT = 500;
|
|
11
|
+
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
|
+
function parseSessionId(value) {
|
|
13
|
+
if (value === undefined)
|
|
14
|
+
return undefined;
|
|
15
|
+
if (!UUID_RE.test(value)) {
|
|
16
|
+
err({ error: `Invalid --session-id "${value}". Must be a UUID.` });
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
8
21
|
const outputSchemaFieldSchema = z.object({}).catchall(z.unknown());
|
|
9
22
|
function extractErrorMessage(error) {
|
|
10
23
|
if (typeof error === "string")
|
|
@@ -48,6 +61,28 @@ function parseOutputSchema(rawOutputSchema, sourceLabel) {
|
|
|
48
61
|
}
|
|
49
62
|
return result.data;
|
|
50
63
|
}
|
|
64
|
+
function parseChoice(value, valid, flag) {
|
|
65
|
+
if (value === undefined)
|
|
66
|
+
return undefined;
|
|
67
|
+
if (valid.includes(value))
|
|
68
|
+
return value;
|
|
69
|
+
err({ error: `Invalid ${flag} "${value}". Must be one of: ${valid.join(", ")}` });
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
function parseMaxSteps(value) {
|
|
73
|
+
if (value === undefined)
|
|
74
|
+
return undefined;
|
|
75
|
+
if (!/^\d+$/.test(value)) {
|
|
76
|
+
err({ error: `Invalid --max-steps "${value}". Must be a positive integer.` });
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
const maxSteps = Number(value);
|
|
80
|
+
if (maxSteps < 1 || maxSteps > MAX_STEPS_LIMIT) {
|
|
81
|
+
err({ error: `--max-steps must be between 1 and ${MAX_STEPS_LIMIT}.` });
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
return maxSteps;
|
|
85
|
+
}
|
|
51
86
|
async function loadOutputSchema(opts) {
|
|
52
87
|
if (opts.outputSchema !== undefined && opts.outputSchemaFile !== undefined) {
|
|
53
88
|
err({ error: "--output-schema and --output-schema-file are mutually exclusive" });
|
|
@@ -77,8 +112,13 @@ export function registerRun(agentCmd) {
|
|
|
77
112
|
.option("--async", "Submit without waiting for result")
|
|
78
113
|
.option("--output-schema <json>", "Inline JSON Schema object for structured output")
|
|
79
114
|
.option("--output-schema-file <path>", "Path to a JSON file containing the output schema")
|
|
115
|
+
.option("--browser-profile <profile>", "Browser profile for execution (lite|stealth)")
|
|
116
|
+
.option("--max-steps <n>", `Maximum tool-call steps before stopping (1-${MAX_STEPS_LIMIT})`)
|
|
117
|
+
.option("--mode <mode>", "Agent behavior mode (default|strict)")
|
|
118
|
+
.option("--session-id <uuid>", "Caller-provided UUID for idempotency / parallel-safe --sync")
|
|
80
119
|
.option("--pretty", "Human-readable output")
|
|
81
120
|
.argument("[goal]", "Goal to automate")
|
|
121
|
+
.addHelpText("after", "\nToken scope note:\n CLI runs use your personal API key. MCP runs use the MCP token.\n These are different namespaces; getting an MCP run ID with the CLI returns 404 by design.\n")
|
|
82
122
|
.action(async (goal, opts) => {
|
|
83
123
|
// If no goal is provided and no subcommand matched, show help
|
|
84
124
|
if (!goal) {
|
|
@@ -114,11 +154,18 @@ export function registerRun(agentCmd) {
|
|
|
114
154
|
process.exit(1);
|
|
115
155
|
}
|
|
116
156
|
const outputSchema = await loadOutputSchema(opts);
|
|
157
|
+
const browserProfile = parseChoice(opts.browserProfile, VALID_BROWSER_PROFILES, "--browser-profile");
|
|
158
|
+
const mode = parseChoice(opts.mode, VALID_MODES, "--mode");
|
|
159
|
+
const maxSteps = parseMaxSteps(opts.maxSteps);
|
|
160
|
+
const sessionId = parseSessionId(opts.sessionId);
|
|
117
161
|
const apiKey = getApiKey();
|
|
118
162
|
const req = {
|
|
119
163
|
goal,
|
|
120
164
|
url: normalizedUrl,
|
|
121
165
|
...(outputSchema !== undefined ? { output_schema: outputSchema } : {}),
|
|
166
|
+
...(browserProfile !== undefined ? { browser_profile: browserProfile } : {}),
|
|
167
|
+
...(mode !== undefined || maxSteps !== undefined ? { agent_config: { mode, max_steps: maxSteps } } : {}),
|
|
168
|
+
...(sessionId !== undefined ? { session_id: sessionId } : {}),
|
|
122
169
|
};
|
|
123
170
|
if (opts.async) {
|
|
124
171
|
let result;
|
package/dist/commands/runs.js
CHANGED
|
@@ -1,8 +1,73 @@
|
|
|
1
1
|
import { RunStatus } from "@tiny-fish/sdk";
|
|
2
2
|
import { getApiKey } from "../lib/auth.js";
|
|
3
|
-
import { cancelRun, getRun, listRuns } from "../lib/client.js";
|
|
3
|
+
import { cancelRun, getRun, getRunSteps, listRuns } from "../lib/client.js";
|
|
4
4
|
import { err, handleApiError, out, outLine } from "../lib/output.js";
|
|
5
5
|
const VALID_STATUSES = Object.values(RunStatus);
|
|
6
|
+
const DEFAULT_WATCH_TIMEOUT_MS = 1_800_000;
|
|
7
|
+
const DEFAULT_WATCH_INTERVAL_MS = 3000;
|
|
8
|
+
const MIN_WATCH_INTERVAL_MS = 500;
|
|
9
|
+
const TERMINAL_STATUSES = [
|
|
10
|
+
RunStatus.COMPLETED,
|
|
11
|
+
RunStatus.FAILED,
|
|
12
|
+
RunStatus.CANCELLED,
|
|
13
|
+
];
|
|
14
|
+
function formatStep(step, index) {
|
|
15
|
+
const action = step.action ?? "(no action)";
|
|
16
|
+
const meta = [step.status, step.duration].filter(Boolean).join(", ");
|
|
17
|
+
return `${String(index + 1).padStart(2, " ")}. ${action}${meta ? ` (${meta})` : ""}`;
|
|
18
|
+
}
|
|
19
|
+
function parsePositiveInt(value, flag, defaultMs, minMs = 1) {
|
|
20
|
+
if (value === undefined)
|
|
21
|
+
return defaultMs;
|
|
22
|
+
// Reject "5000ms", "1e3", "+5", and similar — only whole-digit input.
|
|
23
|
+
if (!/^\d+$/.test(value)) {
|
|
24
|
+
err({ error: `Invalid ${flag} "${value}". Must be a whole-number integer in milliseconds (>= ${minMs}).` });
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
const parsed = Number(value);
|
|
28
|
+
if (parsed < minMs) {
|
|
29
|
+
err({ error: `Invalid ${flag} "${value}". Must be an integer >= ${minMs}ms.` });
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
return parsed;
|
|
33
|
+
}
|
|
34
|
+
function withDeadline(promise, deadline, signal) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const remaining = deadline - Date.now();
|
|
37
|
+
if (remaining <= 0) {
|
|
38
|
+
reject(new Error("watch deadline exceeded"));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (signal.aborted) {
|
|
42
|
+
reject(new Error("watch aborted"));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const timer = setTimeout(() => reject(new Error("watch deadline exceeded")), remaining);
|
|
46
|
+
const onAbort = () => {
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
reject(new Error("watch aborted"));
|
|
49
|
+
};
|
|
50
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
51
|
+
promise.then((v) => { clearTimeout(timer); signal.removeEventListener("abort", onAbort); resolve(v); }, (e) => { clearTimeout(timer); signal.removeEventListener("abort", onAbort); reject(e); });
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function sleep(ms, signal) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
if (signal.aborted) {
|
|
57
|
+
reject(new Error("aborted"));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const timer = setTimeout(() => {
|
|
61
|
+
signal.removeEventListener("abort", onAbort);
|
|
62
|
+
resolve();
|
|
63
|
+
}, ms);
|
|
64
|
+
const onAbort = () => {
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
reject(new Error("aborted"));
|
|
67
|
+
};
|
|
68
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
69
|
+
});
|
|
70
|
+
}
|
|
6
71
|
export function registerRuns(runCmd) {
|
|
7
72
|
// ── run list ───────────────────────────────────────────────────────────────
|
|
8
73
|
runCmd
|
|
@@ -95,6 +160,107 @@ export function registerRuns(runCmd) {
|
|
|
95
160
|
handleApiError(e);
|
|
96
161
|
}
|
|
97
162
|
});
|
|
163
|
+
runCmd
|
|
164
|
+
.command("steps <run_id>")
|
|
165
|
+
.description("Print the step-by-step trace for a run")
|
|
166
|
+
.option("--pretty", "Human-readable output")
|
|
167
|
+
.action(async (runId, opts) => {
|
|
168
|
+
const apiKey = getApiKey();
|
|
169
|
+
try {
|
|
170
|
+
const result = await getRunSteps(runId, apiKey);
|
|
171
|
+
if (opts.pretty) {
|
|
172
|
+
if (result.steps.length === 0) {
|
|
173
|
+
outLine("No steps found.");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
outLine(`Run ID: ${result.run_id}`);
|
|
177
|
+
for (const [index, step] of result.steps.entries()) {
|
|
178
|
+
outLine(formatStep(step, index));
|
|
179
|
+
outLine(` ${step.timestamp}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
out(result);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
handleApiError(e);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
runCmd
|
|
191
|
+
.command("watch <run_id>")
|
|
192
|
+
.description("Poll a run and print new steps as they arrive (exits when run reaches a terminal status)")
|
|
193
|
+
.option("--pretty", "Human-readable output")
|
|
194
|
+
.option("--timeout <ms>", `Stop polling after this many milliseconds (default: ${DEFAULT_WATCH_TIMEOUT_MS})`)
|
|
195
|
+
.option("--interval <ms>", `Poll interval (default: ${DEFAULT_WATCH_INTERVAL_MS}, min: ${MIN_WATCH_INTERVAL_MS})`)
|
|
196
|
+
.action(async (runId, opts) => {
|
|
197
|
+
const apiKey = getApiKey();
|
|
198
|
+
const timeoutMs = parsePositiveInt(opts.timeout, "--timeout", DEFAULT_WATCH_TIMEOUT_MS);
|
|
199
|
+
const intervalMs = parsePositiveInt(opts.interval, "--interval", DEFAULT_WATCH_INTERVAL_MS, MIN_WATCH_INTERVAL_MS);
|
|
200
|
+
const controller = new AbortController();
|
|
201
|
+
const onSigint = () => {
|
|
202
|
+
controller.abort();
|
|
203
|
+
process.exit(130);
|
|
204
|
+
};
|
|
205
|
+
process.once("SIGINT", onSigint);
|
|
206
|
+
const deadline = Date.now() + timeoutMs;
|
|
207
|
+
const seen = new Set();
|
|
208
|
+
try {
|
|
209
|
+
const initial = await withDeadline(getRun(runId, apiKey), deadline, controller.signal);
|
|
210
|
+
if (TERMINAL_STATUSES.includes(initial.status)) {
|
|
211
|
+
err({
|
|
212
|
+
error: `Run ${runId} is already ${initial.status}; nothing to watch. Use 'tinyfish agent run steps ${runId}' for the trace.`,
|
|
213
|
+
});
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
while (!controller.signal.aborted) {
|
|
217
|
+
// One API call per tick — the steps endpoint also returns the run's current status.
|
|
218
|
+
const tick = await withDeadline(getRunSteps(runId, apiKey), deadline, controller.signal);
|
|
219
|
+
let nextIndex = seen.size;
|
|
220
|
+
for (const step of tick.steps) {
|
|
221
|
+
if (seen.has(step.id))
|
|
222
|
+
continue;
|
|
223
|
+
seen.add(step.id);
|
|
224
|
+
if (opts.pretty) {
|
|
225
|
+
outLine(formatStep(step, nextIndex));
|
|
226
|
+
outLine(` ${step.timestamp}`);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
out(step);
|
|
230
|
+
}
|
|
231
|
+
nextIndex += 1;
|
|
232
|
+
}
|
|
233
|
+
if (TERMINAL_STATUSES.includes(tick.status)) {
|
|
234
|
+
if (opts.pretty)
|
|
235
|
+
outLine(`Run ${tick.status.toLowerCase()}.`);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (Date.now() + intervalMs > deadline) {
|
|
239
|
+
err({ error: `Watch timed out after ${timeoutMs}ms (run still ${tick.status}).` });
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
await sleep(intervalMs, controller.signal);
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
return; // aborted
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch (e) {
|
|
251
|
+
if (e instanceof Error && e.message === "watch deadline exceeded") {
|
|
252
|
+
err({ error: `Watch timed out after ${timeoutMs}ms.` });
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
if (e instanceof Error && e.message === "watch aborted") {
|
|
256
|
+
return; // SIGINT handler already triggered process.exit(130)
|
|
257
|
+
}
|
|
258
|
+
handleApiError(e);
|
|
259
|
+
}
|
|
260
|
+
finally {
|
|
261
|
+
process.off("SIGINT", onSigint);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
98
264
|
// ── run cancel ────────────────────────────────────────────────────────────
|
|
99
265
|
runCmd
|
|
100
266
|
.command("cancel <run_id>")
|
package/dist/lib/client.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { type AgentRunAsyncResponse, type AgentRunResponse, type AgentRunWithStreamingResponse, type FetchGetContentsParams, type FetchResponse, type BrowserSession, type
|
|
2
|
-
import type { BatchCancelResponse, BatchGetResponse, BatchRunRequest, BatchRunResponse, CancelRunResponse, CliAgentRunParams } from "./types.js";
|
|
1
|
+
import { type AgentRunAsyncResponse, type AgentRunResponse, type AgentRunWithStreamingResponse, type FetchGetContentsParams, type FetchResponse, type BrowserSession, type Run, type RunListParams, type RunListResponse, type SearchQueryParams, type SearchQueryResponse } from "@tiny-fish/sdk";
|
|
2
|
+
import type { BatchCancelResponse, BatchGetResponse, BatchRunRequest, BatchRunResponse, CancelRunResponse, CliAgentRunParams, CliBrowserSessionCreateParams, RunStepsResponse } from "./types.js";
|
|
3
3
|
export declare function runSync(req: CliAgentRunParams, apiKey: string): Promise<AgentRunResponse>;
|
|
4
4
|
export declare function runAsync(req: CliAgentRunParams, apiKey: string): Promise<AgentRunAsyncResponse>;
|
|
5
5
|
export declare function runStream(req: CliAgentRunParams, apiKey: string, signal?: AbortSignal): AsyncGenerator<AgentRunWithStreamingResponse>;
|
|
6
6
|
export declare function listRuns(opts: RunListParams, apiKey: string): Promise<RunListResponse>;
|
|
7
7
|
export declare function getRun(runId: string, apiKey: string): Promise<Run>;
|
|
8
|
+
export declare function getRunSteps(runId: string, apiKey: string): Promise<RunStepsResponse>;
|
|
8
9
|
export declare function searchQuery(params: SearchQueryParams, apiKey: string): Promise<SearchQueryResponse>;
|
|
9
10
|
export declare function fetchContentGet(params: FetchGetContentsParams, apiKey: string): Promise<FetchResponse>;
|
|
10
|
-
export declare function browserSessionCreate(params:
|
|
11
|
+
export declare function browserSessionCreate(params: CliBrowserSessionCreateParams, apiKey: string): Promise<BrowserSession>;
|
|
11
12
|
export declare function cancelRun(runId: string, apiKey: string): Promise<CancelRunResponse>;
|
|
12
13
|
export declare function submitBatch(req: BatchRunRequest, apiKey: string): Promise<BatchRunResponse>;
|
|
13
14
|
export declare function getBatchRuns(runIds: string[], apiKey: string): Promise<BatchGetResponse>;
|
package/dist/lib/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { APIStatusError, fetchResponseSchema, browserSessionSchema, searchQueryResponseSchema, TinyFish, runSchema, } from "@tiny-fish/sdk";
|
|
1
|
+
import { APIStatusError, agentRunAsyncResponseSchema, agentRunResponseSchema, agentRunWithStreamingResponseSchema, fetchResponseSchema, browserSessionSchema, searchQueryResponseSchema, TinyFish, runSchema, runStatusSchema, } from "@tiny-fish/sdk";
|
|
2
2
|
import { BASE_URL } from "./constants.js";
|
|
3
3
|
import { ApiError } from "./output.js";
|
|
4
4
|
import { z } from "zod";
|
|
@@ -54,6 +54,23 @@ const batchCancelResponseSchema = z.object({
|
|
|
54
54
|
results: z.array(batchCancelResultSchema),
|
|
55
55
|
not_found: z.array(z.string()).nullable(),
|
|
56
56
|
});
|
|
57
|
+
const runStepSchema = z.object({
|
|
58
|
+
id: z.string(),
|
|
59
|
+
timestamp: z.string(),
|
|
60
|
+
status: z.string(),
|
|
61
|
+
action: z.string().nullable(),
|
|
62
|
+
screenshot: z.string().nullable(),
|
|
63
|
+
duration: z.string().nullable(),
|
|
64
|
+
target_elements: z.array(z.unknown()).optional(),
|
|
65
|
+
snapshot_url: z.string().optional(),
|
|
66
|
+
});
|
|
67
|
+
const runStepsResponseSchema = z.object({
|
|
68
|
+
run_id: z.string(),
|
|
69
|
+
// Same payload as `GET /v1/runs/:id` — `?screenshots=none` just suppresses base64 image fields.
|
|
70
|
+
// We surface `status` so callers (e.g. `agent run watch` polling) can avoid a redundant get-run.
|
|
71
|
+
status: runStatusSchema,
|
|
72
|
+
steps: z.array(runStepSchema),
|
|
73
|
+
});
|
|
57
74
|
function parseWithSchema(schema, value, message) {
|
|
58
75
|
const result = schema.safeParse(value);
|
|
59
76
|
if (!result.success) {
|
|
@@ -61,9 +78,59 @@ function parseWithSchema(schema, value, message) {
|
|
|
61
78
|
}
|
|
62
79
|
return result.data;
|
|
63
80
|
}
|
|
81
|
+
function normalizeStreamEvent(event) {
|
|
82
|
+
if (typeof event !== "object" || event === null || event.type !== "COMPLETE") {
|
|
83
|
+
return event;
|
|
84
|
+
}
|
|
85
|
+
const data = event;
|
|
86
|
+
return {
|
|
87
|
+
...data,
|
|
88
|
+
result: typeof data["result"] === "object" && data["result"] !== null ? data["result"] : null,
|
|
89
|
+
error: typeof data["error"] === "object" && data["error"] !== null ? data["error"] : null,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function* parseSseStream(stream) {
|
|
93
|
+
const reader = stream.getReader();
|
|
94
|
+
let buffer = "";
|
|
95
|
+
const parseLine = (rawLine) => {
|
|
96
|
+
const line = rawLine.trim();
|
|
97
|
+
if (!line || line.startsWith(":") || !line.startsWith("data:"))
|
|
98
|
+
return undefined;
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(line.slice("data:".length).trim());
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
try {
|
|
107
|
+
while (true) {
|
|
108
|
+
const { done, value } = await reader.read();
|
|
109
|
+
if (done)
|
|
110
|
+
break;
|
|
111
|
+
buffer += value;
|
|
112
|
+
const lines = buffer.split("\n");
|
|
113
|
+
buffer = lines.pop() ?? "";
|
|
114
|
+
for (const rawLine of lines) {
|
|
115
|
+
const event = parseLine(rawLine);
|
|
116
|
+
if (event !== undefined)
|
|
117
|
+
yield event;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const finalEvent = parseLine(buffer);
|
|
121
|
+
if (finalEvent !== undefined)
|
|
122
|
+
yield finalEvent;
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
reader.releaseLock();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
64
128
|
export async function runSync(req, apiKey) {
|
|
65
129
|
try {
|
|
66
|
-
|
|
130
|
+
const response = await createSdkClient(apiKey).post("/v1/automation/run", {
|
|
131
|
+
json: req,
|
|
132
|
+
});
|
|
133
|
+
return parseWithSchema(agentRunResponseSchema, response, "Invalid agent run response");
|
|
67
134
|
}
|
|
68
135
|
catch (error) {
|
|
69
136
|
rethrowSdkError(error);
|
|
@@ -71,7 +138,10 @@ export async function runSync(req, apiKey) {
|
|
|
71
138
|
}
|
|
72
139
|
export async function runAsync(req, apiKey) {
|
|
73
140
|
try {
|
|
74
|
-
|
|
141
|
+
const response = await createSdkClient(apiKey).post("/v1/automation/run-async", {
|
|
142
|
+
json: req,
|
|
143
|
+
});
|
|
144
|
+
return parseWithSchema(agentRunAsyncResponseSchema, response, "Invalid async run response");
|
|
75
145
|
}
|
|
76
146
|
catch (error) {
|
|
77
147
|
rethrowSdkError(error);
|
|
@@ -80,9 +150,14 @@ export async function runAsync(req, apiKey) {
|
|
|
80
150
|
export async function* runStream(req, apiKey, signal) {
|
|
81
151
|
let stream = null;
|
|
82
152
|
try {
|
|
83
|
-
stream = await createSdkClient(apiKey).
|
|
84
|
-
|
|
85
|
-
|
|
153
|
+
stream = await createSdkClient(apiKey).postStream("/v1/automation/run-sse", {
|
|
154
|
+
json: req,
|
|
155
|
+
signal,
|
|
156
|
+
});
|
|
157
|
+
for await (const event of parseSseStream(stream)) {
|
|
158
|
+
const parsed = agentRunWithStreamingResponseSchema.safeParse(normalizeStreamEvent(event));
|
|
159
|
+
if (parsed.success)
|
|
160
|
+
yield parsed.data;
|
|
86
161
|
}
|
|
87
162
|
}
|
|
88
163
|
catch (error) {
|
|
@@ -91,7 +166,7 @@ export async function* runStream(req, apiKey, signal) {
|
|
|
91
166
|
finally {
|
|
92
167
|
if (stream) {
|
|
93
168
|
try {
|
|
94
|
-
await stream.
|
|
169
|
+
await stream.cancel();
|
|
95
170
|
}
|
|
96
171
|
catch {
|
|
97
172
|
// Ignore close errors — the original stream result/error already decided control flow.
|
|
@@ -115,6 +190,17 @@ export async function getRun(runId, apiKey) {
|
|
|
115
190
|
rethrowSdkError(error);
|
|
116
191
|
}
|
|
117
192
|
}
|
|
193
|
+
export async function getRunSteps(runId, apiKey) {
|
|
194
|
+
try {
|
|
195
|
+
// The public GET /v1/runs/:id route returns the base run payload plus `steps`;
|
|
196
|
+
// the installed SDK runSchema only models the base run, so steps need this local schema.
|
|
197
|
+
const response = await createSdkClient(apiKey).get(`/v1/runs/${encodeURIComponent(runId)}`, { params: { screenshots: "none" } });
|
|
198
|
+
return parseWithSchema(runStepsResponseSchema, response, "Invalid run steps response");
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
rethrowSdkError(error);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
118
204
|
export async function searchQuery(params, apiKey) {
|
|
119
205
|
try {
|
|
120
206
|
const response = await createSdkClient(apiKey).search.query(params);
|
|
@@ -135,7 +221,7 @@ export async function fetchContentGet(params, apiKey) {
|
|
|
135
221
|
}
|
|
136
222
|
export async function browserSessionCreate(params, apiKey) {
|
|
137
223
|
try {
|
|
138
|
-
const response = await createSdkClient(apiKey).browser
|
|
224
|
+
const response = await createSdkClient(apiKey).post("/v1/browser", { json: params });
|
|
139
225
|
return browserSessionSchema.parse(response);
|
|
140
226
|
}
|
|
141
227
|
catch (error) {
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -1,7 +1,32 @@
|
|
|
1
|
-
import type { AgentRunParams, Run, RunStatus } from "@tiny-fish/sdk";
|
|
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
|
+
agent_config?: {
|
|
6
|
+
mode?: "default" | "strict";
|
|
7
|
+
max_steps?: number;
|
|
8
|
+
};
|
|
9
|
+
browser_profile?: BrowserProfile;
|
|
10
|
+
session_id?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface RunStep {
|
|
13
|
+
id: string;
|
|
14
|
+
timestamp: string;
|
|
15
|
+
status: string;
|
|
16
|
+
action: string | null;
|
|
17
|
+
screenshot: string | null;
|
|
18
|
+
duration: string | null;
|
|
19
|
+
target_elements?: unknown[];
|
|
20
|
+
snapshot_url?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface RunStepsResponse {
|
|
23
|
+
run_id: string;
|
|
24
|
+
status: RunStatus;
|
|
25
|
+
steps: RunStep[];
|
|
26
|
+
}
|
|
27
|
+
export interface CliBrowserSessionCreateParams {
|
|
28
|
+
url?: string;
|
|
29
|
+
browser_profile?: "lite" | "stealth";
|
|
5
30
|
}
|
|
6
31
|
export interface CancelRunResponse {
|
|
7
32
|
run_id: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiny-fish/cli",
|
|
3
|
-
"version": "0.1.5-next.
|
|
3
|
+
"version": "0.1.5-next.50",
|
|
4
4
|
"description": "TinyFish CLI — run web automations from your terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
},
|
|
50
50
|
"overrides": {
|
|
51
51
|
"vite": "7.3.2",
|
|
52
|
-
"brace-expansion": "5.0.5"
|
|
52
|
+
"brace-expansion": "5.0.5",
|
|
53
|
+
"postcss": "8.5.10"
|
|
53
54
|
},
|
|
54
55
|
"engines": {
|
|
55
56
|
"node": ">=24.0.0"
|