@redaksjon/protokoll 0.0.12 → 0.0.14
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 +681 -69
- package/dist/feedback.js +5193 -0
- package/dist/feedback.js.map +1 -0
- package/dist/main.js +1861 -2
- package/dist/main.js.map +1 -1
- package/dist/mcp/server.js +1330 -0
- package/dist/mcp/server.js.map +1 -0
- 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/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 +116 -4
- package/guide/interactive.md +15 -14
- package/guide/mcp-integration.md +341 -0
- package/guide/quickstart.md +21 -7
- package/guide/reasoning.md +18 -4
- package/guide/routing.md +192 -97
- package/package.json +5 -3
- package/scripts/coverage-priority.mjs +323 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/vite.config.ts +13 -29
- package/vitest.config.ts +5 -1
- package/dist/agentic/executor.js +0 -315
- package/dist/agentic/executor.js.map +0 -1
- package/dist/agentic/index.js +0 -19
- package/dist/agentic/index.js.map +0 -1
- package/dist/agentic/registry.js +0 -41
- package/dist/agentic/registry.js.map +0 -1
- package/dist/agentic/tools/lookup-person.js +0 -66
- package/dist/agentic/tools/lookup-person.js.map +0 -1
- package/dist/agentic/tools/lookup-project.js +0 -93
- package/dist/agentic/tools/lookup-project.js.map +0 -1
- package/dist/agentic/tools/route-note.js +0 -45
- package/dist/agentic/tools/route-note.js.map +0 -1
- package/dist/agentic/tools/store-context.js +0 -51
- package/dist/agentic/tools/store-context.js.map +0 -1
- package/dist/agentic/tools/verify-spelling.js +0 -57
- package/dist/agentic/tools/verify-spelling.js.map +0 -1
- package/dist/arguments.js +0 -175
- package/dist/arguments.js.map +0 -1
- package/dist/constants.js +0 -84
- package/dist/constants.js.map +0 -1
- package/dist/context/discovery.js +0 -114
- package/dist/context/discovery.js.map +0 -1
- package/dist/context/index.js +0 -58
- package/dist/context/index.js.map +0 -1
- package/dist/context/storage.js +0 -131
- package/dist/context/storage.js.map +0 -1
- package/dist/interactive/handler.js +0 -223
- package/dist/interactive/handler.js.map +0 -1
- package/dist/interactive/index.js +0 -18
- package/dist/interactive/index.js.map +0 -1
- package/dist/interactive/onboarding.js +0 -28
- package/dist/interactive/onboarding.js.map +0 -1
- package/dist/logging.js +0 -46
- package/dist/logging.js.map +0 -1
- package/dist/output/index.js +0 -8
- package/dist/output/index.js.map +0 -1
- package/dist/output/manager.js +0 -105
- package/dist/output/manager.js.map +0 -1
- package/dist/phases/complete.js +0 -107
- package/dist/phases/complete.js.map +0 -1
- package/dist/phases/locate.js +0 -64
- package/dist/phases/locate.js.map +0 -1
- package/dist/pipeline/index.js +0 -8
- package/dist/pipeline/index.js.map +0 -1
- package/dist/pipeline/orchestrator.js +0 -281
- package/dist/pipeline/orchestrator.js.map +0 -1
- package/dist/protokoll.js +0 -114
- package/dist/protokoll.js.map +0 -1
- package/dist/reasoning/client.js +0 -150
- package/dist/reasoning/client.js.map +0 -1
- package/dist/reasoning/index.js +0 -36
- package/dist/reasoning/index.js.map +0 -1
- package/dist/reasoning/strategy.js +0 -60
- package/dist/reasoning/strategy.js.map +0 -1
- package/dist/reflection/collector.js +0 -124
- package/dist/reflection/collector.js.map +0 -1
- package/dist/reflection/index.js +0 -16
- package/dist/reflection/index.js.map +0 -1
- package/dist/reflection/reporter.js +0 -238
- package/dist/reflection/reporter.js.map +0 -1
- package/dist/routing/classifier.js +0 -201
- package/dist/routing/classifier.js.map +0 -1
- package/dist/routing/index.js +0 -27
- package/dist/routing/index.js.map +0 -1
- package/dist/routing/router.js +0 -153
- package/dist/routing/router.js.map +0 -1
- package/dist/transcription/index.js +0 -41
- package/dist/transcription/index.js.map +0 -1
- package/dist/transcription/service.js +0 -64
- package/dist/transcription/service.js.map +0 -1
- package/dist/transcription/types.js +0 -31
- package/dist/transcription/types.js.map +0 -1
- package/dist/util/dates.js +0 -96
- package/dist/util/dates.js.map +0 -1
- package/dist/util/media.js +0 -103
- package/dist/util/media.js.map +0 -1
- package/dist/util/metadata.js +0 -95
- package/dist/util/metadata.js.map +0 -1
- package/dist/util/storage.js +0 -135
- package/dist/util/storage.js.map +0 -1
|
@@ -0,0 +1,1330 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import 'dotenv/config';
|
|
3
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
+
import { resolve, dirname } from 'node:path';
|
|
7
|
+
import { readFile, mkdir, writeFile, unlink, stat } from 'node:fs/promises';
|
|
8
|
+
import { glob } from 'glob';
|
|
9
|
+
import { d as create, g as getLogger, c as create$1, q as create$2, y as create$3, u as DEFAULT_MODEL, z as processFeedback, B as applyChanges, C as parseTranscript, E as combineTranscripts, F as editTranscript, i as DEFAULT_OUTPUT_DIRECTORY, l as DEFAULT_OUTPUT_STRUCTURE, k as DEFAULT_OUTPUT_FILENAME_OPTIONS, p as create$4, v as DEFAULT_TRANSCRIPTION_MODEL, G as DEFAULT_TEMP_DIRECTORY, b as DEFAULT_MAX_AUDIO_SIZE, e as DEFAULT_INTERMEDIATE_DIRECTORY, D as DEFAULT_REASONING_LEVEL } from '../feedback.js';
|
|
10
|
+
import 'readline';
|
|
11
|
+
import 'child_process';
|
|
12
|
+
import 'fs/promises';
|
|
13
|
+
import 'openai';
|
|
14
|
+
import 'fluent-ffmpeg';
|
|
15
|
+
import 'node:fs';
|
|
16
|
+
import 'node:crypto';
|
|
17
|
+
import 'commander';
|
|
18
|
+
import 'js-yaml';
|
|
19
|
+
import 'fs';
|
|
20
|
+
import 'winston';
|
|
21
|
+
import 'node:os';
|
|
22
|
+
import '@riotprompt/riotprompt';
|
|
23
|
+
|
|
24
|
+
const fileExists = async (path) => {
|
|
25
|
+
try {
|
|
26
|
+
await stat(path);
|
|
27
|
+
return true;
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const logger = getLogger();
|
|
33
|
+
const media = create(logger);
|
|
34
|
+
const storage = create$1({ log: logger.debug.bind(logger) });
|
|
35
|
+
async function getAudioMetadata(audioFile) {
|
|
36
|
+
let creationTime = await media.getAudioCreationTime(audioFile);
|
|
37
|
+
if (!creationTime) {
|
|
38
|
+
creationTime = /* @__PURE__ */ new Date();
|
|
39
|
+
}
|
|
40
|
+
const hash = (await storage.hashFile(audioFile, 100)).substring(0, 8);
|
|
41
|
+
return { creationTime, hash };
|
|
42
|
+
}
|
|
43
|
+
async function findProtokolkConfigs(startPath, maxLevels = 10) {
|
|
44
|
+
const configs = [];
|
|
45
|
+
let currentPath = resolve(startPath);
|
|
46
|
+
let levels = 0;
|
|
47
|
+
while (levels < maxLevels) {
|
|
48
|
+
const protokollPath = resolve(currentPath, ".protokoll");
|
|
49
|
+
if (await fileExists(protokollPath)) {
|
|
50
|
+
configs.push(protokollPath);
|
|
51
|
+
}
|
|
52
|
+
const parentPath = dirname(currentPath);
|
|
53
|
+
if (parentPath === currentPath) break;
|
|
54
|
+
currentPath = parentPath;
|
|
55
|
+
levels++;
|
|
56
|
+
}
|
|
57
|
+
return configs;
|
|
58
|
+
}
|
|
59
|
+
async function getConfigInfo(protokollPath) {
|
|
60
|
+
const context = await create$2({ startingDir: dirname(protokollPath) });
|
|
61
|
+
const config = context.getConfig();
|
|
62
|
+
return {
|
|
63
|
+
path: protokollPath,
|
|
64
|
+
projectCount: context.getAllProjects().length,
|
|
65
|
+
peopleCount: context.getAllPeople().length,
|
|
66
|
+
termsCount: context.getAllTerms().length,
|
|
67
|
+
companiesCount: context.getAllCompanies().length,
|
|
68
|
+
outputDirectory: config.outputDirectory,
|
|
69
|
+
model: config.model
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
async function suggestProjectsForFile(audioFile) {
|
|
73
|
+
const audioPath = resolve(audioFile);
|
|
74
|
+
const audioDir = dirname(audioPath);
|
|
75
|
+
const configPaths = await findProtokolkConfigs(audioDir);
|
|
76
|
+
if (configPaths.length === 0) {
|
|
77
|
+
return {
|
|
78
|
+
configs: [],
|
|
79
|
+
suggestions: [],
|
|
80
|
+
needsUserInput: true,
|
|
81
|
+
message: `No .protokoll configuration found for ${audioFile}. You can either: (1) Create a .protokoll directory with project configuration, or (2) Specify a contextDirectory when calling protokoll_process_audio.`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const configs = [];
|
|
85
|
+
const allSuggestions = [];
|
|
86
|
+
for (const configPath of configPaths) {
|
|
87
|
+
const info = await getConfigInfo(configPath);
|
|
88
|
+
configs.push(info);
|
|
89
|
+
const context = await create$2({ startingDir: dirname(configPath) });
|
|
90
|
+
const projects = context.getAllProjects().filter((p) => p.active !== false);
|
|
91
|
+
for (const project of projects) {
|
|
92
|
+
const destination = project.routing?.destination;
|
|
93
|
+
if (destination) {
|
|
94
|
+
const expandedDest = destination.startsWith("~") ? destination.replace("~", process.env.HOME || "") : destination;
|
|
95
|
+
if (audioDir.includes(expandedDest) || expandedDest.includes(audioDir)) {
|
|
96
|
+
allSuggestions.push({
|
|
97
|
+
projectId: project.id,
|
|
98
|
+
projectName: project.name,
|
|
99
|
+
confidence: 0.9,
|
|
100
|
+
reason: `Audio file is in or near project destination: ${destination}`,
|
|
101
|
+
destination
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (project.classification?.explicit_phrases) {
|
|
106
|
+
const dirName = audioDir.split("/").pop() || "";
|
|
107
|
+
for (const phrase of project.classification.explicit_phrases) {
|
|
108
|
+
if (dirName.toLowerCase().includes(phrase.toLowerCase())) {
|
|
109
|
+
allSuggestions.push({
|
|
110
|
+
projectId: project.id,
|
|
111
|
+
projectName: project.name,
|
|
112
|
+
confidence: 0.7,
|
|
113
|
+
reason: `Directory name matches project phrase: "${phrase}"`,
|
|
114
|
+
destination: project.routing?.destination
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const uniqueSuggestions = allSuggestions.filter((s, i, arr) => arr.findIndex((x) => x.projectId === s.projectId) === i).sort((a, b) => b.confidence - a.confidence);
|
|
122
|
+
if (uniqueSuggestions.length === 0) {
|
|
123
|
+
return {
|
|
124
|
+
configs,
|
|
125
|
+
suggestions: [],
|
|
126
|
+
needsUserInput: configs[0].projectCount > 0,
|
|
127
|
+
message: configs[0].projectCount > 0 ? `Found ${configs[0].projectCount} projects but couldn't automatically determine which one this file belongs to. Please specify the project or let me list them for you.` : "Configuration found but no projects defined. Transcripts will use default routing."
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (uniqueSuggestions.length === 1 && uniqueSuggestions[0].confidence >= 0.8) {
|
|
131
|
+
return {
|
|
132
|
+
configs,
|
|
133
|
+
suggestions: uniqueSuggestions,
|
|
134
|
+
needsUserInput: false,
|
|
135
|
+
message: `Detected project: ${uniqueSuggestions[0].projectName} (${uniqueSuggestions[0].reason})`
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
configs,
|
|
140
|
+
suggestions: uniqueSuggestions,
|
|
141
|
+
needsUserInput: true,
|
|
142
|
+
message: `Found ${uniqueSuggestions.length} possible projects. Please confirm which project this file belongs to.`
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const tools = [
|
|
146
|
+
// ========================================
|
|
147
|
+
// Discovery & Configuration Tools
|
|
148
|
+
// ========================================
|
|
149
|
+
{
|
|
150
|
+
name: "protokoll_discover_config",
|
|
151
|
+
description: "Discover Protokoll configurations for a given file or directory. Walks up the directory tree to find .protokoll directories and returns information about each, including project counts, people, terms, and output settings. ALWAYS call this first when asked to transcribe a file to understand the available context. This helps determine which project configuration to use.",
|
|
152
|
+
inputSchema: {
|
|
153
|
+
type: "object",
|
|
154
|
+
properties: {
|
|
155
|
+
path: {
|
|
156
|
+
type: "string",
|
|
157
|
+
description: "Path to a file or directory to search from (searches up the tree)"
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
required: ["path"]
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: "protokoll_suggest_project",
|
|
165
|
+
description: "Suggest which project(s) an audio file might belong to based on its location. Analyzes the file path against configured projects to determine the best match. Returns suggestions with confidence levels and reasons. If multiple projects match or no clear match is found, the response indicates that user input is needed.",
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: "object",
|
|
168
|
+
properties: {
|
|
169
|
+
audioFile: {
|
|
170
|
+
type: "string",
|
|
171
|
+
description: "Path to the audio file to analyze"
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
required: ["audioFile"]
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
// ========================================
|
|
178
|
+
// Transcription Tools
|
|
179
|
+
// ========================================
|
|
180
|
+
{
|
|
181
|
+
name: "protokoll_process_audio",
|
|
182
|
+
description: "Process an audio file through Protokoll's intelligent transcription pipeline. IMPORTANT: Before calling this, use protokoll_discover_config or protokoll_suggest_project to understand which configuration/project should be used. This tool transcribes audio using Whisper, then enhances it with context-aware processing that corrects names, terms, and routes the output to the appropriate project folder. If no contextDirectory is specified, the tool walks up from the audio file to find .protokoll. Returns the enhanced transcript text and output file path.",
|
|
183
|
+
inputSchema: {
|
|
184
|
+
type: "object",
|
|
185
|
+
properties: {
|
|
186
|
+
audioFile: {
|
|
187
|
+
type: "string",
|
|
188
|
+
description: "Absolute path to the audio file to process (m4a, mp3, wav, webm, etc.)"
|
|
189
|
+
},
|
|
190
|
+
contextDirectory: {
|
|
191
|
+
type: "string",
|
|
192
|
+
description: "Path to the .protokoll context directory. If not specified, walks up from the audio file location to find one."
|
|
193
|
+
},
|
|
194
|
+
projectId: {
|
|
195
|
+
type: "string",
|
|
196
|
+
description: "Specific project ID to use for routing (helpful when multiple projects exist)"
|
|
197
|
+
},
|
|
198
|
+
outputDirectory: {
|
|
199
|
+
type: "string",
|
|
200
|
+
description: "Override the default output directory"
|
|
201
|
+
},
|
|
202
|
+
model: {
|
|
203
|
+
type: "string",
|
|
204
|
+
description: "LLM model for enhancement (default: gpt-5.2)"
|
|
205
|
+
},
|
|
206
|
+
transcriptionModel: {
|
|
207
|
+
type: "string",
|
|
208
|
+
description: "Transcription model (default: whisper-1)"
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
required: ["audioFile"]
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: "protokoll_batch_process",
|
|
216
|
+
description: "Process multiple audio files in a directory. Finds all audio files matching the configured extensions and processes them sequentially. Returns a summary of all processed files with their output paths.",
|
|
217
|
+
inputSchema: {
|
|
218
|
+
type: "object",
|
|
219
|
+
properties: {
|
|
220
|
+
inputDirectory: {
|
|
221
|
+
type: "string",
|
|
222
|
+
description: "Absolute path to directory containing audio files"
|
|
223
|
+
},
|
|
224
|
+
extensions: {
|
|
225
|
+
type: "array",
|
|
226
|
+
items: { type: "string" },
|
|
227
|
+
description: 'Audio file extensions to process (default: [".m4a", ".mp3", ".wav", ".webm"])'
|
|
228
|
+
},
|
|
229
|
+
contextDirectory: {
|
|
230
|
+
type: "string",
|
|
231
|
+
description: "Path to the .protokoll context directory"
|
|
232
|
+
},
|
|
233
|
+
outputDirectory: {
|
|
234
|
+
type: "string",
|
|
235
|
+
description: "Override the default output directory"
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
required: ["inputDirectory"]
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
// ========================================
|
|
242
|
+
// Context Management Tools
|
|
243
|
+
// ========================================
|
|
244
|
+
{
|
|
245
|
+
name: "protokoll_context_status",
|
|
246
|
+
description: "Get the status of the Protokoll context system. Shows discovered .protokoll directories, entity counts, and configuration status. Use this to understand what context is available for transcription enhancement.",
|
|
247
|
+
inputSchema: {
|
|
248
|
+
type: "object",
|
|
249
|
+
properties: {
|
|
250
|
+
contextDirectory: {
|
|
251
|
+
type: "string",
|
|
252
|
+
description: "Path to start searching for .protokoll directories (default: cwd)"
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
required: []
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: "protokoll_list_projects",
|
|
260
|
+
description: "List all projects configured in the context. Projects define routing rules for where transcripts should be saved and what classification signals trigger them.",
|
|
261
|
+
inputSchema: {
|
|
262
|
+
type: "object",
|
|
263
|
+
properties: {
|
|
264
|
+
contextDirectory: {
|
|
265
|
+
type: "string",
|
|
266
|
+
description: "Path to the .protokoll context directory"
|
|
267
|
+
},
|
|
268
|
+
includeInactive: {
|
|
269
|
+
type: "boolean",
|
|
270
|
+
description: "Include inactive projects (default: false)"
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
required: []
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: "protokoll_list_people",
|
|
278
|
+
description: "List all people configured in the context. People entries help Protokoll recognize and correctly spell names that Whisper might mishear (using sounds_like phonetic variants).",
|
|
279
|
+
inputSchema: {
|
|
280
|
+
type: "object",
|
|
281
|
+
properties: {
|
|
282
|
+
contextDirectory: {
|
|
283
|
+
type: "string",
|
|
284
|
+
description: "Path to the .protokoll context directory"
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
required: []
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: "protokoll_list_terms",
|
|
292
|
+
description: "List all technical terms and abbreviations in the context. Terms help Protokoll correctly transcribe domain-specific vocabulary.",
|
|
293
|
+
inputSchema: {
|
|
294
|
+
type: "object",
|
|
295
|
+
properties: {
|
|
296
|
+
contextDirectory: {
|
|
297
|
+
type: "string",
|
|
298
|
+
description: "Path to the .protokoll context directory"
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
required: []
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
name: "protokoll_list_companies",
|
|
306
|
+
description: "List all companies configured in the context. Company entries help recognize organization names in transcripts.",
|
|
307
|
+
inputSchema: {
|
|
308
|
+
type: "object",
|
|
309
|
+
properties: {
|
|
310
|
+
contextDirectory: {
|
|
311
|
+
type: "string",
|
|
312
|
+
description: "Path to the .protokoll context directory"
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
required: []
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
name: "protokoll_search_context",
|
|
320
|
+
description: "Search across all context entity types (projects, people, terms, companies). Returns matching entities with their type and key details.",
|
|
321
|
+
inputSchema: {
|
|
322
|
+
type: "object",
|
|
323
|
+
properties: {
|
|
324
|
+
query: {
|
|
325
|
+
type: "string",
|
|
326
|
+
description: "Search query to match against entity names, IDs, and sounds_like variants"
|
|
327
|
+
},
|
|
328
|
+
contextDirectory: {
|
|
329
|
+
type: "string",
|
|
330
|
+
description: "Path to the .protokoll context directory"
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
required: ["query"]
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
name: "protokoll_get_entity",
|
|
338
|
+
description: "Get detailed information about a specific context entity. Returns the full YAML configuration including sounds_like variants, routing, etc.",
|
|
339
|
+
inputSchema: {
|
|
340
|
+
type: "object",
|
|
341
|
+
properties: {
|
|
342
|
+
entityType: {
|
|
343
|
+
type: "string",
|
|
344
|
+
enum: ["project", "person", "term", "company", "ignored"],
|
|
345
|
+
description: "Type of entity to retrieve"
|
|
346
|
+
},
|
|
347
|
+
entityId: {
|
|
348
|
+
type: "string",
|
|
349
|
+
description: "ID of the entity to retrieve"
|
|
350
|
+
},
|
|
351
|
+
contextDirectory: {
|
|
352
|
+
type: "string",
|
|
353
|
+
description: "Path to the .protokoll context directory"
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
required: ["entityType", "entityId"]
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
name: "protokoll_add_person",
|
|
361
|
+
description: "Add a new person to the context. People entries help Protokoll recognize names that Whisper mishears. Include sounds_like variants for phonetic matching.",
|
|
362
|
+
inputSchema: {
|
|
363
|
+
type: "object",
|
|
364
|
+
properties: {
|
|
365
|
+
name: {
|
|
366
|
+
type: "string",
|
|
367
|
+
description: "Full name of the person"
|
|
368
|
+
},
|
|
369
|
+
id: {
|
|
370
|
+
type: "string",
|
|
371
|
+
description: "Unique ID (default: slugified name)"
|
|
372
|
+
},
|
|
373
|
+
firstName: {
|
|
374
|
+
type: "string",
|
|
375
|
+
description: "First name"
|
|
376
|
+
},
|
|
377
|
+
lastName: {
|
|
378
|
+
type: "string",
|
|
379
|
+
description: "Last name"
|
|
380
|
+
},
|
|
381
|
+
company: {
|
|
382
|
+
type: "string",
|
|
383
|
+
description: "Company ID this person is associated with"
|
|
384
|
+
},
|
|
385
|
+
role: {
|
|
386
|
+
type: "string",
|
|
387
|
+
description: "Role or job title"
|
|
388
|
+
},
|
|
389
|
+
sounds_like: {
|
|
390
|
+
type: "array",
|
|
391
|
+
items: { type: "string" },
|
|
392
|
+
description: "Phonetic variants (how Whisper might mishear the name)"
|
|
393
|
+
},
|
|
394
|
+
context: {
|
|
395
|
+
type: "string",
|
|
396
|
+
description: "Additional context about this person"
|
|
397
|
+
},
|
|
398
|
+
contextDirectory: {
|
|
399
|
+
type: "string",
|
|
400
|
+
description: "Path to the .protokoll context directory"
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
required: ["name"]
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
name: "protokoll_add_project",
|
|
408
|
+
description: "Add a new project to the context. Projects define where transcripts should be routed based on classification signals. Include explicit_phrases that should trigger routing to this project.",
|
|
409
|
+
inputSchema: {
|
|
410
|
+
type: "object",
|
|
411
|
+
properties: {
|
|
412
|
+
name: {
|
|
413
|
+
type: "string",
|
|
414
|
+
description: "Project name"
|
|
415
|
+
},
|
|
416
|
+
id: {
|
|
417
|
+
type: "string",
|
|
418
|
+
description: "Unique ID (default: slugified name)"
|
|
419
|
+
},
|
|
420
|
+
destination: {
|
|
421
|
+
type: "string",
|
|
422
|
+
description: "Output directory for this project's transcripts"
|
|
423
|
+
},
|
|
424
|
+
structure: {
|
|
425
|
+
type: "string",
|
|
426
|
+
enum: ["none", "year", "month", "day"],
|
|
427
|
+
description: "Directory structure (default: month)"
|
|
428
|
+
},
|
|
429
|
+
contextType: {
|
|
430
|
+
type: "string",
|
|
431
|
+
enum: ["work", "personal", "mixed"],
|
|
432
|
+
description: "Context type for classification (default: work)"
|
|
433
|
+
},
|
|
434
|
+
explicit_phrases: {
|
|
435
|
+
type: "array",
|
|
436
|
+
items: { type: "string" },
|
|
437
|
+
description: "Phrases that trigger routing to this project"
|
|
438
|
+
},
|
|
439
|
+
topics: {
|
|
440
|
+
type: "array",
|
|
441
|
+
items: { type: "string" },
|
|
442
|
+
description: "Topic keywords for classification"
|
|
443
|
+
},
|
|
444
|
+
description: {
|
|
445
|
+
type: "string",
|
|
446
|
+
description: "Project description"
|
|
447
|
+
},
|
|
448
|
+
contextDirectory: {
|
|
449
|
+
type: "string",
|
|
450
|
+
description: "Path to the .protokoll context directory"
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
required: ["name"]
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
name: "protokoll_add_term",
|
|
458
|
+
description: "Add a new technical term or abbreviation to the context. Terms help Protokoll correctly transcribe domain-specific vocabulary. Include sounds_like variants for phonetic matching.",
|
|
459
|
+
inputSchema: {
|
|
460
|
+
type: "object",
|
|
461
|
+
properties: {
|
|
462
|
+
term: {
|
|
463
|
+
type: "string",
|
|
464
|
+
description: "The term or abbreviation"
|
|
465
|
+
},
|
|
466
|
+
id: {
|
|
467
|
+
type: "string",
|
|
468
|
+
description: "Unique ID (default: slugified term)"
|
|
469
|
+
},
|
|
470
|
+
expansion: {
|
|
471
|
+
type: "string",
|
|
472
|
+
description: "Full expansion if this is an acronym"
|
|
473
|
+
},
|
|
474
|
+
domain: {
|
|
475
|
+
type: "string",
|
|
476
|
+
description: "Domain or field (e.g., engineering, finance)"
|
|
477
|
+
},
|
|
478
|
+
sounds_like: {
|
|
479
|
+
type: "array",
|
|
480
|
+
items: { type: "string" },
|
|
481
|
+
description: "Phonetic variants (how Whisper might mishear the term)"
|
|
482
|
+
},
|
|
483
|
+
projects: {
|
|
484
|
+
type: "array",
|
|
485
|
+
items: { type: "string" },
|
|
486
|
+
description: "Associated project IDs"
|
|
487
|
+
},
|
|
488
|
+
contextDirectory: {
|
|
489
|
+
type: "string",
|
|
490
|
+
description: "Path to the .protokoll context directory"
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
required: ["term"]
|
|
494
|
+
}
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
name: "protokoll_add_company",
|
|
498
|
+
description: "Add a new company to the context. Company entries help recognize organization names in transcripts.",
|
|
499
|
+
inputSchema: {
|
|
500
|
+
type: "object",
|
|
501
|
+
properties: {
|
|
502
|
+
name: {
|
|
503
|
+
type: "string",
|
|
504
|
+
description: "Company name"
|
|
505
|
+
},
|
|
506
|
+
id: {
|
|
507
|
+
type: "string",
|
|
508
|
+
description: "Unique ID (default: slugified name)"
|
|
509
|
+
},
|
|
510
|
+
fullName: {
|
|
511
|
+
type: "string",
|
|
512
|
+
description: "Full legal name"
|
|
513
|
+
},
|
|
514
|
+
industry: {
|
|
515
|
+
type: "string",
|
|
516
|
+
description: "Industry or sector"
|
|
517
|
+
},
|
|
518
|
+
sounds_like: {
|
|
519
|
+
type: "array",
|
|
520
|
+
items: { type: "string" },
|
|
521
|
+
description: "Phonetic variants"
|
|
522
|
+
},
|
|
523
|
+
contextDirectory: {
|
|
524
|
+
type: "string",
|
|
525
|
+
description: "Path to the .protokoll context directory"
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
required: ["name"]
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
name: "protokoll_delete_entity",
|
|
533
|
+
description: "Delete an entity from the context. Removes the entity's YAML file from the context directory.",
|
|
534
|
+
inputSchema: {
|
|
535
|
+
type: "object",
|
|
536
|
+
properties: {
|
|
537
|
+
entityType: {
|
|
538
|
+
type: "string",
|
|
539
|
+
enum: ["project", "person", "term", "company", "ignored"],
|
|
540
|
+
description: "Type of entity to delete"
|
|
541
|
+
},
|
|
542
|
+
entityId: {
|
|
543
|
+
type: "string",
|
|
544
|
+
description: "ID of the entity to delete"
|
|
545
|
+
},
|
|
546
|
+
contextDirectory: {
|
|
547
|
+
type: "string",
|
|
548
|
+
description: "Path to the .protokoll context directory"
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
required: ["entityType", "entityId"]
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
// ========================================
|
|
555
|
+
// Transcript Action Tools
|
|
556
|
+
// ========================================
|
|
557
|
+
{
|
|
558
|
+
name: "protokoll_edit_transcript",
|
|
559
|
+
description: "Edit an existing transcript's title and/or project assignment. Changing the project will update metadata and may move the file to a new location based on the project's routing configuration.",
|
|
560
|
+
inputSchema: {
|
|
561
|
+
type: "object",
|
|
562
|
+
properties: {
|
|
563
|
+
transcriptPath: {
|
|
564
|
+
type: "string",
|
|
565
|
+
description: "Absolute path to the transcript file"
|
|
566
|
+
},
|
|
567
|
+
title: {
|
|
568
|
+
type: "string",
|
|
569
|
+
description: "New title for the transcript"
|
|
570
|
+
},
|
|
571
|
+
projectId: {
|
|
572
|
+
type: "string",
|
|
573
|
+
description: "New project ID to assign"
|
|
574
|
+
},
|
|
575
|
+
contextDirectory: {
|
|
576
|
+
type: "string",
|
|
577
|
+
description: "Path to the .protokoll context directory"
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
required: ["transcriptPath"]
|
|
581
|
+
}
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
name: "protokoll_combine_transcripts",
|
|
585
|
+
description: "Combine multiple transcripts into a single document. Source files are automatically deleted after combining. Metadata from the first transcript is preserved, and content is organized into sections.",
|
|
586
|
+
inputSchema: {
|
|
587
|
+
type: "object",
|
|
588
|
+
properties: {
|
|
589
|
+
transcriptPaths: {
|
|
590
|
+
type: "array",
|
|
591
|
+
items: { type: "string" },
|
|
592
|
+
description: "Array of transcript file paths to combine"
|
|
593
|
+
},
|
|
594
|
+
title: {
|
|
595
|
+
type: "string",
|
|
596
|
+
description: "Title for the combined transcript"
|
|
597
|
+
},
|
|
598
|
+
projectId: {
|
|
599
|
+
type: "string",
|
|
600
|
+
description: "Project ID to assign to the combined transcript"
|
|
601
|
+
},
|
|
602
|
+
contextDirectory: {
|
|
603
|
+
type: "string",
|
|
604
|
+
description: "Path to the .protokoll context directory"
|
|
605
|
+
}
|
|
606
|
+
},
|
|
607
|
+
required: ["transcriptPaths"]
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
name: "protokoll_read_transcript",
|
|
612
|
+
description: "Read a transcript file and parse its metadata and content. Returns structured data including title, metadata, routing info, and content.",
|
|
613
|
+
inputSchema: {
|
|
614
|
+
type: "object",
|
|
615
|
+
properties: {
|
|
616
|
+
transcriptPath: {
|
|
617
|
+
type: "string",
|
|
618
|
+
description: "Absolute path to the transcript file"
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
required: ["transcriptPath"]
|
|
622
|
+
}
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
name: "protokoll_provide_feedback",
|
|
626
|
+
description: 'Provide natural language feedback to correct a transcript. The feedback is processed by an agentic model that can: - Fix spelling and term errors - Add new terms, people, or companies to context - Change project assignment - Update the title Example: "YB should be Wibey" or "San Jay Grouper is actually Sanjay Gupta"',
|
|
627
|
+
inputSchema: {
|
|
628
|
+
type: "object",
|
|
629
|
+
properties: {
|
|
630
|
+
transcriptPath: {
|
|
631
|
+
type: "string",
|
|
632
|
+
description: "Absolute path to the transcript file"
|
|
633
|
+
},
|
|
634
|
+
feedback: {
|
|
635
|
+
type: "string",
|
|
636
|
+
description: "Natural language feedback describing corrections needed"
|
|
637
|
+
},
|
|
638
|
+
model: {
|
|
639
|
+
type: "string",
|
|
640
|
+
description: "LLM model for processing feedback (default: gpt-5.2)"
|
|
641
|
+
},
|
|
642
|
+
contextDirectory: {
|
|
643
|
+
type: "string",
|
|
644
|
+
description: "Path to the .protokoll context directory"
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
required: ["transcriptPath", "feedback"]
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
];
|
|
651
|
+
const slugify = (text) => {
|
|
652
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/--+/g, "-").replace(/^-|-$/g, "");
|
|
653
|
+
};
|
|
654
|
+
const formatEntity = (entity) => {
|
|
655
|
+
const result = {
|
|
656
|
+
id: entity.id,
|
|
657
|
+
name: entity.name,
|
|
658
|
+
type: entity.type
|
|
659
|
+
};
|
|
660
|
+
if (entity.type === "person") {
|
|
661
|
+
const person = entity;
|
|
662
|
+
if (person.firstName) result.firstName = person.firstName;
|
|
663
|
+
if (person.lastName) result.lastName = person.lastName;
|
|
664
|
+
if (person.company) result.company = person.company;
|
|
665
|
+
if (person.role) result.role = person.role;
|
|
666
|
+
if (person.sounds_like) result.sounds_like = person.sounds_like;
|
|
667
|
+
if (person.context) result.context = person.context;
|
|
668
|
+
} else if (entity.type === "project") {
|
|
669
|
+
const project = entity;
|
|
670
|
+
if (project.description) result.description = project.description;
|
|
671
|
+
if (project.classification) result.classification = project.classification;
|
|
672
|
+
if (project.routing) result.routing = project.routing;
|
|
673
|
+
result.active = project.active !== false;
|
|
674
|
+
} else if (entity.type === "term") {
|
|
675
|
+
const term = entity;
|
|
676
|
+
if (term.expansion) result.expansion = term.expansion;
|
|
677
|
+
if (term.domain) result.domain = term.domain;
|
|
678
|
+
if (term.sounds_like) result.sounds_like = term.sounds_like;
|
|
679
|
+
if (term.projects) result.projects = term.projects;
|
|
680
|
+
} else if (entity.type === "company") {
|
|
681
|
+
const company = entity;
|
|
682
|
+
if (company.fullName) result.fullName = company.fullName;
|
|
683
|
+
if (company.industry) result.industry = company.industry;
|
|
684
|
+
if (company.sounds_like) result.sounds_like = company.sounds_like;
|
|
685
|
+
} else if (entity.type === "ignored") {
|
|
686
|
+
const ignored = entity;
|
|
687
|
+
if (ignored.reason) result.reason = ignored.reason;
|
|
688
|
+
if (ignored.ignoredAt) result.ignoredAt = ignored.ignoredAt;
|
|
689
|
+
}
|
|
690
|
+
return result;
|
|
691
|
+
};
|
|
692
|
+
async function handleDiscoverConfig(args) {
|
|
693
|
+
const searchPath = resolve(args.path);
|
|
694
|
+
if (!await fileExists(searchPath)) {
|
|
695
|
+
throw new Error(`Path not found: ${searchPath}`);
|
|
696
|
+
}
|
|
697
|
+
const pathStat = await stat(searchPath);
|
|
698
|
+
const startDir = pathStat.isDirectory() ? searchPath : dirname(searchPath);
|
|
699
|
+
const configPaths = await findProtokolkConfigs(startDir);
|
|
700
|
+
if (configPaths.length === 0) {
|
|
701
|
+
return {
|
|
702
|
+
found: false,
|
|
703
|
+
searchedFrom: startDir,
|
|
704
|
+
configs: [],
|
|
705
|
+
message: "No .protokoll configuration found in the directory hierarchy. To use Protokoll, create a .protokoll directory with your context files (people, projects, terms).",
|
|
706
|
+
suggestion: 'Run "protokoll --init-config" in your project directory to create initial configuration.'
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
const configs = [];
|
|
710
|
+
for (const configPath of configPaths) {
|
|
711
|
+
const info = await getConfigInfo(configPath);
|
|
712
|
+
configs.push(info);
|
|
713
|
+
}
|
|
714
|
+
const primaryConfig = configs[0];
|
|
715
|
+
return {
|
|
716
|
+
found: true,
|
|
717
|
+
searchedFrom: startDir,
|
|
718
|
+
primaryConfig: primaryConfig.path,
|
|
719
|
+
configs,
|
|
720
|
+
summary: {
|
|
721
|
+
totalProjects: configs.reduce((sum, c) => sum + c.projectCount, 0),
|
|
722
|
+
totalPeople: configs.reduce((sum, c) => sum + c.peopleCount, 0),
|
|
723
|
+
totalTerms: configs.reduce((sum, c) => sum + c.termsCount, 0),
|
|
724
|
+
totalCompanies: configs.reduce((sum, c) => sum + c.companiesCount, 0)
|
|
725
|
+
},
|
|
726
|
+
message: configs.length === 1 ? `Found Protokoll configuration at ${primaryConfig.path}` : `Found ${configs.length} Protokoll configurations (using nearest: ${primaryConfig.path})`
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
async function handleSuggestProject(args) {
|
|
730
|
+
const audioFile = resolve(args.audioFile);
|
|
731
|
+
if (!await fileExists(audioFile)) {
|
|
732
|
+
throw new Error(`Audio file not found: ${audioFile}`);
|
|
733
|
+
}
|
|
734
|
+
const result = await suggestProjectsForFile(audioFile);
|
|
735
|
+
return {
|
|
736
|
+
audioFile,
|
|
737
|
+
...result,
|
|
738
|
+
instructions: result.needsUserInput ? "Please specify which project this file belongs to, or let me list available projects." : "Ready to process with the detected project configuration."
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
async function handleProcessAudio(args) {
|
|
742
|
+
const audioFile = resolve(args.audioFile);
|
|
743
|
+
if (!await fileExists(audioFile)) {
|
|
744
|
+
throw new Error(`Audio file not found: ${audioFile}`);
|
|
745
|
+
}
|
|
746
|
+
const context = await create$2({
|
|
747
|
+
startingDir: args.contextDirectory || dirname(audioFile)
|
|
748
|
+
});
|
|
749
|
+
const config = context.getConfig();
|
|
750
|
+
const outputDirectory = args.outputDirectory || config.outputDirectory || DEFAULT_OUTPUT_DIRECTORY;
|
|
751
|
+
const outputStructure = config.outputStructure || DEFAULT_OUTPUT_STRUCTURE;
|
|
752
|
+
const outputFilenameOptions = config.outputFilenameOptions || DEFAULT_OUTPUT_FILENAME_OPTIONS;
|
|
753
|
+
const { creationTime, hash } = await getAudioMetadata(audioFile);
|
|
754
|
+
const pipeline = await create$4({
|
|
755
|
+
model: args.model || DEFAULT_MODEL,
|
|
756
|
+
transcriptionModel: args.transcriptionModel || DEFAULT_TRANSCRIPTION_MODEL,
|
|
757
|
+
reasoningLevel: DEFAULT_REASONING_LEVEL,
|
|
758
|
+
interactive: false,
|
|
759
|
+
// MCP is non-interactive
|
|
760
|
+
selfReflection: false,
|
|
761
|
+
silent: true,
|
|
762
|
+
debug: false,
|
|
763
|
+
dryRun: false,
|
|
764
|
+
contextDirectory: args.contextDirectory,
|
|
765
|
+
intermediateDir: DEFAULT_INTERMEDIATE_DIRECTORY,
|
|
766
|
+
keepIntermediates: false,
|
|
767
|
+
outputDirectory,
|
|
768
|
+
outputStructure,
|
|
769
|
+
outputFilenameOptions,
|
|
770
|
+
maxAudioSize: DEFAULT_MAX_AUDIO_SIZE,
|
|
771
|
+
tempDirectory: DEFAULT_TEMP_DIRECTORY
|
|
772
|
+
});
|
|
773
|
+
const result = await pipeline.process({
|
|
774
|
+
audioFile,
|
|
775
|
+
creation: creationTime,
|
|
776
|
+
hash
|
|
777
|
+
});
|
|
778
|
+
return {
|
|
779
|
+
outputPath: result.outputPath,
|
|
780
|
+
enhancedText: result.enhancedText,
|
|
781
|
+
rawTranscript: result.rawTranscript,
|
|
782
|
+
routedProject: result.routedProject ?? void 0,
|
|
783
|
+
routingConfidence: result.routingConfidence,
|
|
784
|
+
processingTime: result.processingTime,
|
|
785
|
+
toolsUsed: result.toolsUsed,
|
|
786
|
+
correctionsApplied: result.correctionsApplied
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
async function handleBatchProcess(args) {
|
|
790
|
+
const inputDir = resolve(args.inputDirectory);
|
|
791
|
+
const extensions = args.extensions || [".m4a", ".mp3", ".wav", ".webm"];
|
|
792
|
+
if (!await fileExists(inputDir)) {
|
|
793
|
+
throw new Error(`Input directory not found: ${inputDir}`);
|
|
794
|
+
}
|
|
795
|
+
const patterns = extensions.map((ext) => `**/*${ext}`);
|
|
796
|
+
const files = await glob(patterns, { cwd: inputDir, nodir: true, absolute: true });
|
|
797
|
+
if (files.length === 0) {
|
|
798
|
+
return { processed: [], errors: [] };
|
|
799
|
+
}
|
|
800
|
+
const processed = [];
|
|
801
|
+
const errors = [];
|
|
802
|
+
for (const file of files) {
|
|
803
|
+
try {
|
|
804
|
+
const result = await handleProcessAudio({
|
|
805
|
+
audioFile: file,
|
|
806
|
+
contextDirectory: args.contextDirectory,
|
|
807
|
+
outputDirectory: args.outputDirectory
|
|
808
|
+
});
|
|
809
|
+
processed.push(result);
|
|
810
|
+
} catch (error) {
|
|
811
|
+
errors.push({
|
|
812
|
+
file,
|
|
813
|
+
error: error instanceof Error ? error.message : String(error)
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return { processed, errors };
|
|
818
|
+
}
|
|
819
|
+
async function handleContextStatus(args) {
|
|
820
|
+
const context = await create$2({
|
|
821
|
+
startingDir: args.contextDirectory || process.cwd()
|
|
822
|
+
});
|
|
823
|
+
const dirs = context.getDiscoveredDirs();
|
|
824
|
+
const config = context.getConfig();
|
|
825
|
+
return {
|
|
826
|
+
hasContext: context.hasContext(),
|
|
827
|
+
discoveredDirectories: dirs.map((d) => ({
|
|
828
|
+
path: d.path,
|
|
829
|
+
level: d.level,
|
|
830
|
+
isPrimary: d.level === 0
|
|
831
|
+
})),
|
|
832
|
+
entityCounts: {
|
|
833
|
+
projects: context.getAllProjects().length,
|
|
834
|
+
people: context.getAllPeople().length,
|
|
835
|
+
terms: context.getAllTerms().length,
|
|
836
|
+
companies: context.getAllCompanies().length,
|
|
837
|
+
ignored: context.getAllIgnored().length
|
|
838
|
+
},
|
|
839
|
+
config: {
|
|
840
|
+
outputDirectory: config.outputDirectory,
|
|
841
|
+
outputStructure: config.outputStructure,
|
|
842
|
+
model: config.model
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
async function handleListProjects(args) {
|
|
847
|
+
const context = await create$2({
|
|
848
|
+
startingDir: args.contextDirectory || process.cwd()
|
|
849
|
+
});
|
|
850
|
+
let projects = context.getAllProjects();
|
|
851
|
+
if (!args.includeInactive) {
|
|
852
|
+
projects = projects.filter((p) => p.active !== false);
|
|
853
|
+
}
|
|
854
|
+
return {
|
|
855
|
+
count: projects.length,
|
|
856
|
+
projects: projects.map((p) => ({
|
|
857
|
+
id: p.id,
|
|
858
|
+
name: p.name,
|
|
859
|
+
active: p.active !== false,
|
|
860
|
+
destination: p.routing?.destination,
|
|
861
|
+
structure: p.routing?.structure,
|
|
862
|
+
contextType: p.classification?.context_type,
|
|
863
|
+
triggerPhrases: p.classification?.explicit_phrases
|
|
864
|
+
}))
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
async function handleListPeople(args) {
|
|
868
|
+
const context = await create$2({
|
|
869
|
+
startingDir: args.contextDirectory || process.cwd()
|
|
870
|
+
});
|
|
871
|
+
const people = context.getAllPeople();
|
|
872
|
+
return {
|
|
873
|
+
count: people.length,
|
|
874
|
+
people: people.map((p) => ({
|
|
875
|
+
id: p.id,
|
|
876
|
+
name: p.name,
|
|
877
|
+
company: p.company,
|
|
878
|
+
role: p.role,
|
|
879
|
+
sounds_like: p.sounds_like
|
|
880
|
+
}))
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
async function handleListTerms(args) {
|
|
884
|
+
const context = await create$2({
|
|
885
|
+
startingDir: args.contextDirectory || process.cwd()
|
|
886
|
+
});
|
|
887
|
+
const terms = context.getAllTerms();
|
|
888
|
+
return {
|
|
889
|
+
count: terms.length,
|
|
890
|
+
terms: terms.map((t) => ({
|
|
891
|
+
id: t.id,
|
|
892
|
+
name: t.name,
|
|
893
|
+
expansion: t.expansion,
|
|
894
|
+
domain: t.domain,
|
|
895
|
+
sounds_like: t.sounds_like
|
|
896
|
+
}))
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
async function handleListCompanies(args) {
|
|
900
|
+
const context = await create$2({
|
|
901
|
+
startingDir: args.contextDirectory || process.cwd()
|
|
902
|
+
});
|
|
903
|
+
const companies = context.getAllCompanies();
|
|
904
|
+
return {
|
|
905
|
+
count: companies.length,
|
|
906
|
+
companies: companies.map((c) => ({
|
|
907
|
+
id: c.id,
|
|
908
|
+
name: c.name,
|
|
909
|
+
fullName: c.fullName,
|
|
910
|
+
industry: c.industry,
|
|
911
|
+
sounds_like: c.sounds_like
|
|
912
|
+
}))
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
async function handleSearchContext(args) {
|
|
916
|
+
const context = await create$2({
|
|
917
|
+
startingDir: args.contextDirectory || process.cwd()
|
|
918
|
+
});
|
|
919
|
+
const results = context.search(args.query);
|
|
920
|
+
return {
|
|
921
|
+
query: args.query,
|
|
922
|
+
count: results.length,
|
|
923
|
+
results: results.map(formatEntity)
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
async function handleGetEntity(args) {
|
|
927
|
+
const context = await create$2({
|
|
928
|
+
startingDir: args.contextDirectory || process.cwd()
|
|
929
|
+
});
|
|
930
|
+
let entity;
|
|
931
|
+
switch (args.entityType) {
|
|
932
|
+
case "project":
|
|
933
|
+
entity = context.getProject(args.entityId);
|
|
934
|
+
break;
|
|
935
|
+
case "person":
|
|
936
|
+
entity = context.getPerson(args.entityId);
|
|
937
|
+
break;
|
|
938
|
+
case "term":
|
|
939
|
+
entity = context.getTerm(args.entityId);
|
|
940
|
+
break;
|
|
941
|
+
case "company":
|
|
942
|
+
entity = context.getCompany(args.entityId);
|
|
943
|
+
break;
|
|
944
|
+
case "ignored":
|
|
945
|
+
entity = context.getIgnored(args.entityId);
|
|
946
|
+
break;
|
|
947
|
+
}
|
|
948
|
+
if (!entity) {
|
|
949
|
+
throw new Error(`${args.entityType} "${args.entityId}" not found`);
|
|
950
|
+
}
|
|
951
|
+
const filePath = context.getEntityFilePath(entity);
|
|
952
|
+
return {
|
|
953
|
+
...formatEntity(entity),
|
|
954
|
+
filePath
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
async function handleAddPerson(args) {
|
|
958
|
+
const context = await create$2({
|
|
959
|
+
startingDir: args.contextDirectory || process.cwd()
|
|
960
|
+
});
|
|
961
|
+
if (!context.hasContext()) {
|
|
962
|
+
throw new Error("No .protokoll directory found. Initialize context first.");
|
|
963
|
+
}
|
|
964
|
+
const id = args.id || slugify(args.name);
|
|
965
|
+
if (context.getPerson(id)) {
|
|
966
|
+
throw new Error(`Person with ID "${id}" already exists`);
|
|
967
|
+
}
|
|
968
|
+
const person = {
|
|
969
|
+
id,
|
|
970
|
+
name: args.name,
|
|
971
|
+
type: "person",
|
|
972
|
+
...args.firstName && { firstName: args.firstName },
|
|
973
|
+
...args.lastName && { lastName: args.lastName },
|
|
974
|
+
...args.company && { company: args.company },
|
|
975
|
+
...args.role && { role: args.role },
|
|
976
|
+
...args.sounds_like && { sounds_like: args.sounds_like },
|
|
977
|
+
...args.context && { context: args.context }
|
|
978
|
+
};
|
|
979
|
+
await context.saveEntity(person);
|
|
980
|
+
return {
|
|
981
|
+
success: true,
|
|
982
|
+
message: `Person "${args.name}" added successfully`,
|
|
983
|
+
entity: formatEntity(person)
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
async function handleAddProject(args) {
|
|
987
|
+
const context = await create$2({
|
|
988
|
+
startingDir: args.contextDirectory || process.cwd()
|
|
989
|
+
});
|
|
990
|
+
if (!context.hasContext()) {
|
|
991
|
+
throw new Error("No .protokoll directory found. Initialize context first.");
|
|
992
|
+
}
|
|
993
|
+
const id = args.id || slugify(args.name);
|
|
994
|
+
if (context.getProject(id)) {
|
|
995
|
+
throw new Error(`Project with ID "${id}" already exists`);
|
|
996
|
+
}
|
|
997
|
+
const project = {
|
|
998
|
+
id,
|
|
999
|
+
name: args.name,
|
|
1000
|
+
type: "project",
|
|
1001
|
+
classification: {
|
|
1002
|
+
context_type: args.contextType || "work",
|
|
1003
|
+
explicit_phrases: args.explicit_phrases || [],
|
|
1004
|
+
...args.topics && { topics: args.topics }
|
|
1005
|
+
},
|
|
1006
|
+
routing: {
|
|
1007
|
+
...args.destination && { destination: args.destination },
|
|
1008
|
+
structure: args.structure || "month",
|
|
1009
|
+
filename_options: ["date", "time", "subject"]
|
|
1010
|
+
},
|
|
1011
|
+
...args.description && { description: args.description },
|
|
1012
|
+
active: true
|
|
1013
|
+
};
|
|
1014
|
+
await context.saveEntity(project);
|
|
1015
|
+
return {
|
|
1016
|
+
success: true,
|
|
1017
|
+
message: `Project "${args.name}" added successfully`,
|
|
1018
|
+
entity: formatEntity(project)
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
async function handleAddTerm(args) {
|
|
1022
|
+
const context = await create$2({
|
|
1023
|
+
startingDir: args.contextDirectory || process.cwd()
|
|
1024
|
+
});
|
|
1025
|
+
if (!context.hasContext()) {
|
|
1026
|
+
throw new Error("No .protokoll directory found. Initialize context first.");
|
|
1027
|
+
}
|
|
1028
|
+
const id = args.id || slugify(args.term);
|
|
1029
|
+
if (context.getTerm(id)) {
|
|
1030
|
+
throw new Error(`Term with ID "${id}" already exists`);
|
|
1031
|
+
}
|
|
1032
|
+
const term = {
|
|
1033
|
+
id,
|
|
1034
|
+
name: args.term,
|
|
1035
|
+
type: "term",
|
|
1036
|
+
...args.expansion && { expansion: args.expansion },
|
|
1037
|
+
...args.domain && { domain: args.domain },
|
|
1038
|
+
...args.sounds_like && { sounds_like: args.sounds_like },
|
|
1039
|
+
...args.projects && { projects: args.projects }
|
|
1040
|
+
};
|
|
1041
|
+
await context.saveEntity(term);
|
|
1042
|
+
return {
|
|
1043
|
+
success: true,
|
|
1044
|
+
message: `Term "${args.term}" added successfully`,
|
|
1045
|
+
entity: formatEntity(term)
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
async function handleAddCompany(args) {
|
|
1049
|
+
const context = await create$2({
|
|
1050
|
+
startingDir: args.contextDirectory || process.cwd()
|
|
1051
|
+
});
|
|
1052
|
+
if (!context.hasContext()) {
|
|
1053
|
+
throw new Error("No .protokoll directory found. Initialize context first.");
|
|
1054
|
+
}
|
|
1055
|
+
const id = args.id || slugify(args.name);
|
|
1056
|
+
if (context.getCompany(id)) {
|
|
1057
|
+
throw new Error(`Company with ID "${id}" already exists`);
|
|
1058
|
+
}
|
|
1059
|
+
const company = {
|
|
1060
|
+
id,
|
|
1061
|
+
name: args.name,
|
|
1062
|
+
type: "company",
|
|
1063
|
+
...args.fullName && { fullName: args.fullName },
|
|
1064
|
+
...args.industry && { industry: args.industry },
|
|
1065
|
+
...args.sounds_like && { sounds_like: args.sounds_like }
|
|
1066
|
+
};
|
|
1067
|
+
await context.saveEntity(company);
|
|
1068
|
+
return {
|
|
1069
|
+
success: true,
|
|
1070
|
+
message: `Company "${args.name}" added successfully`,
|
|
1071
|
+
entity: formatEntity(company)
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
async function handleDeleteEntity(args) {
|
|
1075
|
+
const context = await create$2({
|
|
1076
|
+
startingDir: args.contextDirectory || process.cwd()
|
|
1077
|
+
});
|
|
1078
|
+
let entity;
|
|
1079
|
+
switch (args.entityType) {
|
|
1080
|
+
case "project":
|
|
1081
|
+
entity = context.getProject(args.entityId);
|
|
1082
|
+
break;
|
|
1083
|
+
case "person":
|
|
1084
|
+
entity = context.getPerson(args.entityId);
|
|
1085
|
+
break;
|
|
1086
|
+
case "term":
|
|
1087
|
+
entity = context.getTerm(args.entityId);
|
|
1088
|
+
break;
|
|
1089
|
+
case "company":
|
|
1090
|
+
entity = context.getCompany(args.entityId);
|
|
1091
|
+
break;
|
|
1092
|
+
case "ignored":
|
|
1093
|
+
entity = context.getIgnored(args.entityId);
|
|
1094
|
+
break;
|
|
1095
|
+
}
|
|
1096
|
+
if (!entity) {
|
|
1097
|
+
throw new Error(`${args.entityType} "${args.entityId}" not found`);
|
|
1098
|
+
}
|
|
1099
|
+
const deleted = await context.deleteEntity(entity);
|
|
1100
|
+
if (!deleted) {
|
|
1101
|
+
throw new Error(`Failed to delete ${args.entityType} "${args.entityId}"`);
|
|
1102
|
+
}
|
|
1103
|
+
return {
|
|
1104
|
+
success: true,
|
|
1105
|
+
message: `${args.entityType} "${args.entityId}" deleted successfully`
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
async function handleEditTranscript(args) {
|
|
1109
|
+
const transcriptPath = resolve(args.transcriptPath);
|
|
1110
|
+
if (!await fileExists(transcriptPath)) {
|
|
1111
|
+
throw new Error(`Transcript not found: ${transcriptPath}`);
|
|
1112
|
+
}
|
|
1113
|
+
if (!args.title && !args.projectId) {
|
|
1114
|
+
throw new Error("Must specify title and/or projectId");
|
|
1115
|
+
}
|
|
1116
|
+
const result = await editTranscript(transcriptPath, {
|
|
1117
|
+
title: args.title,
|
|
1118
|
+
projectId: args.projectId
|
|
1119
|
+
});
|
|
1120
|
+
await mkdir(dirname(result.outputPath), { recursive: true });
|
|
1121
|
+
await writeFile(result.outputPath, result.content, "utf-8");
|
|
1122
|
+
if (result.outputPath !== transcriptPath) {
|
|
1123
|
+
await unlink(transcriptPath);
|
|
1124
|
+
}
|
|
1125
|
+
return {
|
|
1126
|
+
success: true,
|
|
1127
|
+
originalPath: transcriptPath,
|
|
1128
|
+
outputPath: result.outputPath,
|
|
1129
|
+
renamed: result.outputPath !== transcriptPath,
|
|
1130
|
+
message: result.outputPath !== transcriptPath ? `Transcript updated and moved to: ${result.outputPath}` : "Transcript updated"
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
async function handleCombineTranscripts(args) {
|
|
1134
|
+
if (args.transcriptPaths.length < 2) {
|
|
1135
|
+
throw new Error("At least 2 transcript files are required");
|
|
1136
|
+
}
|
|
1137
|
+
for (const path of args.transcriptPaths) {
|
|
1138
|
+
const resolved = resolve(path);
|
|
1139
|
+
if (!await fileExists(resolved)) {
|
|
1140
|
+
throw new Error(`Transcript not found: ${resolved}`);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
const resolvedPaths = args.transcriptPaths.map((p) => resolve(p));
|
|
1144
|
+
const result = await combineTranscripts(resolvedPaths, {
|
|
1145
|
+
title: args.title,
|
|
1146
|
+
projectId: args.projectId
|
|
1147
|
+
});
|
|
1148
|
+
await mkdir(dirname(result.outputPath), { recursive: true });
|
|
1149
|
+
await writeFile(result.outputPath, result.content, "utf-8");
|
|
1150
|
+
const deletedFiles = [];
|
|
1151
|
+
for (const path of resolvedPaths) {
|
|
1152
|
+
try {
|
|
1153
|
+
await unlink(path);
|
|
1154
|
+
deletedFiles.push(path);
|
|
1155
|
+
} catch {
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
return {
|
|
1159
|
+
success: true,
|
|
1160
|
+
outputPath: result.outputPath,
|
|
1161
|
+
sourceFiles: resolvedPaths,
|
|
1162
|
+
deletedFiles,
|
|
1163
|
+
message: `Combined ${resolvedPaths.length} transcripts into: ${result.outputPath}`
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
async function handleReadTranscript(args) {
|
|
1167
|
+
const transcriptPath = resolve(args.transcriptPath);
|
|
1168
|
+
if (!await fileExists(transcriptPath)) {
|
|
1169
|
+
throw new Error(`Transcript not found: ${transcriptPath}`);
|
|
1170
|
+
}
|
|
1171
|
+
const parsed = await parseTranscript(transcriptPath);
|
|
1172
|
+
return {
|
|
1173
|
+
filePath: transcriptPath,
|
|
1174
|
+
title: parsed.title,
|
|
1175
|
+
metadata: parsed.metadata,
|
|
1176
|
+
content: parsed.content,
|
|
1177
|
+
contentLength: parsed.content.length
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
async function handleProvideFeedback(args) {
|
|
1181
|
+
const transcriptPath = resolve(args.transcriptPath);
|
|
1182
|
+
if (!await fileExists(transcriptPath)) {
|
|
1183
|
+
throw new Error(`Transcript not found: ${transcriptPath}`);
|
|
1184
|
+
}
|
|
1185
|
+
const transcriptContent = await readFile(transcriptPath, "utf-8");
|
|
1186
|
+
const context = await create$2({
|
|
1187
|
+
startingDir: args.contextDirectory || dirname(transcriptPath)
|
|
1188
|
+
});
|
|
1189
|
+
const reasoning = create$3({ model: args.model || DEFAULT_MODEL });
|
|
1190
|
+
const feedbackCtx = {
|
|
1191
|
+
transcriptPath,
|
|
1192
|
+
transcriptContent,
|
|
1193
|
+
originalContent: transcriptContent,
|
|
1194
|
+
context,
|
|
1195
|
+
changes: [],
|
|
1196
|
+
verbose: false,
|
|
1197
|
+
dryRun: false
|
|
1198
|
+
};
|
|
1199
|
+
await processFeedback(args.feedback, feedbackCtx, reasoning);
|
|
1200
|
+
let result = null;
|
|
1201
|
+
if (feedbackCtx.changes.length > 0) {
|
|
1202
|
+
result = await applyChanges(feedbackCtx);
|
|
1203
|
+
}
|
|
1204
|
+
return {
|
|
1205
|
+
success: true,
|
|
1206
|
+
changesApplied: feedbackCtx.changes.length,
|
|
1207
|
+
changes: feedbackCtx.changes.map((c) => ({
|
|
1208
|
+
type: c.type,
|
|
1209
|
+
description: c.description
|
|
1210
|
+
})),
|
|
1211
|
+
outputPath: result?.newPath || transcriptPath,
|
|
1212
|
+
moved: result?.moved || false
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
async function main() {
|
|
1216
|
+
const server = new Server(
|
|
1217
|
+
{
|
|
1218
|
+
name: "protokoll",
|
|
1219
|
+
version: "0.0.1",
|
|
1220
|
+
description: "Intelligent audio transcription with context-aware enhancement. Process audio files through a pipeline that transcribes with Whisper, then enhances using LLMs with knowledge of your people, projects, and terminology. Manage context entities (people, projects, terms) to improve recognition. Edit and combine existing transcripts."
|
|
1221
|
+
},
|
|
1222
|
+
{
|
|
1223
|
+
capabilities: {
|
|
1224
|
+
tools: {}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
);
|
|
1228
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
1229
|
+
tools
|
|
1230
|
+
}));
|
|
1231
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1232
|
+
const { name, arguments: args } = request.params;
|
|
1233
|
+
try {
|
|
1234
|
+
let result;
|
|
1235
|
+
switch (name) {
|
|
1236
|
+
// Discovery & Configuration
|
|
1237
|
+
case "protokoll_discover_config":
|
|
1238
|
+
result = await handleDiscoverConfig(args);
|
|
1239
|
+
break;
|
|
1240
|
+
case "protokoll_suggest_project":
|
|
1241
|
+
result = await handleSuggestProject(args);
|
|
1242
|
+
break;
|
|
1243
|
+
// Transcription
|
|
1244
|
+
case "protokoll_process_audio":
|
|
1245
|
+
result = await handleProcessAudio(args);
|
|
1246
|
+
break;
|
|
1247
|
+
case "protokoll_batch_process":
|
|
1248
|
+
result = await handleBatchProcess(args);
|
|
1249
|
+
break;
|
|
1250
|
+
// Context Status
|
|
1251
|
+
case "protokoll_context_status":
|
|
1252
|
+
result = await handleContextStatus(args);
|
|
1253
|
+
break;
|
|
1254
|
+
// Context Listing
|
|
1255
|
+
case "protokoll_list_projects":
|
|
1256
|
+
result = await handleListProjects(args);
|
|
1257
|
+
break;
|
|
1258
|
+
case "protokoll_list_people":
|
|
1259
|
+
result = await handleListPeople(args);
|
|
1260
|
+
break;
|
|
1261
|
+
case "protokoll_list_terms":
|
|
1262
|
+
result = await handleListTerms(args);
|
|
1263
|
+
break;
|
|
1264
|
+
case "protokoll_list_companies":
|
|
1265
|
+
result = await handleListCompanies(args);
|
|
1266
|
+
break;
|
|
1267
|
+
case "protokoll_search_context":
|
|
1268
|
+
result = await handleSearchContext(args);
|
|
1269
|
+
break;
|
|
1270
|
+
case "protokoll_get_entity":
|
|
1271
|
+
result = await handleGetEntity(args);
|
|
1272
|
+
break;
|
|
1273
|
+
// Context Modification
|
|
1274
|
+
case "protokoll_add_person":
|
|
1275
|
+
result = await handleAddPerson(args);
|
|
1276
|
+
break;
|
|
1277
|
+
case "protokoll_add_project":
|
|
1278
|
+
result = await handleAddProject(args);
|
|
1279
|
+
break;
|
|
1280
|
+
case "protokoll_add_term":
|
|
1281
|
+
result = await handleAddTerm(args);
|
|
1282
|
+
break;
|
|
1283
|
+
case "protokoll_add_company":
|
|
1284
|
+
result = await handleAddCompany(args);
|
|
1285
|
+
break;
|
|
1286
|
+
case "protokoll_delete_entity":
|
|
1287
|
+
result = await handleDeleteEntity(args);
|
|
1288
|
+
break;
|
|
1289
|
+
// Transcript Actions
|
|
1290
|
+
case "protokoll_edit_transcript":
|
|
1291
|
+
result = await handleEditTranscript(args);
|
|
1292
|
+
break;
|
|
1293
|
+
case "protokoll_combine_transcripts":
|
|
1294
|
+
result = await handleCombineTranscripts(args);
|
|
1295
|
+
break;
|
|
1296
|
+
case "protokoll_read_transcript":
|
|
1297
|
+
result = await handleReadTranscript(args);
|
|
1298
|
+
break;
|
|
1299
|
+
case "protokoll_provide_feedback":
|
|
1300
|
+
result = await handleProvideFeedback(args);
|
|
1301
|
+
break;
|
|
1302
|
+
default:
|
|
1303
|
+
return {
|
|
1304
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
1305
|
+
isError: true
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
return {
|
|
1309
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1310
|
+
};
|
|
1311
|
+
} catch (error) {
|
|
1312
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1313
|
+
return {
|
|
1314
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
1315
|
+
isError: true
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
});
|
|
1319
|
+
const transport = new StdioServerTransport();
|
|
1320
|
+
await server.connect(transport);
|
|
1321
|
+
}
|
|
1322
|
+
if (require.main === module) {
|
|
1323
|
+
main().catch((error) => {
|
|
1324
|
+
console.error(error);
|
|
1325
|
+
process.exit(1);
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
export { fileExists, findProtokolkConfigs, getAudioMetadata, getConfigInfo, handleAddCompany, handleAddPerson, handleAddProject, handleAddTerm, handleBatchProcess, handleCombineTranscripts, handleContextStatus, handleDeleteEntity, handleDiscoverConfig, handleEditTranscript, handleGetEntity, handleListCompanies, handleListPeople, handleListProjects, handleListTerms, handleProcessAudio, handleProvideFeedback, handleReadTranscript, handleSearchContext, handleSuggestProject, main, suggestProjectsForFile, tools };
|
|
1330
|
+
//# sourceMappingURL=server.js.map
|