@steipete/summarize 0.9.0 → 0.11.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 (398) hide show
  1. package/CHANGELOG.md +121 -0
  2. package/LICENSE +1 -1
  3. package/README.md +391 -183
  4. package/dist/cli.js +1 -1
  5. package/dist/esm/cache.js +134 -64
  6. package/dist/esm/cache.js.map +1 -1
  7. package/dist/esm/cli-main.js +27 -27
  8. package/dist/esm/cli-main.js.map +1 -1
  9. package/dist/esm/cli.js +2 -2
  10. package/dist/esm/cli.js.map +1 -1
  11. package/dist/esm/config.js +396 -126
  12. package/dist/esm/config.js.map +1 -1
  13. package/dist/esm/content/asset.js +53 -50
  14. package/dist/esm/content/asset.js.map +1 -1
  15. package/dist/esm/content/index.js +1 -1
  16. package/dist/esm/content/index.js.map +1 -1
  17. package/dist/esm/costs.js +1 -1
  18. package/dist/esm/costs.js.map +1 -1
  19. package/dist/esm/daemon/agent.js +548 -0
  20. package/dist/esm/daemon/agent.js.map +1 -0
  21. package/dist/esm/daemon/auto-mode.js +3 -3
  22. package/dist/esm/daemon/auto-mode.js.map +1 -1
  23. package/dist/esm/daemon/chat.js +88 -178
  24. package/dist/esm/daemon/chat.js.map +1 -1
  25. package/dist/esm/daemon/cli-entrypoint.js +72 -0
  26. package/dist/esm/daemon/cli-entrypoint.js.map +1 -0
  27. package/dist/esm/daemon/cli.js +91 -83
  28. package/dist/esm/daemon/cli.js.map +1 -1
  29. package/dist/esm/daemon/config.js +15 -15
  30. package/dist/esm/daemon/config.js.map +1 -1
  31. package/dist/esm/daemon/constants.js +6 -6
  32. package/dist/esm/daemon/constants.js.map +1 -1
  33. package/dist/esm/daemon/env-merge.js.map +1 -1
  34. package/dist/esm/daemon/env-snapshot.js +36 -28
  35. package/dist/esm/daemon/env-snapshot.js.map +1 -1
  36. package/dist/esm/daemon/flow-context.js +86 -32
  37. package/dist/esm/daemon/flow-context.js.map +1 -1
  38. package/dist/esm/daemon/launchd.js +119 -47
  39. package/dist/esm/daemon/launchd.js.map +1 -1
  40. package/dist/esm/daemon/meta.js +5 -5
  41. package/dist/esm/daemon/meta.js.map +1 -1
  42. package/dist/esm/daemon/models.js +54 -31
  43. package/dist/esm/daemon/models.js.map +1 -1
  44. package/dist/esm/daemon/process-registry.js +206 -0
  45. package/dist/esm/daemon/process-registry.js.map +1 -0
  46. package/dist/esm/daemon/schtasks.js +96 -32
  47. package/dist/esm/daemon/schtasks.js.map +1 -1
  48. package/dist/esm/daemon/server.js +832 -158
  49. package/dist/esm/daemon/server.js.map +1 -1
  50. package/dist/esm/daemon/summarize-progress.js +11 -11
  51. package/dist/esm/daemon/summarize-progress.js.map +1 -1
  52. package/dist/esm/daemon/summarize.js +61 -32
  53. package/dist/esm/daemon/summarize.js.map +1 -1
  54. package/dist/esm/daemon/systemd.js +96 -35
  55. package/dist/esm/daemon/systemd.js.map +1 -1
  56. package/dist/esm/firecrawl.js +12 -12
  57. package/dist/esm/firecrawl.js.map +1 -1
  58. package/dist/esm/flags.js +55 -31
  59. package/dist/esm/flags.js.map +1 -1
  60. package/dist/esm/index.js +3 -3
  61. package/dist/esm/index.js.map +1 -1
  62. package/dist/esm/language.js +1 -1
  63. package/dist/esm/language.js.map +1 -1
  64. package/dist/esm/llm/cli.js +128 -64
  65. package/dist/esm/llm/cli.js.map +1 -1
  66. package/dist/esm/llm/errors.js +1 -1
  67. package/dist/esm/llm/errors.js.map +1 -1
  68. package/dist/esm/llm/generate-text.js +107 -98
  69. package/dist/esm/llm/generate-text.js.map +1 -1
  70. package/dist/esm/llm/google-models.js +17 -17
  71. package/dist/esm/llm/google-models.js.map +1 -1
  72. package/dist/esm/llm/html-to-markdown.js +3 -3
  73. package/dist/esm/llm/html-to-markdown.js.map +1 -1
  74. package/dist/esm/llm/model-id.js +38 -16
  75. package/dist/esm/llm/model-id.js.map +1 -1
  76. package/dist/esm/llm/prompt.js +5 -5
  77. package/dist/esm/llm/prompt.js.map +1 -1
  78. package/dist/esm/llm/providers/anthropic.js +33 -33
  79. package/dist/esm/llm/providers/anthropic.js.map +1 -1
  80. package/dist/esm/llm/providers/google.js +19 -19
  81. package/dist/esm/llm/providers/google.js.map +1 -1
  82. package/dist/esm/llm/providers/models.js +30 -30
  83. package/dist/esm/llm/providers/models.js.map +1 -1
  84. package/dist/esm/llm/providers/openai.js +36 -35
  85. package/dist/esm/llm/providers/openai.js.map +1 -1
  86. package/dist/esm/llm/providers/shared.js +8 -8
  87. package/dist/esm/llm/providers/shared.js.map +1 -1
  88. package/dist/esm/llm/transcript-to-markdown.js +9 -5
  89. package/dist/esm/llm/transcript-to-markdown.js.map +1 -1
  90. package/dist/esm/llm/usage.js +18 -18
  91. package/dist/esm/llm/usage.js.map +1 -1
  92. package/dist/esm/logging/daemon.js +21 -21
  93. package/dist/esm/logging/daemon.js.map +1 -1
  94. package/dist/esm/logging/ring-file.js +5 -5
  95. package/dist/esm/logging/ring-file.js.map +1 -1
  96. package/dist/esm/markitdown.js +21 -19
  97. package/dist/esm/markitdown.js.map +1 -1
  98. package/dist/esm/media-cache.js +251 -0
  99. package/dist/esm/media-cache.js.map +1 -0
  100. package/dist/esm/model-auto.js +175 -106
  101. package/dist/esm/model-auto.js.map +1 -1
  102. package/dist/esm/model-spec.js +52 -42
  103. package/dist/esm/model-spec.js.map +1 -1
  104. package/dist/esm/pricing/litellm.js +4 -4
  105. package/dist/esm/pricing/litellm.js.map +1 -1
  106. package/dist/esm/processes.js +2 -0
  107. package/dist/esm/processes.js.map +1 -0
  108. package/dist/esm/prompts/index.js +1 -1
  109. package/dist/esm/prompts/index.js.map +1 -1
  110. package/dist/esm/refresh-free.js +81 -81
  111. package/dist/esm/refresh-free.js.map +1 -1
  112. package/dist/esm/run/attachments.js +47 -44
  113. package/dist/esm/run/attachments.js.map +1 -1
  114. package/dist/esm/run/bird.js +125 -12
  115. package/dist/esm/run/bird.js.map +1 -1
  116. package/dist/esm/run/cache-state.js +7 -7
  117. package/dist/esm/run/cache-state.js.map +1 -1
  118. package/dist/esm/run/cli-fallback-state.js +45 -0
  119. package/dist/esm/run/cli-fallback-state.js.map +1 -0
  120. package/dist/esm/run/cli-preflight.js +40 -22
  121. package/dist/esm/run/cli-preflight.js.map +1 -1
  122. package/dist/esm/run/constants.js +12 -12
  123. package/dist/esm/run/constants.js.map +1 -1
  124. package/dist/esm/run/cookies/twitter.js +47 -47
  125. package/dist/esm/run/cookies/twitter.js.map +1 -1
  126. package/dist/esm/run/env.js +21 -15
  127. package/dist/esm/run/env.js.map +1 -1
  128. package/dist/esm/run/fetch-with-timeout.js +4 -4
  129. package/dist/esm/run/fetch-with-timeout.js.map +1 -1
  130. package/dist/esm/run/finish-line.js +78 -71
  131. package/dist/esm/run/finish-line.js.map +1 -1
  132. package/dist/esm/run/flows/asset/extract.js +70 -0
  133. package/dist/esm/run/flows/asset/extract.js.map +1 -0
  134. package/dist/esm/run/flows/asset/input.js +202 -37
  135. package/dist/esm/run/flows/asset/input.js.map +1 -1
  136. package/dist/esm/run/flows/asset/media-policy.js +3 -0
  137. package/dist/esm/run/flows/asset/media-policy.js.map +1 -0
  138. package/dist/esm/run/flows/asset/media.js +233 -0
  139. package/dist/esm/run/flows/asset/media.js.map +1 -0
  140. package/dist/esm/run/flows/asset/output.js +98 -0
  141. package/dist/esm/run/flows/asset/output.js.map +1 -0
  142. package/dist/esm/run/flows/asset/preprocess.js +79 -44
  143. package/dist/esm/run/flows/asset/preprocess.js.map +1 -1
  144. package/dist/esm/run/flows/asset/summary.js +306 -89
  145. package/dist/esm/run/flows/asset/summary.js.map +1 -1
  146. package/dist/esm/run/flows/url/extract.js +31 -31
  147. package/dist/esm/run/flows/url/extract.js.map +1 -1
  148. package/dist/esm/run/flows/url/flow.js +388 -82
  149. package/dist/esm/run/flows/url/flow.js.map +1 -1
  150. package/dist/esm/run/flows/url/markdown.js +61 -56
  151. package/dist/esm/run/flows/url/markdown.js.map +1 -1
  152. package/dist/esm/run/flows/url/slides-output.js +487 -0
  153. package/dist/esm/run/flows/url/slides-output.js.map +1 -0
  154. package/dist/esm/run/flows/url/slides-text.js +628 -0
  155. package/dist/esm/run/flows/url/slides-text.js.map +1 -0
  156. package/dist/esm/run/flows/url/summary.js +493 -152
  157. package/dist/esm/run/flows/url/summary.js.map +1 -1
  158. package/dist/esm/run/format.js +10 -10
  159. package/dist/esm/run/format.js.map +1 -1
  160. package/dist/esm/run/help.js +179 -84
  161. package/dist/esm/run/help.js.map +1 -1
  162. package/dist/esm/run/logging.js +20 -12
  163. package/dist/esm/run/logging.js.map +1 -1
  164. package/dist/esm/run/markdown.js +12 -12
  165. package/dist/esm/run/markdown.js.map +1 -1
  166. package/dist/esm/run/media-cache-state.js +33 -0
  167. package/dist/esm/run/media-cache-state.js.map +1 -0
  168. package/dist/esm/run/model-attempts.js.map +1 -1
  169. package/dist/esm/run/openrouter.js +11 -11
  170. package/dist/esm/run/openrouter.js.map +1 -1
  171. package/dist/esm/run/progress.js +19 -1
  172. package/dist/esm/run/progress.js.map +1 -1
  173. package/dist/esm/run/run-config.js +16 -16
  174. package/dist/esm/run/run-config.js.map +1 -1
  175. package/dist/esm/run/run-context.js +2 -2
  176. package/dist/esm/run/run-context.js.map +1 -1
  177. package/dist/esm/run/run-env.js +55 -54
  178. package/dist/esm/run/run-env.js.map +1 -1
  179. package/dist/esm/run/run-input.js +3 -3
  180. package/dist/esm/run/run-input.js.map +1 -1
  181. package/dist/esm/run/run-metrics.js +16 -16
  182. package/dist/esm/run/run-metrics.js.map +1 -1
  183. package/dist/esm/run/run-models.js +28 -23
  184. package/dist/esm/run/run-models.js.map +1 -1
  185. package/dist/esm/run/run-output.js +3 -3
  186. package/dist/esm/run/run-output.js.map +1 -1
  187. package/dist/esm/run/run-settings.js +108 -21
  188. package/dist/esm/run/run-settings.js.map +1 -1
  189. package/dist/esm/run/run-stream.js +4 -4
  190. package/dist/esm/run/run-stream.js.map +1 -1
  191. package/dist/esm/run/runner.js +327 -100
  192. package/dist/esm/run/runner.js.map +1 -1
  193. package/dist/esm/run/slides-cli.js +226 -0
  194. package/dist/esm/run/slides-cli.js.map +1 -0
  195. package/dist/esm/run/slides-render.js +163 -0
  196. package/dist/esm/run/slides-render.js.map +1 -0
  197. package/dist/esm/run/stdin-temp-file.js +77 -0
  198. package/dist/esm/run/stdin-temp-file.js.map +1 -0
  199. package/dist/esm/run/stream-output.js +17 -10
  200. package/dist/esm/run/stream-output.js.map +1 -1
  201. package/dist/esm/run/streaming.js +16 -16
  202. package/dist/esm/run/streaming.js.map +1 -1
  203. package/dist/esm/run/summary-engine.js +89 -57
  204. package/dist/esm/run/summary-engine.js.map +1 -1
  205. package/dist/esm/run/summary-llm.js +3 -3
  206. package/dist/esm/run/summary-llm.js.map +1 -1
  207. package/dist/esm/run/terminal.js +4 -4
  208. package/dist/esm/run/terminal.js.map +1 -1
  209. package/dist/esm/run/tips.js +2 -2
  210. package/dist/esm/run/tips.js.map +1 -1
  211. package/dist/esm/run/transcriber-cli.js +148 -0
  212. package/dist/esm/run/transcriber-cli.js.map +1 -0
  213. package/dist/esm/run.js +1 -1
  214. package/dist/esm/run.js.map +1 -1
  215. package/dist/esm/shared/contracts.js +1 -1
  216. package/dist/esm/shared/contracts.js.map +1 -1
  217. package/dist/esm/shared/sse-events.js +16 -12
  218. package/dist/esm/shared/sse-events.js.map +1 -1
  219. package/dist/esm/shared/streaming-merge.js +3 -3
  220. package/dist/esm/shared/streaming-merge.js.map +1 -1
  221. package/dist/esm/slides/extract.js +1951 -0
  222. package/dist/esm/slides/extract.js.map +1 -0
  223. package/dist/esm/slides/index.js +4 -0
  224. package/dist/esm/slides/index.js.map +1 -0
  225. package/dist/esm/slides/settings.js +73 -0
  226. package/dist/esm/slides/settings.js.map +1 -0
  227. package/dist/esm/slides/store.js +111 -0
  228. package/dist/esm/slides/store.js.map +1 -0
  229. package/dist/esm/slides/types.js +2 -0
  230. package/dist/esm/slides/types.js.map +1 -0
  231. package/dist/esm/tty/format.js +13 -13
  232. package/dist/esm/tty/format.js.map +1 -1
  233. package/dist/esm/tty/osc-progress.js +22 -2
  234. package/dist/esm/tty/osc-progress.js.map +1 -1
  235. package/dist/esm/tty/progress/fetch-html.js +20 -16
  236. package/dist/esm/tty/progress/fetch-html.js.map +1 -1
  237. package/dist/esm/tty/progress/transcript.js +127 -68
  238. package/dist/esm/tty/progress/transcript.js.map +1 -1
  239. package/dist/esm/tty/spinner.js +21 -10
  240. package/dist/esm/tty/spinner.js.map +1 -1
  241. package/dist/esm/tty/theme.js +189 -0
  242. package/dist/esm/tty/theme.js.map +1 -0
  243. package/dist/esm/tty/website-progress.js +38 -34
  244. package/dist/esm/tty/website-progress.js.map +1 -1
  245. package/dist/esm/version.js +29 -29
  246. package/dist/esm/version.js.map +1 -1
  247. package/dist/types/cache.d.ts +19 -7
  248. package/dist/types/config.d.ts +71 -6
  249. package/dist/types/content/asset.d.ts +8 -6
  250. package/dist/types/content/index.d.ts +1 -1
  251. package/dist/types/costs.d.ts +3 -3
  252. package/dist/types/daemon/agent.d.ts +25 -0
  253. package/dist/types/daemon/auto-mode.d.ts +3 -3
  254. package/dist/types/daemon/chat.d.ts +10 -18
  255. package/dist/types/daemon/cli-entrypoint.d.ts +2 -0
  256. package/dist/types/daemon/config.d.ts +2 -2
  257. package/dist/types/daemon/env-merge.d.ts +1 -1
  258. package/dist/types/daemon/env-snapshot.d.ts +1 -1
  259. package/dist/types/daemon/flow-context.d.ts +24 -4
  260. package/dist/types/daemon/launchd.d.ts +12 -0
  261. package/dist/types/daemon/models.d.ts +6 -2
  262. package/dist/types/daemon/process-registry.d.ts +73 -0
  263. package/dist/types/daemon/schtasks.d.ts +4 -0
  264. package/dist/types/daemon/server.d.ts +2 -2
  265. package/dist/types/daemon/summarize-progress.d.ts +1 -1
  266. package/dist/types/daemon/summarize.d.ts +38 -7
  267. package/dist/types/daemon/systemd.d.ts +4 -0
  268. package/dist/types/firecrawl.d.ts +1 -1
  269. package/dist/types/flags.d.ts +12 -11
  270. package/dist/types/index.d.ts +4 -4
  271. package/dist/types/language.d.ts +1 -1
  272. package/dist/types/llm/attachments.d.ts +1 -1
  273. package/dist/types/llm/cli.d.ts +3 -3
  274. package/dist/types/llm/generate-text.d.ts +7 -7
  275. package/dist/types/llm/html-to-markdown.d.ts +3 -3
  276. package/dist/types/llm/model-id.d.ts +1 -1
  277. package/dist/types/llm/prompt.d.ts +2 -2
  278. package/dist/types/llm/providers/anthropic.d.ts +3 -3
  279. package/dist/types/llm/providers/google.d.ts +3 -3
  280. package/dist/types/llm/providers/models.d.ts +2 -2
  281. package/dist/types/llm/providers/openai.d.ts +4 -4
  282. package/dist/types/llm/providers/shared.d.ts +2 -2
  283. package/dist/types/llm/transcript-to-markdown.d.ts +4 -2
  284. package/dist/types/llm/usage.d.ts +1 -1
  285. package/dist/types/logging/daemon.d.ts +4 -4
  286. package/dist/types/markitdown.d.ts +1 -1
  287. package/dist/types/media-cache.d.ts +22 -0
  288. package/dist/types/model-auto.d.ts +14 -4
  289. package/dist/types/model-spec.d.ts +10 -10
  290. package/dist/types/pricing/litellm.d.ts +1 -1
  291. package/dist/types/processes.d.ts +1 -0
  292. package/dist/types/prompts/index.d.ts +1 -1
  293. package/dist/types/run/attachments.d.ts +7 -7
  294. package/dist/types/run/bird.d.ts +7 -0
  295. package/dist/types/run/cache-state.d.ts +2 -2
  296. package/dist/types/run/cli-fallback-state.d.ts +6 -0
  297. package/dist/types/run/constants.d.ts +1 -1
  298. package/dist/types/run/cookies/twitter.d.ts +1 -1
  299. package/dist/types/run/env.d.ts +1 -1
  300. package/dist/types/run/finish-line.d.ts +7 -6
  301. package/dist/types/run/flows/asset/extract.d.ts +18 -0
  302. package/dist/types/run/flows/asset/input.d.ts +19 -3
  303. package/dist/types/run/flows/asset/media-policy.d.ts +2 -0
  304. package/dist/types/run/flows/asset/media.d.ts +21 -0
  305. package/dist/types/run/flows/asset/output.d.ts +42 -0
  306. package/dist/types/run/flows/asset/preprocess.d.ts +23 -17
  307. package/dist/types/run/flows/asset/summary.d.ts +24 -16
  308. package/dist/types/run/flows/url/extract.d.ts +3 -2
  309. package/dist/types/run/flows/url/flow.d.ts +1 -1
  310. package/dist/types/run/flows/url/markdown.d.ts +6 -6
  311. package/dist/types/run/flows/url/slides-output.d.ts +66 -0
  312. package/dist/types/run/flows/url/slides-text.d.ts +87 -0
  313. package/dist/types/run/flows/url/summary.d.ts +18 -10
  314. package/dist/types/run/flows/url/types.d.ts +52 -21
  315. package/dist/types/run/format.d.ts +3 -3
  316. package/dist/types/run/help.d.ts +4 -1
  317. package/dist/types/run/logging.d.ts +3 -2
  318. package/dist/types/run/media-cache-state.d.ts +7 -0
  319. package/dist/types/run/model-attempts.d.ts +1 -1
  320. package/dist/types/run/progress.d.ts +2 -1
  321. package/dist/types/run/run-config.d.ts +4 -4
  322. package/dist/types/run/run-context.d.ts +3 -1
  323. package/dist/types/run/run-env.d.ts +3 -1
  324. package/dist/types/run/run-input.d.ts +2 -2
  325. package/dist/types/run/run-metrics.d.ts +3 -3
  326. package/dist/types/run/run-models.d.ts +3 -2
  327. package/dist/types/run/run-output.d.ts +1 -1
  328. package/dist/types/run/run-settings.d.ts +20 -5
  329. package/dist/types/run/run-stream.d.ts +2 -2
  330. package/dist/types/run/runner.d.ts +3 -2
  331. package/dist/types/run/slides-cli.d.ts +9 -0
  332. package/dist/types/run/slides-render.d.ts +30 -0
  333. package/dist/types/run/stdin-temp-file.d.ts +9 -0
  334. package/dist/types/run/stream-output.d.ts +3 -2
  335. package/dist/types/run/streaming.d.ts +4 -4
  336. package/dist/types/run/summary-engine.d.ts +22 -12
  337. package/dist/types/run/summary-llm.d.ts +5 -5
  338. package/dist/types/run/transcriber-cli.d.ts +8 -0
  339. package/dist/types/run/types.d.ts +4 -4
  340. package/dist/types/run.d.ts +1 -1
  341. package/dist/types/shared/contracts.d.ts +2 -2
  342. package/dist/types/shared/sse-events.d.ts +26 -6
  343. package/dist/types/slides/extract.d.ts +43 -0
  344. package/dist/types/slides/index.d.ts +5 -0
  345. package/dist/types/slides/settings.d.ts +20 -0
  346. package/dist/types/slides/store.d.ts +15 -0
  347. package/dist/types/slides/types.d.ts +40 -0
  348. package/dist/types/tty/osc-progress.d.ts +5 -5
  349. package/dist/types/tty/progress/fetch-html.d.ts +5 -3
  350. package/dist/types/tty/progress/transcript.d.ts +5 -3
  351. package/dist/types/tty/spinner.d.ts +3 -1
  352. package/dist/types/tty/theme.d.ts +44 -0
  353. package/dist/types/tty/website-progress.d.ts +5 -3
  354. package/dist/types/version.d.ts +1 -1
  355. package/docs/README.md +1 -1
  356. package/docs/_config.yml +26 -0
  357. package/docs/_layouts/default.html +60 -0
  358. package/docs/agent.md +367 -0
  359. package/docs/assets/site.css +748 -0
  360. package/docs/assets/site.js +72 -0
  361. package/docs/assets/summarize-cli.png +0 -0
  362. package/docs/assets/summarize-extension.png +0 -0
  363. package/docs/assets/youtube-slides.png +0 -0
  364. package/docs/cache.md +29 -3
  365. package/docs/chrome-extension.md +72 -16
  366. package/docs/cli.md +59 -13
  367. package/docs/config.md +109 -12
  368. package/docs/extract-only.md +10 -0
  369. package/docs/index.html +224 -0
  370. package/docs/index.md +25 -0
  371. package/docs/llm.md +18 -5
  372. package/docs/manual-tests.md +2 -0
  373. package/docs/media.md +6 -2
  374. package/docs/model-auto.md +3 -2
  375. package/docs/nvidia-onnx-transcription.md +55 -0
  376. package/docs/openai.md +1 -1
  377. package/docs/releasing.md +3 -0
  378. package/docs/site/404.html +4 -1
  379. package/docs/site/assets/site.css +399 -228
  380. package/docs/site/assets/site.js +46 -46
  381. package/docs/site/assets/summarize-cli.png +0 -0
  382. package/docs/site/assets/summarize-extension.png +0 -0
  383. package/docs/site/docs/chrome-extension.html +101 -0
  384. package/docs/site/docs/config.html +30 -8
  385. package/docs/site/docs/extract-only.html +17 -4
  386. package/docs/site/docs/firecrawl.html +13 -3
  387. package/docs/site/docs/index.html +40 -6
  388. package/docs/site/docs/llm.html +20 -5
  389. package/docs/site/docs/openai.html +19 -5
  390. package/docs/site/docs/website.html +30 -9
  391. package/docs/site/docs/youtube.html +13 -3
  392. package/docs/site/index.html +168 -85
  393. package/docs/slides.md +82 -0
  394. package/docs/smoketest.md +29 -20
  395. package/docs/timestamps.md +124 -0
  396. package/docs/website.md +13 -0
  397. package/docs/youtube.md +20 -0
  398. package/package.json +57 -48
