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