zam-core 0.10.8 → 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.8"
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);
@@ -11041,6 +11052,127 @@ function inspectConnectHarnesses(deps = {}) {
11041
11052
  return { zamOnPath: Boolean(foundZam), harnesses };
11042
11053
  }
11043
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
+
11044
11176
  // src/cli/curriculum/breadcrumb.ts
11045
11177
  init_kernel();
11046
11178
  var LAST_SELECTION_KEY = "curriculum.lastSelection";
@@ -27727,6 +27859,297 @@ function getCurriculumProvider(id) {
27727
27859
  return CURRICULUM_PROVIDERS.find((provider) => provider.id === id);
27728
27860
  }
27729
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
+
27730
28153
  // src/cli/llm/capability-probe.ts
27731
28154
  init_kernel();
27732
28155
  var EMBEDDING_MODEL_HINTS = [
@@ -27851,9 +28274,9 @@ function validateModelSave(entry, probe, now = () => (/* @__PURE__ */ new Date()
27851
28274
  // src/cli/llm/vision.ts
27852
28275
  init_kernel();
27853
28276
  import { randomBytes } from "crypto";
27854
- import { readFileSync as readFileSync14 } from "fs";
28277
+ import { readFileSync as readFileSync16 } from "fs";
27855
28278
  import { tmpdir as tmpdir2 } from "os";
27856
- import { basename as basename4, join as join19 } from "path";
28279
+ import { basename as basename5, join as join21 } from "path";
27857
28280
  var LANGUAGE_NAMES2 = {
27858
28281
  en: "English",
27859
28282
  de: "German",
@@ -27889,13 +28312,13 @@ async function observeUiSnapshotViaLLM(db, input) {
27889
28312
  const imageUrls = [];
27890
28313
  const isVideo = /\.(mp4|mov|m4v|avi|mkv|webm)$/i.test(input.imagePath);
27891
28314
  if (isVideo) {
27892
- const { mkdirSync: mkdirSync15, readdirSync: readdirSync3, rmSync: rmSync4 } = await import("fs");
28315
+ const { mkdirSync: mkdirSync16, readdirSync: readdirSync3, rmSync: rmSync4 } = await import("fs");
27893
28316
  const { execSync: execSync5 } = await import("child_process");
27894
- const tempDir = join19(
28317
+ const tempDir = join21(
27895
28318
  tmpdir2(),
27896
28319
  `zam-frames-${randomBytes(4).toString("hex")}`
27897
28320
  );
27898
- mkdirSync15(tempDir, { recursive: true });
28321
+ mkdirSync16(tempDir, { recursive: true });
27899
28322
  try {
27900
28323
  execSync5(
27901
28324
  `ffmpeg -i "${input.imagePath}" -vf "fps=1/3,scale=1280:-1" -vsync vfr "${tempDir}/frame_%03d.png"`,
@@ -27924,7 +28347,7 @@ async function observeUiSnapshotViaLLM(db, input) {
27924
28347
  }
27925
28348
  }
27926
28349
  for (const file of sampledFiles) {
27927
- const bytes = readFileSync14(join19(tempDir, file));
28350
+ const bytes = readFileSync16(join21(tempDir, file));
27928
28351
  imageUrls.push(`data:image/png;base64,${bytes.toString("base64")}`);
27929
28352
  }
27930
28353
  } finally {
@@ -27934,7 +28357,7 @@ async function observeUiSnapshotViaLLM(db, input) {
27934
28357
  }
27935
28358
  }
27936
28359
  } else {
27937
- const imageBytes = readFileSync14(input.imagePath);
28360
+ const imageBytes = readFileSync16(input.imagePath);
27938
28361
  const ext = input.imagePath.split(".").pop()?.toLowerCase();
27939
28362
  const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
27940
28363
  imageUrls.push(`data:${mime};base64,${imageBytes.toString("base64")}`);
@@ -28172,7 +28595,7 @@ function buildReport(input, draft) {
28172
28595
  evidence: [
28173
28596
  {
28174
28597
  type: "keyframe",
28175
- ref: input.evidenceRef ?? basename4(input.imagePath),
28598
+ ref: input.evidenceRef ?? basename5(input.imagePath),
28176
28599
  redacted: input.redacted ?? false
28177
28600
  }
28178
28601
  ],
@@ -28194,7 +28617,7 @@ function uncertainReport(input, summary) {
28194
28617
  evidence: [
28195
28618
  {
28196
28619
  type: "keyframe",
28197
- ref: input.evidenceRef ?? basename4(input.imagePath),
28620
+ ref: input.evidenceRef ?? basename5(input.imagePath),
28198
28621
  redacted: input.redacted ?? false
28199
28622
  }
28200
28623
  ],
@@ -28404,219 +28827,6 @@ async function withProviderScope(machine, action) {
28404
28827
  await withDb(action);
28405
28828
  }
28406
28829
 
28407
- // src/cli/provisioning/index.ts
28408
- import {
28409
- existsSync as existsSync17,
28410
- lstatSync as lstatSync2,
28411
- mkdirSync as mkdirSync13,
28412
- readFileSync as readFileSync15,
28413
- realpathSync,
28414
- rmSync as rmSync2,
28415
- symlinkSync,
28416
- writeFileSync as writeFileSync11
28417
- } from "fs";
28418
- import { basename as basename5, dirname as dirname10, join as join20 } from "path";
28419
- import { fileURLToPath as fileURLToPath5 } from "url";
28420
- var packageRoot3 = [
28421
- fileURLToPath5(new URL("../..", import.meta.url)),
28422
- fileURLToPath5(new URL("../../..", import.meta.url))
28423
- ].find((candidate) => existsSync17(join20(candidate, "package.json"))) ?? fileURLToPath5(new URL("../..", import.meta.url));
28424
- var ALL_SETUP_AGENTS = ["claude", "copilot", "codex", "agent"];
28425
- var SKILL_PAIRS = [
28426
- {
28427
- from: join20(packageRoot3, ".claude", "skills", "zam", "SKILL.md"),
28428
- to: join20(".claude", "skills", "zam", "SKILL.md"),
28429
- agents: ["claude", "copilot"]
28430
- },
28431
- {
28432
- from: join20(packageRoot3, ".agent", "skills", "zam", "SKILL.md"),
28433
- to: join20(".agent", "skills", "zam", "SKILL.md"),
28434
- agents: ["agent"]
28435
- },
28436
- {
28437
- from: join20(packageRoot3, ".agents", "skills", "zam", "SKILL.md"),
28438
- to: join20(".agents", "skills", "zam", "SKILL.md"),
28439
- agents: ["codex"]
28440
- }
28441
- ];
28442
- function parseSetupAgents(value) {
28443
- if (!value || value.trim().toLowerCase() === "all") {
28444
- return new Set(ALL_SETUP_AGENTS);
28445
- }
28446
- const aliases = {
28447
- claude: ["claude"],
28448
- copilot: ["copilot"],
28449
- codex: ["codex"],
28450
- agent: ["agent"],
28451
- opencode: ["agent"]
28452
- };
28453
- const selected = /* @__PURE__ */ new Set();
28454
- for (const raw of value.split(",")) {
28455
- const key = raw.trim().toLowerCase();
28456
- const mapped = aliases[key];
28457
- if (!mapped) {
28458
- throw new Error(
28459
- `Unknown agent "${raw}". Use: all, claude, copilot, codex, agent.`
28460
- );
28461
- }
28462
- for (const item of mapped) selected.add(item);
28463
- }
28464
- return selected;
28465
- }
28466
- function pathExists(path) {
28467
- try {
28468
- lstatSync2(path);
28469
- return true;
28470
- } catch {
28471
- return false;
28472
- }
28473
- }
28474
- function isSymbolicLink(path) {
28475
- try {
28476
- return lstatSync2(path).isSymbolicLink();
28477
- } catch {
28478
- return false;
28479
- }
28480
- }
28481
- function comparableRealPath(path) {
28482
- const real = realpathSync(path);
28483
- return process.platform === "win32" ? real.toLowerCase() : real;
28484
- }
28485
- function pathsResolveToSameDirectory(sourceDir, destinationDir) {
28486
- try {
28487
- return comparableRealPath(destinationDir) === comparableRealPath(sourceDir);
28488
- } catch {
28489
- return false;
28490
- }
28491
- }
28492
- function linkPointsTo(sourceDir, destinationDir) {
28493
- return isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir);
28494
- }
28495
- function isZamSkillCopy(destinationDir) {
28496
- try {
28497
- if (!lstatSync2(destinationDir).isDirectory()) return false;
28498
- const skillFile = join20(destinationDir, "SKILL.md");
28499
- if (!existsSync17(skillFile)) return false;
28500
- return /^name:\s*zam\s*$/m.test(readFileSync15(skillFile, "utf8"));
28501
- } catch {
28502
- return false;
28503
- }
28504
- }
28505
- function classifySkillDestination(sourceDir, destinationDir) {
28506
- if (!existsSync17(sourceDir)) return "source-missing";
28507
- if (!isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir)) {
28508
- return "source-directory";
28509
- }
28510
- if (linkPointsTo(sourceDir, destinationDir)) return "linked";
28511
- if (!pathExists(destinationDir)) return "missing";
28512
- if (isSymbolicLink(destinationDir)) return "broken";
28513
- if (isZamSkillCopy(destinationDir)) return "stale-copy";
28514
- return "unmanaged";
28515
- }
28516
- function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {}) {
28517
- const results = [];
28518
- const log = (message) => {
28519
- if (!opts.quiet) console.log(message);
28520
- };
28521
- for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
28522
- if (!pairAgents.some((agent) => agents.has(agent))) continue;
28523
- const sourceDir = dirname10(from);
28524
- const destinationDir = dirname10(join20(cwd, to));
28525
- const label = dirname10(to);
28526
- const state = classifySkillDestination(sourceDir, destinationDir);
28527
- if (state === "source-missing") {
28528
- if (!opts.quiet) {
28529
- console.warn(` warn source not found, skipping: ${sourceDir}`);
28530
- }
28531
- results.push({
28532
- source: sourceDir,
28533
- destination: destinationDir,
28534
- action: "skipped",
28535
- reason: "source-not-found"
28536
- });
28537
- continue;
28538
- }
28539
- if (state === "source-directory") {
28540
- log(` skip ${label} (package source)`);
28541
- results.push({
28542
- source: sourceDir,
28543
- destination: destinationDir,
28544
- action: "skipped",
28545
- reason: "source-directory"
28546
- });
28547
- continue;
28548
- }
28549
- if (state === "linked") {
28550
- log(` skip ${label} (already linked)`);
28551
- results.push({
28552
- source: sourceDir,
28553
- destination: destinationDir,
28554
- action: "skipped",
28555
- reason: "already-linked"
28556
- });
28557
- continue;
28558
- }
28559
- const destinationExists = state !== "missing";
28560
- const replaceExisting = destinationExists && (Boolean(opts.force) || state === "broken" || state === "stale-copy");
28561
- if (destinationExists && !replaceExisting) {
28562
- if (!opts.quiet) {
28563
- console.warn(
28564
- ` warn ${label} already exists and is not managed by ZAM; use --force to replace it`
28565
- );
28566
- }
28567
- results.push({
28568
- source: sourceDir,
28569
- destination: destinationDir,
28570
- action: "skipped",
28571
- reason: "unmanaged-destination"
28572
- });
28573
- continue;
28574
- }
28575
- const action = destinationExists ? "relinked" : "linked";
28576
- if (opts.dryRun) {
28577
- log(` would ${action === "linked" ? "link" : "relink"} ${label}`);
28578
- } else {
28579
- if (destinationExists) {
28580
- rmSync2(destinationDir, { recursive: true, force: true });
28581
- }
28582
- mkdirSync13(dirname10(destinationDir), { recursive: true });
28583
- symlinkSync(
28584
- sourceDir,
28585
- destinationDir,
28586
- process.platform === "win32" ? "junction" : "dir"
28587
- );
28588
- log(` ${action === "linked" ? "link" : "relink"} ${label}`);
28589
- }
28590
- results.push({ source: sourceDir, destination: destinationDir, action });
28591
- }
28592
- return results;
28593
- }
28594
- function inspectSkillLinks(cwd = process.cwd(), agents = parseSetupAgents()) {
28595
- const results = [];
28596
- for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
28597
- if (!pairAgents.some((agent) => agents.has(agent))) continue;
28598
- const sourceDir = dirname10(from);
28599
- const destinationDir = dirname10(join20(cwd, to));
28600
- results.push({
28601
- agents: pairAgents,
28602
- source: sourceDir,
28603
- destination: destinationDir,
28604
- state: classifySkillDestination(sourceDir, destinationDir)
28605
- });
28606
- }
28607
- return results;
28608
- }
28609
- function summarizeSkillLinkHealth(inspections) {
28610
- if (inspections.some((item) => item.state === "unmanaged")) {
28611
- return "unmanaged";
28612
- }
28613
- const repairable = ["missing", "broken", "stale-copy"];
28614
- if (inspections.some((item) => repairable.includes(item.state))) {
28615
- return "needs-repair";
28616
- }
28617
- return "healthy";
28618
- }
28619
-
28620
28830
  // src/cli/users/identity.ts
28621
28831
  init_kernel();
28622
28832
  async function ensureDefaultUser(db, preferredUserId) {
@@ -28640,13 +28850,13 @@ async function resolveUser(opts, db, resolveOpts) {
28640
28850
  }
28641
28851
 
28642
28852
  // src/cli/workspaces/backup.ts
28643
- import { mkdirSync as mkdirSync14 } from "fs";
28644
- import { join as join21 } from "path";
28853
+ import { mkdirSync as mkdirSync15 } from "fs";
28854
+ import { join as join22 } from "path";
28645
28855
  async function backupDatabaseTo(db, targetDir) {
28646
- const backupDir = join21(targetDir, "zam-backups");
28647
- mkdirSync14(backupDir, { recursive: true });
28856
+ const backupDir = join22(targetDir, "zam-backups");
28857
+ mkdirSync15(backupDir, { recursive: true });
28648
28858
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
28649
- const dest = join21(backupDir, `zam-${stamp}.db`);
28859
+ const dest = join22(backupDir, `zam-${stamp}.db`);
28650
28860
  await db.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
28651
28861
  return dest;
28652
28862
  }
@@ -28777,20 +28987,20 @@ bridgeCommand.command("workspace-info").description("Report the workspace dir, i
28777
28987
  activeWorkspace,
28778
28988
  workspaceDir: activeWorkspace.path,
28779
28989
  defaultWorkspaceDir: defaultWorkspaceDir(),
28780
- dataDir: join22(homedir14(), ".zam")
28990
+ dataDir: join23(homedir15(), ".zam")
28781
28991
  });
28782
28992
  });
28783
28993
  });
