@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,234 +1,100 @@
1
- import { isTwitterStatusUrl, isYouTubeUrl } from "@steipete/summarize-core/content/url";
2
- import { countTokens } from "gpt-tokenizer";
3
1
  import { render as renderMarkdownAnsi } from "markdansi";
4
- import { buildLanguageKey, buildLengthKey, buildPromptHash, buildSummaryCacheKey, hashString, normalizeContentForHash, } from "../../../cache.js";
5
- import { formatOutputLanguageForJson } from "../../../language.js";
6
- import { parseGatewayStyleModelId } from "../../../llm/model-id.js";
7
- import { buildAutoModelAttempts } from "../../../model-auto.js";
8
- import { buildLinkSummaryPrompt, SUMMARY_LENGTH_TARGET_CHARACTERS, SUMMARY_SYSTEM_PROMPT, } from "../../../prompts/index.js";
9
- import { readLastSuccessfulCliProvider, writeLastSuccessfulCliProvider, } from "../../cli-fallback-state.js";
10
- import { parseCliUserModelId } from "../../env.js";
11
- import { buildExtractFinishLabel, buildLengthPartsForFinishLine, writeFinishLine, } from "../../finish-line.js";
12
- import { resolveTargetCharacters } from "../../format.js";
2
+ import { buildExtractFinishLabel, writeFinishLine } from "../../finish-line.js";
13
3
  import { writeVerbose } from "../../logging.js";
14
4
  import { prepareMarkdownForTerminal } from "../../markdown.js";
15
- import { runModelAttempts } from "../../model-attempts.js";
16
- import { buildOpenRouterNoAllowedProvidersMessage } from "../../openrouter.js";
17
5
  import { isRichTty, markdownRenderWidth, supportsColor } from "../../terminal.js";
