@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.
Files changed (73) hide show
  1. package/README.md +5 -2
  2. package/dist/commands/create.d.ts +0 -28
  3. package/dist/commands/create.js +0 -89
  4. package/dist/commands/doctor.d.ts +0 -79
  5. package/dist/commands/doctor.js +1 -187
  6. package/dist/commands/login.js +0 -67
  7. package/dist/commands/ping.d.ts +0 -15
  8. package/dist/commands/ping.js +0 -15
  9. package/dist/commands/scraps.d.ts +0 -120
  10. package/dist/commands/scraps.js +142 -656
  11. package/dist/commands/skills.js +0 -22
  12. package/dist/commands/spec.d.ts +0 -85
  13. package/dist/commands/spec.js +0 -67
  14. package/dist/commands/telemetry.js +0 -4
  15. package/dist/commands/token.js +0 -28
  16. package/dist/commands/upgrade.js +0 -22
  17. package/dist/commands/whoami.d.ts +0 -12
  18. package/dist/commands/whoami.js +0 -6
  19. package/dist/index.d.ts +0 -188
  20. package/dist/index.js +0 -349
  21. package/dist/lib/api.d.ts +0 -78
  22. package/dist/lib/api.js +1 -320
  23. package/dist/lib/cdp-pipe.d.ts +31 -0
  24. package/dist/lib/cdp-pipe.js +141 -0
  25. package/dist/lib/chrome-discovery.d.ts +1 -0
  26. package/dist/lib/chrome-discovery.js +30 -0
  27. package/dist/lib/chrome-launch.d.ts +8 -0
  28. package/dist/lib/chrome-launch.js +53 -0
  29. package/dist/lib/config.d.ts +0 -53
  30. package/dist/lib/config.js +0 -55
  31. package/dist/lib/confirm.d.ts +0 -55
  32. package/dist/lib/confirm.js +0 -47
  33. package/dist/lib/docs.d.ts +0 -123
  34. package/dist/lib/docs.js +0 -169
  35. package/dist/lib/errors.d.ts +0 -134
  36. package/dist/lib/errors.js +0 -151
  37. package/dist/lib/format.d.ts +0 -6
  38. package/dist/lib/format.js +0 -6
  39. package/dist/lib/json.d.ts +0 -35
  40. package/dist/lib/json.js +0 -48
  41. package/dist/lib/jwt.d.ts +0 -7
  42. package/dist/lib/jwt.js +0 -7
  43. package/dist/lib/pinch.d.ts +0 -53
  44. package/dist/lib/pinch.js +6 -112
  45. package/dist/lib/pinchAnimation.d.ts +0 -16
  46. package/dist/lib/pinchAnimation.js +8 -29
  47. package/dist/lib/posthog.d.ts +0 -9
  48. package/dist/lib/posthog.js +0 -23
  49. package/dist/lib/prompt.js +1 -20
  50. package/dist/lib/secure-transport.d.ts +1 -0
  51. package/dist/lib/secure-transport.js +15 -0
  52. package/dist/lib/session-capture-guard.d.ts +6 -0
  53. package/dist/lib/session-capture-guard.js +9 -0
  54. package/dist/lib/session-capture.d.ts +55 -0
  55. package/dist/lib/session-capture.js +319 -0
  56. package/dist/lib/skills.d.ts +0 -175
  57. package/dist/lib/skills.js +1 -216
  58. package/dist/lib/skillsNudge.d.ts +0 -17
  59. package/dist/lib/skillsNudge.js +0 -83
  60. package/dist/lib/spinner.d.ts +0 -39
  61. package/dist/lib/spinner.js +0 -40
  62. package/dist/lib/storage-state.d.ts +55 -0
  63. package/dist/lib/storage-state.js +96 -0
  64. package/dist/lib/tips.d.ts +0 -38
  65. package/dist/lib/tips.js +0 -77
  66. package/dist/lib/updateCheckWorker.js +0 -14
  67. package/dist/lib/updateNotifier.d.ts +0 -17
  68. package/dist/lib/updateNotifier.js +0 -53
  69. package/dist/lib/validate.d.ts +0 -8
  70. package/dist/lib/validate.js +0 -8
  71. package/dist/lib/version.d.ts +0 -12
  72. package/dist/lib/version.js +1 -13
  73. 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
+ }
@@ -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;
@@ -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';
@@ -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;
@@ -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);
@@ -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;