zam-core 0.3.5 → 0.3.7
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/.agent/skills/zam/SKILL.md +7 -2
- package/.agents/skills/zam/SKILL.md +9 -3
- package/.claude/skills/zam/SKILL.md +38 -3
- package/dist/cli/index.js +410 -373
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +47 -9
- package/dist/index.js +224 -110
- package/dist/index.js.map +1 -1
- package/package.json +21 -3
- package/templates/personal/README.md +31 -0
- package/templates/personal/beliefs/README.md +19 -0
- package/templates/personal/goals/README.md +19 -0
- package/templates/personal/package.json +9 -0
package/dist/cli/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
5
|
+
import { dirname as dirname6, join as join15 } from "path";
|
|
6
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4
7
|
import { Command as Command19 } from "commander";
|
|
5
8
|
|
|
6
9
|
// src/cli/commands/bridge.ts
|
|
@@ -230,9 +233,10 @@ async function fetchActiveWorkItems(config) {
|
|
|
230
233
|
|
|
231
234
|
// src/kernel/db/connection.ts
|
|
232
235
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync } from "fs";
|
|
236
|
+
import { createRequire } from "module";
|
|
233
237
|
import { homedir as homedir2 } from "os";
|
|
234
238
|
import { dirname as dirname2, join as join2 } from "path";
|
|
235
|
-
import
|
|
239
|
+
import BetterSqlite3 from "better-sqlite3";
|
|
236
240
|
|
|
237
241
|
// src/kernel/db/schema.ts
|
|
238
242
|
var SCHEMA = `
|
|
@@ -348,9 +352,24 @@ CREATE INDEX IF NOT EXISTS idx_session_steps_session ON session_steps(session_id
|
|
|
348
352
|
// src/kernel/db/connection.ts
|
|
349
353
|
var DEFAULT_DB_DIR = join2(homedir2(), ".zam");
|
|
350
354
|
var DEFAULT_DB_PATH = join2(DEFAULT_DB_DIR, "zam.db");
|
|
355
|
+
var require2 = createRequire(import.meta.url);
|
|
351
356
|
function isRemoteDatabasePath(dbPath) {
|
|
352
357
|
return /^(libsql|https?):\/\//i.test(dbPath);
|
|
353
358
|
}
|
|
359
|
+
function openLocalSqlite(dbPath) {
|
|
360
|
+
return new BetterSqlite3(dbPath);
|
|
361
|
+
}
|
|
362
|
+
function loadLibsql() {
|
|
363
|
+
try {
|
|
364
|
+
const module = require2("libsql");
|
|
365
|
+
return "default" in module ? module.default : module;
|
|
366
|
+
} catch (err) {
|
|
367
|
+
const detail = err instanceof Error ? ` ${err.message}` : "";
|
|
368
|
+
throw new Error(
|
|
369
|
+
`Turso sync requires the optional native libsql backend, which is not available for ${process.platform}/${process.arch}.${detail}`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
354
373
|
function openDatabase(options = {}) {
|
|
355
374
|
const configuredCloud = options.useConfiguredCloud !== false && !options.dbPath && !options.syncUrl ? getTursoCredentials() : null;
|
|
356
375
|
let requiresTurso = false;
|
|
@@ -398,19 +417,24 @@ function openDatabase(options = {}) {
|
|
|
398
417
|
dbOpts.authToken = authToken;
|
|
399
418
|
}
|
|
400
419
|
let db;
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
420
|
+
if (isRemote || isEmbeddedReplica) {
|
|
421
|
+
const LibsqlDatabase = loadLibsql();
|
|
422
|
+
try {
|
|
423
|
+
db = new LibsqlDatabase(dbPath, dbOpts);
|
|
424
|
+
} catch (err) {
|
|
425
|
+
const msg = err.message;
|
|
426
|
+
if (msg.includes("InvalidLocalState") && options.syncUrl) {
|
|
427
|
+
const metaPath = `${dbPath}.meta`;
|
|
428
|
+
const infoPath = `${dbPath}-info`;
|
|
429
|
+
if (existsSync2(metaPath)) rmSync(metaPath);
|
|
430
|
+
if (existsSync2(infoPath)) rmSync(infoPath);
|
|
431
|
+
db = new LibsqlDatabase(dbPath, dbOpts);
|
|
432
|
+
} else {
|
|
433
|
+
throw err;
|
|
434
|
+
}
|
|
413
435
|
}
|
|
436
|
+
} else {
|
|
437
|
+
db = openLocalSqlite(dbPath);
|
|
414
438
|
}
|
|
415
439
|
if (!isRemote && !isEmbeddedReplica) {
|
|
416
440
|
db.pragma("journal_mode = WAL");
|
|
@@ -420,7 +444,7 @@ function openDatabase(options = {}) {
|
|
|
420
444
|
db.pragma("busy_timeout = 5000");
|
|
421
445
|
}
|
|
422
446
|
if (isEmbeddedReplica) {
|
|
423
|
-
db.sync();
|
|
447
|
+
db.sync?.();
|
|
424
448
|
}
|
|
425
449
|
if (options.initialize) {
|
|
426
450
|
db.exec(SCHEMA);
|
|
@@ -794,10 +818,47 @@ function getDueCards(db, userId, now) {
|
|
|
794
818
|
}
|
|
795
819
|
|
|
796
820
|
// src/kernel/models/prerequisite.ts
|
|
821
|
+
function buildAncestorMap(db) {
|
|
822
|
+
const rows = db.prepare("SELECT token_id, requires_id FROM prerequisites").all();
|
|
823
|
+
const map = /* @__PURE__ */ new Map();
|
|
824
|
+
for (const row of rows) {
|
|
825
|
+
let ancestors = map.get(row.token_id);
|
|
826
|
+
if (!ancestors) {
|
|
827
|
+
ancestors = /* @__PURE__ */ new Set();
|
|
828
|
+
map.set(row.token_id, ancestors);
|
|
829
|
+
}
|
|
830
|
+
ancestors.add(row.requires_id);
|
|
831
|
+
}
|
|
832
|
+
return map;
|
|
833
|
+
}
|
|
834
|
+
function wouldCreateCycle(db, tokenId, requiresId) {
|
|
835
|
+
if (tokenId === requiresId) return true;
|
|
836
|
+
const ancestors = buildAncestorMap(db);
|
|
837
|
+
const visited = /* @__PURE__ */ new Set();
|
|
838
|
+
const queue = [requiresId];
|
|
839
|
+
while (queue.length > 0) {
|
|
840
|
+
const current = queue.shift();
|
|
841
|
+
if (current === tokenId) return true;
|
|
842
|
+
if (visited.has(current)) continue;
|
|
843
|
+
visited.add(current);
|
|
844
|
+
const parents = ancestors.get(current);
|
|
845
|
+
if (parents) {
|
|
846
|
+
for (const parent of parents) {
|
|
847
|
+
if (!visited.has(parent)) queue.push(parent);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return false;
|
|
852
|
+
}
|
|
797
853
|
function addPrerequisite(db, tokenId, requiresId) {
|
|
798
854
|
if (tokenId === requiresId) {
|
|
799
855
|
throw new Error("A token cannot be a prerequisite of itself");
|
|
800
856
|
}
|
|
857
|
+
if (wouldCreateCycle(db, tokenId, requiresId)) {
|
|
858
|
+
throw new Error(
|
|
859
|
+
`Cannot add prerequisite: would create a cycle. ${requiresId} already depends on ${tokenId} (directly or transitively).`
|
|
860
|
+
);
|
|
861
|
+
}
|
|
801
862
|
db.prepare(
|
|
802
863
|
"INSERT OR IGNORE INTO prerequisites (token_id, requires_id) VALUES (?, ?)"
|
|
803
864
|
).run(tokenId, requiresId);
|
|
@@ -1080,23 +1141,51 @@ function deleteToken(db, slug) {
|
|
|
1080
1141
|
}
|
|
1081
1142
|
function findTokens(db, query) {
|
|
1082
1143
|
const normalised = query.toLowerCase();
|
|
1083
|
-
const
|
|
1084
|
-
|
|
1085
|
-
);
|
|
1086
|
-
const
|
|
1087
|
-
const
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1144
|
+
const searchTokens = normalised.split(/[\s,.\-_/\\:;!?()[\]{}]+/).filter((t2) => t2.length > 0);
|
|
1145
|
+
if (searchTokens.length === 0) return [];
|
|
1146
|
+
const shortTerms = searchTokens.filter((t2) => t2.length <= 2);
|
|
1147
|
+
const longTerms = searchTokens.filter((t2) => t2.length > 2);
|
|
1148
|
+
const scoreMap = /* @__PURE__ */ new Map();
|
|
1149
|
+
const likeSQL = `SELECT * FROM tokens WHERE deprecated_at IS NULL AND (lower(slug) LIKE ? OR lower(concept) LIKE ? OR lower(domain) LIKE ?)`;
|
|
1150
|
+
for (const term of longTerms) {
|
|
1151
|
+
const pattern = `%${term}%`;
|
|
1152
|
+
const rows = db.prepare(likeSQL).all(pattern, pattern, pattern);
|
|
1153
|
+
for (const row of rows) {
|
|
1154
|
+
const entry = scoreMap.get(row.id);
|
|
1155
|
+
if (entry) {
|
|
1156
|
+
entry.score++;
|
|
1157
|
+
} else {
|
|
1158
|
+
scoreMap.set(row.id, { token: row, score: 1 });
|
|
1159
|
+
}
|
|
1093
1160
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1161
|
+
}
|
|
1162
|
+
if (shortTerms.length > 0 || longTerms.length === 0) {
|
|
1163
|
+
const allTokens = db.prepare("SELECT * FROM tokens WHERE deprecated_at IS NULL").all();
|
|
1164
|
+
for (const token of allTokens) {
|
|
1165
|
+
const words = `${token.slug} ${token.concept} ${token.domain}`.toLowerCase().split(/[\s,.\-_/\\:;!?()[\]{}]+/).filter(Boolean);
|
|
1166
|
+
let matchCount = 0;
|
|
1167
|
+
for (const term of shortTerms.length > 0 ? shortTerms : searchTokens) {
|
|
1168
|
+
for (const w of words) {
|
|
1169
|
+
if (w === term) matchCount++;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
if (matchCount > 0) {
|
|
1173
|
+
const entry = scoreMap.get(token.id);
|
|
1174
|
+
if (entry) {
|
|
1175
|
+
entry.score += matchCount;
|
|
1176
|
+
} else {
|
|
1177
|
+
scoreMap.set(token.id, { token, score: matchCount });
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1096
1180
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1181
|
+
}
|
|
1182
|
+
const scored = [];
|
|
1183
|
+
for (const { token, score } of scoreMap.values()) {
|
|
1184
|
+
let finalScore = score;
|
|
1185
|
+
if (token.concept.toLowerCase().includes(normalised.slice(0, 25))) {
|
|
1186
|
+
finalScore += 3;
|
|
1099
1187
|
}
|
|
1188
|
+
scored.push({ score: finalScore, ...token });
|
|
1100
1189
|
}
|
|
1101
1190
|
scored.sort((a, b) => b.score - a.score);
|
|
1102
1191
|
return scored;
|
|
@@ -2510,7 +2599,8 @@ import {
|
|
|
2510
2599
|
copyFileSync,
|
|
2511
2600
|
existsSync as existsSync6,
|
|
2512
2601
|
mkdirSync as mkdirSync4,
|
|
2513
|
-
readFileSync as readFileSync6
|
|
2602
|
+
readFileSync as readFileSync6,
|
|
2603
|
+
writeFileSync as writeFileSync3
|
|
2514
2604
|
} from "fs";
|
|
2515
2605
|
import { homedir as homedir4 } from "os";
|
|
2516
2606
|
import { join as join6 } from "path";
|
|
@@ -2626,102 +2716,82 @@ function distributeGlobalSkills(home = HOME) {
|
|
|
2626
2716
|
}
|
|
2627
2717
|
return results;
|
|
2628
2718
|
}
|
|
2629
|
-
|
|
2630
|
-
const results = [];
|
|
2631
|
-
const hookLine = `
|
|
2719
|
+
var POSIX_OLD_HOOK = `
|
|
2632
2720
|
# ZAM Shell Observation Hooks
|
|
2633
2721
|
if (command -v zam >/dev/null 2>&1); then eval "$(zam monitor start --quiet)"; fi
|
|
2634
2722
|
`;
|
|
2635
|
-
|
|
2723
|
+
var POWERSHELL_OLD_HOOK = `
|
|
2636
2724
|
# ZAM Shell Observation Hooks
|
|
2637
2725
|
if (Get-Command zam -ErrorAction SilentlyContinue) { Invoke-Expression (& zam monitor start --quiet pwsh) }
|
|
2638
2726
|
`;
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
}
|
|
2727
|
+
var HOOK_MARKER = "# ZAM Monitor Session Helper";
|
|
2728
|
+
function posixHook(shell) {
|
|
2729
|
+
return `
|
|
2730
|
+
${HOOK_MARKER}
|
|
2731
|
+
zam-monitor-session() {
|
|
2732
|
+
local session_id="\${1:-}"
|
|
2733
|
+
if [ -z "$session_id" ]; then
|
|
2734
|
+
printf 'Usage: zam-monitor-session <session-id>
|
|
2735
|
+
' >&2
|
|
2736
|
+
return 2
|
|
2737
|
+
fi
|
|
2738
|
+
eval "$(command zam monitor start --session "$session_id" --shell ${shell})"
|
|
2739
|
+
}
|
|
2740
|
+
`;
|
|
2741
|
+
}
|
|
2742
|
+
var POWERSHELL_HOOK = `
|
|
2743
|
+
${HOOK_MARKER}
|
|
2744
|
+
function Start-ZamMonitor {
|
|
2745
|
+
param([Parameter(Mandatory = $true)][string]$Session)
|
|
2746
|
+
Invoke-Expression (& zam monitor start --session $Session --shell pwsh)
|
|
2747
|
+
}
|
|
2748
|
+
`;
|
|
2749
|
+
function installHook(file, hook, oldHook) {
|
|
2750
|
+
try {
|
|
2751
|
+
const content = existsSync6(file) ? readFileSync6(file, "utf8") : "";
|
|
2752
|
+
if (content.includes(HOOK_MARKER)) {
|
|
2753
|
+
return { success: true, alreadyHooked: true };
|
|
2666
2754
|
}
|
|
2755
|
+
if (content.includes(oldHook.trim())) {
|
|
2756
|
+
writeFileSync3(file, content.replace(oldHook.trim(), hook.trim()), "utf8");
|
|
2757
|
+
} else {
|
|
2758
|
+
appendFileSync2(file, hook);
|
|
2759
|
+
}
|
|
2760
|
+
return { success: true, alreadyHooked: false };
|
|
2761
|
+
} catch {
|
|
2762
|
+
return { success: false, alreadyHooked: false };
|
|
2667
2763
|
}
|
|
2668
|
-
|
|
2764
|
+
}
|
|
2765
|
+
function injectShellHooks(home = HOME) {
|
|
2766
|
+
const results = [];
|
|
2767
|
+
const zshrc = join6(home, ".zshrc");
|
|
2768
|
+
if (existsSync6(zshrc)) {
|
|
2769
|
+
const status = installHook(zshrc, posixHook("zsh"), POSIX_OLD_HOOK);
|
|
2770
|
+
results.push({ shell: "zsh", file: zshrc, ...status });
|
|
2771
|
+
}
|
|
2772
|
+
const bashrc = join6(home, ".bashrc");
|
|
2669
2773
|
if (existsSync6(bashrc)) {
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
if (content.includes("zam monitor start")) {
|
|
2673
|
-
results.push({
|
|
2674
|
-
shell: "bash",
|
|
2675
|
-
file: bashrc,
|
|
2676
|
-
success: true,
|
|
2677
|
-
alreadyHooked: true
|
|
2678
|
-
});
|
|
2679
|
-
} else {
|
|
2680
|
-
appendFileSync2(bashrc, hookLine);
|
|
2681
|
-
results.push({
|
|
2682
|
-
shell: "bash",
|
|
2683
|
-
file: bashrc,
|
|
2684
|
-
success: true,
|
|
2685
|
-
alreadyHooked: false
|
|
2686
|
-
});
|
|
2687
|
-
}
|
|
2688
|
-
} catch {
|
|
2689
|
-
results.push({
|
|
2690
|
-
shell: "bash",
|
|
2691
|
-
file: bashrc,
|
|
2692
|
-
success: false,
|
|
2693
|
-
alreadyHooked: false
|
|
2694
|
-
});
|
|
2695
|
-
}
|
|
2774
|
+
const status = installHook(bashrc, posixHook("bash"), POSIX_OLD_HOOK);
|
|
2775
|
+
results.push({ shell: "bash", file: bashrc, ...status });
|
|
2696
2776
|
}
|
|
2697
2777
|
const pwshDirs = [
|
|
2698
|
-
join6(
|
|
2699
|
-
join6(
|
|
2778
|
+
join6(home, "Documents", "PowerShell"),
|
|
2779
|
+
join6(home, "Documents", "WindowsPowerShell")
|
|
2700
2780
|
];
|
|
2701
2781
|
for (const dir of pwshDirs) {
|
|
2702
2782
|
const profileFile = join6(dir, "Microsoft.PowerShell_profile.ps1");
|
|
2703
2783
|
try {
|
|
2704
2784
|
mkdirSync4(dir, { recursive: true });
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
});
|
|
2716
|
-
} else {
|
|
2717
|
-
appendFileSync2(profileFile, pwshHookLine);
|
|
2718
|
-
results.push({
|
|
2719
|
-
shell: "powershell",
|
|
2720
|
-
file: profileFile,
|
|
2721
|
-
success: true,
|
|
2722
|
-
alreadyHooked: false
|
|
2723
|
-
});
|
|
2724
|
-
}
|
|
2785
|
+
const status = installHook(
|
|
2786
|
+
profileFile,
|
|
2787
|
+
POWERSHELL_HOOK,
|
|
2788
|
+
POWERSHELL_OLD_HOOK
|
|
2789
|
+
);
|
|
2790
|
+
results.push({
|
|
2791
|
+
shell: "powershell",
|
|
2792
|
+
file: profileFile,
|
|
2793
|
+
...status
|
|
2794
|
+
});
|
|
2725
2795
|
} catch {
|
|
2726
2796
|
results.push({
|
|
2727
2797
|
shell: "powershell",
|
|
@@ -2943,7 +3013,7 @@ function t(locale, key, params = {}) {
|
|
|
2943
3013
|
}
|
|
2944
3014
|
|
|
2945
3015
|
// src/kernel/system/installer.ts
|
|
2946
|
-
import { execSync } from "child_process";
|
|
3016
|
+
import { execFileSync, execSync } from "child_process";
|
|
2947
3017
|
import { existsSync as existsSync7 } from "fs";
|
|
2948
3018
|
import { homedir as homedir5 } from "os";
|
|
2949
3019
|
import { join as join7 } from "path";
|
|
@@ -3048,6 +3118,51 @@ function installOllama() {
|
|
|
3048
3118
|
}
|
|
3049
3119
|
}
|
|
3050
3120
|
}
|
|
3121
|
+
function resolveOllamaCommand() {
|
|
3122
|
+
if (hasCommand("ollama")) return "ollama";
|
|
3123
|
+
const candidates = process.platform === "win32" ? [
|
|
3124
|
+
join7(
|
|
3125
|
+
homedir5(),
|
|
3126
|
+
"AppData",
|
|
3127
|
+
"Local",
|
|
3128
|
+
"Programs",
|
|
3129
|
+
"Ollama",
|
|
3130
|
+
"ollama.exe"
|
|
3131
|
+
)
|
|
3132
|
+
] : process.platform === "darwin" ? ["/Applications/Ollama.app/Contents/Resources/ollama"] : [];
|
|
3133
|
+
return candidates.find((candidate) => existsSync7(candidate));
|
|
3134
|
+
}
|
|
3135
|
+
function prepareLocalModel(runner, model) {
|
|
3136
|
+
if (runner === "fastflowlm") {
|
|
3137
|
+
return {
|
|
3138
|
+
success: true,
|
|
3139
|
+
message: `${model} will be downloaded by FastFlowLM on first use.`
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
3142
|
+
if (runner !== "ollama") {
|
|
3143
|
+
return {
|
|
3144
|
+
success: false,
|
|
3145
|
+
message: "No supported local LLM runner was selected."
|
|
3146
|
+
};
|
|
3147
|
+
}
|
|
3148
|
+
const ollamaCommand = resolveOllamaCommand();
|
|
3149
|
+
if (!ollamaCommand) {
|
|
3150
|
+
return {
|
|
3151
|
+
success: false,
|
|
3152
|
+
message: `Ollama was installed but its command is not available yet. Restart the terminal, then run ollama pull ${model}.`
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
3155
|
+
console.log(`Downloading ${model} with Ollama...`);
|
|
3156
|
+
try {
|
|
3157
|
+
execFileSync(ollamaCommand, ["pull", model], { stdio: "inherit" });
|
|
3158
|
+
return { success: true, message: `${model} is ready in Ollama.` };
|
|
3159
|
+
} catch (err) {
|
|
3160
|
+
return {
|
|
3161
|
+
success: false,
|
|
3162
|
+
message: `Could not prepare ${model}: ${err.message}. Start Ollama and run: ollama pull ${model}`
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3051
3166
|
|
|
3052
3167
|
// src/kernel/system/locale.ts
|
|
3053
3168
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -3163,7 +3278,7 @@ function getRepoPaths(db) {
|
|
|
3163
3278
|
import { spawn } from "child_process";
|
|
3164
3279
|
import { existsSync as existsSync9 } from "fs";
|
|
3165
3280
|
var DEFAULT_LLM_URL = "http://localhost:8000/v1";
|
|
3166
|
-
var DEFAULT_LLM_MODEL = "
|
|
3281
|
+
var DEFAULT_LLM_MODEL = "qwen3.5:4b";
|
|
3167
3282
|
var DEFAULT_LLM_API_KEY = "sk-none";
|
|
3168
3283
|
function getLlmConfig(db) {
|
|
3169
3284
|
return {
|
|
@@ -3673,38 +3788,51 @@ function resolveUser(opts, db, resolveOpts) {
|
|
|
3673
3788
|
process.exit(1);
|
|
3674
3789
|
}
|
|
3675
3790
|
|
|
3676
|
-
// src/cli/commands/
|
|
3677
|
-
function
|
|
3678
|
-
console.
|
|
3679
|
-
}
|
|
3680
|
-
function jsonError(message) {
|
|
3681
|
-
console.log(JSON.stringify({ error: message }, null, 2));
|
|
3791
|
+
// src/cli/commands/shared/db.ts
|
|
3792
|
+
function defaultErrorHandler(message) {
|
|
3793
|
+
console.error("Error:", message);
|
|
3682
3794
|
process.exit(1);
|
|
3683
3795
|
}
|
|
3684
|
-
function withDb(fn) {
|
|
3796
|
+
function withDb(fn, onError = defaultErrorHandler) {
|
|
3685
3797
|
let db;
|
|
3686
3798
|
try {
|
|
3687
3799
|
db = openDatabase();
|
|
3688
3800
|
fn(db);
|
|
3689
3801
|
} catch (err) {
|
|
3690
|
-
|
|
3691
|
-
jsonError(err.message);
|
|
3802
|
+
onError(err.message);
|
|
3692
3803
|
} finally {
|
|
3693
3804
|
db?.close();
|
|
3694
3805
|
}
|
|
3695
3806
|
}
|
|
3696
|
-
async function withDbAsync(fn) {
|
|
3807
|
+
async function withDbAsync(fn, onError = defaultErrorHandler) {
|
|
3697
3808
|
let db;
|
|
3698
3809
|
try {
|
|
3699
3810
|
db = openDatabase();
|
|
3700
3811
|
await fn(db);
|
|
3701
3812
|
} catch (err) {
|
|
3702
|
-
|
|
3703
|
-
jsonError(err.message);
|
|
3813
|
+
onError(err.message);
|
|
3704
3814
|
} finally {
|
|
3705
3815
|
db?.close();
|
|
3706
3816
|
}
|
|
3707
3817
|
}
|
|
3818
|
+
function jsonOut(data) {
|
|
3819
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3820
|
+
}
|
|
3821
|
+
|
|
3822
|
+
// src/cli/commands/bridge.ts
|
|
3823
|
+
function jsonOut2(data) {
|
|
3824
|
+
console.log(JSON.stringify(data, null, 2));
|
|
3825
|
+
}
|
|
3826
|
+
function jsonError(message) {
|
|
3827
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
3828
|
+
process.exit(1);
|
|
3829
|
+
}
|
|
3830
|
+
function withDb2(fn) {
|
|
3831
|
+
withDb(fn, jsonError);
|
|
3832
|
+
}
|
|
3833
|
+
async function withDbAsync2(fn) {
|
|
3834
|
+
await withDbAsync(fn, jsonError);
|
|
3835
|
+
}
|
|
3708
3836
|
function getReviewTarget2(db, cardId, userId) {
|
|
3709
3837
|
const target = db.prepare(
|
|
3710
3838
|
`SELECT c.id AS card_id, c.token_id, c.user_id, t.slug
|
|
@@ -3743,13 +3871,13 @@ var bridgeCommand = new Command("bridge").description(
|
|
|
3743
3871
|
"Machine-readable JSON protocol for AI integration"
|
|
3744
3872
|
);
|
|
3745
3873
|
bridgeCommand.command("check-due").description("Check due cards for a user (JSON)").option("--user <id>", "User ID (default: whoami)").action((opts) => {
|
|
3746
|
-
|
|
3874
|
+
withDb2((db) => {
|
|
3747
3875
|
const userId = resolveUser(opts, db, { json: true });
|
|
3748
3876
|
const dueCards = getDueCards(db, userId);
|
|
3749
3877
|
const domains = [
|
|
3750
3878
|
...new Set(dueCards.map((c) => c.domain).filter(Boolean))
|
|
3751
3879
|
].sort();
|
|
3752
|
-
|
|
3880
|
+
jsonOut2({
|
|
3753
3881
|
userId,
|
|
3754
3882
|
dueCount: dueCards.length,
|
|
3755
3883
|
domains,
|
|
@@ -3767,11 +3895,11 @@ bridgeCommand.command("check-due").description("Check due cards for a user (JSON
|
|
|
3767
3895
|
});
|
|
3768
3896
|
});
|
|
3769
3897
|
bridgeCommand.command("get-review").description("Get next review card with prompt (JSON)").option("--user <id>", "User ID (default: whoami)").option("--no-resolve", "Skip resolving the token's source_link into context").action(async (opts) => {
|
|
3770
|
-
await
|
|
3898
|
+
await withDbAsync2(async (db) => {
|
|
3771
3899
|
const userId = resolveUser(opts, db, { json: true });
|
|
3772
3900
|
const queue = buildReviewQueue(db, { userId, maxReviews: 1, maxNew: 1 });
|
|
3773
3901
|
if (queue.items.length === 0) {
|
|
3774
|
-
|
|
3902
|
+
jsonOut2({
|
|
3775
3903
|
userId,
|
|
3776
3904
|
hasReview: false,
|
|
3777
3905
|
card: null,
|
|
@@ -3820,7 +3948,7 @@ bridgeCommand.command("get-review").description("Get next review card with promp
|
|
|
3820
3948
|
}
|
|
3821
3949
|
}
|
|
3822
3950
|
const fullQueue = buildReviewQueue(db, { userId });
|
|
3823
|
-
|
|
3951
|
+
jsonOut2({
|
|
3824
3952
|
userId,
|
|
3825
3953
|
hasReview: true,
|
|
3826
3954
|
card: item,
|
|
@@ -3831,7 +3959,7 @@ bridgeCommand.command("get-review").description("Get next review card with promp
|
|
|
3831
3959
|
});
|
|
3832
3960
|
});
|
|
3833
3961
|
bridgeCommand.command("submit").description("Submit a rating for a card (JSON)").option("--user <id>", "User ID (default: whoami)").requiredOption("--card-id <id>", "Card ID").requiredOption("--rating <n>", "Rating (1-4)").action((opts) => {
|
|
3834
|
-
|
|
3962
|
+
withDb2((db) => {
|
|
3835
3963
|
const userId = resolveUser(opts, db, { json: true });
|
|
3836
3964
|
const rating = Number(opts.rating);
|
|
3837
3965
|
if (rating < 1 || rating > 4) {
|
|
@@ -3843,7 +3971,7 @@ bridgeCommand.command("submit").description("Submit a rating for a card (JSON)")
|
|
|
3843
3971
|
userId,
|
|
3844
3972
|
rating
|
|
3845
3973
|
});
|
|
3846
|
-
|
|
3974
|
+
jsonOut2({
|
|
3847
3975
|
success: true,
|
|
3848
3976
|
rating,
|
|
3849
3977
|
evaluation: result.evaluation,
|
|
@@ -3855,7 +3983,7 @@ bridgeCommand.command("review-action").description("Apply a review action (JSON)
|
|
|
3855
3983
|
"--action <action>",
|
|
3856
3984
|
"Action: rate | skip | edit-token | deprecate-token | delete-token | delete-card | stop"
|
|
3857
3985
|
).option("--rating <n>", "Rating (1-4) for action=rate").option("--concept <concept>", "Updated concept text for action=edit-token").option("--domain <domain>", "Updated domain for action=edit-token").option("--bloom <level>", "Updated Bloom level for action=edit-token").option("--context <context>", "Updated context for action=edit-token").option("--mode <mode>", "Updated symbiosis mode for action=edit-token").option("--source-link <link>", "Updated source link for action=edit-token").option("--confirm", "Confirm destructive delete actions").action((opts) => {
|
|
3858
|
-
|
|
3986
|
+
withDb2((db) => {
|
|
3859
3987
|
const userId = resolveUser(opts, db, { json: true });
|
|
3860
3988
|
const action = opts.action;
|
|
3861
3989
|
const validActions = [
|
|
@@ -3873,7 +4001,7 @@ bridgeCommand.command("review-action").description("Apply a review action (JSON)
|
|
|
3873
4001
|
const target = getReviewTarget2(db, opts.cardId, userId);
|
|
3874
4002
|
if ((action === "delete-token" || action === "delete-card") && !opts.confirm) {
|
|
3875
4003
|
if (action === "delete-token") {
|
|
3876
|
-
|
|
4004
|
+
jsonOut2({
|
|
3877
4005
|
success: true,
|
|
3878
4006
|
action,
|
|
3879
4007
|
preview: true,
|
|
@@ -3883,7 +4011,7 @@ bridgeCommand.command("review-action").description("Apply a review action (JSON)
|
|
|
3883
4011
|
});
|
|
3884
4012
|
return;
|
|
3885
4013
|
}
|
|
3886
|
-
|
|
4014
|
+
jsonOut2({
|
|
3887
4015
|
success: true,
|
|
3888
4016
|
action,
|
|
3889
4017
|
preview: true,
|
|
@@ -3904,7 +4032,7 @@ bridgeCommand.command("review-action").description("Apply a review action (JSON)
|
|
|
3904
4032
|
rating,
|
|
3905
4033
|
tokenUpdates: action === "edit-token" ? parseTokenUpdates(opts) : void 0
|
|
3906
4034
|
});
|
|
3907
|
-
|
|
4035
|
+
jsonOut2({
|
|
3908
4036
|
success: true,
|
|
3909
4037
|
action,
|
|
3910
4038
|
token: {
|
|
@@ -3923,12 +4051,12 @@ bridgeCommand.command("review-action").description("Apply a review action (JSON)
|
|
|
3923
4051
|
});
|
|
3924
4052
|
});
|
|
3925
4053
|
bridgeCommand.command("get-skill").description("Get an agent skill by slug (JSON)").requiredOption("--slug <slug>", "Skill slug").action((opts) => {
|
|
3926
|
-
|
|
4054
|
+
withDb2((db) => {
|
|
3927
4055
|
const skill = getAgentSkill(db, opts.slug);
|
|
3928
4056
|
if (!skill) {
|
|
3929
4057
|
jsonError(`Skill not found: ${opts.slug}`);
|
|
3930
4058
|
}
|
|
3931
|
-
|
|
4059
|
+
jsonOut2({
|
|
3932
4060
|
slug: skill?.slug,
|
|
3933
4061
|
description: skill?.description,
|
|
3934
4062
|
steps: skill?.steps,
|
|
@@ -3939,7 +4067,7 @@ bridgeCommand.command("get-skill").description("Get an agent skill by slug (JSON
|
|
|
3939
4067
|
});
|
|
3940
4068
|
bridgeCommand.command("get-monitor").description("Read monitor log for a session (JSON)").requiredOption("--session <id>", "Session ID").action((opts) => {
|
|
3941
4069
|
if (!monitorLogExists(opts.session)) {
|
|
3942
|
-
|
|
4070
|
+
jsonOut2({
|
|
3943
4071
|
sessionId: opts.session,
|
|
3944
4072
|
exists: false,
|
|
3945
4073
|
commands: [],
|
|
@@ -3960,7 +4088,7 @@ bridgeCommand.command("get-monitor").description("Read monitor log for a session
|
|
|
3960
4088
|
durationMs: new Date(endTs).getTime() - new Date(first.startedAt).getTime()
|
|
3961
4089
|
};
|
|
3962
4090
|
}
|
|
3963
|
-
|
|
4091
|
+
jsonOut2({
|
|
3964
4092
|
sessionId: opts.session,
|
|
3965
4093
|
exists: true,
|
|
3966
4094
|
commands: commands.map((c) => ({
|
|
@@ -3978,7 +4106,7 @@ bridgeCommand.command("get-monitor").description("Read monitor log for a session
|
|
|
3978
4106
|
bridgeCommand.command("analyze-monitor").description("Analyze monitor log with token patterns from stdin (JSON)").requiredOption("--session <id>", "Session ID").action(async (opts) => {
|
|
3979
4107
|
try {
|
|
3980
4108
|
if (!monitorLogExists(opts.session)) {
|
|
3981
|
-
|
|
4109
|
+
jsonOut2({
|
|
3982
4110
|
sessionId: opts.session,
|
|
3983
4111
|
ratings: [],
|
|
3984
4112
|
unmatchedCommands: [],
|
|
@@ -4006,7 +4134,7 @@ bridgeCommand.command("analyze-monitor").description("Analyze monitor log with t
|
|
|
4006
4134
|
const events = readMonitorLog(opts.session);
|
|
4007
4135
|
const commands = pairCommands(events);
|
|
4008
4136
|
const result = analyzeObservation(commands, data?.patterns);
|
|
4009
|
-
|
|
4137
|
+
jsonOut2({
|
|
4010
4138
|
sessionId: opts.session,
|
|
4011
4139
|
...result
|
|
4012
4140
|
});
|
|
@@ -4046,7 +4174,7 @@ bridgeCommand.command("add-token").description("Create a token + card from JSON
|
|
|
4046
4174
|
source_link: data?.source_link ?? null
|
|
4047
4175
|
});
|
|
4048
4176
|
const card = ensureCard(db, token.id, userId);
|
|
4049
|
-
|
|
4177
|
+
jsonOut2({
|
|
4050
4178
|
success: true,
|
|
4051
4179
|
token,
|
|
4052
4180
|
card: {
|
|
@@ -4083,11 +4211,11 @@ bridgeCommand.command("discover-skills").description(
|
|
|
4083
4211
|
try {
|
|
4084
4212
|
files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
|
|
4085
4213
|
} catch {
|
|
4086
|
-
|
|
4214
|
+
jsonOut2({ proposals: [], message: "No monitor logs found." });
|
|
4087
4215
|
return;
|
|
4088
4216
|
}
|
|
4089
4217
|
if (files.length === 0) {
|
|
4090
|
-
|
|
4218
|
+
jsonOut2({ proposals: [], message: "No monitor logs found." });
|
|
4091
4219
|
return;
|
|
4092
4220
|
}
|
|
4093
4221
|
const limit = Number(opts.limit);
|
|
@@ -4102,7 +4230,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
4102
4230
|
}
|
|
4103
4231
|
}
|
|
4104
4232
|
if (sessionCommands.size === 0) {
|
|
4105
|
-
|
|
4233
|
+
jsonOut2({ proposals: [], message: "No command data in monitor logs." });
|
|
4106
4234
|
return;
|
|
4107
4235
|
}
|
|
4108
4236
|
let existingSkillSlugs = [];
|
|
@@ -4118,7 +4246,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
4118
4246
|
minSessions: Number(opts.minSessions),
|
|
4119
4247
|
existingSkillSlugs
|
|
4120
4248
|
});
|
|
4121
|
-
|
|
4249
|
+
jsonOut2({
|
|
4122
4250
|
sessionsAnalyzed: sessionCommands.size,
|
|
4123
4251
|
proposals
|
|
4124
4252
|
});
|
|
@@ -4127,7 +4255,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
4127
4255
|
}
|
|
4128
4256
|
});
|
|
4129
4257
|
bridgeCommand.command("check-llm").description("Check if LLM is enabled and online (JSON)").action(async () => {
|
|
4130
|
-
await
|
|
4258
|
+
await withDbAsync2(async (db) => {
|
|
4131
4259
|
const { enabled, url, model, apiKey } = getLlmConfig(db);
|
|
4132
4260
|
let online = false;
|
|
4133
4261
|
let availableModels = [];
|
|
@@ -4141,7 +4269,7 @@ bridgeCommand.command("check-llm").description("Check if LLM is enabled and onli
|
|
|
4141
4269
|
);
|
|
4142
4270
|
}
|
|
4143
4271
|
}
|
|
4144
|
-
|
|
4272
|
+
jsonOut2({
|
|
4145
4273
|
enabled,
|
|
4146
4274
|
online,
|
|
4147
4275
|
url,
|
|
@@ -4158,18 +4286,18 @@ bridgeCommand.command("ensure-llm").description(
|
|
|
4158
4286
|
"Max time to wait for the server to come online",
|
|
4159
4287
|
"25000"
|
|
4160
4288
|
).action(async (opts) => {
|
|
4161
|
-
await
|
|
4289
|
+
await withDbAsync2(async (db) => {
|
|
4162
4290
|
const result = await ensureLlmReadyHeadless(db, {
|
|
4163
4291
|
timeoutMs: Number(opts.timeout)
|
|
4164
4292
|
});
|
|
4165
|
-
|
|
4293
|
+
jsonOut2(result);
|
|
4166
4294
|
});
|
|
4167
4295
|
});
|
|
4168
4296
|
bridgeCommand.command("translate-question").description("Translate a question dynamically using the local LLM (JSON)").requiredOption("--question <text>", "Question in English to translate").action(async (opts) => {
|
|
4169
|
-
await
|
|
4297
|
+
await withDbAsync2(async (db) => {
|
|
4170
4298
|
const isEnabled = getSetting(db, "llm.enabled") === "true";
|
|
4171
4299
|
if (!isEnabled) {
|
|
4172
|
-
|
|
4300
|
+
jsonOut2({
|
|
4173
4301
|
success: false,
|
|
4174
4302
|
error: "LLM integration is disabled",
|
|
4175
4303
|
translation: opts.question
|
|
@@ -4178,9 +4306,9 @@ bridgeCommand.command("translate-question").description("Translate a question dy
|
|
|
4178
4306
|
}
|
|
4179
4307
|
try {
|
|
4180
4308
|
const translation = await translateQuestionViaLLM(db, opts.question);
|
|
4181
|
-
|
|
4309
|
+
jsonOut2({ success: true, translation });
|
|
4182
4310
|
} catch (err) {
|
|
4183
|
-
|
|
4311
|
+
jsonOut2({
|
|
4184
4312
|
success: false,
|
|
4185
4313
|
error: err.message,
|
|
4186
4314
|
translation: opts.question
|
|
@@ -4191,10 +4319,10 @@ bridgeCommand.command("translate-question").description("Translate a question dy
|
|
|
4191
4319
|
bridgeCommand.command("evaluate-answer").description(
|
|
4192
4320
|
"Evaluate the learner's active-recall answer using the local LLM (JSON)"
|
|
4193
4321
|
).requiredOption("--slug <slug>", "Token slug").requiredOption("--concept <concept>", "Target concept text").requiredOption("--domain <domain>", "Token domain").requiredOption("--bloom-level <level>", "Bloom taxonomy level").requiredOption("--question <question>", "Question prompt presented").requiredOption("--user-answer <answer>", "User's typed answer").option("--context <context>", "Optional token context details").option("--source-link <link>", "Optional source link").action(async (opts) => {
|
|
4194
|
-
await
|
|
4322
|
+
await withDbAsync2(async (db) => {
|
|
4195
4323
|
const isEnabled = getSetting(db, "llm.enabled") === "true";
|
|
4196
4324
|
if (!isEnabled) {
|
|
4197
|
-
|
|
4325
|
+
jsonOut2({
|
|
4198
4326
|
success: false,
|
|
4199
4327
|
error: "LLM integration is disabled",
|
|
4200
4328
|
evaluation: ""
|
|
@@ -4220,9 +4348,9 @@ bridgeCommand.command("evaluate-answer").description(
|
|
|
4220
4348
|
userAnswer: opts.userAnswer,
|
|
4221
4349
|
sourceLinkContent: resolvedContextContent
|
|
4222
4350
|
});
|
|
4223
|
-
|
|
4351
|
+
jsonOut2({ success: true, evaluation });
|
|
4224
4352
|
} catch (err) {
|
|
4225
|
-
|
|
4353
|
+
jsonOut2({
|
|
4226
4354
|
success: false,
|
|
4227
4355
|
error: err.message,
|
|
4228
4356
|
evaluation: ""
|
|
@@ -4231,9 +4359,9 @@ bridgeCommand.command("evaluate-answer").description(
|
|
|
4231
4359
|
});
|
|
4232
4360
|
});
|
|
4233
4361
|
bridgeCommand.command("get-settings").description("Get active ZAM settings (JSON)").action(() => {
|
|
4234
|
-
|
|
4362
|
+
withDb2((db) => {
|
|
4235
4363
|
const { enabled, url, model, locale } = getLlmConfig(db);
|
|
4236
|
-
|
|
4364
|
+
jsonOut2({
|
|
4237
4365
|
locale,
|
|
4238
4366
|
llm: {
|
|
4239
4367
|
enabled,
|
|
@@ -4246,23 +4374,11 @@ bridgeCommand.command("get-settings").description("Get active ZAM settings (JSON
|
|
|
4246
4374
|
|
|
4247
4375
|
// src/cli/commands/card.ts
|
|
4248
4376
|
import { Command as Command2 } from "commander";
|
|
4249
|
-
function withDb2(fn) {
|
|
4250
|
-
let db;
|
|
4251
|
-
try {
|
|
4252
|
-
db = openDatabase();
|
|
4253
|
-
fn(db);
|
|
4254
|
-
} catch (err) {
|
|
4255
|
-
console.error("Error:", err.message);
|
|
4256
|
-
process.exit(1);
|
|
4257
|
-
} finally {
|
|
4258
|
-
db?.close();
|
|
4259
|
-
}
|
|
4260
|
-
}
|
|
4261
4377
|
var cardCommand = new Command2("card").description(
|
|
4262
4378
|
"Manage spaced-repetition cards"
|
|
4263
4379
|
);
|
|
4264
4380
|
cardCommand.command("due").description("Show due tokens for a user").option("--user <id>", "User ID (default: whoami)").option("--json", "Output as JSON").option("--summary", "Show only counts per domain (no slugs or concepts)").action((opts) => {
|
|
4265
|
-
|
|
4381
|
+
withDb((db) => {
|
|
4266
4382
|
const userId = resolveUser(opts, db);
|
|
4267
4383
|
const dueCards = getDueCards(db, userId);
|
|
4268
4384
|
if (opts.json) {
|
|
@@ -4310,7 +4426,7 @@ cardCommand.command("due").description("Show due tokens for a user").option("--u
|
|
|
4310
4426
|
});
|
|
4311
4427
|
});
|
|
4312
4428
|
cardCommand.command("update").description("Apply a rating to a card").option("--user <id>", "User ID (default: whoami)").requiredOption("--token <slug>", "Token slug").requiredOption("--rating <n>", "Rating (1=Again, 2=Hard, 3=Good, 4=Easy)").option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action((opts) => {
|
|
4313
|
-
|
|
4429
|
+
withDb((db) => {
|
|
4314
4430
|
const userId = resolveUser(opts, db);
|
|
4315
4431
|
const token = getTokenBySlug(db, opts.token);
|
|
4316
4432
|
if (!token) {
|
|
@@ -4377,7 +4493,7 @@ cardCommand.command("update").description("Apply a rating to a card").option("--
|
|
|
4377
4493
|
});
|
|
4378
4494
|
});
|
|
4379
4495
|
cardCommand.command("unblock").description("Unblock cards whose prerequisites are met").option("--user <id>", "User ID (default: whoami)").option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action((opts) => {
|
|
4380
|
-
|
|
4496
|
+
withDb((db) => {
|
|
4381
4497
|
const userId = resolveUser(opts, db);
|
|
4382
4498
|
const result = unblockReady(db, userId);
|
|
4383
4499
|
if (opts.quiet) return;
|
|
@@ -4396,7 +4512,7 @@ cardCommand.command("unblock").description("Unblock cards whose prerequisites ar
|
|
|
4396
4512
|
});
|
|
4397
4513
|
});
|
|
4398
4514
|
cardCommand.command("delete").description("Delete one user's card for a token").option("--user <id>", "User ID (default: whoami)").requiredOption("--token <slug>", "Token slug").option("--json", "Output as JSON").action((opts) => {
|
|
4399
|
-
|
|
4515
|
+
withDb((db) => {
|
|
4400
4516
|
const userId = resolveUser(opts, db);
|
|
4401
4517
|
const token = getTokenBySlug(db, opts.token);
|
|
4402
4518
|
if (!token) {
|
|
@@ -4565,22 +4681,10 @@ async function setupTurso(urlArg, tokenArg) {
|
|
|
4565
4681
|
|
|
4566
4682
|
// src/cli/commands/git-sync.ts
|
|
4567
4683
|
import { execSync as execSync4 } from "child_process";
|
|
4568
|
-
import { chmodSync, existsSync as existsSync10, writeFileSync as
|
|
4684
|
+
import { chmodSync, existsSync as existsSync10, writeFileSync as writeFileSync4 } from "fs";
|
|
4569
4685
|
import { join as join9 } from "path";
|
|
4570
4686
|
import { Command as Command4 } from "commander";
|
|
4571
|
-
function
|
|
4572
|
-
let db;
|
|
4573
|
-
try {
|
|
4574
|
-
db = openDatabase();
|
|
4575
|
-
fn(db);
|
|
4576
|
-
} catch (err) {
|
|
4577
|
-
console.error("Error:", err.message);
|
|
4578
|
-
process.exit(1);
|
|
4579
|
-
} finally {
|
|
4580
|
-
db?.close();
|
|
4581
|
-
}
|
|
4582
|
-
}
|
|
4583
|
-
function installHook() {
|
|
4687
|
+
function installHook2() {
|
|
4584
4688
|
const gitDir = join9(process.cwd(), ".git");
|
|
4585
4689
|
if (!existsSync10(gitDir)) {
|
|
4586
4690
|
console.error(
|
|
@@ -4596,7 +4700,7 @@ function installHook() {
|
|
|
4596
4700
|
zam git-sync --commit HEAD --quiet
|
|
4597
4701
|
`;
|
|
4598
4702
|
try {
|
|
4599
|
-
|
|
4703
|
+
writeFileSync4(hookPath, hookContent, { encoding: "utf-8", flag: "w" });
|
|
4600
4704
|
try {
|
|
4601
4705
|
chmodSync(hookPath, "755");
|
|
4602
4706
|
} catch (_e) {
|
|
@@ -4611,10 +4715,10 @@ zam git-sync --commit HEAD --quiet
|
|
|
4611
4715
|
}
|
|
4612
4716
|
var gitSyncCommand = new Command4("git-sync").description("Sync learning cards with recent Git file modifications").option("--commit <hash>", "Git commit hash to check", "HEAD").option("--user <id>", "User ID (default: whoami)").option("--install", "Install git post-commit hook in current repo").option("--quiet", "Suppress verbose output").action((opts) => {
|
|
4613
4717
|
if (opts.install) {
|
|
4614
|
-
|
|
4718
|
+
installHook2();
|
|
4615
4719
|
return;
|
|
4616
4720
|
}
|
|
4617
|
-
|
|
4721
|
+
withDb((db) => {
|
|
4618
4722
|
const userId = resolveUser(opts, db);
|
|
4619
4723
|
let changedFiles = [];
|
|
4620
4724
|
try {
|
|
@@ -4883,7 +4987,7 @@ goalCommand.command("status <slug> <status>").description("Update a goal's statu
|
|
|
4883
4987
|
});
|
|
4884
4988
|
|
|
4885
4989
|
// src/cli/commands/init.ts
|
|
4886
|
-
import { existsSync as existsSync12, mkdirSync as mkdirSync6, writeFileSync as
|
|
4990
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
4887
4991
|
import { homedir as homedir7 } from "os";
|
|
4888
4992
|
import { join as join10 } from "path";
|
|
4889
4993
|
import { confirm, input as input3 } from "@inquirer/prompts";
|
|
@@ -4898,7 +5002,7 @@ function bootstrapSandboxWorkspace(workspaceDir) {
|
|
|
4898
5002
|
mkdirSync6(join10(workspaceDir, "skills"), { recursive: true });
|
|
4899
5003
|
const worldviewFile = join10(workspaceDir, "beliefs", "worldview.md");
|
|
4900
5004
|
if (!existsSync12(worldviewFile)) {
|
|
4901
|
-
|
|
5005
|
+
writeFileSync5(
|
|
4902
5006
|
worldviewFile,
|
|
4903
5007
|
`# Personal Worldview
|
|
4904
5008
|
|
|
@@ -4912,7 +5016,7 @@ Here, I declare the core concepts and principles I want to master.
|
|
|
4912
5016
|
}
|
|
4913
5017
|
const goalsFile = join10(workspaceDir, "goals", "goals.md");
|
|
4914
5018
|
if (!existsSync12(goalsFile)) {
|
|
4915
|
-
|
|
5019
|
+
writeFileSync5(
|
|
4916
5020
|
goalsFile,
|
|
4917
5021
|
`# Personal Goals
|
|
4918
5022
|
|
|
@@ -4975,7 +5079,7 @@ var initCommand = new Command6("init").description("Launch the guided interactiv
|
|
|
4975
5079
|
message: `Would you like ZAM to install and configure ${runnerLabel} automatically?`,
|
|
4976
5080
|
default: true
|
|
4977
5081
|
});
|
|
4978
|
-
let
|
|
5082
|
+
let llmReady = false;
|
|
4979
5083
|
if (proceedInstall) {
|
|
4980
5084
|
let result;
|
|
4981
5085
|
if (profile.recommendedRunner === "fastflowlm") {
|
|
@@ -4985,7 +5089,18 @@ var initCommand = new Command6("init").description("Launch the guided interactiv
|
|
|
4985
5089
|
}
|
|
4986
5090
|
if (result.success) {
|
|
4987
5091
|
console.log(`\x1B[32m\u2713 ${result.message}\x1B[0m`);
|
|
4988
|
-
|
|
5092
|
+
const modelResult = prepareLocalModel(
|
|
5093
|
+
profile.recommendedRunner,
|
|
5094
|
+
profile.recommendedModel
|
|
5095
|
+
);
|
|
5096
|
+
if (modelResult.success) {
|
|
5097
|
+
console.log(`\x1B[32m\u2713 ${modelResult.message}\x1B[0m`);
|
|
5098
|
+
llmReady = true;
|
|
5099
|
+
} else {
|
|
5100
|
+
console.warn(
|
|
5101
|
+
`\x1B[33m\u26A0 Model setup incomplete: ${modelResult.message}\x1B[0m`
|
|
5102
|
+
);
|
|
5103
|
+
}
|
|
4989
5104
|
} else {
|
|
4990
5105
|
console.warn(`\x1B[33m\u26A0 Installation failed: ${result.message}\x1B[0m`);
|
|
4991
5106
|
console.log(
|
|
@@ -5003,15 +5118,14 @@ var initCommand = new Command6("init").description("Launch the guided interactiv
|
|
|
5003
5118
|
console.log(
|
|
5004
5119
|
`\x1B[32m\u2713 Detected and set system language to: ${detectedLocale}\x1B[0m`
|
|
5005
5120
|
);
|
|
5006
|
-
if (
|
|
5121
|
+
if (llmReady) {
|
|
5007
5122
|
setSetting(db, "llm.enabled", "true");
|
|
5008
5123
|
if (profile.recommendedRunner === "fastflowlm") {
|
|
5009
5124
|
setSetting(db, "llm.url", "http://localhost:8000/v1");
|
|
5010
|
-
setSetting(db, "llm.model", "gemma4-it:e4b");
|
|
5011
5125
|
} else {
|
|
5012
5126
|
setSetting(db, "llm.url", "http://localhost:11434/v1");
|
|
5013
|
-
setSetting(db, "llm.model", "llama3.2:3b");
|
|
5014
5127
|
}
|
|
5128
|
+
setSetting(db, "llm.model", profile.recommendedModel);
|
|
5015
5129
|
console.log(
|
|
5016
5130
|
"\x1B[32m\u2713 Configured LLM runner settings in database.\x1B[0m"
|
|
5017
5131
|
);
|
|
@@ -5027,10 +5141,10 @@ var initCommand = new Command6("init").description("Launch the guided interactiv
|
|
|
5027
5141
|
db?.close();
|
|
5028
5142
|
}
|
|
5029
5143
|
console.log(
|
|
5030
|
-
"\n\x1B[1m[5/5] Wiring Developer Agents & Terminal
|
|
5144
|
+
"\n\x1B[1m[5/5] Wiring Developer Agents & Terminal Helpers\x1B[0m"
|
|
5031
5145
|
);
|
|
5032
5146
|
const proceedHooks = await confirm({
|
|
5033
|
-
message: "Distribute ZAM
|
|
5147
|
+
message: "Distribute ZAM skills and install optional monitored-session shell helpers?",
|
|
5034
5148
|
default: true
|
|
5035
5149
|
});
|
|
5036
5150
|
if (proceedHooks) {
|
|
@@ -5043,18 +5157,21 @@ var initCommand = new Command6("init").description("Launch the guided interactiv
|
|
|
5043
5157
|
console.log(` \x1B[31m\u2717 Failed to install in ${res.name}\x1B[0m`);
|
|
5044
5158
|
}
|
|
5045
5159
|
}
|
|
5046
|
-
console.log("
|
|
5160
|
+
console.log("Installing monitored-session helpers in shell profiles...");
|
|
5047
5161
|
const hookResults = injectShellHooks();
|
|
5048
5162
|
for (const res of hookResults) {
|
|
5049
5163
|
if (res.success) {
|
|
5050
|
-
const action = res.alreadyHooked ? "already up-to-date" : "
|
|
5051
|
-
console.log(` \x1B[32m\u2713 ${res.shell}
|
|
5164
|
+
const action = res.alreadyHooked ? "already up-to-date" : "installed successfully";
|
|
5165
|
+
console.log(` \x1B[32m\u2713 ${res.shell} helper: ${action}\x1B[0m`);
|
|
5052
5166
|
} else {
|
|
5053
5167
|
console.log(
|
|
5054
|
-
` \x1B[31m\u2717 Failed to
|
|
5168
|
+
` \x1B[31m\u2717 Failed to install helper in ${res.shell} (${res.file})\x1B[0m`
|
|
5055
5169
|
);
|
|
5056
5170
|
}
|
|
5057
5171
|
}
|
|
5172
|
+
console.log(
|
|
5173
|
+
" Start monitoring with zam-monitor-session <id> (bash/zsh) or Start-ZamMonitor <id> (PowerShell)."
|
|
5174
|
+
);
|
|
5058
5175
|
}
|
|
5059
5176
|
printLine();
|
|
5060
5177
|
console.log(
|
|
@@ -5327,6 +5444,7 @@ async function promptTokenEdit(token) {
|
|
|
5327
5444
|
{ name: "Context", value: "context" },
|
|
5328
5445
|
{ name: "Symbiosis mode", value: "symbiosis_mode" },
|
|
5329
5446
|
{ name: "Source link", value: "source_link" },
|
|
5447
|
+
{ name: "Question", value: "question" },
|
|
5330
5448
|
{ name: "Cancel", value: "cancel" }
|
|
5331
5449
|
]
|
|
5332
5450
|
});
|
|
@@ -5396,6 +5514,17 @@ async function promptTokenEdit(token) {
|
|
|
5396
5514
|
});
|
|
5397
5515
|
return link === token.source_link ? null : { source_link: link || null };
|
|
5398
5516
|
}
|
|
5517
|
+
case "question": {
|
|
5518
|
+
const question = await input4({
|
|
5519
|
+
message: "Recall question (blank allowed):",
|
|
5520
|
+
default: token.question ?? ""
|
|
5521
|
+
});
|
|
5522
|
+
return question === token.question ? null : { question: question || null };
|
|
5523
|
+
}
|
|
5524
|
+
default: {
|
|
5525
|
+
const exhaustive = field;
|
|
5526
|
+
throw new Error(`Unsupported token field: ${exhaustive}`);
|
|
5527
|
+
}
|
|
5399
5528
|
}
|
|
5400
5529
|
}
|
|
5401
5530
|
|
|
@@ -5606,8 +5735,8 @@ ${"\u2550".repeat(50)}`);
|
|
|
5606
5735
|
});
|
|
5607
5736
|
|
|
5608
5737
|
// src/cli/commands/monitor.ts
|
|
5609
|
-
import { execFileSync, execSync as execSync5 } from "child_process";
|
|
5610
|
-
import { unlinkSync, writeFileSync as
|
|
5738
|
+
import { execFileSync as execFileSync2, execSync as execSync5 } from "child_process";
|
|
5739
|
+
import { unlinkSync, writeFileSync as writeFileSync6 } from "fs";
|
|
5611
5740
|
import { tmpdir } from "os";
|
|
5612
5741
|
import { basename as basename2, join as join11 } from "path";
|
|
5613
5742
|
import { Command as Command8 } from "commander";
|
|
@@ -5867,7 +5996,7 @@ end tell` : `tell application "Terminal"
|
|
|
5867
5996
|
end tell`;
|
|
5868
5997
|
const tmpFile = join11(tmpdir(), `zam-monitor-${sessionId}.scpt`);
|
|
5869
5998
|
try {
|
|
5870
|
-
|
|
5999
|
+
writeFileSync6(tmpFile, appleScript);
|
|
5871
6000
|
execSync5(`osascript ${JSON.stringify(tmpFile)}`, { stdio: "ignore" });
|
|
5872
6001
|
console.log(
|
|
5873
6002
|
`Opened ${useIterm ? "iTerm2" : "Terminal.app"} window with monitoring for session ${sessionId}`
|
|
@@ -5895,7 +6024,7 @@ function openWindowsPowerShell(shellSetup, sessionId, dir, requestedShell) {
|
|
|
5895
6024
|
`-ArgumentList @('-NoExit','-NoProfile','-Command',${psSingleQuoted2(shellSetup)})`
|
|
5896
6025
|
].join(" ");
|
|
5897
6026
|
try {
|
|
5898
|
-
|
|
6027
|
+
execFileSync2(
|
|
5899
6028
|
"powershell.exe",
|
|
5900
6029
|
["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", startCommand],
|
|
5901
6030
|
{
|
|
@@ -6032,18 +6161,6 @@ ${"\u2550".repeat(50)}`);
|
|
|
6032
6161
|
// src/cli/commands/session.ts
|
|
6033
6162
|
import { input as input6, select as select2 } from "@inquirer/prompts";
|
|
6034
6163
|
import { Command as Command10 } from "commander";
|
|
6035
|
-
function withDb4(fn) {
|
|
6036
|
-
let db;
|
|
6037
|
-
try {
|
|
6038
|
-
db = openDatabase();
|
|
6039
|
-
fn(db);
|
|
6040
|
-
} catch (err) {
|
|
6041
|
-
console.error("Error:", err.message);
|
|
6042
|
-
process.exit(1);
|
|
6043
|
-
} finally {
|
|
6044
|
-
db?.close();
|
|
6045
|
-
}
|
|
6046
|
-
}
|
|
6047
6164
|
var sessionCommand = new Command10("session").description(
|
|
6048
6165
|
"Manage learning sessions"
|
|
6049
6166
|
);
|
|
@@ -6083,7 +6200,7 @@ sessionCommand.command("start").description("Start a new learning session (revie
|
|
|
6083
6200
|
}
|
|
6084
6201
|
let task = opts.task;
|
|
6085
6202
|
if (!task && !opts.quiet && !opts.json) {
|
|
6086
|
-
task = await selectTask(
|
|
6203
|
+
task = await selectTask();
|
|
6087
6204
|
}
|
|
6088
6205
|
if (!task) {
|
|
6089
6206
|
console.error(
|
|
@@ -6188,11 +6305,11 @@ Time limit reached (${maxMinutes} min). Moving to task selection.`
|
|
|
6188
6305
|
}
|
|
6189
6306
|
return { reviewed, maintained, skipped: false };
|
|
6190
6307
|
}
|
|
6191
|
-
async function selectTask(
|
|
6308
|
+
async function selectTask() {
|
|
6192
6309
|
console.log("\u2550".repeat(50));
|
|
6193
6310
|
console.log("Phase 2: Task Selection");
|
|
6194
6311
|
console.log("\u2550".repeat(50));
|
|
6195
|
-
const adoConfig = loadADOConfig(
|
|
6312
|
+
const adoConfig = loadADOConfig();
|
|
6196
6313
|
if (adoConfig) {
|
|
6197
6314
|
const items = await fetchActiveWorkItems(adoConfig);
|
|
6198
6315
|
if (items.length > 0) {
|
|
@@ -6213,7 +6330,7 @@ async function selectTask(db) {
|
|
|
6213
6330
|
return input6({ message: "Task description:" });
|
|
6214
6331
|
}
|
|
6215
6332
|
sessionCommand.command("log").description("Log a step within a session").requiredOption("--session <id>", "Session ID").requiredOption("--token <slug>", "Token slug").requiredOption("--done-by <who>", "Who performed the step (user or agent)").option("--rating <n>", "Rating (1-4)").option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action((opts) => {
|
|
6216
|
-
|
|
6333
|
+
withDb((db) => {
|
|
6217
6334
|
const token = getTokenBySlug(db, opts.token);
|
|
6218
6335
|
if (!token) {
|
|
6219
6336
|
console.error(`Token not found: ${opts.token}`);
|
|
@@ -6239,7 +6356,7 @@ sessionCommand.command("log").description("Log a step within a session").require
|
|
|
6239
6356
|
});
|
|
6240
6357
|
});
|
|
6241
6358
|
sessionCommand.command("end").description("End a session and show summary").requiredOption("--session <id>", "Session ID").option("--json", "Output as JSON").action((opts) => {
|
|
6242
|
-
|
|
6359
|
+
withDb((db) => {
|
|
6243
6360
|
endSession(db, opts.session);
|
|
6244
6361
|
const summary = getSessionSummary(db, opts.session);
|
|
6245
6362
|
if (opts.json) {
|
|
@@ -6267,23 +6384,11 @@ sessionCommand.command("end").description("End a session and show summary").requ
|
|
|
6267
6384
|
// src/cli/commands/settings.ts
|
|
6268
6385
|
import { existsSync as existsSync13 } from "fs";
|
|
6269
6386
|
import { Command as Command11 } from "commander";
|
|
6270
|
-
function withDb5(fn) {
|
|
6271
|
-
let db;
|
|
6272
|
-
try {
|
|
6273
|
-
db = openDatabase();
|
|
6274
|
-
fn(db);
|
|
6275
|
-
} catch (err) {
|
|
6276
|
-
console.error("Error:", err.message);
|
|
6277
|
-
process.exit(1);
|
|
6278
|
-
} finally {
|
|
6279
|
-
db?.close();
|
|
6280
|
-
}
|
|
6281
|
-
}
|
|
6282
6387
|
var settingsCommand = new Command11("settings").description(
|
|
6283
6388
|
"Manage user settings"
|
|
6284
6389
|
);
|
|
6285
6390
|
settingsCommand.command("show").description("Show all settings").option("--json", "Output as JSON").action((opts) => {
|
|
6286
|
-
|
|
6391
|
+
withDb((db) => {
|
|
6287
6392
|
if (opts.json) {
|
|
6288
6393
|
console.log(JSON.stringify(getAllSettings(db), null, 2));
|
|
6289
6394
|
return;
|
|
@@ -6304,7 +6409,7 @@ settingsCommand.command("show").description("Show all settings").option("--json"
|
|
|
6304
6409
|
});
|
|
6305
6410
|
});
|
|
6306
6411
|
settingsCommand.command("get <key>").description("Get a single setting").option("--json", "Output as JSON").action((key, opts) => {
|
|
6307
|
-
|
|
6412
|
+
withDb((db) => {
|
|
6308
6413
|
const value = getSetting(db, key);
|
|
6309
6414
|
if (opts.json) {
|
|
6310
6415
|
console.log(JSON.stringify({ key, value: value ?? null }));
|
|
@@ -6318,7 +6423,7 @@ settingsCommand.command("get <key>").description("Get a single setting").option(
|
|
|
6318
6423
|
});
|
|
6319
6424
|
});
|
|
6320
6425
|
settingsCommand.command("set <key> <value>").description("Set a setting").option("--quiet", "Suppress output").action((key, value, opts) => {
|
|
6321
|
-
|
|
6426
|
+
withDb((db) => {
|
|
6322
6427
|
let parsedVal = value;
|
|
6323
6428
|
if (key === "llm.enabled") {
|
|
6324
6429
|
const lower = value.toLowerCase();
|
|
@@ -6335,7 +6440,7 @@ settingsCommand.command("set <key> <value>").description("Set a setting").option
|
|
|
6335
6440
|
});
|
|
6336
6441
|
});
|
|
6337
6442
|
settingsCommand.command("delete <key>").description("Delete a setting").option("--quiet", "Suppress output").action((key, opts) => {
|
|
6338
|
-
|
|
6443
|
+
withDb((db) => {
|
|
6339
6444
|
const deleted = deleteSetting(db, key);
|
|
6340
6445
|
if (!opts.quiet) {
|
|
6341
6446
|
if (deleted) {
|
|
@@ -6349,7 +6454,7 @@ settingsCommand.command("delete <key>").description("Delete a setting").option("
|
|
|
6349
6454
|
settingsCommand.command("llm [state]").description(
|
|
6350
6455
|
"Quickly enable/disable or check local LLM integration (on/off/enable/disable)"
|
|
6351
6456
|
).action((state) => {
|
|
6352
|
-
|
|
6457
|
+
withDb((db) => {
|
|
6353
6458
|
if (!state) {
|
|
6354
6459
|
const enabled = getSetting(db, "llm.enabled") || "false";
|
|
6355
6460
|
console.log(
|
|
@@ -6378,7 +6483,7 @@ settingsCommand.command("llm [state]").description(
|
|
|
6378
6483
|
settingsCommand.command("locale [lang]").description(
|
|
6379
6484
|
"Quickly set or check manual ZAM language override (en/de/es/fr/pt/zh/ja)"
|
|
6380
6485
|
).action((lang) => {
|
|
6381
|
-
|
|
6486
|
+
withDb((db) => {
|
|
6382
6487
|
if (!lang) {
|
|
6383
6488
|
const locale = getSetting(db, "system.locale") || "en";
|
|
6384
6489
|
console.log(`Active language (locale): \x1B[36m${locale}\x1B[0m`);
|
|
@@ -6397,7 +6502,7 @@ settingsCommand.command("locale [lang]").description(
|
|
|
6397
6502
|
});
|
|
6398
6503
|
});
|
|
6399
6504
|
settingsCommand.command("repos").description("Show or set Personal, Team, and Organization repository paths").option("--personal <path>", "Set the Personal Repository path").option("--team <path>", "Set the Team Repository path").option("--org <path>", "Set the Organization Repository path").action((opts) => {
|
|
6400
|
-
|
|
6505
|
+
withDb((db) => {
|
|
6401
6506
|
let changed = false;
|
|
6402
6507
|
if (opts.personal !== void 0) {
|
|
6403
6508
|
setSetting(db, "repo.personal", opts.personal);
|
|
@@ -6444,7 +6549,7 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
|
|
|
6444
6549
|
});
|
|
6445
6550
|
|
|
6446
6551
|
// src/cli/commands/setup.ts
|
|
6447
|
-
import { copyFileSync as copyFileSync2, existsSync as existsSync14, mkdirSync as mkdirSync7, writeFileSync as
|
|
6552
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync14, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
6448
6553
|
import { basename as basename3, dirname as dirname4, join as join12 } from "path";
|
|
6449
6554
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6450
6555
|
import { Command as Command12 } from "commander";
|
|
@@ -6513,7 +6618,7 @@ function writeClaudeMd(skipClaudeMd, cwd = process.cwd()) {
|
|
|
6513
6618
|
return;
|
|
6514
6619
|
}
|
|
6515
6620
|
const name = basename3(cwd);
|
|
6516
|
-
|
|
6621
|
+
writeFileSync7(
|
|
6517
6622
|
dest,
|
|
6518
6623
|
`# ZAM Personal Kernel \xE2\u20AC\u201D ${name}
|
|
6519
6624
|
|
|
@@ -6547,7 +6652,7 @@ function writeAgentsMd(skipAgentsMd, cwd = process.cwd()) {
|
|
|
6547
6652
|
return;
|
|
6548
6653
|
}
|
|
6549
6654
|
const name = basename3(cwd);
|
|
6550
|
-
|
|
6655
|
+
writeFileSync7(
|
|
6551
6656
|
dest,
|
|
6552
6657
|
`# ZAM Personal Kernel - ${name}
|
|
6553
6658
|
|
|
@@ -6602,23 +6707,11 @@ var setupCommand = new Command12("setup").description(
|
|
|
6602
6707
|
|
|
6603
6708
|
// src/cli/commands/skill.ts
|
|
6604
6709
|
import { Command as Command13 } from "commander";
|
|
6605
|
-
function withDb6(fn) {
|
|
6606
|
-
let db;
|
|
6607
|
-
try {
|
|
6608
|
-
db = openDatabase();
|
|
6609
|
-
fn(db);
|
|
6610
|
-
} catch (err) {
|
|
6611
|
-
console.error("Error:", err.message);
|
|
6612
|
-
process.exit(1);
|
|
6613
|
-
} finally {
|
|
6614
|
-
db?.close();
|
|
6615
|
-
}
|
|
6616
|
-
}
|
|
6617
6710
|
var skillCommand = new Command13("skill").description(
|
|
6618
6711
|
"Manage agent skill entries (task recipes)"
|
|
6619
6712
|
);
|
|
6620
6713
|
skillCommand.command("list").description("List all agent skills").option("--json", "Output as JSON").action((opts) => {
|
|
6621
|
-
|
|
6714
|
+
withDb((db) => {
|
|
6622
6715
|
const skills = listAgentSkills(db);
|
|
6623
6716
|
if (opts.json) {
|
|
6624
6717
|
console.log(JSON.stringify(skills, null, 2));
|
|
@@ -6641,7 +6734,7 @@ skillCommand.command("list").description("List all agent skills").option("--json
|
|
|
6641
6734
|
});
|
|
6642
6735
|
});
|
|
6643
6736
|
skillCommand.command("show").description("Show a specific agent skill").requiredOption("--slug <slug>", "Skill slug").option("--json", "Output as JSON").action((opts) => {
|
|
6644
|
-
|
|
6737
|
+
withDb((db) => {
|
|
6645
6738
|
const skill = getAgentSkill(db, opts.slug);
|
|
6646
6739
|
if (!skill) {
|
|
6647
6740
|
console.error(`Skill not found: ${opts.slug}`);
|
|
@@ -6671,7 +6764,7 @@ skillCommand.command("add").description("Register a new agent skill").requiredOp
|
|
|
6671
6764
|
"Source: learned | builtin (default: learned)",
|
|
6672
6765
|
"learned"
|
|
6673
6766
|
).option("--json", "Output as JSON").action((opts) => {
|
|
6674
|
-
|
|
6767
|
+
withDb((db) => {
|
|
6675
6768
|
let steps;
|
|
6676
6769
|
try {
|
|
6677
6770
|
steps = JSON.parse(opts.steps);
|
|
@@ -6700,20 +6793,8 @@ skillCommand.command("add").description("Register a new agent skill").requiredOp
|
|
|
6700
6793
|
|
|
6701
6794
|
// src/cli/commands/stats.ts
|
|
6702
6795
|
import { Command as Command14 } from "commander";
|
|
6703
|
-
function withDb7(fn) {
|
|
6704
|
-
let db;
|
|
6705
|
-
try {
|
|
6706
|
-
db = openDatabase();
|
|
6707
|
-
fn(db);
|
|
6708
|
-
} catch (err) {
|
|
6709
|
-
console.error("Error:", err.message);
|
|
6710
|
-
process.exit(1);
|
|
6711
|
-
} finally {
|
|
6712
|
-
db?.close();
|
|
6713
|
-
}
|
|
6714
|
-
}
|
|
6715
6796
|
var statsCommand = new Command14("stats").description("Show learning dashboard for a user").option("--user <id>", "User ID (default: whoami)").option("--json", "Output as JSON").action((opts) => {
|
|
6716
|
-
|
|
6797
|
+
withDb((db) => {
|
|
6717
6798
|
const userId = resolveUser(opts, db);
|
|
6718
6799
|
const stats = getUserStats(db, userId);
|
|
6719
6800
|
const domains = getDomainCompetence(db, userId);
|
|
@@ -6749,65 +6830,18 @@ var statsCommand = new Command14("stats").description("Show learning dashboard f
|
|
|
6749
6830
|
|
|
6750
6831
|
// src/cli/commands/token.ts
|
|
6751
6832
|
import { Command as Command15 } from "commander";
|
|
6752
|
-
async function withDb8(fn) {
|
|
6753
|
-
let db;
|
|
6754
|
-
try {
|
|
6755
|
-
db = openDatabase();
|
|
6756
|
-
await fn(db);
|
|
6757
|
-
} catch (err) {
|
|
6758
|
-
console.error("Error:", err.message);
|
|
6759
|
-
process.exit(1);
|
|
6760
|
-
} finally {
|
|
6761
|
-
db?.close();
|
|
6762
|
-
}
|
|
6763
|
-
}
|
|
6764
|
-
function jsonOut2(data) {
|
|
6765
|
-
console.log(JSON.stringify(data, null, 2));
|
|
6766
|
-
}
|
|
6767
6833
|
var tokenCommand = new Command15("token").description(
|
|
6768
6834
|
"Manage knowledge tokens"
|
|
6769
6835
|
);
|
|
6770
|
-
tokenCommand.command("register").description("Register a new knowledge token").requiredOption("--slug <slug>", "Unique token slug").requiredOption("--concept <concept>", "Concept description").option("--domain <domain>", "Knowledge domain", "").option("--bloom <level>", "Bloom taxonomy level (1-5)", "1").option("--source-link <link>", "Source file path or reference URL", "").option("--question <question>", "Specific question prompt for recall", "").option("--json", "Output as JSON").action(
|
|
6771
|
-
|
|
6836
|
+
tokenCommand.command("register").description("Register a new knowledge token").requiredOption("--slug <slug>", "Unique token slug").requiredOption("--concept <concept>", "Concept description").option("--domain <domain>", "Knowledge domain", "").option("--bloom <level>", "Bloom taxonomy level (1-5)", "1").option("--source-link <link>", "Source file path or reference URL", "").option("--question <question>", "Specific question prompt for recall", "").option("--json", "Output as JSON").action((opts) => {
|
|
6837
|
+
withDb((db) => {
|
|
6772
6838
|
let question = opts.question || null;
|
|
6773
6839
|
if (!question) {
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
try {
|
|
6780
|
-
let sourceLinkContent = null;
|
|
6781
|
-
if (opts.sourceLink) {
|
|
6782
|
-
const resolved = await resolveReviewContext(
|
|
6783
|
-
opts.sourceLink
|
|
6784
|
-
).catch(() => null);
|
|
6785
|
-
if (resolved) {
|
|
6786
|
-
sourceLinkContent = resolved.content;
|
|
6787
|
-
}
|
|
6788
|
-
}
|
|
6789
|
-
question = await generateQuestionViaLLM(db, {
|
|
6790
|
-
slug: opts.slug,
|
|
6791
|
-
concept: opts.concept,
|
|
6792
|
-
domain: opts.domain,
|
|
6793
|
-
bloomLevel: Number(opts.bloom),
|
|
6794
|
-
context: "",
|
|
6795
|
-
sourceLinkContent
|
|
6796
|
-
});
|
|
6797
|
-
console.log(`Generated: "${question}"`);
|
|
6798
|
-
} catch (err) {
|
|
6799
|
-
console.warn(
|
|
6800
|
-
`LLM question generation failed: ${err.message}. Falling back to template.`
|
|
6801
|
-
);
|
|
6802
|
-
}
|
|
6803
|
-
}
|
|
6804
|
-
if (!question) {
|
|
6805
|
-
question = generateConceptFreeCue(
|
|
6806
|
-
Number(opts.bloom),
|
|
6807
|
-
opts.slug,
|
|
6808
|
-
opts.domain
|
|
6809
|
-
);
|
|
6810
|
-
}
|
|
6840
|
+
question = generateConceptFreeCue(
|
|
6841
|
+
Number(opts.bloom),
|
|
6842
|
+
opts.slug,
|
|
6843
|
+
opts.domain
|
|
6844
|
+
);
|
|
6811
6845
|
}
|
|
6812
6846
|
const token = createToken(db, {
|
|
6813
6847
|
slug: opts.slug,
|
|
@@ -6832,7 +6866,7 @@ tokenCommand.command("register").description("Register a new knowledge token").r
|
|
|
6832
6866
|
});
|
|
6833
6867
|
});
|
|
6834
6868
|
tokenCommand.command("find").description("Fuzzy search for tokens").requiredOption("--query <query>", "Search query").option("--json", "Output as JSON").action((opts) => {
|
|
6835
|
-
|
|
6869
|
+
withDb((db) => {
|
|
6836
6870
|
const results = findTokens(db, opts.query);
|
|
6837
6871
|
if (opts.json) {
|
|
6838
6872
|
console.log(JSON.stringify(results, null, 2));
|
|
@@ -6856,7 +6890,7 @@ tokenCommand.command("find").description("Fuzzy search for tokens").requiredOpti
|
|
|
6856
6890
|
});
|
|
6857
6891
|
});
|
|
6858
6892
|
tokenCommand.command("list").description("List all tokens").option("--domain <domain>", "Filter by domain").option("--json", "Output as JSON").action((opts) => {
|
|
6859
|
-
|
|
6893
|
+
withDb((db) => {
|
|
6860
6894
|
const tokens = listTokens(
|
|
6861
6895
|
db,
|
|
6862
6896
|
opts.domain ? { domain: opts.domain } : void 0
|
|
@@ -6888,8 +6922,8 @@ tokenCommand.command("edit").description("Edit a token's mutable fields").requir
|
|
|
6888
6922
|
).option(
|
|
6889
6923
|
"--source-link <link>",
|
|
6890
6924
|
"Updated source file path or reference URL (blank allowed)"
|
|
6891
|
-
).option("--question <question>", "Updated question text (blank allowed)").option("--json", "Output as JSON").action(
|
|
6892
|
-
|
|
6925
|
+
).option("--question <question>", "Updated question text (blank allowed)").option("--json", "Output as JSON").action((opts) => {
|
|
6926
|
+
withDb((db) => {
|
|
6893
6927
|
const updates = {};
|
|
6894
6928
|
if (opts.concept !== void 0) updates.concept = opts.concept;
|
|
6895
6929
|
if (opts.domain !== void 0) updates.domain = opts.domain;
|
|
@@ -6912,7 +6946,7 @@ tokenCommand.command("edit").description("Edit a token's mutable fields").requir
|
|
|
6912
6946
|
}
|
|
6913
6947
|
const token = updateToken(db, opts.slug, updates);
|
|
6914
6948
|
if (opts.json) {
|
|
6915
|
-
|
|
6949
|
+
jsonOut(token);
|
|
6916
6950
|
return;
|
|
6917
6951
|
}
|
|
6918
6952
|
console.log(`Updated token: ${token.slug}`);
|
|
@@ -6926,7 +6960,7 @@ tokenCommand.command("edit").description("Edit a token's mutable fields").requir
|
|
|
6926
6960
|
});
|
|
6927
6961
|
});
|
|
6928
6962
|
tokenCommand.command("prereq").description("Add a prerequisite edge between tokens").requiredOption("--token <slug>", "Token that requires a prerequisite").requiredOption("--requires <slug>", "Required prerequisite token").option("--json", "Output as JSON").action((opts) => {
|
|
6929
|
-
|
|
6963
|
+
withDb((db) => {
|
|
6930
6964
|
const token = getTokenBySlug(db, opts.token);
|
|
6931
6965
|
if (!token) {
|
|
6932
6966
|
console.error(`Token not found: ${opts.token}`);
|
|
@@ -6956,7 +6990,7 @@ tokenCommand.command("prereq").description("Add a prerequisite edge between toke
|
|
|
6956
6990
|
tokenCommand.command("deprecate").description(
|
|
6957
6991
|
"Mark a token as deprecated (excluded from reviews, not deleted)"
|
|
6958
6992
|
).requiredOption("--slug <slug>", "Token slug to deprecate").option("--json", "Output as JSON").action((opts) => {
|
|
6959
|
-
|
|
6993
|
+
withDb((db) => {
|
|
6960
6994
|
const token = deprecateToken(db, opts.slug);
|
|
6961
6995
|
if (opts.json) {
|
|
6962
6996
|
console.log(JSON.stringify(token, null, 2));
|
|
@@ -6968,7 +7002,7 @@ tokenCommand.command("deprecate").description(
|
|
|
6968
7002
|
});
|
|
6969
7003
|
});
|
|
6970
7004
|
tokenCommand.command("delete").description("Hard-delete a token and its dependent learning data").requiredOption("--slug <slug>", "Token slug to delete").option("--force", "Actually delete the token").option("--json", "Output as JSON").action((opts) => {
|
|
6971
|
-
|
|
7005
|
+
withDb((db) => {
|
|
6972
7006
|
const impact = getTokenDeleteImpact(db, opts.slug);
|
|
6973
7007
|
if (!opts.force) {
|
|
6974
7008
|
const preview = {
|
|
@@ -6978,7 +7012,7 @@ tokenCommand.command("delete").description("Hard-delete a token and its dependen
|
|
|
6978
7012
|
impact
|
|
6979
7013
|
};
|
|
6980
7014
|
if (opts.json) {
|
|
6981
|
-
|
|
7015
|
+
jsonOut(preview);
|
|
6982
7016
|
return;
|
|
6983
7017
|
}
|
|
6984
7018
|
console.log(`Delete preview for ${opts.slug}:`);
|
|
@@ -6998,7 +7032,7 @@ tokenCommand.command("delete").description("Hard-delete a token and its dependen
|
|
|
6998
7032
|
}
|
|
6999
7033
|
const result = deleteToken(db, opts.slug);
|
|
7000
7034
|
if (opts.json) {
|
|
7001
|
-
|
|
7035
|
+
jsonOut({
|
|
7002
7036
|
slug: result.token.slug,
|
|
7003
7037
|
deleted: true,
|
|
7004
7038
|
impact: result.impact
|
|
@@ -7013,7 +7047,7 @@ tokenCommand.command("delete").description("Hard-delete a token and its dependen
|
|
|
7013
7047
|
});
|
|
7014
7048
|
});
|
|
7015
7049
|
tokenCommand.command("status").description("Show full status of a token for a user").requiredOption("--token <slug>", "Token slug").option("--user <id>", "User ID (default: whoami)").option("--json", "Output as JSON").action((opts) => {
|
|
7016
|
-
|
|
7050
|
+
withDb((db) => {
|
|
7017
7051
|
const userId = resolveUser(opts, db);
|
|
7018
7052
|
const token = getTokenBySlug(db, opts.token);
|
|
7019
7053
|
if (!token) {
|
|
@@ -7074,7 +7108,7 @@ tokenCommand.command("status").description("Show full status of a token for a us
|
|
|
7074
7108
|
|
|
7075
7109
|
// src/cli/commands/ui.ts
|
|
7076
7110
|
import { spawn as spawn2, spawnSync } from "child_process";
|
|
7077
|
-
import { existsSync as existsSync15
|
|
7111
|
+
import { existsSync as existsSync15 } from "fs";
|
|
7078
7112
|
import { homedir as homedir8 } from "os";
|
|
7079
7113
|
import { dirname as dirname5, join as join13 } from "path";
|
|
7080
7114
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -7120,6 +7154,14 @@ function findBuiltApp(desktopDir) {
|
|
|
7120
7154
|
}
|
|
7121
7155
|
return null;
|
|
7122
7156
|
}
|
|
7157
|
+
function findInstalledApp() {
|
|
7158
|
+
const candidates = process.platform === "win32" ? [
|
|
7159
|
+
process.env.LOCALAPPDATA && join13(process.env.LOCALAPPDATA, "Programs", "ZAM", "ZAM.exe"),
|
|
7160
|
+
process.env.ProgramFiles && join13(process.env.ProgramFiles, "ZAM", "ZAM.exe"),
|
|
7161
|
+
process.env["ProgramFiles(x86)"] && join13(process.env["ProgramFiles(x86)"], "ZAM", "ZAM.exe")
|
|
7162
|
+
] : process.platform === "darwin" ? ["/Applications/ZAM.app", join13(homedir8(), "Applications", "ZAM.app")] : ["/opt/ZAM/zam", "/usr/bin/zam-desktop"];
|
|
7163
|
+
return candidates.find((candidate) => candidate && existsSync15(candidate)) || null;
|
|
7164
|
+
}
|
|
7123
7165
|
function runNpm(args, opts) {
|
|
7124
7166
|
const res = spawnSync("npm", args, {
|
|
7125
7167
|
stdio: "inherit",
|
|
@@ -7135,6 +7177,12 @@ function ensureDesktopDeps(desktopDir) {
|
|
|
7135
7177
|
);
|
|
7136
7178
|
return runNpm(["install"], { cwd: desktopDir }) === 0;
|
|
7137
7179
|
}
|
|
7180
|
+
function prepareDesktopBridge(repoRoot) {
|
|
7181
|
+
console.log(`${C.cyan}Preparing self-contained desktop bridge...${C.reset}`);
|
|
7182
|
+
return runNpm(["run", "desktop:prepare", "--", "--bundle-node"], {
|
|
7183
|
+
cwd: repoRoot
|
|
7184
|
+
}) === 0;
|
|
7185
|
+
}
|
|
7138
7186
|
function requireRust() {
|
|
7139
7187
|
if (hasCommand("cargo")) return true;
|
|
7140
7188
|
console.error(
|
|
@@ -7193,25 +7241,17 @@ function warnIfCliMissing(repoRoot) {
|
|
|
7193
7241
|
);
|
|
7194
7242
|
}
|
|
7195
7243
|
}
|
|
7196
|
-
function
|
|
7197
|
-
try {
|
|
7198
|
-
const zamDir = join13(homedir8(), ".zam");
|
|
7199
|
-
if (!existsSync15(zamDir)) mkdirSync8(zamDir, { recursive: true });
|
|
7200
|
-
writeFileSync7(join13(zamDir, "cli_path"), repoRoot, "utf8");
|
|
7201
|
-
} catch {
|
|
7202
|
-
}
|
|
7203
|
-
}
|
|
7204
|
-
function launchApp(appPath, repoRoot) {
|
|
7244
|
+
function launchApp(appPath, workingDir) {
|
|
7205
7245
|
console.log(`${C.green}\u2713 Launching ZAM Desktop...${C.reset}`);
|
|
7206
7246
|
if (process.platform === "darwin" && appPath.endsWith(".app")) {
|
|
7207
7247
|
spawn2("open", [appPath], {
|
|
7208
|
-
cwd:
|
|
7248
|
+
cwd: workingDir,
|
|
7209
7249
|
detached: true,
|
|
7210
7250
|
stdio: "ignore"
|
|
7211
7251
|
}).unref();
|
|
7212
7252
|
} else {
|
|
7213
7253
|
spawn2(appPath, [], {
|
|
7214
|
-
cwd:
|
|
7254
|
+
cwd: workingDir,
|
|
7215
7255
|
detached: true,
|
|
7216
7256
|
stdio: "ignore",
|
|
7217
7257
|
windowsHide: true
|
|
@@ -7256,6 +7296,11 @@ var uiCommand = new Command16("ui").description("Launch the ZAM Desktop GUI (Act
|
|
|
7256
7296
|
"--build",
|
|
7257
7297
|
"Build the native installer for your OS (needs Rust, one-time)"
|
|
7258
7298
|
).option("--shortcut", "Create Desktop + Start-menu shortcuts to the GUI").action((opts) => {
|
|
7299
|
+
const installedApp = findInstalledApp();
|
|
7300
|
+
if (!opts.dev && !opts.build && !opts.shortcut && installedApp) {
|
|
7301
|
+
launchApp(installedApp, homedir8());
|
|
7302
|
+
return;
|
|
7303
|
+
}
|
|
7259
7304
|
const desktopDir = findDesktopDir();
|
|
7260
7305
|
if (!desktopDir) {
|
|
7261
7306
|
console.error(
|
|
@@ -7264,11 +7309,11 @@ var uiCommand = new Command16("ui").description("Launch the ZAM Desktop GUI (Act
|
|
|
7264
7309
|
process.exit(1);
|
|
7265
7310
|
}
|
|
7266
7311
|
const repoRoot = dirname5(desktopDir);
|
|
7267
|
-
recordCliHome(repoRoot);
|
|
7268
7312
|
if (opts.build) {
|
|
7269
7313
|
if (!requireRust()) process.exit(1);
|
|
7270
7314
|
if (!requireMsvcOnWindows()) process.exit(1);
|
|
7271
7315
|
if (!ensureDesktopDeps(desktopDir)) process.exit(1);
|
|
7316
|
+
if (!prepareDesktopBridge(repoRoot)) process.exit(1);
|
|
7272
7317
|
console.log(
|
|
7273
7318
|
`${C.cyan}Building the native ZAM installer (this takes a few minutes)...${C.reset}`
|
|
7274
7319
|
);
|
|
@@ -7290,7 +7335,7 @@ ${C.green}\u2713 Done. Installer is in:${C.reset}
|
|
|
7290
7335
|
`${C.dim}Run that installer once \u2014 it adds ZAM to the Start menu and Desktop automatically.${C.reset}`
|
|
7291
7336
|
);
|
|
7292
7337
|
console.log(
|
|
7293
|
-
`${C.dim}
|
|
7338
|
+
`${C.dim}The installer includes the CLI bridge and Node runtime.${C.reset}`
|
|
7294
7339
|
);
|
|
7295
7340
|
}
|
|
7296
7341
|
process.exit(code);
|
|
@@ -7307,17 +7352,17 @@ ${C.green}\u2713 Done. Installer is in:${C.reset}
|
|
|
7307
7352
|
}
|
|
7308
7353
|
const builtApp = findBuiltApp(desktopDir);
|
|
7309
7354
|
if (opts.shortcut) {
|
|
7310
|
-
|
|
7355
|
+
const shortcutTarget = installedApp || builtApp;
|
|
7356
|
+
if (!shortcutTarget) {
|
|
7311
7357
|
console.error(
|
|
7312
7358
|
`${C.red}\u2717 No built app yet. Build it first:${C.reset} ${C.cyan}zam ui --build${C.reset}`
|
|
7313
7359
|
);
|
|
7314
7360
|
process.exit(1);
|
|
7315
7361
|
}
|
|
7316
|
-
createShortcuts(
|
|
7362
|
+
createShortcuts(shortcutTarget, dirname5(shortcutTarget));
|
|
7317
7363
|
return;
|
|
7318
7364
|
}
|
|
7319
7365
|
if (builtApp) {
|
|
7320
|
-
warnIfCliMissing(repoRoot);
|
|
7321
7366
|
launchApp(builtApp, repoRoot);
|
|
7322
7367
|
return;
|
|
7323
7368
|
}
|
|
@@ -7351,20 +7396,8 @@ ${C.dim}After that, 'zam ui' launches it directly.${C.reset}`
|
|
|
7351
7396
|
|
|
7352
7397
|
// src/cli/commands/whoami.ts
|
|
7353
7398
|
import { Command as Command17 } from "commander";
|
|
7354
|
-
function withDb9(fn) {
|
|
7355
|
-
let db;
|
|
7356
|
-
try {
|
|
7357
|
-
db = openDatabase();
|
|
7358
|
-
fn(db);
|
|
7359
|
-
} catch (err) {
|
|
7360
|
-
console.error("Error:", err.message);
|
|
7361
|
-
process.exit(1);
|
|
7362
|
-
} finally {
|
|
7363
|
-
db?.close();
|
|
7364
|
-
}
|
|
7365
|
-
}
|
|
7366
7399
|
var whoamiCommand = new Command17("whoami").description("Show or set the default user identity").option("--set <id>", "Set the default user ID").option("--clear", "Remove the default user ID").option("--json", "Output as JSON").action((opts) => {
|
|
7367
|
-
|
|
7400
|
+
withDb((db) => {
|
|
7368
7401
|
if (opts.set) {
|
|
7369
7402
|
setSetting(db, "user.id", opts.set);
|
|
7370
7403
|
if (opts.json) {
|
|
@@ -7550,10 +7583,14 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
|
7550
7583
|
});
|
|
7551
7584
|
|
|
7552
7585
|
// src/cli/index.ts
|
|
7586
|
+
var __dirname = dirname6(fileURLToPath4(import.meta.url));
|
|
7587
|
+
var pkg = JSON.parse(
|
|
7588
|
+
readFileSync7(join15(__dirname, "..", "..", "package.json"), "utf-8")
|
|
7589
|
+
);
|
|
7553
7590
|
var program = new Command19();
|
|
7554
7591
|
program.name("zam").description(
|
|
7555
7592
|
"The Symbiotic Learning Kernel: Elevating Human Intelligence through AI Collaboration."
|
|
7556
|
-
).version(
|
|
7593
|
+
).version(pkg.version);
|
|
7557
7594
|
program.addCommand(initCommand);
|
|
7558
7595
|
program.addCommand(setupCommand);
|
|
7559
7596
|
program.addCommand(tokenCommand);
|