18
- import { coerceSummaryWithSlides, interleaveSlidesIntoTranscript, normalizeSummarySlideHeadings, } from "./slides-text.js";
19
- const MAX_SLIDE_TRANSCRIPT_CHARS_BY_PRESET = {
20
- short: 2500,
21
- medium: 5000,
22
- long: 9000,
23
- xl: 15000,
24
- xxl: 24000,
25
- };
26
- const SLIDE_TRANSCRIPT_DEFAULT_EDGE_SECONDS = 30;
27
- const SLIDE_TRANSCRIPT_LEEWAY_SECONDS = 10;
28
- function parseTimestampSeconds(value) {
29
- const parts = value.split(":").map((item) => Number(item));
30
- if (parts.some((item) => !Number.isFinite(item)))
31
- return null;
32
- if (parts.length === 2) {
33
- const [minutes, seconds] = parts;
34
- return minutes * 60 + seconds;
35
- }
36
- if (parts.length === 3) {
37
- const [hours, minutes, seconds] = parts;
38
- return hours * 3600 + minutes * 60 + seconds;
39
- }
40
- return null;
41
- }
42
- function parseTranscriptTimedText(input) {
43
- if (!input)
44
- return [];
45
- const segments = [];
46
- for (const line of input.split("\n")) {
47
- const trimmed = line.trim();
48
- if (!trimmed.startsWith("["))
49
- continue;
50
- const match = trimmed.match(/^\[(\d{1,2}:\d{2}(?::\d{2})?)\]\s*(.*)$/);
51
- if (!match)
52
- continue;
53
- const seconds = parseTimestampSeconds(match[1]);
54
- if (seconds == null)
55
- continue;
56
- const text = (match[2] ?? "").trim();
57
- if (!text)
58
- continue;
59
- segments.push({ startSeconds: seconds, text });
60
- }
61
- segments.sort((a, b) => a.startSeconds - b.startSeconds);
62
- return segments;
63
- }
64
- function formatTimestamp(seconds) {
65
- const clamped = Math.max(0, Math.floor(seconds));
66
- const hours = Math.floor(clamped / 3600);
67
- const minutes = Math.floor((clamped % 3600) / 60);
68
- const secs = clamped % 60;
69
- const mm = String(minutes).padStart(2, "0");
70
- const ss = String(secs).padStart(2, "0");
71
- if (hours <= 0)
72
- return `${minutes}:${ss}`;
73
- const hh = String(hours).padStart(2, "0");
74
- return `${hh}:${mm}:${ss}`;
75
- }
76
- function truncateTranscript(value, limit) {
77
- if (value.length <= limit)
78
- return value;
79
- const truncated = value.slice(0, limit).trimEnd();
80
- const clean = truncated.replace(/\s+\S*$/, "").trim();
81
- const result = clean.length > 0 ? clean : truncated.trim();
82
- return result.length > 0 ? `${result}…` : "";
6
+ import { coerceSummaryWithSlides, interleaveSlidesIntoTranscript, } from "./slides-text.js";
7
+ import { buildFinishExtras, pickModelForFinishLine, } from "./summary-finish.js";
8
+ import { buildUrlJsonEnv, buildUrlJsonInput } from "./summary-json.js";
9
+ import { buildUrlPrompt as buildSummaryPrompt, } from "./summary-prompt.js";
10
+ import { resolveUrlSummaryExecution } from "./summary-resolution.js";
11
+ import { buildSummaryTimestampLimitInstruction } from "./summary-timestamps.js";
12
+ async function writeUrlJsonOutput({ ctx, url, extracted, effectiveMarkdownMode, prompt, slides, summary, llm, }) {
13
+ const { io, flags, model, hooks } = ctx;
14
+ hooks.clearProgressForStdout();
15
+ const finishReport = flags.shouldComputeReport ? await hooks.buildReport() : null;
16
+ const payload = {
17
+ input: {
18
+ ...buildUrlJsonInput({
19
+ flags,
20
+ url,
21
+ effectiveMarkdownMode,
22
+ modelLabel: model.requestedModelLabel,
23
+ }),
24
+ },
25
+ env: buildUrlJsonEnv(model.apiStatus),
26
+ extracted,
27
+ slides,
28
+ prompt,
29
+ llm,
30
+ metrics: flags.metricsEnabled ? finishReport : null,
31
+ summary,
32
+ };
33
+ io.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
34
+ hooks.restoreProgressAfterStdout?.();
35
+ return finishReport;
83
36
  }
84
- function buildSlidesPromptText({ slides, transcriptTimedText, preset, }) {
85
- if (!slides || slides.slides.length === 0)
86
- return null;
87
- const segments = parseTranscriptTimedText(transcriptTimedText);
88
- const slidesWithTimestamps = slides.slides
89
- .filter((slide) => Number.isFinite(slide.timestamp))
90
- .map((slide) => ({ index: slide.index, timestamp: Math.max(0, Math.floor(slide.timestamp)) }))
91
- .sort((a, b) => a.timestamp - b.timestamp);
92
- if (slidesWithTimestamps.length === 0)
93
- return null;
94
- const totalBudget = Number(MAX_SLIDE_TRANSCRIPT_CHARS_BY_PRESET[preset]);
95
- const perSlideBudget = Math.max(120, Math.floor(totalBudget / Math.max(1, slidesWithTimestamps.length)));
96
- let remaining = totalBudget;
97
- const blocks = [];
98
- for (let i = 0; i < slidesWithTimestamps.length; i += 1) {
99
- const slide = slidesWithTimestamps[i];
100
- if (!slide)
101
- continue;
102
- const prev = slidesWithTimestamps[i - 1];
103
- const next = slidesWithTimestamps[i + 1];
104
- const startBase = prev ? Math.floor((prev.timestamp + slide.timestamp) / 2) : slide.timestamp;
105
- const endBase = next ? Math.ceil((slide.timestamp + next.timestamp) / 2) : slide.timestamp;
106
- const start = Math.max(0, (prev ? startBase : slide.timestamp - SLIDE_TRANSCRIPT_DEFAULT_EDGE_SECONDS) -
107
- SLIDE_TRANSCRIPT_LEEWAY_SECONDS);
108
- const end = (next ? endBase : slide.timestamp + SLIDE_TRANSCRIPT_DEFAULT_EDGE_SECONDS) +
109
- SLIDE_TRANSCRIPT_LEEWAY_SECONDS;
110
- const excerptParts = [];
111
- for (const segment of segments) {
112
- if (segment.startSeconds < start)
113
- continue;
114
- if (segment.startSeconds > end)
115
- break;
116
- excerptParts.push(segment.text);
117
- }
118
- const excerptRaw = excerptParts.join(" ").trim().replace(/\s+/g, " ");
119
- const excerptBudget = remaining > 0 ? Math.min(perSlideBudget, remaining) : 0;
120
- const excerpt = excerptRaw && excerptBudget > 0 ? truncateTranscript(excerptRaw, excerptBudget) : "";
121
- const label = `[slide:${slide.index}] [${formatTimestamp(start)}–${formatTimestamp(end)}]`;
122
- const block = excerpt ? `${label}\n${excerpt}` : label;
123
- blocks.push(block);
124
- remaining = Math.max(0, remaining - block.length);
125
- }
126
- return blocks.length > 0 ? blocks.join("\n\n") : null;
37
+ async function writeUrlMetricsFinishLine({ ctx, extracted, report, transcriptionCostLabel, label, elapsedLabel, model, clearProgress, }) {
38
+ const { io, flags, hooks } = ctx;
39
+ if (!flags.metricsEnabled || !report)
40
+ return;
41
+ if (clearProgress)
42
+ hooks.clearProgressForStdout();
43
+ const costUsd = await hooks.estimateCostUsd();
44
+ writeFinishLine({
45
+ stderr: io.stderr,
46
+ env: io.envForRun,
47
+ elapsedMs: Date.now() - flags.runStartedAtMs,
48
+ elapsedLabel: elapsedLabel ?? null,
49
+ label,
50
+ model,
51
+ report,
52
+ costUsd,
53
+ detailed: flags.metricsDetailed,
54
+ extraParts: buildFinishExtras({
55
+ extracted,
56
+ metricsDetailed: flags.metricsDetailed,
57
+ transcriptionCostLabel,
58
+ }),
59
+ color: flags.verboseColor,
60
+ });
127
61
  }
128
62
  export function buildUrlPrompt({ extracted, outputLanguage, lengthArg, promptOverride, lengthInstruction, languageInstruction, slides, }) {
129
- const isYouTube = extracted.siteName === "YouTube";
130
- const preset = lengthArg.kind === "preset" ? lengthArg.preset : "medium";
131
- const slidesText = buildSlidesPromptText({
132
- slides,
133
- transcriptTimedText: extracted.transcriptTimedText,
134
- preset,
135
- });
136
- return buildLinkSummaryPrompt({
137
- url: extracted.url,
138
- title: extracted.title,
139
- siteName: extracted.siteName,
140
- description: extracted.description,
141
- content: extracted.content,
142
- truncated: extracted.truncated,
143
- hasTranscript: isYouTube ||
144
- (extracted.transcriptSource !== null && extracted.transcriptSource !== "unavailable"),
145
- hasTranscriptTimestamps: Boolean(extracted.transcriptTimedText),
146
- slides: slidesText ? { count: slides?.slides.length ?? 0, text: slidesText } : null,
147
- summaryLength: lengthArg.kind === "preset" ? lengthArg.preset : { maxCharacters: lengthArg.maxCharacters },
63
+ return buildSummaryPrompt({
64
+ extracted,
148
65
  outputLanguage,
149
- shares: [],
150
- promptOverride: promptOverride ?? null,
151
- lengthInstruction: lengthInstruction ?? null,
152
- languageInstruction: languageInstruction ?? null,
66
+ lengthArg,
67
+ promptOverride,
68
+ lengthInstruction,
69
+ languageInstruction,
70
+ slides,
71
+ buildSummaryTimestampLimitInstruction,
153
72
  });
154
73
  }
155
- function shouldBypassShortContentSummary({ extracted, lengthArg, forceSummary, maxOutputTokensArg, json, }) {
156
- if (forceSummary)
157
- return false;
158
- if (!extracted.content || extracted.content.length === 0)
159
- return false;
160
- const targetCharacters = resolveTargetCharacters(lengthArg, SUMMARY_LENGTH_TARGET_CHARACTERS);
161
- if (!Number.isFinite(targetCharacters) || targetCharacters <= 0)
162
- return false;
163
- if (extracted.content.length > targetCharacters)
164
- return false;
165
- if (!json && typeof maxOutputTokensArg === "number") {
166
- const tokenCount = countTokens(extracted.content);
167
- if (tokenCount > maxOutputTokensArg)
168
- return false;
169
- }
170
- return true;
171
- }
172
74
  async function outputSummaryFromExtractedContent({ ctx, url, extracted, extractionUi, prompt, effectiveMarkdownMode, transcriptionCostLabel, slides, footerLabel, verboseMessage, }) {
173
75
  const { io, flags, model, hooks } = ctx;
174
76
  hooks.clearProgressForStdout();
175
77
  const finishModel = pickModelForFinishLine(model.llmCalls, null);
176
78
  if (flags.json) {
177
- const finishReport = flags.shouldComputeReport ? await hooks.buildReport() : null;
178
- const payload = {
179
- input: {
180
- kind: "url",
181
- url,
182
- timeoutMs: flags.timeoutMs,
183
- youtube: flags.youtubeMode,
184
- firecrawl: flags.firecrawlMode,
185
- format: flags.format,
186
- markdown: effectiveMarkdownMode,
187
- timestamps: flags.transcriptTimestamps,
188
- length: flags.lengthArg.kind === "preset"
189
- ? { kind: "preset", preset: flags.lengthArg.preset }
190
- : { kind: "chars", maxCharacters: flags.lengthArg.maxCharacters },
191
- maxOutputTokens: flags.maxOutputTokensArg,
192
- model: model.requestedModelLabel,
193
- language: formatOutputLanguageForJson(flags.outputLanguage),
194
- },
195
- env: {
196
- hasXaiKey: Boolean(model.apiStatus.xaiApiKey),
197
- hasOpenAIKey: Boolean(model.apiStatus.apiKey),
198
- hasOpenRouterKey: Boolean(model.apiStatus.openrouterApiKey),
199
- hasApifyToken: Boolean(model.apiStatus.apifyToken),
200
- hasFirecrawlKey: model.apiStatus.firecrawlConfigured,
201
- hasGoogleKey: model.apiStatus.googleConfigured,
202
- hasAnthropicKey: model.apiStatus.anthropicConfigured,
203
- },
79
+ const finishReport = await writeUrlJsonOutput({
80
+ ctx,
81
+ url,
204
82
  extracted,
205
- slides,
83
+ effectiveMarkdownMode,
206
84
  prompt,
207
- llm: null,
208
- metrics: flags.metricsEnabled ? finishReport : null,
85
+ slides,
209
86
  summary: extracted.content,
210
- };
211
- io.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
212
- if (flags.metricsEnabled && finishReport) {
213
- const costUsd = await hooks.estimateCostUsd();
214
- hooks.clearProgressForStdout();
215
- writeFinishLine({
216
- stderr: io.stderr,
217
- env: io.envForRun,
218
- elapsedMs: Date.now() - flags.runStartedAtMs,
219
- label: extractionUi.finishSourceLabel,
220
- model: finishModel,
221
- report: finishReport,
222
- costUsd,
223
- detailed: flags.metricsDetailed,
224
- extraParts: buildFinishExtras({
225
- extracted,
226
- metricsDetailed: flags.metricsDetailed,
227
- transcriptionCostLabel,
228
- }),
229
- color: flags.verboseColor,
230
- });
231
- }
87
+ llm: null,
88
+ });
89
+ await writeUrlMetricsFinishLine({
90
+ ctx,
91
+ extracted,
92
+ report: finishReport,
93
+ transcriptionCostLabel,
94
+ label: extractionUi.finishSourceLabel,
95
+ model: finishModel,
96
+ clearProgress: true,
97
+ });
232
98
  return;
233
99
  }
234
100
  io.stdout.write(`${extracted.content}\n`);
@@ -243,37 +109,6 @@ async function outputSummaryFromExtractedContent({ ctx, url, extracted, extracti
243
109
  writeVerbose(io.stderr, flags.verbose, verboseMessage, flags.verboseColor, io.envForRun);
244
110
  }
245
111
  }
246
- const buildFinishExtras = ({ extracted, metricsDetailed, transcriptionCostLabel, }) => {
247
- const parts = [
248
- ...(buildLengthPartsForFinishLine(extracted, metricsDetailed) ?? []),
249
- ...(transcriptionCostLabel ? [transcriptionCostLabel] : []),
250
- ];
251
- return parts.length > 0 ? parts : null;
252
- };
253
- const pickModelForFinishLine = (llmCalls, fallback) => {
254
- const findLastModel = (purpose) => {
255
- for (let i = llmCalls.length - 1; i >= 0; i -= 1) {
256
- const call = llmCalls[i];
257
- if (call && call.purpose === purpose)
258
- return call.model;
259
- }
260
- return null;
261
- };
262
- return (findLastModel("summary") ??
263
- findLastModel("markdown") ??
264
- (llmCalls.length > 0 ? (llmCalls[llmCalls.length - 1]?.model ?? null) : null) ??
265
- fallback);
266
- };
267
- const buildModelMetaFromAttempt = (attempt) => {
268
- if (attempt.transport === "cli") {
269
- return { provider: "cli", canonical: attempt.userModelId };
270
- }
271
- const parsed = parseGatewayStyleModelId(attempt.llmModelId ?? attempt.userModelId);
272
- const canonical = attempt.userModelId.toLowerCase().startsWith("openrouter/")
273
- ? attempt.userModelId
274
- : parsed.canonical;
275
- return { provider: parsed.provider, canonical };
276
- };
277
112
  export async function outputExtractedUrl({ ctx, url, extracted, extractionUi, prompt, effectiveMarkdownMode, transcriptionCostLabel, slides, slidesOutput, }) {
278
113
  const { io, flags, model, hooks } = ctx;
279
114
  hooks.clearProgressForStdout();
@@ -285,62 +120,24 @@ export async function outputExtractedUrl({ ctx, url, extracted, extractionUi, pr
285
120
  });
286
121
  const finishModel = pickModelForFinishLine(model.llmCalls, null);
287
122
  if (flags.json) {
288
- const finishReport = flags.shouldComputeReport ? await hooks.buildReport() : null;
289
- const payload = {
290
- input: {
291
- kind: "url",
292
- url,
293
- timeoutMs: flags.timeoutMs,
294
- youtube: flags.youtubeMode,
295
- firecrawl: flags.firecrawlMode,
296
- format: flags.format,
297
- markdown: effectiveMarkdownMode,
298
- timestamps: flags.transcriptTimestamps,
299
- length: flags.lengthArg.kind === "preset"
300
- ? { kind: "preset", preset: flags.lengthArg.preset }
301
- : { kind: "chars", maxCharacters: flags.lengthArg.maxCharacters },
302
- maxOutputTokens: flags.maxOutputTokensArg,
303
- model: model.requestedModelLabel,
304
- language: formatOutputLanguageForJson(flags.outputLanguage),
305
- },
306
- env: {
307
- hasXaiKey: Boolean(model.apiStatus.xaiApiKey),
308
- hasOpenAIKey: Boolean(model.apiStatus.apiKey),
309
- hasOpenRouterKey: Boolean(model.apiStatus.openrouterApiKey),
310
- hasApifyToken: Boolean(model.apiStatus.apifyToken),
311
- hasFirecrawlKey: model.apiStatus.firecrawlConfigured,
312
- hasGoogleKey: model.apiStatus.googleConfigured,
313
- hasAnthropicKey: model.apiStatus.anthropicConfigured,
314
- },
123
+ const finishReport = await writeUrlJsonOutput({
124
+ ctx,
125
+ url,
315
126
  extracted,
316
- slides,
127
+ effectiveMarkdownMode,
317
128
  prompt,
318
- llm: null,
319
- metrics: flags.metricsEnabled ? finishReport : null,
129
+ slides,
320
130
  summary: null,
321
- };
322
- io.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
323
- hooks.restoreProgressAfterStdout?.();
324
- hooks.restoreProgressAfterStdout?.();
325
- if (flags.metricsEnabled && finishReport) {
326
- const costUsd = await hooks.estimateCostUsd();
327
- writeFinishLine({
328
- stderr: io.stderr,
329
- env: io.envForRun,
330
- elapsedMs: Date.now() - flags.runStartedAtMs,
331
- label: finishLabel,
332
- model: finishModel,
333
- report: finishReport,
334
- costUsd,
335
- detailed: flags.metricsDetailed,
336
- extraParts: buildFinishExtras({
337
- extracted,
338
- metricsDetailed: flags.metricsDetailed,
339
- transcriptionCostLabel,
340
- }),
341
- color: flags.verboseColor,
342
- });
343
- }
131
+ llm: null,
132
+ });
133
+ await writeUrlMetricsFinishLine({
134
+ ctx,
135
+ extracted,
136
+ report: finishReport,
137
+ transcriptionCostLabel,
138
+ label: finishLabel,
139
+ model: finishModel,
140
+ });
344
141
  return;
345
142
  }
346
143
  const extractCandidate = flags.transcriptTimestamps &&
@@ -412,133 +209,28 @@ export async function outputExtractedUrl({ ctx, url, extracted, extractionUi, pr
412
209
  const slideFooter = slides ? [`slides ${slides.slides.length}`] : [];
413
210
  hooks.writeViaFooter([...extractionUi.footerParts, ...slideFooter]);
414
211
  const report = flags.shouldComputeReport ? await hooks.buildReport() : null;
415
- if (flags.metricsEnabled && report) {
416
- const costUsd = await hooks.estimateCostUsd();
417
- hooks.clearProgressForStdout();
418
- writeFinishLine({
419
- stderr: io.stderr,
420
- env: io.envForRun,
421
- elapsedMs: Date.now() - flags.runStartedAtMs,
422
- label: finishLabel,
423
- model: finishModel,
424
- report,
425
- costUsd,
426
- detailed: flags.metricsDetailed,
427
- extraParts: buildFinishExtras({
428
- extracted,
429
- metricsDetailed: flags.metricsDetailed,
430
- transcriptionCostLabel,
431
- }),
432
- color: flags.verboseColor,
433
- });
434
- }
212
+ await writeUrlMetricsFinishLine({
213
+ ctx,
214
+ extracted,
215
+ report,
216
+ transcriptionCostLabel,
217
+ label: finishLabel,
218
+ model: finishModel,
219
+ clearProgress: true,
220
+ });
435
221
  }
436
222
  export async function summarizeExtractedUrl({ ctx, url, extracted, extractionUi, prompt, effectiveMarkdownMode, transcriptionCostLabel, onModelChosen, slides, slidesOutput, }) {
437
223
  const { io, flags, model, cache: cacheState, hooks } = ctx;
438
- const lastSuccessfulCliProvider = model.isFallbackModel
439
- ? await readLastSuccessfulCliProvider(io.envForRun)
440
- : null;
441
- const promptPayload = { system: SUMMARY_SYSTEM_PROMPT, userText: prompt };
442
- const promptTokens = countTokens(promptPayload.userText);
443
- const kindForAuto = extracted.siteName === "YouTube" ? "youtube" : "website";
444
- const attempts = await (async () => {
445
- if (model.isFallbackModel) {
446
- const catalog = await model.getLiteLlmCatalog();
447
- const list = buildAutoModelAttempts({
448
- kind: kindForAuto,
449
- promptTokens,
450
- desiredOutputTokens: model.desiredOutputTokens,
451
- requiresVideoUnderstanding: false,
452
- env: model.envForAuto,
453
- config: model.configForModelSelection,
454
- catalog,
455
- openrouterProvidersFromEnv: null,
456
- cliAvailability: model.cliAvailability,
457
- isImplicitAutoSelection: model.isImplicitAutoSelection,
458
- allowAutoCliFallback: model.allowAutoCliFallback,
459
- lastSuccessfulCliProvider,
460
- });
461
- if (flags.verbose) {
462
- for (const attempt of list.slice(0, 8)) {
463
- writeVerbose(io.stderr, flags.verbose, `auto candidate ${attempt.debug}`, flags.verboseColor, io.envForRun);
464
- }
465
- }
466
- return list.map((attempt) => {
467
- if (attempt.transport !== "cli")
468
- return model.summaryEngine.applyZaiOverrides(attempt);
469
- const parsed = parseCliUserModelId(attempt.userModelId);
470
- return { ...attempt, cliProvider: parsed.provider, cliModel: parsed.model };
471
- });
472
- }
473
- /* v8 ignore next */
474
- if (!model.fixedModelSpec) {
475
- throw new Error("Internal error: missing fixed model spec");
476
- }
477
- if (model.fixedModelSpec.transport === "cli") {
478
- return [
479
- {
480
- transport: "cli",
481
- userModelId: model.fixedModelSpec.userModelId,
482
- llmModelId: null,
483
- cliProvider: model.fixedModelSpec.cliProvider,
484
- cliModel: model.fixedModelSpec.cliModel,
485
- openrouterProviders: null,
486
- forceOpenRouter: false,
487
- requiredEnv: model.fixedModelSpec.requiredEnv,
488
- },
489
- ];
490
- }
491
- const openaiOverrides = model.fixedModelSpec.requiredEnv === "Z_AI_API_KEY"
492
- ? {
493
- openaiApiKeyOverride: model.apiStatus.zaiApiKey,
494
- openaiBaseUrlOverride: model.apiStatus.zaiBaseUrl,
495
- forceChatCompletions: true,
496
- }
497
- : {};
498
- return [
499
- {
500
- transport: model.fixedModelSpec.transport === "openrouter" ? "openrouter" : "native",
501
- userModelId: model.fixedModelSpec.userModelId,
502
- llmModelId: model.fixedModelSpec.llmModelId,
503
- openrouterProviders: model.fixedModelSpec.openrouterProviders,
504
- forceOpenRouter: model.fixedModelSpec.forceOpenRouter,
505
- requiredEnv: model.fixedModelSpec.requiredEnv,
506
- ...openaiOverrides,
507
- },
508
- ];
509
- })();
510
- const cacheStore = cacheState.mode === "default" && !flags.summaryCacheBypass ? cacheState.store : null;
511
- const contentHash = cacheStore ? hashString(normalizeContentForHash(extracted.content)) : null;
512
- const promptHash = cacheStore ? buildPromptHash(prompt) : null;
513
- const lengthKey = buildLengthKey(flags.lengthArg);
514
- const languageKey = buildLanguageKey(flags.outputLanguage);
515
- const autoSelectionCacheModel = model.isFallbackModel
516
- ? `selection:${model.requestedModelInput.toLowerCase()}`
517
- : null;
518
- let summaryResult = null;
519
- let usedAttempt = null;
520
- let summaryFromCache = false;
521
- let cacheChecked = false;
522
- const isTweet = extracted.siteName?.toLowerCase() === "x" || isTwitterStatusUrl(extracted.url);
523
- const isYouTube = extracted.siteName === "YouTube" || isYouTubeUrl(url);
524
- const hasMedia = Boolean(extracted.video) ||
525
- (extracted.transcriptSource != null && extracted.transcriptSource !== "unavailable") ||
526
- (typeof extracted.mediaDurationSeconds === "number" && extracted.mediaDurationSeconds > 0) ||
527
- extracted.isVideoOnly === true;
528
- const autoBypass = ctx.model.isFallbackModel && !ctx.model.isNamedModelSelection;
529
- const canBypassShortContent = (autoBypass || isTweet) &&
530
- !flags.slides &&
531
- !hasMedia &&
532
- flags.streamMode !== "on" &&
533
- !isYouTube &&
534
- shouldBypassShortContentSummary({
535
- extracted,
536
- lengthArg: flags.lengthArg,
537
- forceSummary: flags.forceSummary,
538
- maxOutputTokensArg: flags.maxOutputTokensArg,
539
- json: flags.json,
540
- });
541
- if (canBypassShortContent) {
224
+ const resolution = await resolveUrlSummaryExecution({
225
+ ctx,
226
+ url,
227
+ extracted,
228
+ prompt,
229
+ onModelChosen,
230
+ slides,
231
+ slidesOutput,
232
+ });
233
+ if (resolution.kind === "use-extracted") {
542
234
  await outputSummaryFromExtractedContent({
543
235
  ctx,
544
236
  url,
@@ -548,246 +240,37 @@ export async function summarizeExtractedUrl({ ctx, url, extracted, extractionUi,
548
240
  effectiveMarkdownMode,
549
241
  transcriptionCostLabel,
550
242
  slides,
551
- footerLabel: "short content",
552
- verboseMessage: "short content: skipping summary",
243
+ footerLabel: resolution.footerLabel,
244
+ verboseMessage: resolution.verboseMessage,
553
245
  });
554
246
  return;
555
247
  }
556
- if (cacheStore && contentHash && promptHash) {
557
- cacheChecked = true;
558
- if (autoSelectionCacheModel) {
559
- const key = buildSummaryCacheKey({
560
- contentHash,
561
- promptHash,
562
- model: autoSelectionCacheModel,
563
- lengthKey,
564
- languageKey,
565
- });
566
- const cached = cacheStore.getJson("summary", key);
567
- const cachedSummary = cached && typeof cached.summary === "string" ? cached.summary.trim() : null;
568
- const cachedModelId = cached && typeof cached.model === "string" ? cached.model.trim() : null;
569
- if (cachedSummary) {
570
- const cachedAttempt = cachedModelId
571
- ? (attempts.find((attempt) => attempt.userModelId === cachedModelId) ?? null)
572
- : null;
573
- const fallbackAttempt = attempts.find((attempt) => model.summaryEngine.envHasKeyFor(attempt.requiredEnv)) ??
574
- attempts[0] ??
575
- null;
576
- const matchedAttempt = cachedAttempt && model.summaryEngine.envHasKeyFor(cachedAttempt.requiredEnv)
577
- ? cachedAttempt
578
- : fallbackAttempt;
579
- if (matchedAttempt) {
580
- writeVerbose(io.stderr, flags.verbose, "cache hit summary (auto selection)", flags.verboseColor, io.envForRun);
581
- onModelChosen?.(cachedModelId || matchedAttempt.userModelId);
582
- summaryResult = {
583
- summary: cachedSummary,
584
- summaryAlreadyPrinted: false,
585
- modelMeta: buildModelMetaFromAttempt(matchedAttempt),
586
- maxOutputTokensForCall: null,
587
- };
588
- usedAttempt = matchedAttempt;
589
- summaryFromCache = true;
590
- }
591
- }
592
- }
593
- if (!summaryFromCache) {
594
- for (const attempt of attempts) {
595
- if (!model.summaryEngine.envHasKeyFor(attempt.requiredEnv))
596
- continue;
597
- const key = buildSummaryCacheKey({
598
- contentHash,
599
- promptHash,
600
- model: attempt.userModelId,
601
- lengthKey,
602
- languageKey,
603
- });
604
- const cached = cacheStore.getText("summary", key);
605
- if (!cached)
606
- continue;
607
- writeVerbose(io.stderr, flags.verbose, "cache hit summary", flags.verboseColor, io.envForRun);
608
- onModelChosen?.(attempt.userModelId);
609
- summaryResult = {
610
- summary: cached,
611
- summaryAlreadyPrinted: false,
612
- modelMeta: buildModelMetaFromAttempt(attempt),
613
- maxOutputTokensForCall: null,
614
- };
615
- usedAttempt = attempt;
616
- summaryFromCache = true;
617
- break;
618
- }
619
- }
620
- }
621
- if (cacheChecked && !summaryFromCache) {
622
- writeVerbose(io.stderr, flags.verbose, "cache miss summary", flags.verboseColor, io.envForRun);
623
- }
624
- ctx.hooks.onSummaryCached?.(summaryFromCache);
625
- let lastError = null;
626
- let missingRequiredEnvs = new Set();
627
- let sawOpenRouterNoAllowedProviders = false;
628
- if (!summaryResult || !usedAttempt) {
629
- const attemptOutcome = await runModelAttempts({
630
- attempts,
631
- isFallbackModel: model.isFallbackModel,
632
- isNamedModelSelection: model.isNamedModelSelection,
633
- envHasKeyFor: model.summaryEngine.envHasKeyFor,
634
- formatMissingModelError: model.summaryEngine.formatMissingModelError,
635
- onAutoSkip: (attempt) => {
636
- writeVerbose(io.stderr, flags.verbose, `auto skip ${attempt.userModelId}: missing ${attempt.requiredEnv}`, flags.verboseColor, io.envForRun);
637
- },
638
- onAutoFailure: (attempt, error) => {
639
- writeVerbose(io.stderr, flags.verbose, `auto failed ${attempt.userModelId}: ${error instanceof Error ? error.message : String(error)}`, flags.verboseColor, io.envForRun);
640
- },
641
- onFixedModelError: (_attempt, error) => {
642
- throw error;
643
- },
644
- runAttempt: (attempt) => model.summaryEngine.runSummaryAttempt({
645
- attempt,
646
- prompt: promptPayload,
647
- allowStreaming: flags.streamingEnabled,
648
- onModelChosen: onModelChosen ?? null,
649
- streamHandler: slidesOutput?.streamHandler ?? null,
650
- }),
651
- });
652
- summaryResult = attemptOutcome.result;
653
- usedAttempt = attemptOutcome.usedAttempt;
654
- lastError = attemptOutcome.lastError;
655
- missingRequiredEnvs = attemptOutcome.missingRequiredEnvs;
656
- sawOpenRouterNoAllowedProviders = attemptOutcome.sawOpenRouterNoAllowedProviders;
657
- }
658
- if (!summaryResult || !usedAttempt) {
659
- // Auto mode: surface raw extracted content when no model can run.
660
- const withFreeTip = (message) => {
661
- if (!model.isNamedModelSelection || !model.wantsFreeNamedModel)
662
- return message;
663
- return (`${message}\n` +
664
- `Tip: run "summarize refresh-free" to refresh the free model candidates (writes ~/.summarize/config.json).`);
665
- };
666
- if (model.isNamedModelSelection) {
667
- if (lastError === null && missingRequiredEnvs.size > 0) {
668
- throw new Error(withFreeTip(`Missing ${Array.from(missingRequiredEnvs).sort().join(", ")} for --model ${model.requestedModelInput}.`));
669
- }
670
- if (lastError instanceof Error) {
671
- if (sawOpenRouterNoAllowedProviders) {
672
- const message = await buildOpenRouterNoAllowedProvidersMessage({
673
- attempts,
674
- fetchImpl: io.fetch,
675
- timeoutMs: flags.timeoutMs,
676
- });
677
- throw new Error(withFreeTip(message), { cause: lastError });
678
- }
679
- throw new Error(withFreeTip(lastError.message), { cause: lastError });
680
- }
681
- throw new Error(withFreeTip(`No model available for --model ${model.requestedModelInput}`));
682
- }
683
- await outputSummaryFromExtractedContent({
248
+ const { normalizedSummary, summaryAlreadyPrinted, summaryFromCache, usedAttempt, modelMeta, maxOutputTokensForCall, } = resolution;
249
+ if (flags.json) {
250
+ const finishReport = await writeUrlJsonOutput({
684
251
  ctx,
685
252
  url,
686
253
  extracted,
687
- extractionUi,
688
- prompt,
689
254
  effectiveMarkdownMode,
690
- transcriptionCostLabel,
691
- slides,
692
- footerLabel: "no model",
693
- verboseMessage: lastError instanceof Error ? `auto failed all models: ${lastError.message}` : null,
694
- });
695
- return;
696
- }
697
- if (!summaryFromCache && cacheStore && contentHash && promptHash) {
698
- const perModelKey = buildSummaryCacheKey({
699
- contentHash,
700
- promptHash,
701
- model: usedAttempt.userModelId,
702
- lengthKey,
703
- languageKey,
704
- });
705
- cacheStore.setText("summary", perModelKey, summaryResult.summary, cacheState.ttlMs);
706
- writeVerbose(io.stderr, flags.verbose, "cache write summary", flags.verboseColor, io.envForRun);
707
- if (autoSelectionCacheModel) {
708
- const selectionKey = buildSummaryCacheKey({
709
- contentHash,
710
- promptHash,
711
- model: autoSelectionCacheModel,
712
- lengthKey,
713
- languageKey,
714
- });
715
- cacheStore.setJson("summary", selectionKey, { summary: summaryResult.summary, model: usedAttempt.userModelId }, cacheState.ttlMs);
716
- writeVerbose(io.stderr, flags.verbose, "cache write summary (auto selection)", flags.verboseColor, io.envForRun);
717
- }
718
- }
719
- if (!summaryFromCache &&
720
- model.isFallbackModel &&
721
- usedAttempt.transport === "cli" &&
722
- usedAttempt.cliProvider) {
723
- await writeLastSuccessfulCliProvider({
724
- env: io.envForRun,
725
- provider: usedAttempt.cliProvider,
726
- });
727
- }
728
- const { summary, summaryAlreadyPrinted, modelMeta, maxOutputTokensForCall } = summaryResult;
729
- const normalizedSummary = slides && slides.slides.length > 0 ? normalizeSummarySlideHeadings(summary) : summary;
730
- if (flags.json) {
731
- const finishReport = flags.shouldComputeReport ? await hooks.buildReport() : null;
732
- const payload = {
733
- input: {
734
- kind: "url",
735
- url,
736
- timeoutMs: flags.timeoutMs,
737
- youtube: flags.youtubeMode,
738
- firecrawl: flags.firecrawlMode,
739
- format: flags.format,
740
- markdown: effectiveMarkdownMode,
741
- timestamps: flags.transcriptTimestamps,
742
- length: flags.lengthArg.kind === "preset"
743
- ? { kind: "preset", preset: flags.lengthArg.preset }
744
- : { kind: "chars", maxCharacters: flags.lengthArg.maxCharacters },
745
- maxOutputTokens: flags.maxOutputTokensArg,
746
- model: model.requestedModelLabel,
747
- language: formatOutputLanguageForJson(flags.outputLanguage),
748
- },
749
- env: {
750
- hasXaiKey: Boolean(model.apiStatus.xaiApiKey),
751
- hasOpenAIKey: Boolean(model.apiStatus.apiKey),
752
- hasOpenRouterKey: Boolean(model.apiStatus.openrouterApiKey),
753
- hasApifyToken: Boolean(model.apiStatus.apifyToken),
754
- hasFirecrawlKey: model.apiStatus.firecrawlConfigured,
755
- hasGoogleKey: model.apiStatus.googleConfigured,
756
- hasAnthropicKey: model.apiStatus.anthropicConfigured,
757
- },
758
- extracted,
759
- slides,
760
255
  prompt,
256
+ slides,
257
+ summary: normalizedSummary,
761
258
  llm: {
762
259
  provider: modelMeta.provider,
763
260
  model: usedAttempt.userModelId,
764
261
  maxCompletionTokens: maxOutputTokensForCall,
765
262
  strategy: "single",
766
263
  },
767
- metrics: flags.metricsEnabled ? finishReport : null,
768
- summary: normalizedSummary,
769
- };
770
- io.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
771
- if (flags.metricsEnabled && finishReport) {
772
- const costUsd = await hooks.estimateCostUsd();
773
- writeFinishLine({
774
- stderr: io.stderr,
775
- env: io.envForRun,
776
- elapsedMs: Date.now() - flags.runStartedAtMs,
777
- elapsedLabel: summaryFromCache ? "Cached" : null,
778
- label: extractionUi.finishSourceLabel,
779
- model: usedAttempt.userModelId,
780
- report: finishReport,
781
- costUsd,
782
- detailed: flags.metricsDetailed,
783
- extraParts: buildFinishExtras({
784
- extracted,
785
- metricsDetailed: flags.metricsDetailed,
786
- transcriptionCostLabel,
787
- }),
788
- color: flags.verboseColor,
789
- });
790
- }
264
+ });
265
+ await writeUrlMetricsFinishLine({
266
+ ctx,
267
+ extracted,
268
+ report: finishReport,
269
+ transcriptionCostLabel,
270
+ label: extractionUi.finishSourceLabel,
271
+ elapsedLabel: summaryFromCache ? "Cached" : null,
272
+ model: usedAttempt.userModelId,
273
+ });
791
274
  return;
792
275
  }
793
276
  if (slidesOutput) {
@@ -830,25 +313,14 @@ export async function summarizeExtractedUrl({ ctx, url, extracted, extractionUi,
830
313
  hooks.restoreProgressAfterStdout?.();
831
314
  }
832
315
  const report = flags.shouldComputeReport ? await hooks.buildReport() : null;
833
- if (flags.metricsEnabled && report) {
834
- const costUsd = await hooks.estimateCostUsd();
835
- writeFinishLine({
836
- stderr: io.stderr,
837
- env: io.envForRun,
838
- elapsedMs: Date.now() - flags.runStartedAtMs,
839
- elapsedLabel: summaryFromCache ? "Cached" : null,
840
- label: extractionUi.finishSourceLabel,
841
- model: modelMeta.canonical,
842
- report,
843
- costUsd,
844
- detailed: flags.metricsDetailed,
845
- extraParts: buildFinishExtras({
846
- extracted,
847
- metricsDetailed: flags.metricsDetailed,
848
- transcriptionCostLabel,
849
- }),
850
- color: flags.verboseColor,
851
- });
852
- }
316
+ await writeUrlMetricsFinishLine({
317
+ ctx,
318
+ extracted,
319
+ report,
320
+ transcriptionCostLabel,
321
+ label: extractionUi.finishSourceLabel,
322
+ elapsedLabel: summaryFromCache ? "Cached" : null,
323
+ model: modelMeta.canonical,
324
+ });
853
325
  }
854
326
  //# sourceMappingURL=summary.js.map