zam-core 0.10.7 → 0.10.9

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.
@@ -6122,6 +6122,14 @@ function setAgentConnectAutoDone(done, path = defaultConfigPath()) {
6122
6122
  }
6123
6123
  saveInstallConfig(config, path);
6124
6124
  }
6125
+ function getLastRepairedVersion(path = defaultConfigPath()) {
6126
+ return loadInstallConfig(path).lastRepairedVersion;
6127
+ }
6128
+ function setLastRepairedVersion(version, path = defaultConfigPath()) {
6129
+ const config = loadInstallConfig(path);
6130
+ config.lastRepairedVersion = version;
6131
+ saveInstallConfig(config, path);
6132
+ }
6125
6133
  function getConfiguredWorkspaces(path = defaultConfigPath()) {
6126
6134
  return loadInstallConfig(path).workspaces ?? [];
6127
6135
  }
@@ -6844,6 +6852,7 @@ __export(kernel_exports, {
6844
6852
  getInstallMode: () => getInstallMode,
6845
6853
  getKnowledgeContextById: () => getKnowledgeContextById,
6846
6854
  getKnowledgeContextByName: () => getKnowledgeContextByName,
6855
+ getLastRepairedVersion: () => getLastRepairedVersion,
6847
6856
  getMachineAiConfig: () => getMachineAiConfig,
6848
6857
  getMachineAiModels: () => getMachineAiModels,
6849
6858
  getMonitorDir: () => getMonitorDir,
@@ -6939,6 +6948,7 @@ __export(kernel_exports, {
6939
6948
  setAgentConnectAutoDone: () => setAgentConnectAutoDone,
6940
6949
  setInstallChannel: () => setInstallChannel,
6941
6950
  setInstallMode: () => setInstallMode,
6951
+ setLastRepairedVersion: () => setLastRepairedVersion,
6942
6952
  setProviderApiKey: () => setProviderApiKey,
6943
6953
  setSetting: () => setSetting,
6944
6954
  setTursoCredentials: () => setTursoCredentials,
@@ -7016,8 +7026,8 @@ var init_kernel = __esm({
7016
7026
 
7017
7027
  // src/cli/commands/mcp.ts
7018
7028
  init_kernel();
7019
- import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
7020
- import { dirname as dirname11, join as join23 } from "path";
7029
+ import { existsSync as existsSync21, readFileSync as readFileSync18 } from "fs";
7030
+ import { dirname as dirname11, join as join24 } from "path";
7021
7031
  import { fileURLToPath as fileURLToPath6 } from "url";
7022
7032
  import {
7023
7033
  RESOURCE_MIME_TYPE,
@@ -8132,7 +8142,7 @@ async function prepareRecallChain(db, opts) {
8132
8142
  } else {
8133
8143
  spawnLocalRunner(endpoint.url, endpoint.model, endpoint.runner);
8134
8144
  while (Date.now() < deadline) {
8135
- await new Promise((resolve6) => setTimeout(resolve6, 1e3));
8145
+ await new Promise((resolve7) => setTimeout(resolve7, 1e3));
8136
8146
  if (await isLlmOnline(endpoint.url)) {
8137
8147
  online = true;
8138
8148
  break;
@@ -8425,7 +8435,7 @@ async function startLocalRunner(url, model, locale, hint) {
8425
8435
  let attempts = 0;
8426
8436
  const dotsPerLine = 30;
8427
8437
  while (true) {
8428
- await new Promise((resolve6) => setTimeout(resolve6, 500));
8438
+ await new Promise((resolve7) => setTimeout(resolve7, 500));
8429
8439
  if (await isLlmOnline(url)) {
8430
8440
  if (attempts > 0) process.stdout.write("\n");
8431
8441
  return true;
@@ -8485,8 +8495,8 @@ async function fetchWithInteractiveTimeout(url, options = {}) {
8485
8495
  const dotsPerLine = 30;
8486
8496
  while (true) {
8487
8497
  let timeoutId;
8488
- const timeoutPromise = new Promise((resolve6) => {
8489
- timeoutId = setTimeout(() => resolve6("timeout"), timeoutMs);
8498
+ const timeoutPromise = new Promise((resolve7) => {
8499
+ timeoutId = setTimeout(() => resolve7("timeout"), timeoutMs);
8490
8500
  });
8491
8501
  const dotsInterval = setInterval(() => {
8492
8502
  process.stdout.write(".");
@@ -9830,11 +9840,11 @@ async function publishUiIntent(app, input = {}, opts = {}) {
9830
9840
 
9831
9841
  // src/cli/commands/bridge.ts
9832
9842
  init_kernel();
9833
- import { execFileSync as execFileSync4 } from "child_process";
9843
+ import { execFileSync as execFileSync5 } from "child_process";
9834
9844
  import { randomBytes as randomBytes2 } from "crypto";
9835
- import { existsSync as existsSync18, readdirSync as readdirSync2, readFileSync as readFileSync16, rmSync as rmSync3 } from "fs";
9836
- import { homedir as homedir14, tmpdir as tmpdir3 } from "os";
9837
- import { join as join22, resolve as resolve5 } from "path";
9845
+ import { existsSync as existsSync20, readdirSync as readdirSync2, readFileSync as readFileSync17, rmSync as rmSync3 } from "fs";
9846
+ import { homedir as homedir15, tmpdir as tmpdir3 } from "os";
9847
+ import { join as join23, resolve as resolve6 } from "path";
9838
9848
  import { Command } from "commander";
9839
9849
  import { ulid as ulid11 } from "ulid";
9840
9850
 
@@ -9913,7 +9923,7 @@ async function readWebLink(url) {
9913
9923
  redirect: "manual",
9914
9924
  signal: controller.signal,
9915
9925
  headers: {
9916
- "User-Agent": "ZAM-Content-Studio/0.10.7"
9926
+ "User-Agent": "ZAM-Content-Studio/0.10.9"
9917
9927
  }
9918
9928
  });
9919
9929
  if (res.status >= 300 && res.status < 400) {
@@ -10373,6 +10383,7 @@ function detectInstalledConnectHarnesses(options = {}) {
10373
10383
  return detected;
10374
10384
  }
10375
10385
  function parseMcpJsonConfig(path, content) {
10386
+ if (content.trim() === "") return {};
10376
10387
  let parsed;
10377
10388
  try {
10378
10389
  parsed = JSON.parse(content);
@@ -10418,17 +10429,21 @@ function connectHarnessMcp(harnessId, opts) {
10418
10429
  if (!existing.mcpServers) {
10419
10430
  existing.mcpServers = {};
10420
10431
  }
10421
- if (opts.zamPath.endsWith(".js")) {
10422
- existing.mcpServers.zam = {
10423
- command: process.execPath,
10424
- args: [opts.zamPath, "mcp"]
10425
- };
10426
- } else {
10427
- existing.mcpServers.zam = {
10428
- command: opts.zamPath,
10429
- args: ["mcp"]
10430
- };
10432
+ const expected = opts.zamPath.endsWith(".js") ? {
10433
+ command: process.execPath,
10434
+ args: [opts.zamPath, "mcp"]
10435
+ } : {
10436
+ command: opts.zamPath,
10437
+ args: ["mcp"]
10438
+ };
10439
+ const current = existing.mcpServers.zam;
10440
+ if (typeof current === "object" && current !== null && !Array.isArray(current)) {
10441
+ const c = current;
10442
+ if (c.command === expected.command && JSON.stringify(c.args) === JSON.stringify(expected.args)) {
10443
+ alreadyConfigured = true;
10444
+ }
10431
10445
  }
10446
+ existing.mcpServers.zam = expected;
10432
10447
  return JSON.stringify(existing, null, 2);
10433
10448
  };
10434
10449
  if (harnessId === "claude-code") {
@@ -10455,25 +10470,7 @@ function connectHarnessMcp(harnessId, opts) {
10455
10470
  } else if (harnessId === "antigravity") {
10456
10471
  targetPath = join16(opts.home, ".gemini", "config", "mcp_config.json");
10457
10472
  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.";
10458
- let existing = {};
10459
- if (exists(targetPath)) {
10460
- existing = parseMcpJsonConfig(targetPath, read(targetPath));
10461
- }
10462
- if (!existing.mcpServers) {
10463
- existing.mcpServers = {};
10464
- }
10465
- if (opts.zamPath.endsWith(".js")) {
10466
- existing.mcpServers.zam = {
10467
- command: process.execPath,
10468
- args: [opts.zamPath, "mcp"]
10469
- };
10470
- } else {
10471
- existing.mcpServers.zam = {
10472
- command: opts.zamPath,
10473
- args: ["mcp"]
10474
- };
10475
- }
10476
- content = JSON.stringify(existing, null, 2);
10473
+ content = mergeMcpServersJson(targetPath);
10477
10474
  } else if (harnessId === "opencode") {
10478
10475
  targetPath = join16(opts.home, ".config", "opencode", "opencode.json");
10479
10476
  hint = "OpenCode will load the enabled 'zam' MCP server on next launch.";
@@ -10487,11 +10484,19 @@ function connectHarnessMcp(harnessId, opts) {
10487
10484
  }
10488
10485
  const servers = mcp ?? {};
10489
10486
  const cmd = opts.zamPath.endsWith(".js") ? [process.execPath, opts.zamPath, "mcp"] : [opts.zamPath, "mcp"];
10490
- servers.zam = {
10487
+ const expected = {
10491
10488
  type: "local",
10492
10489
  command: cmd,
10493
10490
  enabled: true
10494
10491
  };
10492
+ const current = servers.zam;
10493
+ if (typeof current === "object" && current !== null && !Array.isArray(current)) {
10494
+ const c = current;
10495
+ if (c.type === expected.type && c.enabled === expected.enabled && JSON.stringify(c.command) === JSON.stringify(expected.command)) {
10496
+ alreadyConfigured = true;
10497
+ }
10498
+ }
10499
+ servers.zam = expected;
10495
10500
  existing.mcp = servers;
10496
10501
  content = JSON.stringify(existing, null, 2);
10497
10502
  } else if (harnessId === "codex") {
@@ -10602,21 +10607,25 @@ ${zamExtension}
10602
10607
  if (!existing.mcpServers) {
10603
10608
  existing.mcpServers = {};
10604
10609
  }
10605
- if (opts.zamPath.endsWith(".js")) {
10606
- existing.mcpServers.zam = {
10607
- type: "local",
10608
- command: process.execPath,
10609
- args: [opts.zamPath, "mcp"],
10610
- tools: ["*"]
10611
- };
10612
- } else {
10613
- existing.mcpServers.zam = {
10614
- type: "local",
10615
- command: opts.zamPath,
10616
- args: ["mcp"],
10617
- tools: ["*"]
10618
- };
10610
+ const expected = opts.zamPath.endsWith(".js") ? {
10611
+ type: "local",
10612
+ command: process.execPath,
10613
+ args: [opts.zamPath, "mcp"],
10614
+ tools: ["*"]
10615
+ } : {
10616
+ type: "local",
10617
+ command: opts.zamPath,
10618
+ args: ["mcp"],
10619
+ tools: ["*"]
10620
+ };
10621
+ const current = existing.mcpServers.zam;
10622
+ if (typeof current === "object" && current !== null && !Array.isArray(current)) {
10623
+ const c = current;
10624
+ if (c.type === expected.type && c.command === expected.command && JSON.stringify(c.args) === JSON.stringify(expected.args) && JSON.stringify(c.tools) === JSON.stringify(expected.tools)) {
10625
+ alreadyConfigured = true;
10626
+ }
10619
10627
  }
10628
+ existing.mcpServers.zam = expected;
10620
10629
  content = JSON.stringify(existing, null, 2);
10621
10630
  }
10622
10631
  return {
@@ -11043,6 +11052,127 @@ function inspectConnectHarnesses(deps = {}) {
11043
11052
  return { zamOnPath: Boolean(foundZam), harnesses };
11044
11053
  }
11045
11054
 
11055
+ // src/cli/cli-install.ts
11056
+ import { execFileSync as execFileSync4 } from "child_process";
11057
+ import {
11058
+ chmodSync as chmodSync2,
11059
+ existsSync as existsSync17,
11060
+ mkdirSync as mkdirSync13,
11061
+ readFileSync as readFileSync14,
11062
+ writeFileSync as writeFileSync11
11063
+ } from "fs";
11064
+ import { homedir as homedir14 } from "os";
11065
+ import { delimiter as delimiter2, join as join19, resolve as resolve5 } from "path";
11066
+ var BUILT_CLI_PATTERN = /[\\/]dist[\\/]cli[\\/]index\.js$/;
11067
+ function normalizeForCompare(path, platform) {
11068
+ const resolved = resolve5(path);
11069
+ return platform === "win32" ? resolved.toLowerCase() : resolved;
11070
+ }
11071
+ function windowsShimContent(nodePath, cliPath) {
11072
+ return `@echo off\r
11073
+ "${nodePath}" "${cliPath}" %*\r
11074
+ `;
11075
+ }
11076
+ function unixShimContent(nodePath, cliPath) {
11077
+ return `#!/bin/sh
11078
+ exec "${nodePath}" "${cliPath}" "$@"
11079
+ `;
11080
+ }
11081
+ function psQuote(value) {
11082
+ return `'${value.replace(/'/g, "''")}'`;
11083
+ }
11084
+ function ensureWindowsUserPath(binDir) {
11085
+ const script = [
11086
+ "$ErrorActionPreference = 'Stop'",
11087
+ "$key = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey('Environment', $true)",
11088
+ "$old = [string]$key.GetValue('Path', '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)",
11089
+ `$target = ${psQuote(binDir)}`,
11090
+ "$expanded = ($old -split ';') | Where-Object { $_ -ne '' } | ForEach-Object { [Environment]::ExpandEnvironmentVariables($_).TrimEnd('\\') }",
11091
+ "if ($expanded -contains $target.TrimEnd('\\')) { 'present'; exit 0 }",
11092
+ "$new = if ($old -and -not $old.EndsWith(';')) { $old + ';' + $target } else { $old + $target }",
11093
+ "$key.SetValue('Path', $new, [Microsoft.Win32.RegistryValueKind]::ExpandString)",
11094
+ `$sig = '[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);'`,
11095
+ "$type = Add-Type -MemberDefinition $sig -Name 'ZamEnvBroadcast' -Namespace 'ZamInstall' -PassThru",
11096
+ "[UIntPtr]$result = [UIntPtr]::Zero",
11097
+ "$null = $type::SendMessageTimeout([IntPtr]0xffff, 0x001A, [UIntPtr]::Zero, 'Environment', 2, 5000, [ref]$result)",
11098
+ "'updated'"
11099
+ ].join("; ");
11100
+ const output = execFileSync4(
11101
+ "powershell.exe",
11102
+ ["-NoProfile", "-NonInteractive", "-Command", script],
11103
+ { stdio: ["ignore", "pipe", "pipe"], windowsHide: true }
11104
+ ).toString().trim();
11105
+ return output.endsWith("updated");
11106
+ }
11107
+ function ensureUnixUserPath(home, platform) {
11108
+ const profile = join19(home, platform === "darwin" ? ".zprofile" : ".profile");
11109
+ const existing = existsSync17(profile) ? readFileSync14(profile, "utf8") : "";
11110
+ if (existing.includes(".zam/bin")) return false;
11111
+ const block = `
11112
+ # Added by ZAM: keep the zam CLI on PATH
11113
+ export PATH="$HOME/.zam/bin:$PATH"
11114
+ `;
11115
+ writeFileSync11(profile, existing + block, "utf8");
11116
+ return true;
11117
+ }
11118
+ function installCliShim(options = {}) {
11119
+ const home = options.home ?? homedir14();
11120
+ const platform = options.platform ?? process.platform;
11121
+ const env = options.env ?? process.env;
11122
+ const find = options.find ?? findExecutable;
11123
+ const nodePath = resolve5(options.nodePath ?? process.execPath);
11124
+ const cliPath = resolve5(options.cliPath ?? process.argv[1] ?? "");
11125
+ const binDir = join19(home, ".zam", "bin");
11126
+ const shimPath = join19(binDir, platform === "win32" ? "zam.cmd" : "zam");
11127
+ const report = {
11128
+ status: "ok",
11129
+ binDir,
11130
+ shimPath,
11131
+ nodePath,
11132
+ cliPath,
11133
+ onPath: false,
11134
+ pathUpdated: false,
11135
+ needsNewTerminal: false
11136
+ };
11137
+ if (!BUILT_CLI_PATTERN.test(cliPath) || !existsSync17(cliPath)) {
11138
+ report.status = "skipped";
11139
+ report.detail = `Not running from a built CLI entry (${cliPath}); nothing to link.`;
11140
+ return report;
11141
+ }
11142
+ try {
11143
+ const existing = find("zam");
11144
+ if (existing && normalizeForCompare(existing, platform) !== normalizeForCompare(shimPath, platform)) {
11145
+ report.status = "external";
11146
+ report.onPath = true;
11147
+ report.detail = existing;
11148
+ return report;
11149
+ }
11150
+ const content = platform === "win32" ? windowsShimContent(nodePath, cliPath) : unixShimContent(nodePath, cliPath);
11151
+ const existed = existsSync17(shimPath);
11152
+ const changed = !existed || readFileSync14(shimPath, "utf8") !== content;
11153
+ if (changed) {
11154
+ mkdirSync13(binDir, { recursive: true });
11155
+ writeFileSync11(shimPath, content, "utf8");
11156
+ if (platform !== "win32") chmodSync2(shimPath, 493);
11157
+ }
11158
+ report.status = !existed ? "installed" : changed ? "refreshed" : "ok";
11159
+ const processPathHasBin = (env.PATH ?? "").split(delimiter2).map((entry) => normalizeForCompare(entry.trim() || ".", platform)).includes(normalizeForCompare(binDir, platform));
11160
+ if (processPathHasBin) {
11161
+ report.onPath = true;
11162
+ return report;
11163
+ }
11164
+ const ensureUserPath = options.ensureUserPath ?? (platform === "win32" ? ensureWindowsUserPath : () => ensureUnixUserPath(home, platform));
11165
+ report.pathUpdated = ensureUserPath(binDir);
11166
+ report.onPath = true;
11167
+ report.needsNewTerminal = true;
11168
+ return report;
11169
+ } catch (error) {
11170
+ report.status = "error";
11171
+ report.detail = error instanceof Error ? error.message : String(error);
11172
+ return report;
11173
+ }
11174
+ }
11175
+
11046
11176
  // src/cli/curriculum/breadcrumb.ts
11047
11177
  init_kernel();
11048
11178
  var LAST_SELECTION_KEY = "curriculum.lastSelection";
@@ -27729,6 +27859,297 @@ function getCurriculumProvider(id) {
27729
27859
  return CURRICULUM_PROVIDERS.find((provider) => provider.id === id);
27730
27860
  }
27731
27861
 
27862
+ // src/cli/install-repair.ts
27863
+ init_kernel();
27864
+ import { existsSync as existsSync19 } from "fs";
27865
+
27866
+ // src/cli/provisioning/index.ts
27867
+ import {
27868
+ existsSync as existsSync18,
27869
+ lstatSync as lstatSync2,
27870
+ mkdirSync as mkdirSync14,
27871
+ readFileSync as readFileSync15,
27872
+ realpathSync,
27873
+ rmSync as rmSync2,
27874
+ symlinkSync,
27875
+ writeFileSync as writeFileSync12
27876
+ } from "fs";
27877
+ import { basename as basename4, dirname as dirname10, join as join20 } from "path";
27878
+ import { fileURLToPath as fileURLToPath5 } from "url";
27879
+ var packageRoot3 = [
27880
+ fileURLToPath5(new URL("../..", import.meta.url)),
27881
+ fileURLToPath5(new URL("../../..", import.meta.url))
27882
+ ].find((candidate) => existsSync18(join20(candidate, "package.json"))) ?? fileURLToPath5(new URL("../..", import.meta.url));
27883
+ var ALL_SETUP_AGENTS = ["claude", "copilot", "codex", "agent"];
27884
+ var SKILL_PAIRS = [
27885
+ {
27886
+ from: join20(packageRoot3, ".claude", "skills", "zam", "SKILL.md"),
27887
+ to: join20(".claude", "skills", "zam", "SKILL.md"),
27888
+ agents: ["claude", "copilot"]
27889
+ },
27890
+ {
27891
+ from: join20(packageRoot3, ".agent", "skills", "zam", "SKILL.md"),
27892
+ to: join20(".agent", "skills", "zam", "SKILL.md"),
27893
+ agents: ["agent"]
27894
+ },
27895
+ {
27896
+ from: join20(packageRoot3, ".agents", "skills", "zam", "SKILL.md"),
27897
+ to: join20(".agents", "skills", "zam", "SKILL.md"),
27898
+ agents: ["codex"]
27899
+ }
27900
+ ];
27901
+ function parseSetupAgents(value) {
27902
+ if (!value || value.trim().toLowerCase() === "all") {
27903
+ return new Set(ALL_SETUP_AGENTS);
27904
+ }
27905
+ const aliases = {
27906
+ claude: ["claude"],
27907
+ copilot: ["copilot"],
27908
+ codex: ["codex"],
27909
+ agent: ["agent"],
27910
+ opencode: ["agent"]
27911
+ };
27912
+ const selected = /* @__PURE__ */ new Set();
27913
+ for (const raw of value.split(",")) {
27914
+ const key = raw.trim().toLowerCase();
27915
+ const mapped = aliases[key];
27916
+ if (!mapped) {
27917
+ throw new Error(
27918
+ `Unknown agent "${raw}". Use: all, claude, copilot, codex, agent.`
27919
+ );
27920
+ }
27921
+ for (const item of mapped) selected.add(item);
27922
+ }
27923
+ return selected;
27924
+ }
27925
+ function pathExists(path) {
27926
+ try {
27927
+ lstatSync2(path);
27928
+ return true;
27929
+ } catch {
27930
+ return false;
27931
+ }
27932
+ }
27933
+ function isSymbolicLink(path) {
27934
+ try {
27935
+ return lstatSync2(path).isSymbolicLink();
27936
+ } catch {
27937
+ return false;
27938
+ }
27939
+ }
27940
+ function comparableRealPath(path) {
27941
+ const real = realpathSync(path);
27942
+ return process.platform === "win32" ? real.toLowerCase() : real;
27943
+ }
27944
+ function pathsResolveToSameDirectory(sourceDir, destinationDir) {
27945
+ try {
27946
+ return comparableRealPath(destinationDir) === comparableRealPath(sourceDir);
27947
+ } catch {
27948
+ return false;
27949
+ }
27950
+ }
27951
+ function linkPointsTo(sourceDir, destinationDir) {
27952
+ return isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir);
27953
+ }
27954
+ function isZamSkillCopy(destinationDir) {
27955
+ try {
27956
+ if (!lstatSync2(destinationDir).isDirectory()) return false;
27957
+ const skillFile = join20(destinationDir, "SKILL.md");
27958
+ if (!existsSync18(skillFile)) return false;
27959
+ return /^name:\s*zam\s*$/m.test(readFileSync15(skillFile, "utf8"));
27960
+ } catch {
27961
+ return false;
27962
+ }
27963
+ }
27964
+ function classifySkillDestination(sourceDir, destinationDir) {
27965
+ if (!existsSync18(sourceDir)) return "source-missing";
27966
+ if (!isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir)) {
27967
+ return "source-directory";
27968
+ }
27969
+ if (linkPointsTo(sourceDir, destinationDir)) return "linked";
27970
+ if (!pathExists(destinationDir)) return "missing";
27971
+ if (isSymbolicLink(destinationDir)) return "broken";
27972
+ if (isZamSkillCopy(destinationDir)) return "stale-copy";
27973
+ return "unmanaged";
27974
+ }
27975
+ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {}) {
27976
+ const results = [];
27977
+ const log = (message) => {
27978
+ if (!opts.quiet) console.log(message);
27979
+ };
27980
+ for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
27981
+ if (!pairAgents.some((agent) => agents.has(agent))) continue;
27982
+ const sourceDir = dirname10(from);
27983
+ const destinationDir = dirname10(join20(cwd, to));
27984
+ const label = dirname10(to);
27985
+ const state = classifySkillDestination(sourceDir, destinationDir);
27986
+ if (state === "source-missing") {
27987
+ if (!opts.quiet) {
27988
+ console.warn(` warn source not found, skipping: ${sourceDir}`);
27989
+ }
27990
+ results.push({
27991
+ source: sourceDir,
27992
+ destination: destinationDir,
27993
+ action: "skipped",
27994
+ reason: "source-not-found"
27995
+ });
27996
+ continue;
27997
+ }
27998
+ if (state === "source-directory") {
27999
+ log(` skip ${label} (package source)`);
28000
+ results.push({
28001
+ source: sourceDir,
28002
+ destination: destinationDir,
28003
+ action: "skipped",
28004
+ reason: "source-directory"
28005
+ });
28006
+ continue;
28007
+ }
28008
+ if (state === "linked") {
28009
+ log(` skip ${label} (already linked)`);
28010
+ results.push({
28011
+ source: sourceDir,
28012
+ destination: destinationDir,
28013
+ action: "skipped",
28014
+ reason: "already-linked"
28015
+ });
28016
+ continue;
28017
+ }
28018
+ const destinationExists = state !== "missing";
28019
+ const replaceExisting = destinationExists && (Boolean(opts.force) || state === "broken" || state === "stale-copy");
28020
+ if (destinationExists && !replaceExisting) {
28021
+ if (!opts.quiet) {
28022
+ console.warn(
28023
+ ` warn ${label} already exists and is not managed by ZAM; use --force to replace it`
28024
+ );
28025
+ }
28026
+ results.push({
28027
+ source: sourceDir,
28028
+ destination: destinationDir,
28029
+ action: "skipped",
28030
+ reason: "unmanaged-destination"
28031
+ });
28032
+ continue;
28033
+ }
28034
+ const action = destinationExists ? "relinked" : "linked";
28035
+ if (opts.dryRun) {
28036
+ log(` would ${action === "linked" ? "link" : "relink"} ${label}`);
28037
+ } else {
28038
+ if (destinationExists) {
28039
+ rmSync2(destinationDir, { recursive: true, force: true });
28040
+ }
28041
+ mkdirSync14(dirname10(destinationDir), { recursive: true });
28042
+ symlinkSync(
28043
+ sourceDir,
28044
+ destinationDir,
28045
+ process.platform === "win32" ? "junction" : "dir"
28046
+ );
28047
+ log(` ${action === "linked" ? "link" : "relink"} ${label}`);
28048
+ }
28049
+ results.push({ source: sourceDir, destination: destinationDir, action });
28050
+ }
28051
+ return results;
28052
+ }
28053
+ function inspectSkillLinks(cwd = process.cwd(), agents = parseSetupAgents()) {
28054
+ const results = [];
28055
+ for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
28056
+ if (!pairAgents.some((agent) => agents.has(agent))) continue;
28057
+ const sourceDir = dirname10(from);
28058
+ const destinationDir = dirname10(join20(cwd, to));
28059
+ results.push({
28060
+ agents: pairAgents,
28061
+ source: sourceDir,
28062
+ destination: destinationDir,
28063
+ state: classifySkillDestination(sourceDir, destinationDir)
28064
+ });
28065
+ }
28066
+ return results;
28067
+ }
28068
+ function summarizeSkillLinkHealth(inspections) {
28069
+ if (inspections.some((item) => item.state === "unmanaged")) {
28070
+ return "unmanaged";
28071
+ }
28072
+ const repairable = ["missing", "broken", "stale-copy"];
28073
+ if (inspections.some((item) => repairable.includes(item.state))) {
28074
+ return "needs-repair";
28075
+ }
28076
+ return "healthy";
28077
+ }
28078
+
28079
+ // src/cli/install-repair.ts
28080
+ function defaultRepairWorkspaces() {
28081
+ const summary = {
28082
+ provisioned: 0,
28083
+ missing: 0,
28084
+ relinked: 0
28085
+ };
28086
+ try {
28087
+ const agents = parseSetupAgents();
28088
+ for (const workspace of getConfiguredWorkspaces()) {
28089
+ if (!existsSync19(workspace.path)) {
28090
+ summary.missing += 1;
28091
+ continue;
28092
+ }
28093
+ const results = wireSkills(workspace.path, agents, { quiet: true });
28094
+ summary.provisioned += 1;
28095
+ summary.relinked += results.filter(
28096
+ (result) => result.action === "linked" || result.action === "relinked"
28097
+ ).length;
28098
+ }
28099
+ } catch (error) {
28100
+ summary.error = error instanceof Error ? error.message : String(error);
28101
+ }
28102
+ return summary;
28103
+ }
28104
+ function defaultConnectAgents(deps) {
28105
+ const report = performAgentConnect({}, deps);
28106
+ const companion = report.results.find((result) => result.extension?.kind === "vscode")?.extension?.action ?? null;
28107
+ return {
28108
+ success: report.success,
28109
+ detected: report.detected,
28110
+ connected: report.results.filter(
28111
+ (result) => !result.error && (result.alreadyConfigured || result.wrote)
28112
+ ).length,
28113
+ companion,
28114
+ errors: report.results.flatMap(
28115
+ (result) => result.error ? [`${result.label}: ${result.error}`] : []
28116
+ )
28117
+ };
28118
+ }
28119
+ function performInstallRepair(opts = {}, deps = {}) {
28120
+ const version = (deps.version ?? currentVersion)();
28121
+ const getLastRepaired = deps.getLastRepaired ?? getLastRepairedVersion;
28122
+ if (opts.ifVersionChanged && getLastRepaired() === version) {
28123
+ return {
28124
+ version,
28125
+ skipped: true,
28126
+ cli: null,
28127
+ workspaces: null,
28128
+ agents: null
28129
+ };
28130
+ }
28131
+ const cli = (deps.installCli ?? installCliShim)();
28132
+ const workspaces = (deps.repairWorkspaces ?? defaultRepairWorkspaces)();
28133
+ const agentDeps = {};
28134
+ if (cli.status === "installed" || cli.status === "refreshed" || cli.status === "ok") {
28135
+ agentDeps.findZam = () => cli.shimPath;
28136
+ }
28137
+ let agents;
28138
+ try {
28139
+ agents = (deps.connectAgents ?? defaultConnectAgents)(agentDeps);
28140
+ } catch (error) {
28141
+ agents = {
28142
+ success: false,
28143
+ detected: [],
28144
+ connected: 0,
28145
+ companion: null,
28146
+ errors: [error instanceof Error ? error.message : String(error)]
28147
+ };
28148
+ }
28149
+ (deps.setLastRepaired ?? setLastRepairedVersion)(version);
28150
+ return { version, skipped: false, cli, workspaces, agents };
28151
+ }
28152
+
27732
28153
  // src/cli/llm/capability-probe.ts
27733
28154
  init_kernel();
27734
28155
  var EMBEDDING_MODEL_HINTS = [
@@ -27853,9 +28274,9 @@ function validateModelSave(entry, probe, now = () => (/* @__PURE__ */ new Date()
27853
28274
  // src/cli/llm/vision.ts
27854
28275
  init_kernel();
27855
28276
  import { randomBytes } from "crypto";
27856
- import { readFileSync as readFileSync14 } from "fs";
28277
+ import { readFileSync as readFileSync16 } from "fs";
27857
28278
  import { tmpdir as tmpdir2 } from "os";
27858
- import { basename as basename4, join as join19 } from "path";
28279
+ import { basename as basename5, join as join21 } from "path";
27859
28280
  var LANGUAGE_NAMES2 = {
27860
28281
  en: "English",
27861
28282
  de: "German",
@@ -27891,13 +28312,13 @@ async function observeUiSnapshotViaLLM(db, input) {
27891
28312
  const imageUrls = [];
27892
28313
  const isVideo = /\.(mp4|mov|m4v|avi|mkv|webm)$/i.test(input.imagePath);
27893
28314
  if (isVideo) {
27894
- const { mkdirSync: mkdirSync15, readdirSync: readdirSync3, rmSync: rmSync4 } = await import("fs");
28315
+ const { mkdirSync: mkdirSync16, readdirSync: readdirSync3, rmSync: rmSync4 } = await import("fs");
27895
28316
  const { execSync: execSync5 } = await import("child_process");
27896
- const tempDir = join19(
28317
+ const tempDir = join21(
27897
28318
  tmpdir2(),
27898
28319
  `zam-frames-${randomBytes(4).toString("hex")}`
27899
28320
  );
27900
- mkdirSync15(tempDir, { recursive: true });
28321
+ mkdirSync16(tempDir, { recursive: true });
27901
28322
  try {
27902
28323
  execSync5(
27903
28324
  `ffmpeg -i "${input.imagePath}" -vf "fps=1/3,scale=1280:-1" -vsync vfr "${tempDir}/frame_%03d.png"`,
@@ -27926,7 +28347,7 @@ async function observeUiSnapshotViaLLM(db, input) {
27926
28347
  }
27927
28348
  }
27928
28349
  for (const file of sampledFiles) {
27929
- const bytes = readFileSync14(join19(tempDir, file));
28350
+ const bytes = readFileSync16(join21(tempDir, file));
27930
28351
  imageUrls.push(`data:image/png;base64,${bytes.toString("base64")}`);
27931
28352
  }
27932
28353
  } finally {
@@ -27936,7 +28357,7 @@ async function observeUiSnapshotViaLLM(db, input) {
27936
28357
  }
27937
28358
  }
27938
28359
  } else {
27939
- const imageBytes = readFileSync14(input.imagePath);
28360
+ const imageBytes = readFileSync16(input.imagePath);
27940
28361
  const ext = input.imagePath.split(".").pop()?.toLowerCase();
27941
28362
  const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
27942
28363
  imageUrls.push(`data:${mime};base64,${imageBytes.toString("base64")}`);
@@ -28174,7 +28595,7 @@ function buildReport(input, draft) {
28174
28595
  evidence: [
28175
28596
  {
28176
28597
  type: "keyframe",
28177
- ref: input.evidenceRef ?? basename4(input.imagePath),
28598
+ ref: input.evidenceRef ?? basename5(input.imagePath),
28178
28599
  redacted: input.redacted ?? false
28179
28600
  }
28180
28601
  ],
@@ -28196,7 +28617,7 @@ function uncertainReport(input, summary) {
28196
28617
  evidence: [
28197
28618
  {
28198
28619
  type: "keyframe",
28199
- ref: input.evidenceRef ?? basename4(input.imagePath),
28620
+ ref: input.evidenceRef ?? basename5(input.imagePath),
28200
28621
  redacted: input.redacted ?? false
28201
28622
  }
28202
28623
  ],
@@ -28406,219 +28827,6 @@ async function withProviderScope(machine, action) {
28406
28827
  await withDb(action);
28407
28828
  }
28408
28829
 
28409
- // src/cli/provisioning/index.ts
28410
- import {
28411
- existsSync as existsSync17,
28412
- lstatSync as lstatSync2,
28413
- mkdirSync as mkdirSync13,
28414
- readFileSync as readFileSync15,
28415
- realpathSync,
28416
- rmSync as rmSync2,
28417
- symlinkSync,
28418
- writeFileSync as writeFileSync11
28419
- } from "fs";
28420
- import { basename as basename5, dirname as dirname10, join as join20 } from "path";
28421
- import { fileURLToPath as fileURLToPath5 } from "url";
28422
- var packageRoot3 = [
28423
- fileURLToPath5(new URL("../..", import.meta.url)),
28424
- fileURLToPath5(new URL("../../..", import.meta.url))
28425
- ].find((candidate) => existsSync17(join20(candidate, "package.json"))) ?? fileURLToPath5(new URL("../..", import.meta.url));
28426
- var ALL_SETUP_AGENTS = ["claude", "copilot", "codex", "agent"];
28427
- var SKILL_PAIRS = [
28428
- {
28429
- from: join20(packageRoot3, ".claude", "skills", "zam", "SKILL.md"),
28430
- to: join20(".claude", "skills", "zam", "SKILL.md"),
28431
- agents: ["claude", "copilot"]
28432
- },
28433
- {
28434
- from: join20(packageRoot3, ".agent", "skills", "zam", "SKILL.md"),
28435
- to: join20(".agent", "skills", "zam", "SKILL.md"),
28436
- agents: ["agent"]
28437
- },
28438
- {
28439
- from: join20(packageRoot3, ".agents", "skills", "zam", "SKILL.md"),
28440
- to: join20(".agents", "skills", "zam", "SKILL.md"),
28441
- agents: ["codex"]
28442
- }
28443
- ];
28444
- function parseSetupAgents(value) {
28445
- if (!value || value.trim().toLowerCase() === "all") {
28446
- return new Set(ALL_SETUP_AGENTS);
28447
- }
28448
- const aliases = {
28449
- claude: ["claude"],
28450
- copilot: ["copilot"],
28451
- codex: ["codex"],
28452
- agent: ["agent"],
28453
- opencode: ["agent"]
28454
- };
28455
- const selected = /* @__PURE__ */ new Set();
28456
- for (const raw of value.split(",")) {
28457
- const key = raw.trim().toLowerCase();
28458
- const mapped = aliases[key];
28459
- if (!mapped) {
28460
- throw new Error(
28461
- `Unknown agent "${raw}". Use: all, claude, copilot, codex, agent.`
28462
- );
28463
- }
28464
- for (const item of mapped) selected.add(item);
28465
- }
28466
- return selected;
28467
- }
28468
- function pathExists(path) {
28469
- try {
28470
- lstatSync2(path);
28471
- return true;
28472
- } catch {
28473
- return false;
28474
- }
28475
- }
28476
- function isSymbolicLink(path) {
28477
- try {
28478
- return lstatSync2(path).isSymbolicLink();
28479
- } catch {
28480
- return false;
28481
- }
28482
- }
28483
- function comparableRealPath(path) {
28484
- const real = realpathSync(path);
28485
- return process.platform === "win32" ? real.toLowerCase() : real;
28486
- }
28487
- function pathsResolveToSameDirectory(sourceDir, destinationDir) {
28488
- try {
28489
- return comparableRealPath(destinationDir) === comparableRealPath(sourceDir);
28490
- } catch {
28491
- return false;
28492
- }
28493
- }
28494
- function linkPointsTo(sourceDir, destinationDir) {
28495
- return isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir);
28496
- }
28497
- function isZamSkillCopy(destinationDir) {
28498
- try {
28499
- if (!lstatSync2(destinationDir).isDirectory()) return false;
28500
- const skillFile = join20(destinationDir, "SKILL.md");
28501
- if (!existsSync17(skillFile)) return false;
28502
- return /^name:\s*zam\s*$/m.test(readFileSync15(skillFile, "utf8"));
28503
- } catch {
28504
- return false;
28505
- }
28506
- }
28507
- function classifySkillDestination(sourceDir, destinationDir) {
28508
- if (!existsSync17(sourceDir)) return "source-missing";
28509
- if (!isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir)) {
28510
- return "source-directory";
28511
- }
28512
- if (linkPointsTo(sourceDir, destinationDir)) return "linked";
28513
- if (!pathExists(destinationDir)) return "missing";
28514
- if (isSymbolicLink(destinationDir)) return "broken";
28515
- if (isZamSkillCopy(destinationDir)) return "stale-copy";
28516
- return "unmanaged";
28517
- }
28518
- function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {}) {
28519
- const results = [];
28520
- const log = (message) => {
28521
- if (!opts.quiet) console.log(message);
28522
- };
28523
- for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
28524
- if (!pairAgents.some((agent) => agents.has(agent))) continue;
28525
- const sourceDir = dirname10(from);
28526
- const destinationDir = dirname10(join20(cwd, to));
28527
- const label = dirname10(to);
28528
- const state = classifySkillDestination(sourceDir, destinationDir);
28529
- if (state === "source-missing") {
28530
- if (!opts.quiet) {
28531
- console.warn(` warn source not found, skipping: ${sourceDir}`);
28532
- }
28533
- results.push({
28534
- source: sourceDir,
28535
- destination: destinationDir,
28536
- action: "skipped",
28537
- reason: "source-not-found"
28538
- });
28539
- continue;
28540
- }
28541
- if (state === "source-directory") {
28542
- log(` skip ${label} (package source)`);
28543
- results.push({
28544
- source: sourceDir,
28545
- destination: destinationDir,
28546
- action: "skipped",
28547
- reason: "source-directory"
28548
- });
28549
- continue;
28550
- }
28551
- if (state === "linked") {
28552
- log(` skip ${label} (already linked)`);
28553
- results.push({
28554
- source: sourceDir,
28555
- destination: destinationDir,
28556
- action: "skipped",
28557
- reason: "already-linked"
28558
- });
28559
- continue;
28560
- }
28561
- const destinationExists = state !== "missing";
28562
- const replaceExisting = destinationExists && (Boolean(opts.force) || state === "broken" || state === "stale-copy");
28563
- if (destinationExists && !replaceExisting) {
28564
- if (!opts.quiet) {
28565
- console.warn(
28566
- ` warn ${label} already exists and is not managed by ZAM; use --force to replace it`
28567
- );
28568
- }
28569
- results.push({
28570
- source: sourceDir,
28571
- destination: destinationDir,
28572
- action: "skipped",
28573
- reason: "unmanaged-destination"
28574
- });
28575
- continue;
28576
- }
28577
- const action = destinationExists ? "relinked" : "linked";
28578
- if (opts.dryRun) {
28579
- log(` would ${action === "linked" ? "link" : "relink"} ${label}`);
28580
- } else {
28581
- if (destinationExists) {
28582
- rmSync2(destinationDir, { recursive: true, force: true });
28583
- }
28584
- mkdirSync13(dirname10(destinationDir), { recursive: true });
28585
- symlinkSync(
28586
- sourceDir,
28587
- destinationDir,
28588
- process.platform === "win32" ? "junction" : "dir"
28589
- );
28590
- log(` ${action === "linked" ? "link" : "relink"} ${label}`);
28591
- }
28592
- results.push({ source: sourceDir, destination: destinationDir, action });
28593
- }
28594
- return results;
28595
- }
28596
- function inspectSkillLinks(cwd = process.cwd(), agents = parseSetupAgents()) {
28597
- const results = [];
28598
- for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
28599
- if (!pairAgents.some((agent) => agents.has(agent))) continue;
28600
- const sourceDir = dirname10(from);
28601
- const destinationDir = dirname10(join20(cwd, to));
28602
- results.push({
28603
- agents: pairAgents,
28604
- source: sourceDir,
28605
- destination: destinationDir,
28606
- state: classifySkillDestination(sourceDir, destinationDir)
28607
- });
28608
- }
28609
- return results;
28610
- }
28611
- function summarizeSkillLinkHealth(inspections) {
28612
- if (inspections.some((item) => item.state === "unmanaged")) {
28613
- return "unmanaged";
28614
- }
28615
- const repairable = ["missing", "broken", "stale-copy"];
28616
- if (inspections.some((item) => repairable.includes(item.state))) {
28617
- return "needs-repair";
28618
- }
28619
- return "healthy";
28620
- }
28621
-
28622
28830
  // src/cli/users/identity.ts
28623
28831
  init_kernel();
28624
28832
  async function ensureDefaultUser(db, preferredUserId) {
@@ -28642,13 +28850,13 @@ async function resolveUser(opts, db, resolveOpts) {
28642
28850
  }
28643
28851
 
28644
28852
  // src/cli/workspaces/backup.ts
28645
- import { mkdirSync as mkdirSync14 } from "fs";
28646
- import { join as join21 } from "path";
28853
+ import { mkdirSync as mkdirSync15 } from "fs";
28854
+ import { join as join22 } from "path";
28647
28855
  async function backupDatabaseTo(db, targetDir) {
28648
- const backupDir = join21(targetDir, "zam-backups");
28649
- mkdirSync14(backupDir, { recursive: true });
28856
+ const backupDir = join22(targetDir, "zam-backups");
28857
+ mkdirSync15(backupDir, { recursive: true });
28650
28858
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
28651
- const dest = join21(backupDir, `zam-${stamp}.db`);
28859
+ const dest = join22(backupDir, `zam-${stamp}.db`);
28652
28860
  await db.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
28653
28861
  return dest;
28654
28862
  }
@@ -28779,20 +28987,20 @@ bridgeCommand.command("workspace-info").description("Report the workspace dir, i
28779
28987
  activeWorkspace,
28780
28988
  workspaceDir: activeWorkspace.path,
28781
28989
  defaultWorkspaceDir: defaultWorkspaceDir(),
28782
- dataDir: join22(homedir14(), ".zam")
28990
+ dataDir: join23(homedir15(), ".zam")
28783
28991
  });
28784
28992
  });
28785
28993
  });
28786
28994
  function provisionConfiguredWorkspaces() {
28787
28995
  return getConfiguredWorkspaces().flatMap(
28788
- (workspace) => existsSync18(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
28996
+ (workspace) => existsSync20(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
28789
28997
  );
28790
28998
  }
28791
28999
  function buildWorkspaceLinkHealth(workspaces) {
28792
29000
  const agents = parseSetupAgents();
28793
29001
  const map = {};
28794
29002
  for (const workspace of workspaces) {
28795
- if (!existsSync18(workspace.path)) continue;
29003
+ if (!existsSync20(workspace.path)) continue;
28796
29004
  const links = inspectSkillLinks(workspace.path, agents);
28797
29005
  map[workspace.id] = {
28798
29006
  health: summarizeSkillLinkHealth(links),
@@ -28822,7 +29030,7 @@ bridgeCommand.command("workspace-list").description("List configured ZAM workspa
28822
29030
  activeWorkspace,
28823
29031
  workspaceDir: activeWorkspace.path,
28824
29032
  defaultWorkspaceDir: defaultWorkspaceDir(),
28825
- dataDir: join22(homedir14(), ".zam"),
29033
+ dataDir: join23(homedir15(), ".zam"),
28826
29034
  linkHealth: buildWorkspaceLinkHealth(workspaces)
28827
29035
  });
28828
29036
  });
@@ -28837,7 +29045,7 @@ bridgeCommand.command("workspace-repair-links").description(
28837
29045
  if (!id) jsonError("A non-empty --id is required");
28838
29046
  const workspace = getConfiguredWorkspaces().find((item) => item.id === id);
28839
29047
  if (!workspace) jsonError(`Workspace "${id}" is not configured`);
28840
- if (!existsSync18(workspace.path)) {
29048
+ if (!existsSync20(workspace.path)) {
28841
29049
  jsonError(`Workspace path does not exist: ${workspace.path}`);
28842
29050
  }
28843
29051
  const agents = parseSetupAgents(opts.agents);
@@ -28868,8 +29076,8 @@ function parseBridgeWorkspaceKind(value) {
28868
29076
  bridgeCommand.command("workspace-add").description("Register an existing directory as a ZAM workspace (JSON)").requiredOption("--path <dir>", "Existing workspace/repository directory").option("--id <id>", "Workspace id").option("--label <label>", "Human-readable label").option("--kind <kind>", "Workspace kind", "custom").action(async (opts) => {
28869
29077
  const raw = String(opts.path ?? "").trim();
28870
29078
  if (!raw) jsonError("A non-empty --path is required");
28871
- const path = resolve5(raw);
28872
- if (!existsSync18(path)) jsonError(`Workspace path does not exist: ${path}`);
29079
+ const path = resolve6(raw);
29080
+ if (!existsSync20(path)) jsonError(`Workspace path does not exist: ${path}`);
28873
29081
  const id = opts.id ? String(opts.id).trim() : void 0;
28874
29082
  if (opts.id && !id) jsonError("A non-empty --id is required");
28875
29083
  const kind = parseBridgeWorkspaceKind(opts.kind);
@@ -28913,8 +29121,8 @@ bridgeCommand.command("workspace-remove").description("Unregister a ZAM workspac
28913
29121
  bridgeCommand.command("set-workspace-dir").description("Set the personal workspace directory (JSON)").requiredOption("--dir <path>", "Path to the workspace directory").action(async (opts) => {
28914
29122
  const raw = String(opts.dir ?? "").trim();
28915
29123
  if (!raw) jsonError("A non-empty --dir is required");
28916
- const dir = resolve5(raw);
28917
- if (!existsSync18(dir)) jsonError(`Workspace path does not exist: ${dir}`);
29124
+ const dir = resolve6(raw);
29125
+ if (!existsSync20(dir)) jsonError(`Workspace path does not exist: ${dir}`);
28918
29126
  const skillLinks = wireSkills(dir, parseSetupAgents(), { quiet: true });
28919
29127
  await withDb2(async (db) => {
28920
29128
  const workspace = await activateWorkspacePath(db, dir);
@@ -28977,7 +29185,7 @@ bridgeCommand.command("agent-open").description("Launch an agent harness in the
28977
29185
  jsonError(`Workspace is not configured: ${opts.workspace}`);
28978
29186
  }
28979
29187
  const activeWorkspace = await ensureActiveWorkspace(db);
28980
- const workspace = opts.dir ? existsSync18(opts.dir) ? opts.dir : homedir14() : existingWorkspaceDirOrHome(configuredWorkspace ?? activeWorkspace);
29188
+ const workspace = opts.dir ? existsSync20(opts.dir) ? opts.dir : homedir15() : existingWorkspaceDirOrHome(configuredWorkspace ?? activeWorkspace);
28981
29189
  launchHarness(harness, {
28982
29190
  executable,
28983
29191
  workspace,
@@ -29349,7 +29557,7 @@ bridgeCommand.command("discover-skills").description(
29349
29557
  "20"
29350
29558
  ).action(async (opts) => {
29351
29559
  try {
29352
- const monitorDir = join22(homedir14(), ".zam", "monitor");
29560
+ const monitorDir = join23(homedir15(), ".zam", "monitor");
29353
29561
  let files;
29354
29562
  try {
29355
29563
  files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
@@ -29362,7 +29570,7 @@ bridgeCommand.command("discover-skills").description(
29362
29570
  return;
29363
29571
  }
29364
29572
  const limit = Number(opts.limit);
29365
- const sorted = files.map((f) => ({ name: f, path: join22(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
29573
+ const sorted = files.map((f) => ({ name: f, path: join23(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
29366
29574
  const sessionCommands = /* @__PURE__ */ new Map();
29367
29575
  for (const file of sorted) {
29368
29576
  const sessionId = file.name.replace(".jsonl", "");
@@ -29474,7 +29682,7 @@ bridgeCommand.command("observe-ui-snapshot").description(
29474
29682
  });
29475
29683
  function resolveWindowsPowerShell() {
29476
29684
  try {
29477
- execFileSync4("where.exe", ["pwsh.exe"], { stdio: "ignore" });
29685
+ execFileSync5("where.exe", ["pwsh.exe"], { stdio: "ignore" });
29478
29686
  return "pwsh";
29479
29687
  } catch {
29480
29688
  return "powershell";
@@ -29489,7 +29697,7 @@ function captureScreenshot(outputPath, hwnd, processName) {
29489
29697
  throw new Error(`Invalid process name format: ${processName}`);
29490
29698
  }
29491
29699
  if (platform === "win32") {
29492
- const stdout = execFileSync4(
29700
+ const stdout = execFileSync5(
29493
29701
  resolveWindowsPowerShell(),
29494
29702
  [
29495
29703
  "-NoProfile",
@@ -29802,13 +30010,13 @@ Write-CaptureResult "fullscreen" $hwndVal $matchedBy
29802
30010
  } else if (platform === "darwin") {
29803
30011
  if (hwnd) {
29804
30012
  const parsedHwnd = hwnd.startsWith("0x") ? parseInt(hwnd, 16) : parseInt(hwnd, 10);
29805
- execFileSync4("screencapture", ["-l", String(parsedHwnd), outputPath], {
30013
+ execFileSync5("screencapture", ["-l", String(parsedHwnd), outputPath], {
29806
30014
  stdio: "pipe"
29807
30015
  });
29808
30016
  return { method: "screencapture-window", target: null };
29809
30017
  } else if (processName) {
29810
30018
  try {
29811
- const windowId = execFileSync4(
30019
+ const windowId = execFileSync5(
29812
30020
  "osascript",
29813
30021
  [
29814
30022
  "-e",
@@ -29817,17 +30025,17 @@ Write-CaptureResult "fullscreen" $hwndVal $matchedBy
29817
30025
  { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }
29818
30026
  ).trim();
29819
30027
  if (windowId && /^\d+$/.test(windowId)) {
29820
- execFileSync4("screencapture", ["-l", windowId, outputPath], {
30028
+ execFileSync5("screencapture", ["-l", windowId, outputPath], {
29821
30029
  stdio: "pipe"
29822
30030
  });
29823
30031
  return { method: "screencapture-window", target: null };
29824
30032
  }
29825
30033
  } catch {
29826
30034
  }
29827
- execFileSync4("screencapture", ["-x", outputPath], { stdio: "pipe" });
30035
+ execFileSync5("screencapture", ["-x", outputPath], { stdio: "pipe" });
29828
30036
  return { method: "screencapture-full", target: null };
29829
30037
  } else {
29830
- execFileSync4("screencapture", ["-x", outputPath], { stdio: "pipe" });
30038
+ execFileSync5("screencapture", ["-x", outputPath], { stdio: "pipe" });
29831
30039
  return { method: "screencapture-full", target: null };
29832
30040
  }
29833
30041
  } else {
@@ -29864,7 +30072,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
29864
30072
  return;
29865
30073
  }
29866
30074
  }
29867
- const outputPath = opts.image ?? opts.output ?? join22(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
30075
+ const outputPath = opts.image ?? opts.output ?? join23(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
29868
30076
  const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
29869
30077
  if (!isProvided) {
29870
30078
  const post = decidePostCapture(policy, {
@@ -29892,7 +30100,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
29892
30100
  return;
29893
30101
  }
29894
30102
  }
29895
- const imageBytes = readFileSync16(outputPath);
30103
+ const imageBytes = readFileSync17(outputPath);
29896
30104
  const base64 = imageBytes.toString("base64");
29897
30105
  jsonOut({
29898
30106
  sessionId: opts.session ?? null,
@@ -29919,11 +30127,11 @@ bridgeCommand.command("start-recording").description("Start screen recording in
29919
30127
  return;
29920
30128
  }
29921
30129
  const sessionId = opts.session;
29922
- const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
30130
+ const statePath = join23(tmpdir3(), `zam-recording-${sessionId}.json`);
29923
30131
  const defaultExt = platform === "win32" ? ".mkv" : ".mov";
29924
- const outputPath = opts.output ?? join22(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
29925
- const { existsSync: existsSync20, writeFileSync: writeFileSync12, openSync, closeSync } = await import("fs");
29926
- if (existsSync20(statePath)) {
30132
+ const outputPath = opts.output ?? join23(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
30133
+ const { existsSync: existsSync22, writeFileSync: writeFileSync13, openSync, closeSync } = await import("fs");
30134
+ if (existsSync22(statePath)) {
29927
30135
  jsonOut({
29928
30136
  sessionId,
29929
30137
  started: false,
@@ -29931,7 +30139,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
29931
30139
  });
29932
30140
  return;
29933
30141
  }
29934
- const logPath = join22(tmpdir3(), `zam-recording-${sessionId}.log`);
30142
+ const logPath = join23(tmpdir3(), `zam-recording-${sessionId}.log`);
29935
30143
  let logFd;
29936
30144
  try {
29937
30145
  logFd = openSync(logPath, "w");
@@ -29977,7 +30185,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
29977
30185
  }
29978
30186
  child.unref();
29979
30187
  if (child.pid) {
29980
- writeFileSync12(
30188
+ writeFileSync13(
29981
30189
  statePath,
29982
30190
  JSON.stringify({
29983
30191
  pid: child.pid,
@@ -30013,9 +30221,9 @@ bridgeCommand.command("stop-recording").description(
30013
30221
  return;
30014
30222
  }
30015
30223
  const sessionId = opts.session;
30016
- const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
30017
- const { existsSync: existsSync20, readFileSync: readFileSync18, rmSync: rmSync4 } = await import("fs");
30018
- if (!existsSync20(statePath)) {
30224
+ const statePath = join23(tmpdir3(), `zam-recording-${sessionId}.json`);
30225
+ const { existsSync: existsSync22, readFileSync: readFileSync19, rmSync: rmSync4 } = await import("fs");
30226
+ if (!existsSync22(statePath)) {
30019
30227
  jsonOut({
30020
30228
  sessionId,
30021
30229
  stopped: false,
@@ -30023,7 +30231,7 @@ bridgeCommand.command("stop-recording").description(
30023
30231
  });
30024
30232
  return;
30025
30233
  }
30026
- const state = JSON.parse(readFileSync18(statePath, "utf8"));
30234
+ const state = JSON.parse(readFileSync19(statePath, "utf8"));
30027
30235
  const { pid, outputPath } = state;
30028
30236
  try {
30029
30237
  process.kill(pid, "SIGINT");
@@ -30039,7 +30247,7 @@ bridgeCommand.command("stop-recording").description(
30039
30247
  };
30040
30248
  let attempts = 0;
30041
30249
  while (isProcessRunning(pid) && attempts < 20) {
30042
- await new Promise((resolve6) => setTimeout(resolve6, 250));
30250
+ await new Promise((resolve7) => setTimeout(resolve7, 250));
30043
30251
  attempts++;
30044
30252
  }
30045
30253
  if (isProcessRunning(pid)) {
@@ -30052,7 +30260,7 @@ bridgeCommand.command("stop-recording").description(
30052
30260
  rmSync4(statePath, { force: true });
30053
30261
  } catch {
30054
30262
  }
30055
- if (!existsSync20(outputPath)) {
30263
+ if (!existsSync22(outputPath)) {
30056
30264
  jsonOut({
30057
30265
  sessionId,
30058
30266
  stopped: false,
@@ -30482,7 +30690,7 @@ bridgeCommand.command("cloud-model-hint").description("Suggest a cloud model for
30482
30690
  });
30483
30691
  bridgeCommand.command("local-llm-hints").description("Detect installed local LLM servers and suggest defaults (JSON)").action(() => {
30484
30692
  const profile = getSystemProfile();
30485
- const flmInstalled = hasCommand("flm") || existsSync18("C:\\Program Files\\flm\\flm.exe");
30693
+ const flmInstalled = hasCommand("flm") || existsSync20("C:\\Program Files\\flm\\flm.exe");
30486
30694
  const ollamaInstalled = isOllamaInstalled();
30487
30695
  const runners = [
30488
30696
  { id: "flm", label: "FastFlowLM", installed: flmInstalled },
@@ -30714,13 +30922,15 @@ bridgeCommand.command("desktop-bootstrap").description("Initialize first-run des
30714
30922
  const userId = await ensureDefaultUser(db, opts.user);
30715
30923
  const { enabled, url, model, locale } = await getLlmConfig(db);
30716
30924
  const { workspaceDir, activeWorkspaceId, skillLinks } = await ensureDesktopWorkspace(db);
30925
+ const cli = installCliShim();
30717
30926
  jsonOut({
30718
30927
  userId,
30719
30928
  locale,
30720
30929
  llm: { enabled, url, model },
30721
30930
  activeWorkspaceId,
30722
30931
  workspaceDir,
30723
- skillLinks
30932
+ skillLinks,
30933
+ cli
30724
30934
  });
30725
30935
  });
30726
30936
  });
@@ -30737,6 +30947,22 @@ bridgeCommand.command("get-settings").description("Get active ZAM settings (JSON
30737
30947
  });
30738
30948
  });
30739
30949
  });
30950
+ bridgeCommand.command("install-repair").description(
30951
+ "Verify and repair this machine's ZAM installation: CLI shim + PATH, workspace skill links, agent configs and companion extensions (JSON)"
30952
+ ).option(
30953
+ "--if-version-changed",
30954
+ "Only run when the app version differs from the last repaired one; reports skipped:true otherwise"
30955
+ ).action((opts) => {
30956
+ try {
30957
+ jsonOut(
30958
+ performInstallRepair({
30959
+ ifVersionChanged: Boolean(opts.ifVersionChanged)
30960
+ })
30961
+ );
30962
+ } catch (err) {
30963
+ jsonError(err.message);
30964
+ }
30965
+ });
30740
30966
  bridgeCommand.command("agent-harness-status").description(
30741
30967
  "Detect installed agent harnesses and their ZAM MCP configuration state (JSON)"
30742
30968
  ).action(() => {
@@ -32393,11 +32619,11 @@ bridgeCommand.command("serve").description("Start the persistent JSON-RPC stdin/
32393
32619
 
32394
32620
  // src/cli/commands/mcp.ts
32395
32621
  var __dirname = dirname11(fileURLToPath6(import.meta.url));
32396
- var pkgPath = join23(__dirname, "..", "..", "package.json");
32397
- if (!existsSync19(pkgPath)) {
32398
- pkgPath = join23(__dirname, "..", "..", "..", "package.json");
32622
+ var pkgPath = join24(__dirname, "..", "..", "package.json");
32623
+ if (!existsSync21(pkgPath)) {
32624
+ pkgPath = join24(__dirname, "..", "..", "..", "package.json");
32399
32625
  }
32400
- var pkg = JSON.parse(readFileSync17(pkgPath, "utf-8"));
32626
+ var pkg = JSON.parse(readFileSync18(pkgPath, "utf-8"));
32401
32627
  var STUDIO_RESOURCE_URI = "ui://zam/studio";
32402
32628
  var RECALL_RESOURCE_URI = "ui://zam/recall";
32403
32629
  var GRAPH_RESOURCE_URI = "ui://zam/graph";
@@ -32422,13 +32648,13 @@ var STUDIO_BRIDGE_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
32422
32648
  function loadPanelHtml(fileName, placeholderTitle) {
32423
32649
  const candidates = [
32424
32650
  // dist/cli/commands/mcp.js → dist/ui/
32425
- join23(__dirname, "..", "..", "ui", fileName),
32651
+ join24(__dirname, "..", "..", "ui", fileName),
32426
32652
  // src/cli/commands/mcp.ts via tsx → <repo>/dist/ui/
32427
- join23(__dirname, "..", "..", "..", "dist", "ui", fileName)
32653
+ join24(__dirname, "..", "..", "..", "dist", "ui", fileName)
32428
32654
  ];
32429
32655
  for (const candidate of candidates) {
32430
- if (existsSync19(candidate)) {
32431
- return readFileSync17(candidate, "utf-8");
32656
+ if (existsSync21(candidate)) {
32657
+ return readFileSync18(candidate, "utf-8");
32432
32658
  }
32433
32659
  }
32434
32660
  return `<!doctype html>