zam-core 0.9.3 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/app.js CHANGED
@@ -585,7 +585,12 @@ CREATE TABLE IF NOT EXISTS tokens (
585
585
  deprecated_at TEXT,
586
586
  question TEXT,
587
587
  provider TEXT,
588
- topic_id TEXT
588
+ topic_id TEXT,
589
+ -- Who authored the current question ('manual' | 'llm' | 'template');
590
+ -- validated in code, not via CHECK. Column default is 'llm' so unlabeled
591
+ -- writes (pre-M013 rows, old snapshot restores) count as LLM-era content;
592
+ -- createToken() defaults to 'manual' for API callers instead.
593
+ question_source TEXT NOT NULL DEFAULT 'llm'
589
594
  );
590
595
 
591
596
  -- Prerequisite dependency graph: "to learn A, first know B"
@@ -1109,6 +1114,11 @@ async function runMigrations(db) {
1109
1114
  await db.exec(`
1110
1115
  CREATE INDEX IF NOT EXISTS idx_token_contexts_context ON token_contexts(context_id)
1111
1116
  `);
1117
+ if (tokenCols.length > 0 && !tokenCols.some((c) => c.name === "question_source")) {
1118
+ await db.exec(
1119
+ `ALTER TABLE tokens ADD COLUMN question_source TEXT NOT NULL DEFAULT 'llm'`
1120
+ );
1121
+ }
1112
1122
  }
1113
1123
  var DEFAULT_DB_DIR, DEFAULT_DB_PATH, require2;
1114
1124
  var init_connection = __esm({
@@ -1269,7 +1279,11 @@ var init_snapshot = __esm({
1269
1279
  "review_logs",
1270
1280
  "session_syntheses",
1271
1281
  "user_config",
1272
- "agent_skills"
1282
+ "agent_skills",
1283
+ "sources",
1284
+ "token_sources",
1285
+ "contexts",
1286
+ "token_contexts"
1273
1287
  ];
1274
1288
  }
1275
1289
  });