@@ -1,34 +1,82 @@
1
- import { readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import JSON5 from 'json5';
1
+ import JSON5 from "json5";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { isCliThemeName, listCliThemes } from "./tty/theme.js";
4
5
  function isRecord(value) {
5
- return typeof value === 'object' && value !== null && !Array.isArray(value);
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6
7
  }
7
8
  function parseOptionalBaseUrl(raw) {
8
- return typeof raw === 'string' && raw.trim().length > 0 ? raw.trim() : undefined;
9
+ return typeof raw === "string" && raw.trim().length > 0 ? raw.trim() : undefined;
10
+ }
11
+ function resolveLegacyApiKeysEnv(apiKeys) {
12
+ if (!apiKeys)
13
+ return {};
14
+ const mapped = {};
15
+ if (typeof apiKeys.openai === "string")
16
+ mapped.OPENAI_API_KEY = apiKeys.openai;
17
+ if (typeof apiKeys.anthropic === "string")
18
+ mapped.ANTHROPIC_API_KEY = apiKeys.anthropic;
19
+ if (typeof apiKeys.google === "string")
20
+ mapped.GEMINI_API_KEY = apiKeys.google;
21
+ if (typeof apiKeys.xai === "string")
22
+ mapped.XAI_API_KEY = apiKeys.xai;
23
+ if (typeof apiKeys.openrouter === "string")
24
+ mapped.OPENROUTER_API_KEY = apiKeys.openrouter;
25
+ if (typeof apiKeys.zai === "string")
26
+ mapped.Z_AI_API_KEY = apiKeys.zai;
27
+ if (typeof apiKeys.apify === "string")
28
+ mapped.APIFY_API_TOKEN = apiKeys.apify;
29
+ if (typeof apiKeys.firecrawl === "string")
30
+ mapped.FIRECRAWL_API_KEY = apiKeys.firecrawl;
31
+ if (typeof apiKeys.fal === "string")
32
+ mapped.FAL_KEY = apiKeys.fal;
33
+ return mapped;
34
+ }
35
+ export function resolveConfigEnv(config) {
36
+ if (!config)
37
+ return {};
38
+ return {
39
+ ...resolveLegacyApiKeysEnv(config.apiKeys),
40
+ ...(config.env ?? {}),
41
+ };
42
+ }
43
+ export function mergeConfigEnv({ env, config, }) {
44
+ const configEnv = resolveConfigEnv(config);
45
+ if (Object.keys(configEnv).length === 0)
46
+ return env;
47
+ let changed = false;
48
+ const merged = { ...env };
49
+ for (const [key, value] of Object.entries(configEnv)) {
50
+ const current = merged[key];
51
+ if (typeof current === "string" && current.trim().length > 0)
52
+ continue;
53
+ merged[key] = value;
54
+ changed = true;
55
+ }
56
+ return changed ? merged : env;
9
57
  }
10
58
  function parseProviderBaseUrlConfig(raw, path, providerName) {
11
- if (typeof raw === 'undefined')
59
+ if (typeof raw === "undefined")
12
60
  return undefined;
13
61
  if (!isRecord(raw)) {
14
62
  throw new Error(`Invalid config file ${path}: "${providerName}" must be an object.`);
15
63
  }
16
64
  const baseUrl = parseOptionalBaseUrl(raw.baseUrl);
17
- return typeof baseUrl === 'string' ? { baseUrl } : undefined;
65
+ return typeof baseUrl === "string" ? { baseUrl } : undefined;
18
66
  }
19
67
  function parseAutoRuleKind(value) {
20
- return value === 'text' ||
21
- value === 'website' ||
22
- value === 'youtube' ||
23
- value === 'image' ||
24
- value === 'video' ||
25
- value === 'file'
68
+ return value === "text" ||
69
+ value === "website" ||
70
+ value === "youtube" ||
71
+ value === "image" ||
72
+ value === "video" ||
73
+ value === "file"
26
74
  ? value
27
75
  : null;
28
76
  }
29
77
  function parseCliProvider(value, path) {
30
- const trimmed = typeof value === 'string' ? value.trim().toLowerCase() : '';
31
- if (trimmed === 'claude' || trimmed === 'codex' || trimmed === 'gemini') {
78
+ const trimmed = typeof value === "string" ? value.trim().toLowerCase() : "";
79
+ if (trimmed === "claude" || trimmed === "codex" || trimmed === "gemini" || trimmed === "agent") {
32
80
  return trimmed;
33
81
  }
34
82
  throw new Error(`Invalid config file ${path}: unknown CLI provider "${String(value)}".`);
@@ -39,7 +87,7 @@ function parseStringArray(raw, path, label) {
39
87
  }
40
88
  const items = [];
41
89
  for (const entry of raw) {
42
- if (typeof entry !== 'string') {
90
+ if (typeof entry !== "string") {
43
91
  throw new Error(`Invalid config file ${path}: "${label}" must be an array of strings.`);
44
92
  }
45
93
  const trimmed = entry.trim();
@@ -50,21 +98,21 @@ function parseStringArray(raw, path, label) {
50
98
  return items;
51
99
  }
52
100
  function parseLoggingLevel(raw, path) {
53
- if (typeof raw !== 'string') {
101
+ if (typeof raw !== "string") {
54
102
  throw new Error(`Invalid config file ${path}: "logging.level" must be a string.`);
55
103
  }
56
104
  const trimmed = raw.trim().toLowerCase();
57
- if (trimmed === 'debug' || trimmed === 'info' || trimmed === 'warn' || trimmed === 'error') {
105
+ if (trimmed === "debug" || trimmed === "info" || trimmed === "warn" || trimmed === "error") {
58
106
  return trimmed;
59
107
  }
60
108
  throw new Error(`Invalid config file ${path}: "logging.level" must be one of "debug", "info", "warn", "error".`);
61
109
  }
62
110
  function parseLoggingFormat(raw, path) {
63
- if (typeof raw !== 'string') {
111
+ if (typeof raw !== "string") {
64
112
  throw new Error(`Invalid config file ${path}: "logging.format" must be a string.`);
65
113
  }
66
114
  const trimmed = raw.trim().toLowerCase();
67
- if (trimmed === 'json' || trimmed === 'pretty') {
115
+ if (trimmed === "json" || trimmed === "pretty") {
68
116
  return trimmed;
69
117
  }
70
118
  throw new Error(`Invalid config file ${path}: "logging.format" must be one of "json" or "pretty".`);
@@ -85,12 +133,12 @@ function parseCliProviderConfig(raw, path, label) {
85
133
  if (!isRecord(raw)) {
86
134
  throw new Error(`Invalid config file ${path}: "cli.${label}" must be an object.`);
87
135
  }
88
- if (typeof raw.enabled !== 'undefined') {
136
+ if (typeof raw.enabled !== "undefined") {
89
137
  throw new Error(`Invalid config file ${path}: "cli.${label}.enabled" is not supported. Use "cli.enabled" instead.`);
90
138
  }
91
- const binaryValue = typeof raw.binary === 'string' ? raw.binary.trim() : undefined;
92
- const modelValue = typeof raw.model === 'string' ? raw.model.trim() : undefined;
93
- const extraArgs = typeof raw.extraArgs === 'undefined'
139
+ const binaryValue = typeof raw.binary === "string" ? raw.binary.trim() : undefined;
140
+ const modelValue = typeof raw.model === "string" ? raw.model.trim() : undefined;
141
+ const extraArgs = typeof raw.extraArgs === "undefined"
94
142
  ? undefined
95
143
  : parseStringArray(raw.extraArgs, path, `cli.${label}.extraArgs`);
96
144
  return {
@@ -99,6 +147,33 @@ function parseCliProviderConfig(raw, path, label) {
99
147
  ...(extraArgs && extraArgs.length > 0 ? { extraArgs } : {}),
100
148
  };
101
149
  }
150
+ function parseCliAutoFallbackConfig(raw, path, label) {
151
+ if (!isRecord(raw)) {
152
+ throw new Error(`Invalid config file ${path}: "cli.${label}" must be an object.`);
153
+ }
154
+ const enabled = typeof raw.enabled === "boolean"
155
+ ? raw.enabled
156
+ : typeof raw.enabled === "undefined"
157
+ ? undefined
158
+ : (() => {
159
+ throw new Error(`Invalid config file ${path}: "cli.${label}.enabled" must be a boolean.`);
160
+ })();
161
+ const onlyWhenNoApiKeys = typeof raw.onlyWhenNoApiKeys === "boolean"
162
+ ? raw.onlyWhenNoApiKeys
163
+ : typeof raw.onlyWhenNoApiKeys === "undefined"
164
+ ? undefined
165
+ : (() => {
166
+ throw new Error(`Invalid config file ${path}: "cli.${label}.onlyWhenNoApiKeys" must be a boolean.`);
167
+ })();
168
+ const order = typeof raw.order === "undefined"
169
+ ? undefined
170
+ : parseCliProviderList(raw.order, path, `cli.${label}.order`);
171
+ return {
172
+ ...(typeof enabled === "boolean" ? { enabled } : {}),
173
+ ...(typeof onlyWhenNoApiKeys === "boolean" ? { onlyWhenNoApiKeys } : {}),
174
+ ...(Array.isArray(order) && order.length > 0 ? { order } : {}),
175
+ };
176
+ }
102
177
  function parseWhenKinds(raw, path) {
103
178
  if (!Array.isArray(raw)) {
104
179
  throw new Error(`Invalid config file ${path}: "model.rules[].when" must be an array of kinds.`);
@@ -123,7 +198,7 @@ function parseModelCandidates(raw, path) {
123
198
  }
124
199
  const candidates = [];
125
200
  for (const entry of raw) {
126
- if (typeof entry !== 'string') {
201
+ if (typeof entry !== "string") {
127
202
  throw new Error(`Invalid config file ${path}: "model.rules[].candidates" must be an array of strings.`);
128
203
  }
129
204
  const trimmed = entry.trim();
@@ -142,23 +217,23 @@ function parseTokenBand(raw, path) {
142
217
  }
143
218
  const candidates = parseModelCandidates(raw.candidates, path);
144
219
  const token = (() => {
145
- if (typeof raw.token === 'undefined')
220
+ if (typeof raw.token === "undefined")
146
221
  return undefined;
147
222
  if (!isRecord(raw.token)) {
148
223
  throw new Error(`Invalid config file ${path}: "model.rules[].bands[].token" must be an object.`);
149
224
  }
150
- const min = typeof raw.token.min === 'number' ? raw.token.min : undefined;
151
- const max = typeof raw.token.max === 'number' ? raw.token.max : undefined;
152
- if (typeof min === 'number' && (!Number.isFinite(min) || min < 0)) {
225
+ const min = typeof raw.token.min === "number" ? raw.token.min : undefined;
226
+ const max = typeof raw.token.max === "number" ? raw.token.max : undefined;
227
+ if (typeof min === "number" && (!Number.isFinite(min) || min < 0)) {
153
228
  throw new Error(`Invalid config file ${path}: "model.rules[].bands[].token.min" must be >= 0.`);
154
229
  }
155
- if (typeof max === 'number' && (!Number.isFinite(max) || max < 0)) {
230
+ if (typeof max === "number" && (!Number.isFinite(max) || max < 0)) {
156
231
  throw new Error(`Invalid config file ${path}: "model.rules[].bands[].token.max" must be >= 0.`);
157
232
  }
158
- if (typeof min === 'number' && typeof max === 'number' && min > max) {
233
+ if (typeof min === "number" && typeof max === "number" && min > max) {
159
234
  throw new Error(`Invalid config file ${path}: "model.rules[].bands[].token.min" must be <= "token.max".`);
160
235
  }
161
- return typeof min === 'number' || typeof max === 'number' ? { min, max } : undefined;
236
+ return typeof min === "number" || typeof max === "number" ? { min, max } : undefined;
162
237
  })();
163
238
  return { ...(token ? { token } : {}), candidates };
164
239
  }
@@ -168,15 +243,15 @@ function assertNoComments(raw, path) {
168
243
  let line = 1;
169
244
  let col = 1;
170
245
  for (let i = 0; i < raw.length; i += 1) {
171
- const ch = raw[i] ?? '';
172
- const next = raw[i + 1] ?? '';
246
+ const ch = raw[i] ?? "";
247
+ const next = raw[i + 1] ?? "";
173
248
  if (inString) {
174
249
  if (escaped) {
175
250
  escaped = false;
176
251
  col += 1;
177
252
  continue;
178
253
  }
179
- if (ch === '\\') {
254
+ if (ch === "\\") {
180
255
  escaped = true;
181
256
  col += 1;
182
257
  continue;
@@ -184,7 +259,7 @@ function assertNoComments(raw, path) {
184
259
  if (ch === inString) {
185
260
  inString = null;
186
261
  }
187
- if (ch === '\n') {
262
+ if (ch === "\n") {
188
263
  line += 1;
189
264
  col = 1;
190
265
  }
@@ -199,13 +274,13 @@ function assertNoComments(raw, path) {
199
274
  col += 1;
200
275
  continue;
201
276
  }
202
- if (ch === '/' && next === '/') {
277
+ if (ch === "/" && next === "/") {
203
278
  throw new Error(`Invalid config file ${path}: comments are not allowed (found // at ${line}:${col}).`);
204
279
  }
205
- if (ch === '/' && next === '*') {
280
+ if (ch === "/" && next === "*") {
206
281
  throw new Error(`Invalid config file ${path}: comments are not allowed (found /* at ${line}:${col}).`);
207
282
  }
208
- if (ch === '\n') {
283
+ if (ch === "\n") {
209
284
  line += 1;
210
285
  col = 1;
211
286
  }
@@ -218,10 +293,10 @@ export function loadSummarizeConfig({ env }) {
218
293
  const home = env.HOME?.trim() || env.USERPROFILE?.trim() || null;
219
294
  if (!home)
220
295
  return { config: null, path: null };
221
- const path = join(home, '.summarize', 'config.json');
296
+ const path = join(home, ".summarize", "config.json");
222
297
  let raw;
223
298
  try {
224
- raw = readFileSync(path, 'utf8');
299
+ raw = readFileSync(path, "utf8");
225
300
  }
226
301
  catch {
227
302
  return { config: null, path };
@@ -239,21 +314,21 @@ export function loadSummarizeConfig({ env }) {
239
314
  throw new Error(`Invalid config file ${path}: expected an object at the top level`);
240
315
  }
241
316
  const parseModelConfig = (raw, label) => {
242
- if (typeof raw === 'undefined')
317
+ if (typeof raw === "undefined")
243
318
  return undefined;
244
319
  // Shorthand:
245
320
  // - "auto" -> { mode: "auto" }
246
321
  // - "<provider>/<model>" or "openrouter/<provider>/<model>" -> { id: "..." }
247
322
  // - "<name>" -> { name: "<name>" }
248
- if (typeof raw === 'string') {
323
+ if (typeof raw === "string") {
249
324
  const value = raw.trim();
250
325
  if (value.length === 0) {
251
326
  throw new Error(`Invalid config file ${path}: "${label}" must not be empty.`);
252
327
  }
253
- if (value.toLowerCase() === 'auto') {
254
- return { mode: 'auto' };
328
+ if (value.toLowerCase() === "auto") {
329
+ return { mode: "auto" };
255
330
  }
256
- if (value.includes('/')) {
331
+ if (value.includes("/")) {
257
332
  return { id: value };
258
333
  }
259
334
  return { name: value };
@@ -261,30 +336,30 @@ export function loadSummarizeConfig({ env }) {
261
336
  if (!isRecord(raw)) {
262
337
  throw new Error(`Invalid config file ${path}: "${label}" must be an object.`);
263
338
  }
264
- if (typeof raw.name === 'string') {
339
+ if (typeof raw.name === "string") {
265
340
  const name = raw.name.trim();
266
341
  if (name.length === 0) {
267
342
  throw new Error(`Invalid config file ${path}: "${label}.name" must not be empty.`);
268
343
  }
269
- if (name.toLowerCase() === 'auto') {
344
+ if (name.toLowerCase() === "auto") {
270
345
  throw new Error(`Invalid config file ${path}: "${label}.name" must not be "auto".`);
271
346
  }
272
347
  return { name };
273
348
  }
274
- if (typeof raw.id === 'string') {
349
+ if (typeof raw.id === "string") {
275
350
  const id = raw.id.trim();
276
351
  if (id.length === 0) {
277
352
  throw new Error(`Invalid config file ${path}: "${label}.id" must not be empty.`);
278
353
  }
279
- if (!id.includes('/')) {
354
+ if (!id.includes("/")) {
280
355
  throw new Error(`Invalid config file ${path}: "${label}.id" must be provider-prefixed (e.g. "openai/gpt-5-mini").`);
281
356
  }
282
357
  return { id };
283
358
  }
284
- const hasRules = typeof raw.rules !== 'undefined';
285
- if (raw.mode === 'auto' || (!('mode' in raw) && hasRules)) {
359
+ const hasRules = typeof raw.rules !== "undefined";
360
+ if (raw.mode === "auto" || (!("mode" in raw) && hasRules)) {
286
361
  const rules = (() => {
287
- if (typeof raw.rules === 'undefined')
362
+ if (typeof raw.rules === "undefined")
288
363
  return undefined;
289
364
  if (!Array.isArray(raw.rules)) {
290
365
  throw new Error(`Invalid config file ${path}: "${label}.rules" must be an array.`);
@@ -293,9 +368,9 @@ export function loadSummarizeConfig({ env }) {
293
368
  for (const entry of raw.rules) {
294
369
  if (!isRecord(entry))
295
370
  continue;
296
- const when = typeof entry.when === 'undefined' ? undefined : parseWhenKinds(entry.when, path);
297
- const hasCandidates = typeof entry.candidates !== 'undefined';
298
- const hasBands = typeof entry.bands !== 'undefined';
371
+ const when = typeof entry.when === "undefined" ? undefined : parseWhenKinds(entry.when, path);
372
+ const hasCandidates = typeof entry.candidates !== "undefined";
373
+ const hasBands = typeof entry.bands !== "undefined";
299
374
  if (hasCandidates && hasBands) {
300
375
  throw new Error(`Invalid config file ${path}: "${label}.rules[]" must use either "candidates" or "bands" (not both).`);
301
376
  }
@@ -316,18 +391,18 @@ export function loadSummarizeConfig({ env }) {
316
391
  }
317
392
  return rulesParsed;
318
393
  })();
319
- return { mode: 'auto', ...(rules ? { rules } : {}) };
394
+ return { mode: "auto", ...(rules ? { rules } : {}) };
320
395
  }
321
396
  throw new Error(`Invalid config file ${path}: "${label}" must include either "id", "name", or { "mode": "auto" }.`);
322
397
  };
323
398
  const model = (() => {
324
- return parseModelConfig(parsed.model, 'model');
399
+ return parseModelConfig(parsed.model, "model");
325
400
  })();
326
401
  const language = (() => {
327
402
  const value = parsed.language;
328
- if (typeof value === 'undefined')
403
+ if (typeof value === "undefined")
329
404
  return undefined;
330
- if (typeof value !== 'string') {
405
+ if (typeof value !== "string") {
331
406
  throw new Error(`Invalid config file ${path}: "language" must be a string.`);
332
407
  }
333
408
  const trimmed = value.trim();
@@ -338,9 +413,9 @@ export function loadSummarizeConfig({ env }) {
338
413
  })();
339
414
  const prompt = (() => {
340
415
  const value = parsed.prompt;
341
- if (typeof value === 'undefined')
416
+ if (typeof value === "undefined")
342
417
  return undefined;
343
- if (typeof value !== 'string') {
418
+ if (typeof value !== "string") {
344
419
  throw new Error(`Invalid config file ${path}: "prompt" must be a string.`);
345
420
  }
346
421
  const trimmed = value.trim();
@@ -351,11 +426,11 @@ export function loadSummarizeConfig({ env }) {
351
426
  })();
352
427
  const models = (() => {
353
428
  const root = parsed;
354
- if (typeof root.bags !== 'undefined') {
429
+ if (typeof root.bags !== "undefined") {
355
430
  throw new Error(`Invalid config file ${path}: legacy key "bags" is no longer supported. Use "models" instead.`);
356
431
  }
357
432
  const raw = root.models;
358
- if (typeof raw === 'undefined')
433
+ if (typeof raw === "undefined")
359
434
  return undefined;
360
435
  if (!isRecord(raw)) {
361
436
  throw new Error(`Invalid config file ${path}: "models" must be an object.`);
@@ -367,7 +442,7 @@ export function loadSummarizeConfig({ env }) {
367
442
  if (!key)
368
443
  continue;
369
444
  const keyLower = key.toLowerCase();
370
- if (keyLower === 'auto') {
445
+ if (keyLower === "auto") {
371
446
  throw new Error(`Invalid config file ${path}: model name "auto" is reserved.`);
372
447
  }
373
448
  if (seen.has(keyLower)) {
@@ -376,13 +451,13 @@ export function loadSummarizeConfig({ env }) {
376
451
  if (/\s/.test(key)) {
377
452
  throw new Error(`Invalid config file ${path}: model name "${key}" must not contain spaces.`);
378
453
  }
379
- if (key.includes('/')) {
454
+ if (key.includes("/")) {
380
455
  throw new Error(`Invalid config file ${path}: model name "${key}" must not include "/".`);
381
456
  }
382
457
  const parsedModel = parseModelConfig(value, `models.${key}`);
383
458
  if (!parsedModel)
384
459
  continue;
385
- if ('name' in parsedModel) {
460
+ if ("name" in parsedModel) {
386
461
  throw new Error(`Invalid config file ${path}: "models.${key}" must not reference another model.`);
387
462
  }
388
463
  seen.add(keyLower);
@@ -392,41 +467,91 @@ export function loadSummarizeConfig({ env }) {
392
467
  })();
393
468
  const cache = (() => {
394
469
  const value = parsed.cache;
395
- if (typeof value === 'undefined')
470
+ if (typeof value === "undefined")
396
471
  return undefined;
397
472
  if (!isRecord(value)) {
398
473
  throw new Error(`Invalid config file ${path}: "cache" must be an object.`);
399
474
  }
400
- const enabled = typeof value.enabled === 'boolean' ? value.enabled : undefined;
475
+ const enabled = typeof value.enabled === "boolean" ? value.enabled : undefined;
401
476
  const maxMbRaw = value.maxMb;
402
- const maxMb = typeof maxMbRaw === 'number' && Number.isFinite(maxMbRaw) && maxMbRaw > 0
477
+ const maxMb = typeof maxMbRaw === "number" && Number.isFinite(maxMbRaw) && maxMbRaw > 0
403
478
  ? maxMbRaw
404
- : typeof maxMbRaw === 'undefined'
479
+ : typeof maxMbRaw === "undefined"
405
480
  ? undefined
406
481
  : (() => {
407
482
  throw new Error(`Invalid config file ${path}: "cache.maxMb" must be a number.`);
408
483
  })();
409
484
  const ttlDaysRaw = value.ttlDays;
410
- const ttlDays = typeof ttlDaysRaw === 'number' && Number.isFinite(ttlDaysRaw) && ttlDaysRaw > 0
485
+ const ttlDays = typeof ttlDaysRaw === "number" && Number.isFinite(ttlDaysRaw) && ttlDaysRaw > 0
411
486
  ? ttlDaysRaw
412
- : typeof ttlDaysRaw === 'undefined'
487
+ : typeof ttlDaysRaw === "undefined"
413
488
  ? undefined
414
489
  : (() => {
415
490
  throw new Error(`Invalid config file ${path}: "cache.ttlDays" must be a number.`);
416
491
  })();
417
- const pathValue = typeof value.path === 'string' && value.path.trim().length > 0
492
+ const pathValue = typeof value.path === "string" && value.path.trim().length > 0
418
493
  ? value.path.trim()
419
- : typeof value.path === 'undefined'
494
+ : typeof value.path === "undefined"
420
495
  ? undefined
421
496
  : (() => {
422
497
  throw new Error(`Invalid config file ${path}: "cache.path" must be a string.`);
423
498
  })();
424
- return enabled || maxMb || ttlDays || pathValue
499
+ const media = (() => {
500
+ const mediaValue = value.media;
501
+ if (typeof mediaValue === "undefined")
502
+ return undefined;
503
+ if (!isRecord(mediaValue)) {
504
+ throw new Error(`Invalid config file ${path}: "cache.media" must be an object.`);
505
+ }
506
+ const mediaEnabled = typeof mediaValue.enabled === "boolean" ? mediaValue.enabled : undefined;
507
+ const mediaMaxRaw = mediaValue.maxMb;
508
+ const mediaMaxMb = typeof mediaMaxRaw === "number" && Number.isFinite(mediaMaxRaw) && mediaMaxRaw > 0
509
+ ? mediaMaxRaw
510
+ : typeof mediaMaxRaw === "undefined"
511
+ ? undefined
512
+ : (() => {
513
+ throw new Error(`Invalid config file ${path}: "cache.media.maxMb" must be a number.`);
514
+ })();
515
+ const mediaTtlRaw = mediaValue.ttlDays;
516
+ const mediaTtlDays = typeof mediaTtlRaw === "number" && Number.isFinite(mediaTtlRaw) && mediaTtlRaw > 0
517
+ ? mediaTtlRaw
518
+ : typeof mediaTtlRaw === "undefined"
519
+ ? undefined
520
+ : (() => {
521
+ throw new Error(`Invalid config file ${path}: "cache.media.ttlDays" must be a number.`);
522
+ })();
523
+ const mediaPath = typeof mediaValue.path === "string" && mediaValue.path.trim().length > 0
524
+ ? mediaValue.path.trim()
525
+ : typeof mediaValue.path === "undefined"
526
+ ? undefined
527
+ : (() => {
528
+ throw new Error(`Invalid config file ${path}: "cache.media.path" must be a string.`);
529
+ })();
530
+ const verifyRaw = typeof mediaValue.verify === "string" ? mediaValue.verify.trim().toLowerCase() : "";
531
+ const verify = verifyRaw === "none" || verifyRaw === "size" || verifyRaw === "hash"
532
+ ? verifyRaw
533
+ : verifyRaw.length > 0
534
+ ? (() => {
535
+ throw new Error(`Invalid config file ${path}: "cache.media.verify" must be one of "none", "size", "hash".`);
536
+ })()
537
+ : undefined;
538
+ return mediaEnabled || mediaMaxMb || mediaTtlDays || mediaPath || typeof verify === "string"
539
+ ? {
540
+ ...(typeof mediaEnabled === "boolean" ? { enabled: mediaEnabled } : {}),
541
+ ...(typeof mediaMaxMb === "number" ? { maxMb: mediaMaxMb } : {}),
542
+ ...(typeof mediaTtlDays === "number" ? { ttlDays: mediaTtlDays } : {}),
543
+ ...(typeof mediaPath === "string" ? { path: mediaPath } : {}),
544
+ ...(typeof verify === "string" ? { verify } : {}),
545
+ }
546
+ : undefined;
547
+ })();
548
+ return enabled || maxMb || ttlDays || pathValue || media
425
549
  ? {
426
- ...(typeof enabled === 'boolean' ? { enabled } : {}),
427
- ...(typeof maxMb === 'number' ? { maxMb } : {}),
428
- ...(typeof ttlDays === 'number' ? { ttlDays } : {}),
429
- ...(typeof pathValue === 'string' ? { path: pathValue } : {}),
550
+ ...(typeof enabled === "boolean" ? { enabled } : {}),
551
+ ...(typeof maxMb === "number" ? { maxMb } : {}),
552
+ ...(typeof ttlDays === "number" ? { ttlDays } : {}),
553
+ ...(typeof pathValue === "string" ? { path: pathValue } : {}),
554
+ ...(media ? { media } : {}),
430
555
  }
431
556
  : undefined;
432
557
  })();
@@ -434,40 +559,114 @@ export function loadSummarizeConfig({ env }) {
434
559
  const value = parsed.media;
435
560
  if (!isRecord(value))
436
561
  return undefined;
437
- const videoMode = value.videoMode === 'auto' ||
438
- value.videoMode === 'transcript' ||
439
- value.videoMode === 'understand'
562
+ const videoMode = value.videoMode === "auto" ||
563
+ value.videoMode === "transcript" ||
564
+ value.videoMode === "understand"
440
565
  ? value.videoMode
441
566
  : undefined;
442
567
  return videoMode ? { videoMode } : undefined;
443
568
  })();
569
+ const slides = (() => {
570
+ const value = parsed.slides;
571
+ if (typeof value === "undefined")
572
+ return undefined;
573
+ if (!isRecord(value)) {
574
+ throw new Error(`Invalid config file ${path}: "slides" must be an object.`);
575
+ }
576
+ const enabled = typeof value.enabled === "boolean" ? value.enabled : undefined;
577
+ const ocr = typeof value.ocr === "boolean" ? value.ocr : undefined;
578
+ const dir = typeof value.dir === "string" && value.dir.trim().length > 0
579
+ ? value.dir.trim()
580
+ : typeof value.dir === "undefined"
581
+ ? undefined
582
+ : (() => {
583
+ throw new Error(`Invalid config file ${path}: "slides.dir" must be a string.`);
584
+ })();
585
+ const sceneRaw = value.sceneThreshold;
586
+ const sceneThreshold = typeof sceneRaw === "number" && Number.isFinite(sceneRaw) && sceneRaw >= 0.1 && sceneRaw <= 1
587
+ ? sceneRaw
588
+ : typeof sceneRaw === "undefined"
589
+ ? undefined
590
+ : (() => {
591
+ throw new Error(`Invalid config file ${path}: "slides.sceneThreshold" must be a number between 0.1 and 1.0.`);
592
+ })();
593
+ const maxRaw = value.max;
594
+ const max = typeof maxRaw === "number" &&
595
+ Number.isFinite(maxRaw) &&
596
+ Number.isInteger(maxRaw) &&
597
+ maxRaw > 0
598
+ ? maxRaw
599
+ : typeof maxRaw === "undefined"
600
+ ? undefined
601
+ : (() => {
602
+ throw new Error(`Invalid config file ${path}: "slides.max" must be an integer.`);
603
+ })();
604
+ const minRaw = value.minDuration;
605
+ const minDuration = typeof minRaw === "number" && Number.isFinite(minRaw) && minRaw >= 0
606
+ ? minRaw
607
+ : typeof minRaw === "undefined"
608
+ ? undefined
609
+ : (() => {
610
+ throw new Error(`Invalid config file ${path}: "slides.minDuration" must be a number.`);
611
+ })();
612
+ return enabled ||
613
+ typeof ocr === "boolean" ||
614
+ dir ||
615
+ typeof sceneThreshold === "number" ||
616
+ typeof max === "number" ||
617
+ typeof minDuration === "number"
618
+ ? {
619
+ ...(typeof enabled === "boolean" ? { enabled } : {}),
620
+ ...(typeof ocr === "boolean" ? { ocr } : {}),
621
+ ...(typeof dir === "string" ? { dir } : {}),
622
+ ...(typeof sceneThreshold === "number" ? { sceneThreshold } : {}),
623
+ ...(typeof max === "number" ? { max } : {}),
624
+ ...(typeof minDuration === "number" ? { minDuration } : {}),
625
+ }
626
+ : undefined;
627
+ })();
444
628
  const cli = (() => {
445
629
  const value = parsed.cli;
446
630
  if (!isRecord(value))
447
631
  return undefined;
448
- if (typeof value.disabled !== 'undefined') {
632
+ if (typeof value.disabled !== "undefined") {
449
633
  throw new Error(`Invalid config file ${path}: "cli.disabled" is not supported. Use "cli.enabled" instead.`);
450
634
  }
451
- const enabled = typeof value.enabled !== 'undefined'
452
- ? parseCliProviderList(value.enabled, path, 'cli.enabled')
635
+ const enabled = typeof value.enabled !== "undefined"
636
+ ? parseCliProviderList(value.enabled, path, "cli.enabled")
453
637
  : undefined;
454
- const claude = value.claude ? parseCliProviderConfig(value.claude, path, 'claude') : undefined;
455
- const codex = value.codex ? parseCliProviderConfig(value.codex, path, 'codex') : undefined;
456
- const gemini = value.gemini ? parseCliProviderConfig(value.gemini, path, 'gemini') : undefined;
457
- const promptOverride = typeof value.promptOverride === 'string' && value.promptOverride.trim().length > 0
638
+ const claude = value.claude ? parseCliProviderConfig(value.claude, path, "claude") : undefined;
639
+ const codex = value.codex ? parseCliProviderConfig(value.codex, path, "codex") : undefined;
640
+ const gemini = value.gemini ? parseCliProviderConfig(value.gemini, path, "gemini") : undefined;
641
+ const agent = value.agent ? parseCliProviderConfig(value.agent, path, "agent") : undefined;
642
+ if (typeof value.autoFallback !== "undefined" && typeof value.magicAuto !== "undefined") {
643
+ throw new Error(`Invalid config file ${path}: use only one of "cli.autoFallback" or legacy "cli.magicAuto".`);
644
+ }
645
+ const autoFallback = (() => {
646
+ if (typeof value.autoFallback !== "undefined") {
647
+ return parseCliAutoFallbackConfig(value.autoFallback, path, "autoFallback");
648
+ }
649
+ if (typeof value.magicAuto !== "undefined") {
650
+ return parseCliAutoFallbackConfig(value.magicAuto, path, "magicAuto");
651
+ }
652
+ return undefined;
653
+ })();
654
+ const promptOverride = typeof value.promptOverride === "string" && value.promptOverride.trim().length > 0
458
655
  ? value.promptOverride.trim()
459
656
  : undefined;
460
- const allowTools = typeof value.allowTools === 'boolean' ? value.allowTools : undefined;
461
- const cwd = typeof value.cwd === 'string' && value.cwd.trim().length > 0 ? value.cwd.trim() : undefined;
462
- const extraArgs = typeof value.extraArgs === 'undefined'
657
+ const allowTools = typeof value.allowTools === "boolean" ? value.allowTools : undefined;
658
+ const cwd = typeof value.cwd === "string" && value.cwd.trim().length > 0 ? value.cwd.trim() : undefined;
659
+ const extraArgs = typeof value.extraArgs === "undefined"
463
660
  ? undefined
464
- : parseStringArray(value.extraArgs, path, 'cli.extraArgs');
661
+ : parseStringArray(value.extraArgs, path, "cli.extraArgs");
465
662
  return enabled ||
466
663
  claude ||
467
664
  codex ||
468
665
  gemini ||
666
+ agent ||
667
+ autoFallback ||
469
668
  promptOverride ||
470
- typeof allowTools === 'boolean' ||
669
+ typeof allowTools === "boolean" ||
471
670
  cwd ||
472
671
  (extraArgs && extraArgs.length > 0)
473
672
  ? {
@@ -475,8 +674,10 @@ export function loadSummarizeConfig({ env }) {
475
674
  ...(claude ? { claude } : {}),
476
675
  ...(codex ? { codex } : {}),
477
676
  ...(gemini ? { gemini } : {}),
677
+ ...(agent ? { agent } : {}),
678
+ ...(autoFallback ? { autoFallback } : {}),
478
679
  ...(promptOverride ? { promptOverride } : {}),
479
- ...(typeof allowTools === 'boolean' ? { allowTools } : {}),
680
+ ...(typeof allowTools === "boolean" ? { allowTools } : {}),
480
681
  ...(cwd ? { cwd } : {}),
481
682
  ...(extraArgs && extraArgs.length > 0 ? { extraArgs } : {}),
482
683
  }
@@ -484,45 +685,59 @@ export function loadSummarizeConfig({ env }) {
484
685
  })();
485
686
  const output = (() => {
486
687
  const value = parsed.output;
487
- if (typeof value === 'undefined')
688
+ if (typeof value === "undefined")
488
689
  return undefined;
489
690
  if (!isRecord(value)) {
490
691
  throw new Error(`Invalid config file ${path}: "output" must be an object.`);
491
692
  }
492
- const language = typeof value.language === 'string' && value.language.trim().length > 0
693
+ const language = typeof value.language === "string" && value.language.trim().length > 0
493
694
  ? value.language.trim()
494
695
  : undefined;
495
- return typeof language === 'string' ? { language } : undefined;
696
+ return typeof language === "string" ? { language } : undefined;
697
+ })();
698
+ const ui = (() => {
699
+ const value = parsed.ui;
700
+ if (typeof value === "undefined")
701
+ return undefined;
702
+ if (!isRecord(value)) {
703
+ throw new Error(`Invalid config file ${path}: "ui" must be an object.`);
704
+ }
705
+ const themeRaw = typeof value.theme === "string" ? value.theme.trim().toLowerCase() : "";
706
+ if (themeRaw && !isCliThemeName(themeRaw)) {
707
+ throw new Error(`Invalid config file ${path}: "ui.theme" must be one of ${listCliThemes().join(", ")}.`);
708
+ }
709
+ const theme = themeRaw.length > 0 ? themeRaw : undefined;
710
+ return theme ? { theme } : undefined;
496
711
  })();
497
712
  const logging = (() => {
498
713
  const value = parsed.logging;
499
- if (typeof value === 'undefined')
714
+ if (typeof value === "undefined")
500
715
  return undefined;
501
716
  if (!isRecord(value)) {
502
717
  throw new Error(`Invalid config file ${path}: "logging" must be an object.`);
503
718
  }
504
- const enabled = typeof value.enabled === 'boolean' ? value.enabled : undefined;
505
- const level = typeof value.level === 'undefined' ? undefined : parseLoggingLevel(value.level, path);
506
- const format = typeof value.format === 'undefined' ? undefined : parseLoggingFormat(value.format, path);
507
- const file = typeof value.file === 'string' && value.file.trim().length > 0
719
+ const enabled = typeof value.enabled === "boolean" ? value.enabled : undefined;
720
+ const level = typeof value.level === "undefined" ? undefined : parseLoggingLevel(value.level, path);
721
+ const format = typeof value.format === "undefined" ? undefined : parseLoggingFormat(value.format, path);
722
+ const file = typeof value.file === "string" && value.file.trim().length > 0
508
723
  ? value.file.trim()
509
- : typeof value.file === 'undefined'
724
+ : typeof value.file === "undefined"
510
725
  ? undefined
511
726
  : (() => {
512
727
  throw new Error(`Invalid config file ${path}: "logging.file" must be a string.`);
513
728
  })();
514
729
  const maxMbRaw = value.maxMb;
515
- const maxMb = typeof maxMbRaw === 'number' && Number.isFinite(maxMbRaw) && maxMbRaw > 0
730
+ const maxMb = typeof maxMbRaw === "number" && Number.isFinite(maxMbRaw) && maxMbRaw > 0
516
731
  ? maxMbRaw
517
- : typeof maxMbRaw === 'undefined'
732
+ : typeof maxMbRaw === "undefined"
518
733
  ? undefined
519
734
  : (() => {
520
735
  throw new Error(`Invalid config file ${path}: "logging.maxMb" must be a number.`);
521
736
  })();
522
737
  const maxFilesRaw = value.maxFiles;
523
- const maxFiles = typeof maxFilesRaw === 'number' && Number.isFinite(maxFilesRaw) && maxFilesRaw > 0
738
+ const maxFiles = typeof maxFilesRaw === "number" && Number.isFinite(maxFilesRaw) && maxFilesRaw > 0
524
739
  ? Math.trunc(maxFilesRaw)
525
- : typeof maxFilesRaw === 'undefined'
740
+ : typeof maxFilesRaw === "undefined"
526
741
  ? undefined
527
742
  : (() => {
528
743
  throw new Error(`Invalid config file ${path}: "logging.maxFiles" must be a number.`);
@@ -531,46 +746,97 @@ export function loadSummarizeConfig({ env }) {
531
746
  level ||
532
747
  format ||
533
748
  file ||
534
- typeof maxMb === 'number' ||
535
- typeof maxFiles === 'number'
749
+ typeof maxMb === "number" ||
750
+ typeof maxFiles === "number"
536
751
  ? {
537
- ...(typeof enabled === 'boolean' ? { enabled } : {}),
752
+ ...(typeof enabled === "boolean" ? { enabled } : {}),
538
753
  ...(level ? { level } : {}),
539
754
  ...(format ? { format } : {}),
540
755
  ...(file ? { file } : {}),
541
- ...(typeof maxMb === 'number' ? { maxMb } : {}),
542
- ...(typeof maxFiles === 'number' ? { maxFiles } : {}),
756
+ ...(typeof maxMb === "number" ? { maxMb } : {}),
757
+ ...(typeof maxFiles === "number" ? { maxFiles } : {}),
543
758
  }
544
759
  : undefined;
545
760
  })();
546
761
  const openai = (() => {
547
762
  const value = parsed.openai;
548
- if (typeof value === 'undefined')
763
+ if (typeof value === "undefined")
549
764
  return undefined;
550
765
  if (!isRecord(value)) {
551
766
  throw new Error(`Invalid config file ${path}: "openai" must be an object.`);
552
767
  }
553
768
  const baseUrl = parseOptionalBaseUrl(value.baseUrl);
554
- const useChatCompletions = typeof value.useChatCompletions === 'boolean' ? value.useChatCompletions : undefined;
769
+ const useChatCompletions = typeof value.useChatCompletions === "boolean" ? value.useChatCompletions : undefined;
555
770
  const whisperUsdPerMinuteRaw = value.whisperUsdPerMinute;
556
- const whisperUsdPerMinute = typeof whisperUsdPerMinuteRaw === 'number' &&
771
+ const whisperUsdPerMinute = typeof whisperUsdPerMinuteRaw === "number" &&
557
772
  Number.isFinite(whisperUsdPerMinuteRaw) &&
558
773
  whisperUsdPerMinuteRaw > 0
559
774
  ? whisperUsdPerMinuteRaw
560
775
  : undefined;
561
- return typeof baseUrl === 'string' ||
562
- typeof useChatCompletions === 'boolean' ||
563
- typeof whisperUsdPerMinute === 'number'
776
+ return typeof baseUrl === "string" ||
777
+ typeof useChatCompletions === "boolean" ||
778
+ typeof whisperUsdPerMinute === "number"
564
779
  ? {
565
- ...(typeof baseUrl === 'string' ? { baseUrl } : {}),
566
- ...(typeof useChatCompletions === 'boolean' ? { useChatCompletions } : {}),
567
- ...(typeof whisperUsdPerMinute === 'number' ? { whisperUsdPerMinute } : {}),
780
+ ...(typeof baseUrl === "string" ? { baseUrl } : {}),
781
+ ...(typeof useChatCompletions === "boolean" ? { useChatCompletions } : {}),
782
+ ...(typeof whisperUsdPerMinute === "number" ? { whisperUsdPerMinute } : {}),
568
783
  }
569
784
  : undefined;
570
785
  })();
571
- const anthropic = parseProviderBaseUrlConfig(parsed.anthropic, path, 'anthropic');
572
- const google = parseProviderBaseUrlConfig(parsed.google, path, 'google');
573
- const xai = parseProviderBaseUrlConfig(parsed.xai, path, 'xai');
786
+ const anthropic = parseProviderBaseUrlConfig(parsed.anthropic, path, "anthropic");
787
+ const google = parseProviderBaseUrlConfig(parsed.google, path, "google");
788
+ const xai = parseProviderBaseUrlConfig(parsed.xai, path, "xai");
789
+ const configEnv = (() => {
790
+ const value = parsed.env;
791
+ if (typeof value === "undefined")
792
+ return undefined;
793
+ if (!isRecord(value)) {
794
+ throw new Error(`Invalid config file ${path}: "env" must be an object.`);
795
+ }
796
+ const env = {};
797
+ for (const [rawKey, rawValue] of Object.entries(value)) {
798
+ const key = rawKey.trim();
799
+ if (key.length === 0) {
800
+ throw new Error(`Invalid config file ${path}: "env" contains an empty key.`);
801
+ }
802
+ if (typeof rawValue !== "string") {
803
+ throw new Error(`Invalid config file ${path}: "env.${rawKey}" must be a string.`);
804
+ }
805
+ env[key] = rawValue;
806
+ }
807
+ return Object.keys(env).length > 0 ? env : undefined;
808
+ })();
809
+ const apiKeys = (() => {
810
+ const value = parsed.apiKeys;
811
+ if (typeof value === "undefined")
812
+ return undefined;
813
+ if (!isRecord(value)) {
814
+ throw new Error(`Invalid config file ${path}: "apiKeys" must be an object.`);
815
+ }
816
+ const keys = {};
817
+ const allowed = [
818
+ "openai",
819
+ "anthropic",
820
+ "google",
821
+ "xai",
822
+ "openrouter",
823
+ "zai",
824
+ "apify",
825
+ "firecrawl",
826
+ "fal",
827
+ ];
828
+ for (const [key, val] of Object.entries(value)) {
829
+ const k = key.trim().toLowerCase();
830
+ if (!allowed.includes(k)) {
831
+ throw new Error(`Invalid config file ${path}: unknown apiKeys provider "${key}".`);
832
+ }
833
+ if (typeof val !== "string" || val.trim().length === 0) {
834
+ throw new Error(`Invalid config file ${path}: "apiKeys.${key}" must be a non-empty string.`);
835
+ }
836
+ keys[k] = val.trim();
837
+ }
838
+ return Object.keys(keys).length > 0 ? keys : undefined;
839
+ })();
574
840
  return {
575
841
  config: {
576
842
  ...(model ? { model } : {}),
@@ -579,13 +845,17 @@ export function loadSummarizeConfig({ env }) {
579
845
  ...(cache ? { cache } : {}),
580
846
  ...(models ? { models } : {}),
581
847
  ...(media ? { media } : {}),
848
+ ...(slides ? { slides } : {}),
582
849
  ...(output ? { output } : {}),
850
+ ...(ui ? { ui } : {}),
583
851
  ...(cli ? { cli } : {}),
584
852
  ...(openai ? { openai } : {}),
585
853
  ...(anthropic ? { anthropic } : {}),
586
854
  ...(google ? { google } : {}),
587
855
  ...(xai ? { xai } : {}),
588
856
  ...(logging ? { logging } : {}),
857
+ ...(configEnv ? { env: configEnv } : {}),
858
+ ...(apiKeys ? { apiKeys } : {}),
589
859
  },
590
860
  path,
591
861
  };