@steipete/summarize 0.11.1 → 0.13.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 (427) hide show
  1. package/CHANGELOG.md +73 -1
  2. package/README.md +102 -32
  3. package/dist/cli.js +1 -1
  4. package/dist/esm/cache-keys.js +83 -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 +15 -92
  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 +55 -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 +472 -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/costs.js.map +1 -1
  27. package/dist/esm/daemon/agent-model.js +283 -0
  28. package/dist/esm/daemon/agent-model.js.map +1 -0
  29. package/dist/esm/daemon/agent-request.js +87 -0
  30. package/dist/esm/daemon/agent-request.js.map +1 -0
  31. package/dist/esm/daemon/agent.js +42 -243
  32. package/dist/esm/daemon/agent.js.map +1 -1
  33. package/dist/esm/daemon/chat.js +118 -9
  34. package/dist/esm/daemon/chat.js.map +1 -1
  35. package/dist/esm/daemon/cli.js +121 -9
  36. package/dist/esm/daemon/cli.js.map +1 -1
  37. package/dist/esm/daemon/config.js +65 -9
  38. package/dist/esm/daemon/config.js.map +1 -1
  39. package/dist/esm/daemon/env-snapshot.js +6 -0
  40. package/dist/esm/daemon/env-snapshot.js.map +1 -1
  41. package/dist/esm/daemon/flow-context.js +84 -74
  42. package/dist/esm/daemon/flow-context.js.map +1 -1
  43. package/dist/esm/daemon/models.js +26 -0
  44. package/dist/esm/daemon/models.js.map +1 -1
  45. package/dist/esm/daemon/process-registry.js.map +1 -1
  46. package/dist/esm/daemon/schtasks.js +101 -5
  47. package/dist/esm/daemon/schtasks.js.map +1 -1
  48. package/dist/esm/daemon/server-admin-routes.js +134 -0
  49. package/dist/esm/daemon/server-admin-routes.js.map +1 -0
  50. package/dist/esm/daemon/server-agent-route.js +104 -0
  51. package/dist/esm/daemon/server-agent-route.js.map +1 -0
  52. package/dist/esm/daemon/server-http.js +89 -0
  53. package/dist/esm/daemon/server-http.js.map +1 -0
  54. package/dist/esm/daemon/server-session-routes.js +209 -0
  55. package/dist/esm/daemon/server-session-routes.js.map +1 -0
  56. package/dist/esm/daemon/server-session.js +118 -0
  57. package/dist/esm/daemon/server-session.js.map +1 -0
  58. package/dist/esm/daemon/server-sse.js +28 -0
  59. package/dist/esm/daemon/server-sse.js.map +1 -0
  60. package/dist/esm/daemon/server-summarize-execution.js +357 -0
  61. package/dist/esm/daemon/server-summarize-execution.js.map +1 -0
  62. package/dist/esm/daemon/server-summarize-request.js +119 -0
  63. package/dist/esm/daemon/server-summarize-request.js.map +1 -0
  64. package/dist/esm/daemon/server.js +79 -1121
  65. package/dist/esm/daemon/server.js.map +1 -1
  66. package/dist/esm/daemon/summarize-progress.js +1 -1
  67. package/dist/esm/daemon/summarize-progress.js.map +1 -1
  68. package/dist/esm/daemon/summarize.js.map +1 -1
  69. package/dist/esm/daemon/windows-container.js +21 -0
  70. package/dist/esm/daemon/windows-container.js.map +1 -0
  71. package/dist/esm/llm/cli-exec.js +75 -0
  72. package/dist/esm/llm/cli-exec.js.map +1 -0
  73. package/dist/esm/llm/cli-provider-output.js +415 -0
  74. package/dist/esm/llm/cli-provider-output.js.map +1 -0
  75. package/dist/esm/llm/cli.js +97 -218
  76. package/dist/esm/llm/cli.js.map +1 -1
  77. package/dist/esm/llm/generate-text-document.js +109 -0
  78. package/dist/esm/llm/generate-text-document.js.map +1 -0
  79. package/dist/esm/llm/generate-text-shared.js +121 -0
  80. package/dist/esm/llm/generate-text-shared.js.map +1 -0
  81. package/dist/esm/llm/generate-text-stream.js +291 -0
  82. package/dist/esm/llm/generate-text-stream.js.map +1 -0
  83. package/dist/esm/llm/generate-text.js +172 -480
  84. package/dist/esm/llm/generate-text.js.map +1 -1
  85. package/dist/esm/llm/github-models.js +45 -0
  86. package/dist/esm/llm/github-models.js.map +1 -0
  87. package/dist/esm/llm/html-to-markdown.js.map +1 -1
  88. package/dist/esm/llm/model-id.js +37 -20
  89. package/dist/esm/llm/model-id.js.map +1 -1
  90. package/dist/esm/llm/provider-capabilities.js +2 -0
  91. package/dist/esm/llm/provider-capabilities.js.map +1 -0
  92. package/dist/esm/llm/provider-profile.js +184 -0
  93. package/dist/esm/llm/provider-profile.js.map +1 -0
  94. package/dist/esm/llm/providers/google.js +42 -5
  95. package/dist/esm/llm/providers/google.js.map +1 -1
  96. package/dist/esm/llm/providers/models.js +19 -1
  97. package/dist/esm/llm/providers/models.js.map +1 -1
  98. package/dist/esm/llm/providers/openai.js +243 -5
  99. package/dist/esm/llm/providers/openai.js.map +1 -1
  100. package/dist/esm/llm/transcript-to-markdown.js.map +1 -1
  101. package/dist/esm/media-cache.js +3 -0
  102. package/dist/esm/media-cache.js.map +1 -1
  103. package/dist/esm/model-auto-cli.js +91 -0
  104. package/dist/esm/model-auto-cli.js.map +1 -0
  105. package/dist/esm/model-auto-rules.js +86 -0
  106. package/dist/esm/model-auto-rules.js.map +1 -0
  107. package/dist/esm/model-auto.js +10 -245
  108. package/dist/esm/model-auto.js.map +1 -1
  109. package/dist/esm/model-spec.js +62 -19
  110. package/dist/esm/model-spec.js.map +1 -1
  111. package/dist/esm/refresh-free.js +1 -1
  112. package/dist/esm/refresh-free.js.map +1 -1
  113. package/dist/esm/run/attachments.js +1 -1
  114. package/dist/esm/run/attachments.js.map +1 -1
  115. package/dist/esm/run/bird/exec.js +23 -0
  116. package/dist/esm/run/bird/exec.js.map +1 -0
  117. package/dist/esm/run/bird/media.js +171 -0
  118. package/dist/esm/run/bird/media.js.map +1 -0
  119. package/dist/esm/run/bird/parse.js +82 -0
  120. package/dist/esm/run/bird/parse.js.map +1 -0
  121. package/dist/esm/run/bird/types.js +2 -0
  122. package/dist/esm/run/bird/types.js.map +1 -0
  123. package/dist/esm/run/bird.js +86 -144
  124. package/dist/esm/run/bird.js.map +1 -1
  125. package/dist/esm/run/cache-state.js.map +1 -1
  126. package/dist/esm/run/cli-fallback-state.js +6 -1
  127. package/dist/esm/run/cli-fallback-state.js.map +1 -1
  128. package/dist/esm/run/constants.js +2 -1
  129. package/dist/esm/run/constants.js.map +1 -1
  130. package/dist/esm/run/env.js +24 -3
  131. package/dist/esm/run/env.js.map +1 -1
  132. package/dist/esm/run/finish-line-labels.js +76 -0
  133. package/dist/esm/run/finish-line-labels.js.map +1 -0
  134. package/dist/esm/run/finish-line-lengths.js +96 -0
  135. package/dist/esm/run/finish-line-lengths.js.map +1 -0
  136. package/dist/esm/run/finish-line.js +3 -169
  137. package/dist/esm/run/finish-line.js.map +1 -1
  138. package/dist/esm/run/flows/asset/extract.js.map +1 -1
  139. package/dist/esm/run/flows/asset/input.js +1 -1
  140. package/dist/esm/run/flows/asset/input.js.map +1 -1
  141. package/dist/esm/run/flows/asset/media.js +19 -10
  142. package/dist/esm/run/flows/asset/media.js.map +1 -1
  143. package/dist/esm/run/flows/asset/output.js.map +1 -1
  144. package/dist/esm/run/flows/asset/preprocess.js.map +1 -1
  145. package/dist/esm/run/flows/asset/summary-attempts.js +117 -0
  146. package/dist/esm/run/flows/asset/summary-attempts.js.map +1 -0
  147. package/dist/esm/run/flows/asset/summary.js +30 -107
  148. package/dist/esm/run/flows/asset/summary.js.map +1 -1
  149. package/dist/esm/run/flows/url/extract.js +7 -4
  150. package/dist/esm/run/flows/url/extract.js.map +1 -1
  151. package/dist/esm/run/flows/url/extraction-session.js +174 -0
  152. package/dist/esm/run/flows/url/extraction-session.js.map +1 -0
  153. package/dist/esm/run/flows/url/fetch-options.js +32 -0
  154. package/dist/esm/run/flows/url/fetch-options.js.map +1 -0
  155. package/dist/esm/run/flows/url/flow-progress.js +123 -0
  156. package/dist/esm/run/flows/url/flow-progress.js.map +1 -0
  157. package/dist/esm/run/flows/url/flow.js +70 -462
  158. package/dist/esm/run/flows/url/flow.js.map +1 -1
  159. package/dist/esm/run/flows/url/markdown.js +38 -3
  160. package/dist/esm/run/flows/url/markdown.js.map +1 -1
  161. package/dist/esm/run/flows/url/progress-status-state.js +28 -0
  162. package/dist/esm/run/flows/url/progress-status-state.js.map +1 -0
  163. package/dist/esm/run/flows/url/progress-status.js +51 -0
  164. package/dist/esm/run/flows/url/progress-status.js.map +1 -0
  165. package/dist/esm/run/flows/url/slides-output-render.js +78 -0
  166. package/dist/esm/run/flows/url/slides-output-render.js.map +1 -0
  167. package/dist/esm/run/flows/url/slides-output-state.js +86 -0
  168. package/dist/esm/run/flows/url/slides-output-state.js.map +1 -0
  169. package/dist/esm/run/flows/url/slides-output-stream.js +271 -0
  170. package/dist/esm/run/flows/url/slides-output-stream.js.map +1 -0
  171. package/dist/esm/run/flows/url/slides-output.js +29 -422
  172. package/dist/esm/run/flows/url/slides-output.js.map +1 -1
  173. package/dist/esm/run/flows/url/slides-session.js +159 -0
  174. package/dist/esm/run/flows/url/slides-session.js.map +1 -0
  175. package/dist/esm/run/flows/url/slides-text-markdown.js +431 -0
  176. package/dist/esm/run/flows/url/slides-text-markdown.js.map +1 -0
  177. package/dist/esm/run/flows/url/slides-text-transcript.js +199 -0
  178. package/dist/esm/run/flows/url/slides-text-transcript.js.map +1 -0
  179. package/dist/esm/run/flows/url/slides-text-types.js +2 -0
  180. package/dist/esm/run/flows/url/slides-text-types.js.map +1 -0
  181. package/dist/esm/run/flows/url/slides-text.js +2 -627
  182. package/dist/esm/run/flows/url/slides-text.js.map +1 -1
  183. package/dist/esm/run/flows/url/summary-finish.js +40 -0
  184. package/dist/esm/run/flows/url/summary-finish.js.map +1 -0
  185. package/dist/esm/run/flows/url/summary-json.js +32 -0
  186. package/dist/esm/run/flows/url/summary-json.js.map +1 -0
  187. package/dist/esm/run/flows/url/summary-prompt.js +147 -0
  188. package/dist/esm/run/flows/url/summary-prompt.js.map +1 -0
  189. package/dist/esm/run/flows/url/summary-resolution.js +327 -0
  190. package/dist/esm/run/flows/url/summary-resolution.js.map +1 -0
  191. package/dist/esm/run/flows/url/summary-timestamps.js +136 -0
  192. package/dist/esm/run/flows/url/summary-timestamps.js.map +1 -0
  193. package/dist/esm/run/flows/url/summary.js +139 -667
  194. package/dist/esm/run/flows/url/summary.js.map +1 -1
  195. package/dist/esm/run/flows/url/types.js +31 -1
  196. package/dist/esm/run/flows/url/types.js.map +1 -1
  197. package/dist/esm/run/flows/url/video-only.js +68 -0
  198. package/dist/esm/run/flows/url/video-only.js.map +1 -0
  199. package/dist/esm/run/help.js +15 -5
  200. package/dist/esm/run/help.js.map +1 -1
  201. package/dist/esm/run/markdown-transforms.js +89 -0
  202. package/dist/esm/run/markdown-transforms.js.map +1 -0
  203. package/dist/esm/run/markdown.js +1 -96
  204. package/dist/esm/run/markdown.js.map +1 -1
  205. package/dist/esm/run/run-config.js +1 -1
  206. package/dist/esm/run/run-config.js.map +1 -1
  207. package/dist/esm/run/run-env.js +28 -7
  208. package/dist/esm/run/run-env.js.map +1 -1
  209. package/dist/esm/run/run-models.js +35 -5
  210. package/dist/esm/run/run-models.js.map +1 -1
  211. package/dist/esm/run/run-settings-parse.js +77 -0
  212. package/dist/esm/run/run-settings-parse.js.map +1 -0
  213. package/dist/esm/run/run-settings.js +7 -72
  214. package/dist/esm/run/run-settings.js.map +1 -1
  215. package/dist/esm/run/runner-contexts.js +122 -0
  216. package/dist/esm/run/runner-contexts.js.map +1 -0
  217. package/dist/esm/run/runner-execution.js +82 -0
  218. package/dist/esm/run/runner-execution.js.map +1 -0
  219. package/dist/esm/run/runner-flags.js +97 -0
  220. package/dist/esm/run/runner-flags.js.map +1 -0
  221. package/dist/esm/run/runner-plan.js +369 -0
  222. package/dist/esm/run/runner-plan.js.map +1 -0
  223. package/dist/esm/run/runner-setup.js +109 -0
  224. package/dist/esm/run/runner-setup.js.map +1 -0
  225. package/dist/esm/run/runner-slides.js +49 -0
  226. package/dist/esm/run/runner-slides.js.map +1 -0
  227. package/dist/esm/run/runner.js +53 -692
  228. package/dist/esm/run/runner.js.map +1 -1
  229. package/dist/esm/run/slides-cli.js +3 -2
  230. package/dist/esm/run/slides-cli.js.map +1 -1
  231. package/dist/esm/run/slides-render.js +5 -2
  232. package/dist/esm/run/slides-render.js.map +1 -1
  233. package/dist/esm/run/stdin-temp-file.js +1 -1
  234. package/dist/esm/run/stdin-temp-file.js.map +1 -1
  235. package/dist/esm/run/streaming.js +2 -0
  236. package/dist/esm/run/streaming.js.map +1 -1
  237. package/dist/esm/run/summary-engine.js +50 -10
  238. package/dist/esm/run/summary-engine.js.map +1 -1
  239. package/dist/esm/run/summary-llm.js +2 -1
  240. package/dist/esm/run/summary-llm.js.map +1 -1
  241. package/dist/esm/run/terminal.js +4 -1
  242. package/dist/esm/run/terminal.js.map +1 -1
  243. package/dist/esm/run/transcriber-cli.js +1 -1
  244. package/dist/esm/run/transcriber-cli.js.map +1 -1
  245. package/dist/esm/shared/slides-text.js +2 -0
  246. package/dist/esm/shared/slides-text.js.map +1 -0
  247. package/dist/esm/slides/download.js +242 -0
  248. package/dist/esm/slides/download.js.map +1 -0
  249. package/dist/esm/slides/extract-finalize.js +98 -0
  250. package/dist/esm/slides/extract-finalize.js.map +1 -0
  251. package/dist/esm/slides/extract.js +105 -1685
  252. package/dist/esm/slides/extract.js.map +1 -1
  253. package/dist/esm/slides/frame-extraction.js +372 -0
  254. package/dist/esm/slides/frame-extraction.js.map +1 -0
  255. package/dist/esm/slides/index.js +2 -1
  256. package/dist/esm/slides/index.js.map +1 -1
  257. package/dist/esm/slides/ingest.js +194 -0
  258. package/dist/esm/slides/ingest.js.map +1 -0
  259. package/dist/esm/slides/ocr.js +91 -0
  260. package/dist/esm/slides/ocr.js.map +1 -0
  261. package/dist/esm/slides/process.js +218 -0
  262. package/dist/esm/slides/process.js.map +1 -0
  263. package/dist/esm/slides/scene-detection.js +387 -0
  264. package/dist/esm/slides/scene-detection.js.map +1 -0
  265. package/dist/esm/slides/source-id.js +42 -0
  266. package/dist/esm/slides/source-id.js.map +1 -0
  267. package/dist/esm/slides/source.js +80 -0
  268. package/dist/esm/slides/source.js.map +1 -0
  269. package/dist/esm/tty/progress/fetch-html.js +6 -0
  270. package/dist/esm/tty/progress/fetch-html.js.map +1 -1
  271. package/dist/esm/tty/progress/transcript-state.js +202 -0
  272. package/dist/esm/tty/progress/transcript-state.js.map +1 -0
  273. package/dist/esm/tty/progress/transcript.js +43 -194
  274. package/dist/esm/tty/progress/transcript.js.map +1 -1
  275. package/dist/esm/tty/spinner.js +17 -3
  276. package/dist/esm/tty/spinner.js.map +1 -1
  277. package/dist/esm/tty/website-progress.js +16 -3
  278. package/dist/esm/tty/website-progress.js.map +1 -1
  279. package/dist/esm/version.js +1 -1
  280. package/dist/types/cache-keys.d.ts +44 -0
  281. package/dist/types/cache-slides-cleanup.d.ts +1 -0
  282. package/dist/types/cache.d.ts +2 -10
  283. package/dist/types/config/env.d.ts +6 -0
  284. package/dist/types/config/model.d.ts +3 -0
  285. package/dist/types/config/parse-helpers.d.ts +7 -0
  286. package/dist/types/config/read.d.ts +2 -0
  287. package/dist/types/config/sections.d.ts +34 -0
  288. package/dist/types/config/types.d.ts +238 -0
  289. package/dist/types/config.d.ts +3 -209
  290. package/dist/types/costs.d.ts +1 -1
  291. package/dist/types/daemon/agent-model.d.ts +40 -0
  292. package/dist/types/daemon/agent-request.d.ts +14 -0
  293. package/dist/types/daemon/chat.d.ts +3 -1
  294. package/dist/types/daemon/config.d.ts +13 -2
  295. package/dist/types/daemon/env-snapshot.d.ts +1 -1
  296. package/dist/types/daemon/flow-context.d.ts +2 -2
  297. package/dist/types/daemon/models.d.ts +3 -0
  298. package/dist/types/daemon/schtasks.d.ts +2 -1
  299. package/dist/types/daemon/server-admin-routes.d.ts +22 -0
  300. package/dist/types/daemon/server-agent-route.d.ts +9 -0
  301. package/dist/types/daemon/server-http.d.ts +10 -0
  302. package/dist/types/daemon/server-session-routes.d.ts +11 -0
  303. package/dist/types/daemon/server-session.d.ts +52 -0
  304. package/dist/types/daemon/server-sse.d.ts +12 -0
  305. package/dist/types/daemon/server-summarize-execution.d.ts +70 -0
  306. package/dist/types/daemon/server-summarize-request.d.ts +36 -0
  307. package/dist/types/daemon/server.d.ts +4 -4
  308. package/dist/types/daemon/summarize.d.ts +1 -1
  309. package/dist/types/daemon/windows-container.d.ts +1 -0
  310. package/dist/types/llm/cli-exec.d.ts +13 -0
  311. package/dist/types/llm/cli-provider-output.d.ts +25 -0
  312. package/dist/types/llm/generate-text-document.d.ts +35 -0
  313. package/dist/types/llm/generate-text-shared.d.ts +32 -0
  314. package/dist/types/llm/generate-text-stream.d.ts +27 -0
  315. package/dist/types/llm/generate-text.d.ts +7 -26
  316. package/dist/types/llm/github-models.d.ts +5 -0
  317. package/dist/types/llm/html-to-markdown.d.ts +2 -1
  318. package/dist/types/llm/model-id.d.ts +1 -1
  319. package/dist/types/llm/provider-capabilities.d.ts +2 -0
  320. package/dist/types/llm/provider-profile.d.ts +31 -0
  321. package/dist/types/llm/providers/google.d.ts +6 -0
  322. package/dist/types/llm/providers/models.d.ts +5 -0
  323. package/dist/types/llm/providers/openai.d.ts +9 -5
  324. package/dist/types/llm/providers/types.d.ts +1 -0
  325. package/dist/types/llm/transcript-to-markdown.d.ts +2 -1
  326. package/dist/types/model-auto-cli.d.ts +15 -0
  327. package/dist/types/model-auto-rules.d.ts +7 -0
  328. package/dist/types/model-auto.d.ts +5 -7
  329. package/dist/types/model-spec.d.ts +4 -3
  330. package/dist/types/run/attachments.d.ts +3 -2
  331. package/dist/types/run/bird/exec.d.ts +1 -0
  332. package/dist/types/run/bird/media.d.ts +3 -0
  333. package/dist/types/run/bird/parse.d.ts +3 -0
  334. package/dist/types/run/bird/types.d.ts +18 -0
  335. package/dist/types/run/bird.d.ts +12 -17
  336. package/dist/types/run/cache-state.d.ts +1 -1
  337. package/dist/types/run/constants.d.ts +2 -1
  338. package/dist/types/run/env.d.ts +6 -0
  339. package/dist/types/run/finish-line-labels.d.ts +29 -0
  340. package/dist/types/run/finish-line-lengths.d.ts +23 -0
  341. package/dist/types/run/finish-line.d.ts +2 -52
  342. package/dist/types/run/flows/asset/extract.d.ts +1 -1
  343. package/dist/types/run/flows/asset/input.d.ts +1 -1
  344. package/dist/types/run/flows/asset/preprocess.d.ts +1 -1
  345. package/dist/types/run/flows/asset/summary-attempts.d.ts +24 -0
  346. package/dist/types/run/flows/asset/summary.d.ts +16 -2
  347. package/dist/types/run/flows/url/extraction-session.d.ts +22 -0
  348. package/dist/types/run/flows/url/fetch-options.d.ts +29 -0
  349. package/dist/types/run/flows/url/flow-progress.d.ts +43 -0
  350. package/dist/types/run/flows/url/markdown.d.ts +2 -2
  351. package/dist/types/run/flows/url/progress-status-state.d.ts +17 -0
  352. package/dist/types/run/flows/url/progress-status.d.ts +17 -0
  353. package/dist/types/run/flows/url/slides-output-render.d.ts +43 -0
  354. package/dist/types/run/flows/url/slides-output-state.d.ts +21 -0
  355. package/dist/types/run/flows/url/slides-output-stream.d.ts +18 -0
  356. package/dist/types/run/flows/url/slides-output.d.ts +2 -17
  357. package/dist/types/run/flows/url/slides-session.d.ts +26 -0
  358. package/dist/types/run/flows/url/slides-text-markdown.d.ts +46 -0
  359. package/dist/types/run/flows/url/slides-text-transcript.d.ts +36 -0
  360. package/dist/types/run/flows/url/slides-text-types.d.ts +8 -0
  361. package/dist/types/run/flows/url/slides-text.d.ts +3 -87
  362. package/dist/types/run/flows/url/summary-finish.d.ts +16 -0
  363. package/dist/types/run/flows/url/summary-json.d.ts +51 -0
  364. package/dist/types/run/flows/url/summary-prompt.d.ts +22 -0
  365. package/dist/types/run/flows/url/summary-resolution.d.ts +31 -0
  366. package/dist/types/run/flows/url/summary-timestamps.d.ts +11 -0
  367. package/dist/types/run/flows/url/types.d.ts +20 -0
  368. package/dist/types/run/flows/url/video-only.d.ts +27 -0
  369. package/dist/types/run/markdown-transforms.d.ts +3 -0
  370. package/dist/types/run/run-context.d.ts +4 -0
  371. package/dist/types/run/run-env.d.ts +4 -0
  372. package/dist/types/run/run-settings-parse.d.ts +5 -0
  373. package/dist/types/run/run-settings.d.ts +2 -1
  374. package/dist/types/run/runner-contexts.d.ts +37 -0
  375. package/dist/types/run/runner-execution.d.ts +58 -0
  376. package/dist/types/run/runner-flags.d.ts +41 -0
  377. package/dist/types/run/runner-plan.d.ts +19 -0
  378. package/dist/types/run/runner-setup.d.ts +21 -0
  379. package/dist/types/run/runner-slides.d.ts +9 -0
  380. package/dist/types/run/streaming.d.ts +2 -1
  381. package/dist/types/run/summary-engine.d.ts +8 -4
  382. package/dist/types/run/summary-llm.d.ts +5 -3
  383. package/dist/types/run/terminal.d.ts +2 -0
  384. package/dist/types/run/types.d.ts +3 -2
  385. package/dist/types/shared/slides-text.d.ts +1 -0
  386. package/dist/types/slides/download.d.ts +29 -0
  387. package/dist/types/slides/extract-finalize.d.ts +57 -0
  388. package/dist/types/slides/extract.d.ts +2 -13
  389. package/dist/types/slides/frame-extraction.d.ts +38 -0
  390. package/dist/types/slides/index.d.ts +2 -1
  391. package/dist/types/slides/ingest.d.ts +47 -0
  392. package/dist/types/slides/ocr.d.ts +5 -0
  393. package/dist/types/slides/process.d.ts +22 -0
  394. package/dist/types/slides/scene-detection.d.ts +75 -0
  395. package/dist/types/slides/source-id.d.ts +2 -0
  396. package/dist/types/slides/source.d.ts +8 -0
  397. package/dist/types/tty/progress/fetch-html.d.ts +1 -0
  398. package/dist/types/tty/progress/transcript-state.d.ts +27 -0
  399. package/dist/types/tty/progress/transcript.d.ts +1 -0
  400. package/dist/types/tty/spinner.d.ts +1 -0
  401. package/dist/types/version.d.ts +1 -1
  402. package/docs/README.md +1 -1
  403. package/docs/_config.yml +1 -0
  404. package/docs/agent.md +3 -2
  405. package/docs/assets/site.css +145 -2
  406. package/docs/cache.md +2 -1
  407. package/docs/chrome-extension.md +19 -5
  408. package/docs/cli.md +26 -8
  409. package/docs/config.md +30 -9
  410. package/docs/extract-only.md +2 -2
  411. package/docs/firecrawl.md +2 -1
  412. package/docs/index.html +5 -0
  413. package/docs/llm.md +34 -5
  414. package/docs/manual-tests.md +3 -0
  415. package/docs/media.md +9 -1
  416. package/docs/model-auto.md +2 -2
  417. package/docs/model-provider-resolution.md +57 -0
  418. package/docs/releasing.md +9 -12
  419. package/docs/site/docs/chrome-extension.html +1 -1
  420. package/docs/site/index.html +5 -0
  421. package/docs/slides-rendering-flow.md +46 -0
  422. package/docs/slides.md +5 -5
  423. package/docs/smoketest.md +1 -1
  424. package/docs/transcript-provider-flow.md +73 -0
  425. package/docs/website.md +3 -1
  426. package/docs/youtube.md +4 -2
  427. package/package.json +17 -16