@@ -1754,6 +1768,13 @@ var init_knowledge_context = __esm({
1754
1768
 
1755
1769
  // src/kernel/models/token.ts
1756
1770
  import { ulid as ulid4 } from "ulid";
1771
+ function validateQuestionSource(value) {
1772
+ if (!VALID_QUESTION_SOURCES.includes(value)) {
1773
+ throw new Error(
1774
+ `Invalid question_source: ${value} (expected one of ${VALID_QUESTION_SOURCES.join(", ")})`
1775
+ );
1776
+ }
1777
+ }
1757
1778
  async function createToken(db, input8) {
1758
1779
  const id = ulid4();
1759
1780
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -1762,9 +1783,11 @@ async function createToken(db, input8) {
1762
1783
  throw new Error(`bloom_level must be between 1 and 5, got ${bloom}`);
1763
1784
  }
1764
1785
  const title = input8.title ?? "";
1786
+ const questionSource = input8.question_source ?? "manual";
1787
+ validateQuestionSource(questionSource);
1765
1788
  await db.prepare(`
1766
- INSERT INTO tokens (id, slug, title, concept, domain, bloom_level, context, symbiosis_mode, source_link, question, provider, topic_id, created_at, updated_at)
1767
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1789
+ INSERT INTO tokens (id, slug, title, concept, domain, bloom_level, context, symbiosis_mode, source_link, question, question_source, provider, topic_id, created_at, updated_at)
1790
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1768
1791
  `).run(
1769
1792
  id,
1770
1793
  input8.slug,
@@ -1776,6 +1799,7 @@ async function createToken(db, input8) {
1776
1799
  input8.symbiosis_mode ?? null,
1777
1800
  input8.source_link ?? null,
1778
1801
  input8.question ?? null,
1802
+ questionSource,
1779
1803
  input8.provider ?? null,
1780
1804
  input8.topic_id ?? null,
1781
1805
  now,
@@ -1864,6 +1888,12 @@ async function updateToken(db, slug, updates) {
1864
1888
  fields.push("question = ?");
1865
1889
  values.push(updates.question);
1866
1890
  }
1891
+ const questionSource = updates.question_source ?? (updates.question !== void 0 ? "manual" : void 0);
1892
+ if (questionSource !== void 0) {
1893
+ validateQuestionSource(questionSource);
1894
+ fields.push("question_source = ?");
1895
+ values.push(questionSource);
1896
+ }
1867
1897
  if (updates.provider !== void 0) {
1868
1898
  fields.push("provider = ?");
1869
1899
  values.push(updates.provider);
@@ -2192,6 +2222,9 @@ async function importCurriculumCards(db, userId, cards) {
2192
2222
  symbiosis_mode: symbiosisMode,
2193
2223
  source_link: card.source_link || null,
2194
2224
  question: card.question || null,
2225
+ // Curriculum cards are LLM-extracted; their questions are LLM
2226
+ // inventions, not human-authored content.
2227
+ question_source: "llm",
2195
2228
  provider: card.provider || null,
2196
2229
  topic_id: card.topic_id || null
2197
2230
  });
@@ -2515,10 +2548,12 @@ async function assertPrerequisiteDoesNotCreateCycle(db, tokenId, prerequisiteId)
2515
2548
  throw new Error("Cannot add prerequisite: would create a cycle");
2516
2549
  }
2517
2550
  }
2551
+ var VALID_QUESTION_SOURCES;
2518
2552
  var init_token = __esm({
2519
2553
  "src/kernel/models/token.ts"() {
2520
2554
  "use strict";
2521
2555
  init_card();
2556
+ VALID_QUESTION_SOURCES = ["manual", "llm", "template"];
2522
2557
  }
2523
2558
  });
2524
2559
 
@@ -5142,7 +5177,8 @@ async function buildReviewQueue(db, options) {
5142
5177
  c.state AS state,
5143
5178
  c.due_at AS due_at,
5144
5179
  t.source_link AS source_link,
5145
- t.question AS question
5180
+ t.question AS question,
5181
+ t.question_source AS question_source
5146
5182
  FROM cards c
5147
5183
  JOIN tokens t ON t.id = c.token_id
5148
5184
  WHERE c.user_id = ?
@@ -5172,7 +5208,8 @@ async function buildReviewQueue(db, options) {
5172
5208
  c.state AS state,
5173
5209
  c.due_at AS due_at,
5174
5210
  t.source_link AS source_link,
5175
- t.question AS question
5211
+ t.question AS question,
5212
+ t.question_source AS question_source
5176
5213
  FROM cards c
5177
5214
  JOIN tokens t ON t.id = c.token_id
5178
5215
  WHERE c.user_id = ?
@@ -5241,7 +5278,8 @@ function rowToItem(row) {
5241
5278
  state: row.state,
5242
5279
  dueAt: row.due_at,
5243
5280
  sourceLink: row.source_link,
5244
- question: row.question
5281
+ question: row.question,
5282
+ questionSource: row.question_source
5245
5283
  };
5246
5284
  }
5247
5285
  function intersperseNew(reviews, newCards, interval) {
@@ -6770,9 +6808,9 @@ var init_kernel = __esm({
6770
6808
  });
6771
6809
 
6772
6810
  // src/cli/app.ts
6773
- import { readFileSync as readFileSync17 } from "fs";
6774
- import { dirname as dirname11, join as join25 } from "path";
6775
- import { fileURLToPath as fileURLToPath5 } from "url";
6811
+ import { readFileSync as readFileSync18 } from "fs";
6812
+ import { dirname as dirname12, join as join27 } from "path";
6813
+ import { fileURLToPath as fileURLToPath6 } from "url";
6776
6814
  import { Command as Command27 } from "commander";
6777
6815
 
6778
6816
  // src/cli/commands/agent.ts
@@ -7118,12 +7156,10 @@ function connectHarnessMcp(harnessId, opts) {
7118
7156
  let content = "";
7119
7157
  let alreadyConfigured = false;
7120
7158
  let hint = "";
7121
- if (harnessId === "claude-code") {
7122
- targetPath = join12(opts.cwd, ".mcp.json");
7123
- hint = "Claude Code will prompt you to approve the 'zam' MCP server on next launch.";
7159
+ const mergeMcpServersJson = (path) => {
7124
7160
  let existing = {};
7125
- if (exists(targetPath)) {
7126
- existing = parseMcpJsonConfig(targetPath, read(targetPath));
7161
+ if (exists(path)) {
7162
+ existing = parseMcpJsonConfig(path, read(path));
7127
7163
  }
7128
7164
  if (!existing.mcpServers) {
7129
7165
  existing.mcpServers = {};
@@ -7132,7 +7168,29 @@ function connectHarnessMcp(harnessId, opts) {
7132
7168
  command: opts.zamPath,
7133
7169
  args: ["mcp"]
7134
7170
  };
7135
- content = JSON.stringify(existing, null, 2);
7171
+ return JSON.stringify(existing, null, 2);
7172
+ };
7173
+ if (harnessId === "claude-code") {
7174
+ targetPath = join12(opts.cwd, ".mcp.json");
7175
+ hint = "Claude Code will prompt you to approve the 'zam' MCP server on next launch.";
7176
+ content = mergeMcpServersJson(targetPath);
7177
+ } else if (harnessId === "claude-desktop") {
7178
+ const platform = opts.platform ?? process.platform;
7179
+ targetPath = platform === "win32" ? join12(
7180
+ opts.home,
7181
+ "AppData",
7182
+ "Roaming",
7183
+ "Claude",
7184
+ "claude_desktop_config.json"
7185
+ ) : platform === "darwin" ? join12(
7186
+ opts.home,
7187
+ "Library",
7188
+ "Application Support",
7189
+ "Claude",
7190
+ "claude_desktop_config.json"
7191
+ ) : join12(opts.home, ".config", "Claude", "claude_desktop_config.json");
7192
+ hint = "Restart Claude Desktop to load the 'zam' MCP server; MCP Apps panels render inline in the chat.";
7193
+ content = mergeMcpServersJson(targetPath);
7136
7194
  } else if (harnessId === "antigravity") {
7137
7195
  targetPath = join12(opts.home, ".gemini", "config", "mcp_config.json");
7138
7196
  hint = "Shared config read by Antigravity CLI and IDE (2.0+); older IDE builds read ~/.gemini/antigravity/mcp_config.json instead. Refresh Installed MCP Servers; the first tool call may still require approval.";
@@ -7265,6 +7323,7 @@ var C = {
7265
7323
  var SUPPORTED_AGENTS = ["opencode"];
7266
7324
  var CONNECT_HARNESSES = [
7267
7325
  "claude-code",
7326
+ "claude-desktop",
7268
7327
  "antigravity",
7269
7328
  "codex",
7270
7329
  "opencode",
@@ -7383,7 +7442,7 @@ var openCmd = new Command("open").description("Open an agent harness in the work
7383
7442
  });
7384
7443
  var connectCmd = new Command("connect").description("Configure the ZAM MCP server for an agent harness").argument(
7385
7444
  "<harness>",
7386
- "Harness to connect: claude-code | antigravity | codex | opencode | goose | copilot"
7445
+ "Harness to connect: claude-code | claude-desktop | antigravity | codex | opencode | goose | copilot"
7387
7446
  ).option(
7388
7447
  "--print",
7389
7448
  "Print configuration changes instead of writing them to disk"
@@ -7446,9 +7505,9 @@ var agentCommand = new Command("agent").description("Provision and inspect the a
7446
7505
  init_kernel();
7447
7506
  import { execFileSync as execFileSync3 } from "child_process";
7448
7507
  import { randomBytes as randomBytes2 } from "crypto";
7449
- import { existsSync as existsSync17, readdirSync as readdirSync2, readFileSync as readFileSync13, rmSync as rmSync3 } from "fs";
7508
+ import { existsSync as existsSync17, readdirSync as readdirSync2, readFileSync as readFileSync14, rmSync as rmSync3 } from "fs";
7450
7509
  import { homedir as homedir11, tmpdir as tmpdir3 } from "os";
7451
- import { join as join18, resolve as resolve4 } from "path";
7510
+ import { join as join20, resolve as resolve4 } from "path";
7452
7511
  import { Command as Command2 } from "commander";
7453
7512
  import { ulid as ulid9 } from "ulid";
7454
7513
 
@@ -7720,6 +7779,9 @@ async function generateQuestionViaLLM(db, input8) {
7720
7779
  const bloom = input8.bloomLevel >= 1 && input8.bloomLevel <= 5 ? input8.bloomLevel : 1;
7721
7780
  const verb = BLOOM_VERBS2[bloom];
7722
7781
  const langName = LANGUAGE_NAMES[cfg.locale] || "English";
7782
+ const existingQuestion = input8.existingQuestion?.trim();
7783
+ const variationGuideline = existingQuestion ? `
7784
+ 5. A canonical question for this token already exists. Generate a fresh VARIATION of it: test the same knowledge, but with different wording or from a different angle, so the learner cannot memorize the exact phrasing. Never repeat the canonical question verbatim.` : "";
7723
7785
  const systemPrompt = `You are ZAM, a highly precise agentic skills trainer.
7724
7786
  Your task is to generate a single, clear, conceptual active-recall question (flashcard front) in ${langName} for a knowledge token.
7725
7787
 
@@ -7727,12 +7789,13 @@ Guidelines:
7727
7789
  1. The question MUST match the Bloom level: ${verb} (Level ${bloom}).
7728
7790
  2. CRITICAL: The question MUST NOT contain or reveal the concept text itself! The concept is the answer (flashcard back) that the learner needs to recall.
7729
7791
  3. Keep the question concise, highly specific, and clear. Avoid generic prompts like "What is the concept of..." if possible, and ask about the core mechanism, function, or purpose of the slug/concept without giving the answer away.
7730
- 4. Output ONLY the raw question text in ${langName}. Do not include any preamble, headers, markdown fences, or conversational filler.`;
7792
+ 4. Output ONLY the raw question text in ${langName}. Do not include any preamble, headers, markdown fences, or conversational filler.${variationGuideline}`;
7731
7793
  const userPrompt = `Domain: ${input8.domain}
7732
7794
  Slug: ${input8.slug}
7733
7795
  Concept to Recall (DO NOT REVEAL IN QUESTION): ${input8.concept}
7734
7796
  Context: ${input8.context || "(none)"}
7735
- ${input8.sourceLinkContent ? `Source Reference:
7797
+ ${existingQuestion ? `Canonical Question (vary, do not repeat verbatim): ${existingQuestion}
7798
+ ` : ""}${input8.sourceLinkContent ? `Source Reference:
7736
7799
  ${input8.sourceLinkContent}` : ""}
7737
7800
 
7738
7801
  Active-Recall Question:`;
@@ -8831,7 +8894,8 @@ async function fetchWithInteractiveTimeout(url, options = {}) {
8831
8894
  }
8832
8895
  async function ensureHighQualityQuestion(db, token) {
8833
8896
  const { enabled } = await getLlmConfig(db);
8834
- if (enabled) {
8897
+ const variationEnabled = await getSetting(db, "llm.dynamic_questions") !== "false";
8898
+ if (enabled && variationEnabled) {
8835
8899
  try {
8836
8900
  let sourceLinkContent = null;
8837
8901
  if (token.sourceLink) {
@@ -8847,10 +8911,10 @@ async function ensureHighQualityQuestion(db, token) {
8847
8911
  concept: token.concept,
8848
8912
  domain: token.domain,
8849
8913
  bloomLevel: token.bloomLevel,
8850
- sourceLinkContent
8914
+ sourceLinkContent,
8915
+ existingQuestion: token.question
8851
8916
  });
8852
8917
  if (generated.text.trim().length > 0) {
8853
- await updateToken(db, token.slug, { question: generated.text });
8854
8918
  return {
8855
8919
  question: generated.text,
8856
8920
  source: "llm",
@@ -9058,7 +9122,7 @@ async function readWebLink(url) {
9058
9122
  redirect: "manual",
9059
9123
  signal: controller.signal,
9060
9124
  headers: {
9061
- "User-Agent": "ZAM-Content-Studio/0.9.3"
9125
+ "User-Agent": "ZAM-Content-Studio/0.10.0"
9062
9126
  }
9063
9127
  });
9064
9128
  if (res.status >= 300 && res.status < 400) {
@@ -9123,6 +9187,8 @@ async function readImageOCR(db, imagePath) {
9123
9187
 
9124
9188
  // src/cli/bridge-handlers.ts
9125
9189
  init_kernel();
9190
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync8 } from "fs";
9191
+ import { join as join16 } from "path";
9126
9192
 
9127
9193
  // src/cli/knowledge-contexts.ts
9128
9194
  init_kernel();
@@ -9368,6 +9434,164 @@ async function resolveSuggestMinSimilarity(db) {
9368
9434
  return Number.isFinite(parsed) && parsed > 0 && parsed <= 1 ? parsed : 0.45;
9369
9435
  }
9370
9436
 
9437
+ // src/cli/update/latest-version.ts
9438
+ import { readFileSync as readFileSync11 } from "fs";
9439
+ import { dirname as dirname6, join as join14 } from "path";
9440
+ import { fileURLToPath as fileURLToPath2 } from "url";
9441
+ var GITHUB_REPO = "zam-os/zam";
9442
+ function currentVersion() {
9443
+ const here = dirname6(fileURLToPath2(import.meta.url));
9444
+ for (const up of ["..", "../..", "../../.."]) {
9445
+ try {
9446
+ const pkg2 = JSON.parse(
9447
+ readFileSync11(join14(here, up, "package.json"), "utf-8")
9448
+ );
9449
+ if (pkg2.version) return pkg2.version;
9450
+ } catch {
9451
+ }
9452
+ }
9453
+ return "0.0.0";
9454
+ }
9455
+ async function fetchLatestVersion(repo) {
9456
+ const res = await fetch(
9457
+ `https://api.github.com/repos/${repo}/releases/latest`,
9458
+ {
9459
+ headers: {
9460
+ Accept: "application/vnd.github+json",
9461
+ "User-Agent": "zam-cli"
9462
+ }
9463
+ }
9464
+ );
9465
+ if (!res.ok) {
9466
+ throw new Error(
9467
+ `Could not reach the release server (HTTP ${res.status}). Pass --latest <version> to check offline.`
9468
+ );
9469
+ }
9470
+ const data = await res.json();
9471
+ if (!data.tag_name) throw new Error("No published release found yet.");
9472
+ return data.tag_name;
9473
+ }
9474
+
9475
+ // src/cli/workspaces/active.ts
9476
+ init_kernel();
9477
+ import { existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
9478
+ import { homedir as homedir10 } from "os";
9479
+ import { basename as basename3, join as join15, resolve as resolve3 } from "path";
9480
+ var LEGACY_WORKSPACE_DIR_KEY = "personal.workspace_dir";
9481
+ var DEFAULT_WORKSPACE_ID = "personal";
9482
+ function defaultWorkspaceDir() {
9483
+ return join15(homedir10(), "Documents", "zam");
9484
+ }
9485
+ function normalizeWorkspacePath(path) {
9486
+ const resolved = resolve3(path);
9487
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
9488
+ }
9489
+ function sameWorkspacePath(left, right) {
9490
+ return normalizeWorkspacePath(left) === normalizeWorkspacePath(right);
9491
+ }
9492
+ function labelFromPath(path) {
9493
+ return basename3(path) || "ZAM";
9494
+ }
9495
+ function workspaceIdFromPath(dir) {
9496
+ const base = basename3(dir).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
9497
+ const prefix = base || "workspace";
9498
+ const existing = new Set(getConfiguredWorkspaces().map((item) => item.id));
9499
+ if (!existing.has(prefix)) return prefix;
9500
+ let index = 2;
9501
+ while (existing.has(`${prefix}-${index}`)) index++;
9502
+ return `${prefix}-${index}`;
9503
+ }
9504
+ function findWorkspaceByPath(dir) {
9505
+ return getConfiguredWorkspaces().find(
9506
+ (workspace) => sameWorkspacePath(workspace.path, dir)
9507
+ );
9508
+ }
9509
+ async function clearLegacyWorkspaceDir(db) {
9510
+ await deleteSetting(db, LEGACY_WORKSPACE_DIR_KEY);
9511
+ }
9512
+ async function migrateLegacyWorkspaceDir(db) {
9513
+ const legacyDir = await getSetting(db, LEGACY_WORKSPACE_DIR_KEY);
9514
+ if (!legacyDir) return void 0;
9515
+ const path = resolve3(legacyDir);
9516
+ const existing = findWorkspaceByPath(path);
9517
+ const migrated = existing ?? {
9518
+ id: getConfiguredWorkspaces().some(
9519
+ (workspace) => workspace.id === DEFAULT_WORKSPACE_ID
9520
+ ) ? workspaceIdFromPath(path) : DEFAULT_WORKSPACE_ID,
9521
+ label: labelFromPath(path),
9522
+ kind: "personal",
9523
+ path
9524
+ };
9525
+ if (!existing) {
9526
+ upsertConfiguredWorkspace(migrated);
9527
+ }
9528
+ if (!getActiveWorkspace()) {
9529
+ setActiveWorkspaceId(migrated.id);
9530
+ }
9531
+ await clearLegacyWorkspaceDir(db);
9532
+ return migrated;
9533
+ }
9534
+ async function ensureActiveWorkspace(db) {
9535
+ await migrateLegacyWorkspaceDir(db);
9536
+ const active = getActiveWorkspace();
9537
+ if (active) {
9538
+ mkdirSync9(active.path, { recursive: true });
9539
+ return active;
9540
+ }
9541
+ const configured = getConfiguredWorkspaces()[0];
9542
+ if (configured) {
9543
+ setActiveWorkspaceId(configured.id);
9544
+ mkdirSync9(configured.path, { recursive: true });
9545
+ return configured;
9546
+ }
9547
+ const workspace = {
9548
+ id: DEFAULT_WORKSPACE_ID,
9549
+ label: "Personal",
9550
+ kind: "personal",
9551
+ path: defaultWorkspaceDir()
9552
+ };
9553
+ mkdirSync9(workspace.path, { recursive: true });
9554
+ upsertConfiguredWorkspace(workspace);
9555
+ setActiveWorkspaceId(workspace.id);
9556
+ await clearLegacyWorkspaceDir(db);
9557
+ return workspace;
9558
+ }
9559
+ async function activateWorkspace(db, workspace) {
9560
+ upsertConfiguredWorkspace(workspace);
9561
+ setActiveWorkspaceId(workspace.id);
9562
+ await clearLegacyWorkspaceDir(db);
9563
+ return workspace;
9564
+ }
9565
+ async function activateWorkspacePath(db, dir, opts = {}) {
9566
+ await migrateLegacyWorkspaceDir(db);
9567
+ const path = resolve3(dir);
9568
+ const existing = opts.id ? void 0 : findWorkspaceByPath(path);
9569
+ if (existing) {
9570
+ setActiveWorkspaceId(existing.id);
9571
+ await clearLegacyWorkspaceDir(db);
9572
+ return existing;
9573
+ }
9574
+ const workspace = {
9575
+ id: opts.id || workspaceIdFromPath(path),
9576
+ label: opts.label || labelFromPath(path),
9577
+ kind: opts.kind || "personal",
9578
+ path
9579
+ };
9580
+ return activateWorkspace(db, workspace);
9581
+ }
9582
+ async function removeWorkspaceAndResolveActive(db, id) {
9583
+ removeConfiguredWorkspace(id);
9584
+ await clearLegacyWorkspaceDir(db);
9585
+ const activeWorkspace = await ensureActiveWorkspace(db);
9586
+ return {
9587
+ activeWorkspace,
9588
+ workspaces: getConfiguredWorkspaces()
9589
+ };
9590
+ }
9591
+ function existingWorkspaceDirOrHome(workspace) {
9592
+ return existsSync15(workspace.path) ? workspace.path : homedir10();
9593
+ }
9594
+
9371
9595
  // src/cli/bridge-handlers.ts
9372
9596
  var BLOOM_VERBS3 = {
9373
9597
  1: "Remember",
@@ -9803,7 +10027,10 @@ async function addToken(db, params) {
9803
10027
  context: params.context,
9804
10028
  symbiosis_mode: params.symbiosisMode,
9805
10029
  source_link: params.sourceLink ?? null,
9806
- question: params.question ?? null
10030
+ question: params.question ?? null,
10031
+ // Bridge/MCP callers are agents: their questions are LLM-authored and
10032
+ // stay refreshable. Humans author questions via the token CLI instead.
10033
+ question_source: params.question ? "llm" : void 0
9807
10034
  });
9808
10035
  for (const context of assignedContexts) {
9809
10036
  await assignTokenToContext(db, token.id, context.id);
@@ -10130,6 +10357,33 @@ async function sessionOpen(db, params) {
10130
10357
  relevant: findTokensResult
10131
10358
  };
10132
10359
  }
10360
+ async function backupCreate(db, params) {
10361
+ const targetDir = params.dir || (await ensureActiveWorkspace(db)).path;
10362
+ const snapshot = await exportSnapshot(db);
10363
+ const manifest = verifySnapshot(snapshot);
10364
+ const backupDir = join16(targetDir, "zam-backups");
10365
+ mkdirSync10(backupDir, { recursive: true });
10366
+ const stamp = manifest.createdAt.replace(/[:.]/g, "-");
10367
+ const path = join16(backupDir, `zam-snapshot-${stamp}.sql`);
10368
+ writeFileSync8(path, snapshot, "utf-8");
10369
+ return {
10370
+ ok: true,
10371
+ path,
10372
+ createdAt: manifest.createdAt,
10373
+ checksum: manifest.checksum,
10374
+ tables: manifest.tables
10375
+ };
10376
+ }
10377
+ async function updateCheck(params) {
10378
+ const current = currentVersion();
10379
+ const latest = params.latest ?? await fetchLatestVersion(GITHUB_REPO);
10380
+ const channel = params.channel ?? getInstallChannel();
10381
+ return decideUpdate({
10382
+ currentVersion: current,
10383
+ latestVersion: latest,
10384
+ channel
10385
+ });
10386
+ }
10133
10387
 
10134
10388
  // src/cli/curriculum/breadcrumb.ts
10135
10389
  init_kernel();
@@ -12796,9 +13050,9 @@ function getCurriculumProvider(id) {
12796
13050
  // src/cli/llm/vision.ts
12797
13051
  init_kernel();
12798
13052
  import { randomBytes } from "crypto";
12799
- import { readFileSync as readFileSync11 } from "fs";
13053
+ import { readFileSync as readFileSync12 } from "fs";
12800
13054
  import { tmpdir as tmpdir2 } from "os";
12801
- import { basename as basename3, join as join14 } from "path";
13055
+ import { basename as basename4, join as join17 } from "path";
12802
13056
  var LANGUAGE_NAMES2 = {
12803
13057
  en: "English",
12804
13058
  de: "German",
@@ -12834,13 +13088,13 @@ async function observeUiSnapshotViaLLM(db, input8) {
12834
13088
  const imageUrls = [];
12835
13089
  const isVideo = /\.(mp4|mov|m4v|avi|mkv|webm)$/i.test(input8.imagePath);
12836
13090
  if (isVideo) {
12837
- const { mkdirSync: mkdirSync15, readdirSync: readdirSync3, rmSync: rmSync4 } = await import("fs");
13091
+ const { mkdirSync: mkdirSync16, readdirSync: readdirSync3, rmSync: rmSync4 } = await import("fs");
12838
13092
  const { execSync: execSync6 } = await import("child_process");
12839
- const tempDir = join14(
13093
+ const tempDir = join17(
12840
13094
  tmpdir2(),
12841
13095
  `zam-frames-${randomBytes(4).toString("hex")}`
12842
13096
  );
12843
- mkdirSync15(tempDir, { recursive: true });
13097
+ mkdirSync16(tempDir, { recursive: true });
12844
13098
  try {
12845
13099
  execSync6(
12846
13100
  `ffmpeg -i "${input8.imagePath}" -vf "fps=1/3,scale=1280:-1" -vsync vfr "${tempDir}/frame_%03d.png"`,
@@ -12869,7 +13123,7 @@ async function observeUiSnapshotViaLLM(db, input8) {
12869
13123
  }
12870
13124
  }
12871
13125
  for (const file of sampledFiles) {
12872
- const bytes = readFileSync11(join14(tempDir, file));
13126
+ const bytes = readFileSync12(join17(tempDir, file));
12873
13127
  imageUrls.push(`data:image/png;base64,${bytes.toString("base64")}`);
12874
13128
  }
12875
13129
  } finally {
@@ -12879,7 +13133,7 @@ async function observeUiSnapshotViaLLM(db, input8) {
12879
13133
  }
12880
13134
  }
12881
13135
  } else {
12882
- const imageBytes = readFileSync11(input8.imagePath);
13136
+ const imageBytes = readFileSync12(input8.imagePath);
12883
13137
  const ext = input8.imagePath.split(".").pop()?.toLowerCase();
12884
13138
  const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
12885
13139
  imageUrls.push(`data:${mime};base64,${imageBytes.toString("base64")}`);
@@ -13117,7 +13371,7 @@ function buildReport(input8, draft) {
13117
13371
  evidence: [
13118
13372
  {
13119
13373
  type: "keyframe",
13120
- ref: input8.evidenceRef ?? basename3(input8.imagePath),
13374
+ ref: input8.evidenceRef ?? basename4(input8.imagePath),
13121
13375
  redacted: input8.redacted ?? false
13122
13376
  }
13123
13377
  ],
@@ -13139,7 +13393,7 @@ function uncertainReport(input8, summary) {
13139
13393
  evidence: [
13140
13394
  {
13141
13395
  type: "keyframe",
13142
- ref: input8.evidenceRef ?? basename3(input8.imagePath),
13396
+ ref: input8.evidenceRef ?? basename4(input8.imagePath),
13143
13397
  redacted: input8.redacted ?? false
13144
13398
  }
13145
13399
  ],
@@ -13348,36 +13602,36 @@ async function withProviderScope(machine, action) {
13348
13602
 
13349
13603
  // src/cli/provisioning/index.ts
13350
13604
  import {
13351
- existsSync as existsSync15,
13605
+ existsSync as existsSync16,
13352
13606
  lstatSync,
13353
- mkdirSync as mkdirSync9,
13354
- readFileSync as readFileSync12,
13607
+ mkdirSync as mkdirSync11,
13608
+ readFileSync as readFileSync13,
13355
13609
  realpathSync,
13356
13610
  rmSync as rmSync2,
13357
13611
  symlinkSync,
13358
- writeFileSync as writeFileSync8
13612
+ writeFileSync as writeFileSync9
13359
13613
  } from "fs";
13360
- import { basename as basename4, dirname as dirname6, join as join15 } from "path";
13361
- import { fileURLToPath as fileURLToPath2 } from "url";
13614
+ import { basename as basename5, dirname as dirname7, join as join18 } from "path";
13615
+ import { fileURLToPath as fileURLToPath3 } from "url";
13362
13616
  var packageRoot = [
13363
- fileURLToPath2(new URL("../..", import.meta.url)),
13364
- fileURLToPath2(new URL("../../..", import.meta.url))
13365
- ].find((candidate) => existsSync15(join15(candidate, "package.json"))) ?? fileURLToPath2(new URL("../..", import.meta.url));
13617
+ fileURLToPath3(new URL("../..", import.meta.url)),
13618
+ fileURLToPath3(new URL("../../..", import.meta.url))
13619
+ ].find((candidate) => existsSync16(join18(candidate, "package.json"))) ?? fileURLToPath3(new URL("../..", import.meta.url));
13366
13620
  var ALL_SETUP_AGENTS = ["claude", "copilot", "codex", "agent"];
13367
13621
  var SKILL_PAIRS = [
13368
13622
  {
13369
- from: join15(packageRoot, ".claude", "skills", "zam", "SKILL.md"),
13370
- to: join15(".claude", "skills", "zam", "SKILL.md"),
13623
+ from: join18(packageRoot, ".claude", "skills", "zam", "SKILL.md"),
13624
+ to: join18(".claude", "skills", "zam", "SKILL.md"),
13371
13625
  agents: ["claude", "copilot"]
13372
13626
  },
13373
13627
  {
13374
- from: join15(packageRoot, ".agent", "skills", "zam", "SKILL.md"),
13375
- to: join15(".agent", "skills", "zam", "SKILL.md"),
13628
+ from: join18(packageRoot, ".agent", "skills", "zam", "SKILL.md"),
13629
+ to: join18(".agent", "skills", "zam", "SKILL.md"),
13376
13630
  agents: ["agent"]
13377
13631
  },
13378
13632
  {
13379
- from: join15(packageRoot, ".agents", "skills", "zam", "SKILL.md"),
13380
- to: join15(".agents", "skills", "zam", "SKILL.md"),
13633
+ from: join18(packageRoot, ".agents", "skills", "zam", "SKILL.md"),
13634
+ to: join18(".agents", "skills", "zam", "SKILL.md"),
13381
13635
  agents: ["codex"]
13382
13636
  }
13383
13637
  ];
@@ -13437,15 +13691,15 @@ function linkPointsTo(sourceDir, destinationDir) {
13437
13691
  function isZamSkillCopy(destinationDir) {
13438
13692
  try {
13439
13693
  if (!lstatSync(destinationDir).isDirectory()) return false;
13440
- const skillFile = join15(destinationDir, "SKILL.md");
13441
- if (!existsSync15(skillFile)) return false;
13442
- return /^name:\s*zam\s*$/m.test(readFileSync12(skillFile, "utf8"));
13694
+ const skillFile = join18(destinationDir, "SKILL.md");
13695
+ if (!existsSync16(skillFile)) return false;
13696
+ return /^name:\s*zam\s*$/m.test(readFileSync13(skillFile, "utf8"));
13443
13697
  } catch {
13444
13698
  return false;
13445
13699
  }
13446
13700
  }
13447
13701
  function classifySkillDestination(sourceDir, destinationDir) {
13448
- if (!existsSync15(sourceDir)) return "source-missing";
13702
+ if (!existsSync16(sourceDir)) return "source-missing";
13449
13703
  if (!isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir)) {
13450
13704
  return "source-directory";
13451
13705
  }
@@ -13462,9 +13716,9 @@ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {})
13462
13716
  };
13463
13717
  for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
13464
13718
  if (!pairAgents.some((agent) => agents.has(agent))) continue;
13465
- const sourceDir = dirname6(from);
13466
- const destinationDir = dirname6(join15(cwd, to));
13467
- const label = dirname6(to);
13719
+ const sourceDir = dirname7(from);
13720
+ const destinationDir = dirname7(join18(cwd, to));
13721
+ const label = dirname7(to);
13468
13722
  const state = classifySkillDestination(sourceDir, destinationDir);
13469
13723
  if (state === "source-missing") {
13470
13724
  if (!opts.quiet) {
@@ -13521,7 +13775,7 @@ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {})
13521
13775
  if (destinationExists) {
13522
13776
  rmSync2(destinationDir, { recursive: true, force: true });
13523
13777
  }
13524
- mkdirSync9(dirname6(destinationDir), { recursive: true });
13778
+ mkdirSync11(dirname7(destinationDir), { recursive: true });
13525
13779
  symlinkSync(
13526
13780
  sourceDir,
13527
13781
  destinationDir,
@@ -13537,8 +13791,8 @@ function inspectSkillLinks(cwd = process.cwd(), agents = parseSetupAgents()) {
13537
13791
  const results = [];
13538
13792
  for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
13539
13793
  if (!pairAgents.some((agent) => agents.has(agent))) continue;
13540
- const sourceDir = dirname6(from);
13541
- const destinationDir = dirname6(join15(cwd, to));
13794
+ const sourceDir = dirname7(from);
13795
+ const destinationDir = dirname7(join18(cwd, to));
13542
13796
  results.push({
13543
13797
  agents: pairAgents,
13544
13798
  source: sourceDir,
@@ -13564,15 +13818,15 @@ function upsertMarkedBlock(dest, blockBody, dryRun) {
13564
13818
  const block = `${ZAM_BLOCK_START}
13565
13819
  ${blockBody.trim()}
13566
13820
  ${ZAM_BLOCK_END}`;
13567
- if (!existsSync15(dest)) {
13821
+ if (!existsSync16(dest)) {
13568
13822
  if (!dryRun) {
13569
- mkdirSync9(dirname6(dest), { recursive: true });
13570
- writeFileSync8(dest, `${block}
13823
+ mkdirSync11(dirname7(dest), { recursive: true });
13824
+ writeFileSync9(dest, `${block}
13571
13825
  `, "utf8");
13572
13826
  }
13573
13827
  return "write";
13574
13828
  }
13575
- const existing = readFileSync12(dest, "utf8");
13829
+ const existing = readFileSync13(dest, "utf8");
13576
13830
  if (existing.includes(block)) return "skip";
13577
13831
  const start = existing.indexOf(ZAM_BLOCK_START);
13578
13832
  const end = existing.indexOf(ZAM_BLOCK_END);
@@ -13580,7 +13834,7 @@ ${ZAM_BLOCK_END}`;
13580
13834
 
13581
13835
  ${block}
13582
13836
  `;
13583
- if (!dryRun) writeFileSync8(dest, next, "utf8");
13837
+ if (!dryRun) writeFileSync9(dest, next, "utf8");
13584
13838
  return start >= 0 && end > start ? "update" : "write";
13585
13839
  }
13586
13840
  function logInstructionAction(action, label, dryRun) {
@@ -13592,8 +13846,8 @@ function logInstructionAction(action, label, dryRun) {
13592
13846
  }
13593
13847
  function writeClaudeMd(skipClaudeMd, cwd = process.cwd(), opts = {}) {
13594
13848
  if (skipClaudeMd) return;
13595
- const dest = join15(cwd, "CLAUDE.md");
13596
- if (existsSync15(dest)) {
13849
+ const dest = join18(cwd, "CLAUDE.md");
13850
+ if (existsSync16(dest)) {
13597
13851
  if (!opts.updateExisting) {
13598
13852
  console.log(` skip CLAUDE.md (already present)`);
13599
13853
  return;
@@ -13612,7 +13866,7 @@ ZAM is available in this repository. Use the \`zam\` skill in Claude Code to tur
13612
13866
  logInstructionAction(action, "CLAUDE.md", Boolean(opts.dryRun));
13613
13867
  return;
13614
13868
  }
13615
- const name = basename4(cwd);
13869
+ const name = basename5(cwd);
13616
13870
  const content = `# ZAM Personal Kernel \u2014 ${name}
13617
13871
 
13618
13872
  This is a ZAM personal instance. ZAM builds lasting skills through spaced
@@ -13636,14 +13890,14 @@ Use \`zam connector setup turso\` to store cloud credentials in
13636
13890
  if (opts.dryRun) {
13637
13891
  console.log(` would write CLAUDE.md`);
13638
13892
  } else {
13639
- writeFileSync8(dest, content, "utf8");
13893
+ writeFileSync9(dest, content, "utf8");
13640
13894
  console.log(` write CLAUDE.md`);
13641
13895
  }
13642
13896
  }
13643
13897
  function writeAgentsMd(skipAgentsMd, cwd = process.cwd(), opts = {}) {
13644
13898
  if (skipAgentsMd) return;
13645
- const dest = join15(cwd, "AGENTS.md");
13646
- if (existsSync15(dest)) {
13899
+ const dest = join18(cwd, "AGENTS.md");
13900
+ if (existsSync16(dest)) {
13647
13901
  if (!opts.updateExisting) {
13648
13902
  console.log(` skip AGENTS.md (already present)`);
13649
13903
  return;
@@ -13662,7 +13916,7 @@ ZAM is available in this repository. Select the \`zam\` skill through \`/skills\
13662
13916
  logInstructionAction(action, "AGENTS.md", Boolean(opts.dryRun));
13663
13917
  return;
13664
13918
  }
13665
- const name = basename4(cwd);
13919
+ const name = basename5(cwd);
13666
13920
  const content = `# ZAM Personal Kernel - ${name}
13667
13921
 
13668
13922
  This is a ZAM personal instance. ZAM builds lasting skills through spaced
@@ -13693,12 +13947,12 @@ Codex discovers repository skills under \`.agents/skills/\`. Run
13693
13947
  if (opts.dryRun) {
13694
13948
  console.log(` would write AGENTS.md`);
13695
13949
  } else {
13696
- writeFileSync8(dest, content, "utf8");
13950
+ writeFileSync9(dest, content, "utf8");
13697
13951
  console.log(` write AGENTS.md`);
13698
13952
  }
13699
13953
  }
13700
13954
  function writeCopilotInstructions(cwd = process.cwd(), opts = {}) {
13701
- const dest = join15(cwd, ".github", "copilot-instructions.md");
13955
+ const dest = join18(cwd, ".github", "copilot-instructions.md");
13702
13956
  const action = upsertMarkedBlock(
13703
13957
  dest,
13704
13958
  `## ZAM learning sessions
@@ -13738,134 +13992,14 @@ async function resolveUser(opts, db, resolveOpts) {
13738
13992
  process.exit(1);
13739
13993
  }
13740
13994
 
13741
- // src/cli/workspaces/active.ts
13742
- init_kernel();
13743
- import { existsSync as existsSync16, mkdirSync as mkdirSync10 } from "fs";
13744
- import { homedir as homedir10 } from "os";
13745
- import { basename as basename5, join as join16, resolve as resolve3 } from "path";
13746
- var LEGACY_WORKSPACE_DIR_KEY = "personal.workspace_dir";
13747
- var DEFAULT_WORKSPACE_ID = "personal";
13748
- function defaultWorkspaceDir() {
13749
- return join16(homedir10(), "Documents", "zam");
13750
- }
13751
- function normalizeWorkspacePath(path) {
13752
- const resolved = resolve3(path);
13753
- return process.platform === "win32" ? resolved.toLowerCase() : resolved;
13754
- }
13755
- function sameWorkspacePath(left, right) {
13756
- return normalizeWorkspacePath(left) === normalizeWorkspacePath(right);
13757
- }
13758
- function labelFromPath(path) {
13759
- return basename5(path) || "ZAM";
13760
- }
13761
- function workspaceIdFromPath(dir) {
13762
- const base = basename5(dir).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
13763
- const prefix = base || "workspace";
13764
- const existing = new Set(getConfiguredWorkspaces().map((item) => item.id));
13765
- if (!existing.has(prefix)) return prefix;
13766
- let index = 2;
13767
- while (existing.has(`${prefix}-${index}`)) index++;
13768
- return `${prefix}-${index}`;
13769
- }
13770
- function findWorkspaceByPath(dir) {
13771
- return getConfiguredWorkspaces().find(
13772
- (workspace) => sameWorkspacePath(workspace.path, dir)
13773
- );
13774
- }
13775
- async function clearLegacyWorkspaceDir(db) {
13776
- await deleteSetting(db, LEGACY_WORKSPACE_DIR_KEY);
13777
- }
13778
- async function migrateLegacyWorkspaceDir(db) {
13779
- const legacyDir = await getSetting(db, LEGACY_WORKSPACE_DIR_KEY);
13780
- if (!legacyDir) return void 0;
13781
- const path = resolve3(legacyDir);
13782
- const existing = findWorkspaceByPath(path);
13783
- const migrated = existing ?? {
13784
- id: getConfiguredWorkspaces().some(
13785
- (workspace) => workspace.id === DEFAULT_WORKSPACE_ID
13786
- ) ? workspaceIdFromPath(path) : DEFAULT_WORKSPACE_ID,
13787
- label: labelFromPath(path),
13788
- kind: "personal",
13789
- path
13790
- };
13791
- if (!existing) {
13792
- upsertConfiguredWorkspace(migrated);
13793
- }
13794
- if (!getActiveWorkspace()) {
13795
- setActiveWorkspaceId(migrated.id);
13796
- }
13797
- await clearLegacyWorkspaceDir(db);
13798
- return migrated;
13799
- }
13800
- async function ensureActiveWorkspace(db) {
13801
- await migrateLegacyWorkspaceDir(db);
13802
- const active = getActiveWorkspace();
13803
- if (active) {
13804
- mkdirSync10(active.path, { recursive: true });
13805
- return active;
13806
- }
13807
- const configured = getConfiguredWorkspaces()[0];
13808
- if (configured) {
13809
- setActiveWorkspaceId(configured.id);
13810
- mkdirSync10(configured.path, { recursive: true });
13811
- return configured;
13812
- }
13813
- const workspace = {
13814
- id: DEFAULT_WORKSPACE_ID,
13815
- label: "Personal",
13816
- kind: "personal",
13817
- path: defaultWorkspaceDir()
13818
- };
13819
- mkdirSync10(workspace.path, { recursive: true });
13820
- upsertConfiguredWorkspace(workspace);
13821
- setActiveWorkspaceId(workspace.id);
13822
- await clearLegacyWorkspaceDir(db);
13823
- return workspace;
13824
- }
13825
- async function activateWorkspace(db, workspace) {
13826
- upsertConfiguredWorkspace(workspace);
13827
- setActiveWorkspaceId(workspace.id);
13828
- await clearLegacyWorkspaceDir(db);
13829
- return workspace;
13830
- }
13831
- async function activateWorkspacePath(db, dir, opts = {}) {
13832
- await migrateLegacyWorkspaceDir(db);
13833
- const path = resolve3(dir);
13834
- const existing = opts.id ? void 0 : findWorkspaceByPath(path);
13835
- if (existing) {
13836
- setActiveWorkspaceId(existing.id);
13837
- await clearLegacyWorkspaceDir(db);
13838
- return existing;
13839
- }
13840
- const workspace = {
13841
- id: opts.id || workspaceIdFromPath(path),
13842
- label: opts.label || labelFromPath(path),
13843
- kind: opts.kind || "personal",
13844
- path
13845
- };
13846
- return activateWorkspace(db, workspace);
13847
- }
13848
- async function removeWorkspaceAndResolveActive(db, id) {
13849
- removeConfiguredWorkspace(id);
13850
- await clearLegacyWorkspaceDir(db);
13851
- const activeWorkspace = await ensureActiveWorkspace(db);
13852
- return {
13853
- activeWorkspace,
13854
- workspaces: getConfiguredWorkspaces()
13855
- };
13856
- }
13857
- function existingWorkspaceDirOrHome(workspace) {
13858
- return existsSync16(workspace.path) ? workspace.path : homedir10();
13859
- }
13860
-
13861
13995
  // src/cli/workspaces/backup.ts
13862
- import { mkdirSync as mkdirSync11 } from "fs";
13863
- import { join as join17 } from "path";
13996
+ import { mkdirSync as mkdirSync12 } from "fs";
13997
+ import { join as join19 } from "path";
13864
13998
  async function backupDatabaseTo(db, targetDir) {
13865
- const backupDir = join17(targetDir, "zam-backups");
13866
- mkdirSync11(backupDir, { recursive: true });
13999
+ const backupDir = join19(targetDir, "zam-backups");
14000
+ mkdirSync12(backupDir, { recursive: true });
13867
14001
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
13868
- const dest = join17(backupDir, `zam-${stamp}.db`);
14002
+ const dest = join19(backupDir, `zam-${stamp}.db`);
13869
14003
  await db.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
13870
14004
  return dest;
13871
14005
  }
@@ -13959,6 +14093,35 @@ bridgeCommand.command("backup-db").description("Back up the local database into
13959
14093
  jsonOut2({ ok: true, path });
13960
14094
  });
13961
14095
  });
14096
+ bridgeCommand.command("backup-create").description(
14097
+ "Create a portable SQL snapshot backup (kernel exportSnapshot), distinct from backup-db's VACUUM copy (JSON)"
14098
+ ).option(
14099
+ "--dir <path>",
14100
+ "Target directory (default: workspace dir, else ~/Documents/zam)"
14101
+ ).action(async (opts) => {
14102
+ await withDb2(async (db) => {
14103
+ try {
14104
+ const result = await backupCreate(db, { dir: opts.dir });
14105
+ jsonOut2(result);
14106
+ } catch (err) {
14107
+ jsonError(err.message);
14108
+ }
14109
+ });
14110
+ });
14111
+ bridgeCommand.command("update-check").description("Check whether a newer ZAM release is available (JSON)").option(
14112
+ "--latest <version>",
14113
+ "Compare against this version instead of fetching (offline/deterministic checks)"
14114
+ ).option("--channel <channel>", "Override the detected install channel").action(async (opts) => {
14115
+ try {
14116
+ const result = await updateCheck({
14117
+ latest: opts.latest,
14118
+ channel: opts.channel
14119
+ });
14120
+ jsonOut2(result);
14121
+ } catch (err) {
14122
+ jsonError(err.message);
14123
+ }
14124
+ });
13962
14125
  bridgeCommand.command("workspace-info").description("Report the workspace dir, its default, and the data dir (JSON)").action(async () => {
13963
14126
  await withDb2(async (db) => {
13964
14127
  const activeWorkspace = await ensureActiveWorkspace(db);
@@ -13967,7 +14130,7 @@ bridgeCommand.command("workspace-info").description("Report the workspace dir, i
13967
14130
  activeWorkspace,
13968
14131
  workspaceDir: activeWorkspace.path,
13969
14132
  defaultWorkspaceDir: defaultWorkspaceDir(),
13970
- dataDir: join18(homedir11(), ".zam")
14133
+ dataDir: join20(homedir11(), ".zam")
13971
14134
  });
13972
14135
  });
13973
14136
  });
@@ -14010,7 +14173,7 @@ bridgeCommand.command("workspace-list").description("List configured ZAM workspa
14010
14173
  activeWorkspace,
14011
14174
  workspaceDir: activeWorkspace.path,
14012
14175
  defaultWorkspaceDir: defaultWorkspaceDir(),
14013
- dataDir: join18(homedir11(), ".zam"),
14176
+ dataDir: join20(homedir11(), ".zam"),
14014
14177
  linkHealth: buildWorkspaceLinkHealth(workspaces)
14015
14178
  });
14016
14179
  });
@@ -14537,7 +14700,7 @@ bridgeCommand.command("discover-skills").description(
14537
14700
  "20"
14538
14701
  ).action(async (opts) => {
14539
14702
  try {
14540
- const monitorDir = join18(homedir11(), ".zam", "monitor");
14703
+ const monitorDir = join20(homedir11(), ".zam", "monitor");
14541
14704
  let files;
14542
14705
  try {
14543
14706
  files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
@@ -14550,7 +14713,7 @@ bridgeCommand.command("discover-skills").description(
14550
14713
  return;
14551
14714
  }
14552
14715
  const limit = Number(opts.limit);
14553
- const sorted = files.map((f) => ({ name: f, path: join18(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
14716
+ const sorted = files.map((f) => ({ name: f, path: join20(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
14554
14717
  const sessionCommands = /* @__PURE__ */ new Map();
14555
14718
  for (const file of sorted) {
14556
14719
  const sessionId = file.name.replace(".jsonl", "");
@@ -15052,7 +15215,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
15052
15215
  return;
15053
15216
  }
15054
15217
  }
15055
- const outputPath = opts.image ?? opts.output ?? join18(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
15218
+ const outputPath = opts.image ?? opts.output ?? join20(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
15056
15219
  const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
15057
15220
  if (!isProvided) {
15058
15221
  const post = decidePostCapture(policy, {
@@ -15080,7 +15243,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
15080
15243
  return;
15081
15244
  }
15082
15245
  }
15083
- const imageBytes = readFileSync13(outputPath);
15246
+ const imageBytes = readFileSync14(outputPath);
15084
15247
  const base64 = imageBytes.toString("base64");
15085
15248
  jsonOut2({
15086
15249
  sessionId: opts.session ?? null,
@@ -15107,10 +15270,10 @@ bridgeCommand.command("start-recording").description("Start screen recording in
15107
15270
  return;
15108
15271
  }
15109
15272
  const sessionId = opts.session;
15110
- const statePath = join18(tmpdir3(), `zam-recording-${sessionId}.json`);
15273
+ const statePath = join20(tmpdir3(), `zam-recording-${sessionId}.json`);
15111
15274
  const defaultExt = platform === "win32" ? ".mkv" : ".mov";
15112
- const outputPath = opts.output ?? join18(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
15113
- const { existsSync: existsSync26, writeFileSync: writeFileSync13, openSync, closeSync } = await import("fs");
15275
+ const outputPath = opts.output ?? join20(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
15276
+ const { existsSync: existsSync26, writeFileSync: writeFileSync14, openSync, closeSync } = await import("fs");
15114
15277
  if (existsSync26(statePath)) {
15115
15278
  jsonOut2({
15116
15279
  sessionId,
@@ -15119,7 +15282,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
15119
15282
  });
15120
15283
  return;
15121
15284
  }
15122
- const logPath = join18(tmpdir3(), `zam-recording-${sessionId}.log`);
15285
+ const logPath = join20(tmpdir3(), `zam-recording-${sessionId}.log`);
15123
15286
  let logFd;
15124
15287
  try {
15125
15288
  logFd = openSync(logPath, "w");
@@ -15165,7 +15328,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
15165
15328
  }
15166
15329
  child.unref();
15167
15330
  if (child.pid) {
15168
- writeFileSync13(
15331
+ writeFileSync14(
15169
15332
  statePath,
15170
15333
  JSON.stringify({
15171
15334
  pid: child.pid,
@@ -15201,8 +15364,8 @@ bridgeCommand.command("stop-recording").description(
15201
15364
  return;
15202
15365
  }
15203
15366
  const sessionId = opts.session;
15204
- const statePath = join18(tmpdir3(), `zam-recording-${sessionId}.json`);
15205
- const { existsSync: existsSync26, readFileSync: readFileSync18, rmSync: rmSync4 } = await import("fs");
15367
+ const statePath = join20(tmpdir3(), `zam-recording-${sessionId}.json`);
15368
+ const { existsSync: existsSync26, readFileSync: readFileSync19, rmSync: rmSync4 } = await import("fs");
15206
15369
  if (!existsSync26(statePath)) {
15207
15370
  jsonOut2({
15208
15371
  sessionId,
@@ -15211,7 +15374,7 @@ bridgeCommand.command("stop-recording").description(
15211
15374
  });
15212
15375
  return;
15213
15376
  }
15214
- const state = JSON.parse(readFileSync18(statePath, "utf8"));
15377
+ const state = JSON.parse(readFileSync19(statePath, "utf8"));
15215
15378
  const { pid, outputPath } = state;
15216
15379
  try {
15217
15380
  process.kill(pid, "SIGINT");
@@ -16599,6 +16762,79 @@ bridgeCommand.command("set-active-knowledge-context").description("Set the activ
16599
16762
  jsonOut2({ success: true, activeContext: context.name });
16600
16763
  });
16601
16764
  });
16765
+ var commanderConfiguredForJsonExecution = false;
16766
+ function ensureCommanderThrowsInsteadOfExiting() {
16767
+ if (commanderConfiguredForJsonExecution) return;
16768
+ bridgeCommand.exitOverride();
16769
+ for (const sub of bridgeCommand.commands) {
16770
+ sub.exitOverride();
16771
+ }
16772
+ commanderConfiguredForJsonExecution = true;
16773
+ }
16774
+ async function runBridgeCommandOnce(cmd, args) {
16775
+ ensureCommanderThrowsInsteadOfExiting();
16776
+ let outputBuffer = "";
16777
+ const captureOutput = (str) => {
16778
+ outputBuffer += str;
16779
+ };
16780
+ bridgeCommand.configureOutput({
16781
+ writeOut: captureOutput,
16782
+ writeErr: captureOutput
16783
+ });
16784
+ for (const sub of bridgeCommand.commands) {
16785
+ sub.configureOutput({ writeOut: captureOutput, writeErr: captureOutput });
16786
+ }
16787
+ const originalLog = console.log;
16788
+ const originalError = console.error;
16789
+ console.log = (...logArgs) => {
16790
+ outputBuffer += `${logArgs.map((a) => typeof a === "object" ? JSON.stringify(a) : String(a)).join(" ")}
16791
+ `;
16792
+ };
16793
+ console.error = (...logArgs) => {
16794
+ outputBuffer += `${logArgs.map((a) => typeof a === "object" ? JSON.stringify(a) : String(a)).join(" ")}
16795
+ `;
16796
+ };
16797
+ const wasServeMode = isServeMode;
16798
+ isServeMode = true;
16799
+ try {
16800
+ try {
16801
+ await bridgeCommand.parseAsync(["node", "bridge", cmd, ...args]);
16802
+ } catch (err) {
16803
+ let message;
16804
+ if (err instanceof Error && err.message.startsWith('{"error":')) {
16805
+ try {
16806
+ message = JSON.parse(err.message).error;
16807
+ } catch {
16808
+ message = err.message;
16809
+ }
16810
+ } else if (err?.code?.startsWith("commander.")) {
16811
+ message = outputBuffer.trim() || err.message;
16812
+ } else {
16813
+ message = err.message || String(err);
16814
+ }
16815
+ throw new Error(message);
16816
+ }
16817
+ } finally {
16818
+ console.log = originalLog;
16819
+ console.error = originalError;
16820
+ isServeMode = wasServeMode;
16821
+ }
16822
+ const trimmed = outputBuffer.trim();
16823
+ try {
16824
+ return JSON.parse(trimmed);
16825
+ } catch {
16826
+ return trimmed;
16827
+ }
16828
+ }
16829
+ var bridgeExecutionQueue = Promise.resolve();
16830
+ function executeBridgeCommandJson(cmd, args) {
16831
+ const run = bridgeExecutionQueue.then(() => runBridgeCommandOnce(cmd, args));
16832
+ bridgeExecutionQueue = run.then(
16833
+ () => void 0,
16834
+ () => void 0
16835
+ );
16836
+ return run;
16837
+ }
16602
16838
  bridgeCommand.command("serve").description("Start the persistent JSON-RPC stdin/stdout server").option("--stdin", "Use stdin/stdout for communication").action(async (_opts) => {
16603
16839
  isServeMode = true;
16604
16840
  const {
@@ -16621,25 +16857,7 @@ bridgeCommand.command("serve").description("Start the persistent JSON-RPC stdin/
16621
16857
  logDiag(
16622
16858
  `serve start | homedir=${nodeOs.homedir()} | USERPROFILE=${process.env.USERPROFILE ?? ""} | HOME=${process.env.HOME ?? ""} | cwd=${process.cwd()}`
16623
16859
  );
16624
- bridgeCommand.exitOverride();
16625
- for (const cmd of bridgeCommand.commands) {
16626
- cmd.exitOverride();
16627
- }
16628
- let outputBuffer = "";
16629
- const outputOpts = {
16630
- writeOut: (str) => {
16631
- outputBuffer += str;
16632
- },
16633
- writeErr: (str) => {
16634
- outputBuffer += str;
16635
- }
16636
- };
16637
- bridgeCommand.configureOutput(outputOpts);
16638
- for (const cmd of bridgeCommand.commands) {
16639
- cmd.configureOutput(outputOpts);
16640
- }
16641
16860
  const processRequest = async (line) => {
16642
- outputBuffer = "";
16643
16861
  let requestId = null;
16644
16862
  try {
16645
16863
  const req = JSON.parse(line);
@@ -16659,49 +16877,15 @@ bridgeCommand.command("serve").description("Start the persistent JSON-RPC stdin/
16659
16877
  error: "Missing 'cmd' field"
16660
16878
  });
16661
16879
  }
16662
- const originalLog = console.log;
16663
- const originalError = console.error;
16664
- console.log = (...logArgs) => {
16665
- outputBuffer += `${logArgs.map((a) => typeof a === "object" ? JSON.stringify(a) : String(a)).join(" ")}
16666
- `;
16667
- };
16668
- console.error = (...logArgs) => {
16669
- outputBuffer += `${logArgs.map((a) => typeof a === "object" ? JSON.stringify(a) : String(a)).join(" ")}
16670
- `;
16671
- };
16672
16880
  try {
16673
- await bridgeCommand.parseAsync(["node", "bridge", cmd, ...args]);
16881
+ const result = await executeBridgeCommandJson(cmd, args);
16882
+ return JSON.stringify({ id: requestId, result });
16674
16883
  } catch (err) {
16675
- if (err instanceof Error && err.message.startsWith('{"error":')) {
16676
- try {
16677
- const parsed = JSON.parse(err.message);
16678
- return JSON.stringify({ id: requestId, error: parsed.error });
16679
- } catch {
16680
- return JSON.stringify({ id: requestId, error: err.message });
16681
- }
16682
- }
16683
- if (err.code?.startsWith("commander.")) {
16684
- return JSON.stringify({
16685
- id: requestId,
16686
- error: outputBuffer.trim() || err.message
16687
- });
16688
- }
16689
16884
  return JSON.stringify({
16690
16885
  id: requestId,
16691
16886
  error: err.message || String(err)
16692
16887
  });
16693
- } finally {
16694
- console.log = originalLog;
16695
- console.error = originalError;
16696
- }
16697
- let result;
16698
- const trimmed = outputBuffer.trim();
16699
- try {
16700
- result = JSON.parse(trimmed);
16701
- } catch {
16702
- result = trimmed;
16703
16888
  }
16704
- return JSON.stringify({ id: requestId, result });
16705
16889
  } catch (err) {
16706
16890
  return JSON.stringify({
16707
16891
  id: requestId,
@@ -18006,26 +18190,26 @@ var doctorCommand = new Command5("doctor").description(
18006
18190
  // src/cli/commands/git-sync.ts
18007
18191
  init_kernel();
18008
18192
  import { execSync as execSync5 } from "child_process";
18009
- import { chmodSync as chmodSync2, existsSync as existsSync18, writeFileSync as writeFileSync9 } from "fs";
18010
- import { join as join19 } from "path";
18193
+ import { chmodSync as chmodSync2, existsSync as existsSync18, writeFileSync as writeFileSync10 } from "fs";
18194
+ import { join as join21 } from "path";
18011
18195
  import { Command as Command6 } from "commander";
18012
18196
  function installHook2() {
18013
- const gitDir = join19(process.cwd(), ".git");
18197
+ const gitDir = join21(process.cwd(), ".git");
18014
18198
  if (!existsSync18(gitDir)) {
18015
18199
  console.error(
18016
18200
  "Error: Current directory is not the root of a Git repository."
18017
18201
  );
18018
18202
  process.exit(1);
18019
18203
  }
18020
- const hooksDir = join19(gitDir, "hooks");
18021
- const hookPath = join19(hooksDir, "post-commit");
18204
+ const hooksDir = join21(gitDir, "hooks");
18205
+ const hookPath = join21(hooksDir, "post-commit");
18022
18206
  const hookContent = `#!/bin/sh
18023
18207
  # ZAM Spaced Repetition Auto-Stale Hook
18024
18208
  # Triggered automatically on git commits to decay modified concept cards.
18025
18209
  zam git-sync --commit HEAD --quiet
18026
18210
  `;
18027
18211
  try {
18028
- writeFileSync9(hookPath, hookContent, { encoding: "utf-8", flag: "w" });
18212
+ writeFileSync10(hookPath, hookContent, { encoding: "utf-8", flag: "w" });
18029
18213
  try {
18030
18214
  chmodSync2(hookPath, "755");
18031
18215
  } catch (_e) {
@@ -18128,7 +18312,7 @@ ZAM Auto-Stale Complete: Scanned ${changedFiles.length} file(s).`
18128
18312
 
18129
18313
  // src/cli/commands/goal.ts
18130
18314
  init_kernel();
18131
- import { existsSync as existsSync19, mkdirSync as mkdirSync12 } from "fs";
18315
+ import { existsSync as existsSync19, mkdirSync as mkdirSync13 } from "fs";
18132
18316
  import { resolve as resolve5 } from "path";
18133
18317
  import { input as input2 } from "@inquirer/prompts";
18134
18318
  import { Command as Command7 } from "commander";
@@ -18254,7 +18438,7 @@ ${"\u2500".repeat(50)}`);
18254
18438
  goalCommand.command("create").description("Create a new goal").option("--slug <slug>", "Goal slug (used as filename)").option("--title <title>", "Goal title").option("--parent <slug>", "Parent goal slug").option("--description <text>", "Goal description").option("--json", "Output as JSON").action(async (opts) => {
18255
18439
  const goalsDir = await resolveGoalsDir();
18256
18440
  if (!existsSync19(goalsDir)) {
18257
- mkdirSync12(goalsDir, { recursive: true });
18441
+ mkdirSync13(goalsDir, { recursive: true });
18258
18442
  }
18259
18443
  let slug = opts.slug;
18260
18444
  let title = opts.title;
@@ -18314,9 +18498,9 @@ goalCommand.command("status <slug> <status>").description("Update a goal's statu
18314
18498
 
18315
18499
  // src/cli/commands/init.ts
18316
18500
  init_kernel();
18317
- import { existsSync as existsSync20, mkdirSync as mkdirSync13, writeFileSync as writeFileSync10 } from "fs";
18501
+ import { existsSync as existsSync20, mkdirSync as mkdirSync14, writeFileSync as writeFileSync11 } from "fs";
18318
18502
  import { homedir as homedir12 } from "os";
18319
- import { join as join20, resolve as resolve6 } from "path";
18503
+ import { join as join22, resolve as resolve6 } from "path";
18320
18504
  import { confirm, input as input3 } from "@inquirer/prompts";
18321
18505
  import { Command as Command8 } from "commander";
18322
18506
  var HOME2 = homedir12();
@@ -18324,12 +18508,12 @@ function printLine(char = "\u2550", len = 60, color = "\x1B[36m") {
18324
18508
  console.log(`${color}${char.repeat(len)}\x1B[0m`);
18325
18509
  }
18326
18510
  function bootstrapSandboxWorkspace(workspaceDir) {
18327
- mkdirSync13(join20(workspaceDir, "beliefs"), { recursive: true });
18328
- mkdirSync13(join20(workspaceDir, "goals"), { recursive: true });
18329
- mkdirSync13(join20(workspaceDir, "skills"), { recursive: true });
18330
- const worldviewFile = join20(workspaceDir, "beliefs", "worldview.md");
18511
+ mkdirSync14(join22(workspaceDir, "beliefs"), { recursive: true });
18512
+ mkdirSync14(join22(workspaceDir, "goals"), { recursive: true });
18513
+ mkdirSync14(join22(workspaceDir, "skills"), { recursive: true });
18514
+ const worldviewFile = join22(workspaceDir, "beliefs", "worldview.md");
18331
18515
  if (!existsSync20(worldviewFile)) {
18332
- writeFileSync10(
18516
+ writeFileSync11(
18333
18517
  worldviewFile,
18334
18518
  `# Personal Worldview
18335
18519
 
@@ -18341,9 +18525,9 @@ Here, I declare the core concepts and principles I want to master.
18341
18525
  "utf8"
18342
18526
  );
18343
18527
  }
18344
- const goalsFile = join20(workspaceDir, "goals", "goals.md");
18528
+ const goalsFile = join22(workspaceDir, "goals", "goals.md");
18345
18529
  if (!existsSync20(goalsFile)) {
18346
- writeFileSync10(
18530
+ writeFileSync11(
18347
18531
  goalsFile,
18348
18532
  `# Personal Goals
18349
18533
 
@@ -18365,7 +18549,7 @@ var initCommand = new Command8("init").description("Launch the guided interactiv
18365
18549
  );
18366
18550
  printLine();
18367
18551
  console.log("\n\x1B[1m[1/5] Setting up Local Workspace Sandbox\x1B[0m");
18368
- const defaultWorkspace = join20(HOME2, "Documents", "zam");
18552
+ const defaultWorkspace = join22(HOME2, "Documents", "zam");
18369
18553
  const workspacePath = resolve6(
18370
18554
  await input3({
18371
18555
  message: "Choose your ZAM workspace directory:",
@@ -19567,7 +19751,7 @@ observerCommand.command("revoke <process>").description("Remove a process from o
19567
19751
 
19568
19752
  // src/cli/commands/profile.ts
19569
19753
  init_kernel();
19570
- import { dirname as dirname7, resolve as resolve7 } from "path";
19754
+ import { dirname as dirname8, resolve as resolve7 } from "path";
19571
19755
  import { Command as Command13 } from "commander";
19572
19756
  var C2 = {
19573
19757
  reset: "\x1B[0m",
@@ -19608,7 +19792,7 @@ var profileCommand = new Command13("profile").description("Show or change this m
19608
19792
  mode: getInstallMode(),
19609
19793
  personalDir,
19610
19794
  syncProvider: detectSyncProvider(personalDir),
19611
- dataDir: dirname7(dbPath),
19795
+ dataDir: dirname8(dbPath),
19612
19796
  dbPath
19613
19797
  };
19614
19798
  if (opts.json) {
@@ -19997,7 +20181,7 @@ ${"\u2550".repeat(50)}`);
19997
20181
 
19998
20182
  // src/cli/commands/session.ts
19999
20183
  init_kernel();
20000
- import { readFileSync as readFileSync14 } from "fs";
20184
+ import { readFileSync as readFileSync15 } from "fs";
20001
20185
  import { input as input6, select as select2 } from "@inquirer/prompts";
20002
20186
  import { Command as Command16 } from "commander";
20003
20187
  var sessionCommand = new Command16("session").description(
@@ -20188,7 +20372,7 @@ function loadPatternFile(path) {
20188
20372
  if (!path) return [];
20189
20373
  let parsed;
20190
20374
  try {
20191
- parsed = JSON.parse(readFileSync14(path, "utf-8"));
20375
+ parsed = JSON.parse(readFileSync15(path, "utf-8"));
20192
20376
  } catch (err) {
20193
20377
  throw new Error(
20194
20378
  `Cannot read synthesis patterns from ${path}: ${err.message}`
@@ -20741,8 +20925,8 @@ skillCommand.command("add").description("Register a new agent skill").requiredOp
20741
20925
 
20742
20926
  // src/cli/commands/snapshot.ts
20743
20927
  init_kernel();
20744
- import { existsSync as existsSync22, mkdirSync as mkdirSync14, readFileSync as readFileSync15, writeFileSync as writeFileSync11 } from "fs";
20745
- import { dirname as dirname8, join as join21 } from "path";
20928
+ import { existsSync as existsSync22, mkdirSync as mkdirSync15, readFileSync as readFileSync16, writeFileSync as writeFileSync12 } from "fs";
20929
+ import { dirname as dirname9, join as join23 } from "path";
20746
20930
  import { Command as Command20 } from "commander";
20747
20931
  function defaultOutName() {
20748
20932
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
@@ -20771,12 +20955,12 @@ var exportCmd = new Command20("export").description("Write a portable SQL-text s
20771
20955
  process.stdout.write(snapshot);
20772
20956
  return;
20773
20957
  }
20774
- const out = opts.out ?? join21(personalDir, "snapshots", defaultOutName());
20775
- const dir = dirname8(out);
20958
+ const out = opts.out ?? join23(personalDir, "snapshots", defaultOutName());
20959
+ const dir = dirname9(out);
20776
20960
  if (dir && dir !== "." && !existsSync22(dir)) {
20777
- mkdirSync14(dir, { recursive: true });
20961
+ mkdirSync15(dir, { recursive: true });
20778
20962
  }
20779
- writeFileSync11(out, snapshot, "utf-8");
20963
+ writeFileSync12(out, snapshot, "utf-8");
20780
20964
  console.log(`Snapshot written: ${out}`);
20781
20965
  console.log(
20782
20966
  ` ${total} row(s)${nonEmpty.length ? ` \u2014 ${nonEmpty.join(", ")}` : ""}`
@@ -20794,7 +20978,7 @@ var importCmd = new Command20("import").description("Restore a snapshot into the
20794
20978
  console.error(`Error: Snapshot file not found: ${file}`);
20795
20979
  process.exit(1);
20796
20980
  }
20797
- const snapshot = readFileSync15(file, "utf-8");
20981
+ const snapshot = readFileSync16(file, "utf-8");
20798
20982
  db = await openDatabaseWithSync({ initialize: true });
20799
20983
  const result = await importSnapshot(db, snapshot, { force: opts.force });
20800
20984
  await db.close();
@@ -20816,7 +21000,7 @@ var verifyCmd = new Command20("verify").description("Check a snapshot's manifest
20816
21000
  console.error(`Error: Snapshot file not found: ${file}`);
20817
21001
  process.exit(1);
20818
21002
  }
20819
- const manifest = verifySnapshot(readFileSync15(file, "utf-8"));
21003
+ const manifest = verifySnapshot(readFileSync16(file, "utf-8"));
20820
21004
  const { total, nonEmpty } = summarize(manifest.tables);
20821
21005
  console.log(`Valid snapshot (format v${manifest.version})`);
20822
21006
  console.log(` created: ${manifest.createdAt}`);
@@ -21367,8 +21551,8 @@ init_kernel();
21367
21551
  import { spawn as spawn3, spawnSync } from "child_process";
21368
21552
  import { existsSync as existsSync23 } from "fs";
21369
21553
  import { homedir as homedir13 } from "os";
21370
- import { dirname as dirname9, join as join22 } from "path";
21371
- import { fileURLToPath as fileURLToPath3 } from "url";
21554
+ import { dirname as dirname10, join as join24 } from "path";
21555
+ import { fileURLToPath as fileURLToPath4 } from "url";
21372
21556
  import { Command as Command23 } from "commander";
21373
21557
  var C3 = {
21374
21558
  reset: "\x1B[0m",
@@ -21379,14 +21563,14 @@ var C3 = {
21379
21563
  dim: "\x1B[2m"
21380
21564
  };
21381
21565
  function findDesktopDir() {
21382
- const starts = [process.cwd(), dirname9(fileURLToPath3(import.meta.url))];
21566
+ const starts = [process.cwd(), dirname10(fileURLToPath4(import.meta.url))];
21383
21567
  for (const start of starts) {
21384
21568
  let dir = start;
21385
21569
  for (let i = 0; i < 10; i++) {
21386
- if (existsSync23(join22(dir, "desktop", "src-tauri", "tauri.conf.json"))) {
21387
- return join22(dir, "desktop");
21570
+ if (existsSync23(join24(dir, "desktop", "src-tauri", "tauri.conf.json"))) {
21571
+ return join24(dir, "desktop");
21388
21572
  }
21389
- const parent = dirname9(dir);
21573
+ const parent = dirname10(dir);
21390
21574
  if (parent === dir) break;
21391
21575
  dir = parent;
21392
21576
  }
@@ -21394,18 +21578,18 @@ function findDesktopDir() {
21394
21578
  return null;
21395
21579
  }
21396
21580
  function findBuiltApp(desktopDir) {
21397
- const releaseDir = join22(desktopDir, "src-tauri", "target", "release");
21581
+ const releaseDir = join24(desktopDir, "src-tauri", "target", "release");
21398
21582
  if (process.platform === "win32") {
21399
21583
  for (const name of ["ZAM.exe", "zam.exe", "zam-desktop.exe"]) {
21400
- const p = join22(releaseDir, name);
21584
+ const p = join24(releaseDir, name);
21401
21585
  if (existsSync23(p)) return p;
21402
21586
  }
21403
21587
  } else if (process.platform === "darwin") {
21404
- const app = join22(releaseDir, "bundle", "macos", "ZAM.app");
21588
+ const app = join24(releaseDir, "bundle", "macos", "ZAM.app");
21405
21589
  if (existsSync23(app)) return app;
21406
21590
  } else {
21407
21591
  for (const name of ["zam", "ZAM", "zam-desktop"]) {
21408
- const p = join22(releaseDir, name);
21592
+ const p = join24(releaseDir, name);
21409
21593
  if (existsSync23(p)) return p;
21410
21594
  }
21411
21595
  }
@@ -21413,10 +21597,10 @@ function findBuiltApp(desktopDir) {
21413
21597
  }
21414
21598
  function findInstalledApp() {
21415
21599
  const candidates = process.platform === "win32" ? [
21416
- process.env.LOCALAPPDATA && join22(process.env.LOCALAPPDATA, "Programs", "ZAM", "ZAM.exe"),
21417
- process.env.ProgramFiles && join22(process.env.ProgramFiles, "ZAM", "ZAM.exe"),
21418
- process.env["ProgramFiles(x86)"] && join22(process.env["ProgramFiles(x86)"], "ZAM", "ZAM.exe")
21419
- ] : process.platform === "darwin" ? ["/Applications/ZAM.app", join22(homedir13(), "Applications", "ZAM.app")] : ["/opt/ZAM/zam", "/usr/bin/zam-desktop"];
21600
+ process.env.LOCALAPPDATA && join24(process.env.LOCALAPPDATA, "Programs", "ZAM", "ZAM.exe"),
21601
+ process.env.ProgramFiles && join24(process.env.ProgramFiles, "ZAM", "ZAM.exe"),
21602
+ process.env["ProgramFiles(x86)"] && join24(process.env["ProgramFiles(x86)"], "ZAM", "ZAM.exe")
21603
+ ] : process.platform === "darwin" ? ["/Applications/ZAM.app", join24(homedir13(), "Applications", "ZAM.app")] : ["/opt/ZAM/zam", "/usr/bin/zam-desktop"];
21420
21604
  return candidates.find((candidate) => candidate && existsSync23(candidate)) || null;
21421
21605
  }
21422
21606
  function runNpm(args, opts) {
@@ -21428,7 +21612,7 @@ function runNpm(args, opts) {
21428
21612
  return res.status ?? 1;
21429
21613
  }
21430
21614
  function ensureDesktopDeps(desktopDir) {
21431
- if (existsSync23(join22(desktopDir, "node_modules"))) return true;
21615
+ if (existsSync23(join24(desktopDir, "node_modules"))) return true;
21432
21616
  console.log(
21433
21617
  `${C3.cyan}Installing desktop dependencies (one-time)...${C3.reset}`
21434
21618
  );
@@ -21454,7 +21638,7 @@ function requireRust() {
21454
21638
  function hasMsvcBuildTools() {
21455
21639
  if (process.platform !== "win32") return true;
21456
21640
  const pf86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
21457
- const vswhere = join22(
21641
+ const vswhere = join24(
21458
21642
  pf86,
21459
21643
  "Microsoft Visual Studio",
21460
21644
  "Installer",
@@ -21492,7 +21676,7 @@ function requireMsvcOnWindows() {
21492
21676
  return false;
21493
21677
  }
21494
21678
  function warnIfCliMissing(repoRoot) {
21495
- if (!existsSync23(join22(repoRoot, "dist", "cli", "index.js"))) {
21679
+ if (!existsSync23(join24(repoRoot, "dist", "cli", "index.js"))) {
21496
21680
  console.warn(
21497
21681
  `${C3.yellow}\u26A0 CLI build not found (dist/cli/index.js). The GUI bridge needs it \u2014 run 'npm run build' at the repo root.${C3.reset}`
21498
21682
  );
@@ -21565,7 +21749,7 @@ var uiCommand = new Command23("ui").description("Launch the ZAM Desktop GUI (Act
21565
21749
  );
21566
21750
  process.exit(1);
21567
21751
  }
21568
- const repoRoot = dirname9(desktopDir);
21752
+ const repoRoot = dirname10(desktopDir);
21569
21753
  if (opts.build) {
21570
21754
  if (!requireRust()) process.exit(1);
21571
21755
  if (!requireMsvcOnWindows()) process.exit(1);
@@ -21576,7 +21760,7 @@ var uiCommand = new Command23("ui").description("Launch the ZAM Desktop GUI (Act
21576
21760
  );
21577
21761
  const code = runNpm(["run", "tauri", "build"], { cwd: desktopDir });
21578
21762
  if (code === 0) {
21579
- const bundle = join22(
21763
+ const bundle = join24(
21580
21764
  desktopDir,
21581
21765
  "src-tauri",
21582
21766
  "target",
@@ -21616,7 +21800,7 @@ ${C3.green}\u2713 Done. Installer is in:${C3.reset}
21616
21800
  );
21617
21801
  process.exit(1);
21618
21802
  }
21619
- createShortcuts(shortcutTarget, dirname9(shortcutTarget));
21803
+ createShortcuts(shortcutTarget, dirname10(shortcutTarget));
21620
21804
  return;
21621
21805
  }
21622
21806
  if (builtApp) {
@@ -21654,12 +21838,11 @@ ${C3.dim}After that, 'zam ui' launches it directly.${C3.reset}`
21654
21838
  // src/cli/commands/update.ts
21655
21839
  init_kernel();
21656
21840
  import { spawnSync as spawnSync2 } from "child_process";
21657
- import { existsSync as existsSync24, readFileSync as readFileSync16, realpathSync as realpathSync2 } from "fs";
21658
- import { dirname as dirname10, join as join23 } from "path";
21659
- import { fileURLToPath as fileURLToPath4 } from "url";
21841
+ import { existsSync as existsSync24, readFileSync as readFileSync17, realpathSync as realpathSync2 } from "fs";
21842
+ import { dirname as dirname11, join as join25 } from "path";
21843
+ import { fileURLToPath as fileURLToPath5 } from "url";
21660
21844
  import { confirm as confirm3 } from "@inquirer/prompts";
21661
21845
  import { Command as Command24 } from "commander";
21662
- var GITHUB_REPO = "zam-os/zam";
21663
21846
  var CHANNELS = [
21664
21847
  "developer",
21665
21848
  "direct",
@@ -21675,48 +21858,16 @@ var C4 = {
21675
21858
  red: "\x1B[31m",
21676
21859
  yellow: "\x1B[33m"
21677
21860
  };
21678
- function currentVersion() {
21679
- const here = dirname10(fileURLToPath4(import.meta.url));
21680
- for (const up of ["..", "../..", "../../.."]) {
21681
- try {
21682
- const pkg2 = JSON.parse(
21683
- readFileSync16(join23(here, up, "package.json"), "utf-8")
21684
- );
21685
- if (pkg2.version) return pkg2.version;
21686
- } catch {
21687
- }
21688
- }
21689
- return "0.0.0";
21690
- }
21691
21861
  function versionAt(dir) {
21692
21862
  try {
21693
21863
  const pkg2 = JSON.parse(
21694
- readFileSync16(join23(dir, "package.json"), "utf-8")
21864
+ readFileSync17(join25(dir, "package.json"), "utf-8")
21695
21865
  );
21696
21866
  return pkg2.version ?? "unknown";
21697
21867
  } catch {
21698
21868
  return "unknown";
21699
21869
  }
21700
21870
  }
21701
- async function fetchLatestVersion(repo) {
21702
- const res = await fetch(
21703
- `https://api.github.com/repos/${repo}/releases/latest`,
21704
- {
21705
- headers: {
21706
- Accept: "application/vnd.github+json",
21707
- "User-Agent": "zam-cli"
21708
- }
21709
- }
21710
- );
21711
- if (!res.ok) {
21712
- throw new Error(
21713
- `Could not reach the release server (HTTP ${res.status}). Pass --latest <version> to check offline.`
21714
- );
21715
- }
21716
- const data = await res.json();
21717
- if (!data.tag_name) throw new Error("No published release found yet.");
21718
- return data.tag_name;
21719
- }
21720
21871
  function render2(decision) {
21721
21872
  if (!decision.updateAvailable) {
21722
21873
  console.log(
@@ -21768,14 +21919,14 @@ var checkCmd = new Command24("check").description("Check whether a newer ZAM has
21768
21919
  }
21769
21920
  );
21770
21921
  function findSourceRepo() {
21771
- let dir = realpathSync2(dirname10(fileURLToPath4(import.meta.url)));
21772
- let parent = dirname10(dir);
21922
+ let dir = realpathSync2(dirname11(fileURLToPath5(import.meta.url)));
21923
+ let parent = dirname11(dir);
21773
21924
  while (parent !== dir) {
21774
- if (existsSync24(join23(dir, ".git"))) return dir;
21925
+ if (existsSync24(join25(dir, ".git"))) return dir;
21775
21926
  dir = parent;
21776
- parent = dirname10(dir);
21927
+ parent = dirname11(dir);
21777
21928
  }
21778
- return existsSync24(join23(dir, ".git")) ? dir : null;
21929
+ return existsSync24(join25(dir, ".git")) ? dir : null;
21779
21930
  }
21780
21931
  function runGit(cwd, args, capture) {
21781
21932
  const res = spawnSync2("git", args, {
@@ -21796,7 +21947,7 @@ function runNpm2(args, cwd) {
21796
21947
  function smokeTestBuild(src) {
21797
21948
  const res = spawnSync2(
21798
21949
  process.execPath,
21799
- [join23(src, "dist", "cli", "index.js"), "--version"],
21950
+ [join25(src, "dist", "cli", "index.js"), "--version"],
21800
21951
  {
21801
21952
  cwd: src,
21802
21953
  encoding: "utf8",
@@ -21876,7 +22027,7 @@ Fix it manually in ${src} (try \`npm ci && npm run build\`, and check your Node
21876
22027
  console.log(`${C4.dim}\u2192 zam setup --force${C4.reset}`);
21877
22028
  const setup = spawnSync2(
21878
22029
  process.execPath,
21879
- [join23(src, "dist", "cli", "index.js"), "setup", "--force"],
22030
+ [join25(src, "dist", "cli", "index.js"), "setup", "--force"],
21880
22031
  { cwd: process.cwd(), stdio: "inherit" }
21881
22032
  );
21882
22033
  if (setup.status !== 0) {
@@ -21989,9 +22140,9 @@ var whoamiCommand = new Command25("whoami").description("Show or set the default
21989
22140
  // src/cli/commands/workspace.ts
21990
22141
  init_kernel();
21991
22142
  import { execFileSync as execFileSync4 } from "child_process";
21992
- import { existsSync as existsSync25, writeFileSync as writeFileSync12 } from "fs";
22143
+ import { existsSync as existsSync25, writeFileSync as writeFileSync13 } from "fs";
21993
22144
  import { homedir as homedir14 } from "os";
21994
- import { join as join24, resolve as resolve9 } from "path";
22145
+ import { join as join26, resolve as resolve9 } from "path";
21995
22146
  import { confirm as confirm4, input as input7 } from "@inquirer/prompts";
21996
22147
  import { Command as Command26 } from "commander";
21997
22148
  function runGit2(cwd, args) {
@@ -22226,15 +22377,15 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
22226
22377
  );
22227
22378
  process.exit(1);
22228
22379
  }
22229
- const gitignorePath = join24(workspaceDir, ".gitignore");
22380
+ const gitignorePath = join26(workspaceDir, ".gitignore");
22230
22381
  if (!existsSync25(gitignorePath)) {
22231
- writeFileSync12(
22382
+ writeFileSync13(
22232
22383
  gitignorePath,
22233
22384
  "node_modules/\n.agent/\n.agents/\n.claude/\n.gemini/\n.goose/\n*.log\n",
22234
22385
  "utf8"
22235
22386
  );
22236
22387
  }
22237
- const hasGitRepo = existsSync25(join24(workspaceDir, ".git"));
22388
+ const hasGitRepo = existsSync25(join26(workspaceDir, ".git"));
22238
22389
  if (!hasGitRepo) {
22239
22390
  console.log("Initializing local Git repository...");
22240
22391
  runGit2(workspaceDir, ["init", "-b", "main"]);
@@ -22321,7 +22472,7 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
22321
22472
  }
22322
22473
  });
22323
22474
  workspaceCommand.command("data-dir").description("Print the ZAM data directory (database, credentials, config)").option("--json", "Output as JSON").action((opts) => {
22324
- const dir = join24(homedir14(), ".zam");
22475
+ const dir = join26(homedir14(), ".zam");
22325
22476
  console.log(opts.json ? JSON.stringify({ dataDir: dir }) : dir);
22326
22477
  });
22327
22478
  workspaceCommand.command("backup").description("Back up the local ZAM database into your workspace").option(
@@ -22362,9 +22513,9 @@ workspaceCommand.command("backup").description("Back up the local ZAM database i
22362
22513
  });
22363
22514
 
22364
22515
  // src/cli/app.ts
22365
- var __dirname = dirname11(fileURLToPath5(import.meta.url));
22516
+ var __dirname = dirname12(fileURLToPath6(import.meta.url));
22366
22517
  var pkg = JSON.parse(
22367
- readFileSync17(join25(__dirname, "..", "..", "package.json"), "utf-8")
22518
+ readFileSync18(join27(__dirname, "..", "..", "package.json"), "utf-8")
22368
22519
  );
22369
22520
  var program = new Command27();
22370
22521
  program.name("zam").description(