@redaksjon/protokoll 0.2.0 → 0.3.0
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/README.md +57 -1
- package/dist/main.js +426 -0
- package/dist/main.js.map +1 -1
- package/dist/mcp/server.js +134 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/transcript.js +137 -9
- package/dist/transcript.js.map +1 -1
- package/docs/entity-metadata.md +42 -1
- package/docs/examples.md +172 -1
- package/guide/context-commands.md +417 -16
- package/guide/context-system.md +111 -10
- package/guide/development.md +9 -0
- package/guide/index.md +1 -0
- package/guide/routing.md +215 -0
- package/package.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Protokoll solves this.
|
|
|
18
18
|
Protokoll is an intelligent audio transcription system that uses advanced reasoning models to create highly accurate, context-enhanced transcripts. Unlike basic transcription tools, Protokoll:
|
|
19
19
|
|
|
20
20
|
- **Learns Your World**: Maintains a knowledge base of people, projects, and organizations you mention. When Whisper mishears someone, Protokoll recognizes and corrects it using phonetic variants and context awareness
|
|
21
|
-
- **Routes Intelligently**: Multi-signal classification sends notes to the right destination—work notes stay in your work folder, client calls go to client projects, personal thoughts go to personal notes
|
|
21
|
+
- **Routes Intelligently**: Multi-signal classification sends notes to the right destination—work notes stay in your work folder, client calls go to client projects, personal thoughts go to personal notes. Tags are automatically deduplicated when multiple classification signals identify the same topic
|
|
22
22
|
- **Preserves Everything**: This is NOT a summarizer. Protokoll preserves the full content of what you said while cleaning up filler words, false starts, and obvious transcription errors
|
|
23
23
|
- **Improves Over Time**: The more you use it, the smarter it gets. Build context incrementally and watch transcription quality improve session after session
|
|
24
24
|
- **Zero Configuration Start**: Works out of the box with sensible defaults. No API wrestling, no complex setup—just transcribe
|
|
@@ -583,6 +583,10 @@ protokoll term add --term "Kubernetes" --domain "devops" \
|
|
|
583
583
|
--topics "containers,orchestration,cloud-native" \
|
|
584
584
|
--projects "infrastructure"
|
|
585
585
|
|
|
586
|
+
# Edit existing entities (incremental changes)
|
|
587
|
+
protokoll project edit <id> --add-topic <topic> --add-phrase <phrase>
|
|
588
|
+
protokoll term edit <id> --add-sound <variant> --add-project <id>
|
|
589
|
+
|
|
586
590
|
# Update existing entity with new content (regenerates metadata)
|
|
587
591
|
protokoll project update redaksjon https://github.com/user/redaksjon/README.md
|
|
588
592
|
protokoll term update kubernetes https://kubernetes.io/docs/concepts/overview/
|
|
@@ -730,6 +734,35 @@ Override per-command with `--smart` or `--no-smart` flags.
|
|
|
730
734
|
- OpenAI API key set in environment (`OPENAI_API_KEY`)
|
|
731
735
|
- Network access for URL fetching and API calls
|
|
732
736
|
|
|
737
|
+
### Editing Project Classification
|
|
738
|
+
|
|
739
|
+
Modify how projects are matched to transcripts:
|
|
740
|
+
|
|
741
|
+
```bash
|
|
742
|
+
# Add trigger phrases (high-confidence matching)
|
|
743
|
+
protokoll project edit work \
|
|
744
|
+
--add-phrase "work meeting" \
|
|
745
|
+
--add-phrase "office discussion"
|
|
746
|
+
|
|
747
|
+
# Add topics (theme-based matching)
|
|
748
|
+
protokoll project edit work \
|
|
749
|
+
--add-topic standup \
|
|
750
|
+
--add-topic sprint
|
|
751
|
+
|
|
752
|
+
# Associate people (routes when mentioned)
|
|
753
|
+
protokoll project edit client-alpha \
|
|
754
|
+
--add-person priya-sharma \
|
|
755
|
+
--add-person john-smith
|
|
756
|
+
|
|
757
|
+
# Associate companies
|
|
758
|
+
protokoll project edit client-work \
|
|
759
|
+
--add-company acme-corp
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
Use `protokoll project show <id>` to see all classification fields.
|
|
763
|
+
|
|
764
|
+
**See:** [Context Commands Guide](./guide/context-commands.md#understanding-classification) for detailed explanation of each field.
|
|
765
|
+
|
|
733
766
|
### Example: Adding a Project
|
|
734
767
|
|
|
735
768
|
The interactive prompt guides you through each field with explanations:
|
|
@@ -1229,6 +1262,29 @@ projects: # Associated project IDs where this term is relevant
|
|
|
1229
1262
|
- myapp
|
|
1230
1263
|
```
|
|
1231
1264
|
|
|
1265
|
+
### Project Relationships (Optional)
|
|
1266
|
+
|
|
1267
|
+
If you have a parent project with subprojects, you can model the hierarchy. Protokoll will automatically suggest relationships when you create projects:
|
|
1268
|
+
|
|
1269
|
+
```bash
|
|
1270
|
+
# When creating a project interactively, Protokoll suggests:
|
|
1271
|
+
protokoll project add --name "Kronologi"
|
|
1272
|
+
|
|
1273
|
+
# [Suggested parent project: Redaksjon]
|
|
1274
|
+
# Reason: topic "redaksjon-subproject" indicates subproject
|
|
1275
|
+
# Confidence: high
|
|
1276
|
+
# Set "Redaksjon" as parent? (Y/n): y
|
|
1277
|
+
```
|
|
1278
|
+
|
|
1279
|
+
Or edit relationships manually:
|
|
1280
|
+
|
|
1281
|
+
```bash
|
|
1282
|
+
protokoll project edit kronologi --parent redaksjon --add-sibling protokoll
|
|
1283
|
+
```
|
|
1284
|
+
|
|
1285
|
+
This helps routing and disambiguation. Most users won't need this.
|
|
1286
|
+
|
|
1287
|
+
|
|
1232
1288
|
## Routing System
|
|
1233
1289
|
|
|
1234
1290
|
### Multi-Signal Classification
|
package/dist/main.js
CHANGED
|
@@ -418,6 +418,156 @@ async function main() {
|
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
420
|
|
|
421
|
+
const suggestRelationships = (context, options) => {
|
|
422
|
+
const allProjects = context.getAllProjects();
|
|
423
|
+
const allTerms = context.getAllTerms();
|
|
424
|
+
const suggestions = {};
|
|
425
|
+
const parentCandidates = findParentCandidates(allProjects, options);
|
|
426
|
+
if (parentCandidates.length > 0) {
|
|
427
|
+
suggestions.parent = parentCandidates[0];
|
|
428
|
+
}
|
|
429
|
+
const siblingCandidates = findSiblingCandidates(allProjects, options, suggestions.parent?.id);
|
|
430
|
+
if (siblingCandidates.length > 0) {
|
|
431
|
+
suggestions.siblings = siblingCandidates.slice(0, 5);
|
|
432
|
+
}
|
|
433
|
+
const termCandidates = findRelatedTerms(allTerms, options);
|
|
434
|
+
if (termCandidates.length > 0) {
|
|
435
|
+
suggestions.relatedTerms = termCandidates.slice(0, 5);
|
|
436
|
+
}
|
|
437
|
+
return suggestions;
|
|
438
|
+
};
|
|
439
|
+
function findParentCandidates(projects, options) {
|
|
440
|
+
const candidates = [];
|
|
441
|
+
for (const project of projects) {
|
|
442
|
+
if (project.id === options.projectId) continue;
|
|
443
|
+
const reasons = [];
|
|
444
|
+
let score = 0;
|
|
445
|
+
const projectNameLower = options.projectName.toLowerCase();
|
|
446
|
+
const candidateNameLower = project.name.toLowerCase();
|
|
447
|
+
if (projectNameLower.includes(candidateNameLower)) {
|
|
448
|
+
score += 50;
|
|
449
|
+
reasons.push(`name contains "${project.name}"`);
|
|
450
|
+
}
|
|
451
|
+
if (options.topics) {
|
|
452
|
+
const subprojectTopic = options.topics.find(
|
|
453
|
+
(t) => t.toLowerCase().includes(`${project.id}-subproject`) || t.toLowerCase().includes(`${candidateNameLower}-subproject`)
|
|
454
|
+
);
|
|
455
|
+
if (subprojectTopic) {
|
|
456
|
+
score += 100;
|
|
457
|
+
reasons.push(`topic "${subprojectTopic}" indicates subproject`);
|
|
458
|
+
}
|
|
459
|
+
const projectTopics = project.classification?.topics || [];
|
|
460
|
+
const sharedTopics = options.topics.filter(
|
|
461
|
+
(t) => projectTopics.some((pt) => pt.toLowerCase() === t.toLowerCase())
|
|
462
|
+
);
|
|
463
|
+
if (sharedTopics.length > 0) {
|
|
464
|
+
score += sharedTopics.length * 10;
|
|
465
|
+
reasons.push(`${sharedTopics.length} shared topics`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (options.destination && project.routing?.destination) {
|
|
469
|
+
if (options.destination.startsWith(project.routing.destination + "/")) {
|
|
470
|
+
score += 75;
|
|
471
|
+
reasons.push("destination is subdirectory");
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (options.description && options.description.toLowerCase().includes(candidateNameLower)) {
|
|
475
|
+
score += 25;
|
|
476
|
+
reasons.push("mentioned in description");
|
|
477
|
+
}
|
|
478
|
+
if (score > 0) {
|
|
479
|
+
candidates.push({ project, score, reasons });
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const sorted = candidates.sort((a, b) => b.score - a.score);
|
|
483
|
+
return sorted.map((c) => ({
|
|
484
|
+
id: c.project.id,
|
|
485
|
+
name: c.project.name,
|
|
486
|
+
reason: c.reasons.join(", "),
|
|
487
|
+
confidence: c.score >= 75 ? "high" : c.score >= 40 ? "medium" : "low"
|
|
488
|
+
}));
|
|
489
|
+
}
|
|
490
|
+
function findSiblingCandidates(projects, options, suggestedParentId) {
|
|
491
|
+
const candidates = [];
|
|
492
|
+
for (const project of projects) {
|
|
493
|
+
if (project.id === options.projectId) continue;
|
|
494
|
+
const reasons = [];
|
|
495
|
+
let score = 0;
|
|
496
|
+
if (suggestedParentId && project.relationships?.parent === suggestedParentId) {
|
|
497
|
+
score += 100;
|
|
498
|
+
reasons.push(`shares parent "${suggestedParentId}"`);
|
|
499
|
+
}
|
|
500
|
+
if (options.topics) {
|
|
501
|
+
const projectTopics = project.classification?.topics || [];
|
|
502
|
+
const sharedTopics = options.topics.filter(
|
|
503
|
+
(t) => projectTopics.some((pt) => pt.toLowerCase() === t.toLowerCase())
|
|
504
|
+
);
|
|
505
|
+
if (sharedTopics.length >= 2) {
|
|
506
|
+
score += sharedTopics.length * 15;
|
|
507
|
+
reasons.push(`${sharedTopics.length} shared topics`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (options.destination && project.routing?.destination) {
|
|
511
|
+
const newDir = options.destination.split("/").slice(0, -1).join("/");
|
|
512
|
+
const projDir = project.routing.destination.split("/").slice(0, -1).join("/");
|
|
513
|
+
if (newDir === projDir) {
|
|
514
|
+
score += 30;
|
|
515
|
+
reasons.push("same destination directory");
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (score > 0) {
|
|
519
|
+
candidates.push({ project, score, reasons });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const sorted = candidates.sort((a, b) => b.score - a.score);
|
|
523
|
+
return sorted.map((c) => ({
|
|
524
|
+
id: c.project.id,
|
|
525
|
+
name: c.project.name,
|
|
526
|
+
reason: c.reasons.join(", ")
|
|
527
|
+
}));
|
|
528
|
+
}
|
|
529
|
+
function findRelatedTerms(terms, options) {
|
|
530
|
+
const candidates = [];
|
|
531
|
+
for (const term of terms) {
|
|
532
|
+
const reasons = [];
|
|
533
|
+
let score = 0;
|
|
534
|
+
const projectNameLower = options.projectName.toLowerCase();
|
|
535
|
+
const termNameLower = term.name.toLowerCase();
|
|
536
|
+
if (projectNameLower.includes(termNameLower)) {
|
|
537
|
+
score += 50;
|
|
538
|
+
reasons.push("appears in project name");
|
|
539
|
+
}
|
|
540
|
+
if (options.description && options.description.toLowerCase().includes(termNameLower)) {
|
|
541
|
+
score += 40;
|
|
542
|
+
reasons.push("mentioned in description");
|
|
543
|
+
}
|
|
544
|
+
if (options.topics && term.topics) {
|
|
545
|
+
const sharedTopics = options.topics.filter(
|
|
546
|
+
(t) => term.topics.some((tt) => tt.toLowerCase() === t.toLowerCase())
|
|
547
|
+
);
|
|
548
|
+
if (sharedTopics.length > 0) {
|
|
549
|
+
score += sharedTopics.length * 20;
|
|
550
|
+
reasons.push(`${sharedTopics.length} shared topics`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (options.topics) {
|
|
554
|
+
if (options.topics.some((t) => t.toLowerCase() === termNameLower)) {
|
|
555
|
+
score += 30;
|
|
556
|
+
reasons.push("matches project topic");
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
if (score > 0) {
|
|
560
|
+
candidates.push({ term, score, reasons });
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
const sorted = candidates.sort((a, b) => b.score - a.score);
|
|
564
|
+
return sorted.map((c) => ({
|
|
565
|
+
id: c.term.id,
|
|
566
|
+
name: c.term.name,
|
|
567
|
+
reason: c.reasons.join(", ")
|
|
568
|
+
}));
|
|
569
|
+
}
|
|
570
|
+
|
|
421
571
|
const print$2 = (text) => process.stdout.write(text + "\n");
|
|
422
572
|
const calculateId = (name) => {
|
|
423
573
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
@@ -628,6 +778,12 @@ const displayEntityDetails = (entity, filePath) => {
|
|
|
628
778
|
if (project.classification.topics && project.classification.topics.length > 0) {
|
|
629
779
|
rows.push(["Topics", formatValue$1(project.classification.topics)]);
|
|
630
780
|
}
|
|
781
|
+
if (project.classification.associated_people && project.classification.associated_people.length > 0) {
|
|
782
|
+
rows.push(["Associated People", formatValue$1(project.classification.associated_people)]);
|
|
783
|
+
}
|
|
784
|
+
if (project.classification.associated_companies && project.classification.associated_companies.length > 0) {
|
|
785
|
+
rows.push(["Associated Companies", formatValue$1(project.classification.associated_companies)]);
|
|
786
|
+
}
|
|
631
787
|
}
|
|
632
788
|
if (project.routing) {
|
|
633
789
|
if (project.routing.destination) {
|
|
@@ -641,15 +797,38 @@ const displayEntityDetails = (entity, filePath) => {
|
|
|
641
797
|
if (project.sounds_like && project.sounds_like.length > 0) {
|
|
642
798
|
rows.push(["Sounds Like", formatValue$1(project.sounds_like)]);
|
|
643
799
|
}
|
|
800
|
+
if (project.relationships) {
|
|
801
|
+
const rel = project.relationships;
|
|
802
|
+
const relParts = [];
|
|
803
|
+
if (rel.parent) {
|
|
804
|
+
relParts.push(`Parent: ${rel.parent}`);
|
|
805
|
+
}
|
|
806
|
+
if (rel.children && rel.children.length > 0) {
|
|
807
|
+
relParts.push(`Children: ${rel.children.join(", ")}`);
|
|
808
|
+
}
|
|
809
|
+
if (rel.siblings && rel.siblings.length > 0) {
|
|
810
|
+
relParts.push(`Siblings: ${rel.siblings.join(", ")}`);
|
|
811
|
+
}
|
|
812
|
+
if (rel.relatedTerms && rel.relatedTerms.length > 0) {
|
|
813
|
+
relParts.push(`Related Terms: ${rel.relatedTerms.join(", ")}`);
|
|
814
|
+
}
|
|
815
|
+
if (relParts.length > 0) {
|
|
816
|
+
rows.push(["Relationships", relParts.join("\n ")]);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
644
819
|
rows.push(["Active", project.active !== false ? "true" : "false"]);
|
|
645
820
|
if (project.notes) rows.push(["Notes", project.notes]);
|
|
646
821
|
} else if (entity.type === "term") {
|
|
647
822
|
const term = entity;
|
|
648
823
|
if (term.expansion) rows.push(["Expansion", term.expansion]);
|
|
649
824
|
if (term.domain) rows.push(["Domain", term.domain]);
|
|
825
|
+
if (term.description) rows.push(["Description", term.description]);
|
|
650
826
|
if (term.sounds_like && term.sounds_like.length > 0) {
|
|
651
827
|
rows.push(["Sounds Like", formatValue$1(term.sounds_like)]);
|
|
652
828
|
}
|
|
829
|
+
if (term.topics && term.topics.length > 0) {
|
|
830
|
+
rows.push(["Topics", formatValue$1(term.topics)]);
|
|
831
|
+
}
|
|
653
832
|
if (term.projects && term.projects.length > 0) {
|
|
654
833
|
rows.push(["Projects", formatValue$1(term.projects)]);
|
|
655
834
|
}
|
|
@@ -984,6 +1163,60 @@ Description (Enter for suggested, or edit):
|
|
|
984
1163
|
}
|
|
985
1164
|
}
|
|
986
1165
|
}
|
|
1166
|
+
let relationships;
|
|
1167
|
+
if (!options.yes && (topics.length > 0 || description)) {
|
|
1168
|
+
const relationshipSuggestions = suggestRelationships(context, {
|
|
1169
|
+
projectName: name,
|
|
1170
|
+
projectId: id,
|
|
1171
|
+
topics,
|
|
1172
|
+
destination,
|
|
1173
|
+
description
|
|
1174
|
+
});
|
|
1175
|
+
if (relationshipSuggestions.parent && relationshipSuggestions.parent.confidence !== "low") {
|
|
1176
|
+
print$2(`
|
|
1177
|
+
[Suggested parent project: ${relationshipSuggestions.parent.name}]`);
|
|
1178
|
+
print$2(` Reason: ${relationshipSuggestions.parent.reason}`);
|
|
1179
|
+
print$2(` Confidence: ${relationshipSuggestions.parent.confidence}`);
|
|
1180
|
+
const parentAnswer = await askQuestion$1(rl, `Set "${relationshipSuggestions.parent.name}" as parent? (Y/n): `);
|
|
1181
|
+
if (parentAnswer.toLowerCase() !== "n" && parentAnswer.toLowerCase() !== "no") {
|
|
1182
|
+
relationships = relationships || {};
|
|
1183
|
+
relationships.parent = relationshipSuggestions.parent.id;
|
|
1184
|
+
print$2(` ✓ Parent set to "${relationshipSuggestions.parent.name}"`);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
if (relationshipSuggestions.siblings && relationshipSuggestions.siblings.length > 0) {
|
|
1188
|
+
print$2(`
|
|
1189
|
+
[Suggested sibling projects:]`);
|
|
1190
|
+
relationshipSuggestions.siblings.forEach((s, i) => {
|
|
1191
|
+
print$2(` ${i + 1}. ${s.name} (${s.reason})`);
|
|
1192
|
+
});
|
|
1193
|
+
const siblingsAnswer = await askQuestion$1(rl, "Add siblings? (Enter numbers comma-separated, or Enter to skip): ");
|
|
1194
|
+
if (siblingsAnswer.trim()) {
|
|
1195
|
+
const indices = siblingsAnswer.split(",").map((n) => parseInt(n.trim()) - 1).filter((i) => i >= 0 && i < relationshipSuggestions.siblings.length);
|
|
1196
|
+
if (indices.length > 0) {
|
|
1197
|
+
relationships = relationships || {};
|
|
1198
|
+
relationships.siblings = indices.map((i) => relationshipSuggestions.siblings[i].id);
|
|
1199
|
+
print$2(` ✓ Added ${indices.length} siblings`);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
if (relationshipSuggestions.relatedTerms && relationshipSuggestions.relatedTerms.length > 0) {
|
|
1204
|
+
print$2(`
|
|
1205
|
+
[Suggested related terms:]`);
|
|
1206
|
+
relationshipSuggestions.relatedTerms.forEach((t, i) => {
|
|
1207
|
+
print$2(` ${i + 1}. ${t.name} (${t.reason})`);
|
|
1208
|
+
});
|
|
1209
|
+
const termsAnswer = await askQuestion$1(rl, "Add related terms? (Enter numbers comma-separated, or Enter to skip): ");
|
|
1210
|
+
if (termsAnswer.trim()) {
|
|
1211
|
+
const indices = termsAnswer.split(",").map((n) => parseInt(n.trim()) - 1).filter((i) => i >= 0 && i < relationshipSuggestions.relatedTerms.length);
|
|
1212
|
+
if (indices.length > 0) {
|
|
1213
|
+
relationships = relationships || {};
|
|
1214
|
+
relationships.relatedTerms = indices.map((i) => relationshipSuggestions.relatedTerms[i].id);
|
|
1215
|
+
print$2(` ✓ Added ${indices.length} related terms`);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
987
1220
|
const project = {
|
|
988
1221
|
id,
|
|
989
1222
|
name,
|
|
@@ -1000,6 +1233,7 @@ Description (Enter for suggested, or edit):
|
|
|
1000
1233
|
},
|
|
1001
1234
|
...soundsLike.length && { sounds_like: soundsLike },
|
|
1002
1235
|
...description && { description },
|
|
1236
|
+
...relationships && { relationships },
|
|
1003
1237
|
active: true
|
|
1004
1238
|
};
|
|
1005
1239
|
await context.saveEntity(project);
|
|
@@ -1063,6 +1297,117 @@ Term "${name}" saved successfully.`);
|
|
|
1063
1297
|
rl.close();
|
|
1064
1298
|
}
|
|
1065
1299
|
};
|
|
1300
|
+
const editProject = async (context, id, options) => {
|
|
1301
|
+
const project = context.getProject(id);
|
|
1302
|
+
if (!project) {
|
|
1303
|
+
print$2(`Error: Project "${id}" not found`);
|
|
1304
|
+
process.exit(1);
|
|
1305
|
+
}
|
|
1306
|
+
const mergeArray = (existing, add, remove) => {
|
|
1307
|
+
let result = existing ? [...existing] : [];
|
|
1308
|
+
if (add && add.length > 0) {
|
|
1309
|
+
result = [...result, ...add.filter((v) => !result.includes(v))];
|
|
1310
|
+
}
|
|
1311
|
+
if (remove && remove.length > 0) {
|
|
1312
|
+
result = result.filter((v) => !remove.includes(v));
|
|
1313
|
+
}
|
|
1314
|
+
return result.length > 0 ? result : void 0;
|
|
1315
|
+
};
|
|
1316
|
+
const changes = [];
|
|
1317
|
+
const updated = { ...project };
|
|
1318
|
+
if (options.name) {
|
|
1319
|
+
updated.name = options.name;
|
|
1320
|
+
changes.push(`name: "${options.name}"`);
|
|
1321
|
+
}
|
|
1322
|
+
if (options.description) {
|
|
1323
|
+
updated.description = options.description;
|
|
1324
|
+
changes.push(`description updated`);
|
|
1325
|
+
}
|
|
1326
|
+
if (options.active) {
|
|
1327
|
+
updated.active = options.active === "true";
|
|
1328
|
+
changes.push(`active: ${options.active}`);
|
|
1329
|
+
}
|
|
1330
|
+
const classification = { ...project.classification };
|
|
1331
|
+
if (options.contextType) {
|
|
1332
|
+
classification.context_type = options.contextType;
|
|
1333
|
+
changes.push(`context_type: ${options.contextType}`);
|
|
1334
|
+
}
|
|
1335
|
+
const updatedTopics = mergeArray(classification.topics, options.addTopic, options.removeTopic);
|
|
1336
|
+
if (updatedTopics !== void 0 && updatedTopics !== classification.topics) {
|
|
1337
|
+
classification.topics = updatedTopics;
|
|
1338
|
+
if (options.addTopic?.length) changes.push(`added ${options.addTopic.length} topics`);
|
|
1339
|
+
if (options.removeTopic?.length) changes.push(`removed ${options.removeTopic.length} topics`);
|
|
1340
|
+
}
|
|
1341
|
+
const updatedPhrases = mergeArray(classification.explicit_phrases, options.addPhrase, options.removePhrase);
|
|
1342
|
+
if (updatedPhrases !== void 0 && updatedPhrases !== classification.explicit_phrases) {
|
|
1343
|
+
classification.explicit_phrases = updatedPhrases;
|
|
1344
|
+
if (options.addPhrase?.length) changes.push(`added ${options.addPhrase.length} trigger phrases`);
|
|
1345
|
+
if (options.removePhrase?.length) changes.push(`removed ${options.removePhrase.length} trigger phrases`);
|
|
1346
|
+
}
|
|
1347
|
+
const updatedPeople = mergeArray(classification.associated_people, options.addPerson, options.removePerson);
|
|
1348
|
+
if (updatedPeople !== void 0 && updatedPeople !== classification.associated_people) {
|
|
1349
|
+
classification.associated_people = updatedPeople;
|
|
1350
|
+
if (options.addPerson?.length) changes.push(`added ${options.addPerson.length} associated people`);
|
|
1351
|
+
if (options.removePerson?.length) changes.push(`removed ${options.removePerson.length} associated people`);
|
|
1352
|
+
}
|
|
1353
|
+
const updatedCompanies = mergeArray(classification.associated_companies, options.addCompany, options.removeCompany);
|
|
1354
|
+
if (updatedCompanies !== void 0 && updatedCompanies !== classification.associated_companies) {
|
|
1355
|
+
classification.associated_companies = updatedCompanies;
|
|
1356
|
+
if (options.addCompany?.length) changes.push(`added ${options.addCompany.length} associated companies`);
|
|
1357
|
+
if (options.removeCompany?.length) changes.push(`removed ${options.removeCompany.length} associated companies`);
|
|
1358
|
+
}
|
|
1359
|
+
updated.classification = classification;
|
|
1360
|
+
const routing = { ...project.routing };
|
|
1361
|
+
if (options.destination) {
|
|
1362
|
+
routing.destination = options.destination;
|
|
1363
|
+
changes.push(`destination: ${options.destination}`);
|
|
1364
|
+
}
|
|
1365
|
+
if (options.structure) {
|
|
1366
|
+
routing.structure = options.structure;
|
|
1367
|
+
changes.push(`structure: ${options.structure}`);
|
|
1368
|
+
}
|
|
1369
|
+
updated.routing = routing;
|
|
1370
|
+
if (options.parent || options.addChild || options.removeChild || options.addSibling || options.removeSibling || options.addTerm || options.removeTerm) {
|
|
1371
|
+
const existingRel = project.relationships || {};
|
|
1372
|
+
const relationships = { ...existingRel };
|
|
1373
|
+
if (options.parent) {
|
|
1374
|
+
relationships.parent = options.parent;
|
|
1375
|
+
changes.push(`parent: ${options.parent}`);
|
|
1376
|
+
}
|
|
1377
|
+
const updatedChildren = mergeArray(existingRel.children, options.addChild, options.removeChild);
|
|
1378
|
+
if (updatedChildren) {
|
|
1379
|
+
relationships.children = updatedChildren;
|
|
1380
|
+
if (options.addChild?.length) changes.push(`added ${options.addChild.length} children`);
|
|
1381
|
+
if (options.removeChild?.length) changes.push(`removed ${options.removeChild.length} children`);
|
|
1382
|
+
}
|
|
1383
|
+
const updatedSiblings = mergeArray(existingRel.siblings, options.addSibling, options.removeSibling);
|
|
1384
|
+
if (updatedSiblings) {
|
|
1385
|
+
relationships.siblings = updatedSiblings;
|
|
1386
|
+
if (options.addSibling?.length) changes.push(`added ${options.addSibling.length} siblings`);
|
|
1387
|
+
if (options.removeSibling?.length) changes.push(`removed ${options.removeSibling.length} siblings`);
|
|
1388
|
+
}
|
|
1389
|
+
const updatedRelatedTerms = mergeArray(existingRel.relatedTerms, options.addTerm, options.removeTerm);
|
|
1390
|
+
if (updatedRelatedTerms) {
|
|
1391
|
+
relationships.relatedTerms = updatedRelatedTerms;
|
|
1392
|
+
if (options.addTerm?.length) changes.push(`added ${options.addTerm.length} related terms`);
|
|
1393
|
+
if (options.removeTerm?.length) changes.push(`removed ${options.removeTerm.length} related terms`);
|
|
1394
|
+
}
|
|
1395
|
+
updated.relationships = relationships;
|
|
1396
|
+
}
|
|
1397
|
+
updated.updatedAt = /* @__PURE__ */ new Date();
|
|
1398
|
+
await context.saveEntity(updated);
|
|
1399
|
+
if (changes.length > 0) {
|
|
1400
|
+
print$2(`
|
|
1401
|
+
✓ Updated project "${project.name}":`);
|
|
1402
|
+
changes.forEach((c) => print$2(` • ${c}`));
|
|
1403
|
+
print$2("");
|
|
1404
|
+
} else {
|
|
1405
|
+
print$2(`
|
|
1406
|
+
No changes made to project "${project.name}"
|
|
1407
|
+
`);
|
|
1408
|
+
}
|
|
1409
|
+
displayEntityDetails(updated);
|
|
1410
|
+
};
|
|
1066
1411
|
const updateProject = async (context, id, source, options) => {
|
|
1067
1412
|
const existingProject = context.getProject(id);
|
|
1068
1413
|
if (!existingProject) {
|
|
@@ -1112,6 +1457,68 @@ Updating project: ${existingProject.name} (${existingProject.id})`);
|
|
|
1112
1457
|
print$2(`
|
|
1113
1458
|
✓ Project "${existingProject.name}" updated successfully.`);
|
|
1114
1459
|
};
|
|
1460
|
+
const editTerm = async (context, id, options) => {
|
|
1461
|
+
const term = context.getTerm(id);
|
|
1462
|
+
if (!term) {
|
|
1463
|
+
print$2(`Error: Term "${id}" not found`);
|
|
1464
|
+
process.exit(1);
|
|
1465
|
+
}
|
|
1466
|
+
const mergeArray = (existing, add, remove) => {
|
|
1467
|
+
let result = existing ? [...existing] : [];
|
|
1468
|
+
if (add && add.length > 0) {
|
|
1469
|
+
result = [...result, ...add.filter((v) => !result.includes(v))];
|
|
1470
|
+
}
|
|
1471
|
+
if (remove && remove.length > 0) {
|
|
1472
|
+
result = result.filter((v) => !remove.includes(v));
|
|
1473
|
+
}
|
|
1474
|
+
return result.length > 0 ? result : void 0;
|
|
1475
|
+
};
|
|
1476
|
+
const changes = [];
|
|
1477
|
+
const updated = { ...term };
|
|
1478
|
+
if (options.description) {
|
|
1479
|
+
updated.description = options.description;
|
|
1480
|
+
changes.push("description updated");
|
|
1481
|
+
}
|
|
1482
|
+
if (options.domain) {
|
|
1483
|
+
updated.domain = options.domain;
|
|
1484
|
+
changes.push(`domain: ${options.domain}`);
|
|
1485
|
+
}
|
|
1486
|
+
if (options.expansion) {
|
|
1487
|
+
updated.expansion = options.expansion;
|
|
1488
|
+
changes.push(`expansion: ${options.expansion}`);
|
|
1489
|
+
}
|
|
1490
|
+
const updatedSounds = mergeArray(term.sounds_like, options.addSound, options.removeSound);
|
|
1491
|
+
if (updatedSounds !== term.sounds_like) {
|
|
1492
|
+
updated.sounds_like = updatedSounds;
|
|
1493
|
+
if (options.addSound?.length) changes.push(`added ${options.addSound.length} sounds_like variants`);
|
|
1494
|
+
if (options.removeSound?.length) changes.push(`removed ${options.removeSound.length} sounds_like variants`);
|
|
1495
|
+
}
|
|
1496
|
+
const updatedTopics = mergeArray(term.topics, options.addTopic, options.removeTopic);
|
|
1497
|
+
if (updatedTopics !== term.topics) {
|
|
1498
|
+
updated.topics = updatedTopics;
|
|
1499
|
+
if (options.addTopic?.length) changes.push(`added ${options.addTopic.length} topics`);
|
|
1500
|
+
if (options.removeTopic?.length) changes.push(`removed ${options.removeTopic.length} topics`);
|
|
1501
|
+
}
|
|
1502
|
+
const updatedProjects = mergeArray(term.projects, options.addProject, options.removeProject);
|
|
1503
|
+
if (updatedProjects !== term.projects) {
|
|
1504
|
+
updated.projects = updatedProjects;
|
|
1505
|
+
if (options.addProject?.length) changes.push(`added ${options.addProject.length} project associations`);
|
|
1506
|
+
if (options.removeProject?.length) changes.push(`removed ${options.removeProject.length} project associations`);
|
|
1507
|
+
}
|
|
1508
|
+
updated.updatedAt = /* @__PURE__ */ new Date();
|
|
1509
|
+
await context.saveEntity(updated);
|
|
1510
|
+
if (changes.length > 0) {
|
|
1511
|
+
print$2(`
|
|
1512
|
+
✓ Updated term "${term.name}":`);
|
|
1513
|
+
changes.forEach((c) => print$2(` • ${c}`));
|
|
1514
|
+
print$2("");
|
|
1515
|
+
} else {
|
|
1516
|
+
print$2(`
|
|
1517
|
+
No changes made to term "${term.name}"
|
|
1518
|
+
`);
|
|
1519
|
+
}
|
|
1520
|
+
displayEntityDetails(updated);
|
|
1521
|
+
};
|
|
1115
1522
|
const updateTerm = async (context, id, source, options) => {
|
|
1116
1523
|
const existingTerm = context.getTerm(id);
|
|
1117
1524
|
if (!existingTerm) {
|
|
@@ -1396,6 +1803,14 @@ const createProjectCommand = () => {
|
|
|
1396
1803
|
}
|
|
1397
1804
|
await addProject(context, { source, ...cmdOptions });
|
|
1398
1805
|
});
|
|
1806
|
+
cmd.command("edit <id>").description("Edit project fields (classification, routing, relationships)").option("--name <name>", "Update project name").option("--description <text>", "Update description").option("--destination <path>", "Update routing destination").option("--structure <type>", "Update directory structure: none, year, month, day").option("--context-type <type>", "Update context type: work, personal, mixed").option("--add-topic <topic>", "Add a classification topic (repeatable)", collect, []).option("--remove-topic <topic>", "Remove a classification topic (repeatable)", collect, []).option("--add-phrase <phrase>", "Add trigger phrase (repeatable)", collect, []).option("--remove-phrase <phrase>", "Remove trigger phrase (repeatable)", collect, []).option("--add-person <id>", "Associate person ID (repeatable)", collect, []).option("--remove-person <id>", "Remove associated person (repeatable)", collect, []).option("--add-company <id>", "Associate company ID (repeatable)", collect, []).option("--remove-company <id>", "Remove associated company (repeatable)", collect, []).option("--parent <id>", "Set parent project ID").option("--add-child <id>", "Add child project (repeatable)", collect, []).option("--remove-child <id>", "Remove child project (repeatable)", collect, []).option("--add-sibling <id>", "Add sibling project (repeatable)", collect, []).option("--remove-sibling <id>", "Remove sibling project (repeatable)", collect, []).option("--add-term <id>", "Add related term (repeatable)", collect, []).option("--remove-term <id>", "Remove related term (repeatable)", collect, []).option("--active <bool>", "Set active status (true/false)").action(async (id, options) => {
|
|
1807
|
+
const context = await create$5();
|
|
1808
|
+
if (!context.hasContext()) {
|
|
1809
|
+
print$2("Error: No .protokoll directory found.");
|
|
1810
|
+
process.exit(1);
|
|
1811
|
+
}
|
|
1812
|
+
await editProject(context, id, options);
|
|
1813
|
+
});
|
|
1399
1814
|
cmd.command("delete <id>").description("Delete a project").option("-f, --force", "Skip confirmation").action(async (id, options) => {
|
|
1400
1815
|
const context = await create$5();
|
|
1401
1816
|
await deleteEntity(context, "project", id, options);
|
|
@@ -1410,6 +1825,9 @@ const createProjectCommand = () => {
|
|
|
1410
1825
|
});
|
|
1411
1826
|
return cmd;
|
|
1412
1827
|
};
|
|
1828
|
+
function collect(value, previous) {
|
|
1829
|
+
return previous.concat([value]);
|
|
1830
|
+
}
|
|
1413
1831
|
const createTermCommand = () => {
|
|
1414
1832
|
const cmd = new Command("term").description("Manage terms");
|
|
1415
1833
|
cmd.command("list").description("List all terms").option("-v, --verbose", "Show full details").action(async (options) => {
|
|
@@ -1428,6 +1846,14 @@ const createTermCommand = () => {
|
|
|
1428
1846
|
}
|
|
1429
1847
|
await addTermEnhanced(context, { source, ...cmdOptions });
|
|
1430
1848
|
});
|
|
1849
|
+
cmd.command("edit <id>").description("Edit term fields (sounds_like, projects, metadata)").option("--description <text>", "Update description").option("--domain <domain>", "Update domain").option("--expansion <text>", "Update expansion").option("--add-sound <variant>", "Add sounds_like variant (repeatable)", collect, []).option("--remove-sound <variant>", "Remove sounds_like variant (repeatable)", collect, []).option("--add-topic <topic>", "Add topic (repeatable)", collect, []).option("--remove-topic <topic>", "Remove topic (repeatable)", collect, []).option("--add-project <id>", "Associate with project (repeatable)", collect, []).option("--remove-project <id>", "Remove project association (repeatable)", collect, []).action(async (id, options) => {
|
|
1850
|
+
const context = await create$5();
|
|
1851
|
+
if (!context.hasContext()) {
|
|
1852
|
+
print$2("Error: No .protokoll directory found.");
|
|
1853
|
+
process.exit(1);
|
|
1854
|
+
}
|
|
1855
|
+
await editTerm(context, id, options);
|
|
1856
|
+
});
|
|
1431
1857
|
cmd.command("delete <id>").description("Delete a term").option("-f, --force", "Skip confirmation").action(async (id, options) => {
|
|
1432
1858
|
const context = await create$5();
|
|
1433
1859
|
await deleteEntity(context, "term", id, options);
|