@page-scanner/cli 0.1.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.
Files changed (75) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +217 -0
  3. package/dist/api.d.ts +87 -0
  4. package/dist/api.d.ts.map +1 -0
  5. package/dist/api.js +132 -0
  6. package/dist/api.js.map +1 -0
  7. package/dist/bin.d.ts +3 -0
  8. package/dist/bin.d.ts.map +1 -0
  9. package/dist/bin.js +49 -0
  10. package/dist/bin.js.map +1 -0
  11. package/dist/bridge/server.d.ts +97 -0
  12. package/dist/bridge/server.d.ts.map +1 -0
  13. package/dist/bridge/server.js +329 -0
  14. package/dist/bridge/server.js.map +1 -0
  15. package/dist/bridge/tokens.d.ts +10 -0
  16. package/dist/bridge/tokens.d.ts.map +1 -0
  17. package/dist/bridge/tokens.js +15 -0
  18. package/dist/bridge/tokens.js.map +1 -0
  19. package/dist/cli/args.d.ts +63 -0
  20. package/dist/cli/args.d.ts.map +1 -0
  21. package/dist/cli/args.js +388 -0
  22. package/dist/cli/args.js.map +1 -0
  23. package/dist/cli/commands.d.ts +8 -0
  24. package/dist/cli/commands.d.ts.map +1 -0
  25. package/dist/cli/commands.js +234 -0
  26. package/dist/cli/commands.js.map +1 -0
  27. package/dist/cli/format.d.ts +22 -0
  28. package/dist/cli/format.d.ts.map +1 -0
  29. package/dist/cli/format.js +152 -0
  30. package/dist/cli/format.js.map +1 -0
  31. package/dist/config.d.ts +17 -0
  32. package/dist/config.d.ts.map +1 -0
  33. package/dist/config.js +54 -0
  34. package/dist/config.js.map +1 -0
  35. package/dist/daemon/client.d.ts +28 -0
  36. package/dist/daemon/client.d.ts.map +1 -0
  37. package/dist/daemon/client.js +139 -0
  38. package/dist/daemon/client.js.map +1 -0
  39. package/dist/daemon/rpc-server.d.ts +31 -0
  40. package/dist/daemon/rpc-server.d.ts.map +1 -0
  41. package/dist/daemon/rpc-server.js +254 -0
  42. package/dist/daemon/rpc-server.js.map +1 -0
  43. package/dist/daemon/run.d.ts +20 -0
  44. package/dist/daemon/run.d.ts.map +1 -0
  45. package/dist/daemon/run.js +129 -0
  46. package/dist/daemon/run.js.map +1 -0
  47. package/dist/daemon/self.d.ts +25 -0
  48. package/dist/daemon/self.d.ts.map +1 -0
  49. package/dist/daemon/self.js +113 -0
  50. package/dist/daemon/self.js.map +1 -0
  51. package/dist/daemon/state.d.ts +25 -0
  52. package/dist/daemon/state.d.ts.map +1 -0
  53. package/dist/daemon/state.js +116 -0
  54. package/dist/daemon/state.js.map +1 -0
  55. package/dist/errors.d.ts +32 -0
  56. package/dist/errors.d.ts.map +1 -0
  57. package/dist/errors.js +83 -0
  58. package/dist/errors.js.map +1 -0
  59. package/dist/index.d.ts +17 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +16 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/output.d.ts +13 -0
  64. package/dist/output.d.ts.map +1 -0
  65. package/dist/output.js +71 -0
  66. package/dist/output.js.map +1 -0
  67. package/dist/protocol.d.ts +144 -0
  68. package/dist/protocol.d.ts.map +1 -0
  69. package/dist/protocol.js +128 -0
  70. package/dist/protocol.js.map +1 -0
  71. package/dist/version.d.ts +11 -0
  72. package/dist/version.d.ts.map +1 -0
  73. package/dist/version.js +11 -0
  74. package/dist/version.js.map +1 -0
  75. package/package.json +77 -0
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Turning results into the text a person reads.
3
+ *
4
+ * Every function here is pure: data in, a string out, no writing and no
5
+ * colour. A command can then be tested on what it computed rather than on what
6
+ * it printed, and the `--json` path skips this file entirely.
7
+ *
8
+ * The house style for the tables is "plain enough to pipe". No box drawing, no
9
+ * separator row, no pipes, and no trailing whitespace on any line, because the
10
+ * next thing to read this output is as likely to be `awk` as a person.
11
+ */
12
+ import type { ConnectedBrowser } from '../bridge/server.js';
13
+ import type { TabSummary, TruncationReport, WindowSummary } from '../protocol.js';
14
+ /** A plain column-aligned table. Returns '' for an empty rows array. */
15
+ export declare function table(headers: string[], rows: string[][]): string;
16
+ export declare function formatBrowsers(browsers: ConnectedBrowser[]): string;
17
+ export declare function formatTabs(windows: WindowSummary[], tabs: TabSummary[]): string;
18
+ /** One line, for stderr, when a capture stopped short of the whole page. */
19
+ export declare function formatTruncation(report: TruncationReport): string;
20
+ /** e.g. "3s", "2m 04s". */
21
+ export declare function formatDuration(ms: number): string;
22
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/cli/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAiBlF,wEAAwE;AACxE,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,MAAM,CA+BjE;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAWnE;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,MAAM,CAmC/E;AAED,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CASjE;AAED,2BAA2B;AAC3B,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CASjD"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * How much room a tab title gets.
3
+ *
4
+ * The terminal's real width is deliberately not consulted. A piped run has no
5
+ * width to read (`process.stdout.columns` is undefined when stdout is not a
6
+ * TTY), so the choice would be between one layout when a person looks at it
7
+ * and another when a script does, which is both harder to read and harder to
8
+ * assert on. 60 is a budget in code points, not in terminal columns: a title
9
+ * of CJK or emoji occupies up to twice its length on screen, so the 80-column
10
+ * claim below holds for a Latin title and is a guideline for anything else.
11
+ * Measuring real display width would mean carrying an East Asian Width table,
12
+ * which is not worth it to keep a tab listing tidy.
13
+ */
14
+ const TITLE_WIDTH = 60;
15
+ /** A plain column-aligned table. Returns '' for an empty rows array. */
16
+ export function table(headers, rows) {
17
+ if (rows.length === 0)
18
+ return '';
19
+ // A row is allowed to be shorter or longer than the header row, so the
20
+ // column count is whatever the widest thing passed in has.
21
+ const columns = Math.max(headers.length, ...rows.map((row) => row.length));
22
+ const widths = [];
23
+ for (let index = 0; index < columns; index += 1) {
24
+ let width = headers[index]?.length ?? 0;
25
+ for (const row of rows)
26
+ width = Math.max(width, row[index]?.length ?? 0);
27
+ widths[index] = width;
28
+ }
29
+ const line = (cells) => {
30
+ const padded = [];
31
+ for (let index = 0; index < columns; index += 1) {
32
+ const cell = cells[index] ?? '';
33
+ // The last column is never padded, and the line is trimmed at the end on
34
+ // top of that: a row whose tail cells are empty must not leave invisible
35
+ // whitespace behind for whatever is reading the pipe.
36
+ padded.push(index === columns - 1 ? cell : cell.padEnd(widths[index] ?? 0));
37
+ }
38
+ return padded.join(' ').trimEnd();
39
+ };
40
+ // A caller that passes no headers, or only blank ones, wants rows alone
41
+ // rather than a leading empty line.
42
+ const body = rows.map(line);
43
+ return headers.some((header) => header.length > 0)
44
+ ? [line(headers), ...body].join('\n')
45
+ : body.join('\n');
46
+ }
47
+ export function formatBrowsers(browsers) {
48
+ if (browsers.length === 0)
49
+ return 'No browser is connected.';
50
+ return table(['LABEL', 'BROWSER ID', 'VERSION', 'CONNECTED'], browsers.map((browser) => [
51
+ browser.label,
52
+ browser.browserId,
53
+ browser.extensionVersion,
54
+ formatDuration(browser.connectedForMs),
55
+ ]));
56
+ }
57
+ export function formatTabs(windows, tabs) {
58
+ if (windows.length === 0 && tabs.length === 0)
59
+ return 'No tabs are open.';
60
+ // Seed the groups from the window list so a window with nothing in it still
61
+ // shows up, then file each tab. A tab whose window was not listed gets a
62
+ // group of its own rather than being dropped: the two lists come off the
63
+ // wire separately and the tab is the thing the user asked for.
64
+ const grouped = new Map();
65
+ for (const window of windows)
66
+ grouped.set(window.windowId, []);
67
+ for (const tab of tabs) {
68
+ const group = grouped.get(tab.windowId);
69
+ if (group)
70
+ group.push(tab);
71
+ else
72
+ grouped.set(tab.windowId, [tab]);
73
+ }
74
+ const summaries = new Map(windows.map((window) => [window.windowId, window]));
75
+ const blocks = [];
76
+ for (const [windowId, group] of grouped) {
77
+ const lines = [windowHeading(windowId, summaries.get(windowId), group.length)];
78
+ const body = table(['', 'TAB', 'TITLE', 'URL'], group.map((tab) => [
79
+ tab.active ? '*' : '',
80
+ String(tab.tabId),
81
+ truncate(tab.title.trim() || '(untitled)', TITLE_WIDTH),
82
+ // The URL is the last column and is never cut. It is the one cell a
83
+ // person is likely to copy, and a shortened one is worse than a long
84
+ // line the terminal wraps itself.
85
+ tab.url,
86
+ ]));
87
+ if (body)
88
+ lines.push(body);
89
+ blocks.push(lines.join('\n'));
90
+ }
91
+ return blocks.join('\n\n');
92
+ }
93
+ /** One line, for stderr, when a capture stopped short of the whole page. */
94
+ export function formatTruncation(report) {
95
+ const measured = `${px(report.requestedWidth)}x${px(report.requestedHeight)}`;
96
+ const captured = `${px(report.width)}x${px(report.height)}`;
97
+ const head = `Truncated: the page measured ${measured} CSS px, captured ${captured}.`;
98
+ // The sentence is quoted, not paraphrased. The extension owns the ceiling
99
+ // that caused this and is the only side that knows what it is, so restating
100
+ // it here would be a second copy of a limit that lives over there.
101
+ const message = report.message.trim();
102
+ return message ? `${head} ${message}` : head;
103
+ }
104
+ /** e.g. "3s", "2m 04s". */
105
+ export function formatDuration(ms) {
106
+ if (!Number.isFinite(ms) || ms <= 0)
107
+ return '0s';
108
+ const total = Math.round(ms / 1000);
109
+ if (total < 60)
110
+ return `${total}s`;
111
+ const minutes = Math.floor(total / 60);
112
+ if (minutes < 60)
113
+ return `${minutes}m ${pad2(total % 60)}s`;
114
+ // A browser left connected overnight would otherwise read as "734m 09s".
115
+ // Past an hour the seconds carry no information anyone acts on.
116
+ return `${Math.floor(minutes / 60)}h ${pad2(minutes % 60)}m`;
117
+ }
118
+ function windowHeading(windowId, summary, shown) {
119
+ const parts = [];
120
+ if (summary?.focused)
121
+ parts.push('focused');
122
+ if (summary && summary.windowType)
123
+ parts.push(summary.windowType);
124
+ // The window's own figure, which the extension builds in the same loop that
125
+ // produces the rows, so it should equal them. Printing it rather than the
126
+ // row count means a disagreement shows up here instead of being hidden.
127
+ const count = summary?.tabCount ?? shown;
128
+ parts.push(count === 1 ? '1 tab' : `${count} tabs`);
129
+ return `Window ${windowId} (${parts.join(', ')})`;
130
+ }
131
+ function truncate(text, width) {
132
+ // Cut by code point, not by `slice`. A page title with an emoji in it is
133
+ // ordinary, and slicing UTF-16 code units can land between a surrogate pair
134
+ // and emit a lone surrogate, which a terminal draws as a replacement glyph.
135
+ const characters = Array.from(text);
136
+ if (characters.length <= width)
137
+ return text;
138
+ // One character short of the width, so the ellipsis lands inside it, and
139
+ // trimmed so it is not hung off the end of a space.
140
+ return `${characters
141
+ .slice(0, width - 1)
142
+ .join('')
143
+ .trimEnd()}…`;
144
+ }
145
+ /** A CSS pixel measurement can be fractional; nobody wants to read that. */
146
+ function px(value) {
147
+ return String(Math.round(value));
148
+ }
149
+ function pad2(value) {
150
+ return String(value).padStart(2, '0');
151
+ }
152
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/cli/format.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ export declare const DEFAULT_PORT = 45711;
2
+ export interface PairingConfig {
3
+ port: number;
4
+ token: string;
5
+ }
6
+ /** `PAGE_SCANNER_HOME` exists so a test can point the whole thing at a tmpdir. */
7
+ export declare function configDir(): string;
8
+ export declare function configPath(): string;
9
+ export declare function readConfig(): PairingConfig;
10
+ /** Creates a pairing if there is none, and returns whatever is now on disk. */
11
+ export declare function ensureConfig({ port, rotate, }?: {
12
+ port?: number;
13
+ rotate?: boolean;
14
+ }): PairingConfig;
15
+ /** True on every platform that actually enforces the 0600 above. */
16
+ export declare function configFileIsOwnerOnly(): boolean;
17
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,YAAY,QAAQ,CAAC;AAElC,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,kFAAkF;AAClF,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,UAAU,IAAI,aAAa,CAW1C;AAED,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,MAAc,GACf,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,aAAa,CAU1D;AAED,oEAAoE;AACpE,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C"}
package/dist/config.js ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Where the pairing lives: `~/.page-scanner/config.json`, mode 0600.
3
+ *
4
+ * The token is the only thing standing between a local agent and a scan of any
5
+ * tab the user has open, so the file is owner-readable and nothing else. On
6
+ * Windows that mode is a no-op: NTFS does not implement it, and the file is
7
+ * readable by anything running as the user. The README says so rather than the
8
+ * `pair` output claiming a guarantee the platform does not give.
9
+ *
10
+ * The shape is the one page-scanner-mcp 0.1 wrote, so a browser paired against
11
+ * that version keeps working.
12
+ */
13
+ import { randomBytes } from 'node:crypto';
14
+ import { chmodSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
15
+ import { homedir } from 'node:os';
16
+ import { join } from 'node:path';
17
+ export const DEFAULT_PORT = 45711;
18
+ /** `PAGE_SCANNER_HOME` exists so a test can point the whole thing at a tmpdir. */
19
+ export function configDir() {
20
+ return process.env.PAGE_SCANNER_HOME ?? join(homedir(), '.page-scanner');
21
+ }
22
+ export function configPath() {
23
+ return join(configDir(), 'config.json');
24
+ }
25
+ export function readConfig() {
26
+ try {
27
+ const raw = JSON.parse(readFileSync(configPath(), 'utf8'));
28
+ const record = typeof raw === 'object' && raw !== null ? raw : {};
29
+ return {
30
+ port: Number(record.port) || DEFAULT_PORT,
31
+ token: typeof record.token === 'string' ? record.token : '',
32
+ };
33
+ }
34
+ catch {
35
+ return { port: DEFAULT_PORT, token: '' };
36
+ }
37
+ }
38
+ /** Creates a pairing if there is none, and returns whatever is now on disk. */
39
+ export function ensureConfig({ port, rotate = false, } = {}) {
40
+ const current = readConfig();
41
+ const next = {
42
+ port: port ?? current.port,
43
+ token: rotate || !current.token ? randomBytes(32).toString('base64url') : current.token,
44
+ };
45
+ mkdirSync(configDir(), { recursive: true, mode: 0o700 });
46
+ writeFileSync(configPath(), `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
47
+ chmodSync(configPath(), 0o600);
48
+ return next;
49
+ }
50
+ /** True on every platform that actually enforces the 0600 above. */
51
+ export function configFileIsOwnerOnly() {
52
+ return process.platform !== 'win32';
53
+ }
54
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,CAAC;AAOlC,kFAAkF;AAClF,MAAM,UAAU,SAAS;IACvB,OAAO,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,eAAe,CAAC,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,aAAa,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,GAAY,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;QACpE,MAAM,MAAM,GAAG,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC,CAAE,GAA+B,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/F,OAAO;YACL,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,YAAY;YACzC,KAAK,EAAE,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;SAC5D,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAC3C,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,YAAY,CAAC,EAC3B,IAAI,EACJ,MAAM,GAAG,KAAK,MACyB,EAAE;IACzC,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,IAAI,GAAkB;QAC1B,IAAI,EAAE,IAAI,IAAI,OAAO,CAAC,IAAI;QAC1B,KAAK,EAAE,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK;KACxF,CAAC;IACF,SAAS,CAAC,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACzD,aAAa,CAAC,UAAU,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACnF,SAAS,CAAC,UAAU,EAAE,EAAE,KAAK,CAAC,CAAC;IAC/B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,qBAAqB;IACnC,OAAO,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;AACtC,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { type DaemonState } from './state.js';
2
+ /** How long to wait for a freshly spawned daemon to publish its state and answer. */
3
+ export declare const DAEMON_START_TIMEOUT_MS = 5000;
4
+ export interface DaemonClient {
5
+ state: DaemonState;
6
+ call<T>(method: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<T>;
7
+ }
8
+ export interface ConnectOptions {
9
+ /**
10
+ * Starts the daemon. Injectable so a test can assert the spawn decision
11
+ * without putting a process on the machine.
12
+ */
13
+ startDaemon?: () => void;
14
+ startTimeoutMs?: number;
15
+ /** Injectable so a test does not wait in real time. */
16
+ sleep?: (ms: number) => Promise<void>;
17
+ }
18
+ /**
19
+ * Returns a client for a running daemon, starting one if necessary.
20
+ *
21
+ * A daemon left behind by an older install is shut down rather than reused:
22
+ * its RPC surface may not be the one this process expects, and the failure
23
+ * that produces is much harder to read than a restart.
24
+ */
25
+ export declare function connectDaemon(options?: ConnectOptions): Promise<DaemonClient>;
26
+ /** Returns true when a daemon was running and has been told to stop. */
27
+ export declare function stopDaemon(): Promise<boolean>;
28
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/daemon/client.ts"],"names":[],"mappings":"AAeA,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAEpB,qFAAqF;AACrF,eAAO,MAAM,uBAAuB,OAAQ,CAAC;AAG7C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,WAAW,CAAC;IACnB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC3F;AAED,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uDAAuD;IACvD,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AA4FD;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,YAAY,CAAC,CAoCvF;AAED,wEAAwE;AACxE,wBAAsB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAkBnD"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Talking to the daemon, and starting one if there is none.
3
+ *
4
+ * Every command except `serve` goes through here. The daemon exists because
5
+ * the extension is the WebSocket *client*: something has to be listening
6
+ * before Chrome can dial in, and a one-shot listener per command would mean
7
+ * the browser reconnecting for every invocation, which takes seconds and
8
+ * wakes a service worker that has just been retired.
9
+ */
10
+ import { spawn } from 'node:child_process';
11
+ import { openSync } from 'node:fs';
12
+ import { PageScannerError, toPageScannerError } from '../errors.js';
13
+ import { CLI_VERSION } from '../version.js';
14
+ import { selfCommand } from './self.js';
15
+ import { clearDaemonState, daemonLogPath, processAlive, readDaemonState, } from './state.js';
16
+ /** How long to wait for a freshly spawned daemon to publish its state and answer. */
17
+ export const DAEMON_START_TIMEOUT_MS = 5_000;
18
+ const POLL_INTERVAL_MS = 100;
19
+ const realSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
20
+ function clientFor(state) {
21
+ return {
22
+ state,
23
+ async call(method, params = {}, timeoutMs = 130_000) {
24
+ let response;
25
+ try {
26
+ response = await fetch(`http://127.0.0.1:${state.rpcPort}/rpc`, {
27
+ method: 'POST',
28
+ headers: {
29
+ 'content-type': 'application/json',
30
+ authorization: `Bearer ${state.secret}`,
31
+ },
32
+ body: JSON.stringify({ method, params }),
33
+ // The daemon long-polls a scan, so the client's deadline is the real
34
+ // one. Five seconds of slack over the daemon's own timeout, so its
35
+ // considered answer wins the race against this abort.
36
+ signal: AbortSignal.timeout(timeoutMs + 5_000),
37
+ });
38
+ }
39
+ catch (error) {
40
+ throw new PageScannerError('DAEMON_FAILED', `The daemon on port ${state.rpcPort} did not answer: ${error instanceof Error ? error.message : String(error)}`, 'Run `page-scanner stop` and try again.');
41
+ }
42
+ if (response.status === 401) {
43
+ throw new PageScannerError('DAEMON_FAILED', 'The daemon rejected this process. Its secret changed under us.', 'Run `page-scanner stop` and try again.');
44
+ }
45
+ const envelope = (await response.json());
46
+ if (envelope.ok)
47
+ return envelope.result;
48
+ throw new PageScannerError(envelope.error?.code ?? 'SCAN_FAILED', envelope.error?.message ?? 'The daemon failed without saying why.', envelope.error?.hint);
49
+ },
50
+ };
51
+ }
52
+ /** A cheap liveness probe that never throws. */
53
+ async function healthy(state) {
54
+ try {
55
+ return await clientFor(state).call('health', {}, 2_000);
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
61
+ /** Spawns this same program as `serve --daemon`, detached, and lets go of it. */
62
+ function spawnDaemon() {
63
+ const { command, args } = selfCommand();
64
+ let log;
65
+ try {
66
+ log = openSync(daemonLogPath(), 'a');
67
+ }
68
+ catch (error) {
69
+ throw new PageScannerError('DAEMON_FAILED', `Could not open the daemon log at ${daemonLogPath()}: ${error instanceof Error ? error.message : String(error)}`);
70
+ }
71
+ const child = spawn(command, [...args, 'serve', '--daemon'], {
72
+ detached: true,
73
+ stdio: ['ignore', log, log],
74
+ windowsHide: true,
75
+ });
76
+ child.unref();
77
+ }
78
+ /**
79
+ * Returns a client for a running daemon, starting one if necessary.
80
+ *
81
+ * A daemon left behind by an older install is shut down rather than reused:
82
+ * its RPC surface may not be the one this process expects, and the failure
83
+ * that produces is much harder to read than a restart.
84
+ */
85
+ export async function connectDaemon(options = {}) {
86
+ const sleep = options.sleep ?? realSleep;
87
+ const start = options.startDaemon ?? spawnDaemon;
88
+ const deadlineMs = options.startTimeoutMs ?? DAEMON_START_TIMEOUT_MS;
89
+ const existing = readDaemonState();
90
+ if (existing) {
91
+ const health = await healthy(existing);
92
+ if (health && health.version === CLI_VERSION)
93
+ return clientFor(existing);
94
+ if (health) {
95
+ // Same machine, different version of this CLI. Replace it.
96
+ try {
97
+ await clientFor(existing).call('shutdown', {}, 2_000);
98
+ }
99
+ catch {
100
+ // It is going away either way.
101
+ }
102
+ await sleep(POLL_INTERVAL_MS);
103
+ }
104
+ clearDaemonState();
105
+ }
106
+ start();
107
+ for (let waited = 0; waited < deadlineMs; waited += POLL_INTERVAL_MS) {
108
+ await sleep(POLL_INTERVAL_MS);
109
+ const state = readDaemonState();
110
+ if (!state)
111
+ continue;
112
+ const health = await healthy(state);
113
+ if (health)
114
+ return clientFor(state);
115
+ }
116
+ throw new PageScannerError('DAEMON_FAILED', `The daemon did not come up within ${Math.round(deadlineMs / 1000)}s.`, `Run \`page-scanner serve\` in another terminal to see why, or read ${daemonLogPath()}.`);
117
+ }
118
+ /** Returns true when a daemon was running and has been told to stop. */
119
+ export async function stopDaemon() {
120
+ const state = readDaemonState();
121
+ if (!state) {
122
+ clearDaemonState();
123
+ return false;
124
+ }
125
+ try {
126
+ await clientFor(state).call('shutdown', {}, 2_000);
127
+ return true;
128
+ }
129
+ catch (error) {
130
+ // It may have died without cleaning up after itself. Only report a
131
+ // failure if the process is genuinely still there.
132
+ if (!processAlive(state.pid)) {
133
+ clearDaemonState();
134
+ return false;
135
+ }
136
+ throw toPageScannerError(error);
137
+ }
138
+ }
139
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/daemon/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { type ErrorCode } from '../errors.js';
2
+ import type { BridgeServer } from '../bridge/server.js';
3
+ export interface RpcSuccess {
4
+ ok: true;
5
+ result: unknown;
6
+ }
7
+ export interface RpcFailure {
8
+ ok: false;
9
+ error: {
10
+ code: ErrorCode;
11
+ message: string;
12
+ hint?: string;
13
+ };
14
+ }
15
+ export type RpcResponse = RpcSuccess | RpcFailure;
16
+ export interface RpcServerHandle {
17
+ port: number;
18
+ close(): Promise<void>;
19
+ }
20
+ export interface RpcServerOptions {
21
+ bridge: BridgeServer;
22
+ secret: string;
23
+ /** Called when a `shutdown` request arrives, after it has been answered. */
24
+ onShutdown: () => void;
25
+ /** Called on every authenticated request, so the daemon can stay awake. */
26
+ onActivity?: () => void;
27
+ /** 0 asks the OS for a free port, which is the normal case. */
28
+ port?: number;
29
+ }
30
+ export declare function startRpcServer(options: RpcServerOptions): Promise<RpcServerHandle>;
31
+ //# sourceMappingURL=rpc-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-server.d.ts","sourceRoot":"","sources":["../../src/daemon/rpc-server.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAUlE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAMxD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5D;AAED,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,UAAU,CAAC;AAElD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAoMD,wBAAgB,cAAc,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CA4BlF"}