@tacone/prosey 0.2.4 → 0.3.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/bin/prosey CHANGED
@@ -94210,9 +94210,9 @@ function detectPager(cfgPager) {
94210
94210
  if (cfgPager !== undefined && cfgPager !== "" && cfgPager !== "auto")
94211
94211
  return cfgPager;
94212
94212
  if (hasCommand("bat"))
94213
- return "bat -lmd";
94213
+ return "bat -lmd --style plain";
94214
94214
  if (hasCommand("glow"))
94215
- return "glow";
94215
+ return "glow -p";
94216
94216
  if (hasCommand("mdcat"))
94217
94217
  return "mdcat -l -p";
94218
94218
  if (hasCommand("less"))
@@ -101603,7 +101603,7 @@ var FALLBACK_CONFIG_TOML = `# Default prosey configuration
101603
101603
  # Created automatically on first run. Edit as needed.
101604
101604
 
101605
101605
  # Pager command for transcript and summary output.
101606
- # Defaults to "auto": bat -lmd → glow → mdcat -l -p → less
101606
+ # Defaults to "auto": bat -lmd --style plain → glow -p → mdcat -l -p → less
101607
101607
  # Set to a custom command (e.g. "less -R") to override.
101608
101608
  # Can also be set via the PROSEY_PAGER env var (takes precedence).
101609
101609
  pager = "auto"
@@ -101612,6 +101612,11 @@ pager = "auto"
101612
101612
  # Can also be set via PROSEY_HINTS env var (yes, no, 1, 0, true, false).
101613
101613
  hints = true
101614
101614
 
101615
+ [ai]
101616
+ # Default command for AI operations (summarize, transcribe).
101617
+ # Can be overridden per-section via the command key below.
101618
+ command = "opencode run"
101619
+
101615
101620
  [summarize]
101616
101621
  # Prompt sent to the command via stdin.
101617
101622
  # Customize this to change how transcripts are summarized.
@@ -101619,23 +101624,20 @@ prompt = """
101619
101624
  Write a comprehensive summary of the following transcription.
101620
101625
  """
101621
101626
 
101622
- # Command to execute with the prompt and transcript piped via stdin.
101623
- # The transcript is appended to the prompt automatically.
101624
- #
101625
- # Available options:
101626
- #
101627
- # opencode run — full access (default)
101628
- # opencode run --permissions read — read-only (view files, no edits)
101629
- #
101630
- # claude -p "" --print — full access (--print for clean output)
101631
- # claude --permission-mode plan -p "" --print — read-only (plan/read only)
101632
- #
101633
- # copilot -sp "" — full access (-s = silent, -p = prompt)
101634
- # copilot -sp "" --deny-all-tools read-only (no shell/write access)
101635
- #
101636
- # codex --sandbox default -p "" — full access
101637
- # codex --sandbox read-only -p "" — read-only
101638
- command = "opencode run"
101627
+ # Command override for summarize. Uncomment to use a different command
101628
+ # than the one specified in [ai].
101629
+ # command = "opencode run"
101630
+
101631
+ [transcribe]
101632
+ # Prompt sent to the command via stdin.
101633
+ # Customize this to change how transcripts are formatted as markdown.
101634
+ prompt = """
101635
+ Convert this transcript to clean, readable markdown.
101636
+ """
101637
+
101638
+ # Command override for transcribe. Uncomment to use a different command
101639
+ # than the one specified in [ai].
101640
+ # command = "opencode run"
101639
101641
  `;
101640
101642
  async function readDefaultConfig() {
101641
101643
  const paths = [
@@ -101685,7 +101687,7 @@ async function resetConfig() {
101685
101687
 
101686
101688
  // src/summarize.ts
101687
101689
  import { spawn } from "node:child_process";
101688
- function executeCommand(command, input, cwd) {
101690
+ var defaultExecuteCommand = (command, input, cwd) => {
101689
101691
  return new Promise((resolve, reject) => {
101690
101692
  const proc = spawn(command, [], { shell: true, stdio: "pipe", cwd });
101691
101693
  let stdout = "";
@@ -101706,13 +101708,16 @@ function executeCommand(command, input, cwd) {
101706
101708
  proc.stdin.write(input);
101707
101709
  proc.stdin.end();
101708
101710
  });
101709
- }
101710
- async function summarize(options) {
101711
+ };
101712
+ async function summarize(options, execCommand = defaultExecuteCommand) {
101711
101713
  const { prompt, command, transcript, cwd } = options;
101714
+ if (!prompt) {
101715
+ throw new Error("No prompt configured. A prompt is required in the config.");
101716
+ }
101712
101717
  const fullPrompt = `${prompt}
101713
101718
 
101714
101719
  ${transcript}`;
101715
- const output = await executeCommand(command, fullPrompt, cwd);
101720
+ const output = await execCommand(command, fullPrompt, cwd);
101716
101721
  const cleaned = output.startsWith(fullPrompt) ? output.slice(fullPrompt.length).replace(/\n+$/, "") : output.replace(/\n+$/, "");
101717
101722
  if (!cleaned || cleaned === transcript) {
101718
101723
  throw new Error("Summarization command returned no meaningful output");
@@ -101720,6 +101725,20 @@ ${transcript}`;
101720
101725
  return cleaned;
101721
101726
  }
101722
101727
 
101728
+ // src/config-resolve.ts
101729
+ function resolveSummarizeCmd(config) {
101730
+ return config.summarize?.command ?? config.ai?.command ?? null;
101731
+ }
101732
+ function resolveSummarizePrompt(config) {
101733
+ return config.summarize?.prompt ?? null;
101734
+ }
101735
+ function resolveTranscribeCmd(config) {
101736
+ return config.transcribe?.command ?? config.ai?.command ?? config.summarize?.command ?? null;
101737
+ }
101738
+ function resolveTranscribePrompt(config) {
101739
+ return config.transcribe?.prompt ?? config.summarize?.prompt ?? null;
101740
+ }
101741
+
101723
101742
  // src/cache.ts
101724
101743
  import { createHash } from "node:crypto";
101725
101744
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
@@ -101756,10 +101775,62 @@ async function writeCache(dir, filename, data) {
101756
101775
  await mkdir2(dir, { recursive: true });
101757
101776
  await writeFile2(join2(dir, filename), data, "utf8");
101758
101777
  }
101778
+
101779
+ // src/extract-chapters.ts
101780
+ var lineRegex = /^\s*[\[\(]?(?:(?:(\d{1,2}):)?(\d{1,2}):(\d{2}))[\]\)]?(?:\s*[-–—:.]\s*|\s+)(.+)$/;
101781
+ function extractChapters(description) {
101782
+ const lines = description.split(`
101783
+ `);
101784
+ const chapters = [];
101785
+ for (const line of lines) {
101786
+ const match = line.match(lineRegex);
101787
+ if (!match)
101788
+ continue;
101789
+ const hours = match[1] ? parseInt(match[1], 10) : 0;
101790
+ const minutes = parseInt(match[2], 10);
101791
+ const seconds = parseInt(match[3], 10);
101792
+ const title = match[4].trim();
101793
+ if (!title)
101794
+ continue;
101795
+ const time = hours * 3600 + minutes * 60 + seconds;
101796
+ chapters.push({ time, title });
101797
+ }
101798
+ chapters.sort((a2, b2) => a2.time - b2.time);
101799
+ const seen = new Set;
101800
+ return chapters.filter((c2) => {
101801
+ if (seen.has(c2.time))
101802
+ return false;
101803
+ seen.add(c2.time);
101804
+ return true;
101805
+ });
101806
+ }
101807
+ function formatChaptersAsJson(chapters) {
101808
+ if (chapters.length === 0)
101809
+ return "not available";
101810
+ const obj = {};
101811
+ for (const ch of chapters) {
101812
+ const h2 = Math.floor(ch.time / 3600);
101813
+ const m2 = Math.floor(ch.time % 3600 / 60);
101814
+ const s2 = ch.time % 60;
101815
+ const key = h2 > 0 ? `${String(h2).padStart(2, "0")}:${String(m2).padStart(2, "0")}:${String(s2).padStart(2, "0")}` : `${String(m2).padStart(2, "0")}:${String(s2).padStart(2, "0")}`;
101816
+ obj[key] = ch.title;
101817
+ }
101818
+ return JSON.stringify(obj);
101819
+ }
101820
+ function formatChaptersAsText(chapters) {
101821
+ return chapters.map((c2) => {
101822
+ const h2 = Math.floor(c2.time / 3600);
101823
+ const m2 = Math.floor(c2.time % 3600 / 60);
101824
+ const s2 = c2.time % 60;
101825
+ const timeStr = h2 > 0 ? `${String(h2).padStart(2, "0")}:${String(m2).padStart(2, "0")}:${String(s2).padStart(2, "0")}` : `${String(m2).padStart(2, "0")}:${String(s2).padStart(2, "0")}`;
101826
+ return `${timeStr} ${c2.title}`;
101827
+ }).join(`
101828
+ `);
101829
+ }
101759
101830
  // package.json
101760
101831
  var package_default = {
101761
101832
  name: "@tacone/prosey",
101762
- version: "0.2.4",
101833
+ version: "0.3.0",
101763
101834
  description: "Download YouTube video transcripts from the CLI",
101764
101835
  module: "src/index.ts",
101765
101836
  type: "module",
@@ -101822,6 +101893,31 @@ var package_default = {
101822
101893
  }
101823
101894
  };
101824
101895
 
101896
+ // src/version-check.ts
101897
+ var TIMEOUT_MS = 3000;
101898
+ var registryUrl = `https://registry.npmjs.org/${package_default.name}/latest`;
101899
+ async function checkVersion() {
101900
+ try {
101901
+ const controller = new AbortController;
101902
+ const timer2 = setTimeout(() => controller.abort(), TIMEOUT_MS);
101903
+ const res = await fetch(registryUrl, { signal: controller.signal });
101904
+ clearTimeout(timer2);
101905
+ if (!res.ok) {
101906
+ debug("Version check failed: HTTP", res.status);
101907
+ return null;
101908
+ }
101909
+ const data = await res.json();
101910
+ if (!data.version) {
101911
+ debug("Version check: no version field in response");
101912
+ return null;
101913
+ }
101914
+ return data.version;
101915
+ } catch (err) {
101916
+ debug("Version check error:", err instanceof Error ? err.message : String(err));
101917
+ return null;
101918
+ }
101919
+ }
101920
+
101825
101921
  // node_modules/prettier/index.mjs
101826
101922
  import { createRequire as __prettierCreateRequire } from "module";
101827
101923
  import { fileURLToPath as __prettierFileUrlToPath } from "url";
@@ -120360,22 +120456,40 @@ var debugApis = {
120360
120456
  };
120361
120457
 
120362
120458
  // src/index.ts
120459
+ process.stdout.on("error", (err) => {
120460
+ if (err.code === "EPIPE")
120461
+ process.exit(0);
120462
+ });
120363
120463
  var NAME2 = "prosey";
120364
120464
  var VERSION2 = package_default.version;
120465
+ var latestVersion = null;
120466
+ var versionCheck = checkVersion().then((v11) => {
120467
+ latestVersion = v11;
120468
+ });
120469
+ function exitProcess(code) {
120470
+ if (useHints && code === 0 && latestVersion && latestVersion !== VERSION2) {
120471
+ hint(`\uD83D\uDCE6 New version available: ${latestVersion} — use npm/pnpm/bun -g i ${package_default.name} to upgrade`);
120472
+ }
120473
+ process.exit(code);
120474
+ }
120365
120475
  function help() {
120366
120476
  return `${NAME2} v${VERSION2}
120367
120477
 
120368
120478
  Usage: ${NAME2} [options] <video-url-or-id>
120479
+ ${NAME2} read [options] <video-url-or-id>
120369
120480
  ${NAME2} info [options] <video-url-or-id>
120370
120481
  ${NAME2} summarize [options] <video-url-or-id>
120371
120482
  ${NAME2} config
120483
+ ${NAME2} help
120372
120484
 
120373
120485
  Download a YouTube video transcript or show video details.
120374
120486
 
120375
120487
  Commands:
120488
+ summarize Pipe transcript to the AI command (default command)
120489
+ read Download and print a richly formatted transcript
120376
120490
  info Show video metadata (title, channel, duration, etc.)
120377
- summarize Pipe transcript to the command configured in [summarize]
120378
120491
  config Open config file in $EDITOR
120492
+ help Show this help message
120379
120493
 
120380
120494
  Arguments:
120381
120495
  video-url-or-id YouTube URL (full or short) or bare video ID
@@ -120385,14 +120499,18 @@ Options:
120385
120499
  -t, --timestamps Include timestamps [MM:SS] in output.
120386
120500
  --list List available transcript languages and exit.
120387
120501
  -o, --output <path> Write output to file instead of stdout.
120388
- --json Output as JSON (suppresses details).
120389
- --text Output as plain text (default).
120502
+ --format <type> Output format: markdown (default), text, or json.
120503
+ --json Shortcut for --format json.
120504
+ --text Shortcut for --format text.
120505
+ --markdown Shortcut for --format markdown.
120390
120506
  --details Prepend video details to transcript (default, text only).
120391
120507
  --no-details Suppress video details, transcript only.
120392
120508
  --no-decode-entities Preserve HTML entities (decoded by default).
120393
120509
  --reset-config Reset config file to defaults and exit.
120394
120510
  --no-cache Skip cache and overwrite cache files.
120395
120511
  --no-format Skip prettier formatting.
120512
+ --dry-run Print what would be sent to the AI command and exit.
120513
+ --extract-timestamps Extract chapter timestamps from video description.
120396
120514
  --no-pager Disable pager for stdout output.
120397
120515
  --pager Use pager for stdout output (default).
120398
120516
  --no-hints Disable hints.
@@ -120502,22 +120620,22 @@ async function outputText(text) {
120502
120620
  }
120503
120621
  var pagerCmd = null;
120504
120622
  var args = process.argv.slice(2);
120505
- if (args.length === 0 || args.includes("--help")) {
120623
+ if (args.length === 0 || args.includes("--help") || args.includes("help")) {
120506
120624
  console.log(help());
120507
- process.exit(0);
120625
+ exitProcess(0);
120508
120626
  }
120509
120627
  if (args.includes("--version")) {
120510
120628
  console.log(VERSION2);
120511
- process.exit(0);
120629
+ exitProcess(0);
120512
120630
  }
120513
120631
  if (args.includes("--reset-config")) {
120514
120632
  const path15 = await resetConfig();
120515
120633
  console.log(`Config reset to defaults: ${path15}`);
120516
- process.exit(0);
120634
+ exitProcess(0);
120517
120635
  }
120518
120636
  var config = await loadConfig().catch(() => ({}));
120519
- var mode = "transcript";
120520
- var subcmdIndex = args.findIndex((a5) => a5 === "info" || a5 === "summarize" || a5 === "config");
120637
+ var mode = "summarize";
120638
+ var subcmdIndex = args.findIndex((a5) => a5 === "info" || a5 === "summarize" || a5 === "config" || a5 === "read");
120521
120639
  if (subcmdIndex !== -1) {
120522
120640
  mode = args[subcmdIndex];
120523
120641
  args.splice(subcmdIndex, 1);
@@ -120528,12 +120646,15 @@ var timestamps = false;
120528
120646
  var listOnly = false;
120529
120647
  var outputPath;
120530
120648
  var outputJson = false;
120649
+ var format3 = "markdown";
120531
120650
  var noDecode = false;
120532
120651
  var showDetails = true;
120533
120652
  var noCache = false;
120534
120653
  var noFormat = false;
120535
120654
  var usePager = true;
120536
120655
  var useHints = true;
120656
+ var dryRun = false;
120657
+ var extractTimestamps = false;
120537
120658
  var logLevel = "normal";
120538
120659
  for (let i = 0;i < args.length; i++) {
120539
120660
  const arg = args[i];
@@ -120543,7 +120664,7 @@ for (let i = 0;i < args.length; i++) {
120543
120664
  lang = args[++i] ?? undefined;
120544
120665
  if (!lang) {
120545
120666
  console.error("Error: --lang requires a language code");
120546
- process.exit(1);
120667
+ exitProcess(1);
120547
120668
  }
120548
120669
  } else if (arg === "--timestamps" || arg === "-t") {
120549
120670
  timestamps = true;
@@ -120553,12 +120674,31 @@ for (let i = 0;i < args.length; i++) {
120553
120674
  outputPath = args[++i] ?? undefined;
120554
120675
  if (!outputPath) {
120555
120676
  console.error("Error: -o/--output requires a file path");
120556
- process.exit(1);
120677
+ exitProcess(1);
120557
120678
  }
120558
120679
  } else if (arg === "--json") {
120559
120680
  outputJson = true;
120681
+ format3 = "json";
120560
120682
  } else if (arg === "--text") {
120561
120683
  outputJson = false;
120684
+ format3 = "text";
120685
+ } else if (arg === "--markdown") {
120686
+ format3 = "markdown";
120687
+ } else if (arg === "--format") {
120688
+ const val = args[++i];
120689
+ if (val === "json") {
120690
+ format3 = "json";
120691
+ outputJson = true;
120692
+ } else if (val === "text") {
120693
+ format3 = "text";
120694
+ outputJson = false;
120695
+ } else if (val === "markdown") {
120696
+ format3 = "markdown";
120697
+ outputJson = false;
120698
+ } else {
120699
+ console.error("Error: --format must be text, json, or markdown");
120700
+ exitProcess(1);
120701
+ }
120562
120702
  } else if (arg === "--details") {
120563
120703
  showDetails = true;
120564
120704
  } else if (arg === "--no-details") {
@@ -120581,9 +120721,13 @@ for (let i = 0;i < args.length; i++) {
120581
120721
  logLevel = "verbose";
120582
120722
  } else if (arg === "--no-decode-entities") {
120583
120723
  noDecode = true;
120724
+ } else if (arg === "--dry-run") {
120725
+ dryRun = true;
120726
+ } else if (arg === "--extract-timestamps") {
120727
+ extractTimestamps = true;
120584
120728
  } else if (arg.startsWith("-")) {
120585
120729
  console.error(`Unknown option: ${arg}`);
120586
- process.exit(1);
120730
+ exitProcess(1);
120587
120731
  } else {
120588
120732
  videoId = arg;
120589
120733
  }
@@ -120600,17 +120744,17 @@ if (mode === "config") {
120600
120744
  } else {
120601
120745
  console.log(`Config file: ${path15}`);
120602
120746
  }
120603
- process.exit(0);
120747
+ exitProcess(0);
120604
120748
  }
120605
120749
  if (!videoId) {
120606
120750
  console.error("Error: missing video URL or ID");
120607
120751
  console.log(help());
120608
- process.exit(1);
120752
+ exitProcess(1);
120609
120753
  }
120610
120754
  var extracted = extractVideoId(videoId);
120611
120755
  if (!extracted) {
120612
120756
  console.error("Error: invalid YouTube video URL or ID");
120613
- process.exit(65);
120757
+ exitProcess(65);
120614
120758
  }
120615
120759
  videoId = extracted;
120616
120760
  setLevel(logLevel);
@@ -120626,7 +120770,7 @@ debug("Pager:", pagerCmd ?? "none");
120626
120770
  }
120627
120771
  }
120628
120772
  if (useHints) {
120629
- const hasMarkdownPager = pagerCmd === "bat -lmd" || pagerCmd === "glow" || pagerCmd === "mdcat -l -p";
120773
+ const hasMarkdownPager = pagerCmd === "bat -lmd --style plain" || pagerCmd === "glow -p" || pagerCmd === "mdcat -l -p";
120630
120774
  if (!hasMarkdownPager) {
120631
120775
  hint("Tip: install a markdown highlighter for better output (e.g. bat, glow, mdcat)");
120632
120776
  }
@@ -120636,6 +120780,22 @@ debug("Video ID:", videoId);
120636
120780
  debug("Mode:", mode);
120637
120781
  if (lang)
120638
120782
  debug("Language:", lang);
120783
+ await Promise.race([versionCheck, new Promise((r5) => setTimeout(r5, 1000))]);
120784
+ if (extractTimestamps) {
120785
+ startTimer();
120786
+ info("Fetching transcript...");
120787
+ const result = await fetchTranscript(videoId, {
120788
+ videoDetails: true,
120789
+ lang
120790
+ });
120791
+ info("Transcript fetched");
120792
+ const chapters = extractChapters(result.videoDetails.description);
120793
+ const output = outputJson ? JSON.stringify(chapters, null, 2) + `
120794
+ ` : formatChaptersAsText(chapters) + `
120795
+ `;
120796
+ await outputText(output);
120797
+ exitProcess(0);
120798
+ }
120639
120799
  try {
120640
120800
  if (mode === "info") {
120641
120801
  const result = await fetchTranscript(videoId, { videoDetails: true, lang });
@@ -120644,12 +120804,13 @@ try {
120644
120804
  } else {
120645
120805
  printVideoInfo(result.videoDetails);
120646
120806
  }
120647
- process.exit(0);
120807
+ exitProcess(0);
120648
120808
  }
120649
120809
  if (mode === "summarize") {
120650
- if (!config.summarize?.command) {
120651
- console.error("Error: [summarize] section with a command is required in config");
120652
- process.exit(1);
120810
+ const sumCmd = resolveSummarizeCmd(config);
120811
+ if (!sumCmd) {
120812
+ console.error("Error: no command configured for summarize. Set [ai].command or [summarize].command in config.");
120813
+ exitProcess(1);
120653
120814
  }
120654
120815
  const cacheOpts2 = { lang, mode: "summarize", noDecode };
120655
120816
  const dir2 = cacheDir(videoId, cacheOpts2);
@@ -120677,13 +120838,24 @@ try {
120677
120838
  await writeCache(dir2, "transcript.json", JSON.stringify(segments2));
120678
120839
  debug("Cache written: transcript.json");
120679
120840
  }
120680
- const prompt = config.summarize.prompt ?? "";
120841
+ const prompt2 = resolveSummarizePrompt(config) ?? "";
120842
+ if (!prompt2) {
120843
+ console.error("Error: no prompt configured. Set a prompt in the [summarize] section of your config.");
120844
+ exitProcess(1);
120845
+ }
120681
120846
  const transcriptText = toText(segments2, !noDecode);
120847
+ if (dryRun) {
120848
+ await outputText(`${prompt2}
120849
+
120850
+ ${transcriptText}
120851
+ `);
120852
+ exitProcess(0);
120853
+ }
120682
120854
  if (!summary) {
120683
120855
  info(`Summarizing...`);
120684
120856
  summary = await summarize({
120685
- prompt,
120686
- command: config.summarize.command,
120857
+ prompt: prompt2,
120858
+ command: sumCmd,
120687
120859
  transcript: transcriptText,
120688
120860
  cwd: dir2
120689
120861
  });
@@ -120694,11 +120866,145 @@ try {
120694
120866
  const formatted = noFormat ? summary : await formatMd(summary);
120695
120867
  await outputText(formatted + `
120696
120868
  `);
120697
- process.exit(0);
120869
+ exitProcess(0);
120698
120870
  } else if (listOnly) {
120699
120871
  const languages2 = await listLanguages(videoId);
120700
120872
  printLanguages(languages2);
120701
- process.exit(0);
120873
+ exitProcess(0);
120874
+ }
120875
+ if (format3 === "markdown") {
120876
+ const transcribeCmd = resolveTranscribeCmd(config);
120877
+ if (!transcribeCmd) {
120878
+ console.error("Error: no command configured for transcribe. Set [transcribe].command, [ai].command, or [summarize].command in config.");
120879
+ exitProcess(1);
120880
+ }
120881
+ const cacheOpts2 = { lang, mode: "transcribe", noDecode };
120882
+ const dir2 = cacheDir(videoId, cacheOpts2);
120883
+ let segments2 = null;
120884
+ let md = null;
120885
+ startTimer();
120886
+ let cachedInfo = null;
120887
+ if (!noCache) {
120888
+ const cachedSegments = await readCache(dir2, "transcript.json");
120889
+ const cachedMd = await readCache(dir2, "transcript.md");
120890
+ cachedInfo = await readCache(dir2, "info.json");
120891
+ if (cachedSegments && cachedMd) {
120892
+ info("Transcript cached");
120893
+ debug("Cache hit:", dir2);
120894
+ segments2 = JSON.parse(cachedSegments);
120895
+ md = cachedMd;
120896
+ } else {
120897
+ debug("Cache miss:", dir2);
120898
+ }
120899
+ } else {
120900
+ debug("Cache skipped (--no-cache)");
120901
+ }
120902
+ const prompt2 = resolveTranscribePrompt(config) ?? "";
120903
+ if (!prompt2) {
120904
+ console.error("Error: no prompt configured. Set a prompt in the [transcribe] or [summarize] section of your config.");
120905
+ exitProcess(1);
120906
+ }
120907
+ if (!segments2) {
120908
+ info("Fetching transcript...");
120909
+ const opts = lang ? { lang, videoDetails: true } : { videoDetails: true };
120910
+ const result = await fetchTranscript(videoId, opts);
120911
+ segments2 = result.segments;
120912
+ const infoJson = JSON.stringify({
120913
+ title: result.videoDetails.title,
120914
+ channel: result.videoDetails.author,
120915
+ description: result.videoDetails.description
120916
+ });
120917
+ cachedInfo = infoJson;
120918
+ const chapterValue = formatChaptersAsJson(extractChapters(result.videoDetails.description));
120919
+ const truncatedInfo = JSON.stringify({
120920
+ title: result.videoDetails.title,
120921
+ channel: result.videoDetails.author,
120922
+ description: result.videoDetails.description.slice(0, 1000)
120923
+ });
120924
+ const transcriptText = toText(segments2, !noDecode);
120925
+ const structuredContent = `INFO:
120926
+ ${truncatedInfo}
120927
+
120928
+ TIMESTAMPS:
120929
+ ${chapterValue}
120930
+
120931
+ TEXT:
120932
+ ${transcriptText}`;
120933
+ if (dryRun) {
120934
+ await outputText(`${prompt2}
120935
+
120936
+ ${structuredContent}
120937
+ `);
120938
+ exitProcess(0);
120939
+ }
120940
+ info(`Transcript: ${segments2.length} segments`);
120941
+ await writeCache(dir2, "transcript.json", JSON.stringify(segments2));
120942
+ await writeCache(dir2, "info.json", infoJson);
120943
+ await writeCache(dir2, "chapters.json", chapterValue);
120944
+ debug("Cache written: transcript.json, info.json, chapters.json");
120945
+ if (!md) {
120946
+ info(`Transcribing...`);
120947
+ md = await summarize({
120948
+ prompt: prompt2,
120949
+ command: transcribeCmd,
120950
+ transcript: structuredContent,
120951
+ cwd: dir2
120952
+ });
120953
+ info("Transcription ready");
120954
+ await writeCache(dir2, "transcript.md", md);
120955
+ debug("Cache written: transcript.md");
120956
+ }
120957
+ } else {
120958
+ let chapterValue;
120959
+ if (!cachedInfo) {
120960
+ debug("Cache missing info.json, re-fetching video details");
120961
+ const fallbackOpts = lang ? { lang, videoDetails: true } : { videoDetails: true };
120962
+ const fallbackResult = await fetchTranscript(videoId, fallbackOpts);
120963
+ cachedInfo = JSON.stringify({
120964
+ title: fallbackResult.videoDetails.title,
120965
+ channel: fallbackResult.videoDetails.author,
120966
+ description: fallbackResult.videoDetails.description
120967
+ });
120968
+ await writeCache(dir2, "info.json", cachedInfo);
120969
+ chapterValue = formatChaptersAsJson(extractChapters(fallbackResult.videoDetails.description));
120970
+ await writeCache(dir2, "chapters.json", chapterValue);
120971
+ debug("Cache written: info.json, chapters.json");
120972
+ } else {
120973
+ const cachedChapters = await readCache(dir2, "chapters.json");
120974
+ chapterValue = cachedChapters ?? "not available";
120975
+ }
120976
+ const transcriptText = toText(segments2, !noDecode);
120977
+ const cachedInfoObj = JSON.parse(cachedInfo);
120978
+ const truncatedInfo = JSON.stringify({
120979
+ title: cachedInfoObj.title,
120980
+ channel: cachedInfoObj.channel,
120981
+ description: cachedInfoObj.description.slice(0, 1000)
120982
+ });
120983
+ const structuredContent = `INFO:
120984
+ ${truncatedInfo}
120985
+
120986
+ TIMESTAMPS:
120987
+ ${chapterValue}
120988
+
120989
+ TEXT:
120990
+ ${transcriptText}`;
120991
+ if (!md) {
120992
+ info(`Transcribing...`);
120993
+ md = await summarize({
120994
+ prompt: prompt2,
120995
+ command: transcribeCmd,
120996
+ transcript: structuredContent,
120997
+ cwd: dir2
120998
+ });
120999
+ info("Transcription ready");
121000
+ await writeCache(dir2, "transcript.md", md);
121001
+ debug("Cache written: transcript.md");
121002
+ }
121003
+ }
121004
+ const formatted = noFormat ? md : await formatMd(md);
121005
+ await outputText(formatted + `
121006
+ `);
121007
+ exitProcess(0);
120702
121008
  }
120703
121009
  const decode = !noDecode;
120704
121010
  const cacheOpts = { lang, timestamps, json: outputJson, noDecode };
@@ -120718,6 +121024,11 @@ try {
120718
121024
  } else {
120719
121025
  debug("Cache skipped (--no-cache)");
120720
121026
  }
121027
+ const prompt = resolveTranscribePrompt(config) ?? "";
121028
+ if (!prompt) {
121029
+ console.error("Error: no prompt configured. Set a prompt in the [transcribe] or [summarize] section of your config.");
121030
+ exitProcess(1);
121031
+ }
120721
121032
  if (!segments) {
120722
121033
  info("Fetching transcript...");
120723
121034
  if (showDetails && !outputJson) {
@@ -120752,8 +121063,9 @@ try {
120752
121063
  `;
120753
121064
  await outputText(output);
120754
121065
  }
121066
+ exitProcess(0);
120755
121067
  } catch (err) {
120756
121068
  const message = err instanceof Error ? err.message : String(err);
120757
121069
  console.error(`Error: ${message}`);
120758
- process.exit(1);
121070
+ exitProcess(1);
120759
121071
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tacone/prosey",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "description": "Download YouTube video transcripts from the CLI",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",