@steipete/summarize 0.7.0 → 0.8.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 (158) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +53 -2
  3. package/dist/cli.js +3 -0
  4. package/dist/esm/cache.js +353 -0
  5. package/dist/esm/cache.js.map +1 -0
  6. package/dist/esm/config.js +78 -1
  7. package/dist/esm/config.js.map +1 -1
  8. package/dist/esm/content/asset.js +11 -17
  9. package/dist/esm/content/asset.js.map +1 -1
  10. package/dist/esm/daemon/auto-mode.js +8 -0
  11. package/dist/esm/daemon/auto-mode.js.map +1 -0
  12. package/dist/esm/daemon/cli.js +284 -0
  13. package/dist/esm/daemon/cli.js.map +1 -0
  14. package/dist/esm/daemon/config.js +82 -0
  15. package/dist/esm/daemon/config.js.map +1 -0
  16. package/dist/esm/daemon/constants.js +8 -0
  17. package/dist/esm/daemon/constants.js.map +1 -0
  18. package/dist/esm/daemon/env-merge.js +4 -0
  19. package/dist/esm/daemon/env-merge.js.map +1 -0
  20. package/dist/esm/daemon/env-snapshot.js +43 -0
  21. package/dist/esm/daemon/env-snapshot.js.map +1 -0
  22. package/dist/esm/daemon/flow-context.js +265 -0
  23. package/dist/esm/daemon/flow-context.js.map +1 -0
  24. package/dist/esm/daemon/launchd.js +149 -0
  25. package/dist/esm/daemon/launchd.js.map +1 -0
  26. package/dist/esm/daemon/meta.js +35 -0
  27. package/dist/esm/daemon/meta.js.map +1 -0
  28. package/dist/esm/daemon/models.js +175 -0
  29. package/dist/esm/daemon/models.js.map +1 -0
  30. package/dist/esm/daemon/request-settings.js +91 -0
  31. package/dist/esm/daemon/request-settings.js.map +1 -0
  32. package/dist/esm/daemon/schtasks.js +108 -0
  33. package/dist/esm/daemon/schtasks.js.map +1 -0
  34. package/dist/esm/daemon/server.js +399 -0
  35. package/dist/esm/daemon/server.js.map +1 -0
  36. package/dist/esm/daemon/summarize-progress.js +57 -0
  37. package/dist/esm/daemon/summarize-progress.js.map +1 -0
  38. package/dist/esm/daemon/summarize.js +263 -0
  39. package/dist/esm/daemon/summarize.js.map +1 -0
  40. package/dist/esm/daemon/systemd.js +117 -0
  41. package/dist/esm/daemon/systemd.js.map +1 -0
  42. package/dist/esm/flags.js +3 -1
  43. package/dist/esm/flags.js.map +1 -1
  44. package/dist/esm/llm/generate-text.js +445 -154
  45. package/dist/esm/llm/generate-text.js.map +1 -1
  46. package/dist/esm/llm/html-to-markdown.js +4 -1
  47. package/dist/esm/llm/html-to-markdown.js.map +1 -1
  48. package/dist/esm/llm/prompt.js +14 -0
  49. package/dist/esm/llm/prompt.js.map +1 -0
  50. package/dist/esm/llm/transcript-to-markdown.js +57 -0
  51. package/dist/esm/llm/transcript-to-markdown.js.map +1 -0
  52. package/dist/esm/model-spec.js +2 -2
  53. package/dist/esm/model-spec.js.map +1 -1
  54. package/dist/esm/run/attachments.js +10 -42
  55. package/dist/esm/run/attachments.js.map +1 -1
  56. package/dist/esm/run/cache-state.js +48 -0
  57. package/dist/esm/run/cache-state.js.map +1 -0
  58. package/dist/esm/run/cli-preflight.js +15 -1
  59. package/dist/esm/run/cli-preflight.js.map +1 -1
  60. package/dist/esm/run/cookies/twitter.js +224 -0
  61. package/dist/esm/run/cookies/twitter.js.map +1 -0
  62. package/dist/esm/run/fetch-with-timeout.js +1 -1
  63. package/dist/esm/run/fetch-with-timeout.js.map +1 -1
  64. package/dist/esm/run/finish-line.js +46 -17
  65. package/dist/esm/run/finish-line.js.map +1 -1
  66. package/dist/esm/run/flows/asset/input.js +10 -4
  67. package/dist/esm/run/flows/asset/input.js.map +1 -1
  68. package/dist/esm/run/flows/asset/preprocess.js +52 -72
  69. package/dist/esm/run/flows/asset/preprocess.js.map +1 -1
  70. package/dist/esm/run/flows/asset/summary.js +127 -47
  71. package/dist/esm/run/flows/asset/summary.js.map +1 -1
  72. package/dist/esm/run/flows/url/extract.js +6 -1
  73. package/dist/esm/run/flows/url/extract.js.map +1 -1
  74. package/dist/esm/run/flows/url/flow.js +168 -82
  75. package/dist/esm/run/flows/url/flow.js.map +1 -1
  76. package/dist/esm/run/flows/url/markdown.js +88 -46
  77. package/dist/esm/run/flows/url/markdown.js.map +1 -1
  78. package/dist/esm/run/flows/url/summary.js +263 -185
  79. package/dist/esm/run/flows/url/summary.js.map +1 -1
  80. package/dist/esm/run/help.js +33 -2
  81. package/dist/esm/run/help.js.map +1 -1
  82. package/dist/esm/run/markdown.js +31 -2
  83. package/dist/esm/run/markdown.js.map +1 -1
  84. package/dist/esm/run/progress.js +1 -3
  85. package/dist/esm/run/progress.js.map +1 -1
  86. package/dist/esm/run/run-env.js +36 -2
  87. package/dist/esm/run/run-env.js.map +1 -1
  88. package/dist/esm/run/runner.js +362 -227
  89. package/dist/esm/run/runner.js.map +1 -1
  90. package/dist/esm/run/summary-engine.js +22 -8
  91. package/dist/esm/run/summary-engine.js.map +1 -1
  92. package/dist/esm/run/summary-llm.js +4 -1
  93. package/dist/esm/run/summary-llm.js.map +1 -1
  94. package/dist/esm/tty/format.js +9 -0
  95. package/dist/esm/tty/format.js.map +1 -1
  96. package/dist/esm/tty/spinner.js +37 -11
  97. package/dist/esm/tty/spinner.js.map +1 -1
  98. package/dist/esm/version.js +1 -1
  99. package/dist/types/cache.d.ts +70 -0
  100. package/dist/types/config.d.ts +46 -0
  101. package/dist/types/content/asset.d.ts +4 -3
  102. package/dist/types/daemon/auto-mode.d.ts +8 -0
  103. package/dist/types/daemon/cli.d.ts +9 -0
  104. package/dist/types/daemon/config.d.ts +19 -0
  105. package/dist/types/daemon/constants.d.ts +7 -0
  106. package/dist/types/daemon/env-merge.d.ts +5 -0
  107. package/dist/types/daemon/env-snapshot.d.ts +4 -0
  108. package/dist/types/daemon/flow-context.d.ts +28 -0
  109. package/dist/types/daemon/launchd.d.ts +29 -0
  110. package/dist/types/daemon/meta.d.ts +12 -0
  111. package/dist/types/daemon/models.d.ts +27 -0
  112. package/dist/types/daemon/request-settings.d.ts +27 -0
  113. package/dist/types/daemon/schtasks.d.ts +16 -0
  114. package/dist/types/daemon/server.d.ts +12 -0
  115. package/dist/types/daemon/summarize-progress.d.ts +2 -0
  116. package/dist/types/daemon/summarize.d.ts +59 -0
  117. package/dist/types/daemon/systemd.d.ts +16 -0
  118. package/dist/types/flags.d.ts +1 -1
  119. package/dist/types/llm/generate-text.d.ts +11 -5
  120. package/dist/types/llm/html-to-markdown.d.ts +4 -1
  121. package/dist/types/llm/prompt.d.ts +9 -0
  122. package/dist/types/llm/transcript-to-markdown.d.ts +34 -0
  123. package/dist/types/run/attachments.d.ts +4 -10
  124. package/dist/types/run/cache-state.d.ts +12 -0
  125. package/dist/types/run/cli-preflight.d.ts +1 -0
  126. package/dist/types/run/cookies/twitter.d.ts +17 -0
  127. package/dist/types/run/finish-line.d.ts +31 -1
  128. package/dist/types/run/flows/asset/preprocess.d.ts +5 -2
  129. package/dist/types/run/flows/asset/summary.d.ts +11 -0
  130. package/dist/types/run/flows/url/markdown.d.ts +3 -0
  131. package/dist/types/run/flows/url/summary.d.ts +6 -3
  132. package/dist/types/run/flows/url/types.d.ts +52 -18
  133. package/dist/types/run/help.d.ts +1 -0
  134. package/dist/types/run/run-env.d.ts +6 -0
  135. package/dist/types/run/summary-engine.d.ts +8 -2
  136. package/dist/types/run/summary-llm.d.ts +6 -3
  137. package/dist/types/tty/format.d.ts +1 -0
  138. package/dist/types/tty/spinner.d.ts +2 -0
  139. package/dist/types/version.d.ts +1 -1
  140. package/docs/README.md +5 -0
  141. package/docs/cache.md +72 -0
  142. package/docs/chrome-extension.md +180 -0
  143. package/docs/cli.md +6 -0
  144. package/docs/config.md +65 -1
  145. package/docs/extract-only.md +6 -0
  146. package/docs/firecrawl.md +6 -0
  147. package/docs/language.md +6 -0
  148. package/docs/llm.md +20 -0
  149. package/docs/manual-tests.md +6 -0
  150. package/docs/model-auto.md +6 -0
  151. package/docs/openai.md +6 -0
  152. package/docs/site/index.html +11 -1
  153. package/docs/smoketest.md +6 -0
  154. package/docs/website.md +6 -0
  155. package/docs/youtube.md +9 -2
  156. package/package.json +6 -9
  157. package/dist/cli.cjs +0 -80500
  158. package/dist/cli.cjs.map +0 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,57 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0 - Unreleased
