@trawlme/cli 3.10.0 → 3.12.0
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/scraps.js +250 -10
- package/dist/commands/spec.d.ts +32 -1
- package/dist/commands/spec.js +83 -11
- package/dist/index.d.ts +17 -0
- package/dist/index.js +39 -0
- package/dist/lib/api.d.ts +5 -0
- package/dist/lib/api.js +118 -0
- package/dist/lib/cdp-pipe.d.ts +103 -0
- package/dist/lib/cdp-pipe.js +221 -0
- package/dist/lib/chrome-discovery.d.ts +12 -0
- package/dist/lib/chrome-discovery.js +49 -0
- package/dist/lib/chrome-launch.d.ts +48 -0
- package/dist/lib/chrome-launch.js +122 -0
- package/dist/lib/docs.d.ts +140 -0
- package/dist/lib/docs.js +238 -0
- package/dist/lib/secure-transport.d.ts +8 -0
- package/dist/lib/secure-transport.js +39 -0
- package/dist/lib/session-capture-guard.d.ts +21 -0
- package/dist/lib/session-capture-guard.js +14 -0
- package/dist/lib/session-capture.d.ts +180 -0
- package/dist/lib/session-capture.js +600 -0
- package/dist/lib/storage-state.d.ts +167 -0
- package/dist/lib/storage-state.js +227 -0
- package/docs/agent-quickstart.md +9 -1
- package/package.json +1 -1
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { ChildProcess } from 'node:child_process';
|
|
2
|
+
import type { CdpPipe } from './cdp-pipe.js';
|
|
3
|
+
import { type RawCdpCookie, type RawOriginLocalStorage, type StorageState } from './storage-state.js';
|
|
4
|
+
export interface CaptureCounts {
|
|
5
|
+
cookiesCaptured: number;
|
|
6
|
+
cookiesDroppedOutOfScope: number;
|
|
7
|
+
cookiesDroppedInvalid: number;
|
|
8
|
+
originsCaptured: number;
|
|
9
|
+
originsDroppedOutOfScope: number;
|
|
10
|
+
/** An in-scope page's localStorage could not be captured, for one of
|
|
11
|
+
* four reasons — never a CLI bug, which propagates as a failed capture
|
|
12
|
+
* instead (trawl_cli#183 post-cap review finding B, sharpened by the R4
|
|
13
|
+
* fix below): a `readPageOverCdp` rejection — ANY CDP-level rejection
|
|
14
|
+
* scoped to THIS page's `attachToTarget`/`Runtime.evaluate` round-trip,
|
|
15
|
+
* whatever its error class (a timeout because the page's renderer was
|
|
16
|
+
* blocked — a native dialog, a synchronous script, a paused debugger; or
|
|
17
|
+
* a plain rejection because the target closed mid-attach/mid-evaluate,
|
|
18
|
+
* e.g. an OAuth popup the human closed at the wrong instant); the read
|
|
19
|
+
* threw INSIDE the page (e.g. a `SecurityError` on partitioned/sandboxed
|
|
20
|
+
* storage); or `Runtime.evaluate` succeeded with no exception but
|
|
21
|
+
* returned a value that doesn't match the `{name,value}[]` shape asked
|
|
22
|
+
* for (a page tampering with a global to hand back garbage without ever
|
|
23
|
+
* throwing). The discriminant is PROVENANCE, not error type: did the
|
|
24
|
+
* rejection come out of a CDP call scoped to this one page, or out of
|
|
25
|
+
* this file's OWN code running on data CDP already handed back? Only the
|
|
26
|
+
* latter is a CLI bug, and only the latter propagates. None of these
|
|
27
|
+
* four is the same fact as "0 keys": CDP itself was reachable (or the
|
|
28
|
+
* pipe wouldn't still be open), the origin just could not be read.
|
|
29
|
+
* Counted, never the exception text/URL/value itself (a page controls
|
|
30
|
+
* that content). */
|
|
31
|
+
originsUnreadable: number;
|
|
32
|
+
/** The tracked tab was closed before Enter — cookies were still read,
|
|
33
|
+
* localStorage was not (the completion path the issue calls out:
|
|
34
|
+
* "closing the window also completes"). */
|
|
35
|
+
closedEarly: boolean;
|
|
36
|
+
}
|
|
37
|
+
export type CaptureFailureReason = 'non_interactive' | 'no_chrome' | 'launch_failed' | 'capture_failed' | 'process_exited_before_capture' | 'cdp_protocol_error' | 'no_cookies_in_scope';
|
|
38
|
+
export type CaptureResult = {
|
|
39
|
+
ok: true;
|
|
40
|
+
storageState: StorageState;
|
|
41
|
+
counts: CaptureCounts;
|
|
42
|
+
targetDomain: string;
|
|
43
|
+
} | {
|
|
44
|
+
ok: false;
|
|
45
|
+
reason: CaptureFailureReason;
|
|
46
|
+
message: string;
|
|
47
|
+
};
|
|
48
|
+
interface RawCapture {
|
|
49
|
+
cookies: RawCdpCookie[];
|
|
50
|
+
origins: RawOriginLocalStorage[];
|
|
51
|
+
originsUnreadable: number;
|
|
52
|
+
}
|
|
53
|
+
/** Every effectful step captureSession needs — overridable for tests. */
|
|
54
|
+
export interface CaptureDeps {
|
|
55
|
+
detectNonInteractive(): string | null;
|
|
56
|
+
findChrome(): string | null;
|
|
57
|
+
mkdtemp(): string;
|
|
58
|
+
rmSync(dir: string): void;
|
|
59
|
+
launch(chromePath: string, targetUrl: string, userDataDir: string): Promise<{
|
|
60
|
+
proc: ChildProcess;
|
|
61
|
+
cdp: CdpPipe;
|
|
62
|
+
}>;
|
|
63
|
+
/** Resolve once Chrome has opened the target URL as a page target. */
|
|
64
|
+
findPageTarget(cdp: CdpPipe): Promise<string>;
|
|
65
|
+
/**
|
|
66
|
+
* Race the "done" signal: Enter in the terminal, or the tracked tab
|
|
67
|
+
* being closed. Resolves with whether the tab closed before Enter.
|
|
68
|
+
* `cleanup` MUST be wired to the readline Interface's own `'SIGINT'`
|
|
69
|
+
* event (not just `process.on('SIGINT', …)`) — a TTY readline prompt
|
|
70
|
+
* puts stdin in raw mode, which stops Ctrl-C from ever generating a real
|
|
71
|
+
* OS SIGINT in the first place; only the Interface itself observes the
|
|
72
|
+
* raw 0x03 byte and re-synthesizes it as its own `'SIGINT'` event.
|
|
73
|
+
*/
|
|
74
|
+
waitForDone(cdp: CdpPipe, targetId: string, cleanup: () => void): Promise<{
|
|
75
|
+
closedEarly: boolean;
|
|
76
|
+
}>;
|
|
77
|
+
/** Read the full (unscoped) cookie jar + open pages' localStorage. */
|
|
78
|
+
readCapture(cdp: CdpPipe, targetUrl: string, closedEarly: boolean): Promise<RawCapture>;
|
|
79
|
+
/**
|
|
80
|
+
* Register `cleanup` to run on every ORDINARY terminating signal this
|
|
81
|
+
* process can still run code for: SIGINT, SIGTERM, and SIGHUP
|
|
82
|
+
* (trawl_cli#183 review finding 1 — SIGTERM/SIGHUP were missing
|
|
83
|
+
* entirely, so `timeout` without `--signal`, a bare `kill <pid>`, most
|
|
84
|
+
* supervisors, and closing the terminal tab mid-login all skipped
|
|
85
|
+
* cleanup and left Chrome running with the profile on disk). SIGKILL is
|
|
86
|
+
* the only signal that cannot be handled at all, by any process, ever —
|
|
87
|
+
* see this function's own default implementation
|
|
88
|
+
* (`registerTerminationHandlers`) for that boundary spelled out.
|
|
89
|
+
* Returns an unregister function.
|
|
90
|
+
*/
|
|
91
|
+
onSigint(cleanup: () => void): () => void;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The three real (non-fake) CDP orchestration steps, exported for direct
|
|
95
|
+
* testing against a minimal fake CdpPipe — session-capture.test.ts covers
|
|
96
|
+
* the OUTER guard/race/cleanup logic with these swapped out entirely;
|
|
97
|
+
* session-capture-defaults.test.ts covers these directly instead, since
|
|
98
|
+
* "test the wiring" and "test the wired thing" are two different jobs.
|
|
99
|
+
*/
|
|
100
|
+
export declare function findPageTargetDefault(cdp: CdpPipe): Promise<string>;
|
|
101
|
+
export declare function waitForDoneDefault(cdp: CdpPipe, targetId: string, cleanup: () => void): Promise<{
|
|
102
|
+
closedEarly: boolean;
|
|
103
|
+
}>;
|
|
104
|
+
/**
|
|
105
|
+
* @desc The deadline for the ONE CDP command in this file that runs inside
|
|
106
|
+
* a page's own renderer rather than Chrome's browser process:
|
|
107
|
+
* `Runtime.evaluate` reading `window.localStorage`. Deliberately the SAME
|
|
108
|
+
* 30s as `CdpPipe`'s own browser-process default, not a shorter override —
|
|
109
|
+
* a post-cap review (trawl_cli#183) found that an earlier 5s value here cut
|
|
110
|
+
* off a real, terminating synchronous computation (the shape of an
|
|
111
|
+
* anti-bot challenge solve) at ~5s: `originsUnreadable:1` for a page that
|
|
112
|
+
* would have captured cleanly at 7s. There is no value that is provably
|
|
113
|
+
* "long enough" — a renderer truly blocked forever (a native dialog, a
|
|
114
|
+
* paused debugger) costs the same whether the ceiling is 5s or 30s, while
|
|
115
|
+
* a renderer doing bounded work of unknown-but-finite length keeps a
|
|
116
|
+
* chance of finishing for as long as this stays generous. 30s is a
|
|
117
|
+
* JUDGEMENT call on that trade-off, not a provably-correct number — kept
|
|
118
|
+
* equal to the pipe's own default so this file doesn't invent a second
|
|
119
|
+
* number to defend. Passed explicitly to `send()` anyway (rather than
|
|
120
|
+
* relying on the pipe's own default silently matching) so the deadline
|
|
121
|
+
* stays a named, assertable constant here regardless of what the pipe
|
|
122
|
+
* default happens to be. Whatever the outcome, it is always COUNTED and
|
|
123
|
+
* SURFACED (`originsUnreadable`, plus the human-facing ⚠ line in
|
|
124
|
+
* scraps.ts) — never silently swallowed; see `readCaptureDefault` below
|
|
125
|
+
* for the stderr progress line that covers the wait itself.
|
|
126
|
+
*/
|
|
127
|
+
export declare const LOCALSTORAGE_READ_TIMEOUT_MS = 30000;
|
|
128
|
+
export declare function readCaptureDefault(cdp: CdpPipe, targetUrl: string, closedEarly: boolean): Promise<RawCapture>;
|
|
129
|
+
/**
|
|
130
|
+
* @desc `rmSync` with a short, bounded, SYNCHRONOUS retry (max ~200ms
|
|
131
|
+
* total). Verified against a real Chrome launch (trawl_cli#183's loopback
|
|
132
|
+
* E2E): even after killing Chrome's whole process group (see
|
|
133
|
+
* chrome-launch.ts's `detached`), a helper subprocess can hold a file
|
|
134
|
+
* under the profile open for a few milliseconds after the kill signal is
|
|
135
|
+
* delivered — a bare `rmSync` right after `kill()` lost that race and
|
|
136
|
+
* silently leaked the temp profile every time. Stays synchronous
|
|
137
|
+
* (`Atomics.wait`, no `await`) because cleanup must be callable from a
|
|
138
|
+
* signal handler right before `process.exit()`.
|
|
139
|
+
*/
|
|
140
|
+
export declare function rmSyncWithRetry(dir: string): void;
|
|
141
|
+
/**
|
|
142
|
+
* @desc The default `onSigint` implementation, exported for direct testing
|
|
143
|
+
* (same pattern as `findPageTargetDefault`/`waitForDoneDefault`/
|
|
144
|
+
* `readCaptureDefault` above — this file's own convention keeps "test the
|
|
145
|
+
* wiring" and "test the wired thing" separate). Registers `cleanup` on
|
|
146
|
+
* SIGINT, SIGTERM, AND SIGHUP (trawl_cli#183 review finding 1 — only
|
|
147
|
+
* SIGINT was registered before this; `timeout` without `--signal`, a bare
|
|
148
|
+
* `kill <pid>`, most supervisors, and closing the terminal tab mid-login
|
|
149
|
+
* all default-terminate a process via SIGTERM/SIGHUP, and Node runs no
|
|
150
|
+
* `finally` for a default-terminated process). SIGKILL is the one signal
|
|
151
|
+
* this — or any handler, in any process — cannot observe: the OS tears
|
|
152
|
+
* the process down directly, no userspace code runs at all, so Chrome and
|
|
153
|
+
* the temp profile are left behind whenever that specific signal is what
|
|
154
|
+
* ends this process. That gap is inherent, not something a different
|
|
155
|
+
* signal list here could close.
|
|
156
|
+
*/
|
|
157
|
+
export declare function registerTerminationHandlers(cleanup: () => void): () => void;
|
|
158
|
+
/**
|
|
159
|
+
* @desc The same guard `captureSession` runs internally as its very first
|
|
160
|
+
* step, exposed so the command layer can call it BEFORE printing anything
|
|
161
|
+
* about a Chrome window that may never open. Without this, `scraps.ts`
|
|
162
|
+
* printed its "a Chrome window will open" banner unconditionally, ahead of
|
|
163
|
+
* this exact check inside `captureSession` — so a non-interactive run saw
|
|
164
|
+
* that banner immediately followed by "this cannot work headless"
|
|
165
|
+
* (trawl_cli#183 gap). Reads live process state once; pure
|
|
166
|
+
* `detectNonInteractive` stays the single source of truth for both call
|
|
167
|
+
* sites.
|
|
168
|
+
* @returns {string | null} a factual reason when this command cannot
|
|
169
|
+
* proceed in the current environment, or null when it can.
|
|
170
|
+
*/
|
|
171
|
+
export declare function checkInteractiveEnvironment(): string | null;
|
|
172
|
+
/**
|
|
173
|
+
* @desc Run the full capture flow for one scrap's target URL.
|
|
174
|
+
* @param {string} targetUrl
|
|
175
|
+
* @param {Partial<CaptureDeps>} depsOverride — for tests; real deps fill in
|
|
176
|
+
* the rest.
|
|
177
|
+
* @returns {Promise<CaptureResult>}
|
|
178
|
+
*/
|
|
179
|
+
export declare function captureSession(targetUrl: string, depsOverride?: Partial<CaptureDeps>): Promise<CaptureResult>;
|
|
180
|
+
export {};
|