@trawlme/cli 3.12.0 → 3.12.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/README.md +1 -1
- package/dist/commands/create.d.ts +0 -28
- package/dist/commands/create.js +0 -89
- package/dist/commands/doctor.d.ts +0 -79
- package/dist/commands/doctor.js +1 -187
- package/dist/commands/login.js +0 -67
- package/dist/commands/ping.d.ts +0 -15
- package/dist/commands/ping.js +0 -15
- package/dist/commands/scraps.d.ts +0 -120
- package/dist/commands/scraps.js +10 -724
- package/dist/commands/skills.js +0 -22
- package/dist/commands/spec.d.ts +0 -85
- package/dist/commands/spec.js +0 -67
- package/dist/commands/telemetry.js +0 -4
- package/dist/commands/token.js +0 -28
- package/dist/commands/upgrade.js +0 -22
- package/dist/commands/whoami.d.ts +0 -12
- package/dist/commands/whoami.js +0 -6
- package/dist/index.d.ts +0 -188
- package/dist/index.js +0 -349
- package/dist/lib/api.d.ts +0 -78
- package/dist/lib/api.js +1 -320
- package/dist/lib/cdp-pipe.d.ts +0 -72
- package/dist/lib/cdp-pipe.js +1 -81
- package/dist/lib/chrome-discovery.d.ts +0 -11
- package/dist/lib/chrome-discovery.js +0 -19
- package/dist/lib/chrome-launch.d.ts +0 -40
- package/dist/lib/chrome-launch.js +0 -69
- package/dist/lib/config.d.ts +0 -53
- package/dist/lib/config.js +0 -55
- package/dist/lib/confirm.d.ts +0 -55
- package/dist/lib/confirm.js +0 -47
- package/dist/lib/docs.d.ts +0 -123
- package/dist/lib/docs.js +0 -169
- package/dist/lib/errors.d.ts +0 -134
- package/dist/lib/errors.js +0 -151
- package/dist/lib/format.d.ts +0 -6
- package/dist/lib/format.js +0 -6
- package/dist/lib/json.d.ts +0 -35
- package/dist/lib/json.js +0 -48
- package/dist/lib/jwt.d.ts +0 -7
- package/dist/lib/jwt.js +0 -7
- package/dist/lib/pinch.d.ts +0 -53
- package/dist/lib/pinch.js +6 -112
- package/dist/lib/pinchAnimation.d.ts +0 -16
- package/dist/lib/pinchAnimation.js +8 -29
- package/dist/lib/posthog.d.ts +0 -9
- package/dist/lib/posthog.js +0 -23
- package/dist/lib/prompt.js +1 -20
- package/dist/lib/secure-transport.d.ts +0 -7
- package/dist/lib/secure-transport.js +0 -24
- package/dist/lib/session-capture-guard.d.ts +0 -15
- package/dist/lib/session-capture-guard.js +0 -5
- package/dist/lib/session-capture.d.ts +0 -125
- package/dist/lib/session-capture.js +0 -281
- package/dist/lib/skills.d.ts +0 -175
- package/dist/lib/skills.js +1 -216
- package/dist/lib/skillsNudge.d.ts +0 -17
- package/dist/lib/skillsNudge.js +0 -83
- package/dist/lib/spinner.d.ts +0 -39
- package/dist/lib/spinner.js +0 -40
- package/dist/lib/storage-state.d.ts +0 -112
- package/dist/lib/storage-state.js +0 -131
- package/dist/lib/tips.d.ts +0 -38
- package/dist/lib/tips.js +0 -77
- package/dist/lib/updateCheckWorker.js +0 -14
- package/dist/lib/updateNotifier.d.ts +0 -17
- package/dist/lib/updateNotifier.js +0 -53
- package/dist/lib/validate.d.ts +0 -8
- package/dist/lib/validate.js +0 -8
- package/dist/lib/version.d.ts +0 -12
- package/dist/lib/version.js +1 -13
- package/package.json +2 -2
package/dist/lib/cdp-pipe.js
CHANGED
|
@@ -1,39 +1,11 @@
|
|
|
1
|
-
/** Reserved internal event name — never collides with a real CDP method
|
|
2
|
-
* (every real one is `Domain.method`, always containing a dot). Emitted
|
|
3
|
-
* once the read side of the pipe ends/errors, so any in-flight capture
|
|
4
|
-
* can tell "Chrome went away" apart from "the browser answered". */
|
|
5
1
|
const PIPE_CLOSED = '__pipe_closed__';
|
|
6
|
-
/** A per-command deadline that never fires under normal operation. */
|
|
7
2
|
const DEFAULT_COMMAND_TIMEOUT_MS = 30_000;
|
|
8
|
-
/**
|
|
9
|
-
* A frame Chrome sent could not be parsed as JSON (trawl_cli#183 review
|
|
10
|
-
* finding 3). Distinguished from the generic "Chrome closed the CDP pipe"
|
|
11
|
-
* error so a caller can tell "the peer sent garbage and was cut off" apart
|
|
12
|
-
* from "the process actually exited" — `captureSession` reports the two
|
|
13
|
-
* with different, factual reasons rather than defaulting a corrupt-frame
|
|
14
|
-
* case to "Chrome exited".
|
|
15
|
-
*/
|
|
16
3
|
export class CdpProtocolError extends Error {
|
|
17
4
|
constructor(message) {
|
|
18
5
|
super(message);
|
|
19
6
|
this.name = 'CdpProtocolError';
|
|
20
7
|
}
|
|
21
8
|
}
|
|
22
|
-
/**
|
|
23
|
-
* A `send()` command's own deadline elapsed with no response (trawl_cli#183
|
|
24
|
-
* review finding 3's fix; the gap it reopened is finding 4's bug class —
|
|
25
|
-
* see session-capture.ts's `readCaptureDefault`). Deliberately a SIBLING of
|
|
26
|
-
* `CdpProtocolError`, never a subclass: a protocol error means the pipe
|
|
27
|
-
* itself can no longer be trusted, so it is torn down (`isClosed` flips
|
|
28
|
-
* true, every OTHER pending command rejects too). A timeout means exactly
|
|
29
|
-
* ONE command never got an answer — Chrome is still running, the pipe is
|
|
30
|
-
* still open, every other in-flight/future command is unaffected. Making
|
|
31
|
-
* this a `CdpProtocolError` subclass would let a caller's
|
|
32
|
-
* `instanceof CdpProtocolError` (or a future one) treat a single slow
|
|
33
|
-
* command as proof the whole browser is gone, which is false. Carries only
|
|
34
|
-
* `method` and `timeoutMs` — never `params`, which can carry page-
|
|
35
|
-
* controlled data.
|
|
36
|
-
*/
|
|
37
9
|
export class CdpTimeoutError extends Error {
|
|
38
10
|
method;
|
|
39
11
|
timeoutMs;
|
|
@@ -61,13 +33,6 @@ export class CdpPipe {
|
|
|
61
33
|
readStream.on('end', () => this.onClosed());
|
|
62
34
|
readStream.on('close', () => this.onClosed());
|
|
63
35
|
readStream.on('error', () => this.onClosed());
|
|
64
|
-
// fd 3 (write) and fd 4 (read) are separate sockets — Chrome dying can
|
|
65
|
-
// surface as an EPIPE on a write that lands before the read side's
|
|
66
|
-
// 'end'/'close' ever fires. An unhandled 'error' on a Writable is a
|
|
67
|
-
// thrown exception with no listener, which would kill the whole CLI
|
|
68
|
-
// process and skip captureSession's `finally` (so the temp profile is
|
|
69
|
-
// never cleaned up) — routing it through the same onClosed() path
|
|
70
|
-
// keeps "Chrome went away" a single, always-handled state.
|
|
71
36
|
writeStream.on('error', () => this.onClosed());
|
|
72
37
|
}
|
|
73
38
|
onClosed(err) {
|
|
@@ -83,18 +48,6 @@ export class CdpPipe {
|
|
|
83
48
|
for (const fn of set)
|
|
84
49
|
fn(undefined);
|
|
85
50
|
}
|
|
86
|
-
/**
|
|
87
|
-
* @desc A frame that fails to parse as JSON used to be dropped silently
|
|
88
|
-
* (`msg = undefined`, dispatch skipped) — any `send()` whose response was
|
|
89
|
-
* that exact frame then hung forever, since nothing ever rejected it.
|
|
90
|
-
* Now the whole pipe is torn down the same way a real Chrome exit is:
|
|
91
|
-
* every pending command rejects (with a `CdpProtocolError`, not the
|
|
92
|
-
* generic close message, so the cause is distinguishable), `isClosed`
|
|
93
|
-
* flips true, and `onClose` subscribers fire — the peer sent a frame
|
|
94
|
-
* that doesn't fit the protocol, so nothing it says next can be trusted
|
|
95
|
-
* either. The raw frame content is never included in the error message —
|
|
96
|
-
* it can carry page-controlled data (e.g. a `Runtime.evaluate` result).
|
|
97
|
-
*/
|
|
98
51
|
onProtocolError() {
|
|
99
52
|
if (this.closed)
|
|
100
53
|
return;
|
|
@@ -103,11 +56,6 @@ export class CdpPipe {
|
|
|
103
56
|
this.onClosed(err);
|
|
104
57
|
}
|
|
105
58
|
onData(chunk) {
|
|
106
|
-
// Once torn down (a real close, or a corrupt frame — onProtocolError)
|
|
107
|
-
// the read stream can still be alive and keep delivering chunks from
|
|
108
|
-
// an untrusted peer; without this, a later VALID-looking event frame
|
|
109
|
-
// would still `dispatch()` to any listener still registered instead
|
|
110
|
-
// of being ignored, and `this.buffer` would keep growing forever.
|
|
111
59
|
if (this.closed)
|
|
112
60
|
return;
|
|
113
61
|
this.buffer += chunk;
|
|
@@ -122,7 +70,7 @@ export class CdpPipe {
|
|
|
122
70
|
}
|
|
123
71
|
catch {
|
|
124
72
|
this.onProtocolError();
|
|
125
|
-
return;
|
|
73
|
+
return;
|
|
126
74
|
}
|
|
127
75
|
this.dispatch(msg);
|
|
128
76
|
}
|
|
@@ -146,24 +94,6 @@ export class CdpPipe {
|
|
|
146
94
|
fn(msg.params, msg.sessionId);
|
|
147
95
|
}
|
|
148
96
|
}
|
|
149
|
-
/**
|
|
150
|
-
* Send a CDP command and resolve with its `result`. Rejects if the pipe
|
|
151
|
-
* closes (Chrome exited, or a corrupt frame tore it down — see
|
|
152
|
-
* `onProtocolError`) before a response arrives, or with a
|
|
153
|
-
* `CdpTimeoutError` if no response arrives within `timeoutMs` (defaults
|
|
154
|
-
* to the pipe's own `commandTimeoutMs`, 30s) — the timeout names the
|
|
155
|
-
* METHOD, never `params` (which can carry page-controlled data).
|
|
156
|
-
* @param timeoutMs — per-call override. Lets a caller pin its own named
|
|
157
|
-
* deadline as an assertable constant rather than relying on the pipe's
|
|
158
|
-
* default silently matching (see session-capture.ts's
|
|
159
|
-
* `LOCALSTORAGE_READ_TIMEOUT_MS`, which currently equals this pipe's own
|
|
160
|
-
* 30s default but is passed explicitly anyway — a post-cap review found
|
|
161
|
-
* that giving `Runtime.evaluate` a SHORTER override here, on the
|
|
162
|
-
* assumption a page-blocked renderer "won't unblock itself in 30s any
|
|
163
|
-
* more than in 5", cut off real-but-slow work that finished on its own;
|
|
164
|
-
* there is no override value proven safe, so this parameter exists for
|
|
165
|
-
* callers that want one, not as a recommendation to use a shorter one).
|
|
166
|
-
*/
|
|
167
97
|
send(method, params = {}, sessionId, timeoutMs = this.commandTimeoutMs) {
|
|
168
98
|
if (this.closed)
|
|
169
99
|
return Promise.reject(this.protocolErr ?? new Error('Chrome closed the CDP pipe'));
|
|
@@ -172,10 +102,6 @@ export class CdpPipe {
|
|
|
172
102
|
if (sessionId)
|
|
173
103
|
req.sessionId = sessionId;
|
|
174
104
|
return new Promise((resolve, reject) => {
|
|
175
|
-
// Every settle path (dispatch's resolve/reject, this timeout, a
|
|
176
|
-
// write-side EPIPE below, and onClosed's reject-all on a pipe
|
|
177
|
-
// teardown) goes through these two wrappers, so the timer is always
|
|
178
|
-
// cleared exactly once no matter which path wins the race.
|
|
179
105
|
const settleResolve = (v) => {
|
|
180
106
|
clearTimeout(timer);
|
|
181
107
|
resolve(v);
|
|
@@ -197,24 +123,18 @@ export class CdpPipe {
|
|
|
197
123
|
});
|
|
198
124
|
});
|
|
199
125
|
}
|
|
200
|
-
/** Subscribe to a CDP event (`'Target.targetDestroyed'`, …). Returns an
|
|
201
|
-
* unsubscribe function. */
|
|
202
126
|
on(method, handler) {
|
|
203
127
|
if (!this.listeners.has(method))
|
|
204
128
|
this.listeners.set(method, new Set());
|
|
205
129
|
this.listeners.get(method).add(handler);
|
|
206
130
|
return () => this.listeners.get(method)?.delete(handler);
|
|
207
131
|
}
|
|
208
|
-
/** Subscribe to the pipe closing (Chrome process gone). */
|
|
209
132
|
onClose(handler) {
|
|
210
133
|
return this.on(PIPE_CLOSED, () => handler());
|
|
211
134
|
}
|
|
212
135
|
get isClosed() {
|
|
213
136
|
return this.closed;
|
|
214
137
|
}
|
|
215
|
-
/** Set only when the pipe was torn down because of a corrupt frame — as
|
|
216
|
-
* opposed to a real Chrome exit — so a caller can report the actual
|
|
217
|
-
* cause instead of defaulting every close to "Chrome exited". */
|
|
218
138
|
get protocolError() {
|
|
219
139
|
return this.protocolErr;
|
|
220
140
|
}
|
|
@@ -1,12 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @desc Resolve the Chrome executable to launch. Precedence: an explicit
|
|
3
|
-
* `--chrome <path>` flag (validated by the caller) > `TRAWL_CHROME_PATH` >
|
|
4
|
-
* `PUPPETEER_EXECUTABLE_PATH`/`CHROME_PATH` (common conventions other
|
|
5
|
-
* tools already set) > the well-known per-OS install paths. Returns null
|
|
6
|
-
* when nothing is found — the caller is responsible for a factual,
|
|
7
|
-
* non-guessing error message.
|
|
8
|
-
* @param {NodeJS.ProcessEnv} env
|
|
9
|
-
* @param {NodeJS.Platform} platform
|
|
10
|
-
* @param {(path: string) => boolean} exists — injectable for tests.
|
|
11
|
-
*/
|
|
12
1
|
export declare function findChrome(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform, exists?: (path: string) => boolean): string | null;
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Locate a system Chrome/Chromium executable (trawl_cli#183). Same
|
|
3
|
-
* discovery approach as `@trawlme/skills`'s
|
|
4
|
-
* `trawl-scrap-local-test/scripts/run-local.mjs` `findChrome()` — a short
|
|
5
|
-
* list of well-known install paths per OS, first match wins — extended
|
|
6
|
-
* with an env override and a Windows list (the skill script only needed
|
|
7
|
-
* mac/Linux for a dev-machine local test; this ships to every user's OS).
|
|
8
|
-
*/
|
|
9
1
|
import { existsSync } from 'node:fs';
|
|
10
2
|
const CANDIDATES = {
|
|
11
3
|
darwin: [
|
|
@@ -27,17 +19,6 @@ const CANDIDATES = {
|
|
|
27
19
|
'C:\\Program Files\\Chromium\\Application\\chrome.exe',
|
|
28
20
|
],
|
|
29
21
|
};
|
|
30
|
-
/**
|
|
31
|
-
* @desc Resolve the Chrome executable to launch. Precedence: an explicit
|
|
32
|
-
* `--chrome <path>` flag (validated by the caller) > `TRAWL_CHROME_PATH` >
|
|
33
|
-
* `PUPPETEER_EXECUTABLE_PATH`/`CHROME_PATH` (common conventions other
|
|
34
|
-
* tools already set) > the well-known per-OS install paths. Returns null
|
|
35
|
-
* when nothing is found — the caller is responsible for a factual,
|
|
36
|
-
* non-guessing error message.
|
|
37
|
-
* @param {NodeJS.ProcessEnv} env
|
|
38
|
-
* @param {NodeJS.Platform} platform
|
|
39
|
-
* @param {(path: string) => boolean} exists — injectable for tests.
|
|
40
|
-
*/
|
|
41
22
|
export function findChrome(env = process.env, platform = process.platform, exists = existsSync) {
|
|
42
23
|
const envCandidates = [env.TRAWL_CHROME_PATH, env.PUPPETEER_EXECUTABLE_PATH, env.CHROME_PATH].filter((p) => typeof p === 'string' && p.length > 0);
|
|
43
24
|
for (const p of envCandidates) {
|
|
@@ -1,48 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Spawn a headed, isolated Chrome instance wired for raw CDP over
|
|
3
|
-
* `--remote-debugging-pipe` (trawl_cli#183). Thin glue only — the actual
|
|
4
|
-
* protocol lives in cdp-pipe.ts, kept separate so this file's job (build
|
|
5
|
-
* the right flags, wire fd 3/4, fail loudly if the platform doesn't expose
|
|
6
|
-
* them) stays small enough to unit test with a mocked `spawn`.
|
|
7
|
-
*/
|
|
8
1
|
import { spawn, type ChildProcess } from 'node:child_process';
|
|
9
2
|
import { CdpPipe } from './cdp-pipe.js';
|
|
10
3
|
export interface LaunchedChrome {
|
|
11
4
|
proc: ChildProcess;
|
|
12
5
|
cdp: CdpPipe;
|
|
13
6
|
}
|
|
14
|
-
/**
|
|
15
|
-
* @desc The exact CLI flags Chrome is launched with. Pure + exported so
|
|
16
|
-
* the flag set is testable without spawning a real process.
|
|
17
|
-
* - `--remote-debugging-pipe` — CDP over fd 3 (in) / fd 4 (out), the
|
|
18
|
-
* zero-dependency transport this command relies on.
|
|
19
|
-
* - `--user-data-dir` — an ephemeral, isolated profile (never the user's
|
|
20
|
-
* real Chrome profile/cookies).
|
|
21
|
-
* - `--no-first-run` / `--no-default-browser-check` — skip first-run UI
|
|
22
|
-
* that would otherwise sit in front of the target URL.
|
|
23
|
-
* - `--disable-sync` — never touches the user's real Google account sync.
|
|
24
|
-
* - `--password-store=basic` / `--use-mock-keychain` — a fresh profile
|
|
25
|
-
* would otherwise prompt for the macOS login keychain (Safe Storage) the
|
|
26
|
-
* first time Chrome touches its cookie/password store; these keep the
|
|
27
|
-
* whole flow non-blocking on an ephemeral profile that's deleted right
|
|
28
|
-
* after (mirrors Crawl4AI's `crwl profiles`, cited in the issue).
|
|
29
|
-
* - `--new-window` — always its own window, never a tab folded into an
|
|
30
|
-
* already-running Chrome instance.
|
|
31
|
-
* - No `--no-sandbox`: this runs headed on the user's own machine, not in
|
|
32
|
-
* a locked-down CI container — the sandbox stays on.
|
|
33
|
-
*/
|
|
34
7
|
export declare function buildChromeArgs(userDataDir: string, targetUrl: string): string[];
|
|
35
|
-
/**
|
|
36
|
-
* @desc Launch Chrome and wire a CdpPipe to its fd 3 (write) / fd 4 (read).
|
|
37
|
-
* Returns a Promise rather than the `ChildProcess` synchronously: `spawn()`
|
|
38
|
-
* can fail AFTER returning — EACCES on a non-executable path, an ENOENT
|
|
39
|
-
* race, EPERM, E2BIG — by emitting an `'error'` event rather than throwing,
|
|
40
|
-
* and that event is attached to BEFORE the fd 3/4 check below so a failure
|
|
41
|
-
* from either cause rejects this promise instead of crashing the process
|
|
42
|
-
* (an EventEmitter with zero `'error'` listeners throws the error itself).
|
|
43
|
-
* @rejects {Error} if `spawn()` itself failed (the original error, so
|
|
44
|
-
* `.code` like `EACCES` survives), or if the platform/Chrome build didn't
|
|
45
|
-
* expose fd 3/4 as pipes (e.g. `--remote-debugging-pipe` unsupported) — a
|
|
46
|
-
* factual message, never a silent hang.
|
|
47
|
-
*/
|
|
48
8
|
export declare function launchChrome(executablePath: string, targetUrl: string, userDataDir: string, spawnFn?: typeof spawn): Promise<LaunchedChrome>;
|
|
@@ -1,32 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Spawn a headed, isolated Chrome instance wired for raw CDP over
|
|
3
|
-
* `--remote-debugging-pipe` (trawl_cli#183). Thin glue only — the actual
|
|
4
|
-
* protocol lives in cdp-pipe.ts, kept separate so this file's job (build
|
|
5
|
-
* the right flags, wire fd 3/4, fail loudly if the platform doesn't expose
|
|
6
|
-
* them) stays small enough to unit test with a mocked `spawn`.
|
|
7
|
-
*/
|
|
8
1
|
import { spawn } from 'node:child_process';
|
|
9
2
|
import { CdpPipe } from './cdp-pipe.js';
|
|
10
|
-
/**
|
|
11
|
-
* @desc The exact CLI flags Chrome is launched with. Pure + exported so
|
|
12
|
-
* the flag set is testable without spawning a real process.
|
|
13
|
-
* - `--remote-debugging-pipe` — CDP over fd 3 (in) / fd 4 (out), the
|
|
14
|
-
* zero-dependency transport this command relies on.
|
|
15
|
-
* - `--user-data-dir` — an ephemeral, isolated profile (never the user's
|
|
16
|
-
* real Chrome profile/cookies).
|
|
17
|
-
* - `--no-first-run` / `--no-default-browser-check` — skip first-run UI
|
|
18
|
-
* that would otherwise sit in front of the target URL.
|
|
19
|
-
* - `--disable-sync` — never touches the user's real Google account sync.
|
|
20
|
-
* - `--password-store=basic` / `--use-mock-keychain` — a fresh profile
|
|
21
|
-
* would otherwise prompt for the macOS login keychain (Safe Storage) the
|
|
22
|
-
* first time Chrome touches its cookie/password store; these keep the
|
|
23
|
-
* whole flow non-blocking on an ephemeral profile that's deleted right
|
|
24
|
-
* after (mirrors Crawl4AI's `crwl profiles`, cited in the issue).
|
|
25
|
-
* - `--new-window` — always its own window, never a tab folded into an
|
|
26
|
-
* already-running Chrome instance.
|
|
27
|
-
* - No `--no-sandbox`: this runs headed on the user's own machine, not in
|
|
28
|
-
* a locked-down CI container — the sandbox stays on.
|
|
29
|
-
*/
|
|
30
3
|
export function buildChromeArgs(userDataDir, targetUrl) {
|
|
31
4
|
return [
|
|
32
5
|
'--remote-debugging-pipe',
|
|
@@ -40,48 +13,13 @@ export function buildChromeArgs(userDataDir, targetUrl) {
|
|
|
40
13
|
targetUrl,
|
|
41
14
|
];
|
|
42
15
|
}
|
|
43
|
-
/**
|
|
44
|
-
* @desc Launch Chrome and wire a CdpPipe to its fd 3 (write) / fd 4 (read).
|
|
45
|
-
* Returns a Promise rather than the `ChildProcess` synchronously: `spawn()`
|
|
46
|
-
* can fail AFTER returning — EACCES on a non-executable path, an ENOENT
|
|
47
|
-
* race, EPERM, E2BIG — by emitting an `'error'` event rather than throwing,
|
|
48
|
-
* and that event is attached to BEFORE the fd 3/4 check below so a failure
|
|
49
|
-
* from either cause rejects this promise instead of crashing the process
|
|
50
|
-
* (an EventEmitter with zero `'error'` listeners throws the error itself).
|
|
51
|
-
* @rejects {Error} if `spawn()` itself failed (the original error, so
|
|
52
|
-
* `.code` like `EACCES` survives), or if the platform/Chrome build didn't
|
|
53
|
-
* expose fd 3/4 as pipes (e.g. `--remote-debugging-pipe` unsupported) — a
|
|
54
|
-
* factual message, never a silent hang.
|
|
55
|
-
*/
|
|
56
16
|
export function launchChrome(executablePath, targetUrl, userDataDir, spawnFn = spawn) {
|
|
57
17
|
return new Promise((resolve, reject) => {
|
|
58
|
-
// stderr is 'ignore', not 'pipe': nothing here ever reads it (unlike the
|
|
59
|
-
// rejected `ws`-over-port design, `--remote-debugging-pipe` needs none of
|
|
60
|
-
// Chrome's stderr output), and a 'pipe' nobody drains fills its OS pipe
|
|
61
|
-
// buffer — Chrome's own logging would then block on write mid-session,
|
|
62
|
-
// freezing the browser the human is mid-login in.
|
|
63
18
|
const proc = spawnFn(executablePath, buildChromeArgs(userDataDir, targetUrl), {
|
|
64
19
|
stdio: ['ignore', 'ignore', 'ignore', 'pipe', 'pipe'],
|
|
65
|
-
// POSIX only: makes Chrome the leader of its OWN process group, so its
|
|
66
|
-
// helper subprocesses (renderer/GPU/network service — Chrome forks
|
|
67
|
-
// several even for one window) live in that group too, distinct from
|
|
68
|
-
// ours. Verified against a real launch (trawl_cli#183's loopback E2E):
|
|
69
|
-
// without this, `proc.kill('SIGKILL')` only killed the main Chrome
|
|
70
|
-
// process — its helpers briefly kept the user-data-dir's files open,
|
|
71
|
-
// and `rmSync` silently lost that race, leaking the temp profile on
|
|
72
|
-
// every run. session-capture.ts's cleanup kills `-proc.pid` (the whole
|
|
73
|
-
// group) instead of `proc.pid` alone, which requires this.
|
|
74
20
|
detached: process.platform !== 'win32',
|
|
75
21
|
});
|
|
76
22
|
let settled = false;
|
|
77
|
-
// Attached as the very first thing after `spawnFn` returns — before the
|
|
78
|
-
// fd 3/4 check below, and before anything here ever awaits — so nothing
|
|
79
|
-
// can slip through unobserved. Stays attached FOREVER, including after
|
|
80
|
-
// this promise settles: a LATE error (spawn succeeded, then something
|
|
81
|
-
// else goes wrong long after this promise already resolved) must not
|
|
82
|
-
// crash the process either, and once `settled` is true this listener is
|
|
83
|
-
// a permanent no-op — whatever consumes `proc` from here on notices
|
|
84
|
-
// Chrome went away some other way (the CDP pipe closing).
|
|
85
23
|
proc.on('error', (err) => {
|
|
86
24
|
if (settled)
|
|
87
25
|
return;
|
|
@@ -90,7 +28,6 @@ export function launchChrome(executablePath, targetUrl, userDataDir, spawnFn = s
|
|
|
90
28
|
proc.kill('SIGKILL');
|
|
91
29
|
}
|
|
92
30
|
catch {
|
|
93
|
-
// already gone
|
|
94
31
|
}
|
|
95
32
|
reject(err);
|
|
96
33
|
});
|
|
@@ -102,16 +39,10 @@ export function launchChrome(executablePath, targetUrl, userDataDir, spawnFn = s
|
|
|
102
39
|
proc.kill('SIGKILL');
|
|
103
40
|
}
|
|
104
41
|
catch {
|
|
105
|
-
// already gone
|
|
106
42
|
}
|
|
107
43
|
reject(new Error('Chrome did not expose the CDP pipe (fd 3/4) — this Chrome build or platform may not support --remote-debugging-pipe.'));
|
|
108
44
|
return;
|
|
109
45
|
}
|
|
110
|
-
// Only declare success once the OS confirms the process actually
|
|
111
|
-
// started — the documented signal for that ('spawn', not merely
|
|
112
|
-
// "spawn() returned without throwing") — rather than assuming
|
|
113
|
-
// immediately: that's exactly the assumption the bug this fixes was
|
|
114
|
-
// built on.
|
|
115
46
|
proc.once('spawn', () => {
|
|
116
47
|
if (settled)
|
|
117
48
|
return;
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -4,67 +4,14 @@ interface TrawlConfig {
|
|
|
4
4
|
token: string;
|
|
5
5
|
telemetry: boolean;
|
|
6
6
|
telemetryUserId: string;
|
|
7
|
-
/** Opt-out switch for the passive "update available" notifier
|
|
8
|
-
* (src/lib/updateNotifier.ts). Optional — absent/undefined means enabled;
|
|
9
|
-
* only an explicit `false` disables it. No default entry needed since it's
|
|
10
|
-
* optional. (#129) */
|
|
11
7
|
updateNotifier?: boolean;
|
|
12
|
-
/** Opt-out switch for the throttled post-run referral tip (lib/tips.ts).
|
|
13
|
-
* Same optional/absent-means-enabled shape as `updateNotifier` above. */
|
|
14
8
|
tips?: boolean;
|
|
15
9
|
referralTipShownAt: number;
|
|
16
10
|
skillsNudgeShownAt: number;
|
|
17
11
|
}
|
|
18
12
|
declare const config: Conf<TrawlConfig>;
|
|
19
|
-
/**
|
|
20
|
-
* Resolve the effective API base URL.
|
|
21
|
-
* Precedence: TRAWL_API_URL env > stored `trawl login --url` > default.
|
|
22
|
-
* Mirrors the TRAWL_TOKEN / TRAWL_TELEMETRY session-override pattern so scripts
|
|
23
|
-
* (e.g. infra /trawl-prod-qa against https://dev.trawl.me) can retarget the CLI
|
|
24
|
-
* without mutating the operator's persisted config. (#56)
|
|
25
|
-
*/
|
|
26
13
|
export declare function getApiUrl(): string;
|
|
27
|
-
/**
|
|
28
|
-
* Resolve the effective credential — a scoped API key or a session JWT.
|
|
29
|
-
* Precedence: TRAWL_API_KEY env > TRAWL_TOKEN env > stored `trawl login`
|
|
30
|
-
* token. TRAWL_API_KEY resolves first so an agent harness that carries BOTH
|
|
31
|
-
* (e.g. a scoped key set globally alongside a leftover human TRAWL_TOKEN)
|
|
32
|
-
* gets the key unambiguously, never a silent fall-through to the
|
|
33
|
-
* higher-privilege JWT. Lets CI/agents authenticate headlessly
|
|
34
|
-
* (`TRAWL_API_KEY=trawl_xxx trawl list` or `TRAWL_TOKEN=<jwt> trawl list`)
|
|
35
|
-
* without ever touching the on-disk config — and without a stored token
|
|
36
|
-
* being silently sent to whatever TRAWL_API_URL points at instead (cross-env
|
|
37
|
-
* credential misuse). Every request/upload/publicGet/getText/stream call
|
|
38
|
-
* site in api.ts must read the token through this, never through
|
|
39
|
-
* `config.get('token')` directly. Mirrors getApiUrl(). (#68, #169)
|
|
40
|
-
*/
|
|
41
14
|
export declare function getToken(): string;
|
|
42
|
-
/**
|
|
43
|
-
* Discriminate which of the two credential shapes this CLI can send a
|
|
44
|
-
* `token` is: a scoped API key (`Authorization: Bearer`) or a session JWT
|
|
45
|
-
* (`Cookie: TOKEN=`). The `trawl_` prefix is the SERVER's own test —
|
|
46
|
-
* trawl_node's `authenticateApiKey.js` checks `rawKey.startsWith('trawl_')`
|
|
47
|
-
* — not a convention invented on this side; this function exists so the two
|
|
48
|
-
* sides can never quietly drift apart on what counts as a key. Defaults to
|
|
49
|
-
* the live `getToken()` result so most callers (e.g. `whoami`'s JWT-only
|
|
50
|
-
* guard) need no argument; `api.ts`'s `authHeaders()` instead passes the
|
|
51
|
-
* exact token it already resolved for THIS request, so a request's headers
|
|
52
|
-
* and its classification of that same token can never disagree. (#169)
|
|
53
|
-
*/
|
|
54
15
|
export declare function getAuthMode(token?: string): 'apiKey' | 'jwt';
|
|
55
|
-
/**
|
|
56
|
-
* Which of getToken()'s two env-var overrides actually won, if either —
|
|
57
|
-
* mirrors its TRAWL_API_KEY > TRAWL_TOKEN precedence exactly. Neither
|
|
58
|
-
* `trawl login` nor `trawl logout` can unset a caller's OWN environment, so
|
|
59
|
-
* whichever of these is set keeps overriding the stored config token
|
|
60
|
-
* regardless of what those commands just did (#169 review). login.ts's
|
|
61
|
-
* post-login/post-logout warnings and api.ts's apiKey-mode auth-failure
|
|
62
|
-
* messages/`next` steps all need the SAME answer to "what has to be unset
|
|
63
|
-
* before `trawl login` takes effect" — resolved once here rather than each
|
|
64
|
-
* call site re-deriving its own copy that could quietly drift from
|
|
65
|
-
* getToken()'s actual precedence. Returns null when the credential came from
|
|
66
|
-
* the stored config token instead — nothing to unset there, `trawl login`
|
|
67
|
-
* alone already fixes that case.
|
|
68
|
-
*/
|
|
69
16
|
export declare function getLiveAuthEnvVar(): 'TRAWL_API_KEY' | 'TRAWL_TOKEN' | null;
|
|
70
17
|
export default config;
|
package/dist/lib/config.js
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
import Conf from 'conf';
|
|
2
|
-
/**
|
|
3
|
-
* TRAWL_CONFIG_DIR overrides where Conf stores the config file (its `cwd`
|
|
4
|
-
* option). Without this, the CLI has zero config isolation on macOS — Conf's
|
|
5
|
-
* env-paths dependency hardcodes `~/Library/Preferences/...` and ignores
|
|
6
|
-
* XDG_CONFIG_HOME — so CI/agent runs and concurrent `trawl login`s race on
|
|
7
|
-
* one shared on-disk file. Point at an ephemeral dir for hermetic runs, e.g.
|
|
8
|
-
* `TRAWL_CONFIG_DIR=$(mktemp -d) trawl login --token …`. Rescope of #59. (#68)
|
|
9
|
-
*/
|
|
10
2
|
const configDir = process.env['TRAWL_CONFIG_DIR']?.trim();
|
|
11
3
|
const config = new Conf({
|
|
12
4
|
projectName: 'trawl-cli',
|
|
@@ -20,31 +12,10 @@ const config = new Conf({
|
|
|
20
12
|
skillsNudgeShownAt: 0,
|
|
21
13
|
},
|
|
22
14
|
});
|
|
23
|
-
/**
|
|
24
|
-
* Resolve the effective API base URL.
|
|
25
|
-
* Precedence: TRAWL_API_URL env > stored `trawl login --url` > default.
|
|
26
|
-
* Mirrors the TRAWL_TOKEN / TRAWL_TELEMETRY session-override pattern so scripts
|
|
27
|
-
* (e.g. infra /trawl-prod-qa against https://dev.trawl.me) can retarget the CLI
|
|
28
|
-
* without mutating the operator's persisted config. (#56)
|
|
29
|
-
*/
|
|
30
15
|
export function getApiUrl() {
|
|
31
16
|
const override = process.env['TRAWL_API_URL']?.trim();
|
|
32
17
|
return override ? override : config.get('apiUrl');
|
|
33
18
|
}
|
|
34
|
-
/**
|
|
35
|
-
* Resolve the effective credential — a scoped API key or a session JWT.
|
|
36
|
-
* Precedence: TRAWL_API_KEY env > TRAWL_TOKEN env > stored `trawl login`
|
|
37
|
-
* token. TRAWL_API_KEY resolves first so an agent harness that carries BOTH
|
|
38
|
-
* (e.g. a scoped key set globally alongside a leftover human TRAWL_TOKEN)
|
|
39
|
-
* gets the key unambiguously, never a silent fall-through to the
|
|
40
|
-
* higher-privilege JWT. Lets CI/agents authenticate headlessly
|
|
41
|
-
* (`TRAWL_API_KEY=trawl_xxx trawl list` or `TRAWL_TOKEN=<jwt> trawl list`)
|
|
42
|
-
* without ever touching the on-disk config — and without a stored token
|
|
43
|
-
* being silently sent to whatever TRAWL_API_URL points at instead (cross-env
|
|
44
|
-
* credential misuse). Every request/upload/publicGet/getText/stream call
|
|
45
|
-
* site in api.ts must read the token through this, never through
|
|
46
|
-
* `config.get('token')` directly. Mirrors getApiUrl(). (#68, #169)
|
|
47
|
-
*/
|
|
48
19
|
export function getToken() {
|
|
49
20
|
const apiKey = process.env['TRAWL_API_KEY']?.trim();
|
|
50
21
|
if (apiKey)
|
|
@@ -52,35 +23,9 @@ export function getToken() {
|
|
|
52
23
|
const override = process.env['TRAWL_TOKEN']?.trim();
|
|
53
24
|
return override ? override : config.get('token');
|
|
54
25
|
}
|
|
55
|
-
/**
|
|
56
|
-
* Discriminate which of the two credential shapes this CLI can send a
|
|
57
|
-
* `token` is: a scoped API key (`Authorization: Bearer`) or a session JWT
|
|
58
|
-
* (`Cookie: TOKEN=`). The `trawl_` prefix is the SERVER's own test —
|
|
59
|
-
* trawl_node's `authenticateApiKey.js` checks `rawKey.startsWith('trawl_')`
|
|
60
|
-
* — not a convention invented on this side; this function exists so the two
|
|
61
|
-
* sides can never quietly drift apart on what counts as a key. Defaults to
|
|
62
|
-
* the live `getToken()` result so most callers (e.g. `whoami`'s JWT-only
|
|
63
|
-
* guard) need no argument; `api.ts`'s `authHeaders()` instead passes the
|
|
64
|
-
* exact token it already resolved for THIS request, so a request's headers
|
|
65
|
-
* and its classification of that same token can never disagree. (#169)
|
|
66
|
-
*/
|
|
67
26
|
export function getAuthMode(token = getToken()) {
|
|
68
27
|
return token.startsWith('trawl_') ? 'apiKey' : 'jwt';
|
|
69
28
|
}
|
|
70
|
-
/**
|
|
71
|
-
* Which of getToken()'s two env-var overrides actually won, if either —
|
|
72
|
-
* mirrors its TRAWL_API_KEY > TRAWL_TOKEN precedence exactly. Neither
|
|
73
|
-
* `trawl login` nor `trawl logout` can unset a caller's OWN environment, so
|
|
74
|
-
* whichever of these is set keeps overriding the stored config token
|
|
75
|
-
* regardless of what those commands just did (#169 review). login.ts's
|
|
76
|
-
* post-login/post-logout warnings and api.ts's apiKey-mode auth-failure
|
|
77
|
-
* messages/`next` steps all need the SAME answer to "what has to be unset
|
|
78
|
-
* before `trawl login` takes effect" — resolved once here rather than each
|
|
79
|
-
* call site re-deriving its own copy that could quietly drift from
|
|
80
|
-
* getToken()'s actual precedence. Returns null when the credential came from
|
|
81
|
-
* the stored config token instead — nothing to unset there, `trawl login`
|
|
82
|
-
* alone already fixes that case.
|
|
83
|
-
*/
|
|
84
29
|
export function getLiveAuthEnvVar() {
|
|
85
30
|
if (process.env['TRAWL_API_KEY']?.trim())
|
|
86
31
|
return 'TRAWL_API_KEY';
|
package/dist/lib/confirm.d.ts
CHANGED
|
@@ -1,70 +1,15 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* #107 — the machine contract's non-interactive rule, in one place. True only
|
|
3
|
-
* when a blocking interactive prompt is safe to show: NOT `--json` (a machine
|
|
4
|
-
* consumer needs pure stdout, and there's no human reading a prompt anyway)
|
|
5
|
-
* AND both stdin/stdout are real TTYs. A pipe/redirect/CI runner — including
|
|
6
|
-
* an agent driving this CLI as a subprocess — has nowhere for a human to type
|
|
7
|
-
* an answer; a blocking `readline` prompt in that situation hangs forever
|
|
8
|
-
* instead of ever returning.
|
|
9
|
-
*/
|
|
10
1
|
export declare function isInteractive(opts?: {
|
|
11
2
|
json?: boolean;
|
|
12
3
|
}): boolean;
|
|
13
4
|
export interface ConfirmOutcome {
|
|
14
|
-
/** True when the caller should proceed with the guarded action. */
|
|
15
5
|
proceed: boolean;
|
|
16
|
-
/**
|
|
17
|
-
* True when refused because the invocation is non-interactive (`--json`,
|
|
18
|
-
* or stdin/stdout isn't a real TTY) — a structured usage error has ALREADY
|
|
19
|
-
* been reported (the stderr line and/or the `--json` envelope) and
|
|
20
|
-
* `process.exitCode` set to 2. The caller must return immediately without
|
|
21
|
-
* printing anything else (mirrors every other `usageError()` call site).
|
|
22
|
-
*/
|
|
23
6
|
blocked: boolean;
|
|
24
7
|
}
|
|
25
|
-
/**
|
|
26
|
-
* Shared guard for every destructive y/N confirmation in the CLI (`scraps
|
|
27
|
-
* delete`, `scraps account delete`, …). #107 — before this, each call site
|
|
28
|
-
* hand-rolled its own `readline` question with no non-interactive escape
|
|
29
|
-
* hatch, so a scripted/agent invocation with no `-f`/`--force` would hang
|
|
30
|
-
* forever waiting for a y/N answer that could never arrive.
|
|
31
|
-
*
|
|
32
|
-
* `-f`/`--force` (`opts.force`) always pre-confirms, interactive or not — the
|
|
33
|
-
* caller already gave explicit consent up front. Otherwise:
|
|
34
|
-
* - Non-interactive (`--json`, or stdin/stdout isn't a real TTY): NEVER
|
|
35
|
-
* prompts. Reports a structured usage error (exit 2) instead, via the same
|
|
36
|
-
* central `reportError` formatting path every other error in this CLI
|
|
37
|
-
* uses (stderr human line, or the `--json` envelope on stdout).
|
|
38
|
-
* - Interactive: prompts via `readline` exactly like the pre-#107 call
|
|
39
|
-
* sites did.
|
|
40
|
-
*
|
|
41
|
-
* `message` MUST be plain, unstyled text — it feeds both the refusal
|
|
42
|
-
* UsageError's message (human stderr line AND the `--json` error envelope on
|
|
43
|
-
* stdout) and the fallback prompt text. Before this, call sites passed a
|
|
44
|
-
* `chalk.bold(id)`-styled string as `message`, which leaked raw ANSI escape
|
|
45
|
-
* bytes into the `--json` envelope (e.g. `scraps delete X --json` on the
|
|
46
|
-
* refusal path emitted a message with the raw bold-on/off escape sequence
|
|
47
|
-
* wrapped around the id) — unusable for a script/agent parsing that string.
|
|
48
|
-
* `promptMessage` is the OPTIONAL styled variant shown only for the
|
|
49
|
-
* interactive TTY `[y/N]` prompt (chalk is safe there — no machine ever
|
|
50
|
-
* reads it); it defaults to `message` when omitted. (#107 review F2)
|
|
51
|
-
*/
|
|
52
8
|
export declare function confirmDestructive(message: string, opts?: {
|
|
53
9
|
force?: boolean;
|
|
54
10
|
json?: boolean;
|
|
55
11
|
promptMessage?: string;
|
|
56
12
|
}): Promise<ConfirmOutcome>;
|
|
57
|
-
/**
|
|
58
|
-
* Guard for a required interactive value that has no flag-supplied value yet
|
|
59
|
-
* (login's email prompt, …) — for call sites that propagate errors by
|
|
60
|
-
* `throw`ing and let the top-level handler in index.ts classify + report
|
|
61
|
-
* (login.ts's existing convention), as opposed to `scraps.ts`'s local
|
|
62
|
-
* exitCode-setting `usageError()` style (which should check `isInteractive`
|
|
63
|
-
* directly instead of using this). Same non-interactive contract as
|
|
64
|
-
* `confirmDestructive`: never invokes a blocking prompt when `--json` is set
|
|
65
|
-
* or stdin/stdout isn't a real TTY — throws a `UsageError` naming the flag to
|
|
66
|
-
* pass instead. (#107)
|
|
67
|
-
*/
|
|
68
13
|
export declare function requireInteractive(message: string, opts?: {
|
|
69
14
|
json?: boolean;
|
|
70
15
|
}): void;
|
package/dist/lib/confirm.js
CHANGED
|
@@ -1,47 +1,11 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { UsageError } from './errors.js';
|
|
3
3
|
import { reportError } from './errors.js';
|
|
4
|
-
/**
|
|
5
|
-
* #107 — the machine contract's non-interactive rule, in one place. True only
|
|
6
|
-
* when a blocking interactive prompt is safe to show: NOT `--json` (a machine
|
|
7
|
-
* consumer needs pure stdout, and there's no human reading a prompt anyway)
|
|
8
|
-
* AND both stdin/stdout are real TTYs. A pipe/redirect/CI runner — including
|
|
9
|
-
* an agent driving this CLI as a subprocess — has nowhere for a human to type
|
|
10
|
-
* an answer; a blocking `readline` prompt in that situation hangs forever
|
|
11
|
-
* instead of ever returning.
|
|
12
|
-
*/
|
|
13
4
|
export function isInteractive(opts = {}) {
|
|
14
5
|
if (opts.json)
|
|
15
6
|
return false;
|
|
16
7
|
return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
|
|
17
8
|
}
|
|
18
|
-
/**
|
|
19
|
-
* Shared guard for every destructive y/N confirmation in the CLI (`scraps
|
|
20
|
-
* delete`, `scraps account delete`, …). #107 — before this, each call site
|
|
21
|
-
* hand-rolled its own `readline` question with no non-interactive escape
|
|
22
|
-
* hatch, so a scripted/agent invocation with no `-f`/`--force` would hang
|
|
23
|
-
* forever waiting for a y/N answer that could never arrive.
|
|
24
|
-
*
|
|
25
|
-
* `-f`/`--force` (`opts.force`) always pre-confirms, interactive or not — the
|
|
26
|
-
* caller already gave explicit consent up front. Otherwise:
|
|
27
|
-
* - Non-interactive (`--json`, or stdin/stdout isn't a real TTY): NEVER
|
|
28
|
-
* prompts. Reports a structured usage error (exit 2) instead, via the same
|
|
29
|
-
* central `reportError` formatting path every other error in this CLI
|
|
30
|
-
* uses (stderr human line, or the `--json` envelope on stdout).
|
|
31
|
-
* - Interactive: prompts via `readline` exactly like the pre-#107 call
|
|
32
|
-
* sites did.
|
|
33
|
-
*
|
|
34
|
-
* `message` MUST be plain, unstyled text — it feeds both the refusal
|
|
35
|
-
* UsageError's message (human stderr line AND the `--json` error envelope on
|
|
36
|
-
* stdout) and the fallback prompt text. Before this, call sites passed a
|
|
37
|
-
* `chalk.bold(id)`-styled string as `message`, which leaked raw ANSI escape
|
|
38
|
-
* bytes into the `--json` envelope (e.g. `scraps delete X --json` on the
|
|
39
|
-
* refusal path emitted a message with the raw bold-on/off escape sequence
|
|
40
|
-
* wrapped around the id) — unusable for a script/agent parsing that string.
|
|
41
|
-
* `promptMessage` is the OPTIONAL styled variant shown only for the
|
|
42
|
-
* interactive TTY `[y/N]` prompt (chalk is safe there — no machine ever
|
|
43
|
-
* reads it); it defaults to `message` when omitted. (#107 review F2)
|
|
44
|
-
*/
|
|
45
9
|
export async function confirmDestructive(message, opts = {}) {
|
|
46
10
|
if (opts.force)
|
|
47
11
|
return { proceed: true, blocked: false };
|
|
@@ -61,17 +25,6 @@ export async function confirmDestructive(message, opts = {}) {
|
|
|
61
25
|
rl.close();
|
|
62
26
|
}
|
|
63
27
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Guard for a required interactive value that has no flag-supplied value yet
|
|
66
|
-
* (login's email prompt, …) — for call sites that propagate errors by
|
|
67
|
-
* `throw`ing and let the top-level handler in index.ts classify + report
|
|
68
|
-
* (login.ts's existing convention), as opposed to `scraps.ts`'s local
|
|
69
|
-
* exitCode-setting `usageError()` style (which should check `isInteractive`
|
|
70
|
-
* directly instead of using this). Same non-interactive contract as
|
|
71
|
-
* `confirmDestructive`: never invokes a blocking prompt when `--json` is set
|
|
72
|
-
* or stdin/stdout isn't a real TTY — throws a `UsageError` naming the flag to
|
|
73
|
-
* pass instead. (#107)
|
|
74
|
-
*/
|
|
75
28
|
export function requireInteractive(message, opts = {}) {
|
|
76
29
|
if (!isInteractive(opts)) {
|
|
77
30
|
throw new UsageError(message);
|