4
+
5
+ ### Breaking
6
+
7
+ - ESM-only: `@steipete/summarize` + `@steipete/summarize-core` no longer support CommonJS `require()`; the CLI binary is now ESM.
8
+
9
+ ### Highlights
10
+
11
+ - Chrome: add a real **Side Panel** extension (MV3) that summarizes the **current tab** and renders streamed Markdown.
12
+ - Daemon: add `summarize daemon …` (localhost server on `127.0.0.1:8787`) for extension ↔ CLI integration.
13
+ - Autostart: macOS LaunchAgent, Linux systemd user service, Windows Scheduled Task
14
+ - Token pairing (shared secret)
15
+ - Streaming over SSE
16
+ - Emit finish-line metrics over SSE (panel footer + hover details)
17
+ - Commands: `install`, `status`, `restart`, `uninstall`, `run`
18
+ - Cache: add SQLite cache for transcripts/extractions/summaries with `--no-cache`, `--cache-stats`, `--clear-cache` + config (`cache.enabled/maxMb/ttlDays/path`).
19
+ - Finish line shows “Cached” for summary cache hits (CLI + daemon/extension)
20
+ - Daemon/Chrome stream cache status metadata (`summaryFromCache`)
21
+
22
+ ### Features
23
+
24
+ - YouTube: add `--youtube no-auto` to skip auto-generated captions and prefer creator-uploaded captions; fall back to `yt-dlp` transcription (thanks @dougvk!).
25
+ - CLI: add transcript → Markdown formatting via `--extract --format md --markdown-mode llm` (thanks @dougvk!).
26
+ - X/Twitter: auto-transcribe tweet videos via `yt-dlp`, using browser cookies (Chrome → Safari → Firefox) when available; set `TWITTER_COOKIE_SOURCE` / `TWITTER_*_PROFILE` to control cookie extraction order.
27
+ - Prompt overrides: add `--prompt`, `--prompt-file`, and config `prompt` to replace the default summary instructions.
28
+ - Chrome Side Panel: add length + language controls (presets + custom), forwarded to the daemon.
29
+ - Daemon API: `mode: "auto"` accepts both `url` + extracted page `text`; daemon picks the best pipeline (YouTube/podcasts/media → URL, otherwise prefer visible page text) with a fallback attempt.
30
+ - Daemon/Chrome: stream extra run metadata (`inputSummary`, `modelLabel`) over SSE for richer panel status.
31
+ - Core: expose lightweight URL helpers at `@steipete/summarize-core/content/url` (YouTube/Twitter/podcast/direct-media detection).
32
+ - Chrome Side Panel: new icon + extension `homepage_url` set to `summarize.sh`.
33
+ - Providers: add configurable API base URLs (config + env) for OpenAI/Anthropic/Google/xAI (thanks @bunchjesse for the nudge).
34
+
35
+ ### Improvements
36
+
37
+ - Chrome Side Panel: stream SSE from the panel (no MV3 background stalls), use runtime messaging to avoid “disconnected port” errors, and improve auto-summarize de-dupe.
38
+ - Chrome Side Panel UI: working status in header + 1px progress line (no layout jump), full-width subtitle, page title in header, idle subtitle shows `words/chars` (or media duration + words) + model, subtle metrics footer, continuous background, and native highlight/link accents.
39
+ - Daemon: prefer the installed env snapshot over launchd’s minimal environment (improves `yt-dlp` / `whisper.cpp` PATH reliability, especially for X/Twitter video transcription).
40
+ - X/Twitter: cookie handling now delegates to `yt-dlp --cookies-from-browser` (no sweet-cookie dependency).
41
+ - X/Twitter: skip yt-dlp transcript attempts for long-form tweet text (articles).
42
+ - Transcripts: show yt-dlp download progress bytes and stabilize totals to prevent bouncing progress bars.
43
+ - Finish line: show transcript source labels (`YouTube` / `podcast`) without repeating the label.
44
+ - Streaming: stop/clear progress UI before first streamed output and avoid leading blank lines on non-TTY stdout.
45
+ - URL flow: propagate `extracted.truncated` into the prompt context so summaries can reflect partial inputs.
46
+ - Daemon: unify URL/page summarization with the CLI flows (single code path; keeps extract/cache/model logic in sync).
47
+ - Prompts: auto-require Markdown section headings for longer summaries (xl/xxl or large custom lengths).
48
+
49
+ ## 0.7.1 - 2025-12-26
50
+
51
+ ### Fixed
52
+
53
+ - Packaging: `@steipete/summarize-core` now ships a CJS build for `require()` consumers (fixes `pnpm dlx @steipete/summarize --help` and the published CLI runtime).
54
+
3
55
  ## 0.7.0 - 2025-12-26
