vidistill 0.4.2 → 0.4.3

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 (2) hide show
  1. package/dist/index.js +268 -607
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -259,11 +259,11 @@ function showConfigBox(config) {
259
259
  }
260
260
 
261
261
  // src/commands/distill.ts
262
- import { log as log8, cancel as cancel3, select as select2, isCancel as isCancel3 } from "@clack/prompts";
262
+ import { log as log6, cancel as cancel2 } from "@clack/prompts";
263
263
  import pc3 from "picocolors";
264
264
  import { basename as basename3, extname as extname2, resolve, join as join4 } from "path";
265
265
  import { existsSync as existsSync3, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
266
- import { readFile as readFile2, writeFile as writeFile2, rm, mkdir as mkdir2 } from "fs/promises";
266
+ import { readdir, rm } from "fs/promises";
267
267
 
268
268
  // src/cli/prompts.ts
269
269
  import { text, password, confirm, select, isCancel, cancel } from "@clack/prompts";
@@ -869,7 +869,7 @@ async function detectDuration(source) {
869
869
  }
870
870
 
871
871
  // src/core/pipeline.ts
872
- import { log as log5 } from "@clack/prompts";
872
+ import { log as log4 } from "@clack/prompts";
873
873
 
874
874
  // src/gemini/schemas.ts
875
875
  import { Type } from "@google/genai";
@@ -2086,7 +2086,6 @@ var MODELS = {
2086
2086
  };
2087
2087
 
2088
2088
  // src/core/consensus.ts
2089
- import { log as log4 } from "@clack/prompts";
2090
2089
  function tokenize(content) {
2091
2090
  const tokens = content.match(/[\p{L}\p{N}_]+/gu) ?? [];
2092
2091
  return new Set(tokens);
@@ -2162,9 +2161,7 @@ async function runCodeConsensus(params) {
2162
2161
  try {
2163
2162
  const result = await runFn();
2164
2163
  successfulRuns.push(result);
2165
- } catch (e) {
2166
- const msg = e instanceof Error ? e.message : String(e);
2167
- log4.warn(`consensus run ${i + 1}/${runs} failed: ${msg}`);
2164
+ } catch {
2168
2165
  }
2169
2166
  onProgress?.(i + 1, runs);
2170
2167
  }
@@ -2250,9 +2247,7 @@ async function runLinkConsensus(params) {
2250
2247
  try {
2251
2248
  const result = await runFn();
2252
2249
  successfulRuns.push(result);
2253
- } catch (e) {
2254
- const msg = e instanceof Error ? e.message : String(e);
2255
- log4.warn(`link consensus run ${i + 1}/${runs} failed: ${msg}`);
2250
+ } catch {
2256
2251
  }
2257
2252
  onProgress?.(i + 1, runs);
2258
2253
  }
@@ -2460,39 +2455,26 @@ async function runPipeline(config) {
2460
2455
  onProgress,
2461
2456
  onWait,
2462
2457
  isShuttingDown,
2463
- lang,
2464
- preloadedResults,
2465
- onPassComplete
2458
+ lang
2466
2459
  } = config;
2467
2460
  const errors = [];
2468
2461
  const passesRun = [];
2469
2462
  let videoProfile;
2470
2463
  let strategy;
2471
- if (config.overrideStrategy != null) {
2464
+ onProgress?.({ phase: "pass0", segment: 0, totalSegments: 1, status: "running" });
2465
+ const pass0Attempt = await withRetry(
2466
+ () => rateLimiter.execute(() => runSceneAnalysis({ client, fileUri, mimeType, duration, model, lang }), { onWait }),
2467
+ "pass0"
2468
+ );
2469
+ if (pass0Attempt.error !== null) {
2470
+ log4.warn(pass0Attempt.error);
2471
+ errors.push(pass0Attempt.error);
2472
2472
  videoProfile = DEFAULT_PROFILE;
2473
- strategy = config.overrideStrategy;
2474
- } else if (preloadedResults != null && "pass0-scene" in preloadedResults) {
2475
- videoProfile = preloadedResults["pass0-scene"];
2476
- strategy = config.overrideStrategy ?? preloadedResults["__strategy"] ?? determineStrategy(videoProfile);
2477
- onProgress?.({ phase: "pass0", segment: 0, totalSegments: 1, status: "done" });
2478
- onPassComplete?.("pass0-scene", videoProfile);
2479
2473
  } else {
2480
- onProgress?.({ phase: "pass0", segment: 0, totalSegments: 1, status: "running" });
2481
- const pass0Attempt = await withRetry(
2482
- () => rateLimiter.execute(() => runSceneAnalysis({ client, fileUri, mimeType, duration, model, lang }), { onWait }),
2483
- "pass0"
2484
- );
2485
- if (pass0Attempt.error !== null) {
2486
- log5.warn(pass0Attempt.error);
2487
- errors.push(pass0Attempt.error);
2488
- videoProfile = DEFAULT_PROFILE;
2489
- } else {
2490
- videoProfile = pass0Attempt.result ?? DEFAULT_PROFILE;
2491
- }
2492
- strategy = determineStrategy(videoProfile);
2493
- onProgress?.({ phase: "pass0", segment: 0, totalSegments: 1, status: "done" });
2494
- onPassComplete?.("pass0-scene", videoProfile);
2474
+ videoProfile = pass0Attempt.result ?? DEFAULT_PROFILE;
2495
2475
  }
2476
+ strategy = determineStrategy(videoProfile);
2477
+ onProgress?.({ phase: "pass0", segment: 0, totalSegments: 1, status: "done" });
2496
2478
  const plan = createSegmentPlan(duration, {
2497
2479
  segmentMinutes: strategy.segmentMinutes,
2498
2480
  resolution: strategy.resolution
@@ -2505,23 +2487,7 @@ async function runPipeline(config) {
2505
2487
  const callsPerSegment = 2 + (strategy.passes.includes("chat") ? linkConsensusRuns : 0) + (strategy.passes.includes("implicit") ? 1 : 0);
2506
2488
  const postSegmentCalls = (strategy.passes.includes("people") ? 1 : 0) + (strategy.passes.includes("code") ? 3 : 0) + (strategy.passes.includes("synthesis") ? 1 : 0);
2507
2489
  const totalSteps = n * callsPerSegment + postSegmentCalls;
2508
- let preloadedSteps = 0;
2509
- if (preloadedResults != null) {
2510
- for (let i = 0; i < n; i++) {
2511
- if (`pass1-seg${i}` in preloadedResults) preloadedSteps++;
2512
- if (`pass2-seg${i}` in preloadedResults) preloadedSteps++;
2513
- if (strategy.passes.includes("chat")) {
2514
- if (`pass3c-seg${i}` in preloadedResults) preloadedSteps += linkConsensusRuns;
2515
- }
2516
- if (strategy.passes.includes("implicit")) {
2517
- if (`pass3d-seg${i}` in preloadedResults) preloadedSteps++;
2518
- }
2519
- }
2520
- if (strategy.passes.includes("people") && "pass3b-people" in preloadedResults) preloadedSteps++;
2521
- if (strategy.passes.includes("code") && "pass3a" in preloadedResults) preloadedSteps += 3;
2522
- if (strategy.passes.includes("synthesis") && "synthesis" in preloadedResults) preloadedSteps++;
2523
- }
2524
- let currentStep = preloadedSteps;
2490
+ let currentStep = 0;
2525
2491
  let pass1RanOnce = false;
2526
2492
  let pass2RanOnce = false;
2527
2493
  let pass3cRanOnce = false;
@@ -2537,149 +2503,109 @@ async function runPipeline(config) {
2537
2503
  const segment = segments[i];
2538
2504
  onProgress?.({ phase: "pass1", segment: i, totalSegments: n, status: "running", totalSteps });
2539
2505
  let pass1 = null;
2540
- const pass1Key = `pass1-seg${i}`;
2541
- if (preloadedResults != null && pass1Key in preloadedResults) {
2542
- pass1 = preloadedResults[pass1Key];
2543
- pass1RanOnce = true;
2544
- currentStep++;
2545
- onProgress?.({ phase: "pass1", segment: i, totalSegments: n, status: "done", currentStep, totalSteps });
2546
- onPassComplete?.(pass1Key, pass1);
2506
+ const pass1Attempt = await withRetry(
2507
+ () => rateLimiter.execute(() => runTranscript({ client, fileUri, mimeType, segment, model, resolution, lang }), { onWait }),
2508
+ `segment ${i} pass1`
2509
+ );
2510
+ if (pass1Attempt.error !== null) {
2511
+ log4.warn(pass1Attempt.error);
2512
+ errors.push(pass1Attempt.error);
2547
2513
  } else {
2548
- const pass1Attempt = await withRetry(
2549
- () => rateLimiter.execute(() => runTranscript({ client, fileUri, mimeType, segment, model, resolution, lang }), { onWait }),
2550
- `segment ${i} pass1`
2551
- );
2552
- if (pass1Attempt.error !== null) {
2553
- log5.warn(pass1Attempt.error);
2554
- errors.push(pass1Attempt.error);
2555
- } else {
2556
- pass1 = pass1Attempt.result;
2557
- pass1RanOnce = true;
2558
- }
2559
- currentStep++;
2560
- onProgress?.({ phase: "pass1", segment: i, totalSegments: n, status: "done", currentStep, totalSteps });
2561
- onPassComplete?.(pass1Key, pass1);
2514
+ pass1 = pass1Attempt.result;
2515
+ pass1RanOnce = true;
2562
2516
  }
2517
+ currentStep++;
2518
+ onProgress?.({ phase: "pass1", segment: i, totalSegments: n, status: "done", currentStep, totalSteps });
2563
2519
  onProgress?.({ phase: "pass2", segment: i, totalSegments: n, status: "running", totalSteps });
2564
2520
  let pass2 = null;
2565
- const pass2Key = `pass2-seg${i}`;
2566
- if (preloadedResults != null && pass2Key in preloadedResults) {
2567
- pass2 = preloadedResults[pass2Key];
2568
- pass2RanOnce = true;
2569
- currentStep++;
2570
- onProgress?.({ phase: "pass2", segment: i, totalSegments: n, status: "done", currentStep, totalSteps });
2571
- onPassComplete?.(pass2Key, pass2);
2521
+ const pass2Attempt = await withRetry(
2522
+ () => rateLimiter.execute(
2523
+ () => runVisual({
2524
+ client,
2525
+ fileUri,
2526
+ mimeType,
2527
+ segment,
2528
+ model,
2529
+ resolution,
2530
+ pass1Transcript: pass1 ?? void 0,
2531
+ lang
2532
+ }),
2533
+ { onWait }
2534
+ ),
2535
+ `segment ${i} pass2`
2536
+ );
2537
+ if (pass2Attempt.error !== null) {
2538
+ log4.warn(pass2Attempt.error);
2539
+ errors.push(pass2Attempt.error);
2572
2540
  } else {
2573
- const pass2Attempt = await withRetry(
2574
- () => rateLimiter.execute(
2575
- () => runVisual({
2541
+ pass2 = pass2Attempt.result;
2542
+ pass2RanOnce = true;
2543
+ }
2544
+ currentStep++;
2545
+ onProgress?.({ phase: "pass2", segment: i, totalSegments: n, status: "done", currentStep, totalSteps });
2546
+ let pass3c;
2547
+ if (strategy.passes.includes("chat")) {
2548
+ onProgress?.({ phase: "pass3c", segment: i, totalSegments: n, status: "running", totalSteps });
2549
+ const linkConsensusResult = await runLinkConsensus({
2550
+ config: { runs: linkConsensusRuns, minAgreement: 2 },
2551
+ runFn: () => rateLimiter.execute(
2552
+ () => runChatExtraction({
2576
2553
  client,
2577
2554
  fileUri,
2578
2555
  mimeType,
2579
2556
  segment,
2580
- model,
2557
+ model: MODELS.flash,
2581
2558
  resolution,
2582
- pass1Transcript: pass1 ?? void 0,
2559
+ pass2Result: pass2 ?? void 0,
2583
2560
  lang
2584
2561
  }),
2585
2562
  { onWait }
2586
2563
  ),
2587
- `segment ${i} pass2`
2588
- );
2589
- if (pass2Attempt.error !== null) {
2590
- log5.warn(pass2Attempt.error);
2591
- errors.push(pass2Attempt.error);
2564
+ onProgress: (run3, total) => {
2565
+ currentStep++;
2566
+ onProgress?.({ phase: "pass3c", segment: i, totalSegments: n, status: run3 < total ? "running" : "done", currentStep, totalSteps });
2567
+ }
2568
+ });
2569
+ if (linkConsensusResult.runsCompleted === 0) {
2570
+ const errMsg = `segment ${i} pass3c: all link consensus runs failed`;
2571
+ log4.warn(errMsg);
2572
+ errors.push(errMsg);
2573
+ pass3c = null;
2592
2574
  } else {
2593
- pass2 = pass2Attempt.result;
2594
- pass2RanOnce = true;
2595
- }
2596
- currentStep++;
2597
- onProgress?.({ phase: "pass2", segment: i, totalSegments: n, status: "done", currentStep, totalSteps });
2598
- onPassComplete?.(pass2Key, pass2);
2599
- }
2600
- let pass3c;
2601
- if (strategy.passes.includes("chat")) {
2602
- onProgress?.({ phase: "pass3c", segment: i, totalSegments: n, status: "running", totalSteps });
2603
- const pass3cKey = `pass3c-seg${i}`;
2604
- if (preloadedResults != null && pass3cKey in preloadedResults) {
2605
- pass3c = preloadedResults[pass3cKey];
2575
+ pass3c = linkConsensusResult.merged;
2606
2576
  pass3cRanOnce = true;
2607
- currentStep += linkConsensusRuns;
2608
- onProgress?.({ phase: "pass3c", segment: i, totalSegments: n, status: "done", currentStep, totalSteps });
2609
- onPassComplete?.(pass3cKey, pass3c);
2610
- } else {
2611
- const linkConsensusResult = await runLinkConsensus({
2612
- config: { runs: linkConsensusRuns, minAgreement: 2 },
2613
- runFn: () => rateLimiter.execute(
2614
- () => runChatExtraction({
2615
- client,
2616
- fileUri,
2617
- mimeType,
2618
- segment,
2619
- model: MODELS.flash,
2620
- resolution,
2621
- pass2Result: pass2 ?? void 0,
2622
- lang
2623
- }),
2624
- { onWait }
2625
- ),
2626
- onProgress: (run3, total) => {
2627
- currentStep++;
2628
- onProgress?.({ phase: "pass3c", segment: i, totalSegments: n, status: run3 < total ? "running" : "done", currentStep, totalSteps });
2629
- }
2630
- });
2631
- if (linkConsensusResult.runsCompleted === 0) {
2632
- const errMsg = `segment ${i} pass3c: all link consensus runs failed`;
2633
- log5.warn(errMsg);
2634
- errors.push(errMsg);
2635
- pass3c = null;
2636
- } else {
2637
- pass3c = linkConsensusResult.merged;
2638
- pass3cRanOnce = true;
2639
- }
2640
- onPassComplete?.(pass3cKey, pass3c ?? null);
2641
2577
  }
2642
2578
  }
2643
2579
  let pass3d;
2644
2580
  if (strategy.passes.includes("implicit")) {
2645
2581
  onProgress?.({ phase: "pass3d", segment: i, totalSegments: n, status: "running", totalSteps });
2646
- const pass3dKey = `pass3d-seg${i}`;
2647
- if (preloadedResults != null && pass3dKey in preloadedResults) {
2648
- pass3d = preloadedResults[pass3dKey];
2649
- pass3dRanOnce = true;
2650
- currentStep++;
2651
- onProgress?.({ phase: "pass3d", segment: i, totalSegments: n, status: "done", currentStep, totalSteps });
2652
- onPassComplete?.(pass3dKey, pass3d);
2582
+ const pass3dAttempt = await withRetry(
2583
+ () => rateLimiter.execute(
2584
+ () => runImplicitSignals({
2585
+ client,
2586
+ fileUri,
2587
+ mimeType,
2588
+ segment,
2589
+ model: MODELS.flash,
2590
+ resolution,
2591
+ pass1Result: pass1 ?? void 0,
2592
+ pass2Result: pass2 ?? void 0,
2593
+ lang
2594
+ }),
2595
+ { onWait }
2596
+ ),
2597
+ `segment ${i} pass3d`
2598
+ );
2599
+ if (pass3dAttempt.error !== null) {
2600
+ log4.warn(pass3dAttempt.error);
2601
+ errors.push(pass3dAttempt.error);
2602
+ pass3d = null;
2653
2603
  } else {
2654
- const pass3dAttempt = await withRetry(
2655
- () => rateLimiter.execute(
2656
- () => runImplicitSignals({
2657
- client,
2658
- fileUri,
2659
- mimeType,
2660
- segment,
2661
- model: MODELS.flash,
2662
- resolution,
2663
- pass1Result: pass1 ?? void 0,
2664
- pass2Result: pass2 ?? void 0,
2665
- lang
2666
- }),
2667
- { onWait }
2668
- ),
2669
- `segment ${i} pass3d`
2670
- );
2671
- if (pass3dAttempt.error !== null) {
2672
- log5.warn(pass3dAttempt.error);
2673
- errors.push(pass3dAttempt.error);
2674
- pass3d = null;
2675
- } else {
2676
- pass3d = pass3dAttempt.result;
2677
- pass3dRanOnce = true;
2678
- }
2679
- currentStep++;
2680
- onProgress?.({ phase: "pass3d", segment: i, totalSegments: n, status: "done", currentStep, totalSteps });
2681
- onPassComplete?.(pass3dKey, pass3d ?? null);
2604
+ pass3d = pass3dAttempt.result;
2605
+ pass3dRanOnce = true;
2682
2606
  }
2607
+ currentStep++;
2608
+ onProgress?.({ phase: "pass3d", segment: i, totalSegments: n, status: "done", currentStep, totalSteps });
2683
2609
  }
2684
2610
  results.push({ index: segment.index, pass1, pass2, pass3c, pass3d });
2685
2611
  }
@@ -2709,136 +2635,106 @@ async function runPipeline(config) {
2709
2635
  let peopleExtraction = null;
2710
2636
  if (strategy.passes.includes("people")) {
2711
2637
  onProgress?.({ phase: "pass3b", segment: 0, totalSegments: 1, status: "running", totalSteps });
2712
- const pass3bKey = "pass3b-people";
2713
- if (preloadedResults != null && pass3bKey in preloadedResults) {
2714
- peopleExtraction = preloadedResults[pass3bKey];
2715
- currentStep++;
2716
- onProgress?.({ phase: "pass3b", segment: 0, totalSegments: 1, status: "done", currentStep, totalSteps });
2717
- onPassComplete?.(pass3bKey, peopleExtraction);
2718
- passesRun.push("pass3b");
2638
+ const pass3bAttempt = await withRetry(
2639
+ () => rateLimiter.execute(
2640
+ () => runPeopleExtraction({
2641
+ client,
2642
+ fileUri,
2643
+ mimeType,
2644
+ model: MODELS.flash,
2645
+ pass1Results,
2646
+ lang
2647
+ }),
2648
+ { onWait }
2649
+ ),
2650
+ "pass3b"
2651
+ );
2652
+ if (pass3bAttempt.error !== null) {
2653
+ log4.warn(pass3bAttempt.error);
2654
+ errors.push(pass3bAttempt.error);
2719
2655
  } else {
2720
- const pass3bAttempt = await withRetry(
2721
- () => rateLimiter.execute(
2722
- () => runPeopleExtraction({
2723
- client,
2724
- fileUri,
2725
- mimeType,
2726
- model: MODELS.flash,
2727
- pass1Results,
2728
- lang
2729
- }),
2730
- { onWait }
2731
- ),
2732
- "pass3b"
2733
- );
2734
- if (pass3bAttempt.error !== null) {
2735
- log5.warn(pass3bAttempt.error);
2736
- errors.push(pass3bAttempt.error);
2737
- } else {
2738
- peopleExtraction = pass3bAttempt.result;
2739
- }
2740
- currentStep++;
2741
- onProgress?.({ phase: "pass3b", segment: 0, totalSegments: 1, status: "done", currentStep, totalSteps });
2742
- onPassComplete?.(pass3bKey, peopleExtraction);
2743
- if (peopleExtraction !== null) passesRun.push("pass3b");
2656
+ peopleExtraction = pass3bAttempt.result;
2744
2657
  }
2658
+ currentStep++;
2659
+ onProgress?.({ phase: "pass3b", segment: 0, totalSegments: 1, status: "done", currentStep, totalSteps });
2660
+ if (peopleExtraction !== null) passesRun.push("pass3b");
2745
2661
  }
2746
2662
  let codeReconstruction = null;
2747
2663
  let uncertainCodeFiles;
2748
2664
  if (strategy.passes.includes("code")) {
2749
- const pass3aKey = "pass3a";
2750
- if (preloadedResults != null && pass3aKey in preloadedResults) {
2751
- codeReconstruction = preloadedResults[pass3aKey];
2752
- currentStep += 3;
2753
- onProgress?.({ phase: "pass3a", segment: 2, totalSegments: 3, status: "done", currentStep, totalSteps });
2754
- onPassComplete?.(pass3aKey, codeReconstruction);
2755
- if (codeReconstruction !== null) passesRun.push("pass3a");
2665
+ const consensusConfig = { runs: 3, minAgreement: 2 };
2666
+ const consensusResult = await runCodeConsensus({
2667
+ config: consensusConfig,
2668
+ runFn: () => rateLimiter.execute(
2669
+ () => runCodeReconstruction({
2670
+ client,
2671
+ fileUri,
2672
+ mimeType,
2673
+ duration,
2674
+ model: MODELS.pro,
2675
+ resolution,
2676
+ pass1Results,
2677
+ pass2Results,
2678
+ lang
2679
+ }),
2680
+ { onWait }
2681
+ ),
2682
+ pass2Results,
2683
+ onProgress: (run3, total) => {
2684
+ onProgress?.({ phase: "pass3a", segment: run3 - 1, totalSegments: total, status: "running", totalSteps });
2685
+ currentStep++;
2686
+ onProgress?.({ phase: "pass3a", segment: run3 - 1, totalSegments: total, status: "done", currentStep, totalSteps });
2687
+ }
2688
+ });
2689
+ if (consensusResult.runsCompleted === 0) {
2690
+ const errMsg = "pass3a: all consensus runs failed";
2691
+ log4.warn(errMsg);
2692
+ errors.push(errMsg);
2756
2693
  } else {
2757
- const consensusConfig = { runs: 3, minAgreement: 2 };
2758
- const consensusResult = await runCodeConsensus({
2759
- config: consensusConfig,
2760
- runFn: () => rateLimiter.execute(
2761
- () => runCodeReconstruction({
2762
- client,
2763
- fileUri,
2764
- mimeType,
2765
- duration,
2766
- model: MODELS.pro,
2767
- resolution,
2768
- pass1Results,
2769
- pass2Results,
2770
- lang
2771
- }),
2772
- { onWait }
2773
- ),
2774
- pass2Results,
2775
- onProgress: (run3, total) => {
2776
- onProgress?.({ phase: "pass3a", segment: run3 - 1, totalSegments: total, status: "running", totalSteps });
2777
- currentStep++;
2778
- onProgress?.({ phase: "pass3a", segment: run3 - 1, totalSegments: total, status: "done", currentStep, totalSteps });
2779
- }
2694
+ const validationResult = validateCodeReconstruction({
2695
+ consensusResult,
2696
+ pass2Results
2780
2697
  });
2781
- if (consensusResult.runsCompleted === 0) {
2782
- const errMsg = "pass3a: all consensus runs failed";
2783
- log5.warn(errMsg);
2784
- errors.push(errMsg);
2785
- } else {
2786
- const validationResult = validateCodeReconstruction({
2787
- consensusResult,
2788
- pass2Results
2789
- });
2790
- const allFiles = [...validationResult.confirmed, ...validationResult.uncertain];
2791
- if (allFiles.length > 0) {
2792
- codeReconstruction = {
2793
- files: allFiles,
2794
- dependencies_mentioned: consensusResult.mergedDependencies,
2795
- build_commands: consensusResult.mergedBuildCommands
2796
- };
2797
- uncertainCodeFiles = validationResult.uncertain.map((f) => f.filename);
2798
- }
2698
+ const allFiles = [...validationResult.confirmed, ...validationResult.uncertain];
2699
+ if (allFiles.length > 0) {
2700
+ codeReconstruction = {
2701
+ files: allFiles,
2702
+ dependencies_mentioned: consensusResult.mergedDependencies,
2703
+ build_commands: consensusResult.mergedBuildCommands
2704
+ };
2705
+ uncertainCodeFiles = validationResult.uncertain.map((f) => f.filename);
2799
2706
  }
2800
- onPassComplete?.(pass3aKey, codeReconstruction);
2801
- if (codeReconstruction !== null) passesRun.push("pass3a");
2802
2707
  }
2708
+ if (codeReconstruction !== null) passesRun.push("pass3a");
2803
2709
  }
2804
2710
  let synthesisResult;
2805
2711
  if (strategy.passes.includes("synthesis")) {
2806
2712
  onProgress?.({ phase: "synthesis", segment: 0, totalSegments: 1, status: "running", totalSteps });
2807
- const synthKey = "synthesis";
2808
- if (preloadedResults != null && synthKey in preloadedResults) {
2809
- synthesisResult = preloadedResults[synthKey];
2810
- currentStep++;
2811
- onProgress?.({ phase: "synthesis", segment: 0, totalSegments: 1, status: "done", currentStep, totalSteps });
2812
- onPassComplete?.(synthKey, synthesisResult);
2813
- if (synthesisResult !== void 0) passesRun.push("synthesis");
2713
+ const synthAttempt = await withRetry(
2714
+ () => rateLimiter.execute(
2715
+ () => runSynthesis({
2716
+ client,
2717
+ model: MODELS.pro,
2718
+ segmentResults: results,
2719
+ videoProfile,
2720
+ peopleExtraction,
2721
+ codeReconstruction,
2722
+ context: config.context,
2723
+ lang
2724
+ }),
2725
+ { onWait }
2726
+ ),
2727
+ "synthesis"
2728
+ );
2729
+ if (synthAttempt.error !== null) {
2730
+ log4.warn(synthAttempt.error);
2731
+ errors.push(synthAttempt.error);
2814
2732
  } else {
2815
- const synthAttempt = await withRetry(
2816
- () => rateLimiter.execute(
2817
- () => runSynthesis({
2818
- client,
2819
- model: MODELS.pro,
2820
- segmentResults: results,
2821
- videoProfile,
2822
- peopleExtraction,
2823
- codeReconstruction,
2824
- context: config.context,
2825
- lang
2826
- }),
2827
- { onWait }
2828
- ),
2829
- "synthesis"
2830
- );
2831
- if (synthAttempt.error !== null) {
2832
- log5.warn(synthAttempt.error);
2833
- errors.push(synthAttempt.error);
2834
- } else {
2835
- synthesisResult = synthAttempt.result ?? void 0;
2836
- }
2837
- currentStep++;
2838
- onProgress?.({ phase: "synthesis", segment: 0, totalSegments: 1, status: "done", currentStep, totalSteps });
2839
- onPassComplete?.(synthKey, synthesisResult ?? null);
2840
- if (synthesisResult !== void 0) passesRun.push("synthesis");
2733
+ synthesisResult = synthAttempt.result ?? void 0;
2841
2734
  }
2735
+ currentStep++;
2736
+ onProgress?.({ phase: "synthesis", segment: 0, totalSegments: 1, status: "done", currentStep, totalSteps });
2737
+ if (synthesisResult !== void 0) passesRun.push("synthesis");
2842
2738
  }
2843
2739
  return {
2844
2740
  segments: results,
@@ -4543,7 +4439,7 @@ async function reRenderWithSpeakerMapping(params) {
4543
4439
  }
4544
4440
 
4545
4441
  // src/core/shutdown.ts
4546
- import { log as log6 } from "@clack/prompts";
4442
+ import { log as log5 } from "@clack/prompts";
4547
4443
  function createShutdownHandler(params) {
4548
4444
  const { client, uploadedFileNames } = params;
4549
4445
  let shuttingDown = false;
@@ -4559,10 +4455,10 @@ function createShutdownHandler(params) {
4559
4455
  }
4560
4456
  shuttingDown = true;
4561
4457
  if (hasProgress) {
4562
- log6.warn(`Interrupted \u2014 progress saved (${progressCurrentStep}/${progressTotalSteps} steps)`);
4563
- log6.info(`Resume: vidistill ${params.source} -o ${params.outputDir}/`);
4458
+ log5.warn(`Interrupted \u2014 progress saved (${progressCurrentStep}/${progressTotalSteps} steps)`);
4459
+ log5.info(`Resume: vidistill ${params.source} -o ${params.outputDir}/`);
4564
4460
  } else {
4565
- log6.warn("Interrupted");
4461
+ log5.warn("Interrupted");
4566
4462
  }
4567
4463
  const forceExitHandler = () => {
4568
4464
  process.exit(1);
@@ -4609,135 +4505,7 @@ function createShutdownHandler(params) {
4609
4505
  };
4610
4506
  }
4611
4507
 
4612
- // src/cli/speaker-naming.ts
4613
- import { log as log7, text as text2, confirm as confirm2, isCancel as isCancel2, cancel as cancel2 } from "@clack/prompts";
4614
- function buildSpeakerContext(pipelineResult) {
4615
- const { segments, peopleExtraction } = pipelineResult;
4616
- const speakerMap = /* @__PURE__ */ new Map();
4617
- for (const seg of segments) {
4618
- if (seg.pass1 == null) continue;
4619
- for (const info of seg.pass1.speaker_summary) {
4620
- if (!info.speaker_id) continue;
4621
- const existing = speakerMap.get(info.speaker_id);
4622
- if (existing == null) {
4623
- speakerMap.set(info.speaker_id, { totalEntries: 0, descriptions: [] });
4624
- }
4625
- const entry = speakerMap.get(info.speaker_id);
4626
- const entryCount = seg.pass1.transcript_entries.filter(
4627
- (e) => e.speaker === info.speaker_id
4628
- ).length;
4629
- entry.totalEntries += entryCount;
4630
- if (info.description && info.description.trim().length > 0) {
4631
- entry.descriptions.push(info.description.trim());
4632
- }
4633
- }
4634
- }
4635
- const participantsByLabel = /* @__PURE__ */ new Map();
4636
- if (peopleExtraction?.participants != null) {
4637
- for (const p of peopleExtraction.participants) {
4638
- if (/^SPEAKER_\d+$/.test(p.name)) {
4639
- participantsByLabel.set(p.name, p);
4640
- }
4641
- }
4642
- }
4643
- const contexts = [];
4644
- for (const [label, data] of speakerMap.entries()) {
4645
- const participant = participantsByLabel.get(label);
4646
- let description = "";
4647
- if (participant != null) {
4648
- if (participant.contributions.length > 0) {
4649
- description = participant.contributions[0];
4650
- } else if (participant.role) {
4651
- description = participant.role;
4652
- }
4653
- }
4654
- if (!description && data.descriptions.length > 0) {
4655
- description = data.descriptions[0];
4656
- }
4657
- contexts.push({
4658
- label,
4659
- description,
4660
- speakingSeconds: data.totalEntries
4661
- // Using entry count as proxy
4662
- });
4663
- }
4664
- contexts.sort((a, b) => {
4665
- if (b.speakingSeconds !== a.speakingSeconds) return b.speakingSeconds - a.speakingSeconds;
4666
- return a.label.localeCompare(b.label);
4667
- });
4668
- return contexts;
4669
- }
4670
- async function detectAndPromptMerges(mapping) {
4671
- const byName = /* @__PURE__ */ new Map();
4672
- for (const [label, name] of Object.entries(mapping)) {
4673
- const existing = byName.get(name);
4674
- if (existing == null) {
4675
- byName.set(name, [label]);
4676
- } else {
4677
- existing.push(label);
4678
- }
4679
- }
4680
- const declinedMerges = [];
4681
- const resultMapping = { ...mapping };
4682
- for (const [name, labels] of byName.entries()) {
4683
- if (labels.length < 2) continue;
4684
- const sorted = [...labels].sort((a, b) => a.localeCompare(b));
4685
- const primary = sorted[0];
4686
- const secondaries = sorted.slice(1);
4687
- for (const secondary of secondaries) {
4688
- const answer = await confirm2({
4689
- message: `You assigned '${name}' to both ${primary} and ${secondary}. Merge ${secondary} into ${primary} (${name})?`
4690
- });
4691
- if (isCancel2(answer)) {
4692
- cancel2("Speaker naming cancelled.");
4693
- return null;
4694
- }
4695
- if (answer === true) {
4696
- } else {
4697
- declinedMerges.push([primary, secondary]);
4698
- }
4699
- }
4700
- }
4701
- return { mapping: resultMapping, declinedMerges };
4702
- }
4703
- async function promptForSpeakers(speakers) {
4704
- const mapping = {};
4705
- for (const speaker of speakers) {
4706
- const descPart = speaker.description ? ` \u2014 ${speaker.description}` : "";
4707
- const value = await text2({
4708
- message: `Name for ${speaker.label}${descPart} [${speaker.speakingSeconds} entries]:`,
4709
- placeholder: "Enter name or press Enter to skip"
4710
- });
4711
- if (isCancel2(value) || typeof value !== "string") {
4712
- cancel2("Speaker naming cancelled.");
4713
- return null;
4714
- }
4715
- const trimmed = value.trim();
4716
- if (trimmed.length > 0) {
4717
- mapping[speaker.label] = trimmed;
4718
- }
4719
- }
4720
- return mapping;
4721
- }
4722
- async function promptSpeakerNames(pipelineResult) {
4723
- try {
4724
- const allSpeakers = buildSpeakerContext(pipelineResult);
4725
- if (allSpeakers.length <= 1) {
4726
- return null;
4727
- }
4728
- log7.info(`${String(allSpeakers.length)} speakers detected. Enter names to personalize output (or press Enter to skip each).`);
4729
- const mapping = await promptForSpeakers(allSpeakers);
4730
- if (mapping == null) return null;
4731
- const mergeResult = await detectAndPromptMerges(mapping);
4732
- if (mergeResult == null) return null;
4733
- return mergeResult;
4734
- } catch {
4735
- return null;
4736
- }
4737
- }
4738
-
4739
4508
  // src/commands/distill.ts
4740
- var PROGRESS_SCHEMA_VERSION = 1;
4741
4509
  function peekIsAudio(filePath) {
4742
4510
  if (!existsSync3(filePath)) return false;
4743
4511
  try {
@@ -4765,62 +4533,7 @@ function peekIsAudio(filePath) {
4765
4533
  return false;
4766
4534
  }
4767
4535
  }
4768
- function parseSemver(version2) {
4769
- const parts = version2.split(".").map(Number);
4770
- return [parts[0] || 0, parts[1] || 0, parts[2] || 0];
4771
- }
4772
- async function readProgressFile(outputDir) {
4773
- const progressPath = join4(outputDir, "progress.json");
4774
- if (!existsSync3(progressPath)) return null;
4775
- try {
4776
- const raw = await readFile2(progressPath, "utf8");
4777
- const parsed = JSON.parse(raw);
4778
- if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
4779
- const obj = parsed;
4780
- if (typeof obj["schemaVersion"] !== "number" || typeof obj["vidistillVersion"] !== "string" || typeof obj["completedPasses"] !== "object") return null;
4781
- return parsed;
4782
- } catch {
4783
- return null;
4784
- }
4785
- }
4786
- async function writeProgressFile(outputDir, progressFile) {
4787
- await mkdir2(outputDir, { recursive: true });
4788
- const progressPath = join4(outputDir, "progress.json");
4789
- await writeFile2(progressPath, JSON.stringify(progressFile, null, 2), "utf8");
4790
- }
4791
- async function validateCompletedPasses(outputDir, completedPasses) {
4792
- const invalid = [];
4793
- for (const [passKey, rawFile] of Object.entries(completedPasses)) {
4794
- const rawPath = join4(outputDir, "raw", `${rawFile}.json`);
4795
- if (!existsSync3(rawPath)) {
4796
- invalid.push(passKey);
4797
- continue;
4798
- }
4799
- try {
4800
- const content = await readFile2(rawPath, "utf8");
4801
- JSON.parse(content);
4802
- } catch {
4803
- invalid.push(passKey);
4804
- }
4805
- }
4806
- return invalid;
4807
- }
4808
- async function loadPreloadedResults(outputDir, completedPasses) {
4809
- const preloaded = {};
4810
- for (const [passKey, rawFile] of Object.entries(completedPasses)) {
4811
- const rawPath = join4(outputDir, "raw", `${rawFile}.json`);
4812
- try {
4813
- const content = await readFile2(rawPath, "utf8");
4814
- const parsed = JSON.parse(content);
4815
- if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) continue;
4816
- preloaded[passKey] = parsed;
4817
- } catch {
4818
- }
4819
- }
4820
- return preloaded;
4821
- }
4822
4536
  async function clearDirectory(dir) {
4823
- const { readdir } = await import("fs/promises");
4824
4537
  const entries = await readdir(dir);
4825
4538
  await Promise.all(
4826
4539
  entries.map((entry) => rm(join4(dir, entry), { recursive: true, force: true }))
@@ -4855,7 +4568,7 @@ async function runDistill(args) {
4855
4568
  context = await promptContext();
4856
4569
  break;
4857
4570
  case "cancel":
4858
- cancel3("Cancelled.");
4571
+ cancel2("Cancelled.");
4859
4572
  process.exit(0);
4860
4573
  }
4861
4574
  }
@@ -4897,69 +4610,8 @@ async function runDistill(args) {
4897
4610
  const outputDir = resolve(args.output);
4898
4611
  const slug = slugify(videoTitle);
4899
4612
  const finalOutputDir = `${outputDir}/${slug}`;
4900
- let preloadedResults;
4901
- let overrideStrategy;
4902
- const progressFile = await readProgressFile(finalOutputDir);
4903
- if (progressFile != null) {
4904
- if (progressFile.schemaVersion > PROGRESS_SCHEMA_VERSION) {
4905
- log8.error(
4906
- `This progress file was created by a newer version of vidistill. Please upgrade. (file schemaVersion: ${String(progressFile.schemaVersion)}, current: ${String(PROGRESS_SCHEMA_VERSION)})`
4907
- );
4908
- process.exit(1);
4909
- }
4910
- const currentVer = parseSemver("0.4.2");
4911
- const fileVer = parseSemver(progressFile.vidistillVersion);
4912
- const sameMajorMinor = currentVer[0] === fileVer[0] && currentVer[1] === fileVer[1];
4913
- let versionWarning = null;
4914
- if (!sameMajorMinor) {
4915
- versionWarning = `Progress file was created by v${progressFile.vidistillVersion}, current is v${"0.4.2"}.`;
4916
- }
4917
- const invalidPasses = await validateCompletedPasses(finalOutputDir, progressFile.completedPasses);
4918
- if (invalidPasses.length > 0) {
4919
- log8.warn(`Some completed pass files are missing or corrupt: ${invalidPasses.join(", ")}`);
4920
- }
4921
- const validPasses = Object.fromEntries(
4922
- Object.entries(progressFile.completedPasses).filter(([key]) => !invalidPasses.includes(key))
4923
- );
4924
- const completedCount = Object.keys(validPasses).length;
4925
- if (versionWarning != null) {
4926
- log8.warn(versionWarning);
4927
- }
4928
- if (completedCount === 0) {
4929
- log8.info("No valid passes to resume from. Starting fresh.");
4930
- if (existsSync3(finalOutputDir)) {
4931
- await clearDirectory(finalOutputDir);
4932
- }
4933
- } else {
4934
- const resumeChoice = await select2({
4935
- message: `Found incomplete run (${String(completedCount)} passes done). Resume?`,
4936
- options: [
4937
- { value: "resume", label: "Resume" },
4938
- { value: "fresh", label: "Fresh start" },
4939
- { value: "cancel", label: "Cancel" }
4940
- ]
4941
- });
4942
- if (isCancel3(resumeChoice)) {
4943
- cancel3("Cancelled.");
4944
- process.exit(0);
4945
- }
4946
- if (resumeChoice === "cancel") {
4947
- cancel3("Cancelled.");
4948
- process.exit(0);
4949
- } else if (resumeChoice === "fresh") {
4950
- if (existsSync3(finalOutputDir)) {
4951
- await clearDirectory(finalOutputDir);
4952
- }
4953
- } else {
4954
- preloadedResults = await loadPreloadedResults(finalOutputDir, validPasses);
4955
- if (progressFile.strategy != null) {
4956
- overrideStrategy = progressFile.strategy;
4957
- if (progressFile.videoProfile != null && !("pass0-scene" in preloadedResults)) {
4958
- preloadedResults["pass0-scene"] = progressFile.videoProfile;
4959
- }
4960
- }
4961
- }
4962
- }
4613
+ if (existsSync3(finalOutputDir)) {
4614
+ await clearDirectory(finalOutputDir);
4963
4615
  }
4964
4616
  const shutdownHandler = createShutdownHandler({
4965
4617
  client,
@@ -4973,13 +4625,6 @@ async function runDistill(args) {
4973
4625
  shutdownHandler.register();
4974
4626
  const rateLimiter = new RateLimiter();
4975
4627
  const progress2 = createProgressDisplay();
4976
- const progressState = {
4977
- schemaVersion: PROGRESS_SCHEMA_VERSION,
4978
- vidistillVersion: "0.4.2",
4979
- completedPasses: progressFile != null && preloadedResults != null ? { ...progressFile.completedPasses } : {},
4980
- videoProfile: progressFile?.videoProfile,
4981
- strategy: progressFile?.strategy
4982
- };
4983
4628
  const startTime = Date.now();
4984
4629
  const pipelineResult = await runPipeline({
4985
4630
  client,
@@ -4997,19 +4642,7 @@ async function runDistill(args) {
4997
4642
  }
4998
4643
  },
4999
4644
  onWait: (delayMs) => progress2.onWait(delayMs),
5000
- isShuttingDown: () => shutdownHandler.isShuttingDown(),
5001
- preloadedResults,
5002
- overrideStrategy,
5003
- onPassComplete: (passKey, result) => {
5004
- if (result == null) return;
5005
- progressState.completedPasses[passKey] = passKey;
5006
- if (passKey === "pass0-scene") {
5007
- progressState.videoProfile = result;
5008
- progressState.strategy = determineStrategy(result);
5009
- }
5010
- writeProgressFile(finalOutputDir, progressState).catch(() => {
5011
- });
5012
- }
4645
+ isShuttingDown: () => shutdownHandler.isShuttingDown()
5013
4646
  });
5014
4647
  const elapsedMs = Date.now() - startTime;
5015
4648
  shutdownHandler.deregister();
@@ -5026,43 +4659,71 @@ async function runDistill(args) {
5026
4659
  model,
5027
4660
  processingTimeMs: elapsedMs
5028
4661
  });
5029
- const namingResult = await promptSpeakerNames(pipelineResult);
5030
- if (namingResult != null && Object.keys(namingResult.mapping).length > 0) {
5031
- await reRenderWithSpeakerMapping({
5032
- outputDir: outputResult.outputDir,
5033
- speakerMapping: namingResult.mapping,
5034
- declinedMerges: namingResult.declinedMerges.length > 0 ? namingResult.declinedMerges : void 0
5035
- });
5036
- }
5037
4662
  const elapsedSecs = Math.round(elapsedMs / 1e3);
5038
4663
  const elapsedMins = Math.floor(elapsedSecs / 60);
5039
4664
  const remainSecs = elapsedSecs % 60;
5040
4665
  const elapsed = elapsedMins > 0 ? `${elapsedMins}m ${remainSecs}s` : `${remainSecs}s`;
5041
- log8.success(`Done in ${elapsed}`);
5042
- log8.info(`Output: ${finalOutputDir}/`);
5043
- log8.info(pc3.dim("Open guide.md for an overview"));
5044
- if (pipelineResult.codeReconstruction != null) {
5045
- log8.info(pc3.dim("Tip: vidistill extract code <input> for code-only extraction next time"));
5046
- } else if (pipelineResult.peopleExtraction?.participants != null && pipelineResult.peopleExtraction.participants.length > 1) {
5047
- log8.info(pc3.dim("Tip: vidistill rename-speakers <dir> to assign real names"));
4666
+ log6.success(`Done in ${elapsed}`);
4667
+ log6.info(`Output: ${finalOutputDir}/`);
4668
+ log6.info(pc3.dim("Open guide.md for an overview"));
4669
+ if (pipelineResult.peopleExtraction?.participants != null && pipelineResult.peopleExtraction.participants.length > 1) {
4670
+ log6.info(pc3.dim("Tip: vidistill rename-speakers <dir> to assign real names"));
5048
4671
  }
5049
4672
  if (outputResult.errors.length > 0) {
5050
- log8.warn(`Output errors: ${pc3.yellow(String(outputResult.errors.length))}`);
4673
+ log6.warn(`Output errors: ${pc3.yellow(String(outputResult.errors.length))}`);
5051
4674
  for (const err of outputResult.errors) {
5052
- log8.warn(pc3.dim(` ${err}`));
4675
+ log6.warn(pc3.dim(` ${err}`));
5053
4676
  }
5054
4677
  }
5055
4678
  }
5056
4679
 
5057
4680
  // src/commands/mcp.ts
5058
- import { log as log9 } from "@clack/prompts";
4681
+ import { log as log7 } from "@clack/prompts";
5059
4682
  async function run(_args) {
5060
- log9.info("Not implemented yet.");
4683
+ log7.info("Not implemented yet.");
5061
4684
  }
5062
4685
 
5063
4686
  // src/commands/rename-speakers.ts
5064
4687
  import { join as join5 } from "path";
5065
- import { log as log10, text as text3, isCancel as isCancel4, cancel as cancel4 } from "@clack/prompts";
4688
+ import { log as log9, text as text3, isCancel as isCancel3, cancel as cancel4 } from "@clack/prompts";
4689
+
4690
+ // src/cli/speaker-naming.ts
4691
+ import { log as log8, text as text2, confirm as confirm2, isCancel as isCancel2, cancel as cancel3 } from "@clack/prompts";
4692
+ async function detectAndPromptMerges(mapping) {
4693
+ const byName = /* @__PURE__ */ new Map();
4694
+ for (const [label, name] of Object.entries(mapping)) {
4695
+ const existing = byName.get(name);
4696
+ if (existing == null) {
4697
+ byName.set(name, [label]);
4698
+ } else {
4699
+ existing.push(label);
4700
+ }
4701
+ }
4702
+ const declinedMerges = [];
4703
+ const resultMapping = { ...mapping };
4704
+ for (const [name, labels] of byName.entries()) {
4705
+ if (labels.length < 2) continue;
4706
+ const sorted = [...labels].sort((a, b) => a.localeCompare(b));
4707
+ const primary = sorted[0];
4708
+ const secondaries = sorted.slice(1);
4709
+ for (const secondary of secondaries) {
4710
+ const answer = await confirm2({
4711
+ message: `You assigned '${name}' to both ${primary} and ${secondary}. Merge ${secondary} into ${primary} (${name})?`
4712
+ });
4713
+ if (isCancel2(answer)) {
4714
+ cancel3("Speaker naming cancelled.");
4715
+ return null;
4716
+ }
4717
+ if (answer === true) {
4718
+ } else {
4719
+ declinedMerges.push([primary, secondary]);
4720
+ }
4721
+ }
4722
+ }
4723
+ return { mapping: resultMapping, declinedMerges };
4724
+ }
4725
+
4726
+ // src/commands/rename-speakers.ts
5066
4727
  async function collectSpeakersFromRaw(rawDir) {
5067
4728
  const speakerEntries = /* @__PURE__ */ new Map();
5068
4729
  for (let n = 0; n < 1e3; n++) {
@@ -5186,19 +4847,19 @@ async function runList(outputDir) {
5186
4847
  const metadataPath = join5(outputDir, "metadata.json");
5187
4848
  const metadata = await readJsonFile(metadataPath);
5188
4849
  if (metadata == null) {
5189
- log10.error("Not a vidistill output directory");
4850
+ log9.error("Not a vidistill output directory");
5190
4851
  return;
5191
4852
  }
5192
4853
  const rawDir = join5(outputDir, "raw");
5193
4854
  const speakers = await collectSpeakersFromRaw(rawDir);
5194
4855
  const speakerMapping = metadata.speakerMapping ?? {};
5195
4856
  if (speakers.length === 0 && Object.keys(speakerMapping).length === 0) {
5196
- log10.info("No speakers found.");
4857
+ log9.info("No speakers found.");
5197
4858
  return;
5198
4859
  }
5199
4860
  const groups = groupSpeakersByExistingMapping(speakers, speakerMapping);
5200
4861
  if (groups.length === 0) {
5201
- log10.info("No speakers found.");
4862
+ log9.info("No speakers found.");
5202
4863
  return;
5203
4864
  }
5204
4865
  const lines = groups.map((group, idx) => {
@@ -5207,17 +4868,17 @@ async function runList(outputDir) {
5207
4868
  const labelsStr = group.labels.join(", ");
5208
4869
  return `${String(num)}. ${displayName} (${labelsStr}, ${String(group.totalEntries)} entries)`;
5209
4870
  });
5210
- log10.info(lines.join("\n"));
4871
+ log9.info(lines.join("\n"));
5211
4872
  }
5212
4873
  async function runRename(outputDir, oldName, newName) {
5213
4874
  if (newName.trim().length === 0) {
5214
- log10.error("New name cannot be empty. Use the interactive prompt to clear a mapping.");
4875
+ log9.error("New name cannot be empty. Use the interactive prompt to clear a mapping.");
5215
4876
  return;
5216
4877
  }
5217
4878
  const metadataPath = join5(outputDir, "metadata.json");
5218
4879
  const metadata = await readJsonFile(metadataPath);
5219
4880
  if (metadata == null) {
5220
- log10.error("Not a vidistill output directory");
4881
+ log9.error("Not a vidistill output directory");
5221
4882
  return;
5222
4883
  }
5223
4884
  const speakerMapping = { ...metadata.speakerMapping ?? {} };
@@ -5238,18 +4899,18 @@ async function runRename(outputDir, oldName, newName) {
5238
4899
  const currentNames = Object.values(speakerMapping);
5239
4900
  const unmappedLabels = speakers.filter((s) => speakerMapping[s.label] == null).map((s) => s.label);
5240
4901
  const allNames = [.../* @__PURE__ */ new Set([...currentNames, ...unmappedLabels])];
5241
- log10.error(`No speaker named "${oldName}" found. Current speakers: ${formatNameList(allNames)}`);
4902
+ log9.error(`No speaker named "${oldName}" found. Current speakers: ${formatNameList(allNames)}`);
5242
4903
  return;
5243
4904
  }
5244
4905
  if (matchingKeys.length > 1) {
5245
- log10.error(
4906
+ log9.error(
5246
4907
  `Multiple speakers named "${oldName}" (${matchingKeys.join(", ")}). Use SPEAKER_XX label to specify which one.`
5247
4908
  );
5248
4909
  return;
5249
4910
  }
5250
4911
  const key = matchingKeys[0];
5251
4912
  speakerMapping[key] = newName;
5252
- log10.info("Re-rendering output files with updated speaker names...");
4913
+ log9.info("Re-rendering output files with updated speaker names...");
5253
4914
  const result = await reRenderWithSpeakerMapping({
5254
4915
  outputDir,
5255
4916
  speakerMapping,
@@ -5257,16 +4918,16 @@ async function runRename(outputDir, oldName, newName) {
5257
4918
  });
5258
4919
  if (result.errors.length > 0) {
5259
4920
  for (const err of result.errors) {
5260
- log10.error(err);
4921
+ log9.error(err);
5261
4922
  }
5262
4923
  }
5263
- log10.info(`Done. ${String(result.filesGenerated.length)} file${result.filesGenerated.length === 1 ? "" : "s"} updated.`);
4924
+ log9.info(`Done. ${String(result.filesGenerated.length)} file${result.filesGenerated.length === 1 ? "" : "s"} updated.`);
5264
4925
  }
5265
4926
  async function runMerge(outputDir, sourceName, targetName) {
5266
4927
  const metadataPath = join5(outputDir, "metadata.json");
5267
4928
  const metadata = await readJsonFile(metadataPath);
5268
4929
  if (metadata == null) {
5269
- log10.error("Not a vidistill output directory");
4930
+ log9.error("Not a vidistill output directory");
5270
4931
  return;
5271
4932
  }
5272
4933
  const speakerMapping = { ...metadata.speakerMapping ?? {} };
@@ -5295,19 +4956,19 @@ async function runMerge(outputDir, sourceName, targetName) {
5295
4956
  const targetKeys = findKeys(targetName);
5296
4957
  if (sourceKeys.length === 0) {
5297
4958
  const currentNames = buildCurrentNames(speakers, speakerMapping);
5298
- log10.error(`No speaker named "${sourceName}" found. Current speakers: ${formatNameList(currentNames)}`);
4959
+ log9.error(`No speaker named "${sourceName}" found. Current speakers: ${formatNameList(currentNames)}`);
5299
4960
  return;
5300
4961
  }
5301
4962
  if (targetKeys.length === 0) {
5302
4963
  const currentNames = buildCurrentNames(speakers, speakerMapping);
5303
- log10.error(`No speaker named "${targetName}" found. Current speakers: ${formatNameList(currentNames)}`);
4964
+ log9.error(`No speaker named "${targetName}" found. Current speakers: ${formatNameList(currentNames)}`);
5304
4965
  return;
5305
4966
  }
5306
4967
  const resolvedTargetName = speakerMapping[targetKeys[0]] ?? targetName;
5307
4968
  for (const key of sourceKeys) {
5308
4969
  speakerMapping[key] = resolvedTargetName;
5309
4970
  }
5310
- log10.info("Re-rendering output files with updated speaker names...");
4971
+ log9.info("Re-rendering output files with updated speaker names...");
5311
4972
  const result = await reRenderWithSpeakerMapping({
5312
4973
  outputDir,
5313
4974
  speakerMapping,
@@ -5315,10 +4976,10 @@ async function runMerge(outputDir, sourceName, targetName) {
5315
4976
  });
5316
4977
  if (result.errors.length > 0) {
5317
4978
  for (const err of result.errors) {
5318
- log10.error(err);
4979
+ log9.error(err);
5319
4980
  }
5320
4981
  }
5321
- log10.info(`Done. ${String(result.filesGenerated.length)} file${result.filesGenerated.length === 1 ? "" : "s"} updated.`);
4982
+ log9.info(`Done. ${String(result.filesGenerated.length)} file${result.filesGenerated.length === 1 ? "" : "s"} updated.`);
5322
4983
  }
5323
4984
  function buildCurrentNames(speakers, speakerMapping) {
5324
4985
  const names = /* @__PURE__ */ new Set();
@@ -5335,11 +4996,11 @@ function buildCurrentNames(speakers, speakerMapping) {
5335
4996
  async function run2(args) {
5336
4997
  const { outputDir, list, rename, merge, error } = parseArgs(args);
5337
4998
  if (error != null) {
5338
- log10.error(error);
4999
+ log9.error(error);
5339
5000
  return;
5340
5001
  }
5341
5002
  if (outputDir == null || outputDir.trim() === "") {
5342
- log10.error('Usage: vidistill rename-speakers <output-dir> [--list] [--rename "old" "new"] [--merge "source" "target"]');
5003
+ log9.error('Usage: vidistill rename-speakers <output-dir> [--list] [--rename "old" "new"] [--merge "source" "target"]');
5343
5004
  return;
5344
5005
  }
5345
5006
  if (list) {
@@ -5357,22 +5018,22 @@ async function run2(args) {
5357
5018
  const metadataPath = join5(outputDir, "metadata.json");
5358
5019
  const metadata = await readJsonFile(metadataPath);
5359
5020
  if (metadata == null) {
5360
- log10.error("Not a vidistill output directory");
5021
+ log9.error("Not a vidistill output directory");
5361
5022
  return;
5362
5023
  }
5363
5024
  const rawDir = join5(outputDir, "raw");
5364
5025
  const peopleExtraction = await readJsonFile(join5(rawDir, "pass3b-people.json"));
5365
5026
  if (peopleExtraction == null) {
5366
- log10.info("No speakers detected in this video");
5027
+ log9.info("No speakers detected in this video");
5367
5028
  return;
5368
5029
  }
5369
5030
  const speakers = await collectSpeakersFromRaw(rawDir);
5370
5031
  if (speakers.length === 0) {
5371
- log10.info("No speakers detected in this video");
5032
+ log9.info("No speakers detected in this video");
5372
5033
  return;
5373
5034
  }
5374
5035
  const existingMapping = metadata.speakerMapping ?? {};
5375
- log10.info(
5036
+ log9.info(
5376
5037
  `${String(speakers.length)} speaker${speakers.length === 1 ? "" : "s"} found. Enter names (or press Enter to keep current).`
5377
5038
  );
5378
5039
  const groups = groupSpeakersByExistingMapping(speakers, existingMapping);
@@ -5401,7 +5062,7 @@ async function run2(args) {
5401
5062
  placeholder: "Enter name or press Enter to keep current",
5402
5063
  defaultValue
5403
5064
  });
5404
- if (isCancel4(value) || typeof value !== "string") {
5065
+ if (isCancel3(value) || typeof value !== "string") {
5405
5066
  cancel4("Speaker naming cancelled.");
5406
5067
  return;
5407
5068
  }
@@ -5419,7 +5080,7 @@ async function run2(args) {
5419
5080
  return;
5420
5081
  }
5421
5082
  const { mapping: finalMapping, declinedMerges } = mergeResult;
5422
- log10.info("Re-rendering output files with updated speaker names...");
5083
+ log9.info("Re-rendering output files with updated speaker names...");
5423
5084
  const result = await reRenderWithSpeakerMapping({
5424
5085
  outputDir,
5425
5086
  speakerMapping: finalMapping,
@@ -5427,14 +5088,14 @@ async function run2(args) {
5427
5088
  });
5428
5089
  if (result.errors.length > 0) {
5429
5090
  for (const err of result.errors) {
5430
- log10.error(err);
5091
+ log9.error(err);
5431
5092
  }
5432
5093
  }
5433
- log10.info(`Done. ${String(result.filesGenerated.length)} file${result.filesGenerated.length === 1 ? "" : "s"} updated.`);
5094
+ log9.info(`Done. ${String(result.filesGenerated.length)} file${result.filesGenerated.length === 1 ? "" : "s"} updated.`);
5434
5095
  }
5435
5096
 
5436
5097
  // src/cli/index.ts
5437
- var version = "0.4.2";
5098
+ var version = "0.4.3";
5438
5099
  var DEFAULT_OUTPUT = "./vidistill-output/";
5439
5100
  var SUBCOMMANDS = {
5440
5101
  mcp: run,
@@ -5487,11 +5148,11 @@ Commands: ${Object.keys(SUBCOMMANDS).join(", ")}`
5487
5148
  lang: args.lang
5488
5149
  });
5489
5150
  } catch (err) {
5490
- const { log: log11 } = await import("@clack/prompts");
5151
+ const { log: log10 } = await import("@clack/prompts");
5491
5152
  const { default: pc4 } = await import("picocolors");
5492
5153
  const raw = err instanceof Error ? err.message : String(err);
5493
5154
  const message = raw.split("\n")[0].slice(0, 200);
5494
- log11.error(pc4.red(message));
5155
+ log10.error(pc4.red(message));
5495
5156
  process.exit(1);
5496
5157
  }
5497
5158
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vidistill",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Video intelligence distiller — extract structured notes, transcripts, and insights from any video using Gemini",
5
5
  "type": "module",
6
6
  "license": "MIT",