@steipete/summarize 0.10.0 → 0.11.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 (374) hide show
  1. package/CHANGELOG.md +80 -28
  2. package/README.md +115 -30
  3. package/dist/cli.js +1 -1
  4. package/dist/esm/cache.js +67 -65
  5. package/dist/esm/cache.js.map +1 -1
  6. package/dist/esm/cli-main.js +27 -27
  7. package/dist/esm/cli-main.js.map +1 -1
  8. package/dist/esm/cli.js +2 -2
  9. package/dist/esm/cli.js.map +1 -1
  10. package/dist/esm/config.js +310 -166
  11. package/dist/esm/config.js.map +1 -1
  12. package/dist/esm/content/asset.js +53 -50
  13. package/dist/esm/content/asset.js.map +1 -1
  14. package/dist/esm/content/index.js +1 -1
  15. package/dist/esm/content/index.js.map +1 -1
  16. package/dist/esm/costs.js +1 -1
  17. package/dist/esm/costs.js.map +1 -1
  18. package/dist/esm/daemon/agent.js +165 -164
  19. package/dist/esm/daemon/agent.js.map +1 -1
  20. package/dist/esm/daemon/auto-mode.js +3 -3
  21. package/dist/esm/daemon/auto-mode.js.map +1 -1
  22. package/dist/esm/daemon/chat.js +16 -14
  23. package/dist/esm/daemon/chat.js.map +1 -1
  24. package/dist/esm/daemon/cli-entrypoint.js +72 -0
  25. package/dist/esm/daemon/cli-entrypoint.js.map +1 -0
  26. package/dist/esm/daemon/cli.js +63 -87
  27. package/dist/esm/daemon/cli.js.map +1 -1
  28. package/dist/esm/daemon/config.js +15 -15
  29. package/dist/esm/daemon/config.js.map +1 -1
  30. package/dist/esm/daemon/constants.js +6 -6
  31. package/dist/esm/daemon/constants.js.map +1 -1
  32. package/dist/esm/daemon/env-merge.js.map +1 -1
  33. package/dist/esm/daemon/env-snapshot.js +36 -31
  34. package/dist/esm/daemon/env-snapshot.js.map +1 -1
  35. package/dist/esm/daemon/flow-context.js +59 -28
  36. package/dist/esm/daemon/flow-context.js.map +1 -1
  37. package/dist/esm/daemon/launchd.js +100 -55
  38. package/dist/esm/daemon/launchd.js.map +1 -1
  39. package/dist/esm/daemon/meta.js +5 -5
  40. package/dist/esm/daemon/meta.js.map +1 -1
  41. package/dist/esm/daemon/models.js +54 -31
  42. package/dist/esm/daemon/models.js.map +1 -1
  43. package/dist/esm/daemon/process-registry.js +15 -15
  44. package/dist/esm/daemon/process-registry.js.map +1 -1
  45. package/dist/esm/daemon/schtasks.js +42 -42
  46. package/dist/esm/daemon/schtasks.js.map +1 -1
  47. package/dist/esm/daemon/server.js +248 -244
  48. package/dist/esm/daemon/server.js.map +1 -1
  49. package/dist/esm/daemon/summarize-progress.js +11 -11
  50. package/dist/esm/daemon/summarize-progress.js.map +1 -1
  51. package/dist/esm/daemon/summarize.js +29 -29
  52. package/dist/esm/daemon/summarize.js.map +1 -1
  53. package/dist/esm/daemon/systemd.js +47 -47
  54. package/dist/esm/daemon/systemd.js.map +1 -1
  55. package/dist/esm/firecrawl.js +12 -12
  56. package/dist/esm/firecrawl.js.map +1 -1
  57. package/dist/esm/flags.js +32 -32
  58. package/dist/esm/flags.js.map +1 -1
  59. package/dist/esm/index.js +3 -3
  60. package/dist/esm/index.js.map +1 -1
  61. package/dist/esm/language.js +1 -1
  62. package/dist/esm/language.js.map +1 -1
  63. package/dist/esm/llm/cli.js +128 -64
  64. package/dist/esm/llm/cli.js.map +1 -1
  65. package/dist/esm/llm/errors.js +1 -1
  66. package/dist/esm/llm/errors.js.map +1 -1
  67. package/dist/esm/llm/generate-text.js +107 -98
  68. package/dist/esm/llm/generate-text.js.map +1 -1
  69. package/dist/esm/llm/google-models.js +17 -17
  70. package/dist/esm/llm/google-models.js.map +1 -1
  71. package/dist/esm/llm/html-to-markdown.js +3 -3
  72. package/dist/esm/llm/html-to-markdown.js.map +1 -1
  73. package/dist/esm/llm/model-id.js +38 -16
  74. package/dist/esm/llm/model-id.js.map +1 -1
  75. package/dist/esm/llm/prompt.js +5 -5
  76. package/dist/esm/llm/prompt.js.map +1 -1
  77. package/dist/esm/llm/providers/anthropic.js +33 -33
  78. package/dist/esm/llm/providers/anthropic.js.map +1 -1
  79. package/dist/esm/llm/providers/google.js +19 -19
  80. package/dist/esm/llm/providers/google.js.map +1 -1
  81. package/dist/esm/llm/providers/models.js +30 -30
  82. package/dist/esm/llm/providers/models.js.map +1 -1
  83. package/dist/esm/llm/providers/openai.js +35 -34
  84. package/dist/esm/llm/providers/openai.js.map +1 -1
  85. package/dist/esm/llm/providers/shared.js +8 -8
  86. package/dist/esm/llm/providers/shared.js.map +1 -1
  87. package/dist/esm/llm/transcript-to-markdown.js +9 -5
  88. package/dist/esm/llm/transcript-to-markdown.js.map +1 -1
  89. package/dist/esm/llm/usage.js +18 -18
  90. package/dist/esm/llm/usage.js.map +1 -1
  91. package/dist/esm/logging/daemon.js +21 -21
  92. package/dist/esm/logging/daemon.js.map +1 -1
  93. package/dist/esm/logging/ring-file.js +5 -5
  94. package/dist/esm/logging/ring-file.js.map +1 -1
  95. package/dist/esm/markitdown.js +21 -19
  96. package/dist/esm/markitdown.js.map +1 -1
  97. package/dist/esm/media-cache.js +39 -39
  98. package/dist/esm/media-cache.js.map +1 -1
  99. package/dist/esm/model-auto.js +175 -106
  100. package/dist/esm/model-auto.js.map +1 -1
  101. package/dist/esm/model-spec.js +52 -42
  102. package/dist/esm/model-spec.js.map +1 -1
  103. package/dist/esm/pricing/litellm.js +4 -4
  104. package/dist/esm/pricing/litellm.js.map +1 -1
  105. package/dist/esm/processes.js +1 -1
  106. package/dist/esm/processes.js.map +1 -1
  107. package/dist/esm/prompts/index.js +1 -1
  108. package/dist/esm/prompts/index.js.map +1 -1
  109. package/dist/esm/refresh-free.js +81 -81
  110. package/dist/esm/refresh-free.js.map +1 -1
  111. package/dist/esm/run/attachments.js +47 -44
  112. package/dist/esm/run/attachments.js.map +1 -1
  113. package/dist/esm/run/bird.js +26 -26
  114. package/dist/esm/run/bird.js.map +1 -1
  115. package/dist/esm/run/cache-state.js +7 -7
  116. package/dist/esm/run/cache-state.js.map +1 -1
  117. package/dist/esm/run/cli-fallback-state.js +45 -0
  118. package/dist/esm/run/cli-fallback-state.js.map +1 -0
  119. package/dist/esm/run/cli-preflight.js +24 -24
  120. package/dist/esm/run/cli-preflight.js.map +1 -1
  121. package/dist/esm/run/constants.js +12 -12
  122. package/dist/esm/run/constants.js.map +1 -1
  123. package/dist/esm/run/cookies/twitter.js +47 -47
  124. package/dist/esm/run/cookies/twitter.js.map +1 -1
  125. package/dist/esm/run/env.js +21 -15
  126. package/dist/esm/run/env.js.map +1 -1
  127. package/dist/esm/run/fetch-with-timeout.js +4 -4
  128. package/dist/esm/run/fetch-with-timeout.js.map +1 -1
  129. package/dist/esm/run/finish-line.js +68 -68
  130. package/dist/esm/run/finish-line.js.map +1 -1
  131. package/dist/esm/run/flows/asset/extract.js +15 -15
  132. package/dist/esm/run/flows/asset/extract.js.map +1 -1
  133. package/dist/esm/run/flows/asset/input.js +47 -66
  134. package/dist/esm/run/flows/asset/input.js.map +1 -1
  135. package/dist/esm/run/flows/asset/media-policy.js +1 -1
  136. package/dist/esm/run/flows/asset/media-policy.js.map +1 -1
  137. package/dist/esm/run/flows/asset/media.js +49 -40
  138. package/dist/esm/run/flows/asset/media.js.map +1 -1
  139. package/dist/esm/run/flows/asset/output.js +12 -12
  140. package/dist/esm/run/flows/asset/output.js.map +1 -1
  141. package/dist/esm/run/flows/asset/preprocess.js +79 -44
  142. package/dist/esm/run/flows/asset/preprocess.js.map +1 -1
  143. package/dist/esm/run/flows/asset/summary.js +173 -106
  144. package/dist/esm/run/flows/asset/summary.js.map +1 -1
  145. package/dist/esm/run/flows/url/extract.js +26 -26
  146. package/dist/esm/run/flows/url/extract.js.map +1 -1
  147. package/dist/esm/run/flows/url/flow.js +104 -98
  148. package/dist/esm/run/flows/url/flow.js.map +1 -1
  149. package/dist/esm/run/flows/url/markdown.js +57 -57
  150. package/dist/esm/run/flows/url/markdown.js.map +1 -1
  151. package/dist/esm/run/flows/url/slides-output.js +61 -59
  152. package/dist/esm/run/flows/url/slides-output.js.map +1 -1
  153. package/dist/esm/run/flows/url/slides-text.js +85 -85
  154. package/dist/esm/run/flows/url/slides-text.js.map +1 -1
  155. package/dist/esm/run/flows/url/summary.js +174 -107
  156. package/dist/esm/run/flows/url/summary.js.map +1 -1
  157. package/dist/esm/run/format.js +10 -10
  158. package/dist/esm/run/format.js.map +1 -1
  159. package/dist/esm/run/help.js +141 -135
  160. package/dist/esm/run/help.js.map +1 -1
  161. package/dist/esm/run/logging.js +10 -10
  162. package/dist/esm/run/logging.js.map +1 -1
  163. package/dist/esm/run/markdown.js +12 -12
  164. package/dist/esm/run/markdown.js.map +1 -1
  165. package/dist/esm/run/media-cache-state.js +5 -5
  166. package/dist/esm/run/media-cache-state.js.map +1 -1
  167. package/dist/esm/run/model-attempts.js.map +1 -1
  168. package/dist/esm/run/openrouter.js +11 -11
  169. package/dist/esm/run/openrouter.js.map +1 -1
  170. package/dist/esm/run/progress.js +1 -1
  171. package/dist/esm/run/progress.js.map +1 -1
  172. package/dist/esm/run/run-config.js +16 -16
  173. package/dist/esm/run/run-config.js.map +1 -1
  174. package/dist/esm/run/run-context.js +2 -2
  175. package/dist/esm/run/run-context.js.map +1 -1
  176. package/dist/esm/run/run-env.js +55 -54
  177. package/dist/esm/run/run-env.js.map +1 -1
  178. package/dist/esm/run/run-input.js +3 -3
  179. package/dist/esm/run/run-input.js.map +1 -1
  180. package/dist/esm/run/run-metrics.js +16 -16
  181. package/dist/esm/run/run-metrics.js.map +1 -1
  182. package/dist/esm/run/run-models.js +28 -23
  183. package/dist/esm/run/run-models.js.map +1 -1
  184. package/dist/esm/run/run-output.js +3 -3
  185. package/dist/esm/run/run-output.js.map +1 -1
  186. package/dist/esm/run/run-settings.js +83 -34
  187. package/dist/esm/run/run-settings.js.map +1 -1
  188. package/dist/esm/run/run-stream.js +4 -4
  189. package/dist/esm/run/run-stream.js.map +1 -1
  190. package/dist/esm/run/runner.js +166 -127
  191. package/dist/esm/run/runner.js.map +1 -1
  192. package/dist/esm/run/slides-cli.js +43 -42
  193. package/dist/esm/run/slides-cli.js.map +1 -1
  194. package/dist/esm/run/slides-render.js +36 -36
  195. package/dist/esm/run/slides-render.js.map +1 -1
  196. package/dist/esm/run/stdin-temp-file.js +77 -0
  197. package/dist/esm/run/stdin-temp-file.js.map +1 -0
  198. package/dist/esm/run/stream-output.js +7 -7
  199. package/dist/esm/run/stream-output.js.map +1 -1
  200. package/dist/esm/run/streaming.js +16 -16
  201. package/dist/esm/run/streaming.js.map +1 -1
  202. package/dist/esm/run/summary-engine.js +57 -51
  203. package/dist/esm/run/summary-engine.js.map +1 -1
  204. package/dist/esm/run/summary-llm.js +3 -3
  205. package/dist/esm/run/summary-llm.js.map +1 -1
  206. package/dist/esm/run/terminal.js +4 -4
  207. package/dist/esm/run/terminal.js.map +1 -1
  208. package/dist/esm/run/tips.js +2 -2
  209. package/dist/esm/run/tips.js.map +1 -1
  210. package/dist/esm/run/transcriber-cli.js +49 -49
  211. package/dist/esm/run/transcriber-cli.js.map +1 -1
  212. package/dist/esm/run.js +1 -1
  213. package/dist/esm/run.js.map +1 -1
  214. package/dist/esm/shared/contracts.js +1 -1
  215. package/dist/esm/shared/contracts.js.map +1 -1
  216. package/dist/esm/shared/sse-events.js +16 -16
  217. package/dist/esm/shared/sse-events.js.map +1 -1
  218. package/dist/esm/shared/streaming-merge.js +3 -3
  219. package/dist/esm/shared/streaming-merge.js.map +1 -1
  220. package/dist/esm/slides/extract.js +258 -249
  221. package/dist/esm/slides/extract.js.map +1 -1
  222. package/dist/esm/slides/index.js +3 -3
  223. package/dist/esm/slides/index.js.map +1 -1
  224. package/dist/esm/slides/settings.js +14 -14
  225. package/dist/esm/slides/settings.js.map +1 -1
  226. package/dist/esm/slides/store.js +9 -9
  227. package/dist/esm/slides/store.js.map +1 -1
  228. package/dist/esm/tty/format.js +13 -13
  229. package/dist/esm/tty/format.js.map +1 -1
  230. package/dist/esm/tty/osc-progress.js +1 -1
  231. package/dist/esm/tty/osc-progress.js.map +1 -1
  232. package/dist/esm/tty/progress/fetch-html.js +14 -14
  233. package/dist/esm/tty/progress/fetch-html.js.map +1 -1
  234. package/dist/esm/tty/progress/transcript.js +70 -62
  235. package/dist/esm/tty/progress/transcript.js.map +1 -1
  236. package/dist/esm/tty/spinner.js +20 -9
  237. package/dist/esm/tty/spinner.js.map +1 -1
  238. package/dist/esm/tty/theme.js +92 -92
  239. package/dist/esm/tty/theme.js.map +1 -1
  240. package/dist/esm/tty/website-progress.js +32 -32
  241. package/dist/esm/tty/website-progress.js.map +1 -1
  242. package/dist/esm/version.js +29 -29
  243. package/dist/esm/version.js.map +1 -1
  244. package/dist/types/cache.d.ts +6 -6
  245. package/dist/types/config.d.ts +49 -7
  246. package/dist/types/content/asset.d.ts +8 -6
  247. package/dist/types/content/index.d.ts +1 -1
  248. package/dist/types/costs.d.ts +3 -3
  249. package/dist/types/daemon/agent.d.ts +1 -1
  250. package/dist/types/daemon/auto-mode.d.ts +3 -3
  251. package/dist/types/daemon/chat.d.ts +2 -2
  252. package/dist/types/daemon/cli-entrypoint.d.ts +2 -0
  253. package/dist/types/daemon/config.d.ts +2 -2
  254. package/dist/types/daemon/env-merge.d.ts +1 -1
  255. package/dist/types/daemon/env-snapshot.d.ts +1 -1
  256. package/dist/types/daemon/flow-context.d.ts +7 -7
  257. package/dist/types/daemon/launchd.d.ts +8 -0
  258. package/dist/types/daemon/models.d.ts +6 -2
  259. package/dist/types/daemon/process-registry.d.ts +5 -5
  260. package/dist/types/daemon/server.d.ts +2 -2
  261. package/dist/types/daemon/summarize-progress.d.ts +1 -1
  262. package/dist/types/daemon/summarize.d.ts +7 -7
  263. package/dist/types/firecrawl.d.ts +1 -1
  264. package/dist/types/flags.d.ts +11 -11
  265. package/dist/types/index.d.ts +4 -4
  266. package/dist/types/language.d.ts +1 -1
  267. package/dist/types/llm/attachments.d.ts +1 -1
  268. package/dist/types/llm/cli.d.ts +3 -3
  269. package/dist/types/llm/generate-text.d.ts +7 -7
  270. package/dist/types/llm/html-to-markdown.d.ts +3 -3
  271. package/dist/types/llm/model-id.d.ts +1 -1
  272. package/dist/types/llm/prompt.d.ts +2 -2
  273. package/dist/types/llm/providers/anthropic.d.ts +3 -3
  274. package/dist/types/llm/providers/google.d.ts +3 -3
  275. package/dist/types/llm/providers/models.d.ts +2 -2
  276. package/dist/types/llm/providers/openai.d.ts +4 -4
  277. package/dist/types/llm/providers/shared.d.ts +2 -2
  278. package/dist/types/llm/transcript-to-markdown.d.ts +4 -2
  279. package/dist/types/llm/usage.d.ts +1 -1
  280. package/dist/types/logging/daemon.d.ts +4 -4
  281. package/dist/types/markitdown.d.ts +1 -1
  282. package/dist/types/media-cache.d.ts +2 -2
  283. package/dist/types/model-auto.d.ts +14 -4
  284. package/dist/types/model-spec.d.ts +10 -10
  285. package/dist/types/pricing/litellm.d.ts +1 -1
  286. package/dist/types/processes.d.ts +1 -1
  287. package/dist/types/prompts/index.d.ts +1 -1
  288. package/dist/types/run/attachments.d.ts +7 -7
  289. package/dist/types/run/bird.d.ts +2 -2
  290. package/dist/types/run/cache-state.d.ts +2 -2
  291. package/dist/types/run/cli-fallback-state.d.ts +6 -0
  292. package/dist/types/run/constants.d.ts +1 -1
  293. package/dist/types/run/cookies/twitter.d.ts +1 -1
  294. package/dist/types/run/env.d.ts +1 -1
  295. package/dist/types/run/finish-line.d.ts +5 -5
  296. package/dist/types/run/flows/asset/extract.d.ts +4 -4
  297. package/dist/types/run/flows/asset/input.d.ts +9 -3
  298. package/dist/types/run/flows/asset/media.d.ts +1 -1
  299. package/dist/types/run/flows/asset/output.d.ts +5 -5
  300. package/dist/types/run/flows/asset/preprocess.d.ts +23 -17
  301. package/dist/types/run/flows/asset/summary.d.ts +19 -17
  302. package/dist/types/run/flows/url/extract.d.ts +1 -1
  303. package/dist/types/run/flows/url/flow.d.ts +1 -1
  304. package/dist/types/run/flows/url/markdown.d.ts +6 -6
  305. package/dist/types/run/flows/url/slides-output.d.ts +7 -7
  306. package/dist/types/run/flows/url/slides-text.d.ts +9 -9
  307. package/dist/types/run/flows/url/summary.d.ts +11 -11
  308. package/dist/types/run/flows/url/types.d.ts +26 -22
  309. package/dist/types/run/format.d.ts +3 -3
  310. package/dist/types/run/help.d.ts +1 -1
  311. package/dist/types/run/media-cache-state.d.ts +2 -2
  312. package/dist/types/run/model-attempts.d.ts +1 -1
  313. package/dist/types/run/run-config.d.ts +4 -4
  314. package/dist/types/run/run-context.d.ts +3 -1
  315. package/dist/types/run/run-env.d.ts +3 -1
  316. package/dist/types/run/run-input.d.ts +2 -2
  317. package/dist/types/run/run-metrics.d.ts +3 -3
  318. package/dist/types/run/run-models.d.ts +3 -2
  319. package/dist/types/run/run-output.d.ts +1 -1
  320. package/dist/types/run/run-settings.d.ts +15 -6
  321. package/dist/types/run/run-stream.d.ts +2 -2
  322. package/dist/types/run/runner.d.ts +3 -2
  323. package/dist/types/run/slides-render.d.ts +4 -4
  324. package/dist/types/run/stdin-temp-file.d.ts +9 -0
  325. package/dist/types/run/stream-output.d.ts +1 -1
  326. package/dist/types/run/streaming.d.ts +4 -4
  327. package/dist/types/run/summary-engine.d.ts +11 -11
  328. package/dist/types/run/summary-llm.d.ts +5 -5
  329. package/dist/types/run/types.d.ts +4 -4
  330. package/dist/types/run.d.ts +1 -1
  331. package/dist/types/shared/contracts.d.ts +2 -2
  332. package/dist/types/shared/sse-events.d.ts +9 -9
  333. package/dist/types/slides/extract.d.ts +5 -4
  334. package/dist/types/slides/index.d.ts +5 -5
  335. package/dist/types/slides/store.d.ts +2 -2
  336. package/dist/types/slides/types.d.ts +2 -2
  337. package/dist/types/tty/osc-progress.d.ts +5 -5
  338. package/dist/types/tty/progress/fetch-html.d.ts +3 -3
  339. package/dist/types/tty/progress/transcript.d.ts +3 -3
  340. package/dist/types/tty/spinner.d.ts +2 -2
  341. package/dist/types/tty/theme.d.ts +2 -2
  342. package/dist/types/tty/website-progress.d.ts +3 -3
  343. package/dist/types/version.d.ts +1 -1
  344. package/docs/agent.md +38 -4
  345. package/docs/assets/site.js +46 -46
  346. package/docs/chrome-extension.md +11 -5
  347. package/docs/cli.md +59 -13
  348. package/docs/config.md +59 -10
  349. package/docs/extract-only.md +2 -0
  350. package/docs/index.html +33 -14
  351. package/docs/llm.md +7 -4
  352. package/docs/media.md +5 -4
  353. package/docs/model-auto.md +3 -2
  354. package/docs/nvidia-onnx-transcription.md +3 -3
  355. package/docs/openai.md +1 -1
  356. package/docs/releasing.md +3 -0
  357. package/docs/site/404.html +4 -1
  358. package/docs/site/assets/site.js +46 -46
  359. package/docs/site/docs/chrome-extension.html +18 -6
  360. package/docs/site/docs/config.html +29 -8
  361. package/docs/site/docs/extract-only.html +16 -4
  362. package/docs/site/docs/firecrawl.html +12 -3
  363. package/docs/site/docs/index.html +35 -6
  364. package/docs/site/docs/llm.html +19 -5
  365. package/docs/site/docs/openai.html +18 -5
  366. package/docs/site/docs/website.html +29 -9
  367. package/docs/site/docs/youtube.html +12 -3
  368. package/docs/site/index.html +33 -14
  369. package/docs/slides.md +13 -5
  370. package/docs/smoketest.md +29 -20
  371. package/docs/timestamps.md +21 -0
  372. package/docs/website.md +2 -1
  373. package/docs/youtube.md +4 -0
  374. package/package.json +36 -35
