@tiny-fish/cli 0.1.3 → 0.1.4-next.35
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/batch.d.ts +2 -0
- package/dist/commands/batch.js +223 -0
- package/dist/commands/run.js +43 -31
- package/dist/commands/runs.js +1 -1
- package/dist/index.js +2 -0
- package/dist/lib/auth.d.ts +1 -0
- package/dist/lib/auth.js +1 -1
- package/dist/lib/batch-store.d.ts +5 -0
- package/dist/lib/batch-store.js +32 -0
- package/dist/lib/client.d.ts +10 -6
- package/dist/lib/client.js +134 -100
- package/dist/lib/types.d.ts +33 -93
- package/dist/lib/types.js +1 -18
- package/package.json +12 -6
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import { getApiKey } from "../lib/auth.js";
|
|
4
|
+
import { cancelBatchRuns, getBatchRuns, submitBatch } from "../lib/client.js";
|
|
5
|
+
import { findBatch, loadBatches, saveBatch } from "../lib/batch-store.js";
|
|
6
|
+
import { err, handleApiError, out, outLine } from "../lib/output.js";
|
|
7
|
+
import { BASE_URL } from "../lib/constants.js";
|
|
8
|
+
// ── CSV parsing ───────────────────────────────────────────────────────────────
|
|
9
|
+
/**
|
|
10
|
+
* Parse one RFC 4180 field from `line` starting at `pos`.
|
|
11
|
+
* Returns { value, next } where `next` is the index after the field (at the
|
|
12
|
+
* delimiter comma or end-of-string). Throws on unclosed quotes.
|
|
13
|
+
*/
|
|
14
|
+
function parseField(line, pos, rowNum) {
|
|
15
|
+
if (line[pos] === '"') {
|
|
16
|
+
// Quoted field — read until closing unescaped quote
|
|
17
|
+
let value = "";
|
|
18
|
+
let i = pos + 1;
|
|
19
|
+
while (i < line.length) {
|
|
20
|
+
if (line[i] === '"') {
|
|
21
|
+
if (line[i + 1] === '"') {
|
|
22
|
+
value += '"';
|
|
23
|
+
i += 2;
|
|
24
|
+
continue;
|
|
25
|
+
} // escaped quote
|
|
26
|
+
i++; // skip closing quote
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
value += line[i++];
|
|
30
|
+
}
|
|
31
|
+
if (i > line.length)
|
|
32
|
+
throw new Error(`Row ${rowNum}: unclosed quote in field`);
|
|
33
|
+
// Skip optional comma after closing quote
|
|
34
|
+
return { value, next: line[i] === "," ? i + 1 : i };
|
|
35
|
+
}
|
|
36
|
+
// Unquoted field — read to next comma
|
|
37
|
+
const commaIdx = line.indexOf(",", pos);
|
|
38
|
+
if (commaIdx === -1)
|
|
39
|
+
return { value: line.slice(pos).trim(), next: line.length };
|
|
40
|
+
return { value: line.slice(pos, commaIdx).trim(), next: commaIdx + 1 };
|
|
41
|
+
}
|
|
42
|
+
/** Parse a strict two-column CSV with header row (url,goal). Supports RFC 4180 quoted fields. */
|
|
43
|
+
function parseCsv(content) {
|
|
44
|
+
const lines = content.split(/\r?\n/).filter((l) => l.trim().length > 0);
|
|
45
|
+
if (lines.length === 0)
|
|
46
|
+
throw new Error("CSV file is empty");
|
|
47
|
+
const header = lines[0].trim();
|
|
48
|
+
if (header !== "url,goal") {
|
|
49
|
+
throw new Error(`Invalid CSV header: "${header}". Expected: "url,goal"`);
|
|
50
|
+
}
|
|
51
|
+
const dataLines = lines.slice(1);
|
|
52
|
+
if (dataLines.length === 0)
|
|
53
|
+
throw new Error("CSV file has no data rows");
|
|
54
|
+
return dataLines.map((line, idx) => {
|
|
55
|
+
const rowNum = idx + 2;
|
|
56
|
+
const urlField = parseField(line, 0, rowNum);
|
|
57
|
+
if (urlField.next >= line.length && line.indexOf(",") === -1) {
|
|
58
|
+
throw new Error(`Row ${rowNum}: missing comma separator — "${line}"`);
|
|
59
|
+
}
|
|
60
|
+
const goalField = parseField(line, urlField.next, rowNum);
|
|
61
|
+
if (!urlField.value)
|
|
62
|
+
throw new Error(`Row ${rowNum}: missing url — "${line}"`);
|
|
63
|
+
if (!goalField.value)
|
|
64
|
+
throw new Error(`Row ${rowNum}: missing goal — "${line}"`);
|
|
65
|
+
return { url: urlField.value, goal: goalField.value };
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
// ── register ──────────────────────────────────────────────────────────────────
|
|
69
|
+
export function registerBatch(agentCmd) {
|
|
70
|
+
const batchCmd = agentCmd
|
|
71
|
+
.command("batch")
|
|
72
|
+
.description("Batch run operations")
|
|
73
|
+
.enablePositionalOptions();
|
|
74
|
+
// ── batch run ───────────────────────────────────────────────────────────────
|
|
75
|
+
batchCmd
|
|
76
|
+
.command("run")
|
|
77
|
+
.description("Submit a batch of runs from a CSV file (columns: url,goal)")
|
|
78
|
+
.requiredOption("--input <file>", "Path to CSV file with columns: url,goal")
|
|
79
|
+
.option("--pretty", "Human-readable output")
|
|
80
|
+
.action(async (opts) => {
|
|
81
|
+
// Read CSV file
|
|
82
|
+
let csvContent;
|
|
83
|
+
try {
|
|
84
|
+
csvContent = fs.readFileSync(opts.input, "utf8");
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
err({ error: `Cannot read file: ${opts.input}` });
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
// Parse CSV — throws on any malformed input
|
|
91
|
+
let runs;
|
|
92
|
+
try {
|
|
93
|
+
runs = parseCsv(csvContent);
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
err({ error: String(e instanceof Error ? e.message : e), recommended_action: "Fix the CSV file and try again" });
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
const apiKey = getApiKey();
|
|
100
|
+
const response = await submitBatch({ runs }, apiKey).catch(handleApiError);
|
|
101
|
+
// Surface application-level errors from the API before persisting
|
|
102
|
+
if (response.error)
|
|
103
|
+
handleApiError(response.error);
|
|
104
|
+
const runIds = response.run_ids;
|
|
105
|
+
if (!runIds || runIds.length === 0) {
|
|
106
|
+
err({ error: "Batch submission returned no run IDs", recommended_action: "Check the API key and try again" });
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
const submitted = runIds.length;
|
|
110
|
+
const batchId = randomUUID();
|
|
111
|
+
const createdAt = new Date().toISOString();
|
|
112
|
+
const batch = {
|
|
113
|
+
batch_id: batchId,
|
|
114
|
+
run_ids: runIds,
|
|
115
|
+
total: runs.length,
|
|
116
|
+
submitted,
|
|
117
|
+
created_at: createdAt,
|
|
118
|
+
};
|
|
119
|
+
saveBatch(batch);
|
|
120
|
+
const resultsUrl = `${BASE_URL}/runs`;
|
|
121
|
+
if (opts.pretty) {
|
|
122
|
+
outLine("Batch submitted");
|
|
123
|
+
outLine(`ID: ${batchId}`);
|
|
124
|
+
outLine(`Total: ${runs.length}`);
|
|
125
|
+
outLine(`Submitted: ${submitted}`);
|
|
126
|
+
outLine(`Runs URL: ${resultsUrl}`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
out({ data: { batch_id: batchId, total: runs.length, submitted, results_url: resultsUrl } });
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
// ── batch list ──────────────────────────────────────────────────────────────
|
|
133
|
+
batchCmd
|
|
134
|
+
.command("list")
|
|
135
|
+
.description("List locally tracked batches")
|
|
136
|
+
.option("--pretty", "Human-readable output")
|
|
137
|
+
.action((opts) => {
|
|
138
|
+
const batches = loadBatches();
|
|
139
|
+
if (opts.pretty) {
|
|
140
|
+
if (batches.length === 0) {
|
|
141
|
+
outLine("No batches found.");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
outLine(`${"BATCH ID".padEnd(38)} ${"TOTAL".padEnd(6)} ${"SUBMITTED".padEnd(9)} CREATED AT`);
|
|
145
|
+
outLine(`${"-".repeat(38)} ${"-".repeat(6)} ${"-".repeat(9)} ${"-".repeat(24)}`);
|
|
146
|
+
for (const b of batches) {
|
|
147
|
+
outLine(`${b.batch_id.padEnd(38)} ${String(b.total).padEnd(6)} ${String(b.submitted).padEnd(9)} ${b.created_at}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
out(batches);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
// ── batch get ───────────────────────────────────────────────────────────────
|
|
155
|
+
batchCmd
|
|
156
|
+
.command("get <batch_id>")
|
|
157
|
+
.description("Get run results for a batch")
|
|
158
|
+
.option("--pretty", "Human-readable output")
|
|
159
|
+
.action(async (batchId, opts) => {
|
|
160
|
+
const batch = findBatch(batchId);
|
|
161
|
+
if (!batch) {
|
|
162
|
+
err({ error: `Batch not found: ${batchId}` });
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
const apiKey = getApiKey();
|
|
166
|
+
const response = await getBatchRuns(batch.run_ids, apiKey).catch(handleApiError);
|
|
167
|
+
if (opts.pretty) {
|
|
168
|
+
outLine(`Batch: ${batchId}`);
|
|
169
|
+
outLine(`Total: ${batch.total}`);
|
|
170
|
+
outLine(`Found: ${response.data.length}`);
|
|
171
|
+
if (response.not_found && response.not_found.length > 0) {
|
|
172
|
+
outLine(`Not found: ${response.not_found.length}`);
|
|
173
|
+
}
|
|
174
|
+
for (const run of response.data) {
|
|
175
|
+
outLine("");
|
|
176
|
+
outLine(`Run: ${run.run_id}`);
|
|
177
|
+
outLine(`Status: ${run.status}${run.num_of_steps != null ? ` (${run.num_of_steps} steps)` : ""}`);
|
|
178
|
+
outLine(`Goal: ${run.goal}`);
|
|
179
|
+
if (run.result != null) {
|
|
180
|
+
outLine(`Result: ${JSON.stringify(run.result, null, 2).replace(/\n/g, "\n ")}`);
|
|
181
|
+
}
|
|
182
|
+
else if (run.error) {
|
|
183
|
+
outLine(`Error: ${JSON.stringify(run.error)}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
out({ batch_id: batchId, ...response });
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
// ── batch cancel ─────────────────────────────────────────────────────────────
|
|
192
|
+
batchCmd
|
|
193
|
+
.command("cancel <batch_id>")
|
|
194
|
+
.description("Cancel all runs in a batch")
|
|
195
|
+
.option("--pretty", "Human-readable output")
|
|
196
|
+
.action(async (batchId, opts) => {
|
|
197
|
+
const batch = findBatch(batchId);
|
|
198
|
+
if (!batch) {
|
|
199
|
+
err({ error: `Batch not found: ${batchId}` });
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
const apiKey = getApiKey();
|
|
203
|
+
const response = await cancelBatchRuns(batch.run_ids, apiKey).catch(handleApiError);
|
|
204
|
+
if (opts.pretty) {
|
|
205
|
+
const cancelled = response.results.filter((r) => r.status === "CANCELLED").length;
|
|
206
|
+
outLine(`Batch ${batchId}: ${cancelled}/${response.results.length} run(s) cancelled.`);
|
|
207
|
+
if (response.results.length > 0) {
|
|
208
|
+
outLine("");
|
|
209
|
+
outLine(`${"RUN ID".padEnd(36)} ${"STATUS".padEnd(10)} CANCELLED AT`);
|
|
210
|
+
outLine(`${"-".repeat(36)} ${"-".repeat(10)} ${"-".repeat(24)}`);
|
|
211
|
+
for (const r of response.results) {
|
|
212
|
+
outLine(`${r.run_id.padEnd(36)} ${r.status.padEnd(10)} ${r.cancelled_at ?? "-"}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (response.not_found && response.not_found.length > 0) {
|
|
216
|
+
outLine(`\nNot found: ${response.not_found.join(", ")}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
out(response);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
package/dist/commands/run.js
CHANGED
|
@@ -1,15 +1,30 @@
|
|
|
1
|
+
import { EventType, RunStatus } from "@tiny-fish/sdk";
|
|
1
2
|
import { getApiKey } from "../lib/auth.js";
|
|
2
3
|
import { cancelRun, runAsync, runStream, runSync } from "../lib/client.js";
|
|
3
4
|
import { err, errLine, handleApiError, out, outLine } from "../lib/output.js";
|
|
4
|
-
import { RunStatus, StreamEventType } from "../lib/types.js";
|
|
5
5
|
import { BASE_URL } from "../lib/constants.js";
|
|
6
|
-
|
|
6
|
+
function extractErrorMessage(error) {
|
|
7
|
+
if (typeof error === "string")
|
|
8
|
+
return error;
|
|
9
|
+
if (typeof error === "object" && error !== null) {
|
|
10
|
+
const maybeError = error;
|
|
11
|
+
if (typeof maybeError.message === "string") {
|
|
12
|
+
return maybeError.message;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
7
17
|
function checkRunResult(result, expectComplete = true) {
|
|
8
|
-
|
|
9
|
-
|
|
18
|
+
const errorMessage = extractErrorMessage(result.error);
|
|
19
|
+
if (errorMessage) {
|
|
20
|
+
err({
|
|
21
|
+
error: errorMessage,
|
|
22
|
+
run_id: result.run_id,
|
|
23
|
+
status: "status" in result ? result.status : undefined,
|
|
24
|
+
});
|
|
10
25
|
process.exit(1);
|
|
11
26
|
}
|
|
12
|
-
if (expectComplete && result.status && result.status !== RunStatus.COMPLETED) {
|
|
27
|
+
if (expectComplete && "status" in result && result.status && result.status !== RunStatus.COMPLETED) {
|
|
13
28
|
err({ error: `Run ${result.status}`, run_id: result.run_id, status: result.status });
|
|
14
29
|
process.exit(1);
|
|
15
30
|
}
|
|
@@ -68,8 +83,11 @@ export function registerRun(agentCmd) {
|
|
|
68
83
|
handleApiError(e);
|
|
69
84
|
}
|
|
70
85
|
checkRunResult(result, false); // async = PENDING is expected
|
|
71
|
-
const
|
|
72
|
-
|
|
86
|
+
const asyncResultWithUrl = {
|
|
87
|
+
...result,
|
|
88
|
+
status: RunStatus.PENDING,
|
|
89
|
+
run_url: `${BASE_URL}/runs/${result.run_id}`,
|
|
90
|
+
};
|
|
73
91
|
if (opts.pretty) {
|
|
74
92
|
outLine(`Run submitted\nID: ${asyncResultWithUrl.run_id}\nURL: ${asyncResultWithUrl.run_url}`);
|
|
75
93
|
}
|
|
@@ -82,14 +100,16 @@ export function registerRun(agentCmd) {
|
|
|
82
100
|
errLine("Waiting for result...");
|
|
83
101
|
let result;
|
|
84
102
|
try {
|
|
85
|
-
result = await runSync(req, apiKey
|
|
103
|
+
result = await runSync(req, apiKey);
|
|
86
104
|
}
|
|
87
105
|
catch (e) {
|
|
88
106
|
handleApiError(e);
|
|
89
107
|
}
|
|
90
108
|
checkRunResult(result);
|
|
91
|
-
const
|
|
92
|
-
|
|
109
|
+
const syncResultWithUrl = {
|
|
110
|
+
...result,
|
|
111
|
+
run_url: `${BASE_URL}/runs/${result.run_id}`,
|
|
112
|
+
};
|
|
93
113
|
if (opts.pretty) {
|
|
94
114
|
outLine(`Status: ${syncResultWithUrl.status}\n\n${JSON.stringify(syncResultWithUrl.result ?? {}, null, 2)}`);
|
|
95
115
|
}
|
|
@@ -98,11 +118,10 @@ export function registerRun(agentCmd) {
|
|
|
98
118
|
}
|
|
99
119
|
return;
|
|
100
120
|
}
|
|
101
|
-
// Default: SSE stream —
|
|
121
|
+
// Default: SSE stream — Ctrl+C cancels the active run.
|
|
102
122
|
const controller = new AbortController();
|
|
103
123
|
let siginted = false;
|
|
104
124
|
let capturedRunId = null;
|
|
105
|
-
const timeout = setTimeout(() => controller.abort(), RUN_TIMEOUT_MS);
|
|
106
125
|
const onSigint = () => {
|
|
107
126
|
siginted = true;
|
|
108
127
|
controller.abort();
|
|
@@ -115,6 +134,9 @@ export function registerRun(agentCmd) {
|
|
|
115
134
|
streamFailed = true;
|
|
116
135
|
break;
|
|
117
136
|
}
|
|
137
|
+
if (event.type === EventType.COMPLETE) {
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
118
140
|
}
|
|
119
141
|
}
|
|
120
142
|
catch (e) {
|
|
@@ -130,14 +152,9 @@ export function registerRun(agentCmd) {
|
|
|
130
152
|
process.stderr.write(cancelled ? "\nRun cancelled.\n" : "\n");
|
|
131
153
|
process.exit(130);
|
|
132
154
|
}
|
|
133
|
-
if (controller.signal.aborted) {
|
|
134
|
-
err({ error: "Run timed out after 20 minutes" });
|
|
135
|
-
process.exit(1);
|
|
136
|
-
}
|
|
137
155
|
handleApiError(e);
|
|
138
156
|
}
|
|
139
157
|
finally {
|
|
140
|
-
clearTimeout(timeout);
|
|
141
158
|
process.removeListener("SIGINT", onSigint);
|
|
142
159
|
}
|
|
143
160
|
if (streamFailed)
|
|
@@ -150,42 +167,37 @@ export function registerRun(agentCmd) {
|
|
|
150
167
|
* Returns false on terminal error (caller should exit 1); true otherwise.
|
|
151
168
|
*/
|
|
152
169
|
function handleStreamEvent(event, pretty, onRunId) {
|
|
153
|
-
if (event.type ===
|
|
154
|
-
err({ error: event.error
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
if (event.type === StreamEventType.COMPLETE && event.status !== RunStatus.COMPLETED) {
|
|
158
|
-
err({ error: event.error ?? `Run ${event.status}`, run_id: event.run_id, status: event.status });
|
|
170
|
+
if (event.type === EventType.COMPLETE && event.status !== RunStatus.COMPLETED) {
|
|
171
|
+
err({ error: event.error?.message ?? `Run ${event.status}`, run_id: event.run_id, status: event.status });
|
|
159
172
|
return false;
|
|
160
173
|
}
|
|
161
174
|
if (pretty) {
|
|
162
175
|
switch (event.type) {
|
|
163
|
-
case
|
|
176
|
+
case EventType.STARTED:
|
|
164
177
|
if (onRunId && event.run_id)
|
|
165
178
|
onRunId(event.run_id);
|
|
166
179
|
outLine(`▶ Run started`);
|
|
167
180
|
break;
|
|
168
|
-
case
|
|
181
|
+
case EventType.STREAMING_URL:
|
|
169
182
|
outLine(`🔗 Live view: ${event.streaming_url}`);
|
|
170
183
|
break;
|
|
171
|
-
case
|
|
184
|
+
case EventType.PROGRESS:
|
|
172
185
|
outLine(`• ${event.purpose}`);
|
|
173
186
|
break;
|
|
174
|
-
case
|
|
187
|
+
case EventType.COMPLETE: {
|
|
175
188
|
const runUrl = `${BASE_URL}/runs/${event.run_id}`;
|
|
176
189
|
outLine(`✓ Completed\n\n${JSON.stringify(event.result ?? {}, null, 2)}\n\nView run: ${runUrl}`);
|
|
177
190
|
break;
|
|
178
191
|
}
|
|
179
|
-
case
|
|
192
|
+
case EventType.HEARTBEAT:
|
|
180
193
|
// silently skip — keep-alive noise
|
|
181
194
|
break;
|
|
182
195
|
}
|
|
183
196
|
}
|
|
184
197
|
else {
|
|
185
198
|
// Raw JSON: emit all events (STREAMING_URL and HEARTBEAT are useful for agents)
|
|
186
|
-
if (event.type ===
|
|
187
|
-
|
|
188
|
-
out({ run_id: event.run_id, run_url: `${BASE_URL}/runs/${event.run_id}`, ...eventRest });
|
|
199
|
+
if (event.type === EventType.COMPLETE) {
|
|
200
|
+
out({ ...event, run_url: `${BASE_URL}/runs/${event.run_id}` });
|
|
189
201
|
}
|
|
190
202
|
else {
|
|
191
203
|
out(event);
|
package/dist/commands/runs.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { RunStatus } from "@tiny-fish/sdk";
|
|
1
2
|
import { getApiKey } from "../lib/auth.js";
|
|
2
3
|
import { cancelRun, getRun, listRuns } from "../lib/client.js";
|
|
3
4
|
import { err, handleApiError, out, outLine } from "../lib/output.js";
|
|
4
|
-
import { RunStatus } from "../lib/types.js";
|
|
5
5
|
const VALID_STATUSES = Object.values(RunStatus);
|
|
6
6
|
export function registerRuns(runCmd) {
|
|
7
7
|
// ── run list ───────────────────────────────────────────────────────────────
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { createRequire } from "module";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { registerAuth } from "./commands/auth.js";
|
|
5
|
+
import { registerBatch } from "./commands/batch.js";
|
|
5
6
|
import { registerRun } from "./commands/run.js";
|
|
6
7
|
import { registerRuns } from "./commands/runs.js";
|
|
7
8
|
const { version } = createRequire(import.meta.url)("../package.json");
|
|
@@ -26,6 +27,7 @@ const agentCmd = program
|
|
|
26
27
|
.enablePositionalOptions();
|
|
27
28
|
const runCmd = registerRun(agentCmd);
|
|
28
29
|
registerRuns(runCmd);
|
|
30
|
+
registerBatch(agentCmd);
|
|
29
31
|
// Await parseAsync so async command handlers complete before the process exits
|
|
30
32
|
program.parseAsync(process.argv).catch((e) => {
|
|
31
33
|
process.stderr.write(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }) + "\n");
|
package/dist/lib/auth.d.ts
CHANGED
package/dist/lib/auth.js
CHANGED
|
@@ -5,7 +5,7 @@ import { err } from "./output.js";
|
|
|
5
5
|
const KEY_PREFIXES = ["sk-tinyfish-", "sk-mino-"];
|
|
6
6
|
const MIN_KEY_LENGTH = 20;
|
|
7
7
|
export { DASHBOARD_URL } from "./constants.js";
|
|
8
|
-
function configDir() {
|
|
8
|
+
export function configDir() {
|
|
9
9
|
// os.homedir() is cross-platform; process.env.HOME is undefined on Windows
|
|
10
10
|
return path.join(os.homedir(), ".tinyfish");
|
|
11
11
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { LocalBatch } from "./types.js";
|
|
2
|
+
export declare function loadBatches(): LocalBatch[];
|
|
3
|
+
/** Appends `batch` to the persistent store. Reads existing entries first. */
|
|
4
|
+
export declare function saveBatch(batch: LocalBatch): void;
|
|
5
|
+
export declare function findBatch(batchId: string): LocalBatch | undefined;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import { configDir } from "./auth.js";
|
|
3
|
+
function batchesFile() {
|
|
4
|
+
return `${configDir()}/batches.json`;
|
|
5
|
+
}
|
|
6
|
+
export function loadBatches() {
|
|
7
|
+
const file = batchesFile();
|
|
8
|
+
try {
|
|
9
|
+
const raw = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
10
|
+
if (Array.isArray(raw))
|
|
11
|
+
return raw;
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
// File does not exist — treat as empty store (expected on first use)
|
|
16
|
+
if (typeof e === "object" && e !== null && e.code === "ENOENT")
|
|
17
|
+
return [];
|
|
18
|
+
// Any other error (corrupt JSON, permission denied) — rethrow rather than
|
|
19
|
+
// silently returning [] and risking saveBatch() overwriting the store.
|
|
20
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
21
|
+
throw new Error(`Failed to read batch store at ${file}: ${message}`, { cause: e });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Appends `batch` to the persistent store. Reads existing entries first. */
|
|
25
|
+
export function saveBatch(batch) {
|
|
26
|
+
fs.mkdirSync(configDir(), { recursive: true, mode: 0o700 });
|
|
27
|
+
const existing = loadBatches();
|
|
28
|
+
fs.writeFileSync(batchesFile(), JSON.stringify([...existing, batch], null, 2), { mode: 0o600 });
|
|
29
|
+
}
|
|
30
|
+
export function findBatch(batchId) {
|
|
31
|
+
return loadBatches().find((b) => b.batch_id === batchId);
|
|
32
|
+
}
|
package/dist/lib/client.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import type
|
|
2
|
-
|
|
3
|
-
export declare function
|
|
4
|
-
export declare function
|
|
5
|
-
export declare function
|
|
6
|
-
export declare function
|
|
1
|
+
import { type AgentRunAsyncResponse, type AgentRunParams, type AgentRunResponse, type AgentRunWithStreamingResponse, type Run, type RunListParams, type RunListResponse } from "@tiny-fish/sdk";
|
|
2
|
+
import type { BatchCancelResponse, BatchGetResponse, BatchRunRequest, BatchRunResponse, CancelRunResponse } from "./types.js";
|
|
3
|
+
export declare function runSync(req: AgentRunParams, apiKey: string): Promise<AgentRunResponse>;
|
|
4
|
+
export declare function runAsync(req: AgentRunParams, apiKey: string): Promise<AgentRunAsyncResponse>;
|
|
5
|
+
export declare function runStream(req: AgentRunParams, apiKey: string, signal?: AbortSignal): AsyncGenerator<AgentRunWithStreamingResponse>;
|
|
6
|
+
export declare function listRuns(opts: RunListParams, apiKey: string): Promise<RunListResponse>;
|
|
7
|
+
export declare function getRun(runId: string, apiKey: string): Promise<Run>;
|
|
7
8
|
export declare function cancelRun(runId: string, apiKey: string): Promise<CancelRunResponse>;
|
|
9
|
+
export declare function submitBatch(req: BatchRunRequest, apiKey: string): Promise<BatchRunResponse>;
|
|
10
|
+
export declare function getBatchRuns(runIds: string[], apiKey: string): Promise<BatchGetResponse>;
|
|
11
|
+
export declare function cancelBatchRuns(runIds: string[], apiKey: string): Promise<BatchCancelResponse>;
|
package/dist/lib/client.js
CHANGED
|
@@ -1,125 +1,159 @@
|
|
|
1
|
+
import { APIStatusError, TinyFish, runSchema, } from "@tiny-fish/sdk";
|
|
1
2
|
import { BASE_URL } from "./constants.js";
|
|
2
3
|
import { ApiError } from "./output.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
process.stderr.write(`[debug] ${msg}\n`);
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
class TinyFishCliClient extends TinyFish {
|
|
6
|
+
_buildHeaders() {
|
|
7
|
+
return {
|
|
8
|
+
...super._buildHeaders(),
|
|
9
|
+
"X-TF-Request-Origin": "tinyfish-cli",
|
|
10
|
+
};
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
13
|
+
function createSdkClient(apiKey, timeout) {
|
|
14
|
+
return new TinyFishCliClient({
|
|
15
|
+
apiKey,
|
|
16
|
+
baseURL: BASE_URL,
|
|
17
|
+
maxRetries: 0,
|
|
18
|
+
...(timeout !== undefined ? { timeout } : {}),
|
|
19
|
+
});
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
if (
|
|
23
|
-
|
|
24
|
-
const message = json.error?.message ??
|
|
25
|
-
json.detail ??
|
|
26
|
-
`HTTP ${res.status}`;
|
|
27
|
-
throw new ApiError(res.status, message);
|
|
21
|
+
function rethrowSdkError(error) {
|
|
22
|
+
if (error instanceof APIStatusError) {
|
|
23
|
+
throw new ApiError(error.statusCode, error.message);
|
|
28
24
|
}
|
|
25
|
+
if (error instanceof Error) {
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
throw new Error(String(error));
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
30
|
+
const cancelRunResponseSchema = z.object({
|
|
31
|
+
run_id: z.string(),
|
|
32
|
+
status: z.string(),
|
|
33
|
+
cancelled_at: z.string().nullable(),
|
|
34
|
+
message: z.string().nullable(),
|
|
35
|
+
});
|
|
36
|
+
const batchRunResponseSchema = z.object({
|
|
37
|
+
run_ids: z.array(z.string()).nullable(),
|
|
38
|
+
error: z.object({
|
|
39
|
+
code: z.string(),
|
|
40
|
+
message: z.string(),
|
|
41
|
+
}).nullable(),
|
|
42
|
+
});
|
|
43
|
+
const batchGetResponseSchema = z.object({
|
|
44
|
+
data: z.array(runSchema),
|
|
45
|
+
not_found: z.array(z.string()).nullable(),
|
|
46
|
+
});
|
|
47
|
+
const batchCancelResultSchema = z.object({
|
|
48
|
+
run_id: z.string(),
|
|
49
|
+
status: z.string(),
|
|
50
|
+
cancelled_at: z.string().nullable(),
|
|
51
|
+
message: z.string().nullable(),
|
|
52
|
+
});
|
|
53
|
+
const batchCancelResponseSchema = z.object({
|
|
54
|
+
results: z.array(batchCancelResultSchema),
|
|
55
|
+
not_found: z.array(z.string()).nullable(),
|
|
56
|
+
});
|
|
57
|
+
function parseWithSchema(schema, value, message) {
|
|
58
|
+
const result = schema.safeParse(value);
|
|
59
|
+
if (!result.success) {
|
|
60
|
+
throw new Error(message);
|
|
61
|
+
}
|
|
62
|
+
return result.data;
|
|
56
63
|
}
|
|
57
|
-
|
|
58
|
-
function* parseSseLine(line) {
|
|
59
|
-
const trimmed = line.trim();
|
|
60
|
-
if (!trimmed.startsWith("data:"))
|
|
61
|
-
return;
|
|
62
|
-
const data = trimmed.slice(5).trim();
|
|
63
|
-
if (!data || data === "[DONE]")
|
|
64
|
-
return;
|
|
64
|
+
export async function runSync(req, apiKey) {
|
|
65
65
|
try {
|
|
66
|
-
|
|
66
|
+
return await createSdkClient(apiKey).agent.run(req);
|
|
67
67
|
}
|
|
68
|
-
catch {
|
|
69
|
-
|
|
68
|
+
catch (error) {
|
|
69
|
+
rethrowSdkError(error);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
-
// ── Automation run ────────────────────────────────────────────────────────────
|
|
73
|
-
export async function runSync(req, apiKey, signal) {
|
|
74
|
-
const res = await postJson("/v1/automation/run", req, apiKey, signal);
|
|
75
|
-
return res.json();
|
|
76
|
-
}
|
|
77
72
|
export async function runAsync(req, apiKey) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
try {
|
|
74
|
+
return await createSdkClient(apiKey).agent.queue(req);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
rethrowSdkError(error);
|
|
78
|
+
}
|
|
81
79
|
}
|
|
82
80
|
export async function* runStream(req, apiKey, signal) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const decoder = new TextDecoder();
|
|
89
|
-
let buffer = "";
|
|
90
|
-
while (true) {
|
|
91
|
-
const { done, value } = await reader.read();
|
|
92
|
-
if (done) {
|
|
93
|
-
// Flush remaining buffer — handles final event without trailing newline
|
|
94
|
-
for (const line of buffer.split("\n")) {
|
|
95
|
-
yield* parseSseLine(line);
|
|
96
|
-
}
|
|
97
|
-
break;
|
|
81
|
+
let stream = null;
|
|
82
|
+
try {
|
|
83
|
+
stream = await createSdkClient(apiKey).agent.stream(req, { signal });
|
|
84
|
+
for await (const event of stream) {
|
|
85
|
+
yield event;
|
|
98
86
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
rethrowSdkError(error);
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
if (stream) {
|
|
93
|
+
try {
|
|
94
|
+
await stream.close();
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Ignore close errors — the original stream result/error already decided control flow.
|
|
98
|
+
}
|
|
104
99
|
}
|
|
105
100
|
}
|
|
106
101
|
}
|
|
107
|
-
// ── Run history ───────────────────────────────────────────────────────────────
|
|
108
102
|
export async function listRuns(opts, apiKey) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
params.set("cursor", opts.cursor);
|
|
116
|
-
const qs = params.size ? `?${params}` : "";
|
|
117
|
-
return getJson(`/v1/runs${qs}`, apiKey, AbortSignal.timeout(API_TIMEOUT_MS));
|
|
103
|
+
try {
|
|
104
|
+
return await createSdkClient(apiKey).runs.list(opts);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
rethrowSdkError(error);
|
|
108
|
+
}
|
|
118
109
|
}
|
|
119
110
|
export async function getRun(runId, apiKey) {
|
|
120
|
-
|
|
111
|
+
try {
|
|
112
|
+
return await createSdkClient(apiKey).runs.get(runId);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
rethrowSdkError(error);
|
|
116
|
+
}
|
|
121
117
|
}
|
|
122
118
|
export async function cancelRun(runId, apiKey) {
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
try {
|
|
120
|
+
const response = await createSdkClient(apiKey).post(`/v1/runs/${encodeURIComponent(runId)}/cancel`, { json: {} });
|
|
121
|
+
return parseWithSchema(cancelRunResponseSchema, response, "Invalid cancel run response");
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
rethrowSdkError(error);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
export async function submitBatch(req, apiKey) {
|
|
128
|
+
try {
|
|
129
|
+
const response = await createSdkClient(apiKey).post("/v1/automation/run-batch", {
|
|
130
|
+
json: req,
|
|
131
|
+
});
|
|
132
|
+
return parseWithSchema(batchRunResponseSchema, response, "Invalid batch run response");
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
rethrowSdkError(error);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
export async function getBatchRuns(runIds, apiKey) {
|
|
139
|
+
try {
|
|
140
|
+
const response = await createSdkClient(apiKey).post("/v1/runs/batch", {
|
|
141
|
+
json: { run_ids: runIds },
|
|
142
|
+
});
|
|
143
|
+
return parseWithSchema(batchGetResponseSchema, response, "Invalid batch get response");
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
rethrowSdkError(error);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
export async function cancelBatchRuns(runIds, apiKey) {
|
|
150
|
+
try {
|
|
151
|
+
const response = await createSdkClient(apiKey).post("/v1/runs/batch/cancel", {
|
|
152
|
+
json: { run_ids: runIds },
|
|
153
|
+
});
|
|
154
|
+
return parseWithSchema(batchCancelResponseSchema, response, "Invalid batch cancel response");
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
rethrowSdkError(error);
|
|
158
|
+
}
|
|
125
159
|
}
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -1,102 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
url: string;
|
|
4
|
-
}
|
|
5
|
-
export declare const RunStatus: {
|
|
6
|
-
readonly PENDING: "PENDING";
|
|
7
|
-
readonly RUNNING: "RUNNING";
|
|
8
|
-
readonly COMPLETED: "COMPLETED";
|
|
9
|
-
readonly FAILED: "FAILED";
|
|
10
|
-
readonly CANCELLED: "CANCELLED";
|
|
11
|
-
};
|
|
12
|
-
export type RunStatus = (typeof RunStatus)[keyof typeof RunStatus];
|
|
13
|
-
export declare const StreamEventType: {
|
|
14
|
-
readonly STARTED: "STARTED";
|
|
15
|
-
readonly STREAMING_URL: "STREAMING_URL";
|
|
16
|
-
readonly HEARTBEAT: "HEARTBEAT";
|
|
17
|
-
readonly PROGRESS: "PROGRESS";
|
|
18
|
-
readonly COMPLETE: "COMPLETE";
|
|
19
|
-
readonly ERROR: "ERROR";
|
|
20
|
-
};
|
|
21
|
-
export type StreamEventType = (typeof StreamEventType)[keyof typeof StreamEventType];
|
|
22
|
-
export interface RunResult {
|
|
23
|
-
run_id: string;
|
|
24
|
-
status: RunStatus;
|
|
25
|
-
result?: unknown;
|
|
26
|
-
error?: string;
|
|
27
|
-
}
|
|
28
|
-
/** Discriminated union of all real SSE event shapes from the API */
|
|
29
|
-
export type StreamEvent = {
|
|
30
|
-
type: typeof StreamEventType.STARTED;
|
|
31
|
-
run_id: string;
|
|
32
|
-
timestamp: string;
|
|
33
|
-
} | {
|
|
34
|
-
type: typeof StreamEventType.STREAMING_URL;
|
|
35
|
-
run_id: string;
|
|
36
|
-
streaming_url: string;
|
|
37
|
-
timestamp: string;
|
|
38
|
-
} | {
|
|
39
|
-
type: typeof StreamEventType.HEARTBEAT;
|
|
40
|
-
timestamp: string;
|
|
41
|
-
} | {
|
|
42
|
-
type: typeof StreamEventType.PROGRESS;
|
|
43
|
-
run_id: string;
|
|
44
|
-
purpose: string;
|
|
45
|
-
timestamp: string;
|
|
46
|
-
} | {
|
|
47
|
-
type: typeof StreamEventType.COMPLETE;
|
|
1
|
+
import type { Run, RunStatus } from "@tiny-fish/sdk";
|
|
2
|
+
export interface CancelRunResponse {
|
|
48
3
|
run_id: string;
|
|
49
|
-
status: RunStatus;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
error?: string;
|
|
53
|
-
help_url?: string;
|
|
54
|
-
help_message?: string;
|
|
55
|
-
} | {
|
|
56
|
-
type: typeof StreamEventType.ERROR;
|
|
57
|
-
run_id?: string;
|
|
58
|
-
error: string;
|
|
59
|
-
timestamp: string;
|
|
60
|
-
};
|
|
61
|
-
export interface RunError {
|
|
62
|
-
message: string;
|
|
63
|
-
category: string;
|
|
64
|
-
retry_after: string | null;
|
|
65
|
-
help_url: string | null;
|
|
66
|
-
help_message: string | null;
|
|
4
|
+
status: RunStatus | string;
|
|
5
|
+
cancelled_at: string | null;
|
|
6
|
+
message: string | null;
|
|
67
7
|
}
|
|
68
|
-
export interface
|
|
69
|
-
|
|
70
|
-
status: RunStatus;
|
|
8
|
+
export interface BatchRunEntry {
|
|
9
|
+
url: string;
|
|
71
10
|
goal: string;
|
|
72
|
-
created_at: string;
|
|
73
|
-
started_at: string | null;
|
|
74
|
-
finished_at: string | null;
|
|
75
|
-
num_of_steps: number;
|
|
76
|
-
result: unknown;
|
|
77
|
-
error: RunError | null;
|
|
78
|
-
streaming_url: string | null;
|
|
79
|
-
browser_config: {
|
|
80
|
-
proxy_enabled: boolean | null;
|
|
81
|
-
proxy_country_code: string | null;
|
|
82
|
-
} | null;
|
|
83
11
|
}
|
|
84
|
-
export interface
|
|
85
|
-
|
|
86
|
-
pagination: {
|
|
87
|
-
total: number;
|
|
88
|
-
next_cursor: string | null;
|
|
89
|
-
has_more: boolean;
|
|
90
|
-
};
|
|
12
|
+
export interface BatchRunRequest {
|
|
13
|
+
runs: BatchRunEntry[];
|
|
91
14
|
}
|
|
92
|
-
export interface
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
15
|
+
export interface BatchRunResponse {
|
|
16
|
+
run_ids: string[] | null;
|
|
17
|
+
error: {
|
|
18
|
+
code: string;
|
|
19
|
+
message: string;
|
|
20
|
+
} | null;
|
|
96
21
|
}
|
|
97
|
-
export interface
|
|
22
|
+
export interface BatchGetResponse {
|
|
23
|
+
data: Run[];
|
|
24
|
+
not_found: string[] | null;
|
|
25
|
+
}
|
|
26
|
+
export interface BatchCancelResult {
|
|
98
27
|
run_id: string;
|
|
99
|
-
status:
|
|
100
|
-
cancelled_at: string;
|
|
28
|
+
status: string;
|
|
29
|
+
cancelled_at: string | null;
|
|
101
30
|
message: string | null;
|
|
102
31
|
}
|
|
32
|
+
export interface BatchCancelResponse {
|
|
33
|
+
results: BatchCancelResult[];
|
|
34
|
+
not_found: string[] | null;
|
|
35
|
+
}
|
|
36
|
+
export interface LocalBatch {
|
|
37
|
+
batch_id: string;
|
|
38
|
+
run_ids: string[];
|
|
39
|
+
total: number;
|
|
40
|
+
submitted: number;
|
|
41
|
+
created_at: string;
|
|
42
|
+
}
|
package/dist/lib/types.js
CHANGED
|
@@ -1,18 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// ── Run status enum ───────────────────────────────────────────────────────────
|
|
3
|
-
export const RunStatus = {
|
|
4
|
-
PENDING: "PENDING",
|
|
5
|
-
RUNNING: "RUNNING",
|
|
6
|
-
COMPLETED: "COMPLETED",
|
|
7
|
-
FAILED: "FAILED",
|
|
8
|
-
CANCELLED: "CANCELLED",
|
|
9
|
-
};
|
|
10
|
-
// ── SSE event types ───────────────────────────────────────────────────────────
|
|
11
|
-
export const StreamEventType = {
|
|
12
|
-
STARTED: "STARTED",
|
|
13
|
-
STREAMING_URL: "STREAMING_URL",
|
|
14
|
-
HEARTBEAT: "HEARTBEAT",
|
|
15
|
-
PROGRESS: "PROGRESS",
|
|
16
|
-
COMPLETE: "COMPLETE",
|
|
17
|
-
ERROR: "ERROR",
|
|
18
|
-
};
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiny-fish/cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "TinyFish CLI
|
|
3
|
+
"version": "0.1.4-next.35",
|
|
4
|
+
"description": "TinyFish CLI — run web automations from your terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"tinyfish": "./dist/index.js"
|
|
@@ -23,18 +23,24 @@
|
|
|
23
23
|
"prepublishOnly": "npm run build"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
+
"@tiny-fish/sdk": "^0.0.7",
|
|
26
27
|
"commander": "^12.0.0",
|
|
27
|
-
"globals": "^17.4.0"
|
|
28
|
+
"globals": "^17.4.0",
|
|
29
|
+
"zod": "^4.3.6"
|
|
28
30
|
},
|
|
29
31
|
"devDependencies": {
|
|
32
|
+
"@eslint/js": "^10.0.1",
|
|
30
33
|
"@types/node": "^20.0.0",
|
|
31
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
32
|
-
"@typescript-eslint/parser": "^8.
|
|
33
|
-
"eslint": "^
|
|
34
|
+
"@typescript-eslint/eslint-plugin": "^8.57.2",
|
|
35
|
+
"@typescript-eslint/parser": "^8.57.2",
|
|
36
|
+
"eslint": "^10.1.0",
|
|
34
37
|
"prettier": "^3.0.0",
|
|
35
38
|
"typescript": "^5.0.0",
|
|
36
39
|
"vitest": "^3.0.0"
|
|
37
40
|
},
|
|
41
|
+
"overrides": {
|
|
42
|
+
"brace-expansion": "5.0.5"
|
|
43
|
+
},
|
|
38
44
|
"engines": {
|
|
39
45
|
"node": ">=24.0.0"
|
|
40
46
|
},
|