@@ -1,24 +1,15 @@
1
- import * as urlUtils from "@steipete/summarize-core/content/url";
2
- import { buildExtractCacheKey, buildSlidesCacheKey } from "../../../cache.js";
3
- import { loadRemoteAsset } from "../../../content/asset.js";
4
- import { createLinkPreviewClient, } from "../../../content/index.js";
5
- import { createFirecrawlScraper } from "../../../firecrawl.js";
6
- import { extractSlidesForSource, resolveSlideSource, validateSlidesCache, } from "../../../slides/index.js";
7
- import { createOscProgressController } from "../../../tty/osc-progress.js";
8
- import { startSpinner } from "../../../tty/spinner.js";
9
1
  import { createThemeRenderer, resolveThemeNameFromSources, resolveTrueColor, } from "../../../tty/theme.js";
10
- import { createWebsiteProgress } from "../../../tty/website-progress.js";
11
- import { assertAssetMediaTypeSupported } from "../../attachments.js";
12
- import { readTweetWithBird } from "../../bird.js";
13
2
  import { UVX_TIP } from "../../constants.js";
14
- import { resolveTwitterCookies } from "../../cookies/twitter.js";
15
- import { hasBirdCli, hasUvxCli } from "../../env.js";
3
+ import { hasUvxCli } from "../../env.js";
16
4
  import { estimateWhisperTranscriptionCostUsd, formatOptionalNumber, formatOptionalString, formatUSD, } from "../../format.js";
