@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.
- package/.cursor/rules/definition-of-done.md +1 -0
- package/.cursor/rules/no-emoticons.md +26 -12
- package/README.md +483 -69
- package/dist/agentic/executor.js +473 -41
- package/dist/agentic/executor.js.map +1 -1
- package/dist/agentic/index.js.map +1 -1
- package/dist/agentic/tools/lookup-person.js +123 -4
- package/dist/agentic/tools/lookup-person.js.map +1 -1
- package/dist/agentic/tools/lookup-project.js +139 -22
- package/dist/agentic/tools/lookup-project.js.map +1 -1
- package/dist/agentic/tools/route-note.js +5 -1
- package/dist/agentic/tools/route-note.js.map +1 -1
- package/dist/arguments.js +6 -3
- package/dist/arguments.js.map +1 -1
- package/dist/cli/action.js +704 -0
- package/dist/cli/action.js.map +1 -0
- package/dist/cli/config.js +482 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/context.js +466 -0
- package/dist/cli/context.js.map +1 -0
- package/dist/cli/feedback.js +858 -0
- package/dist/cli/feedback.js.map +1 -0
- package/dist/cli/index.js +103 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/install.js +572 -0
- package/dist/cli/install.js.map +1 -0
- package/dist/cli/transcript.js +199 -0
- package/dist/cli/transcript.js.map +1 -0
- package/dist/constants.js +12 -5
- package/dist/constants.js.map +1 -1
- package/dist/context/discovery.js +1 -1
- package/dist/context/discovery.js.map +1 -1
- package/dist/context/index.js +25 -1
- package/dist/context/index.js.map +1 -1
- package/dist/context/storage.js +57 -4
- package/dist/context/storage.js.map +1 -1
- package/dist/interactive/handler.js +310 -9
- package/dist/interactive/handler.js.map +1 -1
- package/dist/main.js +11 -1
- package/dist/main.js.map +1 -1
- package/dist/output/index.js.map +1 -1
- package/dist/output/manager.js +47 -2
- package/dist/output/manager.js.map +1 -1
- package/dist/phases/complete.js +38 -3
- package/dist/phases/complete.js.map +1 -1
- package/dist/phases/locate.js +1 -1
- package/dist/phases/locate.js.map +1 -1
- package/dist/pipeline/orchestrator.js +104 -31
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/protokoll.js +68 -2
- package/dist/protokoll.js.map +1 -1
- package/dist/reasoning/client.js +83 -0
- package/dist/reasoning/client.js.map +1 -1
- package/dist/reasoning/index.js +1 -0
- package/dist/reasoning/index.js.map +1 -1
- package/dist/routing/router.js +2 -2
- package/dist/routing/router.js.map +1 -1
- package/dist/util/media.js +1 -1
- package/dist/util/media.js.map +1 -1
- package/dist/util/metadata.js.map +1 -1
- package/dist/util/sound.js +116 -0
- package/dist/util/sound.js.map +1 -0
- package/dist/util/storage.js +3 -3
- package/dist/util/storage.js.map +1 -1
- package/docs/duplicate-question-prevention.md +117 -0
- package/docs/examples.md +152 -0
- package/docs/interactive-context-example.md +92 -0
- package/docs/package-lock.json +6 -0
- package/docs/package.json +3 -1
- package/eslint.config.mjs +1 -1
- package/guide/action.md +375 -0
- package/guide/config.md +207 -0
- package/guide/configuration.md +82 -67
- package/guide/context-commands.md +574 -0
- package/guide/context-system.md +20 -7
- package/guide/development.md +106 -4
- package/guide/feedback.md +335 -0
- package/guide/index.md +100 -4
- package/guide/interactive.md +15 -14
- package/guide/quickstart.md +21 -7
- package/guide/reasoning.md +18 -4
- package/guide/routing.md +192 -97
- package/package.json +2 -3
- package/scripts/copy-assets.mjs +47 -0
- package/scripts/coverage-priority.mjs +323 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vite.config.ts +6 -13
- 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
|