28784
28994
  function provisionConfiguredWorkspaces() {
28785
28995
  return getConfiguredWorkspaces().flatMap(
28786
- (workspace) => existsSync18(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
28996
+ (workspace) => existsSync20(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
28787
28997
  );
28788
28998
  }
28789
28999
  function buildWorkspaceLinkHealth(workspaces) {
28790
29000
  const agents = parseSetupAgents();
28791
29001
  const map = {};
28792
29002
  for (const workspace of workspaces) {
28793
- if (!existsSync18(workspace.path)) continue;
29003
+ if (!existsSync20(workspace.path)) continue;
28794
29004
  const links = inspectSkillLinks(workspace.path, agents);
28795
29005
  map[workspace.id] = {
28796
29006
  health: summarizeSkillLinkHealth(links),
@@ -28820,7 +29030,7 @@ bridgeCommand.command("workspace-list").description("List configured ZAM workspa
28820
29030
  activeWorkspace,
28821
29031
  workspaceDir: activeWorkspace.path,
28822
29032
  defaultWorkspaceDir: defaultWorkspaceDir(),
28823
- dataDir: join22(homedir14(), ".zam"),
29033
+ dataDir: join23(homedir15(), ".zam"),
28824
29034
  linkHealth: buildWorkspaceLinkHealth(workspaces)
28825
29035
  });
28826
29036
  });
@@ -28835,7 +29045,7 @@ bridgeCommand.command("workspace-repair-links").description(
28835
29045
  if (!id) jsonError("A non-empty --id is required");
28836
29046
  const workspace = getConfiguredWorkspaces().find((item) => item.id === id);
28837
29047
  if (!workspace) jsonError(`Workspace "${id}" is not configured`);
28838
- if (!existsSync18(workspace.path)) {
29048
+ if (!existsSync20(workspace.path)) {
28839
29049
  jsonError(`Workspace path does not exist: ${workspace.path}`);
28840
29050
  }
28841
29051
  const agents = parseSetupAgents(opts.agents);
@@ -28866,8 +29076,8 @@ function parseBridgeWorkspaceKind(value) {
28866
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) => {
28867
29077
  const raw = String(opts.path ?? "").trim();
28868
29078
  if (!raw) jsonError("A non-empty --path is required");
28869
- const path = resolve5(raw);
28870
- 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}`);
28871
29081
  const id = opts.id ? String(opts.id).trim() : void 0;
28872
29082
  if (opts.id && !id) jsonError("A non-empty --id is required");
28873
29083
  const kind = parseBridgeWorkspaceKind(opts.kind);
@@ -28911,8 +29121,8 @@ bridgeCommand.command("workspace-remove").description("Unregister a ZAM workspac
28911
29121
  bridgeCommand.command("set-workspace-dir").description("Set the personal workspace directory (JSON)").requiredOption("--dir <path>", "Path to the workspace directory").action(async (opts) => {
28912
29122
  const raw = String(opts.dir ?? "").trim();
28913
29123
  if (!raw) jsonError("A non-empty --dir is required");
28914
- const dir = resolve5(raw);
28915
- 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}`);
28916
29126
  const skillLinks = wireSkills(dir, parseSetupAgents(), { quiet: true });
28917
29127
  await withDb2(async (db) => {
28918
29128
  const workspace = await activateWorkspacePath(db, dir);
@@ -28975,7 +29185,7 @@ bridgeCommand.command("agent-open").description("Launch an agent harness in the
28975
29185
  jsonError(`Workspace is not configured: ${opts.workspace}`);
28976
29186
  }
28977
29187
  const activeWorkspace = await ensureActiveWorkspace(db);
28978
- 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);
28979
29189
  launchHarness(harness, {
28980
29190
  executable,
28981
29191
  workspace,
@@ -29347,7 +29557,7 @@ bridgeCommand.command("discover-skills").description(
29347
29557
  "20"
29348
29558
  ).action(async (opts) => {
29349
29559
  try {
29350
- const monitorDir = join22(homedir14(), ".zam", "monitor");
29560
+ const monitorDir = join23(homedir15(), ".zam", "monitor");
29351
29561
  let files;
29352
29562
  try {
29353
29563
  files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
@@ -29360,7 +29570,7 @@ bridgeCommand.command("discover-skills").description(
29360
29570
  return;
29361
29571
  }
29362
29572
  const limit = Number(opts.limit);
29363
- 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);
29364
29574
  const sessionCommands = /* @__PURE__ */ new Map();
29365
29575
  for (const file of sorted) {
29366
29576
  const sessionId = file.name.replace(".jsonl", "");
@@ -29472,7 +29682,7 @@ bridgeCommand.command("observe-ui-snapshot").description(
29472
29682
  });
29473
29683
  function resolveWindowsPowerShell() {
29474
29684
  try {
29475
- execFileSync4("where.exe", ["pwsh.exe"], { stdio: "ignore" });
29685
+ execFileSync5("where.exe", ["pwsh.exe"], { stdio: "ignore" });
29476
29686
  return "pwsh";
29477
29687
  } catch {
29478
29688
  return "powershell";
@@ -29487,7 +29697,7 @@ function captureScreenshot(outputPath, hwnd, processName) {
29487
29697
  throw new Error(`Invalid process name format: ${processName}`);
29488
29698
  }
29489
29699
  if (platform === "win32") {
29490
- const stdout = execFileSync4(
29700
+ const stdout = execFileSync5(
29491
29701
  resolveWindowsPowerShell(),
29492
29702
  [
29493
29703
  "-NoProfile",
@@ -29800,13 +30010,13 @@ Write-CaptureResult "fullscreen" $hwndVal $matchedBy
29800
30010
  } else if (platform === "darwin") {
29801
30011
  if (hwnd) {
29802
30012
  const parsedHwnd = hwnd.startsWith("0x") ? parseInt(hwnd, 16) : parseInt(hwnd, 10);
29803
- execFileSync4("screencapture", ["-l", String(parsedHwnd), outputPath], {
30013
+ execFileSync5("screencapture", ["-l", String(parsedHwnd), outputPath], {
29804
30014
  stdio: "pipe"
29805
30015
  });
29806
30016
  return { method: "screencapture-window", target: null };
29807
30017
  } else if (processName) {
29808
30018
  try {
29809
- const windowId = execFileSync4(
30019
+ const windowId = execFileSync5(
29810
30020
  "osascript",
29811
30021
  [
29812
30022
  "-e",
@@ -29815,17 +30025,17 @@ Write-CaptureResult "fullscreen" $hwndVal $matchedBy
29815
30025
  { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }
29816
30026
  ).trim();
29817
30027
  if (windowId && /^\d+$/.test(windowId)) {
29818
- execFileSync4("screencapture", ["-l", windowId, outputPath], {
30028
+ execFileSync5("screencapture", ["-l", windowId, outputPath], {
29819
30029
  stdio: "pipe"
29820
30030
  });
29821
30031
  return { method: "screencapture-window", target: null };
29822
30032
  }
29823
30033
  } catch {
29824
30034
  }
29825
- execFileSync4("screencapture", ["-x", outputPath], { stdio: "pipe" });
30035
+ execFileSync5("screencapture", ["-x", outputPath], { stdio: "pipe" });
29826
30036
  return { method: "screencapture-full", target: null };
29827
30037
  } else {
29828
- execFileSync4("screencapture", ["-x", outputPath], { stdio: "pipe" });
30038
+ execFileSync5("screencapture", ["-x", outputPath], { stdio: "pipe" });
29829
30039
  return { method: "screencapture-full", target: null };
29830
30040
  }
29831
30041
  } else {
@@ -29862,7 +30072,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
29862
30072
  return;
29863
30073
  }
29864
30074
  }
29865
- 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`);
29866
30076
  const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
29867
30077
  if (!isProvided) {
29868
30078
  const post = decidePostCapture(policy, {
@@ -29890,7 +30100,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
29890
30100
  return;
29891
30101
  }
29892
30102
  }
29893
- const imageBytes = readFileSync16(outputPath);
30103
+ const imageBytes = readFileSync17(outputPath);
29894
30104
  const base64 = imageBytes.toString("base64");
29895
30105
  jsonOut({
29896
30106
  sessionId: opts.session ?? null,
@@ -29917,11 +30127,11 @@ bridgeCommand.command("start-recording").description("Start screen recording in
29917
30127
  return;
29918
30128
  }
29919
30129
  const sessionId = opts.session;
29920
- const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
30130
+ const statePath = join23(tmpdir3(), `zam-recording-${sessionId}.json`);
29921
30131
  const defaultExt = platform === "win32" ? ".mkv" : ".mov";
29922
- const outputPath = opts.output ?? join22(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
29923
- const { existsSync: existsSync20, writeFileSync: writeFileSync12, openSync, closeSync } = await import("fs");
29924
- 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)) {
29925
30135
  jsonOut({
29926
30136
  sessionId,
29927
30137
  started: false,
@@ -29929,7 +30139,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
29929
30139
  });
29930
30140
  return;
29931
30141
  }
29932
- const logPath = join22(tmpdir3(), `zam-recording-${sessionId}.log`);
30142
+ const logPath = join23(tmpdir3(), `zam-recording-${sessionId}.log`);
29933
30143
  let logFd;
29934
30144
  try {
29935
30145
  logFd = openSync(logPath, "w");
@@ -29975,7 +30185,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
29975
30185
  }
29976
30186
  child.unref();
29977
30187
  if (child.pid) {
29978
- writeFileSync12(
30188
+ writeFileSync13(
29979
30189
  statePath,
29980
30190
  JSON.stringify({
29981
30191
  pid: child.pid,
@@ -30011,9 +30221,9 @@ bridgeCommand.command("stop-recording").description(
30011
30221
  return;
30012
30222
  }
30013
30223
  const sessionId = opts.session;
30014
- const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
30015
- const { existsSync: existsSync20, readFileSync: readFileSync18, rmSync: rmSync4 } = await import("fs");
30016
- 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)) {
30017
30227
  jsonOut({
30018
30228
  sessionId,
30019
30229
  stopped: false,
@@ -30021,7 +30231,7 @@ bridgeCommand.command("stop-recording").description(
30021
30231
  });
30022
30232
  return;
30023
30233
  }
30024
- const state = JSON.parse(readFileSync18(statePath, "utf8"));
30234
+ const state = JSON.parse(readFileSync19(statePath, "utf8"));
30025
30235
  const { pid, outputPath } = state;
30026
30236
  try {
30027
30237
  process.kill(pid, "SIGINT");
@@ -30037,7 +30247,7 @@ bridgeCommand.command("stop-recording").description(
30037
30247
  };
30038
30248
  let attempts = 0;
30039
30249
  while (isProcessRunning(pid) && attempts < 20) {
30040
- await new Promise((resolve6) => setTimeout(resolve6, 250));
30250
+ await new Promise((resolve7) => setTimeout(resolve7, 250));
30041
30251
  attempts++;
30042
30252
  }
30043
30253
  if (isProcessRunning(pid)) {
@@ -30050,7 +30260,7 @@ bridgeCommand.command("stop-recording").description(
30050
30260
  rmSync4(statePath, { force: true });
30051
30261
  } catch {
30052
30262
  }
30053
- if (!existsSync20(outputPath)) {
30263
+ if (!existsSync22(outputPath)) {
30054
30264
  jsonOut({
30055
30265
  sessionId,
30056
30266
  stopped: false,
@@ -30480,7 +30690,7 @@ bridgeCommand.command("cloud-model-hint").description("Suggest a cloud model for
30480
30690
  });
30481
30691
  bridgeCommand.command("local-llm-hints").description("Detect installed local LLM servers and suggest defaults (JSON)").action(() => {
30482
30692
  const profile = getSystemProfile();
30483
- const flmInstalled = hasCommand("flm") || existsSync18("C:\\Program Files\\flm\\flm.exe");
30693
+ const flmInstalled = hasCommand("flm") || existsSync20("C:\\Program Files\\flm\\flm.exe");
30484
30694
  const ollamaInstalled = isOllamaInstalled();
30485
30695
  const runners = [
30486
30696
  { id: "flm", label: "FastFlowLM", installed: flmInstalled },
@@ -30712,13 +30922,15 @@ bridgeCommand.command("desktop-bootstrap").description("Initialize first-run des
30712
30922
  const userId = await ensureDefaultUser(db, opts.user);
30713
30923
  const { enabled, url, model, locale } = await getLlmConfig(db);
30714
30924
  const { workspaceDir, activeWorkspaceId, skillLinks } = await ensureDesktopWorkspace(db);
30925
+ const cli = installCliShim();
30715
30926
  jsonOut({
30716
30927
  userId,
30717
30928
  locale,
30718
30929
  llm: { enabled, url, model },
30719
30930
  activeWorkspaceId,
30720
30931
  workspaceDir,
30721
- skillLinks
30932
+ skillLinks,
30933
+ cli
30722
30934
  });
30723
30935
  });
30724
30936
  });
@@ -30735,6 +30947,22 @@ bridgeCommand.command("get-settings").description("Get active ZAM settings (JSON
30735
30947
  });
30736
30948
  });
30737
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
+ });
30738
30966
  bridgeCommand.command("agent-harness-status").description(
30739
30967
  "Detect installed agent harnesses and their ZAM MCP configuration state (JSON)"
30740
30968
  ).action(() => {
@@ -32391,11 +32619,11 @@ bridgeCommand.command("serve").description("Start the persistent JSON-RPC stdin/
32391
32619
 
32392
32620
  // src/cli/commands/mcp.ts
32393
32621
  var __dirname = dirname11(fileURLToPath6(import.meta.url));
32394
- var pkgPath = join23(__dirname, "..", "..", "package.json");
32395
- if (!existsSync19(pkgPath)) {
32396
- pkgPath = join23(__dirname, "..", "..", "..", "package.json");
32622
+ var pkgPath = join24(__dirname, "..", "..", "package.json");
32623
+ if (!existsSync21(pkgPath)) {
32624
+ pkgPath = join24(__dirname, "..", "..", "..", "package.json");
32397
32625
  }
32398
- var pkg = JSON.parse(readFileSync17(pkgPath, "utf-8"));
32626
+ var pkg = JSON.parse(readFileSync18(pkgPath, "utf-8"));
32399
32627
  var STUDIO_RESOURCE_URI = "ui://zam/studio";
32400
32628
  var RECALL_RESOURCE_URI = "ui://zam/recall";
32401
32629
  var GRAPH_RESOURCE_URI = "ui://zam/graph";
@@ -32420,13 +32648,13 @@ var STUDIO_BRIDGE_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
32420
32648
  function loadPanelHtml(fileName, placeholderTitle) {
32421
32649
  const candidates = [
32422
32650
  // dist/cli/commands/mcp.js → dist/ui/
32423
- join23(__dirname, "..", "..", "ui", fileName),
32651
+ join24(__dirname, "..", "..", "ui", fileName),
32424
32652
  // src/cli/commands/mcp.ts via tsx → <repo>/dist/ui/
32425
- join23(__dirname, "..", "..", "..", "dist", "ui", fileName)
32653
+ join24(__dirname, "..", "..", "..", "dist", "ui", fileName)
32426
32654
  ];
32427
32655
  for (const candidate of candidates) {
32428
- if (existsSync19(candidate)) {
32429
- return readFileSync17(candidate, "utf-8");
32656
+ if (existsSync21(candidate)) {
32657
+ return readFileSync18(candidate, "utf-8");
32430
32658
  }
32431
32659
  }
32432
32660
  return `<!doctype html>