@redaksjon/protokoll 0.0.11 → 0.0.13

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 (88) hide show
  1. package/.cursor/rules/definition-of-done.md +1 -0
  2. package/.cursor/rules/no-emoticons.md +26 -12
  3. package/README.md +483 -69
  4. package/dist/agentic/executor.js +473 -41
  5. package/dist/agentic/executor.js.map +1 -1
  6. package/dist/agentic/index.js.map +1 -1
  7. package/dist/agentic/tools/lookup-person.js +123 -4
  8. package/dist/agentic/tools/lookup-person.js.map +1 -1
  9. package/dist/agentic/tools/lookup-project.js +139 -22
  10. package/dist/agentic/tools/lookup-project.js.map +1 -1
  11. package/dist/agentic/tools/route-note.js +5 -1
  12. package/dist/agentic/tools/route-note.js.map +1 -1
  13. package/dist/arguments.js +6 -3
  14. package/dist/arguments.js.map +1 -1
  15. package/dist/cli/action.js +704 -0
  16. package/dist/cli/action.js.map +1 -0
  17. package/dist/cli/config.js +482 -0
  18. package/dist/cli/config.js.map +1 -0
  19. package/dist/cli/context.js +466 -0
  20. package/dist/cli/context.js.map +1 -0
  21. package/dist/cli/feedback.js +858 -0
  22. package/dist/cli/feedback.js.map +1 -0
  23. package/dist/cli/index.js +103 -0
  24. package/dist/cli/index.js.map +1 -0
  25. package/dist/cli/install.js +572 -0
  26. package/dist/cli/install.js.map +1 -0
  27. package/dist/cli/transcript.js +199 -0
  28. package/dist/cli/transcript.js.map +1 -0
  29. package/dist/constants.js +12 -5
  30. package/dist/constants.js.map +1 -1
  31. package/dist/context/discovery.js +1 -1
  32. package/dist/context/discovery.js.map +1 -1
  33. package/dist/context/index.js +25 -1
  34. package/dist/context/index.js.map +1 -1
  35. package/dist/context/storage.js +57 -4
  36. package/dist/context/storage.js.map +1 -1
  37. package/dist/interactive/handler.js +310 -9
  38. package/dist/interactive/handler.js.map +1 -1
  39. package/dist/main.js +11 -1
  40. package/dist/main.js.map +1 -1
  41. package/dist/output/index.js.map +1 -1
  42. package/dist/output/manager.js +47 -2
  43. package/dist/output/manager.js.map +1 -1
  44. package/dist/phases/complete.js +38 -3
  45. package/dist/phases/complete.js.map +1 -1
  46. package/dist/phases/locate.js +1 -1
  47. package/dist/phases/locate.js.map +1 -1
  48. package/dist/pipeline/orchestrator.js +104 -31
  49. package/dist/pipeline/orchestrator.js.map +1 -1
  50. package/dist/protokoll.js +68 -2
  51. package/dist/protokoll.js.map +1 -1
  52. package/dist/reasoning/client.js +83 -0
  53. package/dist/reasoning/client.js.map +1 -1
  54. package/dist/reasoning/index.js +1 -0
  55. package/dist/reasoning/index.js.map +1 -1
  56. package/dist/routing/router.js +2 -2
  57. package/dist/routing/router.js.map +1 -1
  58. package/dist/util/media.js +1 -1
  59. package/dist/util/media.js.map +1 -1
  60. package/dist/util/metadata.js.map +1 -1
  61. package/dist/util/sound.js +116 -0
  62. package/dist/util/sound.js.map +1 -0
  63. package/dist/util/storage.js +3 -3
  64. package/dist/util/storage.js.map +1 -1
  65. package/docs/duplicate-question-prevention.md +117 -0
  66. package/docs/examples.md +152 -0
  67. package/docs/interactive-context-example.md +92 -0
  68. package/docs/package-lock.json +6 -0
  69. package/docs/package.json +3 -1
  70. package/eslint.config.mjs +1 -1
  71. package/guide/action.md +375 -0
  72. package/guide/config.md +207 -0
  73. package/guide/configuration.md +82 -67
  74. package/guide/context-commands.md +574 -0
  75. package/guide/context-system.md +20 -7
  76. package/guide/development.md +106 -4
  77. package/guide/feedback.md +335 -0
  78. package/guide/index.md +100 -4
  79. package/guide/interactive.md +15 -14
  80. package/guide/quickstart.md +21 -7
  81. package/guide/reasoning.md +18 -4
  82. package/guide/routing.md +192 -97
  83. package/package.json +2 -3
  84. package/scripts/copy-assets.mjs +47 -0
  85. package/scripts/coverage-priority.mjs +323 -0
  86. package/tsconfig.tsbuildinfo +1 -1
  87. package/vite.config.ts +6 -13
  88. package/vitest.config.ts +5 -1
