@peekdev/cli 0.1.0-alpha.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 (74) hide show
  1. package/NOTICE +8 -0
  2. package/dist/commands/audit.d.ts +3 -0
  3. package/dist/commands/audit.d.ts.map +1 -0
  4. package/dist/commands/audit.js +96 -0
  5. package/dist/commands/audit.js.map +1 -0
  6. package/dist/commands/init.d.ts +3 -0
  7. package/dist/commands/init.d.ts.map +1 -0
  8. package/dist/commands/init.js +180 -0
  9. package/dist/commands/init.js.map +1 -0
  10. package/dist/commands/sessions.d.ts +3 -0
  11. package/dist/commands/sessions.d.ts.map +1 -0
  12. package/dist/commands/sessions.js +214 -0
  13. package/dist/commands/sessions.js.map +1 -0
  14. package/dist/commands/status.d.ts +2 -0
  15. package/dist/commands/status.d.ts.map +1 -0
  16. package/dist/commands/status.js +88 -0
  17. package/dist/commands/status.js.map +1 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +78 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/lib/audit.d.ts +47 -0
  23. package/dist/lib/audit.d.ts.map +1 -0
  24. package/dist/lib/audit.js +68 -0
  25. package/dist/lib/audit.js.map +1 -0
  26. package/dist/lib/db.d.ts +86 -0
  27. package/dist/lib/db.d.ts.map +1 -0
  28. package/dist/lib/db.js +117 -0
  29. package/dist/lib/db.js.map +1 -0
  30. package/dist/lib/duration.d.ts +14 -0
  31. package/dist/lib/duration.d.ts.map +1 -0
  32. package/dist/lib/duration.js +41 -0
  33. package/dist/lib/duration.js.map +1 -0
  34. package/dist/lib/format/index.d.ts +16 -0
  35. package/dist/lib/format/index.d.ts.map +1 -0
  36. package/dist/lib/format/index.js +43 -0
  37. package/dist/lib/format/index.js.map +1 -0
  38. package/dist/lib/format/json.d.ts +35 -0
  39. package/dist/lib/format/json.d.ts.map +1 -0
  40. package/dist/lib/format/json.js +41 -0
  41. package/dist/lib/format/json.js.map +1 -0
  42. package/dist/lib/format/markdown.d.ts +4 -0
  43. package/dist/lib/format/markdown.d.ts.map +1 -0
  44. package/dist/lib/format/markdown.js +76 -0
  45. package/dist/lib/format/markdown.js.map +1 -0
  46. package/dist/lib/fs-atomic.d.ts +10 -0
  47. package/dist/lib/fs-atomic.d.ts.map +1 -0
  48. package/dist/lib/fs-atomic.js +32 -0
  49. package/dist/lib/fs-atomic.js.map +1 -0
  50. package/dist/lib/init-config.d.ts +81 -0
  51. package/dist/lib/init-config.d.ts.map +1 -0
  52. package/dist/lib/init-config.js +152 -0
  53. package/dist/lib/init-config.js.map +1 -0
  54. package/dist/lib/output.d.ts +5 -0
  55. package/dist/lib/output.d.ts.map +1 -0
  56. package/dist/lib/output.js +23 -0
  57. package/dist/lib/output.js.map +1 -0
  58. package/dist/lib/peek-home.d.ts +5 -0
  59. package/dist/lib/peek-home.d.ts.map +1 -0
  60. package/dist/lib/peek-home.js +11 -0
  61. package/dist/lib/peek-home.js.map +1 -0
  62. package/dist/lib/prompt.d.ts +25 -0
  63. package/dist/lib/prompt.d.ts.map +1 -0
  64. package/dist/lib/prompt.js +62 -0
  65. package/dist/lib/prompt.js.map +1 -0
  66. package/dist/lib/status.d.ts +54 -0
  67. package/dist/lib/status.d.ts.map +1 -0
  68. package/dist/lib/status.js +62 -0
  69. package/dist/lib/status.js.map +1 -0
  70. package/dist/version.d.ts +2 -0
  71. package/dist/version.d.ts.map +1 -0
  72. package/dist/version.js +6 -0
  73. package/dist/version.js.map +1 -0
  74. package/package.json +42 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export declare function run(argv: readonly string[]): Promise<number>;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAqCA,wBAAsB,GAAG,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA6BlE"}
