@tacone/prosey 0.4.0 → 0.6.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
@@ -103244,6 +103244,7 @@ blockquote {
103244
103244
  blockquote:first-child { margin-top: 0; }
103245
103245
  blockquote:last-child { margin-bottom: 0; }
103246
103246
  * { transition: all 0.3s; }
103247
+ #theme-btn, #theme-btn:focus, #theme-btn:active, #theme-btn:hover { box-shadow: none !important; }
103247
103248
  #theme-btn:focus, #theme-btn:active { outline: none !important; }
103248
103249
  #theme-btn:hover, #theme-btn:focus, #theme-btn:active { opacity: 1 !important; }
103249
103250
  img[alt="Prosey"] { filter: grayscale(100%); }
@@ -103259,7 +103260,7 @@ img[alt="Prosey"]:hover, img[alt="Prosey"]:active, img[alt="Prosey"]:focus { fil
103259
103260
  ${body}
103260
103261
  </main>
103261
103262
  <script>
103262
- (function(){var b=document.getElementById('theme-btn'),modes=['auto','light','dark'],icons={auto:'\uD83D\uDCA1',light:'☀️',dark:'\uD83C\uDF19'};function apply(m){var t=m;if(t==='auto')t=window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light';document.documentElement.setAttribute('data-theme',t);localStorage.setItem('prosey-theme',m);b.textContent=icons[m]}b.addEventListener('click',function(){var m=localStorage.getItem('prosey-theme')||'auto',i=modes.indexOf(m);apply(modes[(i+1)%modes.length])})})();
103263
+ (function(){var b=document.getElementById('theme-btn'),icons={auto:'\uD83D\uDCA1',light:'☀️',dark:'\uD83C\uDF19'};function apply(m){var t=m;if(t==='auto')t=window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light';document.documentElement.setAttribute('data-theme',t);localStorage.setItem('prosey-theme',m);b.textContent=icons[m]}b.addEventListener('click',function(){var m=localStorage.getItem('prosey-theme')||'auto',d=window.matchMedia('(prefers-color-scheme:dark)').matches;apply(d?{auto:'light',light:'dark',dark:'auto'}[m]:{auto:'dark',dark:'light',light:'auto'}[m])});apply(localStorage.getItem('prosey-theme')||'auto')})();
103263
103264
  </script>
103264
103265
  </body>
103265
103266
  </html>`;
@@ -103290,7 +103291,7 @@ function openInBrowser(htmlPath) {
103290
103291
  // package.json
103291
103292
  var package_default = {
103292
103293
  name: "@tacone/prosey",
103293
- version: "0.4.0",
103294
+ version: "0.6.0",
103294
103295
  description: "Download YouTube video transcripts from the CLI",
103295
103296
  module: "src/index.ts",
103296
103297
  type: "module",
@@ -122283,10 +122284,12 @@ try {
122283
122284
  const dir2 = cacheDir(videoId, cacheOpts2);
122284
122285
  let segments2 = null;
122285
122286
  let summary = null;
122287
+ let cachedInfo = null;
122286
122288
  startTimer();
122287
122289
  if (!noCache) {
122288
122290
  const cachedSegments = await readCache(dir2, "transcript.json");
122289
122291
  const cachedSummary = await readCache(dir2, "summary.md");
122292
+ cachedInfo = await readCache(dir2, "info.json");
122290
122293
  if (cachedSegments && cachedSummary) {
122291
122294
  info("Transcript cached");
122292
122295
  debug("Cache hit:", dir2);
@@ -122298,23 +122301,83 @@ try {
122298
122301
  } else {
122299
122302
  debug("Cache skipped (--no-cache)");
122300
122303
  }
122301
- if (!segments2) {
122302
- info("Fetching transcript...");
122303
- segments2 = lang ? await fetchTranscript(videoId, { lang }) : await fetchTranscript(videoId);
122304
- info(`Transcript: ${segments2.length} segments`);
122305
- await writeCache(dir2, "transcript.json", JSON.stringify(segments2));
122306
- debug("Cache written: transcript.json");
122307
- }
122308
122304
  const prompt2 = resolveSummarizePrompt(config) ?? "";
122309
122305
  if (!prompt2) {
122310
122306
  console.error("Error: no prompt configured. Set a prompt in the [summarize] section of your config.");
122311
122307
  exitProcess(1);
122312
122308
  }
122313
- const transcriptText = toText(segments2, !noDecode);
122309
+ let structuredContent;
122310
+ if (!segments2) {
122311
+ info("Fetching transcript...");
122312
+ const opts = lang ? { lang, videoDetails: true } : { videoDetails: true };
122313
+ const result = await fetchTranscript(videoId, opts);
122314
+ segments2 = result.segments;
122315
+ const infoJson = JSON.stringify({
122316
+ title: result.videoDetails.title,
122317
+ channel: result.videoDetails.author,
122318
+ description: result.videoDetails.description
122319
+ });
122320
+ cachedInfo = infoJson;
122321
+ const chapterValue = formatChaptersAsJson(extractChapters(result.videoDetails.description));
122322
+ const truncatedInfo = JSON.stringify({
122323
+ title: result.videoDetails.title,
122324
+ channel: result.videoDetails.author,
122325
+ description: result.videoDetails.description.slice(0, 1000)
122326
+ });
122327
+ const transcriptText = toText(segments2, !noDecode);
122328
+ structuredContent = `INFO:
122329
+ ${truncatedInfo}
122330
+
122331
+ TEXT:
122332
+ ${transcriptText}`;
122333
+ if (dryRun) {
122334
+ await outputText(`${prompt2}
122335
+
122336
+ ${structuredContent}
122337
+ `);
122338
+ exitProcess(0);
122339
+ }
122340
+ info(`Transcript: ${segments2.length} segments`);
122341
+ await writeCache(dir2, "transcript.json", JSON.stringify(segments2));
122342
+ await writeCache(dir2, "info.json", infoJson);
122343
+ await writeCache(dir2, "chapters.json", chapterValue);
122344
+ debug("Cache written: transcript.json, info.json, chapters.json");
122345
+ } else {
122346
+ let chapterValue;
122347
+ if (!cachedInfo) {
122348
+ debug("Cache missing info.json, re-fetching video details");
122349
+ const fallbackOpts = lang ? { lang, videoDetails: true } : { videoDetails: true };
122350
+ const fallbackResult = await fetchTranscript(videoId, fallbackOpts);
122351
+ cachedInfo = JSON.stringify({
122352
+ title: fallbackResult.videoDetails.title,
122353
+ channel: fallbackResult.videoDetails.author,
122354
+ description: fallbackResult.videoDetails.description
122355
+ });
122356
+ await writeCache(dir2, "info.json", cachedInfo);
122357
+ chapterValue = formatChaptersAsJson(extractChapters(fallbackResult.videoDetails.description));
122358
+ await writeCache(dir2, "chapters.json", chapterValue);
122359
+ debug("Cache written: info.json, chapters.json");
122360
+ } else {
122361
+ const cachedChapters = await readCache(dir2, "chapters.json");
122362
+ chapterValue = cachedChapters ?? "not available";
122363
+ }
122364
+ const transcriptText = toText(segments2, !noDecode);
122365
+ const cachedInfoObj = JSON.parse(cachedInfo);
122366
+ const truncatedInfo = JSON.stringify({
122367
+ title: cachedInfoObj.title,
122368
+ channel: cachedInfoObj.channel,
122369
+ description: cachedInfoObj.description.slice(0, 1000)
122370
+ });
122371
+ structuredContent = `INFO:
122372
+ ${truncatedInfo}
122373
+
122374
+ TEXT:
122375
+ ${transcriptText}`;
122376
+ }
122314
122377
  if (dryRun) {
122315
122378
  await outputText(`${prompt2}
122316
122379
 
122317
- ${transcriptText}
122380
+ ${structuredContent}
122318
122381
  `);
122319
122382
  exitProcess(0);
122320
122383
  }
@@ -122323,7 +122386,7 @@ ${transcriptText}
122323
122386
  summary = await summarize({
122324
122387
  prompt: prompt2,
122325
122388
  command: sumCmd,
122326
- transcript: transcriptText,
122389
+ transcript: structuredContent,
122327
122390
  cwd: dir2
122328
122391
  });
122329
122392
  info("Summary ready");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tacone/prosey",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Download YouTube video transcripts from the CLI",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -29,7 +29,23 @@ command = "OPENCODE_PERMISSION='{\"read\":\"allow\",\"write\":\"deny\",\"edit\":
29
29
  # Customize this to change how transcripts are summarized.
30
30
 
31
31
  prompt = """
32
- Write a comprehensive summary of the following transcription.
32
+ Write a comprehensive summary of the following video transcription:
33
+
34
+ Guidelines:
35
+ - use the INFO section data to correct spellings in the text (e.g. Pi vs Pie)
36
+ - use the INFO title value as the main title (h1 header)
37
+ - use the TIMESTAMPS only to understand the structure, don't output time markers
38
+ - content length: aim to be the same range as the original text or more.
39
+ - section titles should be h2 headers
40
+
41
+ Content considerations:
42
+
43
+ - findings should be presented in lists or table
44
+ - flows should be presented in ordered lists
45
+ - elaborations are better in prose
46
+ - when content mixes types within a section, lead with prose and break out lists only for discrete items
47
+
48
+ Only output the summary text without adding anything.
33
49
  """
34
50
 
35
51
  # Command override for summarize. Uncomment to use a different command
package/src/html.ts CHANGED
@@ -91,6 +91,7 @@ blockquote {
91
91
  blockquote:first-child { margin-top: 0; }
92
92
  blockquote:last-child { margin-bottom: 0; }
93
93
  * { transition: all 0.3s; }
94
+ #theme-btn, #theme-btn:focus, #theme-btn:active, #theme-btn:hover { box-shadow: none !important; }
94
95
  #theme-btn:focus, #theme-btn:active { outline: none !important; }
95
96
  #theme-btn:hover, #theme-btn:focus, #theme-btn:active { opacity: 1 !important; }
96
97
  img[alt="Prosey"] { filter: grayscale(100%); }
@@ -106,7 +107,7 @@ img[alt="Prosey"]:hover, img[alt="Prosey"]:active, img[alt="Prosey"]:focus { fil
106
107
  ${body}
107
108
  </main>
108
109
  <script>
109
- (function(){var b=document.getElementById('theme-btn'),modes=['auto','light','dark'],icons={auto:'\u{1F4A1}',light:'\u2600\uFE0F',dark:'\u{1F319}'};function apply(m){var t=m;if(t==='auto')t=window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light';document.documentElement.setAttribute('data-theme',t);localStorage.setItem('prosey-theme',m);b.textContent=icons[m]}b.addEventListener('click',function(){var m=localStorage.getItem('prosey-theme')||'auto',i=modes.indexOf(m);apply(modes[(i+1)%modes.length])})})();
110
+ (function(){var b=document.getElementById('theme-btn'),icons={auto:'\u{1F4A1}',light:'\u2600\uFE0F',dark:'\u{1F319}'};function apply(m){var t=m;if(t==='auto')t=window.matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light';document.documentElement.setAttribute('data-theme',t);localStorage.setItem('prosey-theme',m);b.textContent=icons[m]}b.addEventListener('click',function(){var m=localStorage.getItem('prosey-theme')||'auto',d=window.matchMedia('(prefers-color-scheme:dark)').matches;apply(d?{auto:'light',light:'dark',dark:'auto'}[m]:{auto:'dark',dark:'light',light:'auto'}[m])});apply(localStorage.getItem('prosey-theme')||'auto')})();
110
111
  </script>
111
112
  </body>
112
113
  </html>`;
package/src/index.ts CHANGED
@@ -443,12 +443,14 @@ try {
443
443
  const dir = cacheDir(videoId, cacheOpts);
444
444
  let segments: TranscriptSegment[] | null = null;
445
445
  let summary: string | null = null;
446
+ let cachedInfo: string | null = null;
446
447
 
447
448
  startTimer();
448
449
 
449
450
  if (!noCache) {
450
451
  const cachedSegments = await readCache(dir, "transcript.json");
451
452
  const cachedSummary = await readCache(dir, "summary.md");
453
+ cachedInfo = await readCache(dir, "info.json");
452
454
  if (cachedSegments && cachedSummary) {
453
455
  info("Transcript cached");
454
456
  debug("Cache hit:", dir);
@@ -461,14 +463,6 @@ try {
461
463
  debug("Cache skipped (--no-cache)");
462
464
  }
463
465
 
464
- if (!segments) {
465
- info("Fetching transcript...");
466
- segments = lang ? await fetchTranscript(videoId, { lang }) : await fetchTranscript(videoId);
467
- info(`Transcript: ${segments.length} segments`);
468
- await writeCache(dir, "transcript.json", JSON.stringify(segments));
469
- debug("Cache written: transcript.json");
470
- }
471
-
472
466
  const prompt = resolveSummarizePrompt(config) ?? "";
473
467
  if (!prompt) {
474
468
  console.error(
@@ -476,10 +470,80 @@ try {
476
470
  );
477
471
  exitProcess(1);
478
472
  }
479
- const transcriptText = toText(segments, !noDecode);
473
+
474
+ let structuredContent: string;
475
+
476
+ if (!segments) {
477
+ info("Fetching transcript...");
478
+ const opts = lang ? { lang, videoDetails: true as const } : { videoDetails: true as const };
479
+ const result = (await fetchTranscript(videoId, opts)) as {
480
+ videoDetails: VideoDetails;
481
+ segments: TranscriptSegment[];
482
+ };
483
+ segments = result.segments;
484
+ const infoJson = JSON.stringify({
485
+ title: result.videoDetails.title,
486
+ channel: result.videoDetails.author,
487
+ description: result.videoDetails.description,
488
+ });
489
+ cachedInfo = infoJson;
490
+ const chapterValue = formatChaptersAsJson(extractChapters(result.videoDetails.description));
491
+ const truncatedInfo = JSON.stringify({
492
+ title: result.videoDetails.title,
493
+ channel: result.videoDetails.author,
494
+ description: result.videoDetails.description.slice(0, 1000),
495
+ });
496
+ const transcriptText = toText(segments, !noDecode);
497
+ structuredContent = `INFO:\n${truncatedInfo}\n\nTEXT:\n${transcriptText}`;
498
+
499
+ if (dryRun) {
500
+ await outputText(`${prompt}\n\n${structuredContent}\n`);
501
+ exitProcess(0);
502
+ }
503
+
504
+ info(`Transcript: ${segments.length} segments`);
505
+ await writeCache(dir, "transcript.json", JSON.stringify(segments));
506
+ await writeCache(dir, "info.json", infoJson);
507
+ await writeCache(dir, "chapters.json", chapterValue);
508
+ debug("Cache written: transcript.json, info.json, chapters.json");
509
+ } else {
510
+ let chapterValue: string;
511
+ if (!cachedInfo) {
512
+ debug("Cache missing info.json, re-fetching video details");
513
+ const fallbackOpts = lang
514
+ ? { lang, videoDetails: true as const }
515
+ : { videoDetails: true as const };
516
+ const fallbackResult = (await fetchTranscript(videoId, fallbackOpts)) as {
517
+ videoDetails: VideoDetails;
518
+ segments: TranscriptSegment[];
519
+ };
520
+ cachedInfo = JSON.stringify({
521
+ title: fallbackResult.videoDetails.title,
522
+ channel: fallbackResult.videoDetails.author,
523
+ description: fallbackResult.videoDetails.description,
524
+ });
525
+ await writeCache(dir, "info.json", cachedInfo);
526
+ chapterValue = formatChaptersAsJson(
527
+ extractChapters(fallbackResult.videoDetails.description),
528
+ );
529
+ await writeCache(dir, "chapters.json", chapterValue);
530
+ debug("Cache written: info.json, chapters.json");
531
+ } else {
532
+ const cachedChapters = await readCache(dir, "chapters.json");
533
+ chapterValue = cachedChapters ?? "not available";
534
+ }
535
+ const transcriptText = toText(segments, !noDecode);
536
+ const cachedInfoObj = JSON.parse(cachedInfo);
537
+ const truncatedInfo = JSON.stringify({
538
+ title: cachedInfoObj.title,
539
+ channel: cachedInfoObj.channel,
540
+ description: cachedInfoObj.description.slice(0, 1000),
541
+ });
542
+ structuredContent = `INFO:\n${truncatedInfo}\n\nTEXT:\n${transcriptText}`;
543
+ }
480
544
 
481
545
  if (dryRun) {
482
- await outputText(`${prompt}\n\n${transcriptText}\n`);
546
+ await outputText(`${prompt}\n\n${structuredContent}\n`);
483
547
  exitProcess(0);
484
548
  }
485
549
 
@@ -488,7 +552,7 @@ try {
488
552
  summary = await summarize({
489
553
  prompt,
490
554
  command: sumCmd,
491
- transcript: transcriptText,
555
+ transcript: structuredContent,
492
556
  cwd: dir,
493
557
  });
494
558
  info("Summary ready");