@tiny-fish/cli 0.18.1-next.170 → 0.18.1-next.173
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/auth.js +10 -1
- package/dist/commands/connect.js +18 -3
- package/dist/commands/upgrade.js +3 -0
- package/dist/index.js +2 -51
- package/dist/lib/auth.d.ts +14 -0
- package/dist/lib/auth.js +69 -17
- package/dist/lib/client.js +3 -3
- package/dist/lib/notice.d.ts +5 -2
- package/dist/lib/notice.js +13 -7
- package/dist/lib/output.d.ts +2 -0
- package/dist/lib/output.js +8 -0
- package/dist/program.d.ts +6 -0
- package/dist/program.js +57 -0
- package/package.json +1 -1
package/dist/commands/auth.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
2
|
import * as readline from "readline";
|
|
3
3
|
import { Option } from "commander";
|
|
4
|
-
import { clearConfig, getDashboardUrl, loadConfig, maskKey, saveConfig, validateKeyFormat, } from "../lib/auth.js";
|
|
4
|
+
import { CONNECT_ATTEMPT_ENV, clearConfig, getDashboardUrl, loadConfig, maskKey, savePendingConnectAttempt, saveConfig, validateKeyFormat, } from "../lib/auth.js";
|
|
5
5
|
import { err, errLine, out, outLine } from "../lib/output.js";
|
|
6
6
|
/**
|
|
7
7
|
* Read a key from stdin without echoing it to the terminal.
|
|
@@ -66,6 +66,13 @@ async function readKeyFromStdinOrPrompt() {
|
|
|
66
66
|
}
|
|
67
67
|
return readKeyHidden("Paste your API key: ");
|
|
68
68
|
}
|
|
69
|
+
// Sign-in and the connect that follows are separate copied commands, so a page-minted id
|
|
70
|
+
// handed to this process has to outlive it.
|
|
71
|
+
function parkSeedAttempt() {
|
|
72
|
+
const seed = process.env[CONNECT_ATTEMPT_ENV];
|
|
73
|
+
if (seed)
|
|
74
|
+
savePendingConnectAttempt(seed);
|
|
75
|
+
}
|
|
69
76
|
export function registerAuth(program) {
|
|
70
77
|
const auth = program.command("auth").description("Manage your TinyFish API key");
|
|
71
78
|
auth
|
|
@@ -110,6 +117,7 @@ export function registerAuth(program) {
|
|
|
110
117
|
process.exit(1);
|
|
111
118
|
}
|
|
112
119
|
saveConfig(key);
|
|
120
|
+
parkSeedAttempt();
|
|
113
121
|
out({ status: "ok", message: "API key saved", key_preview: maskKey(key) });
|
|
114
122
|
});
|
|
115
123
|
auth
|
|
@@ -127,6 +135,7 @@ export function registerAuth(program) {
|
|
|
127
135
|
process.exit(1);
|
|
128
136
|
}
|
|
129
137
|
saveConfig(key);
|
|
138
|
+
parkSeedAttempt();
|
|
130
139
|
out({ status: "ok", message: "API key saved", key_preview: maskKey(key) });
|
|
131
140
|
});
|
|
132
141
|
auth
|
package/dist/commands/connect.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import spawn from "cross-spawn";
|
|
4
|
-
import { CONNECT_SOURCE, loadConfig, persistApiKeyToEnvironment, resolvedApiKey, saveConnectContext, validateKeyFormat, validatedApiKey, writeConfig, } from "../lib/auth.js";
|
|
4
|
+
import { CONNECT_ATTEMPT_ENV, CONNECT_SOURCE, isAttemptId, loadConfig, persistApiKeyToEnvironment, resolvedApiKey, saveConnectContext, takePendingConnectAttempt, validateKeyFormat, validatedApiKey, writeConfig, } from "../lib/auth.js";
|
|
5
5
|
import { CLI_VERSION, TINYFISH_CLI_PACKAGE } from "../lib/constants.js";
|
|
6
6
|
import { cursorInstallDeeplink, cursorMcpPath, writeCursorMcpConfig, } from "../lib/cursor-config.js";
|
|
7
7
|
import { runConnectAll } from "../lib/connect-all.js";
|
|
@@ -287,9 +287,24 @@ const OPENCLAW = {
|
|
|
287
287
|
unavailableMessage: OPENCLAW_SKILL_UNAVAILABLE_MESSAGE,
|
|
288
288
|
},
|
|
289
289
|
};
|
|
290
|
+
/**
|
|
291
|
+
* One page-minted id seeds one install attempt, so `tinyfish onboard` connecting three agents
|
|
292
|
+
* does not report three installs under it. Later connects in the run fall back to a random id.
|
|
293
|
+
*/
|
|
294
|
+
function takeSeedAttemptId() {
|
|
295
|
+
const fromEnv = process.env[CONNECT_ATTEMPT_ENV];
|
|
296
|
+
// Both sources are read-and-clear, and the delete lands before any subprocess inherits it.
|
|
297
|
+
delete process.env[CONNECT_ATTEMPT_ENV];
|
|
298
|
+
const parked = takePendingConnectAttempt();
|
|
299
|
+
return fromEnv && isAttemptId(fromEnv) ? fromEnv : parked;
|
|
300
|
+
}
|
|
290
301
|
function createConnectTelemetry(mcpUrl, client, opts) {
|
|
291
|
-
//
|
|
292
|
-
|
|
302
|
+
// Consumed even when --url already supplied an id: leaving it behind would let a stale
|
|
303
|
+
// seed reach the agent this connect launches, or the next connect on this machine.
|
|
304
|
+
const seeded = takeSeedAttemptId();
|
|
305
|
+
// A setup-page id (via --url, the env var, or one parked by login) joins "copied the
|
|
306
|
+
// command" to this install attempt.
|
|
307
|
+
const attemptId = opts?.attemptId ?? seeded ?? randomUUID();
|
|
293
308
|
const endpoint = new URL("/api/cli/connect-event", mcpUrl).toString();
|
|
294
309
|
const pending = [];
|
|
295
310
|
async function deliver(body) {
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import spawn from "cross-spawn";
|
|
2
2
|
import { resolvedApiKey } from "../lib/auth.js";
|
|
3
3
|
import { CLI_VERSION, TINYFISH_CLI_PACKAGE } from "../lib/constants.js";
|
|
4
|
+
import { suppressNotice } from "../lib/notice.js";
|
|
4
5
|
import { errLine } from "../lib/output.js";
|
|
5
6
|
import { sendUpgradeCompleted, telemetryDisabled, } from "../lib/setup-telemetry.js";
|
|
6
7
|
import { installSignalGuard, SIGNAL_EXIT_CODES } from "../lib/signals.js";
|
|
@@ -55,6 +56,8 @@ function installedCliVersion() {
|
|
|
55
56
|
}
|
|
56
57
|
/** `runUpgrade` plus its outcome telemetry, which must never change what the upgrade does. */
|
|
57
58
|
export async function runUpgradeCommand() {
|
|
59
|
+
// This command is the upgrade, so a trailing "an update is available" would be noise.
|
|
60
|
+
suppressNotice();
|
|
58
61
|
let reported = false;
|
|
59
62
|
// Read on the failed path too: a failed skill refresh still leaves an upgraded CLI, so
|
|
60
63
|
// `outcome` reports whether every step ran and `to_version` whether the binary moved.
|
package/dist/index.js
CHANGED
|
@@ -1,55 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import { registerAuth } from "./commands/auth.js";
|
|
5
|
-
import { registerBatch } from "./commands/batch.js";
|
|
6
|
-
import { registerFetch } from "./commands/fetch.js";
|
|
7
|
-
import { registerBrowser } from "./commands/browser.js";
|
|
8
|
-
import { registerProfile } from "./commands/profile.js";
|
|
9
|
-
import { registerVault } from "./commands/vault.js";
|
|
10
|
-
import { registerWallet } from "./commands/wallet.js";
|
|
11
|
-
import { registerRun } from "./commands/run.js";
|
|
12
|
-
import { registerRuns } from "./commands/runs.js";
|
|
13
|
-
import { registerConfigureClaude } from "./commands/config-claude.js";
|
|
14
|
-
import { registerSearch } from "./commands/search.js";
|
|
15
|
-
import { registerConnect } from "./commands/connect.js";
|
|
16
|
-
import { registerDoctor } from "./commands/doctor.js";
|
|
17
|
-
import { registerUpgrade } from "./commands/upgrade.js";
|
|
18
|
-
import { registerOnboard } from "./commands/onboard.js";
|
|
19
|
-
const { version } = createRequire(import.meta.url)("../package.json");
|
|
20
|
-
const program = new Command();
|
|
21
|
-
program
|
|
22
|
-
.name("tinyfish")
|
|
23
|
-
.description("TinyFish CLI — run web automations from your terminal or agent.")
|
|
24
|
-
.version(version, "-V, --version", "Show version")
|
|
25
|
-
.helpOption("-h, --help", "Show help")
|
|
26
|
-
.addHelpCommand(false)
|
|
27
|
-
.enablePositionalOptions()
|
|
28
|
-
.option("--debug", "Print HTTP requests and responses to stderr (or set TINYFISH_DEBUG=1)")
|
|
29
|
-
.hook("preAction", (cmd) => {
|
|
30
|
-
if (cmd.opts().debug) {
|
|
31
|
-
process.env["TINYFISH_DEBUG"] = "1";
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
registerAuth(program);
|
|
35
|
-
registerOnboard(program);
|
|
36
|
-
registerConnect(program);
|
|
37
|
-
registerDoctor(program);
|
|
38
|
-
registerUpgrade(program);
|
|
39
|
-
registerConfigureClaude(program);
|
|
40
|
-
registerBrowser(program);
|
|
41
|
-
registerProfile(program);
|
|
42
|
-
registerVault(program);
|
|
43
|
-
registerWallet(program);
|
|
44
|
-
const agentCmd = program
|
|
45
|
-
.command("agent")
|
|
46
|
-
.description("Agent automation commands")
|
|
47
|
-
.enablePositionalOptions();
|
|
48
|
-
const runCmd = registerRun(agentCmd);
|
|
49
|
-
registerRuns(runCmd);
|
|
50
|
-
registerBatch(agentCmd);
|
|
51
|
-
registerSearch(program);
|
|
52
|
-
registerFetch(program);
|
|
2
|
+
import { buildProgram } from "./program.js";
|
|
3
|
+
const { program } = buildProgram();
|
|
53
4
|
// Await parseAsync so async command handlers complete before the process exits
|
|
54
5
|
program.parseAsync(process.argv).catch((e) => {
|
|
55
6
|
process.stderr.write(JSON.stringify({ error: e instanceof Error ? e.message : String(e) }) + "\n");
|
package/dist/lib/auth.d.ts
CHANGED
|
@@ -2,14 +2,28 @@ export { DASHBOARD_URL, getDashboardUrl } from "./constants.js";
|
|
|
2
2
|
export declare function configDir(): string;
|
|
3
3
|
/** Install channel baked onto connect context; the connect flow is always CLI-driven. */
|
|
4
4
|
export declare const CONNECT_SOURCE = "tinyfish_cli";
|
|
5
|
+
export declare const CONNECT_ATTEMPT_ENV = "TINYFISH_CONNECT_ATTEMPT_ID";
|
|
5
6
|
interface ConnectEntry {
|
|
6
7
|
attempt_id: string;
|
|
7
8
|
}
|
|
9
|
+
interface PendingConnectAttempt {
|
|
10
|
+
id: string;
|
|
11
|
+
ts: number;
|
|
12
|
+
}
|
|
8
13
|
interface TinyfishConfig {
|
|
9
14
|
api_key?: string;
|
|
10
15
|
connect?: Record<string, ConnectEntry>;
|
|
16
|
+
pending_connect_attempt?: PendingConnectAttempt;
|
|
11
17
|
}
|
|
12
18
|
export declare function loadConfig(): TinyfishConfig;
|
|
19
|
+
export declare function isAttemptId(value: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Park a page-minted attempt id for the connect that follows in a later process:
|
|
22
|
+
* `auth login` and the connect it leads to are separate copied commands.
|
|
23
|
+
*/
|
|
24
|
+
export declare function savePendingConnectAttempt(attemptId: string): void;
|
|
25
|
+
/** Read-and-clear: one parked id seeds one install attempt, expired or not. */
|
|
26
|
+
export declare function takePendingConnectAttempt(): string | undefined;
|
|
13
27
|
/** Throwing variant for callers with their own cleanup (connect's telemetry flush). */
|
|
14
28
|
export declare function writeConfig(apiKey: string): void;
|
|
15
29
|
export declare function saveConfig(apiKey: string): void;
|
package/dist/lib/auth.js
CHANGED
|
@@ -15,6 +15,9 @@ function configFile() {
|
|
|
15
15
|
}
|
|
16
16
|
/** Install channel baked onto connect context; the connect flow is always CLI-driven. */
|
|
17
17
|
export const CONNECT_SOURCE = "tinyfish_cli";
|
|
18
|
+
// An env var, not a flag: released CLIs ignore an unknown one, and the installer's PATH
|
|
19
|
+
// prepend means a copied command can still run a CLI far older than the page that wrote it.
|
|
20
|
+
export const CONNECT_ATTEMPT_ENV = "TINYFISH_CONNECT_ATTEMPT_ID";
|
|
18
21
|
export function loadConfig() {
|
|
19
22
|
try {
|
|
20
23
|
const raw = JSON.parse(fs.readFileSync(configFile(), "utf8"));
|
|
@@ -27,6 +30,9 @@ export function loadConfig() {
|
|
|
27
30
|
const connect = parseConnectMap(obj["connect"]);
|
|
28
31
|
if (connect)
|
|
29
32
|
config.connect = connect;
|
|
33
|
+
const pending = parsePendingAttempt(obj["pending_connect_attempt"]);
|
|
34
|
+
if (pending)
|
|
35
|
+
config.pending_connect_attempt = pending;
|
|
30
36
|
return config;
|
|
31
37
|
}
|
|
32
38
|
catch {
|
|
@@ -44,16 +50,63 @@ function parseConnectMap(value) {
|
|
|
44
50
|
}
|
|
45
51
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
46
52
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
function parsePendingAttempt(value) {
|
|
54
|
+
const entry = value;
|
|
55
|
+
if (typeof entry?.id !== "string" || typeof entry.ts !== "number")
|
|
56
|
+
return undefined;
|
|
57
|
+
return isAttemptId(entry.id) ? { id: entry.id, ts: entry.ts } : undefined;
|
|
58
|
+
}
|
|
59
|
+
export function isAttemptId(value) {
|
|
60
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The one read-modify-write of the config file. Every caller goes through it so the
|
|
64
|
+
* loadConfig whitelist stays the single definition of what survives a write. Throws;
|
|
65
|
+
* callers own their failure policy, which differs per command.
|
|
66
|
+
*/
|
|
67
|
+
function updateConfig(change) {
|
|
50
68
|
// 0o700: only owner can read/write/execute the directory
|
|
51
|
-
fs.mkdirSync(
|
|
52
|
-
const existing = loadConfig();
|
|
69
|
+
fs.mkdirSync(configDir(), { recursive: true, mode: 0o700 });
|
|
53
70
|
// 0o600: only owner can read/write the config file (key is sensitive)
|
|
54
|
-
fs.writeFileSync(configFile(), JSON.stringify({
|
|
55
|
-
|
|
56
|
-
|
|
71
|
+
fs.writeFileSync(configFile(), JSON.stringify(change(loadConfig())), { mode: 0o600 });
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Park a page-minted attempt id for the connect that follows in a later process:
|
|
75
|
+
* `auth login` and the connect it leads to are separate copied commands.
|
|
76
|
+
*/
|
|
77
|
+
export function savePendingConnectAttempt(attemptId) {
|
|
78
|
+
if (!isAttemptId(attemptId))
|
|
79
|
+
return;
|
|
80
|
+
try {
|
|
81
|
+
updateConfig((config) => ({
|
|
82
|
+
...config,
|
|
83
|
+
pending_connect_attempt: { id: attemptId, ts: Date.now() },
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Attribution is never worth failing a sign-in for.
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Older than this and the id belongs to some abandoned setup, not the connect running now.
|
|
91
|
+
const PENDING_ATTEMPT_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
92
|
+
/** Read-and-clear: one parked id seeds one install attempt, expired or not. */
|
|
93
|
+
export function takePendingConnectAttempt() {
|
|
94
|
+
const pending = loadConfig().pending_connect_attempt;
|
|
95
|
+
if (!pending)
|
|
96
|
+
return undefined;
|
|
97
|
+
try {
|
|
98
|
+
updateConfig(({ pending_connect_attempt: _cleared, ...rest }) => rest);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// An id still on disk would be claimed twice, and a double-counted install is worse
|
|
102
|
+
// than a missed join.
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
return Date.now() - pending.ts <= PENDING_ATTEMPT_MAX_AGE_MS ? pending.id : undefined;
|
|
106
|
+
}
|
|
107
|
+
/** Throwing variant for callers with their own cleanup (connect's telemetry flush). */
|
|
108
|
+
export function writeConfig(apiKey) {
|
|
109
|
+
updateConfig((config) => ({ ...config, api_key: apiKey }));
|
|
57
110
|
}
|
|
58
111
|
export function saveConfig(apiKey) {
|
|
59
112
|
try {
|
|
@@ -65,12 +118,11 @@ export function saveConfig(apiKey) {
|
|
|
65
118
|
}
|
|
66
119
|
}
|
|
67
120
|
export function saveConnectContext(client, attemptId) {
|
|
68
|
-
const dir = configDir();
|
|
69
121
|
try {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
122
|
+
updateConfig((config) => ({
|
|
123
|
+
...config,
|
|
124
|
+
connect: { ...config.connect, [client]: { attempt_id: attemptId } },
|
|
125
|
+
}));
|
|
74
126
|
}
|
|
75
127
|
catch (e) {
|
|
76
128
|
// Best-effort attribution marker: a write failure must not fail a connect that has
|
|
@@ -119,12 +171,12 @@ export function persistApiKeyToEnvironment(apiKey, options = {}) {
|
|
|
119
171
|
fs.chmodSync(profile, 0o600);
|
|
120
172
|
}
|
|
121
173
|
export function clearConfig() {
|
|
122
|
-
|
|
123
|
-
if (!config.api_key)
|
|
174
|
+
if (!loadConfig().api_key)
|
|
124
175
|
return false;
|
|
125
|
-
delete config.api_key;
|
|
126
176
|
try {
|
|
127
|
-
|
|
177
|
+
// The parked id goes too: the next person to sign in on this machine is not the one
|
|
178
|
+
// whose copy minted it.
|
|
179
|
+
updateConfig(({ api_key: _key, pending_connect_attempt: _attempt, ...rest }) => rest);
|
|
128
180
|
return true;
|
|
129
181
|
}
|
|
130
182
|
catch {
|
package/dist/lib/client.js
CHANGED
|
@@ -2,7 +2,7 @@ import { APIStatusError, agentRunAsyncResponseSchema, agentRunResponseSchema, ag
|
|
|
2
2
|
import { CONNECT_SOURCE, loadConfig } from "./auth.js";
|
|
3
3
|
import { API_URL_OVERRIDE, CLI_VERSION } from "./constants.js";
|
|
4
4
|
import { detectHarness, detectHumanInitiated } from "./harness.js";
|
|
5
|
-
import {
|
|
5
|
+
import { captureNotice } from "./notice.js";
|
|
6
6
|
import { ApiError } from "./output.js";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
// Install-level connect context (PF-3169). A known caller is credited only to its own
|
|
@@ -73,7 +73,7 @@ class TinyFishCliClient extends TinyFish {
|
|
|
73
73
|
method: "DELETE",
|
|
74
74
|
headers: this._buildHeaders(),
|
|
75
75
|
});
|
|
76
|
-
|
|
76
|
+
captureNotice(response.headers);
|
|
77
77
|
if (!response.ok) {
|
|
78
78
|
let message = response.statusText;
|
|
79
79
|
try {
|
|
@@ -96,7 +96,7 @@ function createSdkClient(apiKey, timeout) {
|
|
|
96
96
|
...(API_URL_OVERRIDE !== undefined ? { baseURL: API_URL_OVERRIDE } : {}),
|
|
97
97
|
maxRetries: 0,
|
|
98
98
|
// Fires per HTTP attempt, but maxRetries is 0 and the notice latches once per invocation.
|
|
99
|
-
onResponse: ({ headers }) =>
|
|
99
|
+
onResponse: ({ headers }) => captureNotice(headers),
|
|
100
100
|
...(timeout !== undefined ? { timeout } : {}),
|
|
101
101
|
});
|
|
102
102
|
}
|
package/dist/lib/notice.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
1
2
|
/** Test-only: the capture slot and the once-per-invocation latch are module state. */
|
|
2
3
|
export declare function resetNoticeState(): void;
|
|
3
4
|
/** Records the first notice seen; printing is deferred so it lands at the end of a command. */
|
|
4
5
|
export declare function captureNotice(headers: Headers): void;
|
|
5
6
|
/** Advisory only, never changes stdout, the exit code, or control flow. */
|
|
6
7
|
export declare function emitNotice(): void;
|
|
7
|
-
/** For
|
|
8
|
-
export declare function
|
|
8
|
+
/** For commands that are the fix themselves, such as `upgrade`, where the nudge is noise. */
|
|
9
|
+
export declare function suppressNotice(): void;
|
|
10
|
+
/** postAction covers clean completions, the exit hook covers the process.exit and rejection paths, the latch keeps it to one. */
|
|
11
|
+
export declare function installNoticeEmit(program: Command): () => void;
|
package/dist/lib/notice.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
// Surfaces the advisory X-TF-Notice header the server attaches to outdated clients (PF-3301).
|
|
2
1
|
import { CLI_VERSION } from "./constants.js";
|
|
3
2
|
import { detectHumanInitiated } from "./harness.js";
|
|
4
|
-
import { err,
|
|
3
|
+
import { err, sanitizeLine, warnLine } from "./output.js";
|
|
5
4
|
const NOTICE_HEADER = "x-tf-notice";
|
|
6
5
|
const LEVEL_HEADER = "x-tf-notice-level";
|
|
7
6
|
const MAX_LENGTH = 300;
|
|
@@ -41,13 +40,20 @@ export function emitNotice() {
|
|
|
41
40
|
printed = true;
|
|
42
41
|
const { message, level } = captured;
|
|
43
42
|
if (detectHumanInitiated()) {
|
|
44
|
-
|
|
43
|
+
warnLine(message);
|
|
45
44
|
return;
|
|
46
45
|
}
|
|
47
46
|
err({ notice: agentMessage(level), ...(level ? { level } : {}) });
|
|
48
47
|
}
|
|
49
|
-
/** For
|
|
50
|
-
export function
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
/** For commands that are the fix themselves, such as `upgrade`, where the nudge is noise. */
|
|
49
|
+
export function suppressNotice() {
|
|
50
|
+
captured = undefined;
|
|
51
|
+
printed = true;
|
|
52
|
+
}
|
|
53
|
+
/** postAction covers clean completions, the exit hook covers the process.exit and rejection paths, the latch keeps it to one. */
|
|
54
|
+
export function installNoticeEmit(program) {
|
|
55
|
+
const backstop = () => emitNotice();
|
|
56
|
+
program.hook("postAction", () => emitNotice());
|
|
57
|
+
process.once("exit", backstop);
|
|
58
|
+
return () => process.removeListener("exit", backstop);
|
|
53
59
|
}
|
package/dist/lib/output.d.ts
CHANGED
|
@@ -8,5 +8,7 @@ export declare function out(data: unknown): void;
|
|
|
8
8
|
export declare function sanitizeLine(text: string): string;
|
|
9
9
|
export declare function outLine(line: string): void;
|
|
10
10
|
export declare function errLine(line: string): void;
|
|
11
|
+
/** Advisory stderr line, yellow on a terminal and plain everywhere else so piped output stays clean. */
|
|
12
|
+
export declare function warnLine(line: string): void;
|
|
11
13
|
export declare function err(data: unknown): void;
|
|
12
14
|
export declare function handleApiError(e: unknown): never;
|
package/dist/lib/output.js
CHANGED
|
@@ -23,6 +23,14 @@ export function outLine(line) {
|
|
|
23
23
|
export function errLine(line) {
|
|
24
24
|
process.stderr.write(sanitizeLine(line) + "\n");
|
|
25
25
|
}
|
|
26
|
+
const WARN_ON = "\x1b[33m";
|
|
27
|
+
const WARN_OFF = "\x1b[39m";
|
|
28
|
+
/** Advisory stderr line, yellow on a terminal and plain everywhere else so piped output stays clean. */
|
|
29
|
+
export function warnLine(line) {
|
|
30
|
+
const text = sanitizeLine(line);
|
|
31
|
+
const colour = Boolean(process.stderr.isTTY) && !process.env["NO_COLOR"];
|
|
32
|
+
process.stderr.write(colour ? `${WARN_ON}${text}${WARN_OFF}\n` : `${text}\n`);
|
|
33
|
+
}
|
|
26
34
|
export function err(data) {
|
|
27
35
|
const payload = typeof data === "string" ? { error: data } : data;
|
|
28
36
|
const serialized = JSON.stringify(payload) ?? "undefined";
|
package/dist/program.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createRequire } from "module";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { registerAuth } from "./commands/auth.js";
|
|
4
|
+
import { registerBatch } from "./commands/batch.js";
|
|
5
|
+
import { registerFetch } from "./commands/fetch.js";
|
|
6
|
+
import { registerBrowser } from "./commands/browser.js";
|
|
7
|
+
import { registerProfile } from "./commands/profile.js";
|
|
8
|
+
import { registerVault } from "./commands/vault.js";
|
|
9
|
+
import { registerWallet } from "./commands/wallet.js";
|
|
10
|
+
import { registerRun } from "./commands/run.js";
|
|
11
|
+
import { registerRuns } from "./commands/runs.js";
|
|
12
|
+
import { registerConfigureClaude } from "./commands/config-claude.js";
|
|
13
|
+
import { registerSearch } from "./commands/search.js";
|
|
14
|
+
import { registerConnect } from "./commands/connect.js";
|
|
15
|
+
import { registerDoctor } from "./commands/doctor.js";
|
|
16
|
+
import { registerUpgrade } from "./commands/upgrade.js";
|
|
17
|
+
import { registerOnboard } from "./commands/onboard.js";
|
|
18
|
+
import { installNoticeEmit } from "./lib/notice.js";
|
|
19
|
+
/** Split from index.ts so tests can drive the real program instead of hand-assembling one. */
|
|
20
|
+
export function buildProgram() {
|
|
21
|
+
const { version } = createRequire(import.meta.url)("../package.json");
|
|
22
|
+
const program = new Command();
|
|
23
|
+
program
|
|
24
|
+
.name("tinyfish")
|
|
25
|
+
.description("TinyFish CLI — run web automations from your terminal or agent.")
|
|
26
|
+
.version(version, "-V, --version", "Show version")
|
|
27
|
+
.helpOption("-h, --help", "Show help")
|
|
28
|
+
.addHelpCommand(false)
|
|
29
|
+
.enablePositionalOptions()
|
|
30
|
+
.option("--debug", "Print HTTP requests and responses to stderr (or set TINYFISH_DEBUG=1)")
|
|
31
|
+
.hook("preAction", (cmd) => {
|
|
32
|
+
if (cmd.opts().debug) {
|
|
33
|
+
process.env["TINYFISH_DEBUG"] = "1";
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
registerAuth(program);
|
|
37
|
+
registerOnboard(program);
|
|
38
|
+
registerConnect(program);
|
|
39
|
+
registerDoctor(program);
|
|
40
|
+
registerUpgrade(program);
|
|
41
|
+
registerConfigureClaude(program);
|
|
42
|
+
registerBrowser(program);
|
|
43
|
+
registerProfile(program);
|
|
44
|
+
registerVault(program);
|
|
45
|
+
registerWallet(program);
|
|
46
|
+
const agentCmd = program
|
|
47
|
+
.command("agent")
|
|
48
|
+
.description("Agent automation commands")
|
|
49
|
+
.enablePositionalOptions();
|
|
50
|
+
const runCmd = registerRun(agentCmd);
|
|
51
|
+
registerRuns(runCmd);
|
|
52
|
+
registerBatch(agentCmd);
|
|
53
|
+
registerSearch(program);
|
|
54
|
+
registerFetch(program);
|
|
55
|
+
const uninstallNotice = installNoticeEmit(program);
|
|
56
|
+
return { program, uninstallNotice };
|
|
57
|
+
}
|