package/dist/index.js ADDED
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ // `peek` CLI entry (Task 3.6). A thin read-mostly client of the native host's
3
+ // ~/.peek/sessions.db (ADR-0007). Dispatches to the command shells:
4
+ // peek status
5
+ // peek sessions <list|show|export|delete>
6
+ // peek init
7
+ // peek audit log
8
+ //
9
+ // Arg parsing uses the built-in node:util parseArgs at each command boundary
10
+ // (no CLI framework dependency); this top-level dispatcher just routes on the
11
+ // first positional so each subcommand owns its own option schema.
12
+ import { runAudit } from './commands/audit.js';
13
+ import { runInit } from './commands/init.js';
14
+ import { runSessions } from './commands/sessions.js';
15
+ import { runStatus } from './commands/status.js';
16
+ import { CLI_VERSION } from './version.js';
17
+ const HELP = `peek ${CLI_VERSION} — browser-session companion CLI
18
+
19
+ Usage: peek <command> [options]
20
+
21
+ Commands:
22
+ status Native-host registration, DB size + schema, extension state
23
+ sessions list List recent sessions
24
+ sessions show <id> Show one session (metadata + console/network errors)
25
+ sessions export <id> Export a session (--format markdown|json|html|playwright)
26
+ sessions delete <id> Delete a session (or --all-older-than <dur>)
27
+ init Interactive wizard: configure MCP clients + native host
28
+ audit log Show the act-tool audit log (--since/--tool/--client)
29
+
30
+ Run \`peek <command> --help\` for command-specific options.
31
+
32
+ The native messaging host (@peekdev/mcp) owns the database; this CLI reads it.
33
+ Set PEEK_HOME to relocate ~/.peek. Docs: https://github.com/Cubenest/rrweb-stack
34
+ `;
35
+ export async function run(argv) {
36
+ const [command, ...rest] = argv;
37
+ switch (command) {
38
+ case 'status':
39
+ return runStatus();
40
+ case 'sessions':
41
+ return runSessions(rest);
42
+ case 'audit':
43
+ return runAudit(rest);
44
+ case 'init':
45
+ return runInit(rest);
46
+ case 'version':
47
+ case '--version':
48
+ case '-v':
49
+ process.stdout.write(`${CLI_VERSION}\n`);
50
+ return 0;
51
+ case undefined:
52
+ case 'help':
53
+ case '--help':
54
+ case '-h':
55
+ process.stdout.write(HELP);
56
+ // No command given is a usage error (exit 1); explicit help is success.
57
+ return command === undefined ? 1 : 0;
58
+ default:
59
+ process.stderr.write(`peek: unknown command '${command}'\n\n`);
60
+ process.stdout.write(HELP);
61
+ return 1;
62
+ }
63
+ }
64
+ async function main() {
65
+ const code = await run(process.argv.slice(2));
66
+ process.exitCode = code;
67
+ }
68
+ // Run only when invoked directly as the `peek` bin (npx / shell), not when this
69
+ // module is imported (tests, or another package consuming `run`). Guarded the
70
+ // same way as @peekdev/mcp's entry so an ESM `import` has no side effects.
71
+ const invokedDirectly = process.argv[1] !== undefined && import.meta.url === `file://${process.argv[1]}`;
72
+ if (invokedDirectly) {
73
+ main().catch((err) => {
74
+ process.stderr.write(`peek: fatal — ${err instanceof Error ? (err.stack ?? err.message) : String(err)}\n`);
75
+ process.exitCode = 1;
76
+ });
77
+ }
78
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,8EAA8E;AAC9E,oEAAoE;AACpE,gBAAgB;AAChB,4CAA4C;AAC5C,cAAc;AACd,mBAAmB;AACnB,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E,kEAAkE;AAElE,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,IAAI,GAAG,QAAQ,WAAW;;;;;;;;;;;;;;;;;CAiB/B,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,IAAuB;IAC/C,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAEhC,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,QAAQ;YACX,OAAO,SAAS,EAAE,CAAC;QACrB,KAAK,UAAU;YACb,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;QAC3B,KAAK,OAAO;YACV,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC;QACxB,KAAK,MAAM;YACT,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC;QACvB,KAAK,SAAS,CAAC;QACf,KAAK,WAAW,CAAC;QACjB,KAAK,IAAI;YACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,WAAW,IAAI,CAAC,CAAC;YACzC,OAAO,CAAC,CAAC;QACX,KAAK,SAAS,CAAC;QACf,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ,CAAC;QACd,KAAK,IAAI;YACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,wEAAwE;YACxE,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvC;YACE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0BAA0B,OAAO,OAAO,CAAC,CAAC;YAC/D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,OAAO,CAAC,CAAC;IACb,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;AAC1B,CAAC;AAED,gFAAgF;AAChF,8EAA8E;AAC9E,2EAA2E;AAC3E,MAAM,eAAe,GACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;AACnF,IAAI,eAAe,EAAE,CAAC;IACpB,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,iBAAiB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CACrF,CAAC;QACF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,47 @@
1
+ /** One parsed audit entry. Unknown extra fields are preserved verbatim. */
2
+ export interface AuditEntry {
3
+ readonly ts: string;
4
+ readonly tool?: string;
5
+ readonly args?: unknown;
6
+ readonly approvalTs?: string;
7
+ readonly approver?: string;
8
+ readonly client?: string;
9
+ readonly sessionId?: string;
10
+ readonly result?: string;
11
+ readonly [extra: string]: unknown;
12
+ }
13
+ /** A line that did not parse as JSON, kept with its 1-based line number. */
14
+ export interface AuditParseError {
15
+ readonly line: number;
16
+ readonly raw: string;
17
+ readonly error: string;
18
+ }
19
+ export interface ParsedAuditLog {
20
+ readonly entries: AuditEntry[];
21
+ readonly errors: AuditParseError[];
22
+ }
23
+ /**
24
+ * Parse the raw JSONL contents of an audit log. Blank lines are skipped; each
25
+ * non-blank line must be a JSON object with a string `ts`. Lines that fail to
26
+ * parse (or aren't objects/lack `ts`) are collected in `errors` rather than
27
+ * thrown, so a single corrupt line doesn't hide the rest of the log.
28
+ */
29
+ export declare function parseAuditLog(contents: string): ParsedAuditLog;
30
+ export interface AuditFilter {
31
+ /**
32
+ * Only entries at/after this instant (epoch ms). Derived from `--since 1h`
33
+ * via the duration parser by the command shell.
34
+ */
35
+ readonly sinceMs?: number;
36
+ /** Exact `tool` match (e.g. `execute_action`). */
37
+ readonly tool?: string;
38
+ /** Exact `client` match (e.g. `cursor`, `claude-code`). */
39
+ readonly client?: string;
40
+ }
41
+ /**
42
+ * Filter parsed audit entries by `--since` / `--tool` / `--client`. An entry
43
+ * with an unparseable `ts` is dropped only when a `sinceMs` filter is active
44
+ * (it can't satisfy a time bound); otherwise all filters are exact-match.
45
+ */
46
+ export declare function filterAuditEntries(entries: AuditEntry[], filter: AuditFilter): AuditEntry[];
47
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/lib/audit.ts"],"names":[],"mappings":"AASA,2EAA2E;AAC3E,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CACnC;AAED,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC;CACpC;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,CAiC9D;AAED,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,kDAAkD;IAClD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,WAAW,GAAG,UAAU,EAAE,CAU3F"}
@@ -0,0 +1,68 @@
1
+ // `peek audit log` (ADR-0010 / P2 PRD §H3). The native host / extension append
2
+ // one JSONL line per AI-driven act-tool call to ~/.peek/audit.log:
3
+ // { "ts":"2026-05-23T10:14:22Z", "tool":"execute_action",
4
+ // "args":{...}, "approvalTs":"...", "approver":"user",
5
+ // "client":"claude-code", "sessionId":"s_8nQ…", "result":"ok" }
6
+ // This module is pure: parse + filter operate on strings/objects, so the file
7
+ // read and stdout write stay a thin shell. Malformed lines are surfaced, not
8
+ // silently dropped (an unparseable audit line is itself notable).
9
+ /**
10
+ * Parse the raw JSONL contents of an audit log. Blank lines are skipped; each
11
+ * non-blank line must be a JSON object with a string `ts`. Lines that fail to
12
+ * parse (or aren't objects/lack `ts`) are collected in `errors` rather than
13
+ * thrown, so a single corrupt line doesn't hide the rest of the log.
14
+ */
15
+ export function parseAuditLog(contents) {
16
+ const entries = [];
17
+ const errors = [];
18
+ const lines = contents.split('\n');
19
+ for (let i = 0; i < lines.length; i++) {
20
+ const raw = lines[i] ?? '';
21
+ const trimmed = raw.trim();
22
+ if (trimmed.length === 0)
23
+ continue;
24
+ let parsed;
25
+ try {
26
+ parsed = JSON.parse(trimmed);
27
+ }
28
+ catch (err) {
29
+ errors.push({
30
+ line: i + 1,
31
+ raw: trimmed,
32
+ error: err instanceof Error ? err.message : String(err),
33
+ });
34
+ continue;
35
+ }
36
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
37
+ errors.push({ line: i + 1, raw: trimmed, error: 'not a JSON object' });
38
+ continue;
39
+ }
40
+ const obj = parsed;
41
+ if (typeof obj.ts !== 'string') {
42
+ errors.push({ line: i + 1, raw: trimmed, error: 'missing string "ts"' });
43
+ continue;
44
+ }
45
+ entries.push(obj);
46
+ }
47
+ return { entries, errors };
48
+ }
49
+ /**
50
+ * Filter parsed audit entries by `--since` / `--tool` / `--client`. An entry
51
+ * with an unparseable `ts` is dropped only when a `sinceMs` filter is active
52
+ * (it can't satisfy a time bound); otherwise all filters are exact-match.
53
+ */
54
+ export function filterAuditEntries(entries, filter) {
55
+ return entries.filter((e) => {
56
+ if (filter.tool !== undefined && e.tool !== filter.tool)
57
+ return false;
58
+ if (filter.client !== undefined && e.client !== filter.client)
59
+ return false;
60
+ if (filter.sinceMs !== undefined) {
61
+ const tsMs = Date.parse(e.ts);
62
+ if (Number.isNaN(tsMs) || tsMs < filter.sinceMs)
63
+ return false;
64
+ }
65
+ return true;
66
+ });
67
+ }
68
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/lib/audit.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,4DAA4D;AAC5D,2DAA2D;AAC3D,oEAAoE;AACpE,8EAA8E;AAC9E,6EAA6E;AAC7E,kEAAkE;AA2BlE;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAEnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QAC3B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACnC,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,CAAC,GAAG,CAAC;gBACX,GAAG,EAAE,OAAO;gBACZ,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACxD,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3E,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAC;YACvE,SAAS;QACX,CAAC;QACD,MAAM,GAAG,GAAG,MAAiC,CAAC;QAC9C,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC,CAAC;YACzE,SAAS;QACX,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,GAAiB,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AAC7B,CAAC;AAcD;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAqB,EAAE,MAAmB;IAC3E,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QAC1B,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QACtE,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAC5E,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YACjC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC9B,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,MAAM,CAAC,OAAO;gBAAE,OAAO,KAAK,CAAC;QAChE,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,86 @@
1
+ import type { Database } from 'better-sqlite3';
2
+ /** A row of the `sessions` table, as the CLI presents it. */
3
+ export interface SessionRow {
4
+ readonly id: string;
5
+ readonly createdAt: string;
6
+ readonly updatedAt: string;
7
+ readonly url: string | null;
8
+ readonly title: string | null;
9
+ readonly origin: string | null;
10
+ readonly userAgent: string | null;
11
+ readonly eventCount: number;
12
+ readonly bytes: number;
13
+ readonly status: string;
14
+ }
15
+ /** A console message extracted for a session (mirrors `get_session_console_errors`). */
16
+ export interface ConsoleEventRow {
17
+ readonly ts: number;
18
+ readonly level: string;
19
+ readonly message: string;
20
+ readonly stack: string | null;
21
+ readonly url: string | null;
22
+ }
23
+ /** A network request captured for a session (mirrors `get_session_network_errors`). */
24
+ export interface NetworkEventRow {
25
+ readonly ts: number;
26
+ readonly method: string;
27
+ readonly url: string;
28
+ readonly status: number | null;
29
+ readonly statusText: string | null;
30
+ readonly resourceType: string | null;
31
+ readonly durationMs: number | null;
32
+ readonly errorText: string | null;
33
+ }
34
+ /** Per-session aggregate counts used by `peek sessions list` / the summary header. */
35
+ export interface SessionCounts {
36
+ readonly consoleErrors: number;
37
+ readonly networkErrors: number;
38
+ }
39
+ /** A fully-hydrated session for `show` / `export` (metadata + extracted rows). */
40
+ export interface SessionDetail {
41
+ readonly session: SessionRow;
42
+ readonly counts: SessionCounts;
43
+ readonly consoleErrors: ConsoleEventRow[];
44
+ readonly networkErrors: NetworkEventRow[];
45
+ }
46
+ export interface ListSessionsOptions {
47
+ /** Max rows (default 20, per P2 PRD §C.1). */
48
+ readonly limit?: number;
49
+ /** Filter to a single origin (`scheme://host[:port]`). */
50
+ readonly origin?: string;
51
+ }
52
+ /**
53
+ * List the most-recently-updated sessions, newest first. Default limit 20
54
+ * (P2 PRD §C.1 `peek sessions list [--origin <url>] [--limit 20]`).
55
+ */
56
+ export declare function listSessions(db: Database, options?: ListSessionsOptions): SessionRow[];
57
+ /** Fetch one session's metadata, or `undefined` if no such id. */
58
+ export declare function getSession(db: Database, id: string): SessionRow | undefined;
59
+ /** Count error-level console rows and >=400 network rows for a session. */
60
+ export declare function getSessionCounts(db: Database, id: string): SessionCounts;
61
+ export interface ConsoleQueryOptions {
62
+ /** Only `level = 'error'` rows (the default for export/summary). */
63
+ readonly errorsOnly?: boolean;
64
+ /** Max rows (default 50, mirrors the MCP tool). */
65
+ readonly limit?: number;
66
+ }
67
+ /** Console messages for a session, oldest first. */
68
+ export declare function getConsoleEvents(db: Database, id: string, options?: ConsoleQueryOptions): ConsoleEventRow[];
69
+ export interface NetworkQueryOptions {
70
+ /** Minimum HTTP status to include (default 400 — failures only). */
71
+ readonly statusGte?: number;
72
+ /** Max rows (default 50). */
73
+ readonly limit?: number;
74
+ }
75
+ /** Failed/notable network requests for a session, oldest first. */
76
+ export declare function getNetworkEvents(db: Database, id: string, options?: NetworkQueryOptions): NetworkEventRow[];
77
+ /** Hydrate one session fully for `show` / `export`. Returns `undefined` if no such id. */
78
+ export declare function getSessionDetail(db: Database, id: string): SessionDetail | undefined;
79
+ /** Delete one session by id. Child rows cascade (ON DELETE CASCADE). Returns rows removed (0 or 1). */
80
+ export declare function deleteSession(db: Database, id: string): number;
81
+ /**
82
+ * Delete every session whose `updated_at` is strictly before the ISO cutoff.
83
+ * Returns the number of sessions removed (child rows cascade).
84
+ */
85
+ export declare function deleteSessionsOlderThan(db: Database, cutoffIso: string): number;
86
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":"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"}
package/dist/lib/db.js ADDED
@@ -0,0 +1,117 @@
1
+ // Read-mostly query helpers over the native host's ~/.peek/sessions.db. The CLI
2
+ // opens the same SQLite file through @peekdev/mcp's `openDb` (WAL + FK pragmas,
3
+ // migration state) — it never reimplements the DB layer (ADR-0007). The native
4
+ // host writes; the CLI lists / shows / exports / deletes.
5
+ //
6
+ // The returned shapes deliberately mirror the MCP tool return schema (P2 PRD
7
+ // §B3) so `peek sessions export --format json` and the MCP `get_session_*`
8
+ // tools are interchangeable for an AI consumer.
9
+ function mapSession(r) {
10
+ return {
11
+ id: r.id,
12
+ createdAt: r.created_at,
13
+ updatedAt: r.updated_at,
14
+ url: r.url,
15
+ title: r.title,
16
+ origin: r.origin,
17
+ userAgent: r.user_agent,
18
+ eventCount: r.event_count,
19
+ bytes: r.bytes,
20
+ status: r.status,
21
+ };
22
+ }
23
+ /**
24
+ * List the most-recently-updated sessions, newest first. Default limit 20
25
+ * (P2 PRD §C.1 `peek sessions list [--origin <url>] [--limit 20]`).
26
+ */
27
+ export function listSessions(db, options = {}) {
28
+ const limit = options.limit ?? 20;
29
+ const params = [];
30
+ let sql = 'SELECT * FROM sessions';
31
+ if (options.origin !== undefined) {
32
+ sql += ' WHERE origin = ?';
33
+ params.push(options.origin);
34
+ }
35
+ sql += ' ORDER BY updated_at DESC, created_at DESC LIMIT ?';
36
+ params.push(limit);
37
+ const rows = db.prepare(sql).all(...params);
38
+ return rows.map(mapSession);
39
+ }
40
+ /** Fetch one session's metadata, or `undefined` if no such id. */
41
+ export function getSession(db, id) {
42
+ const row = db.prepare('SELECT * FROM sessions WHERE id = ?').get(id);
43
+ return row ? mapSession(row) : undefined;
44
+ }
45
+ /** Count error-level console rows and >=400 network rows for a session. */
46
+ export function getSessionCounts(db, id) {
47
+ const consoleErrors = db
48
+ .prepare("SELECT COUNT(*) AS c FROM console_events WHERE session_id = ? AND level = 'error'")
49
+ .get(id).c;
50
+ const networkErrors = db
51
+ .prepare('SELECT COUNT(*) AS c FROM network_events WHERE session_id = ? AND (status >= 400 OR error_text IS NOT NULL)')
52
+ .get(id).c;
53
+ return { consoleErrors, networkErrors };
54
+ }
55
+ /** Console messages for a session, oldest first. */
56
+ export function getConsoleEvents(db, id, options = {}) {
57
+ const limit = options.limit ?? 50;
58
+ let sql = 'SELECT ts_ms, level, message, stack, url FROM console_events WHERE session_id = ?';
59
+ if (options.errorsOnly)
60
+ sql += " AND level = 'error'";
61
+ sql += ' ORDER BY ts_ms ASC LIMIT ?';
62
+ const rows = db.prepare(sql).all(id, limit);
63
+ return rows.map((r) => ({
64
+ ts: r.ts_ms,
65
+ level: r.level,
66
+ message: r.message,
67
+ stack: r.stack,
68
+ url: r.url,
69
+ }));
70
+ }
71
+ /** Failed/notable network requests for a session, oldest first. */
72
+ export function getNetworkEvents(db, id, options = {}) {
73
+ const statusGte = options.statusGte ?? 400;
74
+ const limit = options.limit ?? 50;
75
+ const rows = db
76
+ .prepare(`SELECT ts_ms, method, url, status, status_text, resource_type, duration_ms, error_text
77
+ FROM network_events
78
+ WHERE session_id = ? AND (status >= ? OR error_text IS NOT NULL)
79
+ ORDER BY ts_ms ASC LIMIT ?`)
80
+ .all(id, statusGte, limit);
81
+ return rows.map((r) => ({
82
+ ts: r.ts_ms,
83
+ method: r.method,
84
+ url: r.url,
85
+ status: r.status,
86
+ statusText: r.status_text,
87
+ resourceType: r.resource_type,
88
+ durationMs: r.duration_ms,
89
+ errorText: r.error_text,
90
+ }));
91
+ }
92
+ /** Hydrate one session fully for `show` / `export`. Returns `undefined` if no such id. */
93
+ export function getSessionDetail(db, id) {
94
+ const session = getSession(db, id);
95
+ if (!session)
96
+ return undefined;
97
+ return {
98
+ session,
99
+ counts: getSessionCounts(db, id),
100
+ consoleErrors: getConsoleEvents(db, id, { errorsOnly: true }),
101
+ networkErrors: getNetworkEvents(db, id),
102
+ };
103
+ }
104
+ /** Delete one session by id. Child rows cascade (ON DELETE CASCADE). Returns rows removed (0 or 1). */
105
+ export function deleteSession(db, id) {
106
+ const info = db.prepare('DELETE FROM sessions WHERE id = ?').run(id);
107
+ return info.changes;
108
+ }
109
+ /**
110
+ * Delete every session whose `updated_at` is strictly before the ISO cutoff.
111
+ * Returns the number of sessions removed (child rows cascade).
112
+ */
113
+ export function deleteSessionsOlderThan(db, cutoffIso) {
114
+ const info = db.prepare('DELETE FROM sessions WHERE updated_at < ?').run(cutoffIso);
115
+ return info.changes;
116
+ }
117
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Parse a short duration string (`30s`, `15m`, `1h`, `7d`, `2w`) into
3
+ * milliseconds. The numeric part must be a non-negative integer; the unit is
4
+ * one of s/m/h/d/w (case-insensitive). Throws a user-facing `Error` on anything
5
+ * else so the caller can print it and exit non-zero.
6
+ */
7
+ export declare function parseDuration(input: string): number;
8
+ /**
9
+ * Resolve a duration into an absolute "older than" cutoff timestamp (epoch ms):
10
+ * everything strictly before the returned instant is "older than" the duration.
11
+ * `now` is injectable for deterministic tests.
12
+ */
13
+ export declare function cutoffBefore(input: string, now?: number): number;
14
+ //# sourceMappingURL=duration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration.d.ts","sourceRoot":"","sources":["../../src/lib/duration.ts"],"names":[],"mappings":"AAgBA;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAcnD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,MAAM,CAE5E"}
@@ -0,0 +1,41 @@
1
+ // Pure duration parsing for `--since 1h` / `--all-older-than 7d` (P2 PRD §C.1,
2
+ // §H3). Accepts a short human duration and returns milliseconds. Kept pure +
3
+ // dependency-free so it can be unit-tested exhaustively; the commands that use
4
+ // it derive an absolute cutoff via `Date.now() - parseDuration(...)`.
5
+ /** Suffix → milliseconds multiplier. */
6
+ const UNIT_MS = {
7
+ s: 1000,
8
+ m: 60 * 1000,
9
+ h: 60 * 60 * 1000,
10
+ d: 24 * 60 * 60 * 1000,
11
+ w: 7 * 24 * 60 * 60 * 1000,
12
+ };
13
+ const DURATION_RE = /^(\d+)\s*([smhdw])$/;
14
+ /**
15
+ * Parse a short duration string (`30s`, `15m`, `1h`, `7d`, `2w`) into
16
+ * milliseconds. The numeric part must be a non-negative integer; the unit is
17
+ * one of s/m/h/d/w (case-insensitive). Throws a user-facing `Error` on anything
18
+ * else so the caller can print it and exit non-zero.
19
+ */
20
+ export function parseDuration(input) {
21
+ const trimmed = input.trim().toLowerCase();
22
+ const match = DURATION_RE.exec(trimmed);
23
+ if (!match) {
24
+ throw new Error(`invalid duration "${input}" — expected a number followed by s, m, h, d, or w (e.g. 30m, 1h, 7d)`);
25
+ }
26
+ // match[1]/match[2] are guaranteed present by the regex shape above, and the
27
+ // unit is restricted to [smhdw] — every one a key of UNIT_MS.
28
+ const value = Number(match[1]);
29
+ const unit = match[2];
30
+ const multiplier = UNIT_MS[unit] ?? 0;
31
+ return value * multiplier;
32
+ }
33
+ /**
34
+ * Resolve a duration into an absolute "older than" cutoff timestamp (epoch ms):
35
+ * everything strictly before the returned instant is "older than" the duration.
36
+ * `now` is injectable for deterministic tests.
37
+ */
38
+ export function cutoffBefore(input, now = Date.now()) {
39
+ return now - parseDuration(input);
40
+ }
41
+ //# sourceMappingURL=duration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration.js","sourceRoot":"","sources":["../../src/lib/duration.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,6EAA6E;AAC7E,+EAA+E;AAC/E,sEAAsE;AAEtE,wCAAwC;AACxC,MAAM,OAAO,GAA2B;IACtC,CAAC,EAAE,IAAI;IACP,CAAC,EAAE,EAAE,GAAG,IAAI;IACZ,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI;IACjB,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;IACtB,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;CAC3B,CAAC;AAEF,MAAM,WAAW,GAAG,qBAAqB,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,qBAAqB,KAAK,uEAAuE,CAClG,CAAC;IACJ,CAAC;IACD,6EAA6E;IAC7E,8DAA8D;IAC9D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAW,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAyB,CAAC;IAC9C,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtC,OAAO,KAAK,GAAG,UAAU,CAAC;AAC5B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,MAAc,IAAI,CAAC,GAAG,EAAE;IAClE,OAAO,GAAG,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;AACpC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { SessionDetail } from '../db.js';
2
+ /** Supported `--format` values (P2 PRD §C.1). */
3
+ export declare const EXPORT_FORMATS: readonly ["markdown", "json", "html", "playwright"];
4
+ export type ExportFormat = (typeof EXPORT_FORMATS)[number];
5
+ export declare function isExportFormat(value: string): value is ExportFormat;
6
+ /** Result of rendering: either content to write, or a not-implemented message. */
7
+ export type FormatResult = {
8
+ readonly ok: true;
9
+ readonly content: string;
10
+ } | {
11
+ readonly ok: false;
12
+ readonly message: string;
13
+ };
14
+ /** Render a hydrated session in the requested format. */
15
+ export declare function formatSession(detail: SessionDetail, format: ExportFormat): FormatResult;
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/format/index.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAI9C,iDAAiD;AACjD,eAAO,MAAM,cAAc,qDAAsD,CAAC;AAClF,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,YAAY,CAEnE;AAED,kFAAkF;AAClF,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD,yDAAyD;AACzD,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,YAAY,GAAG,YAAY,CAuBvF"}
@@ -0,0 +1,43 @@
1
+ // Export-format dispatch (P2 PRD §C.3). `markdown` and `json` are implemented
2
+ // fully (self-contained AI-paste formats read straight from the extracted SQL
3
+ // rows). `html` (self-contained rrweb replay) and `playwright` (repro
4
+ // boilerplate) are deferred:
5
+ // - html would require bundling an rrweb player inline; importing
6
+ // @tracelane/report is explicitly forbidden (ADR-0001 product independence),
7
+ // so a peek-specific viewer is a tracked follow-up.
8
+ // - playwright overlaps the MCP `generate_playwright_repro` tool (Phase 3c
9
+ // Task 3.13); the repro walker should live there and be shared, rather than
10
+ // forked here. Tracked follow-up.
11
+ // The stubs return a non-ok result so the command shell exits non-zero with a
12
+ // clear message (never silently emit an empty/partial file).
13
+ import { formatSessionJson } from './json.js';
14
+ import { formatSessionMarkdown } from './markdown.js';
15
+ /** Supported `--format` values (P2 PRD §C.1). */
16
+ export const EXPORT_FORMATS = ['markdown', 'json', 'html', 'playwright'];
17
+ export function isExportFormat(value) {
18
+ return EXPORT_FORMATS.includes(value);
19
+ }
20
+ /** Render a hydrated session in the requested format. */
21
+ export function formatSession(detail, format) {
22
+ switch (format) {
23
+ case 'markdown':
24
+ return { ok: true, content: formatSessionMarkdown(detail) };
25
+ case 'json':
26
+ return { ok: true, content: formatSessionJson(detail) };
27
+ case 'html':
28
+ return {
29
+ ok: false,
30
+ message: "export format 'html' is not yet implemented (a peek-specific self-contained " +
31
+ 'rrweb replay viewer is tracked for a follow-up; @tracelane/report is intentionally ' +
32
+ 'not reused — ADR-0001 product independence). Use --format markdown or json.',
33
+ };
34
+ case 'playwright':
35
+ return {
36
+ ok: false,
37
+ message: "export format 'playwright' is not yet implemented (the repro generator overlaps the " +
38
+ 'MCP `generate_playwright_repro` tool, Phase 3c Task 3.13, and will share that walker). ' +
39
+ 'Use --format markdown or json.',
40
+ };
41
+ }
42
+ }
43
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/format/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,8EAA8E;AAC9E,sEAAsE;AACtE,6BAA6B;AAC7B,oEAAoE;AACpE,iFAAiF;AACjF,wDAAwD;AACxD,6EAA6E;AAC7E,gFAAgF;AAChF,sCAAsC;AACtC,8EAA8E;AAC9E,6DAA6D;AAG7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEtD,iDAAiD;AACjD,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,CAAU,CAAC;AAGlF,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAQ,cAAoC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC/D,CAAC;AAOD,yDAAyD;AACzD,MAAM,UAAU,aAAa,CAAC,MAAqB,EAAE,MAAoB;IACvE,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,UAAU;YACb,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,qBAAqB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9D,KAAK,MAAM;YACT,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1D,KAAK,MAAM;YACT,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,OAAO,EACL,8EAA8E;oBAC9E,qFAAqF;oBACrF,6EAA6E;aAChF,CAAC;QACJ,KAAK,YAAY;YACf,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,OAAO,EACL,sFAAsF;oBACtF,yFAAyF;oBACzF,gCAAgC;aACnC,CAAC;IACN,CAAC;AACH,CAAC"}
@@ -0,0 +1,35 @@
1
+ import type { SessionDetail } from '../db.js';
2
+ /** The JSON export envelope. Field names mirror the MCP tool return shapes. */
3
+ export interface SessionJsonExport {
4
+ readonly id: string;
5
+ readonly origin: string | null;
6
+ readonly url: string | null;
7
+ readonly title: string | null;
8
+ readonly startedAt: string;
9
+ readonly updatedAt: string;
10
+ readonly status: string;
11
+ readonly eventCount: number;
12
+ readonly bytes: number;
13
+ readonly errorCount: number;
14
+ readonly consoleErrors: ReadonlyArray<{
15
+ ts: number;
16
+ level: string;
17
+ message: string;
18
+ stack: string | null;
19
+ }>;
20
+ readonly networkErrors: ReadonlyArray<{
21
+ ts: number;
22
+ method: string;
23
+ url: string;
24
+ status: number | null;
25
+ statusText: string | null;
26
+ resourceType: string | null;
27
+ durationMs: number | null;
28
+ errorText: string | null;
29
+ }>;
30
+ }
31
+ /** Build the JSON export object (pure; not yet stringified). */
32
+ export declare function toJsonExport(detail: SessionDetail): SessionJsonExport;
33
+ /** Render a session as a pretty-printed JSON string (2-space indent). */
34
+ export declare function formatSessionJson(detail: SessionDetail): string;
35
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../../src/lib/format/json.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,+EAA+E;AAC/E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;QACpC,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;KACtB,CAAC,CAAC;IACH,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;QACpC,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;KAC1B,CAAC,CAAC;CACJ;AAED,gEAAgE;AAChE,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,iBAAiB,CA8BrE;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAE/D"}