4
56
 
5
57
  ### Highlights
package/README.md CHANGED
@@ -51,6 +51,51 @@ Apple Silicon only (arm64).
51
51
  summarize "https://example.com"
52
52
  ```
53
53
 
54
+ ## Chrome Side Panel
55
+
56
+ Want a one-click “always-on” summarizer in Chrome (real Side Panel, not injected UI)?
57
+
58
+ This is a **Chrome extension** + a tiny local **daemon** (autostart service) that streams Markdown summaries for the **currently visible tab** into the Side Panel.
59
+
60
+ Docs + setup: `https://summarize.sh`
61
+
62
+ Quickstart (local daemon):
63
+
64
+ 1) Install summarize (choose one):
65
+ - `npm i -g @steipete/summarize`
66
+ - `brew install steipete/tap/summarize` (macOS arm64)
67
+ 2) Build + load the extension (unpacked):
68
+ - `pnpm -C apps/chrome-extension build`
69
+ - Chrome → `chrome://extensions` → Developer mode → “Load unpacked”
70
+ - Pick: `apps/chrome-extension/.output/chrome-mv3`
71
+ 3) Open the Side Panel → it shows a token + install command.
72
+ 4) Run the install command in Terminal:
73
+ - Installed binary: `summarize daemon install --token <TOKEN>`
74
+ - Repo/dev checkout: `pnpm summarize daemon install --token <TOKEN> --dev`
75
+ 5) Verify / debug:
76
+ - `summarize daemon status`
77
+ - `summarize daemon restart`
78
+
79
+ Notes:
80
+
81
+ - Summarization only runs when the Side Panel is open.
82
+ - “Auto” mode summarizes on navigation (incl. SPAs); otherwise use the button.
83
+ - The daemon is localhost-only and requires a shared token.
84
+ - Daemon autostart: macOS (launchd), Linux (systemd user), Windows (Scheduled Task).
85
+ - Tip: configure `free` via `summarize refresh-free` (requires `OPENROUTER_API_KEY`). Add `--set-default` to also set model=`free`, then set Model to `free` in extension settings.
86
+
87
+ - Docs: `docs/chrome-extension.md`
88
+ - Extension package/dev notes: `apps/chrome-extension/README.md`
89
+
90
+ Troubleshooting:
91
+
92
+ - **“Receiving end does not exist”**: Chrome didn’t inject the content script yet.
93
+ - Extension details → “Site access” → set to “On all sites” (or allow this domain)
94
+ - Reload the tab once.
95
+ - **“Failed to fetch” / daemon unreachable**:
96
+ - Run `summarize daemon status`
97
+ - Check logs: `~/.summarize/logs/daemon.err.log`
98
+
54
99
  Input can be a URL or a local file path:
