@steipete/summarize 0.11.0 → 0.12.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 (361) hide show
  1. package/CHANGELOG.md +44 -1
  2. package/README.md +63 -17
  3. package/dist/cli.js +1 -1
  4. package/dist/esm/cache-keys.js +75 -0
  5. package/dist/esm/cache-keys.js.map +1 -0
  6. package/dist/esm/cache-slides-cleanup.js +47 -0
  7. package/dist/esm/cache-slides-cleanup.js.map +1 -0
  8. package/dist/esm/cache.js +14 -91
  9. package/dist/esm/cache.js.map +1 -1
  10. package/dist/esm/config/env.js +49 -0
  11. package/dist/esm/config/env.js.map +1 -0
  12. package/dist/esm/config/model.js +193 -0
  13. package/dist/esm/config/model.js.map +1 -0
  14. package/dist/esm/config/parse-helpers.js +50 -0
  15. package/dist/esm/config/parse-helpers.js.map +1 -0
  16. package/dist/esm/config/read.js +83 -0
  17. package/dist/esm/config/read.js.map +1 -0
  18. package/dist/esm/config/sections.js +438 -0
  19. package/dist/esm/config/sections.js.map +1 -0
  20. package/dist/esm/config/types.js +2 -0
  21. package/dist/esm/config/types.js.map +1 -0
  22. package/dist/esm/config.js +24 -807
  23. package/dist/esm/config.js.map +1 -1
  24. package/dist/esm/content/asset.js +2 -2
  25. package/dist/esm/content/asset.js.map +1 -1
  26. package/dist/esm/daemon/agent-model.js +235 -0
  27. package/dist/esm/daemon/agent-model.js.map +1 -0
  28. package/dist/esm/daemon/agent-request.js +87 -0
  29. package/dist/esm/daemon/agent-request.js.map +1 -0
  30. package/dist/esm/daemon/agent.js +42 -243
  31. package/dist/esm/daemon/agent.js.map +1 -1
  32. package/dist/esm/daemon/chat.js +69 -8
  33. package/dist/esm/daemon/chat.js.map +1 -1
  34. package/dist/esm/daemon/cli.js +21 -4
  35. package/dist/esm/daemon/cli.js.map +1 -1
  36. package/dist/esm/daemon/config.js +65 -9
  37. package/dist/esm/daemon/config.js.map +1 -1
  38. package/dist/esm/daemon/env-snapshot.js +4 -0
  39. package/dist/esm/daemon/env-snapshot.js.map +1 -1
  40. package/dist/esm/daemon/flow-context.js +8 -1
  41. package/dist/esm/daemon/flow-context.js.map +1 -1
  42. package/dist/esm/daemon/models.js +16 -0
  43. package/dist/esm/daemon/models.js.map +1 -1
  44. package/dist/esm/daemon/process-registry.js.map +1 -1
  45. package/dist/esm/daemon/server-admin-routes.js +134 -0
  46. package/dist/esm/daemon/server-admin-routes.js.map +1 -0
  47. package/dist/esm/daemon/server-agent-route.js +104 -0
  48. package/dist/esm/daemon/server-agent-route.js.map +1 -0
  49. package/dist/esm/daemon/server-http.js +89 -0
  50. package/dist/esm/daemon/server-http.js.map +1 -0
  51. package/dist/esm/daemon/server-session-routes.js +209 -0
  52. package/dist/esm/daemon/server-session-routes.js.map +1 -0
  53. package/dist/esm/daemon/server-session.js +118 -0
  54. package/dist/esm/daemon/server-session.js.map +1 -0
  55. package/dist/esm/daemon/server-sse.js +28 -0
  56. package/dist/esm/daemon/server-sse.js.map +1 -0
  57. package/dist/esm/daemon/server-summarize-execution.js +357 -0
  58. package/dist/esm/daemon/server-summarize-execution.js.map +1 -0
  59. package/dist/esm/daemon/server-summarize-request.js +119 -0
  60. package/dist/esm/daemon/server-summarize-request.js.map +1 -0
  61. package/dist/esm/daemon/server.js +72 -1121
  62. package/dist/esm/daemon/server.js.map +1 -1
  63. package/dist/esm/daemon/summarize-progress.js +1 -1
  64. package/dist/esm/daemon/summarize-progress.js.map +1 -1
  65. package/dist/esm/daemon/summarize.js.map +1 -1
  66. package/dist/esm/llm/cli-exec.js +75 -0
  67. package/dist/esm/llm/cli-exec.js.map +1 -0
  68. package/dist/esm/llm/cli-provider-output.js +191 -0
  69. package/dist/esm/llm/cli-provider-output.js.map +1 -0
  70. package/dist/esm/llm/cli.js +3 -212
  71. package/dist/esm/llm/cli.js.map +1 -1
  72. package/dist/esm/llm/generate-text-document.js +109 -0
  73. package/dist/esm/llm/generate-text-document.js.map +1 -0
  74. package/dist/esm/llm/generate-text-shared.js +102 -0
  75. package/dist/esm/llm/generate-text-shared.js.map +1 -0
  76. package/dist/esm/llm/generate-text-stream.js +258 -0
  77. package/dist/esm/llm/generate-text-stream.js.map +1 -0
  78. package/dist/esm/llm/generate-text.js +145 -480
  79. package/dist/esm/llm/generate-text.js.map +1 -1
  80. package/dist/esm/llm/model-id.js +21 -20
  81. package/dist/esm/llm/model-id.js.map +1 -1
  82. package/dist/esm/llm/provider-capabilities.js +2 -0
  83. package/dist/esm/llm/provider-capabilities.js.map +1 -0
  84. package/dist/esm/llm/provider-profile.js +142 -0
  85. package/dist/esm/llm/provider-profile.js.map +1 -0
  86. package/dist/esm/llm/providers/google.js +42 -5
  87. package/dist/esm/llm/providers/google.js.map +1 -1
  88. package/dist/esm/llm/providers/models.js +13 -0
  89. package/dist/esm/llm/providers/models.js.map +1 -1
  90. package/dist/esm/llm/providers/openai.js.map +1 -1
  91. package/dist/esm/llm/transcript-to-markdown.js.map +1 -1
  92. package/dist/esm/model-auto-cli.js +89 -0
  93. package/dist/esm/model-auto-cli.js.map +1 -0
  94. package/dist/esm/model-auto-rules.js +86 -0
  95. package/dist/esm/model-auto-rules.js.map +1 -0
  96. package/dist/esm/model-auto.js +10 -245
  97. package/dist/esm/model-auto.js.map +1 -1
  98. package/dist/esm/model-spec.js +23 -17
  99. package/dist/esm/model-spec.js.map +1 -1
  100. package/dist/esm/refresh-free.js +1 -1
  101. package/dist/esm/refresh-free.js.map +1 -1
  102. package/dist/esm/run/attachments.js +1 -1
  103. package/dist/esm/run/attachments.js.map +1 -1
  104. package/dist/esm/run/bird/exec.js +23 -0
  105. package/dist/esm/run/bird/exec.js.map +1 -0
  106. package/dist/esm/run/bird/media.js +171 -0
  107. package/dist/esm/run/bird/media.js.map +1 -0
  108. package/dist/esm/run/bird/parse.js +82 -0
  109. package/dist/esm/run/bird/parse.js.map +1 -0
  110. package/dist/esm/run/bird/types.js +2 -0
  111. package/dist/esm/run/bird/types.js.map +1 -0
  112. package/dist/esm/run/bird.js +86 -144
  113. package/dist/esm/run/bird.js.map +1 -1
  114. package/dist/esm/run/cache-state.js.map +1 -1
  115. package/dist/esm/run/constants.js +2 -1
  116. package/dist/esm/run/constants.js.map +1 -1
  117. package/dist/esm/run/env.js +3 -0
  118. package/dist/esm/run/env.js.map +1 -1
  119. package/dist/esm/run/finish-line-labels.js +76 -0
  120. package/dist/esm/run/finish-line-labels.js.map +1 -0
  121. package/dist/esm/run/finish-line-lengths.js +96 -0
  122. package/dist/esm/run/finish-line-lengths.js.map +1 -0
  123. package/dist/esm/run/finish-line.js +3 -169
  124. package/dist/esm/run/finish-line.js.map +1 -1
  125. package/dist/esm/run/flows/asset/extract.js.map +1 -1
  126. package/dist/esm/run/flows/asset/input.js +1 -1
  127. package/dist/esm/run/flows/asset/input.js.map +1 -1
  128. package/dist/esm/run/flows/asset/media.js +19 -10
  129. package/dist/esm/run/flows/asset/media.js.map +1 -1
  130. package/dist/esm/run/flows/asset/output.js.map +1 -1
  131. package/dist/esm/run/flows/asset/preprocess.js.map +1 -1
  132. package/dist/esm/run/flows/asset/summary-attempts.js +109 -0
  133. package/dist/esm/run/flows/asset/summary-attempts.js.map +1 -0
  134. package/dist/esm/run/flows/asset/summary.js +19 -107
  135. package/dist/esm/run/flows/asset/summary.js.map +1 -1
  136. package/dist/esm/run/flows/url/extract.js +7 -4
  137. package/dist/esm/run/flows/url/extract.js.map +1 -1
  138. package/dist/esm/run/flows/url/flow-progress.js +119 -0
  139. package/dist/esm/run/flows/url/flow-progress.js.map +1 -0
  140. package/dist/esm/run/flows/url/flow.js +22 -93
  141. package/dist/esm/run/flows/url/flow.js.map +1 -1
  142. package/dist/esm/run/flows/url/markdown.js +21 -3
  143. package/dist/esm/run/flows/url/markdown.js.map +1 -1
  144. package/dist/esm/run/flows/url/progress-status.js +56 -0
  145. package/dist/esm/run/flows/url/progress-status.js.map +1 -0
  146. package/dist/esm/run/flows/url/slides-output-render.js +78 -0
  147. package/dist/esm/run/flows/url/slides-output-render.js.map +1 -0
  148. package/dist/esm/run/flows/url/slides-output-state.js +86 -0
  149. package/dist/esm/run/flows/url/slides-output-state.js.map +1 -0
  150. package/dist/esm/run/flows/url/slides-output-stream.js +271 -0
  151. package/dist/esm/run/flows/url/slides-output-stream.js.map +1 -0
  152. package/dist/esm/run/flows/url/slides-output.js +29 -422
  153. package/dist/esm/run/flows/url/slides-output.js.map +1 -1
  154. package/dist/esm/run/flows/url/slides-text-markdown.js +431 -0
  155. package/dist/esm/run/flows/url/slides-text-markdown.js.map +1 -0
  156. package/dist/esm/run/flows/url/slides-text-transcript.js +199 -0
  157. package/dist/esm/run/flows/url/slides-text-transcript.js.map +1 -0
  158. package/dist/esm/run/flows/url/slides-text-types.js +2 -0
  159. package/dist/esm/run/flows/url/slides-text-types.js.map +1 -0
  160. package/dist/esm/run/flows/url/slides-text.js +2 -627
  161. package/dist/esm/run/flows/url/slides-text.js.map +1 -1
  162. package/dist/esm/run/flows/url/summary-finish.js +34 -0
  163. package/dist/esm/run/flows/url/summary-finish.js.map +1 -0
  164. package/dist/esm/run/flows/url/summary-json.js +32 -0
  165. package/dist/esm/run/flows/url/summary-json.js.map +1 -0
  166. package/dist/esm/run/flows/url/summary-prompt.js +147 -0
  167. package/dist/esm/run/flows/url/summary-prompt.js.map +1 -0
  168. package/dist/esm/run/flows/url/summary-resolution.js +320 -0
  169. package/dist/esm/run/flows/url/summary-resolution.js.map +1 -0
  170. package/dist/esm/run/flows/url/summary-timestamps.js +136 -0
  171. package/dist/esm/run/flows/url/summary-timestamps.js.map +1 -0
  172. package/dist/esm/run/flows/url/summary.js +49 -543
  173. package/dist/esm/run/flows/url/summary.js.map +1 -1
  174. package/dist/esm/run/help.js +9 -3
  175. package/dist/esm/run/help.js.map +1 -1
  176. package/dist/esm/run/markdown-transforms.js +89 -0
  177. package/dist/esm/run/markdown-transforms.js.map +1 -0
  178. package/dist/esm/run/markdown.js +1 -96
  179. package/dist/esm/run/markdown.js.map +1 -1
  180. package/dist/esm/run/run-env.js +28 -7
  181. package/dist/esm/run/run-env.js.map +1 -1
  182. package/dist/esm/run/run-settings-parse.js +73 -0
  183. package/dist/esm/run/run-settings-parse.js.map +1 -0
  184. package/dist/esm/run/run-settings.js +1 -72
  185. package/dist/esm/run/run-settings.js.map +1 -1
  186. package/dist/esm/run/runner-contexts.js +116 -0
  187. package/dist/esm/run/runner-contexts.js.map +1 -0
  188. package/dist/esm/run/runner-execution.js +62 -0
  189. package/dist/esm/run/runner-execution.js.map +1 -0
  190. package/dist/esm/run/runner-flags.js +97 -0
  191. package/dist/esm/run/runner-flags.js.map +1 -0
  192. package/dist/esm/run/runner-setup.js +109 -0
  193. package/dist/esm/run/runner-setup.js.map +1 -0
  194. package/dist/esm/run/runner-slides.js +38 -0
  195. package/dist/esm/run/runner-slides.js.map +1 -0
  196. package/dist/esm/run/runner.js +99 -390
  197. package/dist/esm/run/runner.js.map +1 -1
  198. package/dist/esm/run/slides-render.js +5 -2
  199. package/dist/esm/run/slides-render.js.map +1 -1
  200. package/dist/esm/run/stdin-temp-file.js +1 -1
  201. package/dist/esm/run/stdin-temp-file.js.map +1 -1
  202. package/dist/esm/run/streaming.js +1 -0
  203. package/dist/esm/run/streaming.js.map +1 -1
  204. package/dist/esm/run/summary-engine.js +26 -10
  205. package/dist/esm/run/summary-engine.js.map +1 -1
  206. package/dist/esm/run/summary-llm.js +2 -1
  207. package/dist/esm/run/summary-llm.js.map +1 -1
  208. package/dist/esm/run/terminal.js +4 -1
  209. package/dist/esm/run/terminal.js.map +1 -1
  210. package/dist/esm/run/transcriber-cli.js +1 -1
  211. package/dist/esm/run/transcriber-cli.js.map +1 -1
  212. package/dist/esm/slides/download.js +242 -0
  213. package/dist/esm/slides/download.js.map +1 -0
  214. package/dist/esm/slides/extract-finalize.js +98 -0
  215. package/dist/esm/slides/extract-finalize.js.map +1 -0
  216. package/dist/esm/slides/extract.js +64 -1621
  217. package/dist/esm/slides/extract.js.map +1 -1
  218. package/dist/esm/slides/frame-extraction.js +372 -0
  219. package/dist/esm/slides/frame-extraction.js.map +1 -0
  220. package/dist/esm/slides/ingest.js +167 -0
  221. package/dist/esm/slides/ingest.js.map +1 -0
  222. package/dist/esm/slides/ocr.js +91 -0
  223. package/dist/esm/slides/ocr.js.map +1 -0
  224. package/dist/esm/slides/process.js +218 -0
  225. package/dist/esm/slides/process.js.map +1 -0
  226. package/dist/esm/slides/scene-detection.js +387 -0
  227. package/dist/esm/slides/scene-detection.js.map +1 -0
  228. package/dist/esm/slides/source-id.js +42 -0
  229. package/dist/esm/slides/source-id.js.map +1 -0
  230. package/dist/esm/tty/progress/fetch-html.js.map +1 -1
  231. package/dist/esm/tty/progress/transcript.js +21 -8
  232. package/dist/esm/tty/progress/transcript.js.map +1 -1
  233. package/dist/esm/tty/spinner.js +8 -2
  234. package/dist/esm/tty/spinner.js.map +1 -1
  235. package/dist/esm/tty/website-progress.js +5 -3
  236. package/dist/esm/tty/website-progress.js.map +1 -1
  237. package/dist/esm/version.js +1 -1
  238. package/dist/types/cache-keys.d.ts +44 -0
  239. package/dist/types/cache-slides-cleanup.d.ts +1 -0
  240. package/dist/types/cache.d.ts +1 -9
  241. package/dist/types/config/env.d.ts +6 -0
  242. package/dist/types/config/model.d.ts +3 -0
  243. package/dist/types/config/parse-helpers.d.ts +7 -0
  244. package/dist/types/config/read.d.ts +2 -0
  245. package/dist/types/config/sections.d.ts +33 -0
  246. package/dist/types/config/types.d.ts +230 -0
  247. package/dist/types/config.d.ts +3 -209
  248. package/dist/types/costs.d.ts +1 -1
  249. package/dist/types/daemon/agent-model.d.ts +40 -0
  250. package/dist/types/daemon/agent-request.d.ts +14 -0
  251. package/dist/types/daemon/chat.d.ts +3 -1
  252. package/dist/types/daemon/config.d.ts +13 -2
  253. package/dist/types/daemon/env-snapshot.d.ts +1 -1
  254. package/dist/types/daemon/flow-context.d.ts +1 -1
  255. package/dist/types/daemon/models.d.ts +1 -0
  256. package/dist/types/daemon/server-admin-routes.d.ts +22 -0
  257. package/dist/types/daemon/server-agent-route.d.ts +9 -0
  258. package/dist/types/daemon/server-http.d.ts +10 -0
  259. package/dist/types/daemon/server-session-routes.d.ts +11 -0
  260. package/dist/types/daemon/server-session.d.ts +52 -0
  261. package/dist/types/daemon/server-sse.d.ts +12 -0
  262. package/dist/types/daemon/server-summarize-execution.d.ts +70 -0
  263. package/dist/types/daemon/server-summarize-request.d.ts +36 -0
  264. package/dist/types/daemon/server.d.ts +3 -4
  265. package/dist/types/daemon/summarize.d.ts +1 -1
  266. package/dist/types/llm/cli-exec.d.ts +13 -0
  267. package/dist/types/llm/cli-provider-output.d.ts +16 -0
  268. package/dist/types/llm/generate-text-document.d.ts +34 -0
  269. package/dist/types/llm/generate-text-shared.d.ts +25 -0
  270. package/dist/types/llm/generate-text-stream.d.ts +26 -0
  271. package/dist/types/llm/generate-text.d.ts +6 -26
  272. package/dist/types/llm/html-to-markdown.d.ts +1 -1
  273. package/dist/types/llm/model-id.d.ts +1 -1
  274. package/dist/types/llm/provider-capabilities.d.ts +2 -0
  275. package/dist/types/llm/provider-profile.d.ts +31 -0
  276. package/dist/types/llm/providers/google.d.ts +6 -0
  277. package/dist/types/llm/providers/models.d.ts +5 -0
  278. package/dist/types/llm/transcript-to-markdown.d.ts +1 -1
  279. package/dist/types/model-auto-cli.d.ts +15 -0
  280. package/dist/types/model-auto-rules.d.ts +7 -0
  281. package/dist/types/model-auto.d.ts +5 -7
  282. package/dist/types/model-spec.d.ts +2 -2
  283. package/dist/types/run/attachments.d.ts +2 -2
  284. package/dist/types/run/bird/exec.d.ts +1 -0
  285. package/dist/types/run/bird/media.d.ts +3 -0
  286. package/dist/types/run/bird/parse.d.ts +3 -0
  287. package/dist/types/run/bird/types.d.ts +18 -0
  288. package/dist/types/run/bird.d.ts +12 -17
  289. package/dist/types/run/cache-state.d.ts +1 -1
  290. package/dist/types/run/constants.d.ts +2 -1
  291. package/dist/types/run/env.d.ts +1 -0
  292. package/dist/types/run/finish-line-labels.d.ts +29 -0
  293. package/dist/types/run/finish-line-lengths.d.ts +23 -0
  294. package/dist/types/run/finish-line.d.ts +2 -52
  295. package/dist/types/run/flows/asset/extract.d.ts +1 -1
  296. package/dist/types/run/flows/asset/input.d.ts +1 -1
  297. package/dist/types/run/flows/asset/preprocess.d.ts +1 -1
  298. package/dist/types/run/flows/asset/summary-attempts.d.ts +24 -0
  299. package/dist/types/run/flows/asset/summary.d.ts +6 -2
  300. package/dist/types/run/flows/url/flow-progress.d.ts +41 -0
  301. package/dist/types/run/flows/url/markdown.d.ts +2 -2
  302. package/dist/types/run/flows/url/progress-status.d.ts +16 -0
  303. package/dist/types/run/flows/url/slides-output-render.d.ts +43 -0
  304. package/dist/types/run/flows/url/slides-output-state.d.ts +21 -0
  305. package/dist/types/run/flows/url/slides-output-stream.d.ts +18 -0
  306. package/dist/types/run/flows/url/slides-output.d.ts +2 -17
  307. package/dist/types/run/flows/url/slides-text-markdown.d.ts +46 -0
  308. package/dist/types/run/flows/url/slides-text-transcript.d.ts +36 -0
  309. package/dist/types/run/flows/url/slides-text-types.d.ts +8 -0
  310. package/dist/types/run/flows/url/slides-text.d.ts +3 -87
  311. package/dist/types/run/flows/url/summary-finish.d.ts +16 -0
  312. package/dist/types/run/flows/url/summary-json.d.ts +51 -0
  313. package/dist/types/run/flows/url/summary-prompt.d.ts +22 -0
  314. package/dist/types/run/flows/url/summary-resolution.d.ts +31 -0
  315. package/dist/types/run/flows/url/summary-timestamps.d.ts +11 -0
  316. package/dist/types/run/flows/url/types.d.ts +4 -0
  317. package/dist/types/run/markdown-transforms.d.ts +3 -0
  318. package/dist/types/run/run-context.d.ts +4 -0
  319. package/dist/types/run/run-env.d.ts +4 -0
  320. package/dist/types/run/run-settings-parse.d.ts +5 -0
  321. package/dist/types/run/runner-contexts.d.ts +62 -0
  322. package/dist/types/run/runner-execution.d.ts +57 -0
  323. package/dist/types/run/runner-flags.d.ts +41 -0
  324. package/dist/types/run/runner-setup.d.ts +21 -0
  325. package/dist/types/run/runner-slides.d.ts +8 -0
  326. package/dist/types/run/streaming.d.ts +1 -1
  327. package/dist/types/run/summary-engine.d.ts +8 -4
  328. package/dist/types/run/summary-llm.d.ts +4 -3
  329. package/dist/types/run/terminal.d.ts +2 -0
  330. package/dist/types/run/types.d.ts +2 -2
  331. package/dist/types/slides/download.d.ts +29 -0
  332. package/dist/types/slides/extract-finalize.d.ts +57 -0
  333. package/dist/types/slides/extract.d.ts +1 -7
  334. package/dist/types/slides/frame-extraction.d.ts +38 -0
  335. package/dist/types/slides/ingest.d.ts +47 -0
  336. package/dist/types/slides/ocr.d.ts +5 -0
  337. package/dist/types/slides/process.d.ts +22 -0
  338. package/dist/types/slides/scene-detection.d.ts +75 -0
  339. package/dist/types/slides/source-id.d.ts +2 -0
  340. package/dist/types/version.d.ts +1 -1
  341. package/docs/_config.yml +1 -0
  342. package/docs/agent.md +3 -2
  343. package/docs/assets/site.css +134 -2
  344. package/docs/cache.md +2 -1
  345. package/docs/chrome-extension.md +12 -4
  346. package/docs/cli.md +2 -2
  347. package/docs/config.md +11 -4
  348. package/docs/index.html +5 -0
  349. package/docs/llm.md +5 -2
  350. package/docs/manual-tests.md +3 -0
  351. package/docs/media.md +3 -1
  352. package/docs/model-auto.md +2 -2
  353. package/docs/model-provider-resolution.md +57 -0
  354. package/docs/site/index.html +5 -0
  355. package/docs/slides-rendering-flow.md +46 -0
  356. package/docs/slides.md +5 -5
  357. package/docs/smoketest.md +1 -1
  358. package/docs/transcript-provider-flow.md +66 -0
  359. package/docs/website.md +1 -0
  360. package/docs/youtube.md +4 -2
  361. package/package.json +34 -41
