vidistill 0.5.1 → 0.5.2

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 (3) hide show
  1. package/README.md +1 -3
  2. package/dist/index.js +188 -247
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -69,16 +69,14 @@ vidistill-output/my-video/
69
69
  ├── guide.md # overview and navigation
70
70
  ├── transcript.md # full timestamped transcript
71
71
  ├── combined.md # transcript + visual notes merged
72
- ├── notes.md # meeting/lecture notes
72
+ ├── notes.md # notes, implicit questions/decisions, recurring themes
73
73
  ├── code/ # extracted and reconstructed source files
74
74
  │ ├── *.ext # individual source files
75
75
  │ └── code-timeline.md # code evolution timeline
76
76
  ├── people.md # speakers and participants
77
77
  ├── chat.md # chat messages and links
78
78
  ├── action-items.md # tasks and follow-ups
79
- ├── insights.md # implicit signals and analysis
80
79
  ├── links.md # all URLs mentioned
81
- ├── prereqs.md # prerequisite knowledge (when detected)
82
80
  ├── timeline.html # interactive visual timeline
83
81
  ├── metadata.json # processing metadata
84
82
  └── raw/ # raw pass outputs
package/dist/index.js CHANGED
@@ -263,6 +263,7 @@ function showConfigBox(config) {
263
263
  const lines = [
264
264
  `Video: ${config.input}`,
265
265
  `Context: ${config.context ?? "(none)"}`,
266
+ `Name: ${config.outputName ?? "(auto-detect)"}`,
266
267
  `Output: ${config.output}`
267
268
  ];
268
269
  if (config.videoType === "audio") {
@@ -276,7 +277,7 @@ function showConfigBox(config) {
276
277
  }
277
278
 
278
279
  // src/commands/distill.ts
279
- import { log as log8, cancel as cancel2 } from "@clack/prompts";
280
+ import { log as log7, cancel as cancel2 } from "@clack/prompts";
280
281
  import pc3 from "picocolors";
281
282
  import { basename as basename3, extname as extname2, resolve, join as join4 } from "path";
282
283
  import { existsSync as existsSync3, openSync as openSync2, readSync as readSync2, closeSync as closeSync2 } from "fs";
@@ -312,6 +313,15 @@ async function promptContext() {
312
313
  const result = value.trim();
313
314
  return result.length > 0 ? result : void 0;
314
315
  }
316
+ async function promptOutputName() {
317
+ const value = await text({
318
+ message: "Output folder name",
319
+ placeholder: "(press Enter to auto-detect from source)"
320
+ });
321
+ handleCancel(value);
322
+ const result = value.trim();
323
+ return result.length > 0 ? result : void 0;
324
+ }
315
325
  async function promptApiKey() {
316
326
  const value = await password({
317
327
  message: "Gemini API key",
@@ -339,6 +349,7 @@ async function promptConfirmation() {
339
349
  { value: "start", label: "Start processing" },
340
350
  { value: "edit-video", label: "Edit video source" },
341
351
  { value: "edit-context", label: "Edit context" },
352
+ { value: "edit-name", label: "Edit output name" },
342
353
  { value: "cancel", label: "Cancel" }
343
354
  ]
344
355
  });
@@ -952,7 +963,7 @@ async function detectDuration(source) {
952
963
  }
953
964
 
954
965
  // src/core/pipeline.ts
955
- import { log as log6 } from "@clack/prompts";
966
+ import { log as log5 } from "@clack/prompts";
956
967
 
957
968
  // src/gemini/schemas.ts
958
969
  import { Type } from "@google/genai";
@@ -1778,9 +1789,6 @@ function mergeTranscriptResults(pass1a, pass1b) {
1778
1789
  };
1779
1790
  }
1780
1791
 
1781
- // src/core/transcript-consensus.ts
1782
- import { log as log5 } from "@clack/prompts";
1783
-
1784
1792
  // src/core/consensus.ts
1785
1793
  function tokenize(content) {
1786
1794
  const tokens = content.match(/[\p{L}\p{N}_]+/gu) ?? [];
@@ -2103,9 +2111,7 @@ async function runDiarizationConsensus(params) {
2103
2111
  try {
2104
2112
  const result = await runFn();
2105
2113
  successfulRuns.push(result);
2106
- } catch (e) {
2107
- const msg = e instanceof Error ? e.message : String(e);
2108
- log5.warn(`diarization consensus run ${i + 1}/${runs} failed: ${msg}`);
2114
+ } catch {
2109
2115
  }
2110
2116
  onProgress?.(i + 1, runs);
2111
2117
  }
@@ -2156,9 +2162,7 @@ async function runTranscriptionConsensus(params) {
2156
2162
  try {
2157
2163
  const result = await runFn();
2158
2164
  successfulRuns.push(result);
2159
- } catch (e) {
2160
- const msg = e instanceof Error ? e.message : String(e);
2161
- log5.warn(`transcription consensus run ${i + 1}/${runs} failed: ${msg}`);
2165
+ } catch {
2162
2166
  }
2163
2167
  onProgress?.(i + 1, runs);
2164
2168
  }
@@ -3003,7 +3007,7 @@ async function runPipeline(config) {
3003
3007
  "pass0"
3004
3008
  );
3005
3009
  if (pass0Attempt.error !== null) {
3006
- log6.warn(pass0Attempt.error);
3010
+ log5.warn(pass0Attempt.error);
3007
3011
  errors.push(pass0Attempt.error);
3008
3012
  videoProfile = DEFAULT_PROFILE;
3009
3013
  } else {
@@ -3050,7 +3054,7 @@ async function runPipeline(config) {
3050
3054
  const pass1aResult = transcriptConsensusResult.result;
3051
3055
  if (pass1aResult === null) {
3052
3056
  const errMsg = `segment ${i} pass1a: all transcription consensus runs failed`;
3053
- log6.warn(errMsg);
3057
+ log5.warn(errMsg);
3054
3058
  errors.push(errMsg);
3055
3059
  }
3056
3060
  let pass1 = null;
@@ -3068,7 +3072,7 @@ async function runPipeline(config) {
3068
3072
  });
3069
3073
  if (pass1bResult === null) {
3070
3074
  const errMsg = `segment ${i} pass1b: all diarization consensus runs failed`;
3071
- log6.warn(errMsg);
3075
+ log5.warn(errMsg);
3072
3076
  errors.push(errMsg);
3073
3077
  pass1 = mergeTranscriptResults(pass1aResult, { speaker_assignments: [], speaker_summary: [] });
3074
3078
  } else {
@@ -3100,7 +3104,7 @@ async function runPipeline(config) {
3100
3104
  `segment ${i} pass2`
3101
3105
  );
3102
3106
  if (pass2Attempt.error !== null) {
3103
- log6.warn(pass2Attempt.error);
3107
+ log5.warn(pass2Attempt.error);
3104
3108
  errors.push(pass2Attempt.error);
3105
3109
  } else {
3106
3110
  pass2 = pass2Attempt.result;
@@ -3133,7 +3137,7 @@ async function runPipeline(config) {
3133
3137
  });
3134
3138
  if (linkConsensusResult.runsCompleted === 0) {
3135
3139
  const errMsg = `segment ${i} pass3c: all link consensus runs failed`;
3136
- log6.warn(errMsg);
3140
+ log5.warn(errMsg);
3137
3141
  errors.push(errMsg);
3138
3142
  pass3c = null;
3139
3143
  } else {
@@ -3162,7 +3166,7 @@ async function runPipeline(config) {
3162
3166
  `segment ${i} pass3d`
3163
3167
  );
3164
3168
  if (pass3dAttempt.error !== null) {
3165
- log6.warn(pass3dAttempt.error);
3169
+ log5.warn(pass3dAttempt.error);
3166
3170
  errors.push(pass3dAttempt.error);
3167
3171
  pass3d = null;
3168
3172
  } else {
@@ -3220,7 +3224,7 @@ async function runPipeline(config) {
3220
3224
  }
3221
3225
  } catch (e) {
3222
3226
  const msg = e instanceof Error ? e.message : String(e);
3223
- log6.warn(`speaker reconciliation failed, continuing with original labels: ${msg}`);
3227
+ log5.warn(`speaker reconciliation failed, continuing with original labels: ${msg}`);
3224
3228
  }
3225
3229
  let peopleExtraction = null;
3226
3230
  if (strategy.passes.includes("people")) {
@@ -3241,7 +3245,7 @@ async function runPipeline(config) {
3241
3245
  "pass3b"
3242
3246
  );
3243
3247
  if (pass3bAttempt.error !== null) {
3244
- log6.warn(pass3bAttempt.error);
3248
+ log5.warn(pass3bAttempt.error);
3245
3249
  errors.push(pass3bAttempt.error);
3246
3250
  } else {
3247
3251
  peopleExtraction = pass3bAttempt.result;
@@ -3279,7 +3283,7 @@ async function runPipeline(config) {
3279
3283
  });
3280
3284
  if (consensusResult.runsCompleted === 0) {
3281
3285
  const errMsg = "pass3a: all consensus runs failed";
3282
- log6.warn(errMsg);
3286
+ log5.warn(errMsg);
3283
3287
  errors.push(errMsg);
3284
3288
  } else {
3285
3289
  const validationResult = validateCodeReconstruction({
@@ -3318,7 +3322,7 @@ async function runPipeline(config) {
3318
3322
  "synthesis"
3319
3323
  );
3320
3324
  if (synthAttempt.error !== null) {
3321
- log6.warn(synthAttempt.error);
3325
+ log5.warn(synthAttempt.error);
3322
3326
  errors.push(synthAttempt.error);
3323
3327
  } else {
3324
3328
  synthesisResult = synthAttempt.result ?? void 0;
@@ -3389,6 +3393,40 @@ function renderProcessingDetails(pipelineResult, speakerMapping) {
3389
3393
  lines.push(`- **Segments processed:** ${pipelineResult.segments.length}`);
3390
3394
  return lines.join("\n");
3391
3395
  }
3396
+ var PREREQ_LEVEL_ORDER = ["advanced", "intermediate", "basic"];
3397
+ var PREREQ_LEVEL_LABELS = {
3398
+ advanced: "Advanced",
3399
+ intermediate: "Intermediate",
3400
+ basic: "Basic"
3401
+ };
3402
+ function renderPrerequisites(prerequisites) {
3403
+ if (prerequisites == null || prerequisites.length === 0) return "";
3404
+ const grouped = /* @__PURE__ */ new Map();
3405
+ for (const level of PREREQ_LEVEL_ORDER) {
3406
+ grouped.set(level, []);
3407
+ }
3408
+ for (const c of prerequisites) {
3409
+ const bucket = grouped.get(c.assumed_knowledge_level);
3410
+ if (bucket != null) {
3411
+ bucket.push(c);
3412
+ }
3413
+ }
3414
+ const lines = ["", "## Prerequisites", ""];
3415
+ for (const level of PREREQ_LEVEL_ORDER) {
3416
+ const concepts = grouped.get(level) ?? [];
3417
+ if (concepts.length === 0) continue;
3418
+ lines.push(`### ${PREREQ_LEVEL_LABELS[level]} Knowledge`, "");
3419
+ for (const c of concepts) {
3420
+ lines.push(`**${c.concept}**`);
3421
+ lines.push("");
3422
+ lines.push(c.brief_explanation);
3423
+ lines.push("");
3424
+ lines.push(`_First assumed at: ${c.timestamp_first_assumed}_`);
3425
+ lines.push("");
3426
+ }
3427
+ }
3428
+ return lines.join("\n");
3429
+ }
3392
3430
  function renderIncompletePasses(pipelineResult) {
3393
3431
  const { errors, interrupted } = pipelineResult;
3394
3432
  const hasErrors = errors.length > 0;
@@ -3434,7 +3472,7 @@ function writeGuide(params) {
3434
3472
  "## Suggestions",
3435
3473
  "",
3436
3474
  renderSuggestions(synthesisResult, speakerMapping),
3437
- "",
3475
+ renderPrerequisites(synthesisResult?.prerequisites),
3438
3476
  "## Processing Details",
3439
3477
  "",
3440
3478
  renderProcessingDetails(pipelineResult, speakerMapping),
@@ -3725,13 +3763,79 @@ function renderActionItems(items, speakerMapping) {
3725
3763
  lines.push("");
3726
3764
  return lines;
3727
3765
  }
3766
+ function collectImplicitQuestions(segments) {
3767
+ const questions = [];
3768
+ for (const seg of segments) {
3769
+ if (seg.pass3d != null) {
3770
+ questions.push(...seg.pass3d.questions_implicit);
3771
+ }
3772
+ }
3773
+ return questions;
3774
+ }
3775
+ function collectImplicitDecisions(segments) {
3776
+ const decisions = [];
3777
+ for (const seg of segments) {
3778
+ if (seg.pass3d != null) {
3779
+ decisions.push(...seg.pass3d.decisions_implicit);
3780
+ }
3781
+ }
3782
+ return decisions;
3783
+ }
3784
+ function collectEmphasisPatterns(segments) {
3785
+ const patterns = [];
3786
+ for (const seg of segments) {
3787
+ if (seg.pass3d != null) {
3788
+ patterns.push(...seg.pass3d.emphasis_patterns);
3789
+ }
3790
+ }
3791
+ return patterns;
3792
+ }
3793
+ function renderImplicitQuestions(questions, speakerMapping) {
3794
+ if (questions.length === 0) return [];
3795
+ const lines = ["## Implicit Questions", ""];
3796
+ for (const q of questions) {
3797
+ lines.push(`- ${replaceNamesInText(q, speakerMapping)}`);
3798
+ }
3799
+ lines.push("");
3800
+ return lines;
3801
+ }
3802
+ function renderImplicitDecisions(decisions, speakerMapping) {
3803
+ if (decisions.length === 0) return [];
3804
+ const lines = ["## Implicit Decisions", ""];
3805
+ for (const d of decisions) {
3806
+ lines.push(`- ${replaceNamesInText(d, speakerMapping)}`);
3807
+ }
3808
+ lines.push("");
3809
+ return lines;
3810
+ }
3811
+ function renderRecurringThemes(patterns, speakerMapping) {
3812
+ if (patterns.length === 0) return [];
3813
+ const sorted = [...patterns].sort((a, b) => b.times_mentioned - a.times_mentioned);
3814
+ const lines = ["## Recurring Themes", ""];
3815
+ for (const p of sorted) {
3816
+ const ts = p.timestamps.length > 0 ? ` _(${p.timestamps.join(", ")})_` : "";
3817
+ lines.push(`### ${p.concept} (\xD7${p.times_mentioned})${ts}`);
3818
+ lines.push("");
3819
+ if (p.significance.length > 0) {
3820
+ lines.push(replaceNamesInText(p.significance, speakerMapping));
3821
+ lines.push("");
3822
+ }
3823
+ }
3824
+ return lines;
3825
+ }
3728
3826
  function hasMeaningfulContent(s) {
3729
3827
  return s.key_concepts.length > 0 || s.key_decisions.length > 0 || s.topics.length > 0 || s.questions_raised.length > 0 || s.action_items.length > 0;
3730
3828
  }
3829
+ function hasPass3dContent(segments) {
3830
+ return segments.some(
3831
+ (s) => s.pass3d != null && (s.pass3d.questions_implicit.length > 0 || s.pass3d.decisions_implicit.length > 0 || s.pass3d.emphasis_patterns.length > 0)
3832
+ );
3833
+ }
3731
3834
  function writeNotes(params) {
3732
- const { synthesisResult, speakerMapping } = params;
3835
+ const { synthesisResult, segments, speakerMapping } = params;
3733
3836
  if (synthesisResult == null) return null;
3734
- if (!hasMeaningfulContent(synthesisResult)) return null;
3837
+ const hasPass3d = segments != null && hasPass3dContent(segments);
3838
+ if (!hasMeaningfulContent(synthesisResult) && !hasPass3d) return null;
3735
3839
  const sections = ["# Notes", ""];
3736
3840
  if (synthesisResult.overview.length > 0) {
3737
3841
  sections.push(replaceNamesInText(synthesisResult.overview, speakerMapping));
@@ -3742,6 +3846,11 @@ function writeNotes(params) {
3742
3846
  sections.push(...renderTopics(synthesisResult.topics, speakerMapping));
3743
3847
  sections.push(...renderQuestions(synthesisResult.questions_raised, speakerMapping));
3744
3848
  sections.push(...renderActionItems(synthesisResult.action_items, speakerMapping));
3849
+ if (segments != null) {
3850
+ sections.push(...renderImplicitQuestions(collectImplicitQuestions(segments), speakerMapping));
3851
+ sections.push(...renderImplicitDecisions(collectImplicitDecisions(segments), speakerMapping));
3852
+ sections.push(...renderRecurringThemes(collectEmphasisPatterns(segments), speakerMapping));
3853
+ }
3745
3854
  while (sections[sections.length - 1] === "") sections.pop();
3746
3855
  return sections.join("\n");
3747
3856
  }
@@ -4068,150 +4177,6 @@ function writeActionItems(params) {
4068
4177
  return sections.join("\n");
4069
4178
  }
4070
4179
 
4071
- // src/output/insights.ts
4072
- function collectEmotionalShifts(segments) {
4073
- const shifts = [];
4074
- for (const seg of segments) {
4075
- if (seg.pass3d != null) {
4076
- shifts.push(...seg.pass3d.emotional_shifts);
4077
- }
4078
- }
4079
- return shifts;
4080
- }
4081
- function collectEmphasisPatterns(segments) {
4082
- const patterns = [];
4083
- for (const seg of segments) {
4084
- if (seg.pass3d != null) {
4085
- patterns.push(...seg.pass3d.emphasis_patterns);
4086
- }
4087
- }
4088
- return patterns;
4089
- }
4090
- function collectImplicitQuestions(segments) {
4091
- const questions = [];
4092
- for (const seg of segments) {
4093
- if (seg.pass3d != null) {
4094
- questions.push(...seg.pass3d.questions_implicit);
4095
- }
4096
- }
4097
- return questions;
4098
- }
4099
- function collectImplicitDecisions(segments) {
4100
- const decisions = [];
4101
- for (const seg of segments) {
4102
- if (seg.pass3d != null) {
4103
- decisions.push(...seg.pass3d.decisions_implicit);
4104
- }
4105
- }
4106
- return decisions;
4107
- }
4108
- function renderEmotionalShifts(shifts, speakerMapping) {
4109
- if (shifts.length === 0) return [];
4110
- const lines = ["## Emotional Shifts", ""];
4111
- for (const s of shifts) {
4112
- lines.push(`- **[${s.timestamp}]** ${s.from_state} \u2192 ${s.to_state}`);
4113
- if (s.trigger.length > 0) {
4114
- lines.push(` _Trigger: ${replaceNamesInText(s.trigger, speakerMapping)}_`);
4115
- }
4116
- }
4117
- lines.push("");
4118
- return lines;
4119
- }
4120
- function renderEmphasisPatterns(patterns, speakerMapping) {
4121
- if (patterns.length === 0) return [];
4122
- const sorted = [...patterns].sort((a, b) => b.times_mentioned - a.times_mentioned);
4123
- const lines = ["## Emphasis Patterns", ""];
4124
- for (const p of sorted) {
4125
- const ts = p.timestamps.length > 0 ? ` _(${p.timestamps.join(", ")})_` : "";
4126
- lines.push(`### ${p.concept} (\xD7${p.times_mentioned})${ts}`);
4127
- lines.push("");
4128
- if (p.significance.length > 0) {
4129
- lines.push(replaceNamesInText(p.significance, speakerMapping));
4130
- lines.push("");
4131
- }
4132
- }
4133
- return lines;
4134
- }
4135
- function renderImplicitQuestions(questions, speakerMapping) {
4136
- if (questions.length === 0) return [];
4137
- const lines = ["## Implicit Questions", ""];
4138
- for (const q of questions) {
4139
- lines.push(`- ${replaceNamesInText(q, speakerMapping)}`);
4140
- }
4141
- lines.push("");
4142
- return lines;
4143
- }
4144
- function renderImplicitDecisions(decisions, speakerMapping) {
4145
- if (decisions.length === 0) return [];
4146
- const lines = ["## Implicit Decisions", ""];
4147
- for (const d of decisions) {
4148
- lines.push(`- ${replaceNamesInText(d, speakerMapping)}`);
4149
- }
4150
- lines.push("");
4151
- return lines;
4152
- }
4153
- function writeInsights(params) {
4154
- const { segments, speakerMapping } = params;
4155
- const hasPass3d = segments.some((s) => s.pass3d != null);
4156
- if (!hasPass3d) return null;
4157
- const emotionalShifts = collectEmotionalShifts(segments);
4158
- const emphasisPatterns = collectEmphasisPatterns(segments);
4159
- const implicitQuestions = collectImplicitQuestions(segments);
4160
- const implicitDecisions = collectImplicitDecisions(segments);
4161
- if (emotionalShifts.length === 0 && emphasisPatterns.length === 0 && implicitQuestions.length === 0 && implicitDecisions.length === 0) {
4162
- return null;
4163
- }
4164
- const sections = ["# Insights", ""];
4165
- sections.push(...renderEmotionalShifts(emotionalShifts, speakerMapping));
4166
- sections.push(...renderEmphasisPatterns(emphasisPatterns, speakerMapping));
4167
- sections.push(...renderImplicitQuestions(implicitQuestions, speakerMapping));
4168
- sections.push(...renderImplicitDecisions(implicitDecisions, speakerMapping));
4169
- while (sections[sections.length - 1] === "") sections.pop();
4170
- return sections.join("\n");
4171
- }
4172
-
4173
- // src/output/prereqs.ts
4174
- var LEVEL_ORDER = ["advanced", "intermediate", "basic"];
4175
- var LEVEL_LABELS = {
4176
- advanced: "Advanced",
4177
- intermediate: "Intermediate",
4178
- basic: "Basic"
4179
- };
4180
- function renderLevelSection(level, concepts) {
4181
- if (concepts.length === 0) return [];
4182
- const lines = [`## ${LEVEL_LABELS[level]} Knowledge`, ""];
4183
- for (const c of concepts) {
4184
- lines.push(`### ${c.concept}`);
4185
- lines.push("");
4186
- lines.push(c.brief_explanation);
4187
- lines.push("");
4188
- lines.push(`_First assumed at: ${c.timestamp_first_assumed}_`);
4189
- lines.push("");
4190
- }
4191
- return lines;
4192
- }
4193
- function writePrereqs(params) {
4194
- const { prerequisites } = params;
4195
- if (prerequisites == null || prerequisites.length === 0) return null;
4196
- const grouped = /* @__PURE__ */ new Map();
4197
- for (const level of LEVEL_ORDER) {
4198
- grouped.set(level, []);
4199
- }
4200
- for (const c of prerequisites) {
4201
- const bucket = grouped.get(c.assumed_knowledge_level);
4202
- if (bucket != null) {
4203
- bucket.push(c);
4204
- }
4205
- }
4206
- const sections = ["# Prerequisites", ""];
4207
- for (const level of LEVEL_ORDER) {
4208
- const concepts = grouped.get(level) ?? [];
4209
- sections.push(...renderLevelSection(level, concepts));
4210
- }
4211
- while (sections[sections.length - 1] === "") sections.pop();
4212
- return sections.join("\n");
4213
- }
4214
-
4215
4180
  // src/output/timeline.ts
4216
4181
  function toPercent(seconds, duration) {
4217
4182
  if (duration <= 0) return "0";
@@ -4701,13 +4666,9 @@ function resolveFilesToGenerate(params) {
4701
4666
  }
4702
4667
  if (hasPass3d) {
4703
4668
  optional.add("action-items.md");
4704
- optional.add("insights.md");
4705
4669
  }
4706
- if (synthesisResult != null) optional.add("notes.md");
4670
+ if (synthesisResult != null || hasPass3d) optional.add("notes.md");
4707
4671
  if (peopleExtraction != null) optional.add("people.md");
4708
- if (synthesisResult?.prerequisites != null && Array.isArray(synthesisResult.prerequisites) && synthesisResult.prerequisites.length > 0) {
4709
- optional.add("prereqs.md");
4710
- }
4711
4672
  const hasPass1 = segments.some((s) => s.pass1 != null);
4712
4673
  if (hasPass1 || hasPass2) optional.add("timeline.html");
4713
4674
  return optional;
@@ -4766,7 +4727,7 @@ async function generateOutput(params) {
4766
4727
  }
4767
4728
  if (filesToGenerate.has("notes.md")) {
4768
4729
  try {
4769
- const content = writeNotes({ synthesisResult: pipelineResult.synthesisResult, speakerMapping: expandedMapping });
4730
+ const content = writeNotes({ synthesisResult: pipelineResult.synthesisResult, segments: pipelineResult.segments, speakerMapping: expandedMapping });
4770
4731
  if (content != null) {
4771
4732
  await writeOutputFile("notes.md", content);
4772
4733
  }
@@ -4818,26 +4779,6 @@ async function generateOutput(params) {
4818
4779
  errors.push(`action-items.md: ${String(err)}`);
4819
4780
  }
4820
4781
  }
4821
- if (filesToGenerate.has("insights.md")) {
4822
- try {
4823
- const content = writeInsights({ segments: pipelineResult.segments, speakerMapping: expandedMapping });
4824
- if (content != null) {
4825
- await writeOutputFile("insights.md", content);
4826
- }
4827
- } catch (err) {
4828
- errors.push(`insights.md: ${String(err)}`);
4829
- }
4830
- }
4831
- if (filesToGenerate.has("prereqs.md")) {
4832
- try {
4833
- const content = writePrereqs({ prerequisites: pipelineResult.synthesisResult?.prerequisites });
4834
- if (content != null) {
4835
- await writeOutputFile("prereqs.md", content);
4836
- }
4837
- } catch (err) {
4838
- errors.push(`prereqs.md: ${String(err)}`);
4839
- }
4840
- }
4841
4782
  if (filesToGenerate.has("timeline.html")) {
4842
4783
  try {
4843
4784
  const content = generateTimeline({ pipelineResult, duration, speakerMapping: expandedMapping });
@@ -4954,7 +4895,7 @@ async function reRenderWithSpeakerMapping(params) {
4954
4895
  }
4955
4896
  if (filesToReRender.has("notes.md")) {
4956
4897
  try {
4957
- const content = writeNotes({ synthesisResult: pipelineResult.synthesisResult, speakerMapping: expandedMapping });
4898
+ const content = writeNotes({ synthesisResult: pipelineResult.synthesisResult, segments: pipelineResult.segments, speakerMapping: expandedMapping });
4958
4899
  if (content != null) await writeOutputFile("notes.md", content);
4959
4900
  } catch (err) {
4960
4901
  errors.push(`notes.md: ${String(err)}`);
@@ -4988,14 +4929,6 @@ async function reRenderWithSpeakerMapping(params) {
4988
4929
  errors.push(`action-items.md: ${String(err)}`);
4989
4930
  }
4990
4931
  }
4991
- if (filesToReRender.has("insights.md")) {
4992
- try {
4993
- const content = writeInsights({ segments: pipelineResult.segments, speakerMapping: expandedMapping });
4994
- if (content != null) await writeOutputFile("insights.md", content);
4995
- } catch (err) {
4996
- errors.push(`insights.md: ${String(err)}`);
4997
- }
4998
- }
4999
4932
  if (filesToReRender.has("timeline.html")) {
5000
4933
  try {
5001
4934
  const content = generateTimeline({ pipelineResult, duration, speakerMapping: expandedMapping });
@@ -5036,7 +4969,7 @@ async function reRenderWithSpeakerMapping(params) {
5036
4969
  }
5037
4970
 
5038
4971
  // src/core/shutdown.ts
5039
- import { log as log7 } from "@clack/prompts";
4972
+ import { log as log6 } from "@clack/prompts";
5040
4973
  function createShutdownHandler(params) {
5041
4974
  const { client, uploadedFileNames } = params;
5042
4975
  let shuttingDown = false;
@@ -5052,10 +4985,10 @@ function createShutdownHandler(params) {
5052
4985
  }
5053
4986
  shuttingDown = true;
5054
4987
  if (hasProgress) {
5055
- log7.warn(`Interrupted \u2014 progress saved (${progressCurrentStep}/${progressTotalSteps} steps)`);
5056
- log7.info(`Resume: vidistill ${params.source} -o ${params.outputDir}/`);
4988
+ log6.warn(`Interrupted \u2014 progress saved (${progressCurrentStep}/${progressTotalSteps} steps)`);
4989
+ log6.info(`Resume: vidistill ${params.source} -o ${params.outputDir}/`);
5057
4990
  } else {
5058
- log7.warn("Interrupted");
4991
+ log6.warn("Interrupted");
5059
4992
  }
5060
4993
  const forceExitHandler = () => {
5061
4994
  process.exit(1);
@@ -5140,6 +5073,7 @@ async function runDistill(args) {
5140
5073
  const apiKey = await resolveApiKey();
5141
5074
  let rawInput = args.input ?? await promptVideoSource();
5142
5075
  let context = args.context ?? await promptContext();
5076
+ let outputName = await promptOutputName();
5143
5077
  const allFlagsProvided = args.input != null && args.context != null;
5144
5078
  if (!allFlagsProvided) {
5145
5079
  let confirmed = false;
@@ -5150,6 +5084,7 @@ async function runDistill(args) {
5150
5084
  input: rawInput,
5151
5085
  context,
5152
5086
  output: args.output,
5087
+ outputName,
5153
5088
  videoType: inputIsAudio ? "audio" : void 0,
5154
5089
  lang: args.lang
5155
5090
  });
@@ -5164,6 +5099,9 @@ async function runDistill(args) {
5164
5099
  case "edit-context":
5165
5100
  context = await promptContext();
5166
5101
  break;
5102
+ case "edit-name":
5103
+ outputName = await promptOutputName();
5104
+ break;
5167
5105
  case "cancel":
5168
5106
  cancel2("Cancelled.");
5169
5107
  process.exit(0);
@@ -5188,7 +5126,7 @@ async function runDistill(args) {
5188
5126
  });
5189
5127
  } catch {
5190
5128
  duration = 600;
5191
- log8.warn("Could not detect video duration \u2014 defaulting to 10 minutes. Install yt-dlp for full-length processing: brew install yt-dlp");
5129
+ log7.warn("Could not detect video duration \u2014 defaulting to 10 minutes. Install yt-dlp for full-length processing: brew install yt-dlp");
5192
5130
  }
5193
5131
  if (result.uploadedFileName != null) {
5194
5132
  uploadedFileNames = [result.uploadedFileName];
@@ -5208,6 +5146,9 @@ async function runDistill(args) {
5208
5146
  }
5209
5147
  videoTitle = basename3(resolved.value, extname2(resolved.value));
5210
5148
  }
5149
+ if (outputName != null) {
5150
+ videoTitle = outputName;
5151
+ }
5211
5152
  const model = MODELS.flash;
5212
5153
  const outputDir = resolve(args.output);
5213
5154
  const slug = slugify(videoTitle);
@@ -5265,32 +5206,32 @@ async function runDistill(args) {
5265
5206
  const elapsedMins = Math.floor(elapsedSecs / 60);
5266
5207
  const remainSecs = elapsedSecs % 60;
5267
5208
  const elapsed = elapsedMins > 0 ? `${elapsedMins}m ${remainSecs}s` : `${remainSecs}s`;
5268
- log8.success(`Done in ${elapsed}`);
5269
- log8.info(`Output: ${finalOutputDir}/`);
5270
- log8.info(pc3.dim("Open guide.md for an overview"));
5209
+ log7.success(`Done in ${elapsed}`);
5210
+ log7.info(`Output: ${finalOutputDir}/`);
5211
+ log7.info(pc3.dim("Open guide.md for an overview"));
5271
5212
  if (pipelineResult.peopleExtraction?.participants != null && pipelineResult.peopleExtraction.participants.length > 1) {
5272
- log8.info(pc3.dim("Tip: vidistill rename-speakers <dir> to assign real names"));
5213
+ log7.info(pc3.dim("Tip: vidistill rename-speakers <dir> to assign real names"));
5273
5214
  }
5274
5215
  if (outputResult.errors.length > 0) {
5275
- log8.warn(`Output errors: ${pc3.yellow(String(outputResult.errors.length))}`);
5216
+ log7.warn(`Output errors: ${pc3.yellow(String(outputResult.errors.length))}`);
5276
5217
  for (const err of outputResult.errors) {
5277
- log8.warn(pc3.dim(` ${err}`));
5218
+ log7.warn(pc3.dim(` ${err}`));
5278
5219
  }
5279
5220
  }
5280
5221
  }
5281
5222
 
5282
5223
  // src/commands/mcp.ts
5283
- import { log as log9 } from "@clack/prompts";
5224
+ import { log as log8 } from "@clack/prompts";
5284
5225
  async function run(_args) {
5285
- log9.info("Not implemented yet.");
5226
+ log8.info("Not implemented yet.");
5286
5227
  }
5287
5228
 
5288
5229
  // src/commands/rename-speakers.ts
5289
5230
  import { join as join5 } from "path";
5290
- import { log as log11, text as text3, isCancel as isCancel3, cancel as cancel4 } from "@clack/prompts";
5231
+ import { log as log10, text as text3, isCancel as isCancel3, cancel as cancel4 } from "@clack/prompts";
5291
5232
 
5292
5233
  // src/cli/speaker-naming.ts
5293
- import { log as log10, text as text2, confirm as confirm2, isCancel as isCancel2, cancel as cancel3 } from "@clack/prompts";
5234
+ import { log as log9, text as text2, confirm as confirm2, isCancel as isCancel2, cancel as cancel3 } from "@clack/prompts";
5294
5235
  async function detectAndPromptMerges(mapping) {
5295
5236
  const byName = /* @__PURE__ */ new Map();
5296
5237
  for (const [label, name] of Object.entries(mapping)) {
@@ -5449,19 +5390,19 @@ async function runList(outputDir) {
5449
5390
  const metadataPath = join5(outputDir, "metadata.json");
5450
5391
  const metadata = await readJsonFile(metadataPath);
5451
5392
  if (metadata == null) {
5452
- log11.error("Not a vidistill output directory");
5393
+ log10.error("Not a vidistill output directory");
5453
5394
  return;
5454
5395
  }
5455
5396
  const rawDir = join5(outputDir, "raw");
5456
5397
  const speakers = await collectSpeakersFromRaw(rawDir);
5457
5398
  const speakerMapping = metadata.speakerMapping ?? {};
5458
5399
  if (speakers.length === 0 && Object.keys(speakerMapping).length === 0) {
5459
- log11.info("No speakers found.");
5400
+ log10.info("No speakers found.");
5460
5401
  return;
5461
5402
  }
5462
5403
  const groups = groupSpeakersByExistingMapping(speakers, speakerMapping);
5463
5404
  if (groups.length === 0) {
5464
- log11.info("No speakers found.");
5405
+ log10.info("No speakers found.");
5465
5406
  return;
5466
5407
  }
5467
5408
  const lines = groups.map((group, idx) => {
@@ -5470,17 +5411,17 @@ async function runList(outputDir) {
5470
5411
  const labelsStr = group.labels.join(", ");
5471
5412
  return `${String(num)}. ${displayName} (${labelsStr}, ${String(group.totalEntries)} entries)`;
5472
5413
  });
5473
- log11.info(lines.join("\n"));
5414
+ log10.info(lines.join("\n"));
5474
5415
  }
5475
5416
  async function runRename(outputDir, oldName, newName) {
5476
5417
  if (newName.trim().length === 0) {
5477
- log11.error("New name cannot be empty. Use the interactive prompt to clear a mapping.");
5418
+ log10.error("New name cannot be empty. Use the interactive prompt to clear a mapping.");
5478
5419
  return;
5479
5420
  }
5480
5421
  const metadataPath = join5(outputDir, "metadata.json");
5481
5422
  const metadata = await readJsonFile(metadataPath);
5482
5423
  if (metadata == null) {
5483
- log11.error("Not a vidistill output directory");
5424
+ log10.error("Not a vidistill output directory");
5484
5425
  return;
5485
5426
  }
5486
5427
  const speakerMapping = { ...metadata.speakerMapping ?? {} };
@@ -5501,18 +5442,18 @@ async function runRename(outputDir, oldName, newName) {
5501
5442
  const currentNames = Object.values(speakerMapping);
5502
5443
  const unmappedLabels = speakers.filter((s) => speakerMapping[s.label] == null).map((s) => s.label);
5503
5444
  const allNames = [.../* @__PURE__ */ new Set([...currentNames, ...unmappedLabels])];
5504
- log11.error(`No speaker named "${oldName}" found. Current speakers: ${formatNameList(allNames)}`);
5445
+ log10.error(`No speaker named "${oldName}" found. Current speakers: ${formatNameList(allNames)}`);
5505
5446
  return;
5506
5447
  }
5507
5448
  if (matchingKeys.length > 1) {
5508
- log11.error(
5449
+ log10.error(
5509
5450
  `Multiple speakers named "${oldName}" (${matchingKeys.join(", ")}). Use SPEAKER_XX label to specify which one.`
5510
5451
  );
5511
5452
  return;
5512
5453
  }
5513
5454
  const key = matchingKeys[0];
5514
5455
  speakerMapping[key] = newName;
5515
- log11.info("Re-rendering output files with updated speaker names...");
5456
+ log10.info("Re-rendering output files with updated speaker names...");
5516
5457
  const result = await reRenderWithSpeakerMapping({
5517
5458
  outputDir,
5518
5459
  speakerMapping,
@@ -5520,16 +5461,16 @@ async function runRename(outputDir, oldName, newName) {
5520
5461
  });
5521
5462
  if (result.errors.length > 0) {
5522
5463
  for (const err of result.errors) {
5523
- log11.error(err);
5464
+ log10.error(err);
5524
5465
  }
5525
5466
  }
5526
- log11.info(`Done. ${String(result.filesGenerated.length)} file${result.filesGenerated.length === 1 ? "" : "s"} updated.`);
5467
+ log10.info(`Done. ${String(result.filesGenerated.length)} file${result.filesGenerated.length === 1 ? "" : "s"} updated.`);
5527
5468
  }
5528
5469
  async function runMerge(outputDir, sourceName, targetName) {
5529
5470
  const metadataPath = join5(outputDir, "metadata.json");
5530
5471
  const metadata = await readJsonFile(metadataPath);
5531
5472
  if (metadata == null) {
5532
- log11.error("Not a vidistill output directory");
5473
+ log10.error("Not a vidistill output directory");
5533
5474
  return;
5534
5475
  }
5535
5476
  const speakerMapping = { ...metadata.speakerMapping ?? {} };
@@ -5558,19 +5499,19 @@ async function runMerge(outputDir, sourceName, targetName) {
5558
5499
  const targetKeys = findKeys(targetName);
5559
5500
  if (sourceKeys.length === 0) {
5560
5501
  const currentNames = buildCurrentNames(speakers, speakerMapping);
5561
- log11.error(`No speaker named "${sourceName}" found. Current speakers: ${formatNameList(currentNames)}`);
5502
+ log10.error(`No speaker named "${sourceName}" found. Current speakers: ${formatNameList(currentNames)}`);
5562
5503
  return;
5563
5504
  }
5564
5505
  if (targetKeys.length === 0) {
5565
5506
  const currentNames = buildCurrentNames(speakers, speakerMapping);
5566
- log11.error(`No speaker named "${targetName}" found. Current speakers: ${formatNameList(currentNames)}`);
5507
+ log10.error(`No speaker named "${targetName}" found. Current speakers: ${formatNameList(currentNames)}`);
5567
5508
  return;
5568
5509
  }
5569
5510
  const resolvedTargetName = speakerMapping[targetKeys[0]] ?? targetName;
5570
5511
  for (const key of sourceKeys) {
5571
5512
  speakerMapping[key] = resolvedTargetName;
5572
5513
  }
5573
- log11.info("Re-rendering output files with updated speaker names...");
5514
+ log10.info("Re-rendering output files with updated speaker names...");
5574
5515
  const result = await reRenderWithSpeakerMapping({
5575
5516
  outputDir,
5576
5517
  speakerMapping,
@@ -5578,10 +5519,10 @@ async function runMerge(outputDir, sourceName, targetName) {
5578
5519
  });
5579
5520
  if (result.errors.length > 0) {
5580
5521
  for (const err of result.errors) {
5581
- log11.error(err);
5522
+ log10.error(err);
5582
5523
  }
5583
5524
  }
5584
- log11.info(`Done. ${String(result.filesGenerated.length)} file${result.filesGenerated.length === 1 ? "" : "s"} updated.`);
5525
+ log10.info(`Done. ${String(result.filesGenerated.length)} file${result.filesGenerated.length === 1 ? "" : "s"} updated.`);
5585
5526
  }
5586
5527
  function buildCurrentNames(speakers, speakerMapping) {
5587
5528
  const names = /* @__PURE__ */ new Set();
@@ -5598,11 +5539,11 @@ function buildCurrentNames(speakers, speakerMapping) {
5598
5539
  async function run2(args) {
5599
5540
  const { outputDir, list, rename, merge, error } = parseArgs(args);
5600
5541
  if (error != null) {
5601
- log11.error(error);
5542
+ log10.error(error);
5602
5543
  return;
5603
5544
  }
5604
5545
  if (outputDir == null || outputDir.trim() === "") {
5605
- log11.error('Usage: vidistill rename-speakers <output-dir> [--list] [--rename "old" "new"] [--merge "source" "target"]');
5546
+ log10.error('Usage: vidistill rename-speakers <output-dir> [--list] [--rename "old" "new"] [--merge "source" "target"]');
5606
5547
  return;
5607
5548
  }
5608
5549
  if (list) {
@@ -5620,22 +5561,22 @@ async function run2(args) {
5620
5561
  const metadataPath = join5(outputDir, "metadata.json");
5621
5562
  const metadata = await readJsonFile(metadataPath);
5622
5563
  if (metadata == null) {
5623
- log11.error("Not a vidistill output directory");
5564
+ log10.error("Not a vidistill output directory");
5624
5565
  return;
5625
5566
  }
5626
5567
  const rawDir = join5(outputDir, "raw");
5627
5568
  const peopleExtraction = await readJsonFile(join5(rawDir, "pass3b-people.json"));
5628
5569
  if (peopleExtraction == null) {
5629
- log11.info("No speakers detected in this video");
5570
+ log10.info("No speakers detected in this video");
5630
5571
  return;
5631
5572
  }
5632
5573
  const speakers = await collectSpeakersFromRaw(rawDir);
5633
5574
  if (speakers.length === 0) {
5634
- log11.info("No speakers detected in this video");
5575
+ log10.info("No speakers detected in this video");
5635
5576
  return;
5636
5577
  }
5637
5578
  const existingMapping = metadata.speakerMapping ?? {};
5638
- log11.info(
5579
+ log10.info(
5639
5580
  `${String(speakers.length)} speaker${speakers.length === 1 ? "" : "s"} found. Enter names (or press Enter to keep current).`
5640
5581
  );
5641
5582
  const groups = groupSpeakersByExistingMapping(speakers, existingMapping);
@@ -5682,7 +5623,7 @@ async function run2(args) {
5682
5623
  return;
5683
5624
  }
5684
5625
  const { mapping: finalMapping, declinedMerges } = mergeResult;
5685
- log11.info("Re-rendering output files with updated speaker names...");
5626
+ log10.info("Re-rendering output files with updated speaker names...");
5686
5627
  const result = await reRenderWithSpeakerMapping({
5687
5628
  outputDir,
5688
5629
  speakerMapping: finalMapping,
@@ -5690,14 +5631,14 @@ async function run2(args) {
5690
5631
  });
5691
5632
  if (result.errors.length > 0) {
5692
5633
  for (const err of result.errors) {
5693
- log11.error(err);
5634
+ log10.error(err);
5694
5635
  }
5695
5636
  }
5696
- log11.info(`Done. ${String(result.filesGenerated.length)} file${result.filesGenerated.length === 1 ? "" : "s"} updated.`);
5637
+ log10.info(`Done. ${String(result.filesGenerated.length)} file${result.filesGenerated.length === 1 ? "" : "s"} updated.`);
5697
5638
  }
5698
5639
 
5699
5640
  // src/cli/index.ts
5700
- var version = "0.5.1";
5641
+ var version = "0.5.2";
5701
5642
  var DEFAULT_OUTPUT = "./vidistill-output/";
5702
5643
  var SUBCOMMANDS = {
5703
5644
  mcp: run,
@@ -5750,11 +5691,11 @@ Commands: ${Object.keys(SUBCOMMANDS).join(", ")}`
5750
5691
  lang: args.lang
5751
5692
  });
5752
5693
  } catch (err) {
5753
- const { log: log12 } = await import("@clack/prompts");
5694
+ const { log: log11 } = await import("@clack/prompts");
5754
5695
  const { default: pc4 } = await import("picocolors");
5755
5696
  const raw = err instanceof Error ? err.message : String(err);
5756
5697
  const message = raw.split("\n")[0].slice(0, 200);
5757
- log12.error(pc4.red(message));
5698
+ log11.error(pc4.red(message));
5758
5699
  process.exit(1);
5759
5700
  }
5760
5701
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vidistill",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
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",