55
100
 
56
101
  ```bash
@@ -161,7 +206,7 @@ Use `summarize --help` or `summarize help` for the full help text.
161
206
  - `--plain`: Keep raw output (no ANSI/OSC Markdown rendering)
162
207
  - `--no-color`: disable ANSI colors
163
208
  - `--format md|text`: website/file content format (default `text`)
164
- - `--markdown-mode off|auto|llm|readability`: HTML→Markdown conversion mode (default `readability`; `readability` uses Readability article HTML as input)
209
+ - `--markdown-mode off|auto|llm|readability`: Markdown conversion mode (default `readability`). For websites: HTML→Markdown conversion. For YouTube transcripts: `llm` formats the raw transcript into clean Markdown (headings/paragraphs).
165
210
  - `--preprocess off|auto|always`: controls `uvx markitdown` usage (default `auto`; `always` forces file preprocessing)
166
211
  - Install `uvx`: `brew install uv` (or https://astral.sh/uv/)
167
212
  - `--extract`: print extracted content and exit (no summary) — only for URLs
@@ -239,7 +284,7 @@ Non-YouTube URLs go through a “fetch → extract” pipeline. When the direct
239
284
 
240
285
  - `--firecrawl off|auto|always` (default `auto`)
241
286
  - `--extract --format md|text` (default `text`; if `--format` is omitted, `--extract` defaults to `md` for non-YouTube URLs)
242
- - `--markdown-mode off|auto|llm|readability` (default `readability`; only affects `--format md` for non-YouTube URLs)
287
+ - `--markdown-mode off|auto|llm|readability` (default `readability`; for non-YouTube URLs this controls HTML→Markdown conversion)
243
288
  - `auto`: use an LLM converter when configured; may fall back to `uvx markitdown`
244
289
  - `llm`: force LLM conversion (requires a configured model key)
245
290
  - `off`: disable LLM conversion (still may return Firecrawl Markdown when configured)
@@ -262,6 +307,12 @@ Environment variables for yt-dlp mode:
262
307
 
263
308
  Apify costs money but tends to be more reliable when captions exist.
264
309
 
310
+ Format the extracted transcript as Markdown (headings + paragraphs) via an LLM:
311
+
312
+ ```bash
313
+ summarize "https://www.youtube.com/watch?v=..." --extract --format md --markdown-mode llm
314
+ ```
315
+
265
316
  ## Media transcription (Whisper)
266
317
 
267
318
  `--video-mode transcript` forces audio/video inputs (local files or direct media URLs) through Whisper first, then summarizes the transcript text. Prefers local `whisper.cpp` when available; otherwise requires `OPENAI_API_KEY` or `FAL_KEY`.
package/dist/cli.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ if (!process.env.SUMMARIZE_GIT_SHA) process.env.SUMMARIZE_GIT_SHA = "c7a95ea4"
3
+ await import('./esm/cli.js')
@@ -0,0 +1,353 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, mkdirSync, rmSync, statSync } from 'node:fs';
3
+ import { dirname, isAbsolute, join, resolve as resolvePath } from 'node:path';
4
+ export const CACHE_FORMAT_VERSION = 1;
5
+ export const DEFAULT_CACHE_MAX_MB = 512;
6
+ export const DEFAULT_CACHE_TTL_DAYS = 30;
7
+ const TRANSCRIPT_SOURCES = [
8
+ 'youtubei',
9
+ 'captionTracks',
10
+ 'yt-dlp',
11
+ 'podcastTranscript',
12
+ 'whisper',
13
+ 'apify',
14
+ 'html',
15
+ 'unavailable',
16
+ 'unknown',
17
+ ];
18
+ function normalizeTranscriptSource(value) {
19
+ if (typeof value !== 'string')
20
+ return null;
21
+ return TRANSCRIPT_SOURCES.includes(value) ? value : null;
22
+ }
23
+ const isBun = typeof globalThis.Bun !== 'undefined';
24
+ let warningFilterInstalled = false;
25
+ const installSqliteWarningFilter = () => {
26
+ if (warningFilterInstalled)
27
+ return;
28
+ warningFilterInstalled = true;
29
+ const original = process.emitWarning.bind(process);
30
+ process.emitWarning = ((warning, ...args) => {
31
+ const message = typeof warning === 'string'
32
+ ? warning
33
+ : warning && typeof warning.message === 'string'
34
+ ? String(warning.message)
35
+ : '';
36
+ const type = typeof args[0] === 'string' ? args[0] : args[0]?.type;
37
+ const name = warning?.name;
38
+ const normalizedType = typeof type === 'string' ? type : typeof name === 'string' ? name : '';
39
+ if (normalizedType === 'ExperimentalWarning' && message.toLowerCase().includes('sqlite')) {
40
+ return;
41
+ }
42
+ return original(warning, ...args);
43
+ });
44
+ };
45
+ async function openSqlite(path) {
46
+ if (isBun) {
47
+ const mod = (await import('bun:sqlite'));
48
+ return new mod.Database(path);
49
+ }
50
+ installSqliteWarningFilter();
51
+ const mod = (await import('node:sqlite'));
52
+ return new mod.DatabaseSync(path);
53
+ }
54
+ function ensureDir(path) {
55
+ mkdirSync(path, { recursive: true });
56
+ }
57
+ function resolveHomeDir(env) {
58
+ const home = env.HOME?.trim() || env.USERPROFILE?.trim();
59
+ return home || null;
60
+ }
61
+ export function resolveCachePath({ env, cachePath, }) {
62
+ const home = resolveHomeDir(env);
63
+ const raw = cachePath?.trim();
64
+ if (raw && raw.length > 0) {
65
+ if (raw.startsWith('~')) {
66
+ if (!home)
67
+ return null;
68
+ const expanded = raw === '~' ? home : join(home, raw.slice(2));
69
+ return resolvePath(expanded);
70
+ }
71
+ return isAbsolute(raw) ? raw : home ? resolvePath(join(home, raw)) : null;
72
+ }
73
+ if (!home)
74
+ return null;
75
+ return join(home, '.summarize', 'cache.sqlite');
76
+ }
77
+ export async function createCacheStore({ path, maxBytes, transcriptNamespace, }) {
78
+ ensureDir(dirname(path));
79
+ const db = await openSqlite(path);
80
+ db.exec('PRAGMA journal_mode=WAL');
81
+ db.exec('PRAGMA synchronous=NORMAL');
82
+ db.exec('PRAGMA busy_timeout=5000');
83
+ db.exec('PRAGMA auto_vacuum=INCREMENTAL');
84
+ db.exec(`
85
+ CREATE TABLE IF NOT EXISTS cache_entries (
86
+ kind TEXT NOT NULL,
87
+ key TEXT NOT NULL,
88
+ value TEXT NOT NULL,
89
+ size_bytes INTEGER NOT NULL,
90
+ created_at INTEGER NOT NULL,
91
+ last_accessed_at INTEGER NOT NULL,
92
+ expires_at INTEGER,
93
+ PRIMARY KEY (kind, key)
94
+ )
95
+ `);
96
+ db.exec('CREATE INDEX IF NOT EXISTS idx_cache_accessed ON cache_entries(last_accessed_at)');
97
+ db.exec('CREATE INDEX IF NOT EXISTS idx_cache_expires ON cache_entries(expires_at)');
98
+ const stmtGet = db.prepare('SELECT value, expires_at, size_bytes FROM cache_entries WHERE kind = ? AND key = ?');
99
+ const stmtTouch = db.prepare('UPDATE cache_entries SET last_accessed_at = ? WHERE kind = ? AND key = ?');
100
+ const stmtDelete = db.prepare('DELETE FROM cache_entries WHERE kind = ? AND key = ?');
101
+ const stmtDeleteExpired = db.prepare('DELETE FROM cache_entries WHERE expires_at IS NOT NULL AND expires_at <= ?');
102
+ const stmtUpsert = db.prepare(`
103
+ INSERT INTO cache_entries (
104
+ kind, key, value, size_bytes, created_at, last_accessed_at, expires_at
105
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
106
+ ON CONFLICT(kind, key) DO UPDATE SET
107
+ value = excluded.value,
108
+ size_bytes = excluded.size_bytes,
109
+ created_at = excluded.created_at,
110
+ last_accessed_at = excluded.last_accessed_at,
111
+ expires_at = excluded.expires_at
112
+ `);
113
+ const stmtTotalSize = db.prepare('SELECT COALESCE(SUM(size_bytes), 0) AS total FROM cache_entries');
114
+ const stmtOldest = db.prepare('SELECT kind, key, size_bytes FROM cache_entries ORDER BY last_accessed_at ASC LIMIT ?');
115
+ const stmtClear = db.prepare('DELETE FROM cache_entries');
116
+ const sweepExpired = (now) => {
117
+ stmtDeleteExpired.run(now);
118
+ };
119
+ const enforceSize = () => {
120
+ if (!Number.isFinite(maxBytes) || maxBytes <= 0)
121
+ return;
122
+ const row = stmtTotalSize.get();
123
+ let total = typeof row?.total === 'number' ? row.total : 0;
124
+ if (total <= maxBytes)
125
+ return;
126
+ const batchSize = 50;
127
+ while (total > maxBytes) {
128
+ const rows = stmtOldest.all(batchSize);
129
+ if (rows.length === 0)
130
+ break;
131
+ for (const row of rows) {
132
+ if (total <= maxBytes)
133
+ break;
134
+ stmtDelete.run(row.kind, row.key);
135
+ total -= row.size_bytes ?? 0;
136
+ }
137
+ if (total <= maxBytes)
138
+ break;
139
+ }
140
+ db.exec('PRAGMA incremental_vacuum');
141
+ };
142
+ const readEntry = (kind, key, now) => {
143
+ const row = stmtGet.get(kind, key);
144
+ if (!row)
145
+ return null;
146
+ const expiresAt = row.expires_at;
147
+ if (typeof expiresAt === 'number' && expiresAt <= now) {
148
+ stmtDelete.run(kind, key);
149
+ return { ...row, expires_at: expiresAt };
150
+ }
151
+ stmtTouch.run(now, kind, key);
152
+ return row;
153
+ };
154
+ const getText = (kind, key) => {
155
+ const now = Date.now();
156
+ const row = readEntry(kind, key, now);
157
+ if (!row)
158
+ return null;
159
+ const expiresAt = row.expires_at;
160
+ if (typeof expiresAt === 'number' && expiresAt <= now)
161
+ return null;
162
+ return row.value;
163
+ };
164
+ const getJson = (kind, key) => {
165
+ const text = getText(kind, key);
166
+ if (!text)
167
+ return null;
168
+ try {
169
+ return JSON.parse(text);
170
+ }
171
+ catch {
172
+ return null;
173
+ }
174
+ };
175
+ const setText = (kind, key, value, ttlMs) => {
176
+ const now = Date.now();
177
+ sweepExpired(now);
178
+ const expiresAt = typeof ttlMs === 'number' ? now + ttlMs : null;
179
+ const sizeBytes = Buffer.byteLength(value, 'utf8');
180
+ stmtUpsert.run(kind, key, value, sizeBytes, now, now, expiresAt);
181
+ enforceSize();
182
+ };
183
+ const setJson = (kind, key, value, ttlMs) => {
184
+ setText(kind, key, JSON.stringify(value), ttlMs);
185
+ };
186
+ const clear = () => {
187
+ stmtClear.run();
188
+ db.exec('PRAGMA incremental_vacuum');
189
+ };
190
+ const close = () => {
191
+ try {
192
+ db.exec('PRAGMA wal_checkpoint(TRUNCATE)');
193
+ }
194
+ catch {
195
+ // ignore
196
+ }
197
+ db.close?.();
198
+ };
199
+ const normalizedTranscriptNamespace = typeof transcriptNamespace === 'string' && transcriptNamespace.trim().length > 0
200
+ ? transcriptNamespace.trim()
201
+ : null;
202
+ const getTranscriptKey = (url) => buildTranscriptCacheKey({
203
+ url,
204
+ namespace: normalizedTranscriptNamespace,
205
+ });
206
+ const transcriptCache = {
207
+ get: async ({ url }) => {
208
+ const now = Date.now();
209
+ const key = getTranscriptKey(url);
210
+ const row = readEntry('transcript', key, now);
211
+ if (!row)
212
+ return null;
213
+ const expired = typeof row.expires_at === 'number' && row.expires_at <= now;
214
+ let payload = null;
215
+ try {
216
+ payload = JSON.parse(row.value);
217
+ }
218
+ catch {
219
+ payload = null;
220
+ }
221
+ return {
222
+ content: payload?.content ?? null,
223
+ source: normalizeTranscriptSource(payload?.source) ?? null,
224
+ expired,
225
+ metadata: payload?.metadata ?? null,
226
+ };
227
+ },
228
+ set: async ({ url, content, source, ttlMs, metadata, service, resourceKey }) => {
229
+ const key = getTranscriptKey(url);
230
+ setJson('transcript', key, {
231
+ content,
232
+ source,
233
+ metadata: metadata ?? null,
234
+ service,
235
+ resourceKey,
236
+ namespace: normalizedTranscriptNamespace,
237
+ formatVersion: CACHE_FORMAT_VERSION,
238
+ }, ttlMs);
239
+ },
240
+ };
241
+ return { getText, getJson, setText, setJson, clear, close, transcriptCache };
242
+ }
243
+ export function clearCacheFiles(path) {
244
+ rmSync(path, { force: true });
245
+ rmSync(`${path}-wal`, { force: true });
246
+ rmSync(`${path}-shm`, { force: true });
247
+ }
248
+ export function hashString(value) {
249
+ return createHash('sha256').update(value).digest('hex');
250
+ }
251
+ export function hashJson(value) {
252
+ return hashString(JSON.stringify(value));
253
+ }
254
+ export function normalizeContentForHash(content) {
255
+ return content.replaceAll('\r\n', '\n').trim();
256
+ }
257
+ export function extractTaggedBlock(prompt, tag) {
258
+ const open = `<${tag}>`;
259
+ const close = `</${tag}>`;
260
+ const start = prompt.indexOf(open);
261
+ if (start === -1)
262
+ return null;
263
+ const end = prompt.indexOf(close, start + open.length);
264
+ if (end === -1)
265
+ return null;
266
+ return prompt.slice(start + open.length, end).trim();
267
+ }
268
+ export function buildPromptHash(prompt) {
269
+ const instructions = extractTaggedBlock(prompt, 'instructions') ?? prompt;
270
+ return hashString(instructions.trim());
271
+ }
272
+ export function buildLengthKey(lengthArg) {
273
+ return lengthArg.kind === 'preset'
274
+ ? `preset:${lengthArg.preset}`
275
+ : `chars:${lengthArg.maxCharacters}`;
276
+ }
277
+ export function buildLanguageKey(outputLanguage) {
278
+ return outputLanguage.kind === 'auto' ? 'auto' : outputLanguage.tag;
279
+ }
280
+ export function buildExtractCacheKey({ url, options, }) {
281
+ return hashJson({ url, options, formatVersion: CACHE_FORMAT_VERSION });
282
+ }
283
+ export function buildSummaryCacheKey({ contentHash, promptHash, model, lengthKey, languageKey, }) {
284
+ return hashJson({
285
+ contentHash,
286
+ promptHash,
287
+ model,
288
+ lengthKey,
289
+ languageKey,
290
+ formatVersion: CACHE_FORMAT_VERSION,
291
+ });
292
+ }
293
+ export function buildTranscriptCacheKey({ url, namespace, formatVersion, }) {
294
+ return hashJson({
295
+ url,
296
+ namespace,
297
+ formatVersion: formatVersion ?? CACHE_FORMAT_VERSION,
298
+ });
299
+ }
300
+ export async function readCacheStats(path) {
301
+ if (!existsSync(path))
302
+ return null;
303
+ const db = await openSqlite(path);
304
+ try {
305
+ db.exec('PRAGMA query_only = ON');
306
+ }
307
+ catch {
308
+ // ignore
309
+ }
310
+ const counts = {
311
+ extract: 0,
312
+ summary: 0,
313
+ transcript: 0,
314
+ };
315
+ const rows = db.prepare('SELECT kind, COUNT(*) AS count FROM cache_entries GROUP BY kind').all();
316
+ for (const row of rows) {
317
+ if (row?.kind && typeof row.count === 'number' && row.kind in counts) {
318
+ counts[row.kind] = row.count;
319
+ }
320
+ }
321
+ const totalRow = db.prepare('SELECT COUNT(*) AS count FROM cache_entries').get();
322
+ const totalEntries = typeof totalRow?.count === 'number' ? totalRow.count : 0;
323
+ db.close?.();
324
+ return {
325
+ path,
326
+ sizeBytes: getSqliteFileSizeBytes(path),
327
+ totalEntries,
328
+ counts,
329
+ };
330
+ }
331
+ export function getSqliteFileSizeBytes(path) {
332
+ let total = 0;
333
+ try {
334
+ total += statSync(path).size;
335
+ }
336
+ catch {
337
+ // ignore
338
+ }
339
+ try {
340
+ total += statSync(`${path}-wal`).size;
341
+ }
342
+ catch {
343
+ // ignore
344
+ }
345
+ try {
346
+ total += statSync(`${path}-shm`).size;
347
+ }
348
+ catch {
349
+ // ignore
350
+ }
351
+ return total;
352
+ }
353
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,18 @@ import JSON5 from 'json5';
4
4
  function isRecord(value) {
5
5
  return typeof value === 'object' && value !== null && !Array.isArray(value);
6
6
  }
7
+ function parseOptionalBaseUrl(raw) {
8
+ return typeof raw === 'string' && raw.trim().length > 0 ? raw.trim() : undefined;
9
+ }
10
+ function parseProviderBaseUrlConfig(raw, path, providerName) {
11
+ if (typeof raw === 'undefined')
12
+ return undefined;
13
+ if (!isRecord(raw)) {
14
+ throw new Error(`Invalid config file ${path}: "${providerName}" must be an object.`);
15
+ }
16
+ const baseUrl = parseOptionalBaseUrl(raw.baseUrl);
17
+ return typeof baseUrl === 'string' ? { baseUrl } : undefined;
18
+ }
7
19
  function parseAutoRuleKind(value) {
8
20
  return value === 'text' ||
9
21
  value === 'website' ||
@@ -304,6 +316,19 @@ export function loadSummarizeConfig({ env }) {
304
316
  }
305
317
  return trimmed;
306
318
  })();
319
+ const prompt = (() => {
320
+ const value = parsed.prompt;
321
+ if (typeof value === 'undefined')
322
+ return undefined;
323
+ if (typeof value !== 'string') {
324
+ throw new Error(`Invalid config file ${path}: "prompt" must be a string.`);
325
+ }
326
+ const trimmed = value.trim();
327
+ if (!trimmed) {
328
+ throw new Error(`Invalid config file ${path}: "prompt" must not be empty.`);
329
+ }
330
+ return trimmed;
331
+ })();
307
332
  const models = (() => {
308
333
  const root = parsed;
309
334
  if (typeof root.bags !== 'undefined') {
@@ -345,6 +370,46 @@ export function loadSummarizeConfig({ env }) {
345
370
  }
346
371
  return Object.keys(out).length > 0 ? out : undefined;
347
372
  })();
373
+ const cache = (() => {
374
+ const value = parsed.cache;
375
+ if (typeof value === 'undefined')
376
+ return undefined;
377
+ if (!isRecord(value)) {
378
+ throw new Error(`Invalid config file ${path}: "cache" must be an object.`);
379
+ }
380
+ const enabled = typeof value.enabled === 'boolean' ? value.enabled : undefined;
381
+ const maxMbRaw = value.maxMb;
382
+ const maxMb = typeof maxMbRaw === 'number' && Number.isFinite(maxMbRaw) && maxMbRaw > 0
383
+ ? maxMbRaw
384
+ : typeof maxMbRaw === 'undefined'
385
+ ? undefined
386
+ : (() => {
387
+ throw new Error(`Invalid config file ${path}: "cache.maxMb" must be a number.`);
388
+ })();
389
+ const ttlDaysRaw = value.ttlDays;
390
+ const ttlDays = typeof ttlDaysRaw === 'number' && Number.isFinite(ttlDaysRaw) && ttlDaysRaw > 0
391
+ ? ttlDaysRaw
392
+ : typeof ttlDaysRaw === 'undefined'
393
+ ? undefined
394
+ : (() => {
395
+ throw new Error(`Invalid config file ${path}: "cache.ttlDays" must be a number.`);
396
+ })();
397
+ const pathValue = typeof value.path === 'string' && value.path.trim().length > 0
398
+ ? value.path.trim()
399
+ : typeof value.path === 'undefined'
400
+ ? undefined
401
+ : (() => {
402
+ throw new Error(`Invalid config file ${path}: "cache.path" must be a string.`);
403
+ })();
404
+ return enabled || maxMb || ttlDays || pathValue
405
+ ? {
406
+ ...(typeof enabled === 'boolean' ? { enabled } : {}),
407
+ ...(typeof maxMb === 'number' ? { maxMb } : {}),
408
+ ...(typeof ttlDays === 'number' ? { ttlDays } : {}),
409
+ ...(typeof pathValue === 'string' ? { path: pathValue } : {}),
410
+ }
411
+ : undefined;
412
+ })();
348
413
  const media = (() => {
349
414
  const value = parsed.media;
350
415
  if (!isRecord(value))
@@ -416,6 +481,7 @@ export function loadSummarizeConfig({ env }) {
416
481
  if (!isRecord(value)) {
417
482
  throw new Error(`Invalid config file ${path}: "openai" must be an object.`);
418
483
  }
484
+ const baseUrl = parseOptionalBaseUrl(value.baseUrl);
419
485
  const useChatCompletions = typeof value.useChatCompletions === 'boolean' ? value.useChatCompletions : undefined;
420
486
  const whisperUsdPerMinuteRaw = value.whisperUsdPerMinute;
421
487
  const whisperUsdPerMinute = typeof whisperUsdPerMinuteRaw === 'number' &&
@@ -423,22 +489,33 @@ export function loadSummarizeConfig({ env }) {
423
489
  whisperUsdPerMinuteRaw > 0
424
490
  ? whisperUsdPerMinuteRaw
425
491
  : undefined;
426
- return typeof useChatCompletions === 'boolean' || typeof whisperUsdPerMinute === 'number'
492
+ return typeof baseUrl === 'string' ||
493
+ typeof useChatCompletions === 'boolean' ||
494
+ typeof whisperUsdPerMinute === 'number'
427
495
  ? {
496
+ ...(typeof baseUrl === 'string' ? { baseUrl } : {}),
428
497
  ...(typeof useChatCompletions === 'boolean' ? { useChatCompletions } : {}),
429
498
  ...(typeof whisperUsdPerMinute === 'number' ? { whisperUsdPerMinute } : {}),
430
499
  }
431
500
  : undefined;
432
501
  })();
502
+ const anthropic = parseProviderBaseUrlConfig(parsed.anthropic, path, 'anthropic');
503
+ const google = parseProviderBaseUrlConfig(parsed.google, path, 'google');
504
+ const xai = parseProviderBaseUrlConfig(parsed.xai, path, 'xai');
433
505
  return {
434
506
  config: {
435
507
  ...(model ? { model } : {}),
436
508
  ...(language ? { language } : {}),
509
+ ...(prompt ? { prompt } : {}),
510
+ ...(cache ? { cache } : {}),
437
511
  ...(models ? { models } : {}),
438
512
  ...(media ? { media } : {}),
439
513
  ...(output ? { output } : {}),
440
514
  ...(cli ? { cli } : {}),
441
515
  ...(openai ? { openai } : {}),
516
+ ...(anthropic ? { anthropic } : {}),
517
+ ...(google ? { google } : {}),
518
+ ...(xai ? { xai } : {}),
442
519
  },
443
520
  path,
444
521
  };