@tiny-fish/cli 0.1.0 → 0.1.1
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/run.d.ts +1 -1
- package/dist/commands/run.js +47 -20
- package/dist/commands/runs.d.ts +1 -1
- package/dist/commands/runs.js +5 -8
- package/dist/index.js +7 -2
- package/dist/lib/client.js +3 -1
- package/dist/lib/types.d.ts +9 -9
- package/package.json +8 -4
package/dist/commands/run.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
export declare function registerRun(
|
|
2
|
+
export declare function registerRun(agentCmd: Command): Command;
|
package/dist/commands/run.js
CHANGED
|
@@ -2,45 +2,59 @@ import { getApiKey } from "../lib/auth.js";
|
|
|
2
2
|
import { runAsync, runStream, runSync } from "../lib/client.js";
|
|
3
3
|
import { err, handleApiError, out, outLine } from "../lib/output.js";
|
|
4
4
|
import { RunStatus, StreamEventType } from "../lib/types.js";
|
|
5
|
+
import { BASE_URL } from "../lib/constants.js";
|
|
5
6
|
const RUN_TIMEOUT_MS = 20 * 60 * 1000; // 20 minutes
|
|
6
7
|
function checkRunResult(result, expectComplete = true) {
|
|
7
8
|
if (result.error) {
|
|
8
|
-
err({ error: result.error,
|
|
9
|
+
err({ error: result.error, run_id: result.run_id, status: result.status });
|
|
9
10
|
process.exit(1);
|
|
10
11
|
}
|
|
11
12
|
if (expectComplete && result.status && result.status !== RunStatus.COMPLETED) {
|
|
12
|
-
err({ error: `Run ${result.status}`,
|
|
13
|
+
err({ error: `Run ${result.status}`, run_id: result.run_id, status: result.status });
|
|
13
14
|
process.exit(1);
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
|
-
export function registerRun(
|
|
17
|
-
|
|
18
|
-
.command("run
|
|
19
|
-
.description("Run a browser automation")
|
|
20
|
-
.
|
|
17
|
+
export function registerRun(agentCmd) {
|
|
18
|
+
const runCmd = agentCmd
|
|
19
|
+
.command("run")
|
|
20
|
+
.description("Run a browser automation or manage runs")
|
|
21
|
+
.option("--url <url>", "Target URL for the automation")
|
|
21
22
|
.option("--sync", "Wait for result without streaming steps")
|
|
22
23
|
.option("--async", "Submit without waiting for result")
|
|
23
24
|
.option("--pretty", "Human-readable output")
|
|
25
|
+
.argument("[goal]", "Goal to automate")
|
|
24
26
|
.action(async (goal, opts) => {
|
|
27
|
+
// If no goal is provided and no subcommand matched, show help
|
|
28
|
+
if (!goal) {
|
|
29
|
+
runCmd.help();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!opts.url) {
|
|
33
|
+
err({ error: "required option '--url <url>' not specified" });
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
25
36
|
if (opts.sync && opts.async) {
|
|
26
37
|
err({ error: "--sync and --async are mutually exclusive" });
|
|
27
38
|
process.exit(1);
|
|
28
39
|
}
|
|
29
|
-
//
|
|
40
|
+
// Normalise URL: accept bare hostnames by prepending https://.
|
|
41
|
+
// The server requires a valid URL — it does not auto-prefix.
|
|
42
|
+
let normalizedUrl = opts.url;
|
|
30
43
|
try {
|
|
31
44
|
new URL(opts.url);
|
|
32
45
|
}
|
|
33
46
|
catch {
|
|
34
47
|
try {
|
|
35
48
|
new URL(`https://${opts.url}`);
|
|
49
|
+
normalizedUrl = `https://${opts.url}`;
|
|
36
50
|
}
|
|
37
51
|
catch {
|
|
38
|
-
err({ error:
|
|
52
|
+
err({ error: `Invalid --url "${opts.url}". Provide a valid URL, e.g. https://example.com or example.com` });
|
|
39
53
|
process.exit(1);
|
|
40
54
|
}
|
|
41
55
|
}
|
|
42
56
|
const apiKey = getApiKey();
|
|
43
|
-
const req = { goal, url:
|
|
57
|
+
const req = { goal, url: normalizedUrl };
|
|
44
58
|
if (opts.async) {
|
|
45
59
|
let result;
|
|
46
60
|
try {
|
|
@@ -50,11 +64,13 @@ export function registerRun(program) {
|
|
|
50
64
|
handleApiError(e);
|
|
51
65
|
}
|
|
52
66
|
checkRunResult(result, false); // async = PENDING is expected
|
|
67
|
+
const { run_id: _asyncRunId, ...asyncRest } = result; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
68
|
+
const asyncResultWithUrl = { run_id: result.run_id, run_url: `${BASE_URL}/runs/${result.run_id}`, ...asyncRest };
|
|
53
69
|
if (opts.pretty) {
|
|
54
|
-
outLine(`Run submitted\nID: ${
|
|
70
|
+
outLine(`Run submitted\nID: ${asyncResultWithUrl.run_id}\nStatus: ${asyncResultWithUrl.status}\nURL: ${asyncResultWithUrl.run_url}`);
|
|
55
71
|
}
|
|
56
72
|
else {
|
|
57
|
-
out(
|
|
73
|
+
out(asyncResultWithUrl);
|
|
58
74
|
}
|
|
59
75
|
return;
|
|
60
76
|
}
|
|
@@ -68,11 +84,13 @@ export function registerRun(program) {
|
|
|
68
84
|
handleApiError(e);
|
|
69
85
|
}
|
|
70
86
|
checkRunResult(result);
|
|
87
|
+
const { run_id: _syncRunId, ...syncRest } = result; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
88
|
+
const syncResultWithUrl = { run_id: result.run_id, run_url: `${BASE_URL}/runs/${result.run_id}`, ...syncRest };
|
|
71
89
|
if (opts.pretty) {
|
|
72
|
-
outLine(`Status: ${
|
|
90
|
+
outLine(`Status: ${syncResultWithUrl.status}\n\n${JSON.stringify(syncResultWithUrl.result ?? {}, null, 2)}`);
|
|
73
91
|
}
|
|
74
92
|
else {
|
|
75
|
-
out(
|
|
93
|
+
out(syncResultWithUrl);
|
|
76
94
|
}
|
|
77
95
|
return;
|
|
78
96
|
}
|
|
@@ -112,6 +130,7 @@ export function registerRun(program) {
|
|
|
112
130
|
if (streamFailed)
|
|
113
131
|
process.exit(1);
|
|
114
132
|
});
|
|
133
|
+
return runCmd;
|
|
115
134
|
}
|
|
116
135
|
/**
|
|
117
136
|
* Handle one SSE event from the stream.
|
|
@@ -119,11 +138,11 @@ export function registerRun(program) {
|
|
|
119
138
|
*/
|
|
120
139
|
function handleStreamEvent(event, pretty) {
|
|
121
140
|
if (event.type === StreamEventType.ERROR) {
|
|
122
|
-
err({ error: event.error,
|
|
141
|
+
err({ error: event.error, run_id: event.run_id, status: StreamEventType.ERROR });
|
|
123
142
|
return false;
|
|
124
143
|
}
|
|
125
144
|
if (event.type === StreamEventType.COMPLETE && event.status !== RunStatus.COMPLETED) {
|
|
126
|
-
err({ error: event.error ?? `Run ${event.status}`,
|
|
145
|
+
err({ error: event.error ?? `Run ${event.status}`, run_id: event.run_id, status: event.status });
|
|
127
146
|
return false;
|
|
128
147
|
}
|
|
129
148
|
if (pretty) {
|
|
@@ -132,14 +151,16 @@ function handleStreamEvent(event, pretty) {
|
|
|
132
151
|
outLine(`▶ Run started`);
|
|
133
152
|
break;
|
|
134
153
|
case StreamEventType.STREAMING_URL:
|
|
135
|
-
outLine(`🔗 Live view: ${event.
|
|
154
|
+
outLine(`🔗 Live view: ${event.streaming_url}`);
|
|
136
155
|
break;
|
|
137
156
|
case StreamEventType.PROGRESS:
|
|
138
157
|
outLine(`• ${event.purpose}`);
|
|
139
158
|
break;
|
|
140
|
-
case StreamEventType.COMPLETE:
|
|
141
|
-
|
|
159
|
+
case StreamEventType.COMPLETE: {
|
|
160
|
+
const runUrl = `${BASE_URL}/runs/${event.run_id}`;
|
|
161
|
+
outLine(`✓ Completed\n\n${JSON.stringify(event.result ?? {}, null, 2)}\n\nView run: ${runUrl}`);
|
|
142
162
|
break;
|
|
163
|
+
}
|
|
143
164
|
case StreamEventType.HEARTBEAT:
|
|
144
165
|
// silently skip — keep-alive noise
|
|
145
166
|
break;
|
|
@@ -147,7 +168,13 @@ function handleStreamEvent(event, pretty) {
|
|
|
147
168
|
}
|
|
148
169
|
else {
|
|
149
170
|
// Raw JSON: emit all events (STREAMING_URL and HEARTBEAT are useful for agents)
|
|
150
|
-
|
|
171
|
+
if (event.type === StreamEventType.COMPLETE) {
|
|
172
|
+
const { run_id: _eventRunId, ...eventRest } = event; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
173
|
+
out({ run_id: event.run_id, run_url: `${BASE_URL}/runs/${event.run_id}`, ...eventRest });
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
out(event);
|
|
177
|
+
}
|
|
151
178
|
}
|
|
152
179
|
return true;
|
|
153
180
|
}
|
package/dist/commands/runs.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
export declare function registerRuns(
|
|
2
|
+
export declare function registerRuns(runCmd: Command): void;
|
package/dist/commands/runs.js
CHANGED
|
@@ -3,12 +3,9 @@ import { getRun, listRuns } from "../lib/client.js";
|
|
|
3
3
|
import { err, handleApiError, out, outLine } from "../lib/output.js";
|
|
4
4
|
import { RunStatus } from "../lib/types.js";
|
|
5
5
|
const VALID_STATUSES = Object.values(RunStatus);
|
|
6
|
-
export function registerRuns(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
.description("Manage past automation runs");
|
|
10
|
-
// ── runs list ──────────────────────────────────────────────────────────────
|
|
11
|
-
runs
|
|
6
|
+
export function registerRuns(runCmd) {
|
|
7
|
+
// ── run list ───────────────────────────────────────────────────────────────
|
|
8
|
+
runCmd
|
|
12
9
|
.command("list")
|
|
13
10
|
.description("List your automation runs")
|
|
14
11
|
.option("--status <status>", "Filter by status (PENDING|RUNNING|COMPLETED|FAILED|CANCELLED)")
|
|
@@ -65,8 +62,8 @@ export function registerRuns(program) {
|
|
|
65
62
|
handleApiError(e);
|
|
66
63
|
}
|
|
67
64
|
});
|
|
68
|
-
// ──
|
|
69
|
-
|
|
65
|
+
// ── run get ────────────────────────────────────────────────────────────────
|
|
66
|
+
runCmd
|
|
70
67
|
.command("get <run_id>")
|
|
71
68
|
.description("Get a run by ID")
|
|
72
69
|
.option("--pretty", "Human-readable output")
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ program
|
|
|
12
12
|
.version(version, "-V, --version", "Show version")
|
|
13
13
|
.helpOption("-h, --help", "Show help")
|
|
14
14
|
.addHelpCommand(false)
|
|
15
|
+
.enablePositionalOptions()
|
|
15
16
|
.option("--debug", "Print HTTP requests and responses to stderr (or set TINYFISH_DEBUG=1)")
|
|
16
17
|
.hook("preAction", (cmd) => {
|
|
17
18
|
if (cmd.opts().debug) {
|
|
@@ -19,8 +20,12 @@ program
|
|
|
19
20
|
}
|
|
20
21
|
});
|
|
21
22
|
registerAuth(program);
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
const agentCmd = program
|
|
24
|
+
.command("agent")
|
|
25
|
+
.description("Agent automation commands")
|
|
26
|
+
.enablePositionalOptions();
|
|
27
|
+
const runCmd = registerRun(agentCmd);
|
|
28
|
+
registerRuns(runCmd);
|
|
24
29
|
// Await parseAsync so async command handlers complete before the process exits
|
|
25
30
|
program.parseAsync(process.argv).catch((e) => {
|
|
26
31
|
process.stderr.write(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }) + "\n");
|
package/dist/lib/client.js
CHANGED
|
@@ -18,7 +18,9 @@ function headers(apiKey) {
|
|
|
18
18
|
async function throwIfError(res) {
|
|
19
19
|
if (!res.ok) {
|
|
20
20
|
const json = await res.json().catch(() => ({}));
|
|
21
|
-
const message = json.
|
|
21
|
+
const message = json.error?.message ??
|
|
22
|
+
json.detail ??
|
|
23
|
+
`HTTP ${res.status}`;
|
|
22
24
|
throw new ApiError(res.status, message);
|
|
23
25
|
}
|
|
24
26
|
}
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -20,41 +20,41 @@ export declare const StreamEventType: {
|
|
|
20
20
|
};
|
|
21
21
|
export type StreamEventType = (typeof StreamEventType)[keyof typeof StreamEventType];
|
|
22
22
|
export interface RunResult {
|
|
23
|
-
|
|
23
|
+
run_id: string;
|
|
24
24
|
status: RunStatus;
|
|
25
|
-
|
|
25
|
+
result?: unknown;
|
|
26
26
|
error?: string;
|
|
27
27
|
}
|
|
28
28
|
/** Discriminated union of all real SSE event shapes from the API */
|
|
29
29
|
export type StreamEvent = {
|
|
30
30
|
type: typeof StreamEventType.STARTED;
|
|
31
|
-
|
|
31
|
+
run_id: string;
|
|
32
32
|
timestamp: string;
|
|
33
33
|
} | {
|
|
34
34
|
type: typeof StreamEventType.STREAMING_URL;
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
run_id: string;
|
|
36
|
+
streaming_url: string;
|
|
37
37
|
timestamp: string;
|
|
38
38
|
} | {
|
|
39
39
|
type: typeof StreamEventType.HEARTBEAT;
|
|
40
40
|
timestamp: string;
|
|
41
41
|
} | {
|
|
42
42
|
type: typeof StreamEventType.PROGRESS;
|
|
43
|
-
|
|
43
|
+
run_id: string;
|
|
44
44
|
purpose: string;
|
|
45
45
|
timestamp: string;
|
|
46
46
|
} | {
|
|
47
47
|
type: typeof StreamEventType.COMPLETE;
|
|
48
|
-
|
|
48
|
+
run_id: string;
|
|
49
49
|
status: RunStatus;
|
|
50
50
|
timestamp: string;
|
|
51
|
-
|
|
51
|
+
result?: unknown;
|
|
52
52
|
error?: string;
|
|
53
53
|
help_url?: string;
|
|
54
54
|
help_message?: string;
|
|
55
55
|
} | {
|
|
56
56
|
type: typeof StreamEventType.ERROR;
|
|
57
|
-
|
|
57
|
+
run_id?: string;
|
|
58
58
|
error: string;
|
|
59
59
|
timestamp: string;
|
|
60
60
|
};
|
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.1",
|
|
4
|
+
"description": "TinyFish CLI \u2014 run web automations from your terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"tinyfish": "./dist/index.js"
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsc",
|
|
16
|
-
"prepack": "npm run build",
|
|
17
16
|
"test": "vitest --run",
|
|
18
17
|
"test:file": "vitest --run",
|
|
19
18
|
"test:watch": "vitest",
|
|
20
19
|
"test:integration": "vitest --run --config vitest.integration.config.ts",
|
|
21
20
|
"lint": "eslint src tests",
|
|
22
21
|
"format": "prettier --write src tests",
|
|
23
|
-
"type-check": "tsc --noEmit --project tsconfig.all.json"
|
|
22
|
+
"type-check": "tsc --noEmit --project tsconfig.all.json",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"commander": "^12.0.0",
|
|
@@ -37,5 +37,9 @@
|
|
|
37
37
|
},
|
|
38
38
|
"engines": {
|
|
39
39
|
"node": ">=24.0.0"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"registry": "https://registry.npmjs.org/",
|
|
43
|
+
"access": "restricted"
|
|
40
44
|
}
|
|
41
45
|
}
|