@@ -0,0 +1,572 @@
1
+ import * as readline from 'readline';
2
+ import * as yaml from 'js-yaml';
3
+ import * as fs from 'fs/promises';
4
+ import * as path from 'node:path';
5
+ import { DEFAULT_MODEL, DEFAULT_TRANSCRIPTION_MODEL, VERSION } from '../constants.js';
6
+
7
+ // Helper to print to stdout
8
+ const print = (text)=>process.stdout.write(text + '\n');
9
+ // ANSI color codes for pretty output
10
+ const colors = {
11
+ reset: '\x1b[0m',
12
+ bold: '\x1b[1m',
13
+ dim: '\x1b[2m',
14
+ green: '\x1b[32m',
15
+ yellow: '\x1b[33m',
16
+ blue: '\x1b[34m',
17
+ cyan: '\x1b[36m',
18
+ magenta: '\x1b[35m'
19
+ };
20
+ const bold = (text)=>`${colors.bold}${text}${colors.reset}`;
21
+ const dim = (text)=>`${colors.dim}${text}${colors.reset}`;
22
+ const green = (text)=>`${colors.green}${text}${colors.reset}`;
23
+ const yellow = (text)=>`${colors.yellow}${text}${colors.reset}`;
24
+ const blue = (text)=>`${colors.blue}${text}${colors.reset}`;
25
+ const cyan = (text)=>`${colors.cyan}${text}${colors.reset}`;
26
+ const magenta = (text)=>`${colors.magenta}${text}${colors.reset}`;
27
+ // Helper for interactive prompts
28
+ const askQuestion = (rl, question)=>{
29
+ return new Promise((resolve)=>{
30
+ rl.question(question, (answer)=>{
31
+ resolve(answer.trim());
32
+ });
33
+ });
34
+ };
35
+ const createReadline = ()=>readline.createInterface({
36
+ input: process.stdin,
37
+ output: process.stdout
38
+ });
39
+ // Model information for guidance
40
+ const MODEL_INFO = {
41
+ reasoning: [
42
+ {
43
+ name: 'gpt-5.2',
44
+ provider: 'OpenAI',
45
+ notes: 'Default - High reasoning, best quality',
46
+ recommended: true
47
+ },
48
+ {
49
+ name: 'gpt-5.1',
50
+ provider: 'OpenAI',
51
+ notes: 'High reasoning, balanced'
52
+ },
53
+ {
54
+ name: 'gpt-5',
55
+ provider: 'OpenAI',
56
+ notes: 'Fast and capable'
57
+ },
58
+ {
59
+ name: 'gpt-4o',
60
+ provider: 'OpenAI',
61
+ notes: 'Previous gen, still capable'
62
+ },
63
+ {
64
+ name: 'gpt-4o-mini',
65
+ provider: 'OpenAI',
66
+ notes: 'Fast, lower cost'
67
+ },
68
+ {
69
+ name: 'o1',
70
+ provider: 'OpenAI',
71
+ notes: 'Reasoning-focused'
72
+ },
73
+ {
74
+ name: 'o1-mini',
75
+ provider: 'OpenAI',
76
+ notes: 'Faster reasoning'
77
+ },
78
+ {
79
+ name: 'claude-3-5-sonnet',
80
+ provider: 'Anthropic',
81
+ notes: 'Recommended for quality'
82
+ },
83
+ {
84
+ name: 'claude-3-opus',
85
+ provider: 'Anthropic',
86
+ notes: 'Highest capability'
87
+ }
88
+ ],
89
+ transcription: [
90
+ {
91
+ name: 'whisper-1',
92
+ notes: 'Default, reliable',
93
+ recommended: true
94
+ },
95
+ {
96
+ name: 'gpt-4o-transcribe',
97
+ notes: 'Newer, supports prompting'
98
+ }
99
+ ]
100
+ };
101
+ /**
102
+ * Print the welcome banner
103
+ */ const printWelcome = ()=>{
104
+ print('');
105
+ print('═'.repeat(60));
106
+ print(bold(cyan(` ╔═╗┬─┐┌─┐┌┬┐┌─┐┬┌─┌─┐┬ ┬ `)));
107
+ print(bold(cyan(` ╠═╝├┬┘│ │ │ │ │├┴┐│ ││ │ `)));
108
+ print(bold(cyan(` ╩ ┴└─└─┘ ┴ └─┘┴ ┴└─┘┴─┘┴─┘`)));
109
+ print('');
110
+ print(dim(` Intelligent Audio Transcription`));
111
+ print(dim(` Version ${VERSION}`));
112
+ print('═'.repeat(60));
113
+ print('');
114
+ print(`Welcome to ${bold('Protokoll')}! This wizard will help you set up your`);
115
+ print(`configuration for intelligent audio transcription.`);
116
+ print('');
117
+ };
118
+ /**
119
+ * Print the models table
120
+ */ const printModelsTable = (type)=>{
121
+ const models = MODEL_INFO[type];
122
+ print('');
123
+ print(bold(`Available ${type === 'reasoning' ? 'Reasoning' : 'Transcription'} Models:`));
124
+ print('');
125
+ print(` ${dim('Model'.padEnd(20))} ${dim('Provider'.padEnd(12))} ${dim('Notes')}`);
126
+ print(' ' + '─'.repeat(56));
127
+ for (const model of models){
128
+ const marker = model.recommended ? green('★ ') : ' ';
129
+ const provider = 'provider' in model ? model.provider : 'OpenAI';
130
+ print(` ${marker}${model.name.padEnd(18)} ${provider.padEnd(12)} ${model.notes}`);
131
+ }
132
+ print('');
133
+ if (models.some((m)=>m.recommended)) {
134
+ print(` ${green('★')} = Recommended`);
135
+ print('');
136
+ }
137
+ };
138
+ /**
139
+ * Ask for model selection
140
+ */ const askModel = async (rl)=>{
141
+ print(bold(yellow('─── Step 1: Model Selection ───')));
142
+ print('');
143
+ print(`Protokoll uses two AI models:`);
144
+ print(` 1. ${bold('Reasoning Model')}: Enhances transcripts, corrects names, routes notes`);
145
+ print(` 2. ${bold('Transcription Model')}: Converts audio to text (Whisper)`);
146
+ print('');
147
+ // Reasoning model
148
+ printModelsTable('reasoning');
149
+ print(`You can use any OpenAI or Anthropic model. The default (${DEFAULT_MODEL}) provides`);
150
+ print(`the best balance of quality and capability.`);
151
+ print('');
152
+ const modelInput = await askQuestion(rl, `Reasoning model [${DEFAULT_MODEL}]: `);
153
+ const model = modelInput || DEFAULT_MODEL;
154
+ print(green(` ✓ Using reasoning model: ${model}`));
155
+ print('');
156
+ // Transcription model
157
+ printModelsTable('transcription');
158
+ const transInput = await askQuestion(rl, `Transcription model [${DEFAULT_TRANSCRIPTION_MODEL}]: `);
159
+ const transcriptionModel = transInput || DEFAULT_TRANSCRIPTION_MODEL;
160
+ print(green(` ✓ Using transcription model: ${transcriptionModel}`));
161
+ print('');
162
+ return {
163
+ model,
164
+ transcriptionModel
165
+ };
166
+ };
167
+ /**
168
+ * Ask for directory configuration
169
+ */ const askDirectories = async (rl)=>{
170
+ print(bold(yellow('─── Step 2: Directory Configuration ───')));
171
+ print('');
172
+ print(`Protokoll needs to know where your audio files are and where`);
173
+ print(`transcripts should be saved.`);
174
+ print('');
175
+ // Input directory
176
+ print(bold('Audio Input Directory'));
177
+ print(dim(` Where do your audio recordings live? This is where Protokoll`));
178
+ print(dim(` will look for files to transcribe.`));
179
+ print('');
180
+ const inputDir = await askQuestion(rl, `Audio input directory [./recordings]: `);
181
+ const inputDirectory = inputDir || './recordings';
182
+ print(green(` ✓ Will look for audio files in: ${inputDirectory}`));
183
+ print('');
184
+ // Output directory
185
+ print(bold('Transcript Output Directory'));
186
+ print(dim(` Where should transcribed notes be saved? This is your default`));
187
+ print(dim(` destination (projects can override this).`));
188
+ print('');
189
+ const outputDir = await askQuestion(rl, `Transcript output directory [~/notes]: `);
190
+ const outputDirectory = outputDir || '~/notes';
191
+ print(green(` ✓ Will save transcripts to: ${outputDirectory}`));
192
+ print('');
193
+ // Processed directory
194
+ print(bold('Processed Audio Directory'));
195
+ print(dim(` After transcription, should audio files be moved somewhere?`));
196
+ print(dim(` Leave blank to keep them in place, or specify a directory.`));
197
+ print('');
198
+ const processedDir = await askQuestion(rl, `Move processed audio to (Enter to skip): `);
199
+ const processedDirectory = processedDir || undefined;
200
+ if (processedDirectory) {
201
+ print(green(` ✓ Will move processed audio to: ${processedDirectory}`));
202
+ } else {
203
+ print(dim(` ○ Audio files will remain in place after processing`));
204
+ }
205
+ print('');
206
+ return {
207
+ inputDirectory,
208
+ outputDirectory,
209
+ processedDirectory
210
+ };
211
+ };
212
+ /**
213
+ * Ask about projects/contexts
214
+ */ const askAboutProjects = async (rl)=>{
215
+ print(bold(yellow('─── Step 3: Projects & Contexts ───')));
216
+ print('');
217
+ print(`Protokoll can route transcripts to different destinations based`);
218
+ print(`on content. This is done through ${bold('Projects')}.`);
219
+ print('');
220
+ print(bold(`What's a Project?`));
221
+ print(` • A named context that triggers specific routing`);
222
+ print(` • Examples: "Work Notes", "Personal Journal", "Client Alpha"`);
223
+ print(` • Each project has trigger phrases that identify it`);
224
+ print(` • Projects route notes to specific folders automatically`);
225
+ print('');
226
+ print(bold(`What's a Context?`));
227
+ print(` • The broader category a project belongs to`);
228
+ print(` • Three types: ${cyan('work')}, ${magenta('personal')}, or ${yellow('mixed')}`);
229
+ print(` • Helps Protokoll understand note categorization`);
230
+ print('');
231
+ print(bold(`Examples:`));
232
+ print(dim(` • "Meeting with Sarah about Project Alpha" → routes to ~/work/alpha/`));
233
+ print(dim(` • "Reminder to pick up groceries" → routes to ~/personal/`));
234
+ print(dim(` • "Skiing trip planning" → routes to ~/personal/trips/`));
235
+ print('');
236
+ const answer = await askQuestion(rl, `Do you want to set up projects? (Y/n): `);
237
+ const useProjects = answer.toLowerCase() !== 'n';
238
+ if (useProjects) {
239
+ print(green(` ✓ Let's set up some projects!`));
240
+ } else {
241
+ print(dim(` ○ Skipping project setup (you can add them later)`));
242
+ }
243
+ print('');
244
+ return useProjects;
245
+ };
246
+ /**
247
+ * Interactive project creation
248
+ */ const createProject = async (rl, existingIds)=>{
249
+ print('');
250
+ print(bold(blue('─── New Project ───')));
251
+ print('');
252
+ const name = await askQuestion(rl, `Project name (or Enter to finish): `);
253
+ if (!name) {
254
+ return null;
255
+ }
256
+ // Generate ID
257
+ const suggestedId = name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
258
+ let finalId = suggestedId;
259
+ // Check for conflicts
260
+ if (existingIds.has(suggestedId)) {
261
+ const customId = await askQuestion(rl, `ID "${suggestedId}" exists. Enter new ID: `);
262
+ finalId = customId.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
263
+ } else {
264
+ const idInput = await askQuestion(rl, `ID [${suggestedId}]: `);
265
+ if (idInput) {
266
+ finalId = idInput.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
267
+ }
268
+ }
269
+ // Description
270
+ const description = await askQuestion(rl, `Description (Enter to skip): `);
271
+ // Context type
272
+ print(`Context type: ${cyan('work')} | ${magenta('personal')} | ${yellow('mixed')}`);
273
+ const ctxInput = await askQuestion(rl, `Context type [work]: `);
274
+ const contextType = [
275
+ 'work',
276
+ 'personal',
277
+ 'mixed'
278
+ ].includes(ctxInput) ? ctxInput : 'work';
279
+ // Destination
280
+ print(dim(` Leave blank to use global default output directory`));
281
+ const destination = await askQuestion(rl, `Output destination (Enter for default): `);
282
+ // Structure
283
+ print(`Directory structure: ${dim('none')} | ${dim('year')} | ${bold('month')} | ${dim('day')}`);
284
+ print(dim(` none: output/transcript.md`));
285
+ print(dim(` year: output/2026/transcript.md`));
286
+ print(dim(` month: output/2026/01/transcript.md`));
287
+ print(dim(` day: output/2026/01/15/transcript.md`));
288
+ const structInput = await askQuestion(rl, `Structure [month]: `);
289
+ const structure = [
290
+ 'none',
291
+ 'year',
292
+ 'month',
293
+ 'day'
294
+ ].includes(structInput) ? structInput : 'month';
295
+ // Trigger phrases
296
+ print(`Trigger phrases - words/phrases that identify this project`);
297
+ print(dim(` Examples: "work note", "project alpha", "client meeting"`));
298
+ const phrasesInput = await askQuestion(rl, `Trigger phrases (comma-separated): `);
299
+ const triggerPhrases = phrasesInput ? phrasesInput.split(',').map((s)=>s.trim()).filter(Boolean) : [];
300
+ const project = {
301
+ name,
302
+ id: finalId,
303
+ ...description && {
304
+ description
305
+ },
306
+ ...destination && {
307
+ destination
308
+ },
309
+ contextType,
310
+ structure,
311
+ triggerPhrases
312
+ };
313
+ print('');
314
+ print(green(` ✓ Project "${name}" created`));
315
+ return project;
316
+ };
317
+ /**
318
+ * Create multiple projects
319
+ */ const createProjects = async (rl)=>{
320
+ const projects = [];
321
+ const existingIds = new Set();
322
+ print('');
323
+ print(`Let's create your first project. You can add more after.`);
324
+ print(dim(`Press Enter without a name when you're done adding projects.`));
325
+ while(true){
326
+ const project = await createProject(rl, existingIds);
327
+ if (!project) {
328
+ break;
329
+ }
330
+ projects.push(project);
331
+ existingIds.add(project.id);
332
+ print('');
333
+ print(dim(`Added ${projects.length} project(s). Add another or press Enter to continue.`));
334
+ }
335
+ return projects;
336
+ };
337
+ /**
338
+ * Write the configuration files
339
+ */ const writeConfiguration = async (config)=>{
340
+ const configDir = '.protokoll';
341
+ const configPath = path.join(process.cwd(), configDir);
342
+ // Create directory structure
343
+ await fs.mkdir(configPath, {
344
+ recursive: true
345
+ });
346
+ await fs.mkdir(path.join(configPath, 'context', 'projects'), {
347
+ recursive: true
348
+ });
349
+ await fs.mkdir(path.join(configPath, 'context', 'people'), {
350
+ recursive: true
351
+ });
352
+ await fs.mkdir(path.join(configPath, 'context', 'terms'), {
353
+ recursive: true
354
+ });
355
+ await fs.mkdir(path.join(configPath, 'context', 'companies'), {
356
+ recursive: true
357
+ });
358
+ await fs.mkdir(path.join(configPath, 'context', 'ignored'), {
359
+ recursive: true
360
+ });
361
+ // Build config.yaml content
362
+ const configContent = {
363
+ model: config.model,
364
+ transcriptionModel: config.transcriptionModel,
365
+ inputDirectory: config.inputDirectory,
366
+ outputDirectory: config.outputDirectory
367
+ };
368
+ if (config.processedDirectory) {
369
+ configContent.processedDirectory = config.processedDirectory;
370
+ }
371
+ // Write config.yaml
372
+ const configYaml = yaml.dump(configContent, {
373
+ lineWidth: -1
374
+ });
375
+ await fs.writeFile(path.join(configPath, 'config.yaml'), configYaml, 'utf-8');
376
+ // Write project files
377
+ for (const project of config.projects){
378
+ const projectData = {
379
+ id: project.id,
380
+ name: project.name,
381
+ ...project.description && {
382
+ description: project.description
383
+ },
384
+ classification: {
385
+ context_type: project.contextType,
386
+ explicit_phrases: project.triggerPhrases
387
+ },
388
+ routing: {
389
+ ...project.destination && {
390
+ destination: project.destination
391
+ },
392
+ structure: project.structure,
393
+ filename_options: [
394
+ 'date',
395
+ 'time',
396
+ 'subject'
397
+ ]
398
+ },
399
+ active: true
400
+ };
401
+ const projectYaml = yaml.dump(projectData, {
402
+ lineWidth: -1
403
+ });
404
+ await fs.writeFile(path.join(configPath, 'context', 'projects', `${project.id}.yaml`), projectYaml, 'utf-8');
405
+ }
406
+ return configPath;
407
+ };
408
+ /**
409
+ * Print configuration summary
410
+ */ const printSummary = (config, configPath)=>{
411
+ print('');
412
+ print('═'.repeat(60));
413
+ print(bold(green(' ✓ Installation Complete!')));
414
+ print('═'.repeat(60));
415
+ print('');
416
+ print(bold('Configuration Summary'));
417
+ print('─'.repeat(40));
418
+ print('');
419
+ print(bold('Models:'));
420
+ print(` Reasoning: ${cyan(config.model)}`);
421
+ print(` Transcription: ${cyan(config.transcriptionModel)}`);
422
+ print('');
423
+ print(bold('Directories:'));
424
+ print(` Audio Input: ${config.inputDirectory}`);
425
+ print(` Output: ${config.outputDirectory}`);
426
+ if (config.processedDirectory) {
427
+ print(` Processed: ${config.processedDirectory}`);
428
+ }
429
+ print('');
430
+ if (config.projects.length > 0) {
431
+ print(bold(`Projects (${config.projects.length}):`));
432
+ for (const project of config.projects){
433
+ print(` ${green('●')} ${project.name} (${project.id})`);
434
+ if (project.destination) {
435
+ print(` → ${project.destination}`);
436
+ }
437
+ if (project.triggerPhrases.length > 0) {
438
+ print(` Triggers: ${dim(project.triggerPhrases.join(', '))}`);
439
+ }
440
+ }
441
+ print('');
442
+ }
443
+ print(bold('Configuration saved to:'));
444
+ print(` ${configPath}/config.yaml`);
445
+ print('');
446
+ };
447
+ /**
448
+ * Print getting started guide
449
+ */ const printGettingStarted = (config)=>{
450
+ print('═'.repeat(60));
451
+ print(bold(' Getting Started'));
452
+ print('═'.repeat(60));
453
+ print('');
454
+ print(bold('1. Set your API key:'));
455
+ print(` ${dim('export OPENAI_API_KEY="sk-your-key"')}`);
456
+ print('');
457
+ print(bold('2. Start transcribing:'));
458
+ print(` ${cyan(`protokoll --input-directory ${config.inputDirectory}`)}`);
459
+ print('');
460
+ print(bold('3. Add context over time:'));
461
+ print(` ${dim('protokoll person add')} # Add people you mention`);
462
+ print(` ${dim('protokoll project add')} # Add new projects`);
463
+ print(` ${dim('protokoll term add')} # Add technical terms`);
464
+ print('');
465
+ print(bold('4. Provide feedback:'));
466
+ print(` ${dim('protokoll feedback <transcript>')} # Improve routing`);
467
+ print('');
468
+ print('═'.repeat(60));
469
+ print(bold(' Documentation & Help'));
470
+ print('═'.repeat(60));
471
+ print('');
472
+ print(bold('Documentation:'));
473
+ print(` ${blue('https://github.com/redaksjon/protokoll')}`);
474
+ print('');
475
+ print(bold('Quick Guide:'));
476
+ print(` ${dim('protokoll --help')} # All command options`);
477
+ print(` ${dim('protokoll context status')} # View context system`);
478
+ print(` ${dim('protokoll project list')} # List all projects`);
479
+ print('');
480
+ print(bold('Useful Commands:'));
481
+ print(` ${dim('protokoll --batch')} # Non-interactive (for cron)`);
482
+ print(` ${dim('protokoll --verbose')} # Detailed output`);
483
+ print(` ${dim('protokoll --dry-run')} # Preview without saving`);
484
+ print('');
485
+ print('═'.repeat(60));
486
+ print(`${green('Ready to go!')} Run ${cyan('protokoll --help')} for more options.`);
487
+ print('═'.repeat(60));
488
+ print('');
489
+ };
490
+ /**
491
+ * Check if configuration already exists
492
+ */ const checkExistingConfig = async ()=>{
493
+ const configPath = path.join(process.cwd(), '.protokoll', 'config.yaml');
494
+ try {
495
+ await fs.access(configPath);
496
+ return true;
497
+ } catch {
498
+ return false;
499
+ }
500
+ };
501
+ /**
502
+ * Run the install wizard
503
+ */ const runInstallWizard = async ()=>{
504
+ const rl = createReadline();
505
+ try {
506
+ // Check for existing config
507
+ const hasExisting = await checkExistingConfig();
508
+ printWelcome();
509
+ if (hasExisting) {
510
+ print(yellow('⚠ Configuration already exists at .protokoll/config.yaml'));
511
+ print('');
512
+ const overwrite = await askQuestion(rl, `Overwrite existing configuration? (y/N): `);
513
+ if (overwrite.toLowerCase() !== 'y') {
514
+ print('');
515
+ print('Installation cancelled. Your existing configuration is unchanged.');
516
+ print(`Run ${cyan('protokoll context status')} to view your current setup.`);
517
+ print('');
518
+ return;
519
+ }
520
+ print('');
521
+ }
522
+ // Step 1: Model selection
523
+ const { model, transcriptionModel } = await askModel(rl);
524
+ // Step 2: Directory configuration
525
+ const { inputDirectory, outputDirectory, processedDirectory } = await askDirectories(rl);
526
+ // Step 3: Projects
527
+ const useProjects = await askAboutProjects(rl);
528
+ let projects = [];
529
+ if (useProjects) {
530
+ projects = await createProjects(rl);
531
+ }
532
+ // Write configuration
533
+ print('');
534
+ print(dim('Writing configuration...'));
535
+ const config = {
536
+ model,
537
+ transcriptionModel,
538
+ inputDirectory,
539
+ outputDirectory,
540
+ processedDirectory,
541
+ useProjects,
542
+ projects
543
+ };
544
+ const configPath = await writeConfiguration(config);
545
+ // Print summary and getting started guide
546
+ printSummary(config, configPath);
547
+ printGettingStarted(config);
548
+ } finally{
549
+ rl.close();
550
+ }
551
+ };
552
+ /**
553
+ * Register the install command
554
+ */ const registerInstallCommand = (program)=>{
555
+ program.command('install').description('Interactive setup wizard for first-time configuration').action(async ()=>{
556
+ await runInstallWizard();
557
+ });
558
+ };
559
+ /**
560
+ * Check if this is an install command
561
+ */ const isInstallCommand = ()=>{
562
+ const args = process.argv.slice(2);
563
+ return args.length > 0 && args[0] === 'install';
564
+ };
565
+ /**
566
+ * Run the install command directly
567
+ */ const runInstallCLI = async ()=>{
568
+ await runInstallWizard();
569
+ };
570
+
571
+ export { isInstallCommand, registerInstallCommand, runInstallCLI };
572
+ //# sourceMappingURL=install.js.map