17
5
  import { writeVerbose } from "../../logging.js";
18
- import { deriveExtractionUi, fetchLinkContentWithBirdTip, logExtractionDiagnostics, } from "./extract.js";
6
+ import { deriveExtractionUi, logExtractionDiagnostics } from "./extract.js";
7
+ import { createUrlExtractionSession } from "./extraction-session.js";
8
+ import { createUrlFlowProgress, writeSlidesBackgroundFailureWarning } from "./flow-progress.js";
19
9
  import { createMarkdownConverters } from "./markdown.js";
20
- import { createSlidesTerminalOutput } from "./slides-output.js";
10
+ import { createUrlSlidesSession } from "./slides-session.js";
21
11
  import { buildUrlPrompt, outputExtractedUrl, summarizeExtractedUrl } from "./summary.js";
12
+ import { handleVideoOnlyExtractedContent } from "./video-only.js";
22
13
  export async function runUrlFlow({ ctx, url, isYoutubeUrl, }) {
23
14
  if (!url) {
24
15
  throw new Error("Only HTTP and HTTPS URLs can be summarized");
@@ -30,6 +21,9 @@ export async function runUrlFlow({ ctx, url, isYoutubeUrl, }) {
30
21
  trueColor: resolveTrueColor(io.envForRun),
31
22
  });
32
23
  const markdown = createMarkdownConverters(ctx, { isYoutubeUrl });
24
+ if (flags.firecrawlMode === "always" && isYoutubeUrl) {
25
+ throw new Error("--firecrawl always is not supported for YouTube URLs; use --youtube auto|web|yt-dlp|apify instead");
26
+ }
33
27
  if (flags.firecrawlMode === "always" && !model.apiStatus.firecrawlConfigured) {
34
28
  throw new Error("--firecrawl always requires FIRECRAWL_API_KEY");
35
29
  }
@@ -39,387 +33,29 @@ export async function runUrlFlow({ ctx, url, isYoutubeUrl, }) {
39
33
  writeVerbose(io.stderr, flags.verbose, `configFile path=${formatOptionalString(flags.configPath)} model=${formatOptionalString(flags.configModelLabel)}`, flags.verboseColor, io.envForRun);
40
34
  writeVerbose(io.stderr, flags.verbose, `env xaiKey=${Boolean(model.apiStatus.xaiApiKey)} openaiKey=${Boolean(model.apiStatus.apiKey)} zaiKey=${Boolean(model.apiStatus.zaiApiKey)} googleKey=${model.apiStatus.googleConfigured} anthropicKey=${model.apiStatus.anthropicConfigured} openrouterKey=${model.apiStatus.openrouterConfigured} apifyToken=${Boolean(model.apiStatus.apifyToken)} firecrawlKey=${model.apiStatus.firecrawlConfigured}`, flags.verboseColor, io.envForRun);
41
35
  writeVerbose(io.stderr, flags.verbose, `markdown htmlRequested=${markdown.markdownRequested} transcriptRequested=${markdown.transcriptMarkdownRequested} provider=${markdown.markdownProvider}`, flags.verboseColor, io.envForRun);
42
- const firecrawlApiKey = model.apiStatus.firecrawlApiKey;
43
- const scrapeWithFirecrawl = model.apiStatus.firecrawlConfigured && flags.firecrawlMode !== "off" && firecrawlApiKey
44
- ? createFirecrawlScraper({
45
- apiKey: firecrawlApiKey,
46
- fetchImpl: io.fetch,
47
- })
48
- : null;
49
- const readTweetWithBirdClient = hasBirdCli(io.env)
50
- ? ({ url, timeoutMs }) => readTweetWithBird({ url, timeoutMs, env: io.env })
51
- : null;
52
36
  writeVerbose(io.stderr, flags.verbose, "extract start", flags.verboseColor, io.envForRun);
53
- const oscProgress = createOscProgressController({
54
- label: "Fetching website",
55
- env: io.env,
56
- isTty: flags.progressEnabled,
57
- write: (data) => io.stderr.write(data),
58
- });
59
- oscProgress.setIndeterminate("Fetching website");
60
- const spinner = startSpinner({
61
- text: `${theme.label("Fetching website")}${theme.dim(" (connecting)…")}`,
62
- enabled: flags.progressEnabled,
63
- stream: io.stderr,
64
- color: theme.palette.spinner,
65
- });
66
- const styleLabel = (text) => theme.label(text);
67
- const styleDim = (text) => theme.dim(text);
68
- const renderStatus = (label, detail = "…") => `${styleLabel(label)}${styleDim(detail)}`;
69
- const renderStatusWithMeta = (label, meta, suffix = "…") => `${styleLabel(label)} ${meta}${styleDim(suffix)}`;
70
- const renderStatusFromText = (text) => {
71
- const match = text.match(/^([^:]+):(.*)$/);
72
- if (!match)
73
- return styleLabel(text);
74
- return `${styleLabel(match[1])}${styleDim(`:${match[2]}`)}`;
75
- };
76
- const handleSignal = () => {
77
- try {
78
- spinner.stopAndClear();
79
- }
80
- catch {
81
- // ignore
82
- }
83
- oscProgress.clear();
84
- };
85
- const handleSigint = () => {
86
- handleSignal();
87
- process.exit(130);
88
- };
89
- const handleSigterm = () => {
90
- handleSignal();
91
- process.exit(143);
92
- };
93
- if (flags.progressEnabled) {
94
- process.once("SIGINT", handleSigint);
95
- process.once("SIGTERM", handleSigterm);
96
- }
97
- if (!hooks.onSlidesProgress && flags.progressEnabled) {
98
- hooks.onSlidesProgress = (text) => {
99
- const match = text.match(/(\d{1,3})%/);
100
- const percent = match ? Number(match[1]) : null;
101
- spinner.setText(renderStatusFromText(text));
102
- if (Number.isFinite(percent) && percent !== null) {
103
- oscProgress.setPercent("Slides", Math.max(0, Math.min(100, percent)));
104
- }
105
- else {
106
- oscProgress.setIndeterminate("Slides");
107
- }
108
- };
109
- }
110
- const websiteProgress = createWebsiteProgress({
111
- enabled: flags.progressEnabled,
112
- spinner,
113
- oscProgress,
114
- theme,
115
- });
116
- const cacheStore = cacheState.mode === "default" ? cacheState.store : null;
117
- const transcriptCache = cacheStore ? cacheStore.transcriptCache : null;
118
- const client = createLinkPreviewClient({
119
- env: io.envForRun,
120
- apifyApiToken: model.apiStatus.apifyToken,
121
- ytDlpPath: model.apiStatus.ytDlpPath,
122
- transcription: {
123
- env: io.envForRun,
124
- falApiKey: model.apiStatus.falApiKey,
125
- groqApiKey: model.apiStatus.groqApiKey,
126
- openaiApiKey: model.apiStatus.openaiTranscriptionKey,
127
- },
128
- scrapeWithFirecrawl,
129
- convertHtmlToMarkdown: markdown.convertHtmlToMarkdown,
130
- readTweetWithBird: readTweetWithBirdClient,
131
- resolveTwitterCookies: async (_args) => {
132
- const res = await resolveTwitterCookies({ env: io.env });
133
- return {
134
- cookiesFromBrowser: res.cookies.cookiesFromBrowser,
135
- source: res.cookies.source,
136
- warnings: res.warnings,
137
- };
37
+ const { handleSigint, handleSigterm, hooks: progressHooks, pauseProgress, progressStatus, renderStatus, renderStatusFromText, renderStatusWithMeta, spinner, stopProgress, styleDim, styleLabel, websiteProgress, } = createUrlFlowProgress({ ctx, theme });
38
+ const flowCtx = progressHooks === hooks ? ctx : { ...ctx, hooks: progressHooks };
39
+ const activeHooks = flowCtx.hooks;
40
+ const extractionSession = createUrlExtractionSession({
41
+ ctx: flowCtx,
42
+ markdown: {
43
+ convertHtmlToMarkdown: markdown.convertHtmlToMarkdown,
44
+ effectiveMarkdownMode: markdown.effectiveMarkdownMode,
45
+ markdownRequested: markdown.markdownRequested,
138
46
  },
139
- fetch: io.fetch,
140
- transcriptCache,
141
- mediaCache: ctx.mediaCache ?? null,
142
- onProgress: websiteProgress || hooks.onLinkPreviewProgress
47
+ onProgress: websiteProgress || activeHooks.onLinkPreviewProgress
143
48
  ? (event) => {
144
49
  websiteProgress?.onProgress(event);
145
- hooks.onLinkPreviewProgress?.(event);
50
+ activeHooks.onLinkPreviewProgress?.(event);
146
51
  }
147
52
  : null,
148
53
  });
149
- let stopped = false;
150
- const stopProgress = () => {
151
- if (stopped)
152
- return;
153
- stopped = true;
154
- websiteProgress?.stop?.();
155
- spinner.stopAndClear();
156
- oscProgress.clear();
157
- };
158
- const pauseProgressLine = () => {
159
- spinner.pause();
160
- return () => spinner.resume();
161
- };
162
- hooks.setClearProgressBeforeStdout(pauseProgressLine);
54
+ const pauseProgressLine = pauseProgress;
55
+ activeHooks.setClearProgressBeforeStdout(pauseProgressLine);
163
56
  try {
164
- const buildFetchOptions = () => ({
165
- timeoutMs: flags.timeoutMs,
166
- maxCharacters: typeof flags.maxExtractCharacters === "number" && flags.maxExtractCharacters > 0
167
- ? flags.maxExtractCharacters
168
- : undefined,
169
- youtubeTranscript: flags.youtubeMode,
170
- mediaTranscript: flags.videoMode === "transcript" ? "prefer" : "auto",
171
- transcriptTimestamps: flags.transcriptTimestamps,
172
- firecrawl: flags.firecrawlMode,
173
- format: markdown.markdownRequested ? "markdown" : "text",
174
- markdownMode: markdown.markdownRequested ? markdown.effectiveMarkdownMode : undefined,
175
- cacheMode: cacheState.mode,
176
- });
177
- const fetchWithCache = async (targetUrl, { bypassExtractCache = false, } = {}) => {
178
- const options = buildFetchOptions();
179
- const cacheKey = cacheStore && cacheState.mode === "default"
180
- ? buildExtractCacheKey({
181
- url: targetUrl,
182
- options: {
183
- youtubeTranscript: options.youtubeTranscript,
184
- mediaTranscript: options.mediaTranscript,
185
- firecrawl: options.firecrawl,
186
- format: options.format,
187
- markdownMode: options.markdownMode ?? null,
188
- transcriptTimestamps: options.transcriptTimestamps ?? false,
189
- ...(typeof options.maxCharacters === "number"
190
- ? { maxCharacters: options.maxCharacters }
191
- : {}),
192
- },
193
- })
194
- : null;
195
- if (!bypassExtractCache && cacheKey && cacheStore) {
196
- const cached = cacheStore.getJson("extract", cacheKey);
197
- if (cached) {
198
- writeVerbose(io.stderr, flags.verbose, "cache hit extract", flags.verboseColor, io.envForRun);
199
- return cached;
200
- }
201
- writeVerbose(io.stderr, flags.verbose, "cache miss extract", flags.verboseColor, io.envForRun);
202
- }
203
- try {
204
- const extracted = await fetchLinkContentWithBirdTip({
205
- client,
206
- url: targetUrl,
207
- options,
208
- env: io.env,
209
- });
210
- if (cacheKey && cacheStore) {
211
- cacheStore.setJson("extract", cacheKey, extracted, cacheState.ttlMs);
212
- writeVerbose(io.stderr, flags.verbose, "cache write extract", flags.verboseColor, io.envForRun);
213
- }
214
- return extracted;
215
- }
216
- catch (err) {
217
- const preferUrlMode = typeof urlUtils.shouldPreferUrlMode === "function"
218
- ? urlUtils.shouldPreferUrlMode(targetUrl)
219
- : false;
220
- const isTwitter = urlUtils.isTwitterStatusUrl?.(targetUrl) ?? false;
221
- if (!preferUrlMode || isTwitter)
222
- throw err;
223
- // Fallback: skip HTML fetch and proceed with URL-only extraction (YouTube/direct media).
224
- writeVerbose(io.stderr, flags.verbose, `extract fallback url-only (${err.message ?? String(err)})`, flags.verboseColor, io.envForRun);
225
- return {
226
- content: "",
227
- title: null,
228
- description: null,
229
- url: targetUrl,
230
- siteName: null,
231
- wordCount: 0,
232
- totalCharacters: 0,
233
- truncated: false,
234
- mediaDurationSeconds: null,
235
- video: null,
236
- isVideoOnly: true,
237
- transcriptSource: null,
238
- transcriptCharacters: null,
239
- transcriptWordCount: null,
240
- transcriptLines: null,
241
- transcriptMetadata: null,
242
- transcriptSegments: null,
243
- transcriptTimedText: null,
244
- transcriptionProvider: null,
245
- diagnostics: {
246
- strategy: "html",
247
- firecrawl: {
248
- attempted: false,
249
- used: false,
250
- cacheMode: cacheState.mode,
251
- cacheStatus: "bypassed",
252
- notes: "skipped (url-only fallback)",
253
- },
254
- markdown: {
255
- requested: false,
256
- used: false,
257
- provider: null,
258
- notes: "skipped (url fallback)",
259
- },
260
- transcript: {
261
- cacheMode: cacheState.mode,
262
- cacheStatus: "unknown",
263
- textProvided: false,
264
- provider: null,
265
- attemptedProviders: [],
266
- },
267
- },
268
- };
269
- }
270
- };
271
- let extracted = await fetchWithCache(url);
272
- if (flags.slides && !resolveSlideSource({ url, extracted })) {
273
- const isTwitter = urlUtils.isTwitterStatusUrl?.(url) ?? false;
274
- if (isTwitter) {
275
- const refreshed = await fetchWithCache(url, { bypassExtractCache: true });
276
- if (resolveSlideSource({ url, extracted: refreshed })) {
277
- writeVerbose(io.stderr, flags.verbose, "extract refresh for slides", flags.verboseColor, io.envForRun);
278
- extracted = refreshed;
279
- }
280
- }
281
- }
57
+ let extracted = await extractionSession.fetchInitialExtract(url);
282
58
  let extractionUi = deriveExtractionUi(extracted);
283
- let slidesExtracted = null;
284
- let slidesDone = false;
285
- let slidesTimelineResolved = false;
286
- let resolveSlidesTimeline = null;
287
- const slidesTimelinePromise = flags.slides
288
- ? new Promise((resolve) => {
289
- resolveSlidesTimeline = resolve;
290
- })
291
- : null;
292
- const resolveTimeline = (value) => {
293
- if (slidesTimelineResolved)
294
- return;
295
- slidesTimelineResolved = true;
296
- resolveSlidesTimeline?.(value);
297
- };
298
- const slidesOutputEnabled = Boolean(flags.slides) && flags.slidesOutput !== false && !flags.json && !flags.extractMode;
299
- const slidesOutput = createSlidesTerminalOutput({
300
- io,
301
- flags: { plain: flags.plain, lengthArg: flags.lengthArg, slidesDebug: flags.slidesDebug },
302
- extracted,
303
- slides: null,
304
- enabled: slidesOutputEnabled,
305
- outputMode: "delta",
306
- clearProgressForStdout: hooks.clearProgressForStdout,
307
- restoreProgressAfterStdout: hooks.restoreProgressAfterStdout ?? null,
308
- onProgressText: flags.progressEnabled
309
- ? (text) => spinner.setText(renderStatusFromText(text))
310
- : null,
311
- });
312
- if (slidesOutput) {
313
- const existingSlidesExtracted = hooks.onSlidesExtracted;
314
- const existingSlidesDone = hooks.onSlidesDone;
315
- const existingSlideChunk = hooks.onSlideChunk;
316
- hooks.onSlidesExtracted = (value) => {
317
- existingSlidesExtracted?.(value);
318
- slidesOutput.onSlidesExtracted(value);
319
- };
320
- hooks.onSlidesDone = (result) => {
321
- existingSlidesDone?.(result);
322
- slidesOutput.onSlidesDone(result);
323
- };
324
- hooks.onSlideChunk = (chunk) => {
325
- existingSlideChunk?.(chunk);
326
- slidesOutput.onSlideChunk(chunk);
327
- };
328
- }
329
- const markSlidesDone = (result) => {
330
- if (slidesDone)
331
- return;
332
- slidesDone = true;
333
- hooks.onSlidesDone?.(result);
334
- };
335
- const runSlidesExtraction = async () => {
336
- if (!flags.slides)
337
- return null;
338
- if (slidesExtracted) {
339
- if (!slidesDone)
340
- markSlidesDone({ ok: true });
341
- return slidesExtracted;
342
- }
343
- let errorMessage = null;
344
- try {
345
- const source = resolveSlideSource({ url, extracted });
346
- if (!source) {
347
- throw new Error("Slides are only supported for YouTube or direct video URLs.");
348
- }
349
- const slidesCacheKey = cacheStore && cacheState.mode === "default"
350
- ? buildSlidesCacheKey({ url: source.url, settings: flags.slides })
351
- : null;
352
- if (slidesCacheKey && cacheStore) {
353
- const cached = cacheStore.getJson("slides", slidesCacheKey);
354
- const validated = cached
355
- ? await validateSlidesCache({ cached, source, settings: flags.slides })
356
- : null;
357
- if (validated) {
358
- writeVerbose(io.stderr, flags.verbose, "cache hit slides", flags.verboseColor, io.envForRun);
359
- slidesExtracted = validated;
360
- resolveTimeline(validated);
361
- ctx.hooks.onSlidesExtracted?.(slidesExtracted);
362
- ctx.hooks.onSlidesProgress?.("Slides: cached 100%");
363
- return slidesExtracted;
364
- }
365
- writeVerbose(io.stderr, flags.verbose, "cache miss slides", flags.verboseColor, io.envForRun);
366
- }
367
- if (flags.progressEnabled) {
368
- spinner.setText(renderStatus("Extracting slides"));
369
- oscProgress.setIndeterminate("Extracting slides");
370
- }
371
- // Prefer indeterminate progress until we get real percentage updates from the slide pipeline.
372
- ctx.hooks.onSlidesProgress?.("Slides: extracting");
373
- const onSlidesLog = (message) => {
374
- writeVerbose(io.stderr, flags.verbose, `slides ${message}`, flags.verboseColor, io.envForRun);
375
- };
376
- slidesExtracted = await extractSlidesForSource({
377
- source,
378
- settings: flags.slides,
379
- noCache: cacheState.mode === "bypass",
380
- mediaCache: ctx.mediaCache,
381
- env: io.env,
382
- timeoutMs: flags.timeoutMs,
383
- ytDlpPath: model.apiStatus.ytDlpPath,
384
- ytDlpCookiesFromBrowser: model.apiStatus.ytDlpCookiesFromBrowser,
385
- ffmpegPath: null,
386
- tesseractPath: null,
387
- hooks: {
388
- onSlideChunk: (chunk) => ctx.hooks.onSlideChunk?.(chunk),
389
- onSlidesTimeline: (timeline) => {
390
- resolveTimeline(timeline);
391
- ctx.hooks.onSlidesExtracted?.(timeline);
392
- },
393
- onSlidesProgress: ctx.hooks.onSlidesProgress ?? undefined,
394
- onSlidesLog,
395
- },
396
- });
397
- if (slidesExtracted) {
398
- ctx.hooks.onSlidesExtracted?.(slidesExtracted);
399
- ctx.hooks.onSlidesProgress?.(`Slides: done (${slidesExtracted.slides.length.toString()} slides) 100%`);
400
- if (slidesCacheKey && cacheStore) {
401
- cacheStore.setJson("slides", slidesCacheKey, slidesExtracted, cacheState.ttlMs);
402
- writeVerbose(io.stderr, flags.verbose, "cache write slides", flags.verboseColor, io.envForRun);
403
- }
404
- }
405
- if (flags.progressEnabled) {
406
- updateSummaryProgress();
407
- }
408
- return slidesExtracted;
409
- }
410
- catch (error) {
411
- errorMessage = error instanceof Error ? error.message : String(error);
412
- throw error;
413
- }
414
- finally {
415
- if (!slidesTimelineResolved) {
416
- resolveTimeline(slidesExtracted ?? null);
417
- }
418
- if (!slidesDone) {
419
- markSlidesDone(errorMessage ? { ok: false, error: errorMessage } : { ok: true });
420
- }
421
- }
422
- };
423
59
  const formatSummaryProgress = (modelId) => {
424
60
  const dim = (value) => theme.dim(value);
425
61
  const accent = (value) => theme.accent(value);
@@ -432,13 +68,20 @@ export async function runUrlFlow({ ctx, url, isYoutubeUrl, }) {
432
68
  if (!flags.progressEnabled)
433
69
  return;
434
70
  websiteProgress?.stop?.();
435
- if (!flags.extractMode) {
436
- oscProgress.setIndeterminate("Summarizing");
437
- }
438
- spinner.setText(flags.extractMode
71
+ progressStatus.setSummary(flags.extractMode
439
72
  ? `${styleLabel("Extracted")}${styleDim(` (${extractionUi.contentSizeLabel}${extractionUi.viaSourceLabel})`)}`
440
- : formatSummaryProgress());
73
+ : formatSummaryProgress(), flags.extractMode ? null : "Summarizing");
441
74
  };
75
+ const slidesSession = createUrlSlidesSession({
76
+ ctx: flowCtx,
77
+ url,
78
+ extracted,
79
+ cacheStore: extractionSession.cacheStore,
80
+ progressStatus,
81
+ renderStatus,
82
+ renderStatusFromText,
83
+ updateSummaryProgress,
84
+ });
442
85
  updateSummaryProgress();
443
86
  logExtractionDiagnostics({
444
87
  extracted,
@@ -459,73 +102,38 @@ export async function runUrlFlow({ ctx, url, isYoutubeUrl, }) {
459
102
  !hasUvxCli(io.env)) {
460
103
  io.stderr.write(`${UVX_TIP}\n`);
461
104
  }
462
- if (!isYoutubeUrl && extracted.isVideoOnly && extracted.video) {
463
- if (extracted.video.kind === "youtube") {
464
- writeVerbose(io.stderr, flags.verbose, `video-only page detected; switching to YouTube URL ${extracted.video.url}`, flags.verboseColor, io.envForRun);
465
- if (flags.progressEnabled) {
466
- spinner.setText(renderStatus("Video-only page", ": fetching YouTube transcript…"));
467
- }
468
- extracted = await fetchWithCache(extracted.video.url);
469
- extractionUi = deriveExtractionUi(extracted);
470
- updateSummaryProgress();
471
- }
472
- else if (extracted.video.kind === "direct") {
473
- const directVideoSlides = await runSlidesExtraction();
474
- const wantsVideoUnderstanding = flags.videoMode === "understand" || flags.videoMode === "auto";
475
- // Direct video URLs require a model that can consume video attachments (currently Gemini).
476
- const canVideoUnderstand = wantsVideoUnderstanding &&
477
- model.apiStatus.googleConfigured &&
478
- (model.requestedModel.kind === "auto" ||
479
- (model.fixedModelSpec?.transport === "native" &&
480
- model.fixedModelSpec.provider === "google"));
481
- if (canVideoUnderstand) {
482
- hooks.onExtracted?.(extracted);
483
- if (flags.progressEnabled)
484
- spinner.setText(renderStatus("Downloading video"));
485
- const loadedVideo = await loadRemoteAsset({
486
- url: extracted.video.url,
487
- fetchImpl: io.fetch,
488
- timeoutMs: flags.timeoutMs,
489
- });
490
- assertAssetMediaTypeSupported({ attachment: loadedVideo.attachment, sizeLabel: null });
491
- let chosenModel = null;
492
- if (flags.progressEnabled)
493
- spinner.setText(renderStatus("Summarizing video"));
494
- await hooks.summarizeAsset({
495
- sourceKind: "asset-url",
496
- sourceLabel: loadedVideo.sourceLabel,
497
- attachment: loadedVideo.attachment,
498
- onModelChosen: (modelId) => {
499
- chosenModel = modelId;
500
- hooks.onModelChosen?.(modelId);
501
- if (flags.progressEnabled) {
502
- const meta = `${styleDim("(")}${styleDim("model: ")}${theme.accent(modelId)}${styleDim(")")}`;
503
- spinner.setText(renderStatusWithMeta("Summarizing video", meta));
504
- }
505
- },
506
- });
507
- const slideCount = directVideoSlides ? directVideoSlides.slides.length : null;
508
- hooks.writeViaFooter([
509
- ...extractionUi.footerParts,
510
- ...(chosenModel ? [`model ${chosenModel}`] : []),
511
- ...(slideCount != null ? [`slides ${slideCount}`] : []),
512
- ]);
513
- return;
514
- }
515
- }
105
+ const videoOnlyResult = await handleVideoOnlyExtractedContent({
106
+ ctx,
107
+ extracted,
108
+ extractionUi,
109
+ isYoutubeUrl,
110
+ fetchWithCache: (targetUrl) => extractionSession.fetchWithCache(targetUrl),
111
+ runSlidesExtraction: slidesSession.runSlidesExtraction,
112
+ renderStatus,
113
+ renderStatusWithMeta,
114
+ spinner,
115
+ styleDim,
116
+ updateSummaryProgress,
117
+ accent: theme.accent,
118
+ });
119
+ if (videoOnlyResult.handled) {
120
+ return;
516
121
  }
517
- // Start slides in parallel; wait for real timing data before prompting.
122
+ extracted = videoOnlyResult.extracted;
123
+ extractionUi = videoOnlyResult.extractionUi;
124
+ slidesSession.setExtracted(extracted);
125
+ updateSummaryProgress();
518
126
  if (flags.slides) {
519
- void runSlidesExtraction().catch((error) => {
127
+ void slidesSession.runSlidesExtraction().catch((error) => {
520
128
  const message = error instanceof Error ? error.message : String(error);
521
- ctx.hooks.onSlidesProgress?.(`Slides: failed (${message})`);
129
+ writeSlidesBackgroundFailureWarning({ ctx, theme, message });
522
130
  writeVerbose(io.stderr, flags.verbose, `slides failed: ${message}`, flags.verboseColor, io.envForRun);
523
131
  });
524
132
  }
525
- hooks.onExtracted?.(extracted);
133
+ activeHooks.onExtracted?.(extracted);
526
134
  let slidesForPrompt = null;
527
- if (slidesTimelinePromise) {
528
- slidesForPrompt = await slidesTimelinePromise;
135
+ if (slidesSession.slidesTimelinePromise) {
136
+ slidesForPrompt = await slidesSession.slidesTimelinePromise;
529
137
  }
530
138
  const prompt = buildUrlPrompt({
531
139
  extracted,
@@ -534,7 +142,7 @@ export async function runUrlFlow({ ctx, url, isYoutubeUrl, }) {
534
142
  promptOverride: flags.promptOverride ?? null,
535
143
  lengthInstruction: flags.lengthInstruction ?? null,
536
144
  languageInstruction: flags.languageInstruction ?? null,
537
- slides: slidesForPrompt ?? slidesExtracted ?? null,
145
+ slides: slidesForPrompt ?? slidesSession.getSlidesExtracted() ?? null,
538
146
  });
539
147
  // Whisper transcription costs need to be folded into the finish line totals.
540
148
  const transcriptionCostUsd = estimateWhisperTranscriptionCostUsd({
@@ -544,7 +152,7 @@ export async function runUrlFlow({ ctx, url, isYoutubeUrl, }) {
544
152
  openaiWhisperUsdPerMinute: model.openaiWhisperUsdPerMinute,
545
153
  });
546
154
  const transcriptionCostLabel = typeof transcriptionCostUsd === "number" ? `txcost=${formatUSD(transcriptionCostUsd)}` : null;
547
- hooks.setTranscriptionCost(transcriptionCostUsd, transcriptionCostLabel);
155
+ activeHooks.setTranscriptionCost(transcriptionCostUsd, transcriptionCostLabel);
548
156
  if (flags.extractMode) {
549
157
  // Apply transcript→markdown conversion if requested
550
158
  let extractedForOutput = extracted;
@@ -583,19 +191,19 @@ export async function runUrlFlow({ ctx, url, isYoutubeUrl, }) {
583
191
  prompt,
584
192
  effectiveMarkdownMode: markdown.effectiveMarkdownMode,
585
193
  transcriptionCostLabel,
586
- slides: slidesExtracted ?? slidesForPrompt ?? null,
587
- slidesOutput,
194
+ slides: slidesSession.getSlidesExtracted() ?? slidesForPrompt ?? null,
195
+ slidesOutput: slidesSession.slidesOutput,
588
196
  });
589
197
  return;
590
198
  }
591
199
  const onModelChosen = (modelId) => {
592
- hooks.onModelChosen?.(modelId);
200
+ activeHooks.onModelChosen?.(modelId);
593
201
  if (!flags.progressEnabled)
594
202
  return;
595
- spinner.setText(formatSummaryProgress(modelId));
203
+ progressStatus.setSummary(formatSummaryProgress(modelId), "Summarizing");
596
204
  };
597
205
  await summarizeExtractedUrl({
598
- ctx,
206
+ ctx: flowCtx,
599
207
  url,
600
208
  extracted,
601
209
  extractionUi,
@@ -603,8 +211,8 @@ export async function runUrlFlow({ ctx, url, isYoutubeUrl, }) {
603
211
  effectiveMarkdownMode: markdown.effectiveMarkdownMode,
604
212
  transcriptionCostLabel,
605
213
  onModelChosen,
606
- slides: slidesExtracted ?? slidesForPrompt ?? null,
607
- slidesOutput,
214
+ slides: slidesSession.getSlidesExtracted() ?? slidesForPrompt ?? null,
215
+ slidesOutput: slidesSession.slidesOutput,
608
216
  });
609
217
  }
610
218
  finally {
@@ -612,7 +220,7 @@ export async function runUrlFlow({ ctx, url, isYoutubeUrl, }) {
612
220
  process.off("SIGINT", handleSigint);
613
221
  process.off("SIGTERM", handleSigterm);
614
222
  }
615
- hooks.clearProgressIfCurrent(pauseProgressLine);
223
+ activeHooks.clearProgressIfCurrent(pauseProgressLine);
616
224
  stopProgress();
617
225
  }
618
226
  }