@steipete/summarize 0.10.0 → 0.11.1

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