@@ -1,32 +1,32 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { createReadStream, promises as fs } from 'node:fs';
3
- import http from 'node:http';
4
- import path from 'node:path';
5
- import { Writable } from 'node:stream';
6
- import { loadSummarizeConfig } from '../config.js';
7
- import { createDaemonLogger } from '../logging/daemon.js';
8
- import { runWithProcessContext, setProcessObserver } from '../processes.js';
9
- import { refreshFree } from '../refresh-free.js';
10
- import { createCacheStateFromConfig, refreshCacheStoreIfMissing } from '../run/cache-state.js';
11
- import { resolveExecutableInPath } from '../run/env.js';
12
- import { formatModelLabelForDisplay } from '../run/finish-line.js';
13
- import { createMediaCacheFromConfig } from '../run/media-cache-state.js';
14
- import { resolveRunOverrides } from '../run/run-settings.js';
15
- import { encodeSseEvent } from '../shared/sse-events.js';
16
- import { resolveSlideImagePath, resolveSlideSettings } from '../slides/index.js';
17
- import { resolvePackageVersion } from '../version.js';
18
- import { completeAgentResponse, streamAgentResponse } from './agent.js';
19
- import { resolveAutoDaemonMode } from './auto-mode.js';
20
- import { DAEMON_HOST, DAEMON_PORT_DEFAULT } from './constants.js';
21
- import { resolveDaemonLogPaths } from './launchd.js';
22
- import { buildModelPickerOptions } from './models.js';
23
- import { buildProcessListResult, buildProcessLogsResult, ProcessRegistry, } from './process-registry.js';
24
- import { extractContentForUrl, streamSummaryForUrl, streamSummaryForVisiblePage, } from './summarize.js';
1
+ import { randomUUID } from "node:crypto";
2
+ import { createReadStream, promises as fs } from "node:fs";
3
+ import http from "node:http";
4
+ import path from "node:path";
5
+ import { Writable } from "node:stream";
6
+ import { loadSummarizeConfig } from "../config.js";
7
+ import { createDaemonLogger } from "../logging/daemon.js";
8
+ import { runWithProcessContext, setProcessObserver } from "../processes.js";
9
+ import { refreshFree } from "../refresh-free.js";
10
+ import { createCacheStateFromConfig, refreshCacheStoreIfMissing } from "../run/cache-state.js";
11
+ import { resolveExecutableInPath } from "../run/env.js";
12
+ import { formatModelLabelForDisplay } from "../run/finish-line.js";
13
+ import { createMediaCacheFromConfig } from "../run/media-cache-state.js";
14
+ import { resolveRunOverrides } from "../run/run-settings.js";
15
+ import { encodeSseEvent } from "../shared/sse-events.js";
16
+ import { resolveSlideImagePath, resolveSlideSettings } from "../slides/index.js";
17
+ import { resolvePackageVersion } from "../version.js";
18
+ import { completeAgentResponse, streamAgentResponse } from "./agent.js";
19
+ import { resolveAutoDaemonMode } from "./auto-mode.js";
20
+ import { DAEMON_HOST, DAEMON_PORT_DEFAULT } from "./constants.js";
21
+ import { resolveDaemonLogPaths } from "./launchd.js";
22
+ import { buildModelPickerOptions } from "./models.js";
23
+ import { buildProcessListResult, buildProcessLogsResult, ProcessRegistry, } from "./process-registry.js";
24
+ import { extractContentForUrl, streamSummaryForUrl, streamSummaryForVisiblePage, } from "./summarize.js";
25
25
  function json(res, status, payload, headers) {
26
26
  const body = `${JSON.stringify(payload)}\n`;
27
27
  res.writeHead(status, {
28
- 'content-type': 'application/json; charset=utf-8',
29
- 'content-length': Buffer.byteLength(body).toString(),
28
+ "content-type": "application/json; charset=utf-8",
29
+ "content-length": Buffer.byteLength(body).toString(),
30
30
  ...headers,
31
31
  });
32
32
  res.end(body);
@@ -40,15 +40,15 @@ async function readLogTail({ filePath, maxBytes, maxLines, }) {
40
40
  const stat = await fs.stat(filePath);
41
41
  const size = stat.size;
42
42
  const readBytes = Math.max(0, Math.min(size, maxBytes));
43
- const handle = await fs.open(filePath, 'r');
43
+ const handle = await fs.open(filePath, "r");
44
44
  try {
45
45
  const buffer = Buffer.alloc(readBytes);
46
46
  const start = Math.max(0, size - readBytes);
47
47
  await handle.read(buffer, 0, readBytes, start);
48
- let text = buffer.toString('utf8');
48
+ let text = buffer.toString("utf8");
49
49
  let truncated = size > readBytes;
50
50
  if (truncated) {
51
- const firstNewline = text.indexOf('\n');
51
+ const firstNewline = text.indexOf("\n");
52
52
  if (firstNewline !== -1) {
53
53
  text = text.slice(firstNewline + 1);
54
54
  }
@@ -65,17 +65,17 @@ async function readLogTail({ filePath, maxBytes, maxLines, }) {
65
65
  }
66
66
  }
67
67
  function text(res, status, body, headers) {
68
- const out = body.endsWith('\n') ? body : `${body}\n`;
68
+ const out = body.endsWith("\n") ? body : `${body}\n`;
69
69
  res.writeHead(status, {
70
- 'content-type': 'text/plain; charset=utf-8',
71
- 'content-length': Buffer.byteLength(out).toString(),
70
+ "content-type": "text/plain; charset=utf-8",
71
+ "content-length": Buffer.byteLength(out).toString(),
72
72
  ...headers,
73
73
  });
74
74
  res.end(out);
75
75
  }
76
76
  function resolveOriginHeader(req) {
77
77
  const origin = req.headers.origin;
78
- if (typeof origin !== 'string')
78
+ if (typeof origin !== "string")
79
79
  return null;
80
80
  if (!origin.trim())
81
81
  return null;
@@ -85,20 +85,20 @@ function corsHeaders(origin) {
85
85
  if (!origin)
86
86
  return {};
87
87
  return {
88
- 'access-control-allow-origin': origin,
89
- 'access-control-allow-credentials': 'true',
90
- 'access-control-allow-headers': 'authorization, content-type',
91
- 'access-control-allow-methods': 'GET,POST,OPTIONS',
88
+ "access-control-allow-origin": origin,
89
+ "access-control-allow-credentials": "true",
90
+ "access-control-allow-headers": "authorization, content-type",
91
+ "access-control-allow-methods": "GET,POST,OPTIONS",
92
92
  // Chrome Private Network Access (PNA): allow requests to localhost from secure contexts.
93
93
  // Without this, extensions often fail with a generic "Failed to fetch".
94
- 'access-control-allow-private-network': 'true',
95
- 'access-control-max-age': '600',
96
- vary: 'Origin',
94
+ "access-control-allow-private-network": "true",
95
+ "access-control-max-age": "600",
96
+ vary: "Origin",
97
97
  };
98
98
  }
99
99
  function readBearerToken(req) {
100
100
  const header = req.headers.authorization;
101
- if (typeof header !== 'string')
101
+ if (typeof header !== "string")
102
102
  return null;
103
103
  const m = header.match(/^Bearer\s+(.+)\s*$/i);
104
104
  return m?.[1]?.trim() || null;
@@ -113,40 +113,40 @@ async function readJsonBody(req, maxBytes) {
113
113
  throw new Error(`Body too large (>${maxBytes} bytes)`);
114
114
  chunks.push(buf);
115
115
  }
116
- const text = Buffer.concat(chunks).toString('utf8');
116
+ const text = Buffer.concat(chunks).toString("utf8");
117
117
  return JSON.parse(text);
118
118
  }
119
119
  function wantsJsonResponse(req, url) {
120
- const format = url.searchParams.get('format');
121
- if (format && format.toLowerCase() === 'json')
120
+ const format = url.searchParams.get("format");
121
+ if (format && format.toLowerCase() === "json")
122
122
  return true;
123
123
  const accept = req.headers.accept;
124
- if (typeof accept !== 'string')
124
+ if (typeof accept !== "string")
125
125
  return false;
126
126
  const lower = accept.toLowerCase();
127
- if (lower.includes('text/event-stream'))
127
+ if (lower.includes("text/event-stream"))
128
128
  return false;
129
- return lower.includes('application/json');
129
+ return lower.includes("application/json");
130
130
  }
131
131
  function parseDiagnostics(raw) {
132
- if (!raw || typeof raw !== 'object') {
132
+ if (!raw || typeof raw !== "object") {
133
133
  return { includeContent: false };
134
134
  }
135
135
  const obj = raw;
136
136
  return { includeContent: Boolean(obj.includeContent) };
137
137
  }
138
138
  function createLineWriter(onLine) {
139
- let buffer = '';
139
+ let buffer = "";
140
140
  return new Writable({
141
141
  write(chunk, _encoding, callback) {
142
142
  buffer += chunk.toString();
143
- let index = buffer.indexOf('\n');
143
+ let index = buffer.indexOf("\n");
144
144
  while (index >= 0) {
145
145
  const line = buffer.slice(0, index).trimEnd();
146
146
  buffer = buffer.slice(index + 1);
147
147
  if (line.trim().length > 0)
148
148
  onLine(line);
149
- index = buffer.indexOf('\n');
149
+ index = buffer.indexOf("\n");
150
150
  }
151
151
  callback();
152
152
  },
@@ -154,7 +154,7 @@ function createLineWriter(onLine) {
154
154
  const line = buffer.trim();
155
155
  if (line)
156
156
  onLine(line);
157
- buffer = '';
157
+ buffer = "";
158
158
  callback();
159
159
  },
160
160
  });
@@ -198,7 +198,7 @@ function pushToSession(session, evt, onSessionEvent) {
198
198
  break;
199
199
  session.bufferBytes -= removed.bytes;
200
200
  }
201
- if (evt.event === 'done' || evt.event === 'error') {
201
+ if (evt.event === "done" || evt.event === "error") {
202
202
  session.done = true;
203
203
  }
204
204
  }
@@ -218,10 +218,10 @@ function pushSlidesToSession(session, evt, onSessionEvent) {
218
218
  break;
219
219
  session.slidesBufferBytes -= removed.bytes;
220
220
  }
221
- if (evt.event === 'done' || evt.event === 'error') {
221
+ if (evt.event === "done" || evt.event === "error") {
222
222
  session.slidesDone = true;
223
223
  }
224
- if (evt.event === 'status') {
224
+ if (evt.event === "status") {
225
225
  session.slidesLastStatus = evt.data.text;
226
226
  }
227
227
  }
@@ -234,24 +234,24 @@ function emitMeta(session, patch, onSessionEvent) {
234
234
  return;
235
235
  }
236
236
  session.lastMeta = next;
237
- pushToSession(session, { event: 'meta', data: next }, onSessionEvent);
237
+ pushToSession(session, { event: "meta", data: next }, onSessionEvent);
238
238
  }
239
239
  function emitSlides(session, data, onSessionEvent) {
240
- pushToSession(session, { event: 'slides', data }, onSessionEvent);
241
- pushSlidesToSession(session, { event: 'slides', data }, onSessionEvent);
240
+ pushToSession(session, { event: "slides", data }, onSessionEvent);
241
+ pushSlidesToSession(session, { event: "slides", data }, onSessionEvent);
242
242
  }
243
243
  function emitSlidesStatus(session, text, onSessionEvent) {
244
244
  const trimmed = text.trim();
245
245
  if (!trimmed)
246
246
  return;
247
- pushSlidesToSession(session, { event: 'status', data: { text: trimmed } }, onSessionEvent);
247
+ pushSlidesToSession(session, { event: "status", data: { text: trimmed } }, onSessionEvent);
248
248
  }
249
249
  function emitSlidesDone(session, result, onSessionEvent) {
250
250
  if (!result.ok) {
251
- const message = result.error?.trim() || 'Slides failed.';
252
- pushSlidesToSession(session, { event: 'error', data: { message } }, onSessionEvent);
251
+ const message = result.error?.trim() || "Slides failed.";
252
+ pushSlidesToSession(session, { event: "error", data: { message } }, onSessionEvent);
253
253
  }
254
- pushSlidesToSession(session, { event: 'done', data: {} }, onSessionEvent);
254
+ pushSlidesToSession(session, { event: "done", data: {} }, onSessionEvent);
255
255
  }
256
256
  function resolveHomeDir(env) {
257
257
  const home = env.HOME?.trim() || env.USERPROFILE?.trim();
@@ -261,14 +261,14 @@ function resolveHomeDir(env) {
261
261
  }
262
262
  function resolveSlidesSettings({ env, request, }) {
263
263
  const slidesValue = request.slides;
264
- const tesseractAvailable = resolveToolPath('tesseract', env, 'TESSERACT_PATH') !== null;
264
+ const tesseractAvailable = resolveToolPath("tesseract", env, "TESSERACT_PATH") !== null;
265
265
  const slidesOcrValue = tesseractAvailable ? request.slidesOcr : false;
266
266
  return resolveSlideSettings({
267
267
  slides: slidesValue,
268
268
  slidesOcr: slidesOcrValue,
269
- slidesDir: request.slidesDir ?? '.summarize/slides',
269
+ slidesDir: request.slidesDir ?? ".summarize/slides",
270
270
  slidesSceneThreshold: request.slidesSceneThreshold,
271
- slidesSceneThresholdExplicit: typeof request.slidesSceneThreshold !== 'undefined',
271
+ slidesSceneThresholdExplicit: typeof request.slidesSceneThreshold !== "undefined",
272
272
  slidesMax: request.slidesMax,
273
273
  slidesMinDuration: request.slidesMinDuration,
274
274
  cwd: resolveHomeDir(env),
@@ -285,16 +285,16 @@ function buildSlidesPayload({ slides, port, }) {
285
285
  slides: slides.slides.map((slide) => ({
286
286
  index: slide.index,
287
287
  timestamp: slide.timestamp,
288
- imageUrl: `${baseUrl}/${slide.index}${typeof slide.imageVersion === 'number' && slide.imageVersion > 0
288
+ imageUrl: `${baseUrl}/${slide.index}${typeof slide.imageVersion === "number" && slide.imageVersion > 0
289
289
  ? `?v=${slide.imageVersion}`
290
- : ''}`,
290
+ : ""}`,
291
291
  ocrText: slide.ocrText ?? null,
292
292
  ocrConfidence: slide.ocrConfidence ?? null,
293
293
  })),
294
294
  };
295
295
  }
296
296
  function resolveToolPath(binary, env, explicitEnvKey) {
297
- const explicit = explicitEnvKey && typeof env[explicitEnvKey] === 'string' ? env[explicitEnvKey]?.trim() : '';
297
+ const explicit = explicitEnvKey && typeof env[explicitEnvKey] === "string" ? env[explicitEnvKey]?.trim() : "";
298
298
  if (explicit)
299
299
  return resolveExecutableInPath(explicit, env);
300
300
  return resolveExecutableInPath(binary, env);
@@ -328,12 +328,12 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
328
328
  const { config: summarizeConfig } = loadSummarizeConfig({ env });
329
329
  const daemonLogger = createDaemonLogger({ env, config: summarizeConfig });
330
330
  const daemonLogPaths = resolveDaemonLogPaths(env);
331
- const daemonLogFile = daemonLogger.config?.file ?? path.join(daemonLogPaths.logDir, 'daemon.jsonl');
331
+ const daemonLogFile = daemonLogger.config?.file ?? path.join(daemonLogPaths.logDir, "daemon.jsonl");
332
332
  const cacheState = await createCacheStateFromConfig({
333
333
  envForRun: env,
334
334
  config: summarizeConfig,
335
335
  noCacheFlag: false,
336
- transcriptNamespace: 'yt:auto',
336
+ transcriptNamespace: "yt:auto",
337
337
  });
338
338
  const mediaCache = await createMediaCacheFromConfig({
339
339
  envForRun: env,
@@ -349,40 +349,40 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
349
349
  void (async () => {
350
350
  const origin = resolveOriginHeader(req);
351
351
  const cors = corsHeaders(origin);
352
- if (req.method === 'OPTIONS') {
352
+ if (req.method === "OPTIONS") {
353
353
  res.writeHead(204, cors);
354
354
  res.end();
355
355
  return;
356
356
  }
357
- const url = new URL(req.url ?? '/', `http://${DAEMON_HOST}:${port}`);
357
+ const url = new URL(req.url ?? "/", `http://${DAEMON_HOST}:${port}`);
358
358
  const pathname = url.pathname;
359
- if (req.method === 'GET' && pathname === '/health') {
359
+ if (req.method === "GET" && pathname === "/health") {
360
360
  json(res, 200, buildHealthPayload(import.meta.url), cors);
361
361
  return;
362
362
  }
363
363
  const token = readBearerToken(req);
364
364
  const authed = token && token === config.token;
365
- if (pathname.startsWith('/v1/') && !authed) {
366
- json(res, 401, { ok: false, error: 'unauthorized' }, cors);
365
+ if (pathname.startsWith("/v1/") && !authed) {
366
+ json(res, 401, { ok: false, error: "unauthorized" }, cors);
367
367
  return;
368
368
  }
369
- if (req.method === 'GET' && pathname === '/v1/ping') {
369
+ if (req.method === "GET" && pathname === "/v1/ping") {
370
370
  json(res, 200, { ok: true }, cors);
371
371
  return;
372
372
  }
373
- if (req.method === 'GET' && pathname === '/v1/logs') {
374
- const source = url.searchParams.get('source')?.trim() || 'daemon';
375
- const tailParam = url.searchParams.get('tail')?.trim() || '';
376
- const tail = clampNumber(Number(tailParam || '800'), 50, 5000);
377
- const maxBytes = clampNumber(Number(url.searchParams.get('maxBytes') ?? '262144'), 16_384, 2_000_000);
373
+ if (req.method === "GET" && pathname === "/v1/logs") {
374
+ const source = url.searchParams.get("source")?.trim() || "daemon";
375
+ const tailParam = url.searchParams.get("tail")?.trim() || "";
376
+ const tail = clampNumber(Number(tailParam || "800"), 50, 5000);
377
+ const maxBytes = clampNumber(Number(url.searchParams.get("maxBytes") ?? "262144"), 16_384, 2_000_000);
378
378
  const sources = {
379
379
  daemon: {
380
380
  filePath: daemonLogFile,
381
- format: daemonLogger.config?.format ?? 'json',
381
+ format: daemonLogger.config?.format ?? "json",
382
382
  enabled: daemonLogger.enabled,
383
383
  },
384
- stdout: { filePath: daemonLogPaths.stdoutPath, format: 'text' },
385
- stderr: { filePath: daemonLogPaths.stderrPath, format: 'text' },
384
+ stdout: { filePath: daemonLogPaths.stdoutPath, format: "text" },
385
+ stderr: { filePath: daemonLogPaths.stderrPath, format: "text" },
386
386
  };
387
387
  const selected = sources[source];
388
388
  if (!selected) {
@@ -391,9 +391,9 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
391
391
  }
392
392
  const stat = await fs.stat(selected.filePath).catch(() => null);
393
393
  if (!stat?.isFile()) {
394
- const disabledNote = source === 'daemon' && selected.enabled === false
395
- ? 'Daemon logging is disabled (no log file).'
396
- : 'Log file not found.';
394
+ const disabledNote = source === "daemon" && selected.enabled === false
395
+ ? "Daemon logging is disabled (no log file)."
396
+ : "Log file not found.";
397
397
  json(res, 404, { ok: false, error: disabledNote }, cors);
398
398
  return;
399
399
  }
@@ -402,8 +402,8 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
402
402
  maxBytes,
403
403
  maxLines: tail,
404
404
  });
405
- const warning = source === 'daemon' && selected.enabled === false
406
- ? 'Daemon logging disabled; showing existing file only.'
405
+ const warning = source === "daemon" && selected.enabled === false
406
+ ? "Daemon logging disabled; showing existing file only."
407
407
  : null;
408
408
  json(res, 200, {
409
409
  ok: true,
@@ -419,28 +419,28 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
419
419
  return;
420
420
  }
421
421
  const processLogsMatch = pathname.match(/^\/v1\/processes\/([^/]+)\/logs$/);
422
- if (req.method === 'GET' && processLogsMatch) {
422
+ if (req.method === "GET" && processLogsMatch) {
423
423
  const id = processLogsMatch[1];
424
- const tail = clampNumber(Number(url.searchParams.get('tail') ?? '200'), 20, 1000);
425
- const streamRaw = (url.searchParams.get('stream') ?? 'merged').toLowerCase();
426
- const stream = streamRaw === 'stdout' || streamRaw === 'stderr' ? streamRaw : 'merged';
424
+ const tail = clampNumber(Number(url.searchParams.get("tail") ?? "200"), 20, 1000);
425
+ const streamRaw = (url.searchParams.get("stream") ?? "merged").toLowerCase();
426
+ const stream = streamRaw === "stdout" || streamRaw === "stderr" ? streamRaw : "merged";
427
427
  const result = buildProcessLogsResult(processRegistry, id, { tail, stream });
428
428
  if (!result) {
429
- json(res, 404, { ok: false, error: 'not found' }, cors);
429
+ json(res, 404, { ok: false, error: "not found" }, cors);
430
430
  return;
431
431
  }
432
432
  json(res, 200, result, cors);
433
433
  return;
434
434
  }
435
- if (req.method === 'GET' && pathname === '/v1/processes') {
436
- const includeCompleted = (url.searchParams.get('includeCompleted') ?? '').toLowerCase() === 'true' ||
437
- url.searchParams.get('includeCompleted') === '1';
438
- const limit = clampNumber(Number(url.searchParams.get('limit') ?? '80'), 10, 200);
435
+ if (req.method === "GET" && pathname === "/v1/processes") {
436
+ const includeCompleted = (url.searchParams.get("includeCompleted") ?? "").toLowerCase() === "true" ||
437
+ url.searchParams.get("includeCompleted") === "1";
438
+ const limit = clampNumber(Number(url.searchParams.get("limit") ?? "80"), 10, 200);
439
439
  const result = buildProcessListResult(processRegistry, { includeCompleted, limit });
440
440
  json(res, 200, result, cors);
441
441
  return;
442
442
  }
443
- if (req.method === 'GET' && pathname === '/v1/models') {
443
+ if (req.method === "GET" && pathname === "/v1/models") {
444
444
  const result = await buildModelPickerOptions({
445
445
  env,
446
446
  envForRun: env,
@@ -450,10 +450,10 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
450
450
  json(res, 200, result, cors);
451
451
  return;
452
452
  }
453
- if (req.method === 'GET' && pathname === '/v1/tools') {
454
- const ytDlpPath = resolveToolPath('yt-dlp', env, 'YT_DLP_PATH');
455
- const ffmpegPath = resolveToolPath('ffmpeg', env, 'FFMPEG_PATH');
456
- const tesseractPath = resolveToolPath('tesseract', env, 'TESSERACT_PATH');
453
+ if (req.method === "GET" && pathname === "/v1/tools") {
454
+ const ytDlpPath = resolveToolPath("yt-dlp", env, "YT_DLP_PATH");
455
+ const ffmpegPath = resolveToolPath("ffmpeg", env, "FFMPEG_PATH");
456
+ const tesseractPath = resolveToolPath("tesseract", env, "TESSERACT_PATH");
457
457
  json(res, 200, {
458
458
  ok: true,
459
459
  tools: {
@@ -464,7 +464,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
464
464
  }, cors);
465
465
  return;
466
466
  }
467
- if (req.method === 'POST' && pathname === '/v1/refresh-free') {
467
+ if (req.method === "POST" && pathname === "/v1/refresh-free") {
468
468
  if (activeRefreshSessionId) {
469
469
  json(res, 200, { ok: true, id: activeRefreshSessionId, running: true }, cors);
470
470
  return;
@@ -475,19 +475,19 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
475
475
  json(res, 200, { ok: true, id: session.id }, cors);
476
476
  void (async () => {
477
477
  const pushStatus = (text) => {
478
- pushToSession(session, { event: 'status', data: { text } }, onSessionEvent);
478
+ pushToSession(session, { event: "status", data: { text } }, onSessionEvent);
479
479
  };
480
480
  try {
481
- pushStatus('Refresh free: starting…');
481
+ pushStatus("Refresh free: starting…");
482
482
  const stdout = createLineWriter(pushStatus);
483
483
  const stderr = createLineWriter(pushStatus);
484
484
  await refreshFree({ env, fetchImpl, stdout, stderr });
485
- pushToSession(session, { event: 'done', data: {} }, onSessionEvent);
485
+ pushToSession(session, { event: "done", data: {} }, onSessionEvent);
486
486
  }
487
487
  catch (error) {
488
488
  const message = error instanceof Error ? error.message : String(error);
489
- pushToSession(session, { event: 'error', data: { message } }, onSessionEvent);
490
- console.error('[summarize-daemon] refresh-free failed', error);
489
+ pushToSession(session, { event: "error", data: { message } }, onSessionEvent);
490
+ console.error("[summarize-daemon] refresh-free failed", error);
491
491
  }
492
492
  finally {
493
493
  if (activeRefreshSessionId === session.id) {
@@ -501,8 +501,8 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
501
501
  })();
502
502
  return;
503
503
  }
504
- if (req.method === 'POST' && pathname === '/v1/summarize') {
505
- await refreshCacheStoreIfMissing({ cacheState, transcriptNamespace: 'yt:auto' });
504
+ if (req.method === "POST" && pathname === "/v1/summarize") {
505
+ await refreshCacheStoreIfMissing({ cacheState, transcriptNamespace: "yt:auto" });
506
506
  let body;
507
507
  try {
508
508
  body = await readJsonBody(req, 2_000_000);
@@ -512,32 +512,32 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
512
512
  json(res, 400, { ok: false, error: message }, cors);
513
513
  return;
514
514
  }
515
- if (!body || typeof body !== 'object') {
516
- json(res, 400, { ok: false, error: 'invalid json' }, cors);
515
+ if (!body || typeof body !== "object") {
516
+ json(res, 400, { ok: false, error: "invalid json" }, cors);
517
517
  return;
518
518
  }
519
519
  const obj = body;
520
- const pageUrl = typeof obj.url === 'string' ? obj.url.trim() : '';
521
- const title = typeof obj.title === 'string' ? obj.title.trim() : null;
522
- const textContent = typeof obj.text === 'string' ? obj.text : '';
520
+ const pageUrl = typeof obj.url === "string" ? obj.url.trim() : "";
521
+ const title = typeof obj.title === "string" ? obj.title.trim() : null;
522
+ const textContent = typeof obj.text === "string" ? obj.text : "";
523
523
  const truncated = Boolean(obj.truncated);
524
- const modelOverride = typeof obj.model === 'string' ? obj.model.trim() : null;
525
- const lengthRaw = typeof obj.length === 'string' ? obj.length.trim() : '';
526
- const languageRaw = typeof obj.language === 'string' ? obj.language.trim() : '';
527
- const promptRaw = typeof obj.prompt === 'string' ? obj.prompt : '';
524
+ const modelOverride = typeof obj.model === "string" ? obj.model.trim() : null;
525
+ const lengthRaw = typeof obj.length === "string" ? obj.length.trim() : "";
526
+ const languageRaw = typeof obj.language === "string" ? obj.language.trim() : "";
527
+ const promptRaw = typeof obj.prompt === "string" ? obj.prompt : "";
528
528
  const promptOverride = promptRaw.trim() || null;
529
529
  const noCache = Boolean(obj.noCache);
530
530
  const extractOnly = Boolean(obj.extractOnly);
531
- const modeRaw = typeof obj.mode === 'string' ? obj.mode.trim().toLowerCase() : '';
532
- const mode = modeRaw === 'url' ? 'url' : modeRaw === 'page' ? 'page' : 'auto';
533
- const maxCharactersCandidate = typeof obj.maxExtractCharacters === 'number' && Number.isFinite(obj.maxExtractCharacters)
531
+ const modeRaw = typeof obj.mode === "string" ? obj.mode.trim().toLowerCase() : "";
532
+ const mode = modeRaw === "url" ? "url" : modeRaw === "page" ? "page" : "auto";
533
+ const maxCharactersCandidate = typeof obj.maxExtractCharacters === "number" && Number.isFinite(obj.maxExtractCharacters)
534
534
  ? obj.maxExtractCharacters
535
- : typeof obj.maxCharacters === 'number' && Number.isFinite(obj.maxCharacters)
535
+ : typeof obj.maxCharacters === "number" && Number.isFinite(obj.maxCharacters)
536
536
  ? obj.maxCharacters
537
537
  : null;
538
538
  const maxCharacters = maxCharactersCandidate && maxCharactersCandidate > 0 ? maxCharactersCandidate : null;
539
- const formatRaw = typeof obj.format === 'string' ? obj.format.trim().toLowerCase() : '';
540
- const format = formatRaw === 'markdown' || formatRaw === 'md' ? 'markdown' : 'text';
539
+ const formatRaw = typeof obj.format === "string" ? obj.format.trim().toLowerCase() : "";
540
+ const format = formatRaw === "markdown" || formatRaw === "md" ? "markdown" : "text";
541
541
  const overrides = resolveRunOverrides({
542
542
  firecrawl: obj.firecrawl,
543
543
  markdownMode: obj.markdownMode,
@@ -549,26 +549,30 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
549
549
  timeout: obj.timeout,
550
550
  retries: obj.retries,
551
551
  maxOutputTokens: obj.maxOutputTokens,
552
+ autoCliFallback: obj.autoCliFallback,
553
+ autoCliOrder: obj.autoCliOrder,
554
+ magicCliAuto: obj.magicCliAuto,
555
+ magicCliOrder: obj.magicCliOrder,
552
556
  });
553
557
  const slidesSettings = resolveSlidesSettings({ env, request: obj });
554
558
  const diagnostics = parseDiagnostics(obj.diagnostics);
555
559
  const includeContentLog = daemonLogger.enabled && diagnostics.includeContent;
556
560
  const hasText = Boolean(textContent.trim());
557
561
  if (!pageUrl || !/^https?:\/\//i.test(pageUrl)) {
558
- json(res, 400, { ok: false, error: 'missing url' }, cors);
562
+ json(res, 400, { ok: false, error: "missing url" }, cors);
559
563
  return;
560
564
  }
561
565
  if (extractOnly) {
562
- if (mode === 'page') {
563
- json(res, 400, { ok: false, error: 'extractOnly requires mode=url' }, cors);
566
+ if (mode === "page") {
567
+ json(res, 400, { ok: false, error: "extractOnly requires mode=url" }, cors);
564
568
  return;
565
569
  }
566
570
  try {
567
571
  const requestCache = noCache
568
- ? { ...cacheState, mode: 'bypass', store: null }
572
+ ? { ...cacheState, mode: "bypass", store: null }
569
573
  : cacheState;
570
574
  const runId = randomUUID();
571
- const { extracted, slides } = await runWithProcessContext({ runId, source: 'extract' }, async () => extractContentForUrl({
575
+ const { extracted, slides } = await runWithProcessContext({ runId, source: "extract" }, async () => extractContentForUrl({
572
576
  env,
573
577
  fetchImpl,
574
578
  input: { url: pageUrl, title, maxCharacters },
@@ -620,20 +624,20 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
620
624
  }
621
625
  return;
622
626
  }
623
- if (mode === 'page' && !hasText) {
624
- json(res, 400, { ok: false, error: 'missing text' }, cors);
627
+ if (mode === "page" && !hasText) {
628
+ json(res, 400, { ok: false, error: "missing text" }, cors);
625
629
  return;
626
630
  }
627
631
  const session = createSession();
628
632
  session.slidesRequested = Boolean(slidesSettings);
629
633
  sessions.set(session.id, session);
630
- const requestLogger = daemonLogger.getSubLogger('daemon.summarize', {
634
+ const requestLogger = daemonLogger.getSubLogger("daemon.summarize", {
631
635
  requestId: session.id,
632
636
  });
633
637
  const logStartedAt = Date.now();
634
638
  let logSummaryFromCache = false;
635
639
  let logInputSummary = null;
636
- let logSummaryText = '';
640
+ let logSummaryText = "";
637
641
  let logExtracted = null;
638
642
  const logInput = includeContentLog
639
643
  ? {
@@ -655,7 +659,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
655
659
  }
656
660
  : null;
657
661
  requestLogger?.info({
658
- event: 'summarize.request',
662
+ event: "summarize.request",
659
663
  url: pageUrl,
660
664
  mode,
661
665
  hasText,
@@ -669,7 +673,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
669
673
  ...(includeContentLog ? { diagnostics } : {}),
670
674
  });
671
675
  json(res, 200, { ok: true, id: session.id }, cors);
672
- void runWithProcessContext({ runId: session.id, source: 'summarize' }, async () => {
676
+ void runWithProcessContext({ runId: session.id, source: "summarize" }, async () => {
673
677
  const slideLogState = {
674
678
  startedAt: null,
675
679
  requested: Boolean(slidesSettings),
@@ -689,7 +693,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
689
693
  if (includeContentLog) {
690
694
  logSummaryText += chunk;
691
695
  }
692
- pushToSession(session, { event: 'chunk', data: { text: chunk } }, onSessionEvent);
696
+ pushToSession(session, { event: "chunk", data: { text: chunk } }, onSessionEvent);
693
697
  },
694
698
  onModelChosen: (modelId) => {
695
699
  if (session.lastMeta.model === modelId)
@@ -704,40 +708,40 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
704
708
  const clean = text.trim();
705
709
  if (!clean)
706
710
  return;
707
- pushToSession(session, { event: 'status', data: { text: clean } }, onSessionEvent);
711
+ pushToSession(session, { event: "status", data: { text: clean } }, onSessionEvent);
708
712
  },
709
713
  writeMeta: (data) => {
710
- if (typeof data.inputSummary === 'string') {
714
+ if (typeof data.inputSummary === "string") {
711
715
  logInputSummary = data.inputSummary;
712
716
  }
713
- if (typeof data.summaryFromCache === 'boolean') {
717
+ if (typeof data.summaryFromCache === "boolean") {
714
718
  logSummaryFromCache = data.summaryFromCache;
715
719
  }
716
720
  emitMeta(session, {
717
- inputSummary: typeof data.inputSummary === 'string' ? data.inputSummary : null,
718
- summaryFromCache: typeof data.summaryFromCache === 'boolean' ? data.summaryFromCache : null,
721
+ inputSummary: typeof data.inputSummary === "string" ? data.inputSummary : null,
722
+ summaryFromCache: typeof data.summaryFromCache === "boolean" ? data.summaryFromCache : null,
719
723
  }, onSessionEvent);
720
724
  },
721
725
  };
722
- const normalizedModelOverride = modelOverride && modelOverride.toLowerCase() !== 'auto' ? modelOverride : null;
726
+ const normalizedModelOverride = modelOverride && modelOverride.toLowerCase() !== "auto" ? modelOverride : null;
723
727
  const requestCache = noCache
724
- ? { ...cacheState, mode: 'bypass', store: null }
728
+ ? { ...cacheState, mode: "bypass", store: null }
725
729
  : cacheState;
726
730
  let liveSlides = null;
727
731
  const runWithMode = async (resolved) => {
728
- if (resolved === 'url' && slideLogState.requested) {
732
+ if (resolved === "url" && slideLogState.requested) {
729
733
  slideLogState.startedAt = Date.now();
730
734
  console.log(`[summarize-daemon] slides: start url=${pageUrl} (session=${session.id})`);
731
735
  if (includeContentLog) {
732
736
  requestLogger?.info({
733
- event: 'slides.start',
737
+ event: "slides.start",
734
738
  url: pageUrl,
735
739
  sessionId: session.id,
736
740
  ...(logSlidesSettings ? { settings: logSlidesSettings } : {}),
737
741
  });
738
742
  }
739
743
  }
740
- return resolved === 'url'
744
+ return resolved === "url"
741
745
  ? await streamSummaryForUrl({
742
746
  env,
743
747
  fetchImpl,
@@ -770,11 +774,11 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
770
774
  }
771
775
  if (slideLogState.startedAt) {
772
776
  const elapsedMs = Date.now() - slideLogState.startedAt;
773
- console.log(`[summarize-daemon] slides: done count=${slides.slides.length} ocr=${slides.ocrAvailable} elapsedMs=${elapsedMs} warnings=${slides.warnings.join('; ')}`);
777
+ console.log(`[summarize-daemon] slides: done count=${slides.slides.length} ocr=${slides.ocrAvailable} elapsedMs=${elapsedMs} warnings=${slides.warnings.join("; ")}`);
774
778
  }
775
779
  if (includeContentLog) {
776
780
  requestLogger?.info({
777
- event: 'slides.done',
781
+ event: "slides.done",
778
782
  url: pageUrl,
779
783
  sessionId: session.id,
780
784
  slidesCount: slides.slides.length,
@@ -793,19 +797,19 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
793
797
  emitSlidesDone(session, result, onSessionEvent);
794
798
  },
795
799
  onSlidesProgress: (text) => {
796
- const clean = typeof text === 'string' ? text.trim() : '';
800
+ const clean = typeof text === "string" ? text.trim() : "";
797
801
  if (!clean)
798
802
  return;
799
803
  slideLogState.lastStatus = clean;
800
804
  slideLogState.statusCount += 1;
801
- if (clean.toLowerCase().includes('cached')) {
805
+ if (clean.toLowerCase().includes("cached")) {
802
806
  slideLogState.cacheHit = true;
803
807
  }
804
808
  const progressMatch = clean.match(/(\d+)%/);
805
809
  const progress = progressMatch ? Number(progressMatch[1]) : null;
806
810
  if (includeContentLog) {
807
811
  requestLogger?.info({
808
- event: 'slides.status',
812
+ event: "slides.status",
809
813
  url: pageUrl,
810
814
  sessionId: session.id,
811
815
  status: clean,
@@ -834,7 +838,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
834
838
  enabled: false,
835
839
  chosenThreshold: 0,
836
840
  confidence: 0,
837
- strategy: 'none',
841
+ strategy: "none",
838
842
  },
839
843
  maxSlides: 0,
840
844
  minSlideDuration: 0,
@@ -879,7 +883,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
879
883
  });
880
884
  };
881
885
  const result = await (async () => {
882
- if (mode !== 'auto')
886
+ if (mode !== "auto")
883
887
  return runWithMode(mode);
884
888
  const { primary, fallback } = resolveAutoDaemonMode({ url: pageUrl, hasText });
885
889
  try {
@@ -888,7 +892,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
888
892
  catch (error) {
889
893
  if (!fallback || emittedOutput)
890
894
  throw error;
891
- sink.writeStatus?.('Primary failed. Trying fallback…');
895
+ sink.writeStatus?.("Primary failed. Trying fallback…");
892
896
  try {
893
897
  return await runWithMode(fallback);
894
898
  }
@@ -905,10 +909,10 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
905
909
  modelLabel: formatModelLabelForDisplay(result.usedModel),
906
910
  }, onSessionEvent);
907
911
  }
908
- pushToSession(session, { event: 'metrics', data: result.metrics }, onSessionEvent);
909
- pushToSession(session, { event: 'done', data: {} }, onSessionEvent);
912
+ pushToSession(session, { event: "metrics", data: result.metrics }, onSessionEvent);
913
+ pushToSession(session, { event: "done", data: {} }, onSessionEvent);
910
914
  requestLogger?.info({
911
- event: 'summarize.done',
915
+ event: "summarize.done",
912
916
  url: pageUrl,
913
917
  mode,
914
918
  model: result.usedModel,
@@ -940,14 +944,14 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
940
944
  }
941
945
  catch (error) {
942
946
  const message = error instanceof Error ? error.message : String(error);
943
- pushToSession(session, { event: 'error', data: { message } }, onSessionEvent);
947
+ pushToSession(session, { event: "error", data: { message } }, onSessionEvent);
944
948
  if (session.slidesRequested && !session.slidesDone) {
945
949
  emitSlidesDone(session, { ok: false, error: message }, onSessionEvent);
946
950
  }
947
951
  // Preserve full stack trace in daemon logs for debugging.
948
- console.error('[summarize-daemon] summarize failed', error);
952
+ console.error("[summarize-daemon] summarize failed", error);
949
953
  requestLogger?.error({
950
- event: 'summarize.error',
954
+ event: "summarize.error",
951
955
  url: pageUrl,
952
956
  mode,
953
957
  elapsedMs: Date.now() - logStartedAt,
@@ -986,7 +990,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
986
990
  });
987
991
  return;
988
992
  }
989
- if (req.method === 'POST' && pathname === '/v1/agent') {
993
+ if (req.method === "POST" && pathname === "/v1/agent") {
990
994
  let body;
991
995
  try {
992
996
  body = await readJsonBody(req, 4_000_000);
@@ -996,35 +1000,35 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
996
1000
  json(res, 400, { ok: false, error: message }, cors);
997
1001
  return;
998
1002
  }
999
- if (!body || typeof body !== 'object') {
1000
- json(res, 400, { ok: false, error: 'invalid json' }, cors);
1003
+ if (!body || typeof body !== "object") {
1004
+ json(res, 400, { ok: false, error: "invalid json" }, cors);
1001
1005
  return;
1002
1006
  }
1003
1007
  const obj = body;
1004
- const pageUrl = typeof obj.url === 'string' ? obj.url.trim() : '';
1005
- const pageTitle = typeof obj.title === 'string' ? obj.title.trim() : null;
1006
- const pageContent = typeof obj.pageContent === 'string' ? obj.pageContent : '';
1008
+ const pageUrl = typeof obj.url === "string" ? obj.url.trim() : "";
1009
+ const pageTitle = typeof obj.title === "string" ? obj.title.trim() : null;
1010
+ const pageContent = typeof obj.pageContent === "string" ? obj.pageContent : "";
1007
1011
  const messages = obj.messages;
1008
- const modelOverride = typeof obj.model === 'string' ? obj.model.trim() : null;
1012
+ const modelOverride = typeof obj.model === "string" ? obj.model.trim() : null;
1009
1013
  const tools = Array.isArray(obj.tools)
1010
- ? obj.tools.filter((tool) => typeof tool === 'string')
1014
+ ? obj.tools.filter((tool) => typeof tool === "string")
1011
1015
  : [];
1012
1016
  const automationEnabled = Boolean(obj.automationEnabled);
1013
1017
  if (!pageUrl) {
1014
- json(res, 400, { ok: false, error: 'missing url' }, cors);
1018
+ json(res, 400, { ok: false, error: "missing url" }, cors);
1015
1019
  return;
1016
1020
  }
1017
1021
  const runId = `agent-${randomUUID()}`;
1018
1022
  const wantsJson = wantsJsonResponse(req, url);
1019
1023
  if (wantsJson) {
1020
1024
  try {
1021
- const assistant = await runWithProcessContext({ runId, source: 'agent' }, async () => completeAgentResponse({
1025
+ const assistant = await runWithProcessContext({ runId, source: "agent" }, async () => completeAgentResponse({
1022
1026
  env,
1023
1027
  pageUrl,
1024
1028
  pageTitle,
1025
1029
  pageContent,
1026
1030
  messages,
1027
- modelOverride: modelOverride && modelOverride.toLowerCase() !== 'auto' ? modelOverride : null,
1031
+ modelOverride: modelOverride && modelOverride.toLowerCase() !== "auto" ? modelOverride : null,
1028
1032
  tools,
1029
1033
  automationEnabled,
1030
1034
  }));
@@ -1032,110 +1036,110 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
1032
1036
  }
1033
1037
  catch (error) {
1034
1038
  const message = error instanceof Error ? error.message : String(error);
1035
- console.error('[summarize-daemon] agent failed', error);
1039
+ console.error("[summarize-daemon] agent failed", error);
1036
1040
  json(res, 500, { ok: false, error: message }, cors);
1037
1041
  }
1038
1042
  return;
1039
1043
  }
1040
1044
  res.writeHead(200, {
1041
- 'content-type': 'text/event-stream; charset=utf-8',
1042
- 'cache-control': 'no-cache',
1043
- connection: 'keep-alive',
1044
- 'x-accel-buffering': 'no',
1045
+ "content-type": "text/event-stream; charset=utf-8",
1046
+ "cache-control": "no-cache",
1047
+ connection: "keep-alive",
1048
+ "x-accel-buffering": "no",
1045
1049
  ...cors,
1046
1050
  });
1047
1051
  const controller = new AbortController();
1048
1052
  const abort = () => controller.abort();
1049
- req.on('close', abort);
1050
- res.on('close', abort);
1053
+ req.on("close", abort);
1054
+ res.on("close", abort);
1051
1055
  const writeEvent = (event) => {
1052
1056
  if (res.writableEnded)
1053
1057
  return;
1054
1058
  res.write(encodeSseEvent(event));
1055
1059
  };
1056
1060
  try {
1057
- await runWithProcessContext({ runId, source: 'agent' }, async () => streamAgentResponse({
1061
+ await runWithProcessContext({ runId, source: "agent" }, async () => streamAgentResponse({
1058
1062
  env,
1059
1063
  pageUrl,
1060
1064
  pageTitle,
1061
1065
  pageContent,
1062
1066
  messages,
1063
- modelOverride: modelOverride && modelOverride.toLowerCase() !== 'auto' ? modelOverride : null,
1067
+ modelOverride: modelOverride && modelOverride.toLowerCase() !== "auto" ? modelOverride : null,
1064
1068
  tools,
1065
1069
  automationEnabled,
1066
- onChunk: (text) => writeEvent({ event: 'chunk', data: { text } }),
1067
- onAssistant: (assistant) => writeEvent({ event: 'assistant', data: assistant }),
1070
+ onChunk: (text) => writeEvent({ event: "chunk", data: { text } }),
1071
+ onAssistant: (assistant) => writeEvent({ event: "assistant", data: assistant }),
1068
1072
  signal: controller.signal,
1069
1073
  }));
1070
- writeEvent({ event: 'done', data: {} });
1074
+ writeEvent({ event: "done", data: {} });
1071
1075
  res.end();
1072
1076
  }
1073
1077
  catch (error) {
1074
1078
  if (controller.signal.aborted)
1075
1079
  return;
1076
1080
  const message = error instanceof Error ? error.message : String(error);
1077
- console.error('[summarize-daemon] agent failed', error);
1078
- writeEvent({ event: 'error', data: { message } });
1079
- writeEvent({ event: 'done', data: {} });
1081
+ console.error("[summarize-daemon] agent failed", error);
1082
+ writeEvent({ event: "error", data: { message } });
1083
+ writeEvent({ event: "done", data: {} });
1080
1084
  res.end();
1081
1085
  }
1082
1086
  return;
1083
1087
  }
1084
1088
  const slidesMatch = pathname.match(/^\/v1\/summarize\/([^/]+)\/slides$/);
1085
- if (req.method === 'GET' && slidesMatch) {
1089
+ if (req.method === "GET" && slidesMatch) {
1086
1090
  const id = slidesMatch[1];
1087
1091
  const session = id ? sessions.get(id) : null;
1088
1092
  if (!session || !session.slides) {
1089
- json(res, 200, { ok: false, error: 'not found' }, cors);
1093
+ json(res, 200, { ok: false, error: "not found" }, cors);
1090
1094
  return;
1091
1095
  }
1092
1096
  json(res, 200, { ok: true, slides: buildSlidesPayload({ slides: session.slides, port }) }, cors);
1093
1097
  return;
1094
1098
  }
1095
1099
  const slideImageMatch = pathname.match(/^\/v1\/summarize\/([^/]+)\/slides\/(\d+)$/);
1096
- if (req.method === 'GET' && slideImageMatch) {
1100
+ if (req.method === "GET" && slideImageMatch) {
1097
1101
  const id = slideImageMatch[1];
1098
1102
  const index = Number(slideImageMatch[2]);
1099
1103
  const session = id ? sessions.get(id) : null;
1100
1104
  if (!session || !session.slides || !Number.isFinite(index)) {
1101
- json(res, 404, { ok: false, error: 'not found' }, cors);
1105
+ json(res, 404, { ok: false, error: "not found" }, cors);
1102
1106
  return;
1103
1107
  }
1104
1108
  const slide = session.slides.slides.find((item) => item.index === index);
1105
1109
  if (!slide) {
1106
- json(res, 404, { ok: false, error: 'not found' }, cors);
1110
+ json(res, 404, { ok: false, error: "not found" }, cors);
1107
1111
  return;
1108
1112
  }
1109
1113
  try {
1110
1114
  const stat = await fs.stat(slide.imagePath);
1111
1115
  res.writeHead(200, {
1112
- 'content-type': 'image/png',
1113
- 'content-length': stat.size.toString(),
1114
- 'cache-control': 'no-cache',
1116
+ "content-type": "image/png",
1117
+ "content-length": stat.size.toString(),
1118
+ "cache-control": "no-cache",
1115
1119
  ...cors,
1116
1120
  });
1117
1121
  const stream = createReadStream(slide.imagePath);
1118
1122
  stream.pipe(res);
1119
- stream.on('error', () => res.end());
1123
+ stream.on("error", () => res.end());
1120
1124
  }
1121
1125
  catch {
1122
- json(res, 404, { ok: false, error: 'not found' }, cors);
1126
+ json(res, 404, { ok: false, error: "not found" }, cors);
1123
1127
  }
1124
1128
  return;
1125
1129
  }
1126
1130
  const stableSlideImageMatch = pathname.match(/^\/v1\/slides\/([^/]+)\/(\d+)$/);
1127
- if (req.method === 'GET' && stableSlideImageMatch) {
1131
+ if (req.method === "GET" && stableSlideImageMatch) {
1128
1132
  const sourceId = stableSlideImageMatch[1];
1129
1133
  const index = Number(stableSlideImageMatch[2]);
1130
1134
  if (!sourceId || !Number.isFinite(index) || index <= 0) {
1131
- json(res, 404, { ok: false, error: 'not found' }, cors);
1135
+ json(res, 404, { ok: false, error: "not found" }, cors);
1132
1136
  return;
1133
1137
  }
1134
- const slidesRoot = path.resolve(resolveHomeDir(env), '.summarize', 'slides');
1138
+ const slidesRoot = path.resolve(resolveHomeDir(env), ".summarize", "slides");
1135
1139
  const slidesDir = path.join(slidesRoot, sourceId);
1136
- const payloadPath = path.join(slidesDir, 'slides.json');
1140
+ const payloadPath = path.join(slidesDir, "slides.json");
1137
1141
  const resolveFromDisk = async () => {
1138
- const raw = await fs.readFile(payloadPath, 'utf8').catch(() => null);
1142
+ const raw = await fs.readFile(payloadPath, "utf8").catch(() => null);
1139
1143
  if (raw) {
1140
1144
  try {
1141
1145
  const parsed = JSON.parse(raw);
@@ -1150,12 +1154,12 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
1150
1154
  // fall through
1151
1155
  }
1152
1156
  }
1153
- const prefix = `slide_${String(index).padStart(4, '0')}`;
1157
+ const prefix = `slide_${String(index).padStart(4, "0")}`;
1154
1158
  const entries = await fs.readdir(slidesDir).catch(() => null);
1155
1159
  if (!entries)
1156
1160
  return null;
1157
1161
  const candidates = entries
1158
- .filter((name) => name.startsWith(prefix) && name.endsWith('.png'))
1162
+ .filter((name) => name.startsWith(prefix) && name.endsWith(".png"))
1159
1163
  .map((name) => path.join(slidesDir, name));
1160
1164
  if (candidates.length === 0)
1161
1165
  return null;
@@ -1174,12 +1178,12 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
1174
1178
  if (!filePath) {
1175
1179
  // Return a tiny transparent PNG (placeholder) instead of 404 to avoid broken-image icons
1176
1180
  // while extraction is still running.
1177
- const placeholder = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO3kq0cAAAAASUVORK5CYII=', 'base64');
1181
+ const placeholder = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO3kq0cAAAAASUVORK5CYII=", "base64");
1178
1182
  res.writeHead(200, {
1179
- 'content-type': 'image/png',
1180
- 'content-length': placeholder.length.toString(),
1181
- 'cache-control': 'no-store',
1182
- 'x-summarize-slide-ready': '0',
1183
+ "content-type": "image/png",
1184
+ "content-length": placeholder.length.toString(),
1185
+ "cache-control": "no-store",
1186
+ "x-summarize-slide-ready": "0",
1183
1187
  ...cors,
1184
1188
  });
1185
1189
  res.end(placeholder);
@@ -1188,23 +1192,23 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
1188
1192
  try {
1189
1193
  const stat = await fs.stat(filePath);
1190
1194
  res.writeHead(200, {
1191
- 'content-type': 'image/png',
1192
- 'content-length': stat.size.toString(),
1193
- 'cache-control': 'no-store',
1194
- 'x-summarize-slide-ready': '1',
1195
+ "content-type": "image/png",
1196
+ "content-length": stat.size.toString(),
1197
+ "cache-control": "no-store",
1198
+ "x-summarize-slide-ready": "1",
1195
1199
  ...cors,
1196
1200
  });
1197
1201
  const stream = createReadStream(filePath);
1198
1202
  stream.pipe(res);
1199
- stream.on('error', () => res.end());
1203
+ stream.on("error", () => res.end());
1200
1204
  }
1201
1205
  catch {
1202
- json(res, 404, { ok: false, error: 'not found' }, cors);
1206
+ json(res, 404, { ok: false, error: "not found" }, cors);
1203
1207
  }
1204
1208
  return;
1205
1209
  }
1206
1210
  const eventsMatch = pathname.match(/^\/v1\/summarize\/([^/]+)\/events$/);
1207
- if (req.method === 'GET' && eventsMatch) {
1211
+ if (req.method === "GET" && eventsMatch) {
1208
1212
  const id = eventsMatch[1];
1209
1213
  if (!id) {
1210
1214
  json(res, 404, { ok: false }, cors);
@@ -1212,14 +1216,14 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
1212
1216
  }
1213
1217
  const session = sessions.get(id);
1214
1218
  if (!session) {
1215
- json(res, 404, { ok: false, error: 'not found' }, cors);
1219
+ json(res, 404, { ok: false, error: "not found" }, cors);
1216
1220
  return;
1217
1221
  }
1218
1222
  res.writeHead(200, {
1219
1223
  ...cors,
1220
- 'content-type': 'text/event-stream; charset=utf-8',
1221
- 'cache-control': 'no-cache, no-transform',
1222
- connection: 'keep-alive',
1224
+ "content-type": "text/event-stream; charset=utf-8",
1225
+ "cache-control": "no-cache, no-transform",
1226
+ connection: "keep-alive",
1223
1227
  });
1224
1228
  session.clients.add(res);
1225
1229
  for (const entry of session.buffer) {
@@ -1234,14 +1238,14 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
1234
1238
  res.write(`: keepalive ${Date.now()}\n\n`);
1235
1239
  }, 15_000);
1236
1240
  keepalive.unref();
1237
- res.on('close', () => {
1241
+ res.on("close", () => {
1238
1242
  clearInterval(keepalive);
1239
1243
  session.clients.delete(res);
1240
1244
  });
1241
1245
  return;
1242
1246
  }
1243
1247
  const slidesEventsMatch = pathname.match(/^\/v1\/summarize\/([^/]+)\/slides\/events$/);
1244
- if (req.method === 'GET' && slidesEventsMatch) {
1248
+ if (req.method === "GET" && slidesEventsMatch) {
1245
1249
  const id = slidesEventsMatch[1];
1246
1250
  if (!id) {
1247
1251
  json(res, 404, { ok: false }, cors);
@@ -1249,29 +1253,29 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
1249
1253
  }
1250
1254
  const session = sessions.get(id);
1251
1255
  if (!session || !session.slidesRequested) {
1252
- json(res, 404, { ok: false, error: 'not found' }, cors);
1256
+ json(res, 404, { ok: false, error: "not found" }, cors);
1253
1257
  return;
1254
1258
  }
1255
1259
  res.writeHead(200, {
1256
1260
  ...cors,
1257
- 'content-type': 'text/event-stream; charset=utf-8',
1258
- 'cache-control': 'no-cache, no-transform',
1259
- connection: 'keep-alive',
1261
+ "content-type": "text/event-stream; charset=utf-8",
1262
+ "cache-control": "no-cache, no-transform",
1263
+ connection: "keep-alive",
1260
1264
  });
1261
1265
  session.slidesClients.add(res);
1262
1266
  for (const entry of session.slidesBuffer) {
1263
1267
  res.write(encodeSseEvent(entry.event));
1264
1268
  }
1265
- const hasSlidesEvent = session.slidesBuffer.some((entry) => entry.event.event === 'slides');
1269
+ const hasSlidesEvent = session.slidesBuffer.some((entry) => entry.event.event === "slides");
1266
1270
  if (!hasSlidesEvent && session.slides) {
1267
1271
  res.write(encodeSseEvent({
1268
- event: 'slides',
1272
+ event: "slides",
1269
1273
  data: buildSlidesPayload({ slides: session.slides, port }),
1270
1274
  }));
1271
1275
  }
1272
- const hasStatusEvent = session.slidesBuffer.some((entry) => entry.event.event === 'status');
1276
+ const hasStatusEvent = session.slidesBuffer.some((entry) => entry.event.event === "status");
1273
1277
  if (!hasStatusEvent && session.slidesLastStatus) {
1274
- res.write(encodeSseEvent({ event: 'status', data: { text: session.slidesLastStatus } }));
1278
+ res.write(encodeSseEvent({ event: "status", data: { text: session.slidesLastStatus } }));
1275
1279
  }
1276
1280
  if (session.slidesDone) {
1277
1281
  res.end();
@@ -1282,14 +1286,14 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
1282
1286
  res.write(`: keepalive ${Date.now()}\n\n`);
1283
1287
  }, 15_000);
1284
1288
  keepalive.unref();
1285
- res.on('close', () => {
1289
+ res.on("close", () => {
1286
1290
  clearInterval(keepalive);
1287
1291
  session.slidesClients.delete(res);
1288
1292
  });
1289
1293
  return;
1290
1294
  }
1291
1295
  const refreshEventsMatch = pathname.match(/^\/v1\/refresh-free\/([^/]+)\/events$/);
1292
- if (req.method === 'GET' && refreshEventsMatch) {
1296
+ if (req.method === "GET" && refreshEventsMatch) {
1293
1297
  const id = refreshEventsMatch[1];
1294
1298
  if (!id) {
1295
1299
  json(res, 404, { ok: false }, cors);
@@ -1297,14 +1301,14 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
1297
1301
  }
1298
1302
  const session = refreshSessions.get(id);
1299
1303
  if (!session) {
1300
- json(res, 404, { ok: false, error: 'not found' }, cors);
1304
+ json(res, 404, { ok: false, error: "not found" }, cors);
1301
1305
  return;
1302
1306
  }
1303
1307
  res.writeHead(200, {
1304
1308
  ...cors,
1305
- 'content-type': 'text/event-stream; charset=utf-8',
1306
- 'cache-control': 'no-cache, no-transform',
1307
- connection: 'keep-alive',
1309
+ "content-type": "text/event-stream; charset=utf-8",
1310
+ "cache-control": "no-cache, no-transform",
1311
+ connection: "keep-alive",
1308
1312
  });
1309
1313
  session.clients.add(res);
1310
1314
  for (const entry of session.buffer) {
@@ -1319,13 +1323,13 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
1319
1323
  res.write(`: keepalive ${Date.now()}\n\n`);
1320
1324
  }, 15_000);
1321
1325
  keepalive.unref();
1322
- res.on('close', () => {
1326
+ res.on("close", () => {
1323
1327
  clearInterval(keepalive);
1324
1328
  session.clients.delete(res);
1325
1329
  });
1326
1330
  return;
1327
1331
  }
1328
- text(res, 404, 'Not found', cors);
1332
+ text(res, 404, "Not found", cors);
1329
1333
  })().catch((error) => {
1330
1334
  const origin = resolveOriginHeader(req);
1331
1335
  const cors = corsHeaders(origin);
@@ -1344,10 +1348,10 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
1344
1348
  });
1345
1349
  try {
1346
1350
  await new Promise((resolve, reject) => {
1347
- server.once('error', reject);
1351
+ server.once("error", reject);
1348
1352
  server.listen(port, DAEMON_HOST, () => {
1349
1353
  const address = server.address();
1350
- const actualPort = address && typeof address === 'object' && typeof address.port === 'number'
1354
+ const actualPort = address && typeof address === "object" && typeof address.port === "number"
1351
1355
  ? address.port
1352
1356
  : port;
1353
1357
  onListening?.(actualPort);
@@ -1362,14 +1366,14 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
1362
1366
  resolved = true;
1363
1367
  server.close(() => resolve());
1364
1368
  };
1365
- process.once('SIGTERM', onStop);
1366
- process.once('SIGINT', onStop);
1369
+ process.once("SIGTERM", onStop);
1370
+ process.once("SIGINT", onStop);
1367
1371
  if (signal) {
1368
1372
  if (signal.aborted) {
1369
1373
  onStop();
1370
1374
  }
1371
1375
  else {
1372
- signal.addEventListener('abort', onStop, { once: true });
1376
+ signal.addEventListener("abort", onStop, { once: true });
1373
1377
  }
1374
1378
  }
1375
1379
  });