@@ -1,403 +1,15 @@
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";
5
- function isRecord(value) {
6
- return typeof value === "object" && value !== null && !Array.isArray(value);
7
- }
8
- function parseOptionalBaseUrl(raw) {
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;
57
- }
58
- function parseProviderBaseUrlConfig(raw, path, providerName) {
59
- if (typeof raw === "undefined")
60
- return undefined;
61
- if (!isRecord(raw)) {
62
- throw new Error(`Invalid config file ${path}: "${providerName}" must be an object.`);
63
- }
64
- const baseUrl = parseOptionalBaseUrl(raw.baseUrl);
65
- return typeof baseUrl === "string" ? { baseUrl } : undefined;
66
- }
67
- function parseAutoRuleKind(value) {
68
- return value === "text" ||
69
- value === "website" ||
70
- value === "youtube" ||
71
- value === "image" ||
72
- value === "video" ||
73
- value === "file"
74
- ? value
75
- : null;
76
- }
77
- function parseCliProvider(value, path) {
78
- const trimmed = typeof value === "string" ? value.trim().toLowerCase() : "";
79
- if (trimmed === "claude" || trimmed === "codex" || trimmed === "gemini" || trimmed === "agent") {
80
- return trimmed;
81
- }
82
- throw new Error(`Invalid config file ${path}: unknown CLI provider "${String(value)}".`);
83
- }
84
- function parseStringArray(raw, path, label) {
85
- if (!Array.isArray(raw)) {
86
- throw new Error(`Invalid config file ${path}: "${label}" must be an array of strings.`);
87
- }
88
- const items = [];
89
- for (const entry of raw) {
90
- if (typeof entry !== "string") {
91
- throw new Error(`Invalid config file ${path}: "${label}" must be an array of strings.`);
92
- }
93
- const trimmed = entry.trim();
94
- if (!trimmed)
95
- continue;
96
- items.push(trimmed);
97
- }
98
- return items;
99
- }
100
- function parseLoggingLevel(raw, path) {
101
- if (typeof raw !== "string") {
102
- throw new Error(`Invalid config file ${path}: "logging.level" must be a string.`);
103
- }
104
- const trimmed = raw.trim().toLowerCase();
105
- if (trimmed === "debug" || trimmed === "info" || trimmed === "warn" || trimmed === "error") {
106
- return trimmed;
107
- }
108
- throw new Error(`Invalid config file ${path}: "logging.level" must be one of "debug", "info", "warn", "error".`);
109
- }
110
- function parseLoggingFormat(raw, path) {
111
- if (typeof raw !== "string") {
112
- throw new Error(`Invalid config file ${path}: "logging.format" must be a string.`);
113
- }
114
- const trimmed = raw.trim().toLowerCase();
115
- if (trimmed === "json" || trimmed === "pretty") {
116
- return trimmed;
117
- }
118
- throw new Error(`Invalid config file ${path}: "logging.format" must be one of "json" or "pretty".`);
119
- }
120
- function parseCliProviderList(raw, path, label) {
121
- if (!Array.isArray(raw)) {
122
- throw new Error(`Invalid config file ${path}: "${label}" must be an array.`);
123
- }
124
- const providers = [];
125
- for (const entry of raw) {
126
- const parsed = parseCliProvider(entry, path);
127
- if (!providers.includes(parsed))
128
- providers.push(parsed);
129
- }
130
- return providers.length > 0 ? providers : undefined;
131
- }
132
- function parseCliProviderConfig(raw, path, label) {
133
- if (!isRecord(raw)) {
134
- throw new Error(`Invalid config file ${path}: "cli.${label}" must be an object.`);
135
- }
136
- if (typeof raw.enabled !== "undefined") {
137
- throw new Error(`Invalid config file ${path}: "cli.${label}.enabled" is not supported. Use "cli.enabled" instead.`);
138
- }
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"
142
- ? undefined
143
- : parseStringArray(raw.extraArgs, path, `cli.${label}.extraArgs`);
144
- return {
145
- ...(binaryValue ? { binary: binaryValue } : {}),
146
- ...(modelValue ? { model: modelValue } : {}),
147
- ...(extraArgs && extraArgs.length > 0 ? { extraArgs } : {}),
148
- };
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
- }
177
- function parseWhenKinds(raw, path) {
178
- if (!Array.isArray(raw)) {
179
- throw new Error(`Invalid config file ${path}: "model.rules[].when" must be an array of kinds.`);
180
- }
181
- if (raw.length === 0) {
182
- throw new Error(`Invalid config file ${path}: "model.rules[].when" must not be empty.`);
183
- }
184
- const kinds = [];
185
- for (const entry of raw) {
186
- const kind = parseAutoRuleKind(entry);
187
- if (!kind) {
188
- throw new Error(`Invalid config file ${path}: unknown "when" kind "${String(entry)}".`);
189
- }
190
- if (!kinds.includes(kind))
191
- kinds.push(kind);
192
- }
193
- return kinds;
194
- }
195
- function parseModelCandidates(raw, path) {
196
- if (!Array.isArray(raw)) {
197
- throw new Error(`Invalid config file ${path}: "model.rules[].candidates" must be an array of strings.`);
198
- }
199
- const candidates = [];
200
- for (const entry of raw) {
201
- if (typeof entry !== "string") {
202
- throw new Error(`Invalid config file ${path}: "model.rules[].candidates" must be an array of strings.`);
203
- }
204
- const trimmed = entry.trim();
205
- if (trimmed.length === 0)
206
- continue;
207
- candidates.push(trimmed);
208
- }
209
- if (candidates.length === 0) {
210
- throw new Error(`Invalid config file ${path}: "model.rules[].candidates" must not be empty.`);
211
- }
212
- return candidates;
213
- }
214
- function parseTokenBand(raw, path) {
215
- if (!isRecord(raw)) {
216
- throw new Error(`Invalid config file ${path}: "model.rules[].bands[]" must be an object.`);
217
- }
218
- const candidates = parseModelCandidates(raw.candidates, path);
219
- const token = (() => {
220
- if (typeof raw.token === "undefined")
221
- return undefined;
222
- if (!isRecord(raw.token)) {
223
- throw new Error(`Invalid config file ${path}: "model.rules[].bands[].token" must be an object.`);
224
- }
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)) {
228
- throw new Error(`Invalid config file ${path}: "model.rules[].bands[].token.min" must be >= 0.`);
229
- }
230
- if (typeof max === "number" && (!Number.isFinite(max) || max < 0)) {
231
- throw new Error(`Invalid config file ${path}: "model.rules[].bands[].token.max" must be >= 0.`);
232
- }
233
- if (typeof min === "number" && typeof max === "number" && min > max) {
234
- throw new Error(`Invalid config file ${path}: "model.rules[].bands[].token.min" must be <= "token.max".`);
235
- }
236
- return typeof min === "number" || typeof max === "number" ? { min, max } : undefined;
237
- })();
238
- return { ...(token ? { token } : {}), candidates };
239
- }
240
- function assertNoComments(raw, path) {
241
- let inString = null;
242
- let escaped = false;
243
- let line = 1;
244
- let col = 1;
245
- for (let i = 0; i < raw.length; i += 1) {
246
- const ch = raw[i] ?? "";
247
- const next = raw[i + 1] ?? "";
248
- if (inString) {
249
- if (escaped) {
250
- escaped = false;
251
- col += 1;
252
- continue;
253
- }
254
- if (ch === "\\") {
255
- escaped = true;
256
- col += 1;
257
- continue;
258
- }
259
- if (ch === inString) {
260
- inString = null;
261
- }
262
- if (ch === "\n") {
263
- line += 1;
264
- col = 1;
265
- }
266
- else {
267
- col += 1;
268
- }
269
- continue;
270
- }
271
- if (ch === '"' || ch === "'") {
272
- inString = ch;
273
- escaped = false;
274
- col += 1;
275
- continue;
276
- }
277
- if (ch === "/" && next === "/") {
278
- throw new Error(`Invalid config file ${path}: comments are not allowed (found // at ${line}:${col}).`);
279
- }
280
- if (ch === "/" && next === "*") {
281
- throw new Error(`Invalid config file ${path}: comments are not allowed (found /* at ${line}:${col}).`);
282
- }
283
- if (ch === "\n") {
284
- line += 1;
285
- col = 1;
286
- }
287
- else {
288
- col += 1;
289
- }
290
- }
291
- }
1
+ import { parseModelConfig, parseModelsConfig } from "./config/model.js";
2
+ import { readParsedConfigFile, resolveSummarizeConfigPath } from "./config/read.js";
3
+ import { parseApiKeysConfig, parseCacheConfig, parseCliConfig, parseEnvConfig, parseLoggingConfig, parseMediaConfig, parseOpenAiConfig, parseOutputConfig, parseProviderBaseUrlConfig, parseSlidesConfig, parseUiConfig, } from "./config/sections.js";
4
+ export { mergeConfigEnv, resolveConfigEnv } from "./config/env.js";
292
5
  export function loadSummarizeConfig({ env }) {
293
- const home = env.HOME?.trim() || env.USERPROFILE?.trim() || null;
294
- if (!home)
6
+ const path = resolveSummarizeConfigPath(env);
7
+ if (!path)
295
8
  return { config: null, path: null };
296
- const path = join(home, ".summarize", "config.json");
297
- let raw;
298
- try {
299
- raw = readFileSync(path, "utf8");
300
- }
301
- catch {
9
+ const parsed = readParsedConfigFile(path);
10
+ if (!parsed)
302
11
  return { config: null, path };
303
- }
304
- let parsed;
305
- assertNoComments(raw, path);
306
- try {
307
- parsed = JSON5.parse(raw);
308
- }
309
- catch (error) {
310
- const message = error instanceof Error ? error.message : String(error);
311
- throw new Error(`Invalid JSON in config file ${path}: ${message}`);
312
- }
313
- if (!isRecord(parsed)) {
314
- throw new Error(`Invalid config file ${path}: expected an object at the top level`);
315
- }
316
- const parseModelConfig = (raw, label) => {
317
- if (typeof raw === "undefined")
318
- return undefined;
319
- // Shorthand:
320
- // - "auto" -> { mode: "auto" }
321
- // - "<provider>/<model>" or "openrouter/<provider>/<model>" -> { id: "..." }
322
- // - "<name>" -> { name: "<name>" }
323
- if (typeof raw === "string") {
324
- const value = raw.trim();
325
- if (value.length === 0) {
326
- throw new Error(`Invalid config file ${path}: "${label}" must not be empty.`);
327
- }
328
- if (value.toLowerCase() === "auto") {
329
- return { mode: "auto" };
330
- }
331
- if (value.includes("/")) {
332
- return { id: value };
333
- }
334
- return { name: value };
335
- }
336
- if (!isRecord(raw)) {
337
- throw new Error(`Invalid config file ${path}: "${label}" must be an object.`);
338
- }
339
- if (typeof raw.name === "string") {
340
- const name = raw.name.trim();
341
- if (name.length === 0) {
342
- throw new Error(`Invalid config file ${path}: "${label}.name" must not be empty.`);
343
- }
344
- if (name.toLowerCase() === "auto") {
345
- throw new Error(`Invalid config file ${path}: "${label}.name" must not be "auto".`);
346
- }
347
- return { name };
348
- }
349
- if (typeof raw.id === "string") {
350
- const id = raw.id.trim();
351
- if (id.length === 0) {
352
- throw new Error(`Invalid config file ${path}: "${label}.id" must not be empty.`);
353
- }
354
- if (!id.includes("/")) {
355
- throw new Error(`Invalid config file ${path}: "${label}.id" must be provider-prefixed (e.g. "openai/gpt-5-mini").`);
356
- }
357
- return { id };
358
- }
359
- const hasRules = typeof raw.rules !== "undefined";
360
- if (raw.mode === "auto" || (!("mode" in raw) && hasRules)) {
361
- const rules = (() => {
362
- if (typeof raw.rules === "undefined")
363
- return undefined;
364
- if (!Array.isArray(raw.rules)) {
365
- throw new Error(`Invalid config file ${path}: "${label}.rules" must be an array.`);
366
- }
367
- const rulesParsed = [];
368
- for (const entry of raw.rules) {
369
- if (!isRecord(entry))
370
- continue;
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";
374
- if (hasCandidates && hasBands) {
375
- throw new Error(`Invalid config file ${path}: "${label}.rules[]" must use either "candidates" or "bands" (not both).`);
376
- }
377
- if (hasCandidates) {
378
- const candidates = parseModelCandidates(entry.candidates, path);
379
- rulesParsed.push({ ...(when ? { when } : {}), candidates });
380
- continue;
381
- }
382
- if (hasBands) {
383
- if (!Array.isArray(entry.bands) || entry.bands.length === 0) {
384
- throw new Error(`Invalid config file ${path}: "${label}.rules[].bands" must be a non-empty array.`);
385
- }
386
- const bands = entry.bands.map((b) => parseTokenBand(b, path));
387
- rulesParsed.push({ ...(when ? { when } : {}), bands });
388
- continue;
389
- }
390
- throw new Error(`Invalid config file ${path}: "${label}.rules[]" must include "candidates" or "bands".`);
391
- }
392
- return rulesParsed;
393
- })();
394
- return { mode: "auto", ...(rules ? { rules } : {}) };
395
- }
396
- throw new Error(`Invalid config file ${path}: "${label}" must include either "id", "name", or { "mode": "auto" }.`);
397
- };
398
- const model = (() => {
399
- return parseModelConfig(parsed.model, "model");
400
- })();
12
+ const model = parseModelConfig(parsed.model, path, "model");
401
13
  const language = (() => {
402
14
  const value = parsed.language;
403
15
  if (typeof value === "undefined")
@@ -424,419 +36,22 @@ export function loadSummarizeConfig({ env }) {
424
36
  }
425
37
  return trimmed;
426
38
  })();
427
- const models = (() => {
428
- const root = parsed;
429
- if (typeof root.bags !== "undefined") {
430
- throw new Error(`Invalid config file ${path}: legacy key "bags" is no longer supported. Use "models" instead.`);
431
- }
432
- const raw = root.models;
433
- if (typeof raw === "undefined")
434
- return undefined;
435
- if (!isRecord(raw)) {
436
- throw new Error(`Invalid config file ${path}: "models" must be an object.`);
437
- }
438
- const out = {};
439
- const seen = new Set();
440
- for (const [keyRaw, value] of Object.entries(raw)) {
441
- const key = keyRaw.trim();
442
- if (!key)
443
- continue;
444
- const keyLower = key.toLowerCase();
445
- if (keyLower === "auto") {
446
- throw new Error(`Invalid config file ${path}: model name "auto" is reserved.`);
447
- }
448
- if (seen.has(keyLower)) {
449
- throw new Error(`Invalid config file ${path}: duplicate model name "${key}".`);
450
- }
451
- if (/\s/.test(key)) {
452
- throw new Error(`Invalid config file ${path}: model name "${key}" must not contain spaces.`);
453
- }
454
- if (key.includes("/")) {
455
- throw new Error(`Invalid config file ${path}: model name "${key}" must not include "/".`);
456
- }
457
- const parsedModel = parseModelConfig(value, `models.${key}`);
458
- if (!parsedModel)
459
- continue;
460
- if ("name" in parsedModel) {
461
- throw new Error(`Invalid config file ${path}: "models.${key}" must not reference another model.`);
462
- }
463
- seen.add(keyLower);
464
- out[key] = parsedModel;
465
- }
466
- return Object.keys(out).length > 0 ? out : undefined;
467
- })();
468
- const cache = (() => {
469
- const value = parsed.cache;
470
- if (typeof value === "undefined")
471
- return undefined;
472
- if (!isRecord(value)) {
473
- throw new Error(`Invalid config file ${path}: "cache" must be an object.`);
474
- }
475
- const enabled = typeof value.enabled === "boolean" ? value.enabled : undefined;
476
- const maxMbRaw = value.maxMb;
477
- const maxMb = typeof maxMbRaw === "number" && Number.isFinite(maxMbRaw) && maxMbRaw > 0
478
- ? maxMbRaw
479
- : typeof maxMbRaw === "undefined"
480
- ? undefined
481
- : (() => {
482
- throw new Error(`Invalid config file ${path}: "cache.maxMb" must be a number.`);
483
- })();
484
- const ttlDaysRaw = value.ttlDays;
485
- const ttlDays = typeof ttlDaysRaw === "number" && Number.isFinite(ttlDaysRaw) && ttlDaysRaw > 0
486
- ? ttlDaysRaw
487
- : typeof ttlDaysRaw === "undefined"
488
- ? undefined
489
- : (() => {
490
- throw new Error(`Invalid config file ${path}: "cache.ttlDays" must be a number.`);
491
- })();
492
- const pathValue = typeof value.path === "string" && value.path.trim().length > 0
493
- ? value.path.trim()
494
- : typeof value.path === "undefined"
495
- ? undefined
496
- : (() => {
497
- throw new Error(`Invalid config file ${path}: "cache.path" must be a string.`);
498
- })();
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
549
- ? {
550
- ...(typeof enabled === "boolean" ? { enabled } : {}),
551
- ...(typeof maxMb === "number" ? { maxMb } : {}),
552
- ...(typeof ttlDays === "number" ? { ttlDays } : {}),
553
- ...(typeof pathValue === "string" ? { path: pathValue } : {}),
554
- ...(media ? { media } : {}),
555
- }
556
- : undefined;
557
- })();
558
- const media = (() => {
559
- const value = parsed.media;
560
- if (!isRecord(value))
561
- return undefined;
562
- const videoMode = value.videoMode === "auto" ||
563
- value.videoMode === "transcript" ||
564
- value.videoMode === "understand"
565
- ? value.videoMode
566
- : undefined;
567
- return videoMode ? { videoMode } : undefined;
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
- })();
628
- const cli = (() => {
629
- const value = parsed.cli;
630
- if (!isRecord(value))
631
- return undefined;
632
- if (typeof value.disabled !== "undefined") {
633
- throw new Error(`Invalid config file ${path}: "cli.disabled" is not supported. Use "cli.enabled" instead.`);
634
- }
635
- const enabled = typeof value.enabled !== "undefined"
636
- ? parseCliProviderList(value.enabled, path, "cli.enabled")
637
- : undefined;
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
655
- ? value.promptOverride.trim()
656
- : 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"
660
- ? undefined
661
- : parseStringArray(value.extraArgs, path, "cli.extraArgs");
662
- return enabled ||
663
- claude ||
664
- codex ||
665
- gemini ||
666
- agent ||
667
- autoFallback ||
668
- promptOverride ||
669
- typeof allowTools === "boolean" ||
670
- cwd ||
671
- (extraArgs && extraArgs.length > 0)
672
- ? {
673
- ...(enabled ? { enabled } : {}),
674
- ...(claude ? { claude } : {}),
675
- ...(codex ? { codex } : {}),
676
- ...(gemini ? { gemini } : {}),
677
- ...(agent ? { agent } : {}),
678
- ...(autoFallback ? { autoFallback } : {}),
679
- ...(promptOverride ? { promptOverride } : {}),
680
- ...(typeof allowTools === "boolean" ? { allowTools } : {}),
681
- ...(cwd ? { cwd } : {}),
682
- ...(extraArgs && extraArgs.length > 0 ? { extraArgs } : {}),
683
- }
684
- : undefined;
685
- })();
686
- const output = (() => {
687
- const value = parsed.output;
688
- if (typeof value === "undefined")
689
- return undefined;
690
- if (!isRecord(value)) {
691
- throw new Error(`Invalid config file ${path}: "output" must be an object.`);
692
- }
693
- const language = typeof value.language === "string" && value.language.trim().length > 0
694
- ? value.language.trim()
695
- : 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;
711
- })();
712
- const logging = (() => {
713
- const value = parsed.logging;
714
- if (typeof value === "undefined")
715
- return undefined;
716
- if (!isRecord(value)) {
717
- throw new Error(`Invalid config file ${path}: "logging" must be an object.`);
718
- }
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
723
- ? value.file.trim()
724
- : typeof value.file === "undefined"
725
- ? undefined
726
- : (() => {
727
- throw new Error(`Invalid config file ${path}: "logging.file" must be a string.`);
728
- })();
729
- const maxMbRaw = value.maxMb;
730
- const maxMb = typeof maxMbRaw === "number" && Number.isFinite(maxMbRaw) && maxMbRaw > 0
731
- ? maxMbRaw
732
- : typeof maxMbRaw === "undefined"
733
- ? undefined
734
- : (() => {
735
- throw new Error(`Invalid config file ${path}: "logging.maxMb" must be a number.`);
736
- })();
737
- const maxFilesRaw = value.maxFiles;
738
- const maxFiles = typeof maxFilesRaw === "number" && Number.isFinite(maxFilesRaw) && maxFilesRaw > 0
739
- ? Math.trunc(maxFilesRaw)
740
- : typeof maxFilesRaw === "undefined"
741
- ? undefined
742
- : (() => {
743
- throw new Error(`Invalid config file ${path}: "logging.maxFiles" must be a number.`);
744
- })();
745
- return enabled ||
746
- level ||
747
- format ||
748
- file ||
749
- typeof maxMb === "number" ||
750
- typeof maxFiles === "number"
751
- ? {
752
- ...(typeof enabled === "boolean" ? { enabled } : {}),
753
- ...(level ? { level } : {}),
754
- ...(format ? { format } : {}),
755
- ...(file ? { file } : {}),
756
- ...(typeof maxMb === "number" ? { maxMb } : {}),
757
- ...(typeof maxFiles === "number" ? { maxFiles } : {}),
758
- }
759
- : undefined;
760
- })();
761
- const openai = (() => {
762
- const value = parsed.openai;
763
- if (typeof value === "undefined")
764
- return undefined;
765
- if (!isRecord(value)) {
766
- throw new Error(`Invalid config file ${path}: "openai" must be an object.`);
767
- }
768
- const baseUrl = parseOptionalBaseUrl(value.baseUrl);
769
- const useChatCompletions = typeof value.useChatCompletions === "boolean" ? value.useChatCompletions : undefined;
770
- const whisperUsdPerMinuteRaw = value.whisperUsdPerMinute;
771
- const whisperUsdPerMinute = typeof whisperUsdPerMinuteRaw === "number" &&
772
- Number.isFinite(whisperUsdPerMinuteRaw) &&
773
- whisperUsdPerMinuteRaw > 0
774
- ? whisperUsdPerMinuteRaw
775
- : undefined;
776
- return typeof baseUrl === "string" ||
777
- typeof useChatCompletions === "boolean" ||
778
- typeof whisperUsdPerMinute === "number"
779
- ? {
780
- ...(typeof baseUrl === "string" ? { baseUrl } : {}),
781
- ...(typeof useChatCompletions === "boolean" ? { useChatCompletions } : {}),
782
- ...(typeof whisperUsdPerMinute === "number" ? { whisperUsdPerMinute } : {}),
783
- }
784
- : undefined;
785
- })();
39
+ const models = parseModelsConfig(parsed, path);
40
+ const cache = parseCacheConfig(parsed, path);
41
+ const media = parseMediaConfig(parsed);
42
+ const slides = parseSlidesConfig(parsed, path);
43
+ const cli = parseCliConfig(parsed, path);
44
+ const output = parseOutputConfig(parsed, path);
45
+ const ui = parseUiConfig(parsed, path);
46
+ const logging = parseLoggingConfig(parsed, path);
47
+ const openai = parseOpenAiConfig(parsed, path);
48
+ const nvidia = parseProviderBaseUrlConfig(parsed.nvidia, path, "nvidia");
786
49
  const anthropic = parseProviderBaseUrlConfig(parsed.anthropic, path, "anthropic");
787
50
  const google = parseProviderBaseUrlConfig(parsed.google, path, "google");
788
51
  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
- })();
52
+ const zai = parseProviderBaseUrlConfig(parsed.zai, path, "zai");
53
+ const configEnv = parseEnvConfig(parsed, path);
54
+ const apiKeys = parseApiKeysConfig(parsed, path);
840
55
  return {
841
56
  config: {
842
57
  ...(model ? { model } : {}),
@@ -850,9 +65,11 @@ export function loadSummarizeConfig({ env }) {
850
65
  ...(ui ? { ui } : {}),
851
66
  ...(cli ? { cli } : {}),
852
67
  ...(openai ? { openai } : {}),
68
+ ...(nvidia ? { nvidia } : {}),
853
69
  ...(anthropic ? { anthropic } : {}),
854
70
  ...(google ? { google } : {}),
855
71
  ...(xai ? { xai } : {}),
72
+ ...(zai ? { zai } : {}),
856
73
  ...(logging ? { logging } : {}),
857
74
  ...(configEnv ? { env: configEnv } : {}),
858
75
  ...(apiKeys ? { apiKeys } : {}),