@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 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
@@ -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);
@@ -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;
@@ -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>")
@@ -1,13 +1,14 @@
1
- import { type AgentRunAsyncResponse, type AgentRunResponse, type AgentRunWithStreamingResponse, type FetchGetContentsParams, type FetchResponse, type BrowserSession, type BrowserSessionCreateParams, type Run, type RunListParams, type RunListResponse, type SearchQueryParams, type SearchQueryResponse } from "@tiny-fish/sdk";
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: BrowserSessionCreateParams, apiKey: string): Promise<BrowserSession>;
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>;
@@ -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
- return await createSdkClient(apiKey).agent.run(req);
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
- return await createSdkClient(apiKey).agent.queue(req);
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).agent.stream(req, { signal });
84
- for await (const event of stream) {
85
- yield event;
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.close();
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.sessions.create(params);
224
+ const response = await createSdkClient(apiKey).post("/v1/browser", { json: params });
139
225
  return browserSessionSchema.parse(response);
140
226
  }
141
227
  catch (error) {
@@ -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.47",
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"