zam-core 0.3.6 → 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/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 Database from "libsql";
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
- try {
402
- db = new Database(dbPath, dbOpts);
403
- } catch (err) {
404
- const msg = err.message;
405
- if (msg.includes("InvalidLocalState") && options.syncUrl) {
406
- const metaPath = `${dbPath}.meta`;
407
- const infoPath = `${dbPath}-info`;
408
- if (existsSync2(metaPath)) rmSync(metaPath);
409
- if (existsSync2(infoPath)) rmSync(infoPath);
410
- db = new Database(dbPath, dbOpts);
411
- } else {
412
- throw err;
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 qTokens = new Set(
1084
- normalised.split(/[\s,.\-_/\\:;!?()[\]{}]+/).filter((t2) => t2.length > 2)
1085
- );
1086
- const tokens = db.prepare("SELECT * FROM tokens WHERE deprecated_at IS NULL").all();
1087
- const scored = [];
1088
- for (const t2 of tokens) {
1089
- const words = `${t2.slug} ${t2.concept} ${t2.domain}`.toLowerCase().split(/[\s,.\-_/\\:;!?()[\]{}]+/).filter(Boolean);
1090
- let score = 0;
1091
- for (const w of words) {
1092
- if (qTokens.has(w)) score++;
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
- if (t2.concept.toLowerCase().includes(normalised.slice(0, 25))) {
1095
- score += 3;
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
- if (score > 0) {
1098
- scored.push({ score, ...t2 });
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
- function injectShellHooks() {
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
- const pwshHookLine = `
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
- const zshrc = join6(HOME, ".zshrc");
2640
- if (existsSync6(zshrc)) {
2641
- try {
2642
- const content = readFileSync6(zshrc, "utf8");
2643
- if (content.includes("zam monitor start")) {
2644
- results.push({
2645
- shell: "zsh",
2646
- file: zshrc,
2647
- success: true,
2648
- alreadyHooked: true
2649
- });
2650
- } else {
2651
- appendFileSync2(zshrc, hookLine);
2652
- results.push({
2653
- shell: "zsh",
2654
- file: zshrc,
2655
- success: true,
2656
- alreadyHooked: false
2657
- });
2658
- }
2659
- } catch {
2660
- results.push({
2661
- shell: "zsh",
2662
- file: zshrc,
2663
- success: false,
2664
- alreadyHooked: false
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
- const bashrc = join6(HOME, ".bashrc");
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
- try {
2671
- const content = readFileSync6(bashrc, "utf8");
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(HOME, "Documents", "PowerShell"),
2699
- join6(HOME, "Documents", "WindowsPowerShell")
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
- let content = "";
2706
- if (existsSync6(profileFile)) {
2707
- content = readFileSync6(profileFile, "utf8");
2708
- }
2709
- if (content.includes("zam monitor start")) {
2710
- results.push({
2711
- shell: "powershell",
2712
- file: profileFile,
2713
- success: true,
2714
- alreadyHooked: true
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 = "gemma4-it:e4b";
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/bridge.ts
3677
- function jsonOut(data) {
3678
- console.log(JSON.stringify(data, null, 2));
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
- db?.close();
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
- db?.close();
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
- withDb((db) => {
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
- jsonOut({
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 withDbAsync(async (db) => {
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
- jsonOut({
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
- jsonOut({
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
- withDb((db) => {
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
- jsonOut({
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
- withDb((db) => {
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
- jsonOut({
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
- jsonOut({
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
- jsonOut({
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
- withDb((db) => {
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
- jsonOut({
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
- jsonOut({
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
- jsonOut({
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
- jsonOut({
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
- jsonOut({
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
- jsonOut({
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
- jsonOut({ proposals: [], message: "No monitor logs found." });
4214
+ jsonOut2({ proposals: [], message: "No monitor logs found." });
4087
4215
  return;
4088
4216
  }
4089
4217
  if (files.length === 0) {
4090
- jsonOut({ proposals: [], message: "No monitor logs found." });
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
- jsonOut({ proposals: [], message: "No command data in monitor logs." });
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
- jsonOut({
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 withDbAsync(async (db) => {
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
- jsonOut({
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 withDbAsync(async (db) => {
4289
+ await withDbAsync2(async (db) => {
4162
4290
  const result = await ensureLlmReadyHeadless(db, {
4163
4291
  timeoutMs: Number(opts.timeout)
4164
4292
  });
4165
- jsonOut(result);
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 withDbAsync(async (db) => {
4297
+ await withDbAsync2(async (db) => {
4170
4298
  const isEnabled = getSetting(db, "llm.enabled") === "true";
4171
4299
  if (!isEnabled) {
4172
- jsonOut({
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
- jsonOut({ success: true, translation });
4309
+ jsonOut2({ success: true, translation });
4182
4310
  } catch (err) {
4183
- jsonOut({
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 withDbAsync(async (db) => {
4322
+ await withDbAsync2(async (db) => {
4195
4323
  const isEnabled = getSetting(db, "llm.enabled") === "true";
4196
4324
  if (!isEnabled) {
4197
- jsonOut({
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
- jsonOut({ success: true, evaluation });
4351
+ jsonOut2({ success: true, evaluation });
4224
4352
  } catch (err) {
4225
- jsonOut({
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
- withDb((db) => {
4362
+ withDb2((db) => {
4235
4363
  const { enabled, url, model, locale } = getLlmConfig(db);
4236
- jsonOut({
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
- withDb2((db) => {
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
- withDb2((db) => {
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
- withDb2((db) => {
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
- withDb2((db) => {
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 writeFileSync3 } from "fs";
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 withDb3(fn) {
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
- writeFileSync3(hookPath, hookContent, { encoding: "utf-8", flag: "w" });
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
- installHook();
4718
+ installHook2();
4615
4719
  return;
4616
4720
  }
4617
- withDb3((db) => {
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 writeFileSync4 } from "fs";
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
- writeFileSync4(
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
- writeFileSync4(
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 installSuccess = false;
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
- installSuccess = true;
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 (installSuccess) {
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 Hooks\x1B[0m"
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 active-recall skills and enable automatic terminal command observation?",
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("Injecting observation hooks into shell profiles...");
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" : "injected successfully";
5051
- console.log(` \x1B[32m\u2713 ${res.shell} hook: ${action}\x1B[0m`);
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 inject hook in ${res.shell} (${res.file})\x1B[0m`
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 writeFileSync5 } from "fs";
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
- writeFileSync5(tmpFile, appleScript);
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
- execFileSync(
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(db);
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(db) {
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(db);
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
- withDb4((db) => {
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
- withDb4((db) => {
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
- withDb5((db) => {
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
- withDb5((db) => {
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
- withDb5((db) => {
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
- withDb5((db) => {
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
- withDb5((db) => {
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
- withDb5((db) => {
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
- withDb5((db) => {
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 writeFileSync6 } from "fs";
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
- writeFileSync6(
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
- writeFileSync6(
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
- withDb6((db) => {
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
- withDb6((db) => {
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
- withDb6((db) => {
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
- withDb7((db) => {
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(async (opts) => {
6771
- await withDb8(async (db) => {
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
- const isLlmEnabled = getSetting(db, "llm.enabled") === "true";
6775
- if (isLlmEnabled) {
6776
- console.log(
6777
- "Generating high-quality active-recall question via local LLM..."
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
- withDb8((db) => {
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
- withDb8((db) => {
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(async (opts) => {
6892
- await withDb8(async (db) => {
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
- jsonOut2(token);
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
- withDb8((db) => {
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
- withDb8((db) => {
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
- withDb8((db) => {
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
- jsonOut2(preview);
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
- jsonOut2({
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
- withDb8((db) => {
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, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "fs";
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 recordCliHome(repoRoot) {
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: repoRoot,
7248
+ cwd: workingDir,
7209
7249
  detached: true,
7210
7250
  stdio: "ignore"
7211
7251
  }).unref();
7212
7252
  } else {
7213
7253
  spawn2(appPath, [], {
7214
- cwd: repoRoot,
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}Recorded this repo (${repoRoot}) in ~/.zam/cli_path so the installed app finds the CLI.${C.reset}`
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
- if (!builtApp) {
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(builtApp, repoRoot);
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
- withDb9((db) => {
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("0.3.5");
7593
+ ).version(pkg.version);
7557
7594
  program.addCommand(initCommand);
7558
7595
  program.addCommand(setupCommand);
7559
7596
  program.addCommand(tokenCommand);