@trawlme/cli 3.11.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 +5 -2
- 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 +142 -656
- 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 +31 -0
- package/dist/lib/cdp-pipe.js +141 -0
- package/dist/lib/chrome-discovery.d.ts +1 -0
- package/dist/lib/chrome-discovery.js +30 -0
- package/dist/lib/chrome-launch.d.ts +8 -0
- package/dist/lib/chrome-launch.js +53 -0
- 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 +1 -0
- package/dist/lib/secure-transport.js +15 -0
- package/dist/lib/session-capture-guard.d.ts +6 -0
- package/dist/lib/session-capture-guard.js +9 -0
- package/dist/lib/session-capture.d.ts +55 -0
- package/dist/lib/session-capture.js +319 -0
- 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 +55 -0
- package/dist/lib/storage-state.js +96 -0
- 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
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
const CANDIDATES = {
|
|
3
|
+
darwin: [
|
|
4
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
5
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
6
|
+
'/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta',
|
|
7
|
+
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
|
|
8
|
+
],
|
|
9
|
+
linux: [
|
|
10
|
+
'/usr/bin/google-chrome',
|
|
11
|
+
'/usr/bin/google-chrome-stable',
|
|
12
|
+
'/usr/bin/chromium-browser',
|
|
13
|
+
'/usr/bin/chromium',
|
|
14
|
+
'/snap/bin/chromium',
|
|
15
|
+
],
|
|
16
|
+
win32: [
|
|
17
|
+
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
18
|
+
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
19
|
+
'C:\\Program Files\\Chromium\\Application\\chrome.exe',
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
export function findChrome(env = process.env, platform = process.platform, exists = existsSync) {
|
|
23
|
+
const envCandidates = [env.TRAWL_CHROME_PATH, env.PUPPETEER_EXECUTABLE_PATH, env.CHROME_PATH].filter((p) => typeof p === 'string' && p.length > 0);
|
|
24
|
+
for (const p of envCandidates) {
|
|
25
|
+
if (exists(p))
|
|
26
|
+
return p;
|
|
27
|
+
}
|
|
28
|
+
const candidates = CANDIDATES[platform] ?? [];
|
|
29
|
+
return candidates.find(exists) ?? null;
|
|
30
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { spawn, type ChildProcess } from 'node:child_process';
|
|
2
|
+
import { CdpPipe } from './cdp-pipe.js';
|
|
3
|
+
export interface LaunchedChrome {
|
|
4
|
+
proc: ChildProcess;
|
|
5
|
+
cdp: CdpPipe;
|
|
6
|
+
}
|
|
7
|
+
export declare function buildChromeArgs(userDataDir: string, targetUrl: string): string[];
|
|
8
|
+
export declare function launchChrome(executablePath: string, targetUrl: string, userDataDir: string, spawnFn?: typeof spawn): Promise<LaunchedChrome>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { CdpPipe } from './cdp-pipe.js';
|
|
3
|
+
export function buildChromeArgs(userDataDir, targetUrl) {
|
|
4
|
+
return [
|
|
5
|
+
'--remote-debugging-pipe',
|
|
6
|
+
`--user-data-dir=${userDataDir}`,
|
|
7
|
+
'--no-first-run',
|
|
8
|
+
'--no-default-browser-check',
|
|
9
|
+
'--disable-sync',
|
|
10
|
+
'--password-store=basic',
|
|
11
|
+
'--use-mock-keychain',
|
|
12
|
+
'--new-window',
|
|
13
|
+
targetUrl,
|
|
14
|
+
];
|
|
15
|
+
}
|
|
16
|
+
export function launchChrome(executablePath, targetUrl, userDataDir, spawnFn = spawn) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const proc = spawnFn(executablePath, buildChromeArgs(userDataDir, targetUrl), {
|
|
19
|
+
stdio: ['ignore', 'ignore', 'ignore', 'pipe', 'pipe'],
|
|
20
|
+
detached: process.platform !== 'win32',
|
|
21
|
+
});
|
|
22
|
+
let settled = false;
|
|
23
|
+
proc.on('error', (err) => {
|
|
24
|
+
if (settled)
|
|
25
|
+
return;
|
|
26
|
+
settled = true;
|
|
27
|
+
try {
|
|
28
|
+
proc.kill('SIGKILL');
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
}
|
|
32
|
+
reject(err);
|
|
33
|
+
});
|
|
34
|
+
const writeStream = proc.stdio[3];
|
|
35
|
+
const readStream = proc.stdio[4];
|
|
36
|
+
if (!writeStream || !readStream || typeof writeStream.write !== 'function') {
|
|
37
|
+
settled = true;
|
|
38
|
+
try {
|
|
39
|
+
proc.kill('SIGKILL');
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
}
|
|
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.'));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
proc.once('spawn', () => {
|
|
47
|
+
if (settled)
|
|
48
|
+
return;
|
|
49
|
+
settled = true;
|
|
50
|
+
resolve({ proc, cdp: new CdpPipe(writeStream, readStream) });
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
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);
|
package/dist/lib/docs.d.ts
CHANGED
|
@@ -1,140 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* trawl_cli#185 — ONE source of truth for the docs URL(s) an agent (or
|
|
3
|
-
* human) can be pointed at from three surfaces: `spec --json` (top-level
|
|
4
|
-
* `docsUrl`/`llmsUrl` + a per-command `docs` deep link), a run's JSON
|
|
5
|
-
* payload (`doctor`/`data --errors`/`run-info`, keyed on `failureKind` —
|
|
6
|
-
* NOT errors.ts's unrelated `ErrorEnvelope.kind`, see the doc comment on
|
|
7
|
-
* `FAILURE_KIND_DOC_PATHS` below), and the `--help` footer. Three hardcoded
|
|
8
|
-
* lists here would diverge within months — that exact "same fact computed
|
|
9
|
-
* in two places" defect has recurred repeatedly across this epic — so every
|
|
10
|
-
* surface reads these same tables/functions, never its own copy.
|
|
11
|
-
*
|
|
12
|
-
* Resolution ladder (issue #185 — all three rungs REQUIRED, in this order,
|
|
13
|
-
* evaluated PER FIELD, never as one bundled decision — see `resolveDocsUrls`):
|
|
14
|
-
*
|
|
15
|
-
* 1. Prefer the server. `externalDocs.url` on the OpenAPI document at
|
|
16
|
-
* `<apiBase>/api/spec.json` is the standard OpenAPI field for exactly
|
|
17
|
-
* this, and reading it makes a self-hosted install work with ZERO CLI
|
|
18
|
-
* change. It is unset (null) server-side today, so this is
|
|
19
|
-
* forward-looking — build it anyway, and it must win outright over rung
|
|
20
|
-
* 2 when present. The one caller allowed to fetch it is `spec.ts`'s own
|
|
21
|
-
* action, bounded and swallowed-on-failure (mirrors lib/tips.ts's
|
|
22
|
-
* `isReferralProgramUserFacing`) — a deliberate, once-per-invocation
|
|
23
|
-
* agent probe, not "every command". Every other surface (an error
|
|
24
|
-
* payload on an arbitrary failing command, the `--help` footer) must
|
|
25
|
-
* never add a network call or a failure mode to a command nobody asked
|
|
26
|
-
* to hit the docs host for — see `resolveDocsUrls`'s `externalDocsUrl`
|
|
27
|
-
* parameter, which only ever arrives pre-fetched.
|
|
28
|
-
* 2. Else derive, by stripping a leading `api.` host label from the
|
|
29
|
-
* configured API base — but ONLY for a KNOWN first-party `trawl.me`
|
|
30
|
-
* host. `api.trawl.me` -> `trawl.me` (prod: API and docs are genuinely
|
|
31
|
-
* on different hosts); `dev.trawl.me` (no `api.` prefix) -> unchanged
|
|
32
|
-
* (dev serves docs on the SAME host as its API). A generic, unscoped
|
|
33
|
-
* `api.`-strip applied to ANY host is itself a guess — it assumes a
|
|
34
|
-
* self-hosted `api.acme.internal` serves docs at `acme.internal`, which
|
|
35
|
-
* nothing here can know. Scoping the strip to `trawl.me` is what makes
|
|
36
|
-
* rung 3 (below) ever actually fire for a self-hosted base.
|
|
37
|
-
* 3. Else OMIT the field entirely. Never emit a guessed URL (issue's Rule
|
|
38
|
-
* 3): a wrong URL sends an agent to a 404 WITH CONFIDENCE, worse than no
|
|
39
|
-
* URL at all — a self-hosted install with no server-declared
|
|
40
|
-
* `externalDocs` and a base outside `trawl.me` gets no `docsUrl`/
|
|
41
|
-
* `llmsUrl`, not a hopeful default pointed at OUR docs host.
|
|
42
|
-
*
|
|
43
|
-
* `llmsUrl` has no OpenAPI-standard field to read (rung 1 contributes
|
|
44
|
-
* nothing to it) — deriving it from the ORIGIN of a server-declared
|
|
45
|
-
* `externalDocs.url` would itself be a guess for a self-hosted install
|
|
46
|
-
* (rule 3 again), so `llmsUrl` resolves ONLY via rung 2 (the known-host
|
|
47
|
-
* derivation) or omission. It never rides along with a rung-1 `docsUrl`.
|
|
48
|
-
*
|
|
49
|
-
* PROVENANCE (defect fix, reviewer repro against a live mock server): the
|
|
50
|
-
* top-level `docsUrl` above is deliberately the FLATTENED "whichever rung
|
|
51
|
-
* won" value — right for a human reading `spec --json`'s top-level field,
|
|
52
|
-
* WRONG as an input to `resolveCommandDocsUrl`/`resolveFailureKindDocsUrl`
|
|
53
|
-
* below. Those two append one of THIS CLI's own hardcoded guide slugs
|
|
54
|
-
* (`COMMAND_DOC_PATHS`/`FAILURE_KIND_DOC_PATHS`) onto whatever `docsUrl`
|
|
55
|
-
* resolved — safe onto a rung-2 root we derived ourselves (we know
|
|
56
|
-
* trawl.me's guide tree), never safe onto a rung-1 root (an arbitrary
|
|
57
|
-
* third party's own docs site, self-hosted, with no reason to carry our
|
|
58
|
-
* slugs). A mock server declaring `externalDocs.url: 'http://h/guide'`
|
|
59
|
-
* used to get `http://h/guide/build-your-scrap/account-sessions` appended
|
|
60
|
-
* — a confidently-wrong 404. So `DocsUrls.docsUrlIsDerived` carries rung
|
|
61
|
-
* provenance ALONGSIDE the string (never flattened to a bare string again)
|
|
62
|
-
* and both deep-link functions now take the whole `DocsUrls`-shaped object
|
|
63
|
-
* and gate construction on that flag — see its own doc comment below.
|
|
64
|
-
*/
|
|
65
1
|
export interface DocsUrls {
|
|
66
2
|
docsUrl?: string;
|
|
67
3
|
llmsUrl?: string;
|
|
68
|
-
/**
|
|
69
|
-
* True iff `docsUrl` came from rung 2 (THIS CLI's own derivation from a
|
|
70
|
-
* KNOWN `trawl.me` host) — the only case where it is safe to append one of
|
|
71
|
-
* this CLI's own guide slugs onto it (see `resolveCommandDocsUrl`/
|
|
72
|
-
* `resolveFailureKindDocsUrl`). False when `docsUrl` is a rung-1
|
|
73
|
-
* SERVER-declared root instead — an arbitrary third party's own docs site,
|
|
74
|
-
* which has no reason to host our guide slugs, even when that server
|
|
75
|
-
* happens to run on a `trawl.me` host. Always a defined boolean whenever
|
|
76
|
-
* `docsUrl` itself is present; undefined only when `docsUrl` is undefined
|
|
77
|
-
* too (rung 3 — nothing resolved at all).
|
|
78
|
-
*/
|
|
79
4
|
docsUrlIsDerived?: boolean;
|
|
80
5
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Rung 2 — see the module doc comment. Returns `{ protocol, host }` for a
|
|
83
|
-
* KNOWN first-party API base (preserving the base's own protocol rather
|
|
84
|
-
* than assuming `https:`), or `null` when the base isn't recognized
|
|
85
|
-
* (self-hosted, a custom domain, `localhost`, an unparseable string, …) —
|
|
86
|
-
* `null` must propagate to omission (rung 3), never to a guessed host.
|
|
87
|
-
*/
|
|
88
6
|
export declare function deriveDocsOrigin(apiBaseUrl: string): {
|
|
89
7
|
protocol: string;
|
|
90
8
|
host: string;
|
|
91
9
|
} | null;
|
|
92
|
-
/** Same rung as `deriveDocsOrigin`, collapsed to just the host string —
|
|
93
|
-
* convenience for a caller that only needs the derived host, not the
|
|
94
|
-
* protocol (kept as its own export since `docs.test.ts` exercises the host
|
|
95
|
-
* derivation independently of protocol handling). */
|
|
96
10
|
export declare function deriveDocsHost(apiBaseUrl: string): string | null;
|
|
97
|
-
/**
|
|
98
|
-
* The full ladder, PER FIELD (see module doc comment for why `llmsUrl`
|
|
99
|
-
* cannot ride along with a rung-1 `docsUrl`). Pure and synchronous — no
|
|
100
|
-
* network, safe to call from any surface (an error payload, the `--help`
|
|
101
|
-
* footer) without adding latency or a new failure mode. `externalDocsUrl`
|
|
102
|
-
* is rung 1's input: only ever supplied by `spec.ts`'s action, after its
|
|
103
|
-
* own bounded, swallowed-on-failure fetch — every other caller omits it and
|
|
104
|
-
* gets rungs 2/3 only.
|
|
105
|
-
*/
|
|
106
11
|
export declare function resolveDocsUrls(opts: {
|
|
107
12
|
apiBaseUrl: string;
|
|
108
13
|
externalDocsUrl?: string | null;
|
|
109
14
|
}): DocsUrls;
|
|
110
|
-
/**
|
|
111
|
-
* `resolveCommandDocsUrl` returns `undefined` (never a bare path or a
|
|
112
|
-
* relative link) whenever any of three things is missing — `docsUrl`
|
|
113
|
-
* unresolved (rung 3 already fired), `docsUrl` resolved but NOT derived
|
|
114
|
-
* (rung 1 — a server-declared root; see `DocsUrls.docsUrlIsDerived`'s doc
|
|
115
|
-
* comment for why appending our own guide slug onto a third party's root is
|
|
116
|
-
* exactly the confidently-wrong-404 defect this gate closes), or no guide is
|
|
117
|
-
* mapped for this command — so a spec consumer never has to special-case a
|
|
118
|
-
* partial value. Takes the whole resolved `DocsUrls` object (never a bare
|
|
119
|
-
* string) specifically so this provenance can never again be flattened away
|
|
120
|
-
* before it reaches here.
|
|
121
|
-
*/
|
|
122
15
|
export declare function resolveCommandDocsUrl(commandName: string, docs: Pick<DocsUrls, 'docsUrl' | 'docsUrlIsDerived'>): string | undefined;
|
|
123
|
-
/** Same "undefined unless docsUrl is resolved AND derived (rung 2)" gate as
|
|
124
|
-
* `resolveCommandDocsUrl` above, same reason — never append this CLI's own
|
|
125
|
-
* guide slug onto a rung-1 server-declared root. Callers MUST gate the call
|
|
126
|
-
* on their own staleness rule first (e.g. `doctor.ts`'s `isAuthWall`) — this
|
|
127
|
-
* function only knows the string-to-path mapping, not whether a stamped
|
|
128
|
-
* `failureKind` is still live on a run patched afterward. */
|
|
129
16
|
export declare function resolveFailureKindDocsUrl(kind: string | null | undefined, docs: Pick<DocsUrls, 'docsUrl' | 'docsUrlIsDerived'>): string | undefined;
|
|
130
|
-
/**
|
|
131
|
-
* `--help` footer text (issue #185 scope item 3) — a single dim line, for
|
|
132
|
-
* humans, e.g. "Docs: https://trawl.me/docs". Returns `undefined` (never an
|
|
133
|
-
* empty or guessed line) when no `docsUrl` resolved — mirrors
|
|
134
|
-
* lib/tips.ts's shape (a pure function separated from IO, so it's testable
|
|
135
|
-
* without mocking chalk/console) but NOT its TTY gate: tips.ts suppresses a
|
|
136
|
-
* promotional nudge from piped output, but a docs line inside `--help |
|
|
137
|
-
* less` is exactly the kind of thing worth keeping. Un-colored here — the
|
|
138
|
-
* caller (index.ts) applies `chalk.dim` so this stays trivially testable.
|
|
139
|
-
*/
|
|
140
17
|
export declare function docsFooterLine(docsUrl?: string): string | undefined;
|