zam-core 0.10.2 → 0.10.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/app.js CHANGED
@@ -5481,20 +5481,20 @@ import { homedir as homedir5 } from "os";
5481
5481
  import { join as join8 } from "path";
5482
5482
  import { fileURLToPath } from "url";
5483
5483
  function getPackageSkillPath(agent = "default") {
5484
- const packageRoot3 = [
5484
+ const packageRoot4 = [
5485
5485
  fileURLToPath(new URL("../../..", import.meta.url)),
5486
5486
  fileURLToPath(new URL("../..", import.meta.url)),
5487
5487
  fileURLToPath(new URL("..", import.meta.url))
5488
5488
  ].find((candidate) => existsSync7(join8(candidate, "package.json"))) ?? "";
5489
- if (!packageRoot3) return "";
5489
+ if (!packageRoot4) return "";
5490
5490
  if (agent === "codex") {
5491
- const codexPath = join8(packageRoot3, ".agents", "skills", "zam", "SKILL.md");
5491
+ const codexPath = join8(packageRoot4, ".agents", "skills", "zam", "SKILL.md");
5492
5492
  if (existsSync7(codexPath)) return codexPath;
5493
5493
  return "";
5494
5494
  }
5495
5495
  if (agent === "claude") {
5496
5496
  const claudePath = join8(
5497
- packageRoot3,
5497
+ packageRoot4,
5498
5498
  ".claude",
5499
5499
  "skills",
5500
5500
  "zam",
@@ -5503,9 +5503,9 @@ function getPackageSkillPath(agent = "default") {
5503
5503
  if (existsSync7(claudePath)) return claudePath;
5504
5504
  return "";
5505
5505
  }
5506
- let path = join8(packageRoot3, ".agent", "skills", "zam", "SKILL.md");
5506
+ let path = join8(packageRoot4, ".agent", "skills", "zam", "SKILL.md");
5507
5507
  if (existsSync7(path)) return path;
5508
- path = join8(packageRoot3, ".claude", "skills", "zam", "SKILL.md");
5508
+ path = join8(packageRoot4, ".claude", "skills", "zam", "SKILL.md");
5509
5509
  if (existsSync7(path)) return path;
5510
5510
  return "";
5511
5511
  }
@@ -5946,6 +5946,18 @@ function saveMachineAiConfig(ai, path = defaultConfigPath()) {
5946
5946
  config.ai = ai;
5947
5947
  saveInstallConfig(config, path);
5948
5948
  }
5949
+ function getAgentConnectAutoDone(path = defaultConfigPath()) {
5950
+ return loadInstallConfig(path).agent?.connectAutoDone === true;
5951
+ }
5952
+ function setAgentConnectAutoDone(done, path = defaultConfigPath()) {
5953
+ const config = loadInstallConfig(path);
5954
+ if (done) {
5955
+ config.agent = { ...config.agent ?? {}, connectAutoDone: true };
5956
+ } else if (config.agent) {
5957
+ delete config.agent.connectAutoDone;
5958
+ }
5959
+ saveInstallConfig(config, path);
5960
+ }
5949
5961
  function getConfiguredWorkspaces(path = defaultConfigPath()) {
5950
5962
  return loadInstallConfig(path).workspaces ?? [];
5951
5963
  }
@@ -6622,6 +6634,7 @@ __export(kernel_exports, {
6622
6634
  getActiveWorkspace: () => getActiveWorkspace,
6623
6635
  getActiveWorkspaceContext: () => getActiveWorkspaceContext,
6624
6636
  getActiveWorkspaceId: () => getActiveWorkspaceId,
6637
+ getAgentConnectAutoDone: () => getAgentConnectAutoDone,
6625
6638
  getAgentSkill: () => getAgentSkill,
6626
6639
  getAllSettings: () => getAllSettings,
6627
6640
  getAllSettingsDetailed: () => getAllSettingsDetailed,
@@ -6731,6 +6744,7 @@ __export(kernel_exports, {
6731
6744
  setADOCredentials: () => setADOCredentials,
6732
6745
  setActiveWorkspaceContext: () => setActiveWorkspaceContext,
6733
6746
  setActiveWorkspaceId: () => setActiveWorkspaceId,
6747
+ setAgentConnectAutoDone: () => setAgentConnectAutoDone,
6734
6748
  setInstallChannel: () => setInstallChannel,
6735
6749
  setInstallMode: () => setInstallMode,
6736
6750
  setProviderApiKey: () => setProviderApiKey,
@@ -6808,18 +6822,23 @@ var init_kernel = __esm({
6808
6822
  });
6809
6823
 
6810
6824
  // src/cli/app.ts
6811
- import { readFileSync as readFileSync19 } from "fs";
6812
- import { dirname as dirname13, join as join28 } from "path";
6813
- import { fileURLToPath as fileURLToPath7 } from "url";
6825
+ import { readFileSync as readFileSync20 } from "fs";
6826
+ import { dirname as dirname14, join as join29 } from "path";
6827
+ import { fileURLToPath as fileURLToPath8 } from "url";
6814
6828
  import { Command as Command27 } from "commander";
6815
6829
 
6816
6830
  // src/cli/commands/agent.ts
6817
6831
  init_kernel();
6818
- import { existsSync as existsSync14, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8 } from "fs";
6819
- import { homedir as homedir10 } from "os";
6820
- import { dirname as dirname6, join as join14 } from "path";
6832
+ import { existsSync as existsSync15 } from "fs";
6833
+ import { join as join15 } from "path";
6821
6834
  import { Command } from "commander";
6822
6835
 
6836
+ // src/cli/agent-connect.ts
6837
+ init_kernel();
6838
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
6839
+ import { homedir as homedir11 } from "os";
6840
+ import { dirname as dirname7 } from "path";
6841
+
6823
6842
  // src/cli/agent-harness.ts
6824
6843
  import { spawn } from "child_process";
6825
6844
  import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
@@ -7040,6 +7059,18 @@ function openTerminalWindow(opts) {
7040
7059
  }
7041
7060
 
7042
7061
  // src/cli/agent-harness.ts
7062
+ var ANTIGRAVITY_IDE_CANDIDATE_PATHS = {
7063
+ darwin: [
7064
+ join12(
7065
+ homedir8(),
7066
+ ".antigravity-ide",
7067
+ "antigravity-ide",
7068
+ "bin",
7069
+ "antigravity-ide"
7070
+ ),
7071
+ "/Applications/Antigravity IDE.app/Contents/Resources/app/bin/antigravity-ide"
7072
+ ]
7073
+ };
7043
7074
  var AGENT_HARNESSES = [
7044
7075
  { id: "claude-code", label: "Claude Code", kind: "cli", command: "claude" },
7045
7076
  { id: "codex", label: "Codex", kind: "cli", command: "codex" },
@@ -7061,19 +7092,41 @@ var AGENT_HARNESSES = [
7061
7092
  id: "antigravity",
7062
7093
  label: "Antigravity",
7063
7094
  kind: "app",
7064
- command: "antigravity"
7095
+ command: "antigravity",
7096
+ candidatePaths: {
7097
+ darwin: [
7098
+ ...ANTIGRAVITY_IDE_CANDIDATE_PATHS.darwin ?? [],
7099
+ "/Applications/Antigravity.app/Contents/MacOS/Antigravity"
7100
+ ]
7101
+ }
7065
7102
  },
7066
7103
  { id: "goose", label: "goose", kind: "cli", command: "goose" }
7067
7104
  ];
7068
7105
  function getHarness(id) {
7069
7106
  return AGENT_HARNESSES.find((h) => h.id === id);
7070
7107
  }
7108
+ function resolveAntigravityIdeExecutable(deps = {}) {
7109
+ const find = deps.find ?? findExecutable;
7110
+ const exists = deps.exists ?? existsSync12;
7111
+ const platform = deps.platform ?? process.platform;
7112
+ const found = find("antigravity-ide");
7113
+ if (found) return found;
7114
+ return ANTIGRAVITY_IDE_CANDIDATE_PATHS[platform]?.find((path) => exists(path)) ?? null;
7115
+ }
7071
7116
  function resolveHarnessExecutable(harness, overrideCommand, deps = {}) {
7072
7117
  const find = deps.find ?? findExecutable;
7073
7118
  const exists = deps.exists ?? existsSync12;
7074
7119
  const platform = deps.platform ?? process.platform;
7075
7120
  const found = find(overrideCommand || harness.command);
7076
7121
  if (found) return found;
7122
+ if (!overrideCommand && harness.id === "antigravity") {
7123
+ const foundIde = resolveAntigravityIdeExecutable({
7124
+ find,
7125
+ exists,
7126
+ platform
7127
+ });
7128
+ if (foundIde) return foundIde;
7129
+ }
7077
7130
  if (harness.kind === "app") {
7078
7131
  for (const candidate of harness.candidatePaths?.[platform] ?? []) {
7079
7132
  if (exists(candidate)) return candidate;
@@ -7118,6 +7171,76 @@ function launchHarness(harness, opts) {
7118
7171
  console.log(`Launched ${harness.label} in ${opts.workspace}`);
7119
7172
  }
7120
7173
  }
7174
+ function detectInstalledConnectHarnesses(options = {}) {
7175
+ const home = options.home ?? homedir8();
7176
+ const platform = options.platform ?? process.platform;
7177
+ const find = options.find ?? findExecutable;
7178
+ const exists = options.exists ?? existsSync12;
7179
+ const detected = [];
7180
+ const hasCommandOrPath = (command, paths = []) => Boolean(find(command)) || paths.some((path) => exists(path));
7181
+ if (hasCommandOrPath("codex", [
7182
+ join12(home, ".codex"),
7183
+ ...platform === "darwin" ? ["/Applications/Codex.app"] : platform === "win32" ? [join12(home, "AppData", "Local", "Programs", "Codex")] : []
7184
+ ])) {
7185
+ detected.push("codex");
7186
+ }
7187
+ const vscodePaths = platform === "darwin" ? [
7188
+ "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
7189
+ join12(
7190
+ home,
7191
+ "Applications",
7192
+ "Visual Studio Code.app",
7193
+ "Contents",
7194
+ "Resources",
7195
+ "app",
7196
+ "bin",
7197
+ "code"
7198
+ )
7199
+ ] : platform === "win32" ? [
7200
+ join12(
7201
+ home,
7202
+ "AppData",
7203
+ "Local",
7204
+ "Programs",
7205
+ "Microsoft VS Code",
7206
+ "bin",
7207
+ "code.cmd"
7208
+ )
7209
+ ] : ["/usr/bin/code", "/usr/local/bin/code", "/snap/bin/code"];
7210
+ if (hasCommandOrPath("code", vscodePaths)) detected.push("vscode");
7211
+ const copilotHome = options.copilotHome ?? join12(home, ".copilot");
7212
+ if (hasCommandOrPath("copilot", [
7213
+ copilotHome,
7214
+ ...platform === "darwin" ? ["/Applications/GitHub Copilot.app"] : []
7215
+ ])) {
7216
+ detected.push("copilot");
7217
+ }
7218
+ if (hasCommandOrPath("opencode", [join12(home, ".config", "opencode")])) {
7219
+ detected.push("opencode");
7220
+ }
7221
+ if (hasCommandOrPath("goose", [join12(home, ".config", "goose")])) {
7222
+ detected.push("goose");
7223
+ }
7224
+ if (hasCommandOrPath("antigravity", [
7225
+ join12(home, ".gemini", "config"),
7226
+ ...platform === "darwin" ? [
7227
+ join12(
7228
+ home,
7229
+ ".antigravity-ide",
7230
+ "antigravity-ide",
7231
+ "bin",
7232
+ "antigravity-ide"
7233
+ ),
7234
+ "/Applications/Antigravity.app",
7235
+ "/Applications/Antigravity IDE.app"
7236
+ ] : []
7237
+ ]) || find("antigravity-ide")) {
7238
+ detected.push("antigravity");
7239
+ }
7240
+ const claudeDesktopPath = platform === "darwin" ? "/Applications/Claude.app" : platform === "win32" ? join12(home, "AppData", "Local", "AnthropicClaude") : join12(home, ".config", "Claude");
7241
+ if (exists(claudeDesktopPath)) detected.push("claude-desktop");
7242
+ return detected;
7243
+ }
7121
7244
  function parseMcpJsonConfig(path, content) {
7122
7245
  let parsed;
7123
7246
  try {
@@ -7248,6 +7371,36 @@ approval_mode = "prompt"
7248
7371
  content = existingStr ? `${existingStr.trimEnd()}
7249
7372
  ${block}` : block;
7250
7373
  }
7374
+ } else if (harnessId === "vscode") {
7375
+ const platform = opts.platform ?? process.platform;
7376
+ targetPath = platform === "win32" ? join12(opts.home, "AppData", "Roaming", "Code", "User", "mcp.json") : platform === "darwin" ? join12(
7377
+ opts.home,
7378
+ "Library",
7379
+ "Application Support",
7380
+ "Code",
7381
+ "User",
7382
+ "mcp.json"
7383
+ ) : join12(opts.home, ".config", "Code", "User", "mcp.json");
7384
+ hint = "Reload VS Code after setup. ZAM Companion stays separate from the Codex chat and can be moved to any panel or sidebar.";
7385
+ let existing = {};
7386
+ if (exists(targetPath)) {
7387
+ existing = parseMcpJsonConfig(targetPath, read(targetPath));
7388
+ }
7389
+ if (existing.servers !== void 0 && (typeof existing.servers !== "object" || existing.servers === null || Array.isArray(existing.servers))) {
7390
+ throw new Error(
7391
+ `Cannot update ${targetPath}: servers must be a JSON object`
7392
+ );
7393
+ }
7394
+ if (existing.inputs !== void 0 && !Array.isArray(existing.inputs)) {
7395
+ throw new Error(`Cannot update ${targetPath}: inputs must be an array`);
7396
+ }
7397
+ if (!existing.servers) existing.servers = {};
7398
+ const expectedServer = { command: opts.zamPath, args: ["mcp"] };
7399
+ const currentServer = existing.servers.zam;
7400
+ alreadyConfigured = Array.isArray(existing.inputs) && typeof currentServer === "object" && currentServer !== null && !Array.isArray(currentServer) && JSON.stringify(currentServer) === JSON.stringify(expectedServer);
7401
+ existing.servers.zam = expectedServer;
7402
+ if (!existing.inputs) existing.inputs = [];
7403
+ content = JSON.stringify(existing, null, 2);
7251
7404
  } else if (harnessId === "goose") {
7252
7405
  targetPath = join12(opts.home, ".config", "goose", "config.yaml");
7253
7406
  hint = "goose will load the 'zam' extension on next session start. Run 'goose configure' to manage extensions.";
@@ -7290,7 +7443,7 @@ ${zamExtension}
7290
7443
  opts.copilotHome ?? join12(opts.home, ".copilot"),
7291
7444
  "mcp-config.json"
7292
7445
  );
7293
- hint = "Restart GitHub Copilot or start a new session to load the 'zam' MCP server and its Studio, Recall, Graph, and Settings canvases.";
7446
+ hint = "Restart GitHub Copilot or start a new session to load the 'zam' MCP server and its focused Recall, Graph, and Settings canvases.";
7294
7447
  let existing = {};
7295
7448
  if (exists(targetPath)) {
7296
7449
  existing = parseMcpJsonConfig(targetPath, read(targetPath));
@@ -7429,30 +7582,312 @@ function installCopilotExtension(options) {
7429
7582
  };
7430
7583
  }
7431
7584
 
7432
- // src/cli/commands/agent.ts
7433
- var C = {
7434
- reset: "\x1B[0m",
7435
- bold: "\x1B[1m",
7436
- cyan: "\x1B[36m",
7437
- dim: "\x1B[2m",
7438
- green: "\x1B[32m",
7439
- yellow: "\x1B[33m"
7440
- };
7441
- var SUPPORTED_AGENTS = ["opencode"];
7585
+ // src/cli/vscode-extension.ts
7586
+ import { execFileSync as execFileSync3 } from "child_process";
7587
+ import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync11, writeFileSync as writeFileSync8 } from "fs";
7588
+ import { homedir as homedir10 } from "os";
7589
+ import { dirname as dirname6, join as join14 } from "path";
7590
+ import { fileURLToPath as fileURLToPath3 } from "url";
7591
+ var packageRoot2 = [
7592
+ fileURLToPath3(new URL("../..", import.meta.url)),
7593
+ fileURLToPath3(new URL("../../..", import.meta.url))
7594
+ ].find((candidate) => existsSync14(join14(candidate, "package.json"))) ?? fileURLToPath3(new URL("../..", import.meta.url));
7595
+ function resolveVscodeExecutable(options = {}) {
7596
+ const home = options.home ?? homedir10();
7597
+ const platform = options.platform ?? process.platform;
7598
+ const find = options.find ?? findExecutable;
7599
+ const exists = options.exists ?? existsSync14;
7600
+ const found = find("code");
7601
+ if (found) return found;
7602
+ const candidates = platform === "darwin" ? [
7603
+ "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
7604
+ join14(
7605
+ home,
7606
+ "Applications",
7607
+ "Visual Studio Code.app",
7608
+ "Contents",
7609
+ "Resources",
7610
+ "app",
7611
+ "bin",
7612
+ "code"
7613
+ )
7614
+ ] : platform === "win32" ? [
7615
+ join14(
7616
+ home,
7617
+ "AppData",
7618
+ "Local",
7619
+ "Programs",
7620
+ "Microsoft VS Code",
7621
+ "bin",
7622
+ "code.cmd"
7623
+ )
7624
+ ] : ["/usr/bin/code", "/usr/local/bin/code", "/snap/bin/code"];
7625
+ return candidates.find((candidate) => exists(candidate)) ?? null;
7626
+ }
7627
+ function packageVersion() {
7628
+ const parsed = JSON.parse(
7629
+ readFileSync11(join14(packageRoot2, "package.json"), "utf8")
7630
+ );
7631
+ if (typeof parsed.version !== "string" || !parsed.version) {
7632
+ throw new Error("Cannot determine the ZAM package version");
7633
+ }
7634
+ return parsed.version;
7635
+ }
7636
+ function planVscodeExtensionInstall(options) {
7637
+ const home = options.home ?? homedir10();
7638
+ const version = options.version ?? packageVersion();
7639
+ const assetsDir = options.assetsDir ?? join14(packageRoot2, "dist", "vscode-extension");
7640
+ const vsixPath = join14(assetsDir, `ZAM_Companion_${version}.vsix`);
7641
+ if (!existsSync14(vsixPath)) {
7642
+ throw new Error(
7643
+ `ZAM Companion VSIX asset is missing: ${vsixPath}. Run \`npm run build\` and retry.`
7644
+ );
7645
+ }
7646
+ const codePath = options.codePath ?? resolveVscodeExecutable({
7647
+ home,
7648
+ platform: options.platform,
7649
+ find: options.find,
7650
+ exists: options.exists
7651
+ });
7652
+ if (!codePath) {
7653
+ throw new Error(
7654
+ "Visual Studio Code was not detected. Install VS Code or add its 'code' command to PATH."
7655
+ );
7656
+ }
7657
+ return {
7658
+ version,
7659
+ vsixPath,
7660
+ codePath,
7661
+ launchConfigPath: join14(home, ".zam", "vscode-launch.json"),
7662
+ launch: { command: options.zamPath, args: ["mcp"] }
7663
+ };
7664
+ }
7665
+ function installVscodeExtension(options) {
7666
+ const plan = planVscodeExtensionInstall(options);
7667
+ if (options.dryRun) return { ...plan, action: "planned" };
7668
+ const launchContent = `${JSON.stringify(
7669
+ {
7670
+ schemaVersion: 1,
7671
+ version: plan.version,
7672
+ command: plan.launch.command,
7673
+ args: plan.launch.args
7674
+ },
7675
+ null,
7676
+ 2
7677
+ )}
7678
+ `;
7679
+ const existed = existsSync14(plan.launchConfigPath);
7680
+ const changed = !existed || readFileSync11(plan.launchConfigPath, "utf8") !== launchContent;
7681
+ const run = options.run ?? ((command, args) => {
7682
+ execFileSync3(command, args, { stdio: "pipe" });
7683
+ });
7684
+ run(plan.codePath, ["--install-extension", plan.vsixPath, "--force"]);
7685
+ if (changed) {
7686
+ mkdirSync9(dirname6(plan.launchConfigPath), { recursive: true });
7687
+ writeFileSync8(plan.launchConfigPath, launchContent, "utf8");
7688
+ }
7689
+ return {
7690
+ ...plan,
7691
+ action: !existed ? "installed" : changed ? "updated" : "unchanged"
7692
+ };
7693
+ }
7694
+
7695
+ // src/cli/agent-connect.ts
7442
7696
  var CONNECT_HARNESSES = [
7443
7697
  "claude-code",
7444
7698
  "claude-desktop",
7445
7699
  "antigravity",
7446
7700
  "codex",
7701
+ "vscode",
7447
7702
  "opencode",
7448
7703
  "goose",
7449
7704
  "copilot"
7450
7705
  ];
7706
+ var USER_SCOPED_CONNECT_HARNESSES = [
7707
+ "claude-desktop",
7708
+ "antigravity",
7709
+ "codex",
7710
+ "vscode",
7711
+ "opencode",
7712
+ "goose",
7713
+ "copilot"
7714
+ ];
7715
+ var CONNECT_HARNESS_LABELS = {
7716
+ "claude-code": "Claude Code",
7717
+ "claude-desktop": "Claude Desktop",
7718
+ antigravity: "Antigravity",
7719
+ codex: "Codex",
7720
+ vscode: "VS Code",
7721
+ opencode: "OpenCode",
7722
+ goose: "Goose",
7723
+ copilot: "GitHub Copilot"
7724
+ };
7451
7725
  function isConnectHarnessId(value) {
7452
7726
  return CONNECT_HARNESSES.includes(value);
7453
7727
  }
7728
+ function resolveDeps(deps) {
7729
+ const home = deps.home ?? homedir11();
7730
+ const cwd = deps.cwd ?? process.cwd();
7731
+ const copilotHome = deps.copilotHome ?? process.env.COPILOT_HOME;
7732
+ return {
7733
+ home,
7734
+ cwd,
7735
+ copilotHome,
7736
+ findZam: deps.findZam ?? (() => findExecutable("zam")),
7737
+ detect: deps.detect ?? (() => detectInstalledConnectHarnesses({ home, copilotHome })),
7738
+ connectMcp: deps.connectMcp ?? connectHarnessMcp,
7739
+ writeConfig: deps.writeConfig ?? ((path, content) => {
7740
+ mkdirSync10(dirname7(path), { recursive: true });
7741
+ writeFileSync9(path, content, "utf-8");
7742
+ }),
7743
+ installCopilot: deps.installCopilot ?? installCopilotExtension,
7744
+ installVscode: deps.installVscode ?? installVscodeExtension,
7745
+ resolveAntigravity: deps.resolveAntigravity ?? resolveAntigravityIdeExecutable,
7746
+ refreshSkills: deps.refreshSkills ?? distributeGlobalSkills
7747
+ };
7748
+ }
7749
+ function performAgentConnect(opts = {}, deps = {}) {
7750
+ const d = resolveDeps(deps);
7751
+ const dryRun = Boolean(opts.dryRun);
7752
+ const detected = opts.harness ? [opts.harness] : d.detect();
7753
+ const foundZam = d.findZam();
7754
+ const zamPath = foundZam ?? "zam";
7755
+ const results = [];
7756
+ for (const harness of detected) {
7757
+ const label = CONNECT_HARNESS_LABELS[harness];
7758
+ try {
7759
+ const prepared = d.connectMcp(harness, {
7760
+ zamPath,
7761
+ cwd: d.cwd,
7762
+ home: d.home,
7763
+ copilotHome: d.copilotHome
7764
+ });
7765
+ let extension = null;
7766
+ if (harness === "copilot") {
7767
+ const installed = d.installCopilot({
7768
+ home: d.home,
7769
+ zamPath,
7770
+ dryRun
7771
+ });
7772
+ extension = {
7773
+ kind: "copilot",
7774
+ action: installed.action,
7775
+ location: installed.destinationDir,
7776
+ detail: `${installed.launch.command} ${installed.launch.args.join(" ")}`
7777
+ };
7778
+ } else if (harness === "vscode") {
7779
+ const installed = d.installVscode({ home: d.home, zamPath, dryRun });
7780
+ extension = {
7781
+ kind: "vscode",
7782
+ action: installed.action,
7783
+ location: installed.vsixPath,
7784
+ detail: installed.launchConfigPath
7785
+ };
7786
+ } else if (harness === "antigravity") {
7787
+ const antigravityPath = d.resolveAntigravity();
7788
+ if (antigravityPath) {
7789
+ const installed = d.installVscode({
7790
+ home: d.home,
7791
+ zamPath,
7792
+ codePath: antigravityPath,
7793
+ dryRun
7794
+ });
7795
+ extension = {
7796
+ kind: "vscode",
7797
+ action: installed.action,
7798
+ location: installed.vsixPath,
7799
+ detail: installed.launchConfigPath
7800
+ };
7801
+ }
7802
+ }
7803
+ let wrote = false;
7804
+ if (!prepared.alreadyConfigured && !dryRun) {
7805
+ d.writeConfig(prepared.path, prepared.content);
7806
+ wrote = true;
7807
+ }
7808
+ results.push({
7809
+ harness,
7810
+ label,
7811
+ path: prepared.path,
7812
+ content: prepared.content,
7813
+ alreadyConfigured: prepared.alreadyConfigured,
7814
+ wrote,
7815
+ hint: prepared.hint,
7816
+ extension
7817
+ });
7818
+ } catch (error) {
7819
+ results.push({
7820
+ harness,
7821
+ label,
7822
+ path: "",
7823
+ content: "",
7824
+ alreadyConfigured: false,
7825
+ wrote: false,
7826
+ hint: "",
7827
+ extension: null,
7828
+ error: error instanceof Error ? error.message : String(error)
7829
+ });
7830
+ }
7831
+ }
7832
+ let skills = null;
7833
+ if (!dryRun && detected.length > 0) {
7834
+ const skillResults = d.refreshSkills(d.home);
7835
+ skills = {
7836
+ refreshed: skillResults.filter((result) => result.success).length,
7837
+ total: skillResults.length
7838
+ };
7839
+ }
7840
+ return {
7841
+ success: results.every((result) => !result.error),
7842
+ detected,
7843
+ zamPath,
7844
+ zamOnPath: Boolean(foundZam),
7845
+ results,
7846
+ skills
7847
+ };
7848
+ }
7849
+ function inspectConnectHarnesses(deps = {}) {
7850
+ const d = resolveDeps(deps);
7851
+ const foundZam = d.findZam();
7852
+ const zamPath = foundZam ?? "zam";
7853
+ const installed = new Set(d.detect());
7854
+ const harnesses = USER_SCOPED_CONNECT_HARNESSES.map((harness) => {
7855
+ const status = {
7856
+ harness,
7857
+ label: CONNECT_HARNESS_LABELS[harness],
7858
+ installed: installed.has(harness),
7859
+ configured: false,
7860
+ configPath: ""
7861
+ };
7862
+ try {
7863
+ const probe = d.connectMcp(harness, {
7864
+ zamPath,
7865
+ cwd: d.cwd,
7866
+ home: d.home,
7867
+ copilotHome: d.copilotHome
7868
+ });
7869
+ status.configured = probe.alreadyConfigured;
7870
+ status.configPath = probe.path;
7871
+ } catch (error) {
7872
+ status.note = error instanceof Error ? error.message : String(error);
7873
+ }
7874
+ return status;
7875
+ });
7876
+ return { zamOnPath: Boolean(foundZam), harnesses };
7877
+ }
7878
+
7879
+ // src/cli/commands/agent.ts
7880
+ var C = {
7881
+ reset: "\x1B[0m",
7882
+ bold: "\x1B[1m",
7883
+ cyan: "\x1B[36m",
7884
+ dim: "\x1B[2m",
7885
+ green: "\x1B[32m",
7886
+ yellow: "\x1B[33m"
7887
+ };
7888
+ var SUPPORTED_AGENTS = ["opencode"];
7454
7889
  function agentsMdPresent(cwd = process.cwd()) {
7455
- return existsSync14(join14(cwd, "AGENTS.md"));
7890
+ return existsSync15(join15(cwd, "AGENTS.md"));
7456
7891
  }
7457
7892
  function printStatus() {
7458
7893
  const installed = hasCommand("opencode");
@@ -7558,95 +7993,87 @@ var openCmd = new Command("open").description("Open an agent harness in the work
7558
7993
  const workspace = opts.dir ?? process.cwd();
7559
7994
  launchHarness(harness, { executable, workspace, shell });
7560
7995
  });
7561
- var connectCmd = new Command("connect").description("Configure the ZAM MCP server for an agent harness").argument(
7562
- "<harness>",
7563
- "Harness to connect: claude-code | claude-desktop | antigravity | codex | opencode | goose | copilot"
7996
+ var connectCmd = new Command("connect").description(
7997
+ "Configure ZAM for detected user agent harnesses, or one explicit harness"
7998
+ ).argument(
7999
+ "[harness]",
8000
+ "Optional harness: claude-code | claude-desktop | antigravity | codex | vscode | opencode | goose | copilot"
7564
8001
  ).option(
7565
8002
  "--print",
7566
8003
  "Print configuration changes instead of writing them to disk"
7567
8004
  ).action(async (harnessArg, opts) => {
7568
- if (!isConnectHarnessId(harnessArg)) {
8005
+ let explicitHarness;
8006
+ if (harnessArg) {
8007
+ if (!isConnectHarnessId(harnessArg)) {
8008
+ console.error(
8009
+ `Unsupported harness: ${harnessArg}. Supported: ${CONNECT_HARNESSES.join(", ")}.`
8010
+ );
8011
+ process.exit(1);
8012
+ } else {
8013
+ explicitHarness = harnessArg;
8014
+ }
8015
+ }
8016
+ const report = performAgentConnect({
8017
+ harness: explicitHarness,
8018
+ dryRun: Boolean(opts.print)
8019
+ });
8020
+ if (report.detected.length === 0) {
7569
8021
  console.error(
7570
- `Unsupported harness: ${harnessArg}. Supported: ${CONNECT_HARNESSES.join(", ")}.`
8022
+ "No supported user-scoped agent harness was detected. Install Codex, VS Code, or another supported host, or pass a harness explicitly."
7571
8023
  );
7572
8024
  process.exit(1);
7573
8025
  }
7574
- let zamPath = findExecutable("zam");
7575
- if (!zamPath) {
8026
+ if (!report.zamOnPath) {
7576
8027
  console.warn(
7577
8028
  `${C.yellow}Warning: 'zam' executable was not found on your PATH. Falling back to literal 'zam'.${C.reset}`
7578
8029
  );
7579
- zamPath = "zam";
7580
8030
  }
7581
- let result;
7582
- try {
7583
- result = connectHarnessMcp(harnessArg, {
7584
- zamPath,
7585
- cwd: process.cwd(),
7586
- home: homedir10(),
7587
- copilotHome: process.env.COPILOT_HOME
7588
- });
7589
- } catch (error) {
7590
- console.error(
7591
- `Error preparing MCP configuration: ${error instanceof Error ? error.message : String(error)}`
7592
- );
7593
- process.exit(1);
7594
- }
7595
- let copilotExtension;
7596
- if (harnessArg === "copilot") {
7597
- try {
7598
- copilotExtension = installCopilotExtension({
7599
- home: homedir10(),
7600
- zamPath,
7601
- dryRun: Boolean(opts.print)
7602
- });
7603
- } catch (error) {
7604
- console.error(
7605
- `Error preparing Copilot MCP Apps extension: ${error instanceof Error ? error.message : String(error)}`
7606
- );
7607
- process.exit(1);
8031
+ for (const result of report.results) {
8032
+ if (result.error) {
8033
+ console.error(`Error connecting ${result.harness}: ${result.error}`);
8034
+ continue;
7608
8035
  }
7609
- }
7610
- if (opts.print) {
7611
- console.log(`Path: ${result.path}`);
7612
- console.log(`Content:
8036
+ if (opts.print) {
8037
+ if (report.results.length > 1)
8038
+ console.log(`${C.bold}${result.harness}${C.reset}`);
8039
+ console.log(`Path: ${result.path}`);
8040
+ console.log(`Content:
7613
8041
  ${result.content}`);
7614
- if (copilotExtension) {
7615
- console.log(`Extension: ${copilotExtension.destinationDir}`);
8042
+ if (result.extension?.kind === "copilot") {
8043
+ console.log(`Extension: ${result.extension.location}`);
8044
+ console.log(`Launch: ${result.extension.detail}`);
8045
+ } else if (result.extension) {
8046
+ console.log(`Extension: ${result.extension.location}`);
8047
+ console.log(`Launch config: ${result.extension.detail}`);
8048
+ }
8049
+ continue;
8050
+ }
8051
+ if (result.alreadyConfigured) {
7616
8052
  console.log(
7617
- `Launch: ${copilotExtension.launch.command} ${copilotExtension.launch.args.join(" ")}`
8053
+ `${C.green}\u2713${C.reset} ${result.harness}: MCP server 'zam' already configured in ${result.path}`
7618
8054
  );
7619
- }
7620
- return;
7621
- }
7622
- if (result.alreadyConfigured) {
7623
- console.log(
7624
- `${C.green}\u2713${C.reset} MCP server 'zam' already configured in ${result.path}`
7625
- );
7626
- if (copilotExtension) {
8055
+ } else {
7627
8056
  console.log(
7628
- `${C.green}\u2713${C.reset} MCP Apps extension ${copilotExtension.action} at ${copilotExtension.destinationDir}`
8057
+ `${C.green}\u2713${C.reset} ${result.harness}: wrote MCP configuration to ${result.path}`
7629
8058
  );
7630
8059
  }
7631
- console.log(` ${C.dim}${result.hint}${C.reset}`);
7632
- return;
7633
- }
7634
- try {
7635
- mkdirSync9(dirname6(result.path), { recursive: true });
7636
- writeFileSync8(result.path, result.content, "utf-8");
7637
- console.log(
7638
- `${C.green}\u2713${C.reset} Wrote MCP configuration to ${result.path}`
7639
- );
7640
- if (copilotExtension) {
8060
+ if (result.extension?.kind === "copilot") {
8061
+ console.log(
8062
+ `${C.green}\u2713${C.reset} Copilot MCP Apps extension ${result.extension.action} at ${result.extension.location}`
8063
+ );
8064
+ } else if (result.extension) {
7641
8065
  console.log(
7642
- `${C.green}\u2713${C.reset} MCP Apps extension ${copilotExtension.action} at ${copilotExtension.destinationDir}`
8066
+ `${C.green}\u2713${C.reset} ZAM Companion ${result.extension.action} from ${result.extension.location}`
7643
8067
  );
7644
8068
  }
7645
8069
  console.log(` ${C.dim}${result.hint}${C.reset}`);
7646
- } catch (error) {
7647
- console.error(
7648
- `Error writing MCP configuration: ${error instanceof Error ? error.message : String(error)}`
8070
+ }
8071
+ if (report.skills) {
8072
+ console.log(
8073
+ `${C.green}\u2713${C.reset} Refreshed ${report.skills.refreshed}/${report.skills.total} global ZAM skill installations`
7649
8074
  );
8075
+ }
8076
+ if (!report.success) {
7650
8077
  process.exit(1);
7651
8078
  }
7652
8079
  });
@@ -7654,11 +8081,11 @@ var agentCommand = new Command("agent").description("Provision and inspect the a
7654
8081
 
7655
8082
  // src/cli/commands/bridge.ts
7656
8083
  init_kernel();
7657
- import { execFileSync as execFileSync3 } from "child_process";
8084
+ import { execFileSync as execFileSync4 } from "child_process";
7658
8085
  import { randomBytes as randomBytes2 } from "crypto";
7659
- import { existsSync as existsSync18, readdirSync as readdirSync2, readFileSync as readFileSync15, rmSync as rmSync3 } from "fs";
7660
- import { homedir as homedir12, tmpdir as tmpdir3 } from "os";
7661
- import { join as join21, resolve as resolve5 } from "path";
8086
+ import { existsSync as existsSync19, readdirSync as readdirSync2, readFileSync as readFileSync16, rmSync as rmSync3 } from "fs";
8087
+ import { homedir as homedir13, tmpdir as tmpdir3 } from "os";
8088
+ import { join as join22, resolve as resolve5 } from "path";
7662
8089
  import { Command as Command2 } from "commander";
7663
8090
  import { ulid as ulid9 } from "ulid";
7664
8091
 
@@ -7669,11 +8096,12 @@ import fs from "fs";
7669
8096
  // src/cli/llm/client.ts
7670
8097
  init_kernel();
7671
8098
  import { spawn as spawn2 } from "child_process";
7672
- import { existsSync as existsSync15, readFileSync as readFileSync11, statSync as statSync2 } from "fs";
8099
+ import { existsSync as existsSync16, readFileSync as readFileSync12, statSync as statSync2 } from "fs";
7673
8100
  var DEFAULT_LLM_URL = "http://localhost:8000/v1";
7674
8101
  var DEFAULT_LLM_MAX_TOKENS = 1e4;
7675
8102
  var RECALL_QUESTION_MAX_OUTPUT_TOKENS = 400;
7676
8103
  var RECALL_EVALUATION_MAX_OUTPUT_TOKENS = 1200;
8104
+ var RECALL_DISCUSSION_MAX_OUTPUT_TOKENS = 1200;
7677
8105
  var RECALL_ENDPOINT_CACHE_MS = 6e4;
7678
8106
  var cachedRecallEndpoint = null;
7679
8107
  function recallEndpointSignature(cfg) {
@@ -8036,6 +8464,64 @@ Evaluation:`;
8036
8464
  providerName: endpoint.providerName
8037
8465
  };
8038
8466
  }
8467
+ async function discussReviewViaLLM(db, input8) {
8468
+ const cfg = await getProviderForRole(db, "recall");
8469
+ const endpoint = await resolveUsableRecallEndpoint(db);
8470
+ const langName = LANGUAGE_NAMES[cfg.locale] || "English";
8471
+ const systemPrompt = `You are ZAM, a warm, precise, and encouraging skills trainer in a follow-up discussion about one flashcard.
8472
+ The learner has already answered, the reference answer is revealed, and your evaluation feedback was shown \u2014 nothing about this card is a spoiler anymore.
8473
+
8474
+ Guidelines:
8475
+ 1. Answer the learner's follow-up directly and concretely in ${langName}, grounded in the card's target concept, context, and source reference.
8476
+ 2. Stay scoped to this card and its concept. If the learner drifts to unrelated territory, answer briefly and steer back to the concept.
8477
+ 3. Keep replies conversational and short (a few sentences) unless the learner explicitly asks for depth. Plain text only \u2014 no markdown wrapper, headers, or bullet lists.
8478
+ 4. The self-rating is the learner's own choice. If asked, explain the FSRS scale (1 forgot, 2 hard, 3 good, 4 easy) but never pressure them toward a specific rating.`;
8479
+ const cardFrame = `The card under discussion:
8480
+ Domain: ${input8.domain}
8481
+ Slug: ${input8.slug}
8482
+ Recall Question: ${input8.question}
8483
+ Learner's Answer: ${input8.userAnswer}
8484
+
8485
+ Target Concept (Correct Answer): ${input8.concept}
8486
+ Target Context: ${input8.context || "(none)"}
8487
+ ${input8.sourceLinkContent ? `Source Code Reference:
8488
+ ${input8.sourceLinkContent}` : ""}`;
8489
+ const messages = [
8490
+ { role: "system", content: systemPrompt },
8491
+ { role: "user", content: cardFrame }
8492
+ ];
8493
+ const feedback = input8.feedback?.trim();
8494
+ if (feedback) {
8495
+ messages.push({ role: "assistant", content: feedback });
8496
+ }
8497
+ for (const turn of input8.thread) {
8498
+ messages.push({ role: turn.role, content: turn.content });
8499
+ }
8500
+ messages.push({ role: "user", content: input8.message });
8501
+ const res = await fetchWithInteractiveTimeout(
8502
+ `${endpoint.url}/chat/completions`,
8503
+ {
8504
+ method: "POST",
8505
+ headers: {
8506
+ "Content-Type": "application/json",
8507
+ Authorization: `Bearer ${endpoint.apiKey}`
8508
+ },
8509
+ body: JSON.stringify({
8510
+ model: endpoint.model,
8511
+ messages,
8512
+ temperature: 0.3,
8513
+ max_tokens: RECALL_DISCUSSION_MAX_OUTPUT_TOKENS
8514
+ }),
8515
+ locale: cfg.locale
8516
+ }
8517
+ );
8518
+ const text = await readChatContent(res, "LLM discussion");
8519
+ return {
8520
+ text,
8521
+ model: endpoint.model,
8522
+ providerName: endpoint.providerName
8523
+ };
8524
+ }
8039
8525
  var MAX_IMPORT_TEXT_CHARS = 2e5;
8040
8526
  var VALID_GENERATED_MODES = /* @__PURE__ */ new Set(["shadowing", "copilot", "autonomy"]);
8041
8527
  function parseGeneratedCardArray(responseText, label, limits) {
@@ -8385,7 +8871,7 @@ async function extractTextFromScanViaLLM(db, imagePath) {
8385
8871
  "Vision role is not enabled in settings (llm.vision.enabled)"
8386
8872
  );
8387
8873
  }
8388
- if (!existsSync15(imagePath)) {
8874
+ if (!existsSync16(imagePath)) {
8389
8875
  throw new Error(`Scan file not found: ${imagePath}`);
8390
8876
  }
8391
8877
  const stat = statSync2(imagePath);
@@ -8406,7 +8892,7 @@ async function extractTextFromScanViaLLM(db, imagePath) {
8406
8892
  if (!mime) {
8407
8893
  throw new Error("Unsupported scan format; use PNG, JPEG, or WebP");
8408
8894
  }
8409
- const imageBytes = readFileSync11(imagePath);
8895
+ const imageBytes = readFileSync12(imagePath);
8410
8896
  const dataUrl = `data:${mime};base64,${imageBytes.toString("base64")}`;
8411
8897
  const visionEndpoint = await getVisionConfig(db);
8412
8898
  const langName = LANGUAGE_NAMES[p.locale] || "English";
@@ -8838,7 +9324,7 @@ function spawnLocalRunner(url, model, hint) {
8838
9324
  const { runner, port } = detectRunner(url, model, hint);
8839
9325
  try {
8840
9326
  if (runner === "fastflowlm") {
8841
- const flmExe = existsSync15("C:\\Program Files\\flm\\flm.exe") ? "C:\\Program Files\\flm\\flm.exe" : "flm";
9327
+ const flmExe = existsSync16("C:\\Program Files\\flm\\flm.exe") ? "C:\\Program Files\\flm\\flm.exe" : "flm";
8842
9328
  if (!hasCommand("flm") && flmExe === "flm") return;
8843
9329
  spawn2(flmExe, ["serve", model, "--port", port], {
8844
9330
  detached: true,
@@ -8859,7 +9345,7 @@ function spawnLocalRunner(url, model, hint) {
8859
9345
  async function startLocalRunner(url, model, locale, hint) {
8860
9346
  const { runner, port } = detectRunner(url, model, hint);
8861
9347
  if (runner === "fastflowlm") {
8862
- const flmExe = existsSync15("C:\\Program Files\\flm\\flm.exe") ? "C:\\Program Files\\flm\\flm.exe" : "flm";
9348
+ const flmExe = existsSync16("C:\\Program Files\\flm\\flm.exe") ? "C:\\Program Files\\flm\\flm.exe" : "flm";
8863
9349
  if (!hasCommand("flm") && flmExe === "flm") {
8864
9350
  console.warn(
8865
9351
  "\x1B[31m\u2717 FastFlowLM is configured but could not be found on the system.\x1B[0m"
@@ -9273,7 +9759,7 @@ async function readWebLink(url) {
9273
9759
  redirect: "manual",
9274
9760
  signal: controller.signal,
9275
9761
  headers: {
9276
- "User-Agent": "ZAM-Content-Studio/0.10.0"
9762
+ "User-Agent": "ZAM-Content-Studio/0.10.4"
9277
9763
  }
9278
9764
  });
9279
9765
  if (res.status >= 300 && res.status < 400) {
@@ -9338,8 +9824,8 @@ async function readImageOCR(db, imagePath) {
9338
9824
 
9339
9825
  // src/cli/bridge-handlers.ts
9340
9826
  init_kernel();
9341
- import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync9 } from "fs";
9342
- import { join as join17 } from "path";
9827
+ import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync10 } from "fs";
9828
+ import { join as join18 } from "path";
9343
9829
 
9344
9830
  // src/cli/knowledge-contexts.ts
9345
9831
  init_kernel();
@@ -9586,16 +10072,16 @@ async function resolveSuggestMinSimilarity(db) {
9586
10072
  }
9587
10073
 
9588
10074
  // src/cli/update/latest-version.ts
9589
- import { readFileSync as readFileSync12 } from "fs";
9590
- import { dirname as dirname7, join as join15 } from "path";
9591
- import { fileURLToPath as fileURLToPath3 } from "url";
10075
+ import { readFileSync as readFileSync13 } from "fs";
10076
+ import { dirname as dirname8, join as join16 } from "path";
10077
+ import { fileURLToPath as fileURLToPath4 } from "url";
9592
10078
  var GITHUB_REPO = "zam-os/zam";
9593
10079
  function currentVersion() {
9594
- const here = dirname7(fileURLToPath3(import.meta.url));
10080
+ const here = dirname8(fileURLToPath4(import.meta.url));
9595
10081
  for (const up of ["..", "../..", "../../.."]) {
9596
10082
  try {
9597
10083
  const pkg2 = JSON.parse(
9598
- readFileSync12(join15(here, up, "package.json"), "utf-8")
10084
+ readFileSync13(join16(here, up, "package.json"), "utf-8")
9599
10085
  );
9600
10086
  if (pkg2.version) return pkg2.version;
9601
10087
  } catch {
@@ -9625,13 +10111,13 @@ async function fetchLatestVersion(repo) {
9625
10111
 
9626
10112
  // src/cli/workspaces/active.ts
9627
10113
  init_kernel();
9628
- import { existsSync as existsSync16, mkdirSync as mkdirSync10 } from "fs";
9629
- import { homedir as homedir11 } from "os";
9630
- import { basename as basename3, join as join16, resolve as resolve4 } from "path";
10114
+ import { existsSync as existsSync17, mkdirSync as mkdirSync11 } from "fs";
10115
+ import { homedir as homedir12 } from "os";
10116
+ import { basename as basename3, join as join17, resolve as resolve4 } from "path";
9631
10117
  var LEGACY_WORKSPACE_DIR_KEY = "personal.workspace_dir";
9632
10118
  var DEFAULT_WORKSPACE_ID = "personal";
9633
10119
  function defaultWorkspaceDir() {
9634
- return join16(homedir11(), "Documents", "zam");
10120
+ return join17(homedir12(), "Documents", "zam");
9635
10121
  }
9636
10122
  function normalizeWorkspacePath(path) {
9637
10123
  const resolved = resolve4(path);
@@ -9686,13 +10172,13 @@ async function ensureActiveWorkspace(db) {
9686
10172
  await migrateLegacyWorkspaceDir(db);
9687
10173
  const active = getActiveWorkspace();
9688
10174
  if (active) {
9689
- mkdirSync10(active.path, { recursive: true });
10175
+ mkdirSync11(active.path, { recursive: true });
9690
10176
  return active;
9691
10177
  }
9692
10178
  const configured = getConfiguredWorkspaces()[0];
9693
10179
  if (configured) {
9694
10180
  setActiveWorkspaceId(configured.id);
9695
- mkdirSync10(configured.path, { recursive: true });
10181
+ mkdirSync11(configured.path, { recursive: true });
9696
10182
  return configured;
9697
10183
  }
9698
10184
  const workspace = {
@@ -9701,7 +10187,7 @@ async function ensureActiveWorkspace(db) {
9701
10187
  kind: "personal",
9702
10188
  path: defaultWorkspaceDir()
9703
10189
  };
9704
- mkdirSync10(workspace.path, { recursive: true });
10190
+ mkdirSync11(workspace.path, { recursive: true });
9705
10191
  upsertConfiguredWorkspace(workspace);
9706
10192
  setActiveWorkspaceId(workspace.id);
9707
10193
  await clearLegacyWorkspaceDir(db);
@@ -9740,7 +10226,7 @@ async function removeWorkspaceAndResolveActive(db, id) {
9740
10226
  };
9741
10227
  }
9742
10228
  function existingWorkspaceDirOrHome(workspace) {
9743
- return existsSync16(workspace.path) ? workspace.path : homedir11();
10229
+ return existsSync17(workspace.path) ? workspace.path : homedir12();
9744
10230
  }
9745
10231
 
9746
10232
  // src/cli/bridge-handlers.ts
@@ -10512,11 +10998,11 @@ async function backupCreate(db, params) {
10512
10998
  const targetDir = params.dir || (await ensureActiveWorkspace(db)).path;
10513
10999
  const snapshot = await exportSnapshot(db);
10514
11000
  const manifest = verifySnapshot(snapshot);
10515
- const backupDir = join17(targetDir, "zam-backups");
10516
- mkdirSync11(backupDir, { recursive: true });
11001
+ const backupDir = join18(targetDir, "zam-backups");
11002
+ mkdirSync12(backupDir, { recursive: true });
10517
11003
  const stamp = manifest.createdAt.replace(/[:.]/g, "-");
10518
- const path = join17(backupDir, `zam-snapshot-${stamp}.sql`);
10519
- writeFileSync9(path, snapshot, "utf-8");
11004
+ const path = join18(backupDir, `zam-snapshot-${stamp}.sql`);
11005
+ writeFileSync10(path, snapshot, "utf-8");
10520
11006
  return {
10521
11007
  ok: true,
10522
11008
  path,
@@ -13201,9 +13687,9 @@ function getCurriculumProvider(id) {
13201
13687
  // src/cli/llm/vision.ts
13202
13688
  init_kernel();
13203
13689
  import { randomBytes } from "crypto";
13204
- import { readFileSync as readFileSync13 } from "fs";
13690
+ import { readFileSync as readFileSync14 } from "fs";
13205
13691
  import { tmpdir as tmpdir2 } from "os";
13206
- import { basename as basename4, join as join18 } from "path";
13692
+ import { basename as basename4, join as join19 } from "path";
13207
13693
  var LANGUAGE_NAMES2 = {
13208
13694
  en: "English",
13209
13695
  de: "German",
@@ -13239,13 +13725,13 @@ async function observeUiSnapshotViaLLM(db, input8) {
13239
13725
  const imageUrls = [];
13240
13726
  const isVideo = /\.(mp4|mov|m4v|avi|mkv|webm)$/i.test(input8.imagePath);
13241
13727
  if (isVideo) {
13242
- const { mkdirSync: mkdirSync17, readdirSync: readdirSync3, rmSync: rmSync4 } = await import("fs");
13728
+ const { mkdirSync: mkdirSync18, readdirSync: readdirSync3, rmSync: rmSync4 } = await import("fs");
13243
13729
  const { execSync: execSync6 } = await import("child_process");
13244
- const tempDir = join18(
13730
+ const tempDir = join19(
13245
13731
  tmpdir2(),
13246
13732
  `zam-frames-${randomBytes(4).toString("hex")}`
13247
13733
  );
13248
- mkdirSync17(tempDir, { recursive: true });
13734
+ mkdirSync18(tempDir, { recursive: true });
13249
13735
  try {
13250
13736
  execSync6(
13251
13737
  `ffmpeg -i "${input8.imagePath}" -vf "fps=1/3,scale=1280:-1" -vsync vfr "${tempDir}/frame_%03d.png"`,
@@ -13274,7 +13760,7 @@ async function observeUiSnapshotViaLLM(db, input8) {
13274
13760
  }
13275
13761
  }
13276
13762
  for (const file of sampledFiles) {
13277
- const bytes = readFileSync13(join18(tempDir, file));
13763
+ const bytes = readFileSync14(join19(tempDir, file));
13278
13764
  imageUrls.push(`data:image/png;base64,${bytes.toString("base64")}`);
13279
13765
  }
13280
13766
  } finally {
@@ -13284,7 +13770,7 @@ async function observeUiSnapshotViaLLM(db, input8) {
13284
13770
  }
13285
13771
  }
13286
13772
  } else {
13287
- const imageBytes = readFileSync13(input8.imagePath);
13773
+ const imageBytes = readFileSync14(input8.imagePath);
13288
13774
  const ext = input8.imagePath.split(".").pop()?.toLowerCase();
13289
13775
  const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
13290
13776
  imageUrls.push(`data:${mime};base64,${imageBytes.toString("base64")}`);
@@ -13753,36 +14239,36 @@ async function withProviderScope(machine, action) {
13753
14239
 
13754
14240
  // src/cli/provisioning/index.ts
13755
14241
  import {
13756
- existsSync as existsSync17,
14242
+ existsSync as existsSync18,
13757
14243
  lstatSync as lstatSync2,
13758
- mkdirSync as mkdirSync12,
13759
- readFileSync as readFileSync14,
14244
+ mkdirSync as mkdirSync13,
14245
+ readFileSync as readFileSync15,
13760
14246
  realpathSync,
13761
14247
  rmSync as rmSync2,
13762
14248
  symlinkSync,
13763
- writeFileSync as writeFileSync10
14249
+ writeFileSync as writeFileSync11
13764
14250
  } from "fs";
13765
- import { basename as basename5, dirname as dirname8, join as join19 } from "path";
13766
- import { fileURLToPath as fileURLToPath4 } from "url";
13767
- var packageRoot2 = [
13768
- fileURLToPath4(new URL("../..", import.meta.url)),
13769
- fileURLToPath4(new URL("../../..", import.meta.url))
13770
- ].find((candidate) => existsSync17(join19(candidate, "package.json"))) ?? fileURLToPath4(new URL("../..", import.meta.url));
14251
+ import { basename as basename5, dirname as dirname9, join as join20 } from "path";
14252
+ import { fileURLToPath as fileURLToPath5 } from "url";
14253
+ var packageRoot3 = [
14254
+ fileURLToPath5(new URL("../..", import.meta.url)),
14255
+ fileURLToPath5(new URL("../../..", import.meta.url))
14256
+ ].find((candidate) => existsSync18(join20(candidate, "package.json"))) ?? fileURLToPath5(new URL("../..", import.meta.url));
13771
14257
  var ALL_SETUP_AGENTS = ["claude", "copilot", "codex", "agent"];
13772
14258
  var SKILL_PAIRS = [
13773
14259
  {
13774
- from: join19(packageRoot2, ".claude", "skills", "zam", "SKILL.md"),
13775
- to: join19(".claude", "skills", "zam", "SKILL.md"),
14260
+ from: join20(packageRoot3, ".claude", "skills", "zam", "SKILL.md"),
14261
+ to: join20(".claude", "skills", "zam", "SKILL.md"),
13776
14262
  agents: ["claude", "copilot"]
13777
14263
  },
13778
14264
  {
13779
- from: join19(packageRoot2, ".agent", "skills", "zam", "SKILL.md"),
13780
- to: join19(".agent", "skills", "zam", "SKILL.md"),
14265
+ from: join20(packageRoot3, ".agent", "skills", "zam", "SKILL.md"),
14266
+ to: join20(".agent", "skills", "zam", "SKILL.md"),
13781
14267
  agents: ["agent"]
13782
14268
  },
13783
14269
  {
13784
- from: join19(packageRoot2, ".agents", "skills", "zam", "SKILL.md"),
13785
- to: join19(".agents", "skills", "zam", "SKILL.md"),
14270
+ from: join20(packageRoot3, ".agents", "skills", "zam", "SKILL.md"),
14271
+ to: join20(".agents", "skills", "zam", "SKILL.md"),
13786
14272
  agents: ["codex"]
13787
14273
  }
13788
14274
  ];
@@ -13842,15 +14328,15 @@ function linkPointsTo(sourceDir, destinationDir) {
13842
14328
  function isZamSkillCopy(destinationDir) {
13843
14329
  try {
13844
14330
  if (!lstatSync2(destinationDir).isDirectory()) return false;
13845
- const skillFile = join19(destinationDir, "SKILL.md");
13846
- if (!existsSync17(skillFile)) return false;
13847
- return /^name:\s*zam\s*$/m.test(readFileSync14(skillFile, "utf8"));
14331
+ const skillFile = join20(destinationDir, "SKILL.md");
14332
+ if (!existsSync18(skillFile)) return false;
14333
+ return /^name:\s*zam\s*$/m.test(readFileSync15(skillFile, "utf8"));
13848
14334
  } catch {
13849
14335
  return false;
13850
14336
  }
13851
14337
  }
13852
14338
  function classifySkillDestination(sourceDir, destinationDir) {
13853
- if (!existsSync17(sourceDir)) return "source-missing";
14339
+ if (!existsSync18(sourceDir)) return "source-missing";
13854
14340
  if (!isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir)) {
13855
14341
  return "source-directory";
13856
14342
  }
@@ -13867,9 +14353,9 @@ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {})
13867
14353
  };
13868
14354
  for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
13869
14355
  if (!pairAgents.some((agent) => agents.has(agent))) continue;
13870
- const sourceDir = dirname8(from);
13871
- const destinationDir = dirname8(join19(cwd, to));
13872
- const label = dirname8(to);
14356
+ const sourceDir = dirname9(from);
14357
+ const destinationDir = dirname9(join20(cwd, to));
14358
+ const label = dirname9(to);
13873
14359
  const state = classifySkillDestination(sourceDir, destinationDir);
13874
14360
  if (state === "source-missing") {
13875
14361
  if (!opts.quiet) {
@@ -13926,7 +14412,7 @@ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {})
13926
14412
  if (destinationExists) {
13927
14413
  rmSync2(destinationDir, { recursive: true, force: true });
13928
14414
  }
13929
- mkdirSync12(dirname8(destinationDir), { recursive: true });
14415
+ mkdirSync13(dirname9(destinationDir), { recursive: true });
13930
14416
  symlinkSync(
13931
14417
  sourceDir,
13932
14418
  destinationDir,
@@ -13942,8 +14428,8 @@ function inspectSkillLinks(cwd = process.cwd(), agents = parseSetupAgents()) {
13942
14428
  const results = [];
13943
14429
  for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
13944
14430
  if (!pairAgents.some((agent) => agents.has(agent))) continue;
13945
- const sourceDir = dirname8(from);
13946
- const destinationDir = dirname8(join19(cwd, to));
14431
+ const sourceDir = dirname9(from);
14432
+ const destinationDir = dirname9(join20(cwd, to));
13947
14433
  results.push({
13948
14434
  agents: pairAgents,
13949
14435
  source: sourceDir,
@@ -13969,15 +14455,15 @@ function upsertMarkedBlock(dest, blockBody, dryRun) {
13969
14455
  const block = `${ZAM_BLOCK_START}
13970
14456
  ${blockBody.trim()}
13971
14457
  ${ZAM_BLOCK_END}`;
13972
- if (!existsSync17(dest)) {
14458
+ if (!existsSync18(dest)) {
13973
14459
  if (!dryRun) {
13974
- mkdirSync12(dirname8(dest), { recursive: true });
13975
- writeFileSync10(dest, `${block}
14460
+ mkdirSync13(dirname9(dest), { recursive: true });
14461
+ writeFileSync11(dest, `${block}
13976
14462
  `, "utf8");
13977
14463
  }
13978
14464
  return "write";
13979
14465
  }
13980
- const existing = readFileSync14(dest, "utf8");
14466
+ const existing = readFileSync15(dest, "utf8");
13981
14467
  if (existing.includes(block)) return "skip";
13982
14468
  const start = existing.indexOf(ZAM_BLOCK_START);
13983
14469
  const end = existing.indexOf(ZAM_BLOCK_END);
@@ -13985,7 +14471,7 @@ ${ZAM_BLOCK_END}`;
13985
14471
 
13986
14472
  ${block}
13987
14473
  `;
13988
- if (!dryRun) writeFileSync10(dest, next, "utf8");
14474
+ if (!dryRun) writeFileSync11(dest, next, "utf8");
13989
14475
  return start >= 0 && end > start ? "update" : "write";
13990
14476
  }
13991
14477
  function logInstructionAction(action, label, dryRun) {
@@ -13997,8 +14483,8 @@ function logInstructionAction(action, label, dryRun) {
13997
14483
  }
13998
14484
  function writeClaudeMd(skipClaudeMd, cwd = process.cwd(), opts = {}) {
13999
14485
  if (skipClaudeMd) return;
14000
- const dest = join19(cwd, "CLAUDE.md");
14001
- if (existsSync17(dest)) {
14486
+ const dest = join20(cwd, "CLAUDE.md");
14487
+ if (existsSync18(dest)) {
14002
14488
  if (!opts.updateExisting) {
14003
14489
  console.log(` skip CLAUDE.md (already present)`);
14004
14490
  return;
@@ -14041,14 +14527,14 @@ Use \`zam connector setup turso\` to store cloud credentials in
14041
14527
  if (opts.dryRun) {
14042
14528
  console.log(` would write CLAUDE.md`);
14043
14529
  } else {
14044
- writeFileSync10(dest, content, "utf8");
14530
+ writeFileSync11(dest, content, "utf8");
14045
14531
  console.log(` write CLAUDE.md`);
14046
14532
  }
14047
14533
  }
14048
14534
  function writeAgentsMd(skipAgentsMd, cwd = process.cwd(), opts = {}) {
14049
14535
  if (skipAgentsMd) return;
14050
- const dest = join19(cwd, "AGENTS.md");
14051
- if (existsSync17(dest)) {
14536
+ const dest = join20(cwd, "AGENTS.md");
14537
+ if (existsSync18(dest)) {
14052
14538
  if (!opts.updateExisting) {
14053
14539
  console.log(` skip AGENTS.md (already present)`);
14054
14540
  return;
@@ -14098,12 +14584,12 @@ Codex discovers repository skills under \`.agents/skills/\`. Run
14098
14584
  if (opts.dryRun) {
14099
14585
  console.log(` would write AGENTS.md`);
14100
14586
  } else {
14101
- writeFileSync10(dest, content, "utf8");
14587
+ writeFileSync11(dest, content, "utf8");
14102
14588
  console.log(` write AGENTS.md`);
14103
14589
  }
14104
14590
  }
14105
14591
  function writeCopilotInstructions(cwd = process.cwd(), opts = {}) {
14106
- const dest = join19(cwd, ".github", "copilot-instructions.md");
14592
+ const dest = join20(cwd, ".github", "copilot-instructions.md");
14107
14593
  const action = upsertMarkedBlock(
14108
14594
  dest,
14109
14595
  `## ZAM learning sessions
@@ -14144,13 +14630,13 @@ async function resolveUser(opts, db, resolveOpts) {
14144
14630
  }
14145
14631
 
14146
14632
  // src/cli/workspaces/backup.ts
14147
- import { mkdirSync as mkdirSync13 } from "fs";
14148
- import { join as join20 } from "path";
14633
+ import { mkdirSync as mkdirSync14 } from "fs";
14634
+ import { join as join21 } from "path";
14149
14635
  async function backupDatabaseTo(db, targetDir) {
14150
- const backupDir = join20(targetDir, "zam-backups");
14151
- mkdirSync13(backupDir, { recursive: true });
14636
+ const backupDir = join21(targetDir, "zam-backups");
14637
+ mkdirSync14(backupDir, { recursive: true });
14152
14638
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
14153
- const dest = join20(backupDir, `zam-${stamp}.db`);
14639
+ const dest = join21(backupDir, `zam-${stamp}.db`);
14154
14640
  await db.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
14155
14641
  return dest;
14156
14642
  }
@@ -14281,20 +14767,20 @@ bridgeCommand.command("workspace-info").description("Report the workspace dir, i
14281
14767
  activeWorkspace,
14282
14768
  workspaceDir: activeWorkspace.path,
14283
14769
  defaultWorkspaceDir: defaultWorkspaceDir(),
14284
- dataDir: join21(homedir12(), ".zam")
14770
+ dataDir: join22(homedir13(), ".zam")
14285
14771
  });
14286
14772
  });
14287
14773
  });
14288
14774
  function provisionConfiguredWorkspaces() {
14289
14775
  return getConfiguredWorkspaces().flatMap(
14290
- (workspace) => existsSync18(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
14776
+ (workspace) => existsSync19(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
14291
14777
  );
14292
14778
  }
14293
14779
  function buildWorkspaceLinkHealth(workspaces) {
14294
14780
  const agents = parseSetupAgents();
14295
14781
  const map = {};
14296
14782
  for (const workspace of workspaces) {
14297
- if (!existsSync18(workspace.path)) continue;
14783
+ if (!existsSync19(workspace.path)) continue;
14298
14784
  const links = inspectSkillLinks(workspace.path, agents);
14299
14785
  map[workspace.id] = {
14300
14786
  health: summarizeSkillLinkHealth(links),
@@ -14324,7 +14810,7 @@ bridgeCommand.command("workspace-list").description("List configured ZAM workspa
14324
14810
  activeWorkspace,
14325
14811
  workspaceDir: activeWorkspace.path,
14326
14812
  defaultWorkspaceDir: defaultWorkspaceDir(),
14327
- dataDir: join21(homedir12(), ".zam"),
14813
+ dataDir: join22(homedir13(), ".zam"),
14328
14814
  linkHealth: buildWorkspaceLinkHealth(workspaces)
14329
14815
  });
14330
14816
  });
@@ -14339,7 +14825,7 @@ bridgeCommand.command("workspace-repair-links").description(
14339
14825
  if (!id) jsonError("A non-empty --id is required");
14340
14826
  const workspace = getConfiguredWorkspaces().find((item) => item.id === id);
14341
14827
  if (!workspace) jsonError(`Workspace "${id}" is not configured`);
14342
- if (!existsSync18(workspace.path)) {
14828
+ if (!existsSync19(workspace.path)) {
14343
14829
  jsonError(`Workspace path does not exist: ${workspace.path}`);
14344
14830
  }
14345
14831
  const agents = parseSetupAgents(opts.agents);
@@ -14371,7 +14857,7 @@ bridgeCommand.command("workspace-add").description("Register an existing directo
14371
14857
  const raw = String(opts.path ?? "").trim();
14372
14858
  if (!raw) jsonError("A non-empty --path is required");
14373
14859
  const path = resolve5(raw);
14374
- if (!existsSync18(path)) jsonError(`Workspace path does not exist: ${path}`);
14860
+ if (!existsSync19(path)) jsonError(`Workspace path does not exist: ${path}`);
14375
14861
  const id = opts.id ? String(opts.id).trim() : void 0;
14376
14862
  if (opts.id && !id) jsonError("A non-empty --id is required");
14377
14863
  const kind = parseBridgeWorkspaceKind(opts.kind);
@@ -14416,7 +14902,7 @@ bridgeCommand.command("set-workspace-dir").description("Set the personal workspa
14416
14902
  const raw = String(opts.dir ?? "").trim();
14417
14903
  if (!raw) jsonError("A non-empty --dir is required");
14418
14904
  const dir = resolve5(raw);
14419
- if (!existsSync18(dir)) jsonError(`Workspace path does not exist: ${dir}`);
14905
+ if (!existsSync19(dir)) jsonError(`Workspace path does not exist: ${dir}`);
14420
14906
  const skillLinks = wireSkills(dir, parseSetupAgents(), { quiet: true });
14421
14907
  await withDb2(async (db) => {
14422
14908
  const workspace = await activateWorkspacePath(db, dir);
@@ -14479,7 +14965,7 @@ bridgeCommand.command("agent-open").description("Launch an agent harness in the
14479
14965
  jsonError(`Workspace is not configured: ${opts.workspace}`);
14480
14966
  }
14481
14967
  const activeWorkspace = await ensureActiveWorkspace(db);
14482
- const workspace = opts.dir ? existsSync18(opts.dir) ? opts.dir : homedir12() : existingWorkspaceDirOrHome(configuredWorkspace ?? activeWorkspace);
14968
+ const workspace = opts.dir ? existsSync19(opts.dir) ? opts.dir : homedir13() : existingWorkspaceDirOrHome(configuredWorkspace ?? activeWorkspace);
14483
14969
  launchHarness(harness, {
14484
14970
  executable,
14485
14971
  workspace,
@@ -14851,7 +15337,7 @@ bridgeCommand.command("discover-skills").description(
14851
15337
  "20"
14852
15338
  ).action(async (opts) => {
14853
15339
  try {
14854
- const monitorDir = join21(homedir12(), ".zam", "monitor");
15340
+ const monitorDir = join22(homedir13(), ".zam", "monitor");
14855
15341
  let files;
14856
15342
  try {
14857
15343
  files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
@@ -14864,7 +15350,7 @@ bridgeCommand.command("discover-skills").description(
14864
15350
  return;
14865
15351
  }
14866
15352
  const limit = Number(opts.limit);
14867
- const sorted = files.map((f) => ({ name: f, path: join21(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
15353
+ const sorted = files.map((f) => ({ name: f, path: join22(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
14868
15354
  const sessionCommands = /* @__PURE__ */ new Map();
14869
15355
  for (const file of sorted) {
14870
15356
  const sessionId = file.name.replace(".jsonl", "");
@@ -14976,7 +15462,7 @@ bridgeCommand.command("observe-ui-snapshot").description(
14976
15462
  });
14977
15463
  function resolveWindowsPowerShell() {
14978
15464
  try {
14979
- execFileSync3("where.exe", ["pwsh.exe"], { stdio: "ignore" });
15465
+ execFileSync4("where.exe", ["pwsh.exe"], { stdio: "ignore" });
14980
15466
  return "pwsh";
14981
15467
  } catch {
14982
15468
  return "powershell";
@@ -14991,7 +15477,7 @@ function captureScreenshot(outputPath, hwnd, processName) {
14991
15477
  throw new Error(`Invalid process name format: ${processName}`);
14992
15478
  }
14993
15479
  if (platform === "win32") {
14994
- const stdout = execFileSync3(
15480
+ const stdout = execFileSync4(
14995
15481
  resolveWindowsPowerShell(),
14996
15482
  [
14997
15483
  "-NoProfile",
@@ -15304,13 +15790,13 @@ Write-CaptureResult "fullscreen" $hwndVal $matchedBy
15304
15790
  } else if (platform === "darwin") {
15305
15791
  if (hwnd) {
15306
15792
  const parsedHwnd = hwnd.startsWith("0x") ? parseInt(hwnd, 16) : parseInt(hwnd, 10);
15307
- execFileSync3("screencapture", ["-l", String(parsedHwnd), outputPath], {
15793
+ execFileSync4("screencapture", ["-l", String(parsedHwnd), outputPath], {
15308
15794
  stdio: "pipe"
15309
15795
  });
15310
15796
  return { method: "screencapture-window", target: null };
15311
15797
  } else if (processName) {
15312
15798
  try {
15313
- const windowId = execFileSync3(
15799
+ const windowId = execFileSync4(
15314
15800
  "osascript",
15315
15801
  [
15316
15802
  "-e",
@@ -15319,17 +15805,17 @@ Write-CaptureResult "fullscreen" $hwndVal $matchedBy
15319
15805
  { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }
15320
15806
  ).trim();
15321
15807
  if (windowId && /^\d+$/.test(windowId)) {
15322
- execFileSync3("screencapture", ["-l", windowId, outputPath], {
15808
+ execFileSync4("screencapture", ["-l", windowId, outputPath], {
15323
15809
  stdio: "pipe"
15324
15810
  });
15325
15811
  return { method: "screencapture-window", target: null };
15326
15812
  }
15327
15813
  } catch {
15328
15814
  }
15329
- execFileSync3("screencapture", ["-x", outputPath], { stdio: "pipe" });
15815
+ execFileSync4("screencapture", ["-x", outputPath], { stdio: "pipe" });
15330
15816
  return { method: "screencapture-full", target: null };
15331
15817
  } else {
15332
- execFileSync3("screencapture", ["-x", outputPath], { stdio: "pipe" });
15818
+ execFileSync4("screencapture", ["-x", outputPath], { stdio: "pipe" });
15333
15819
  return { method: "screencapture-full", target: null };
15334
15820
  }
15335
15821
  } else {
@@ -15366,7 +15852,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
15366
15852
  return;
15367
15853
  }
15368
15854
  }
15369
- const outputPath = opts.image ?? opts.output ?? join21(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
15855
+ const outputPath = opts.image ?? opts.output ?? join22(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
15370
15856
  const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
15371
15857
  if (!isProvided) {
15372
15858
  const post = decidePostCapture(policy, {
@@ -15394,7 +15880,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
15394
15880
  return;
15395
15881
  }
15396
15882
  }
15397
- const imageBytes = readFileSync15(outputPath);
15883
+ const imageBytes = readFileSync16(outputPath);
15398
15884
  const base64 = imageBytes.toString("base64");
15399
15885
  jsonOut2({
15400
15886
  sessionId: opts.session ?? null,
@@ -15421,11 +15907,11 @@ bridgeCommand.command("start-recording").description("Start screen recording in
15421
15907
  return;
15422
15908
  }
15423
15909
  const sessionId = opts.session;
15424
- const statePath = join21(tmpdir3(), `zam-recording-${sessionId}.json`);
15910
+ const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
15425
15911
  const defaultExt = platform === "win32" ? ".mkv" : ".mov";
15426
- const outputPath = opts.output ?? join21(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
15427
- const { existsSync: existsSync27, writeFileSync: writeFileSync15, openSync, closeSync } = await import("fs");
15428
- if (existsSync27(statePath)) {
15912
+ const outputPath = opts.output ?? join22(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
15913
+ const { existsSync: existsSync28, writeFileSync: writeFileSync16, openSync, closeSync } = await import("fs");
15914
+ if (existsSync28(statePath)) {
15429
15915
  jsonOut2({
15430
15916
  sessionId,
15431
15917
  started: false,
@@ -15433,7 +15919,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
15433
15919
  });
15434
15920
  return;
15435
15921
  }
15436
- const logPath = join21(tmpdir3(), `zam-recording-${sessionId}.log`);
15922
+ const logPath = join22(tmpdir3(), `zam-recording-${sessionId}.log`);
15437
15923
  let logFd;
15438
15924
  try {
15439
15925
  logFd = openSync(logPath, "w");
@@ -15479,7 +15965,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
15479
15965
  }
15480
15966
  child.unref();
15481
15967
  if (child.pid) {
15482
- writeFileSync15(
15968
+ writeFileSync16(
15483
15969
  statePath,
15484
15970
  JSON.stringify({
15485
15971
  pid: child.pid,
@@ -15515,9 +16001,9 @@ bridgeCommand.command("stop-recording").description(
15515
16001
  return;
15516
16002
  }
15517
16003
  const sessionId = opts.session;
15518
- const statePath = join21(tmpdir3(), `zam-recording-${sessionId}.json`);
15519
- const { existsSync: existsSync27, readFileSync: readFileSync20, rmSync: rmSync4 } = await import("fs");
15520
- if (!existsSync27(statePath)) {
16004
+ const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
16005
+ const { existsSync: existsSync28, readFileSync: readFileSync21, rmSync: rmSync4 } = await import("fs");
16006
+ if (!existsSync28(statePath)) {
15521
16007
  jsonOut2({
15522
16008
  sessionId,
15523
16009
  stopped: false,
@@ -15525,7 +16011,7 @@ bridgeCommand.command("stop-recording").description(
15525
16011
  });
15526
16012
  return;
15527
16013
  }
15528
- const state = JSON.parse(readFileSync20(statePath, "utf8"));
16014
+ const state = JSON.parse(readFileSync21(statePath, "utf8"));
15529
16015
  const { pid, outputPath } = state;
15530
16016
  try {
15531
16017
  process.kill(pid, "SIGINT");
@@ -15554,7 +16040,7 @@ bridgeCommand.command("stop-recording").description(
15554
16040
  rmSync4(statePath, { force: true });
15555
16041
  } catch {
15556
16042
  }
15557
- if (!existsSync27(outputPath)) {
16043
+ if (!existsSync28(outputPath)) {
15558
16044
  jsonOut2({
15559
16045
  sessionId,
15560
16046
  stopped: false,
@@ -15791,7 +16277,7 @@ bridgeCommand.command("cloud-model-hint").description("Suggest a cloud model for
15791
16277
  });
15792
16278
  bridgeCommand.command("local-llm-hints").description("Detect installed local LLM servers and suggest defaults (JSON)").action(() => {
15793
16279
  const profile = getSystemProfile();
15794
- const flmInstalled = hasCommand("flm") || existsSync18("C:\\Program Files\\flm\\flm.exe");
16280
+ const flmInstalled = hasCommand("flm") || existsSync19("C:\\Program Files\\flm\\flm.exe");
15795
16281
  const ollamaInstalled = isOllamaInstalled();
15796
16282
  const runners = [
15797
16283
  { id: "flm", label: "FastFlowLM", installed: flmInstalled },
@@ -15931,6 +16417,93 @@ bridgeCommand.command("evaluate-answer").description(
15931
16417
  }
15932
16418
  });
15933
16419
  });
16420
+ function parseDiscussionThread(raw) {
16421
+ if (!raw) return [];
16422
+ let parsed;
16423
+ try {
16424
+ parsed = JSON.parse(raw);
16425
+ } catch {
16426
+ throw new Error("Invalid --thread: not valid JSON");
16427
+ }
16428
+ if (!Array.isArray(parsed)) {
16429
+ throw new Error("Invalid --thread: expected a JSON array of turns");
16430
+ }
16431
+ return parsed.map((turn, index) => {
16432
+ const candidate = turn;
16433
+ if (candidate?.role !== "user" && candidate?.role !== "assistant" || typeof candidate?.content !== "string") {
16434
+ throw new Error(
16435
+ `Invalid --thread: turn ${index} must be {"role": "user"|"assistant", "content": string}`
16436
+ );
16437
+ }
16438
+ return { role: candidate.role, content: candidate.content };
16439
+ });
16440
+ }
16441
+ bridgeCommand.command("discuss-review").description(
16442
+ "Answer one turn of the post-reveal follow-up discussion about a card (JSON)"
16443
+ ).requiredOption("--slug <slug>", "Token slug").requiredOption("--concept <concept>", "Target concept text").requiredOption("--domain <domain>", "Token domain").requiredOption("--bloom-level <level>", "Bloom taxonomy level").requiredOption("--question <question>", "Question prompt presented").requiredOption("--user-answer <answer>", "User's typed answer").requiredOption("--message <text>", "The learner's newest discussion turn").option("--feedback <text>", "AI feedback already shown for this answer").option(
16444
+ "--thread <json>",
16445
+ 'Prior turns, oldest first, as JSON: [{"role":"user"|"assistant","content":"\u2026"},\u2026]'
16446
+ ).option("--context <context>", "Optional token context details").option("--source-link <link>", "Optional source link").option(
16447
+ "--source-content <content>",
16448
+ "Pre-resolved source reference content (skips re-fetch when set)"
16449
+ ).action(async (opts) => {
16450
+ await withDb2(async (db) => {
16451
+ const isEnabled = await getSetting(db, "llm.enabled") === "true";
16452
+ if (!isEnabled) {
16453
+ jsonOut2({
16454
+ success: false,
16455
+ error: "LLM integration is disabled",
16456
+ reply: ""
16457
+ });
16458
+ return;
16459
+ }
16460
+ let thread;
16461
+ try {
16462
+ thread = parseDiscussionThread(opts.thread);
16463
+ } catch (err) {
16464
+ jsonOut2({
16465
+ success: false,
16466
+ error: err.message,
16467
+ reply: ""
16468
+ });
16469
+ return;
16470
+ }
16471
+ let resolvedContextContent = opts.sourceContent ?? null;
16472
+ if (resolvedContextContent == null && opts.sourceLink) {
16473
+ try {
16474
+ const resolved = await resolveReviewContext(opts.sourceLink);
16475
+ resolvedContextContent = resolved?.content ?? null;
16476
+ } catch {
16477
+ }
16478
+ }
16479
+ try {
16480
+ const result = await discussReviewViaLLM(db, {
16481
+ slug: opts.slug,
16482
+ concept: opts.concept,
16483
+ domain: opts.domain,
16484
+ bloomLevel: Number(opts.bloomLevel),
16485
+ context: opts.context,
16486
+ question: opts.question,
16487
+ userAnswer: opts.userAnswer,
16488
+ sourceLinkContent: resolvedContextContent,
16489
+ feedback: opts.feedback ?? null,
16490
+ thread,
16491
+ message: opts.message
16492
+ });
16493
+ jsonOut2({
16494
+ success: true,
16495
+ reply: result.text,
16496
+ replyModel: result.model
16497
+ });
16498
+ } catch (err) {
16499
+ jsonOut2({
16500
+ success: false,
16501
+ error: err.message,
16502
+ reply: ""
16503
+ });
16504
+ }
16505
+ });
16506
+ });
15934
16507
  bridgeCommand.command("desktop-bootstrap").description("Initialize first-run desktop state (JSON)").option("--user <id>", "Preferred user ID when none is configured").action(async (opts) => {
15935
16508
  await withDb2(async (db) => {
15936
16509
  const userId = await ensureDefaultUser(db, opts.user);
@@ -15959,6 +16532,54 @@ bridgeCommand.command("get-settings").description("Get active ZAM settings (JSON
15959
16532
  });
15960
16533
  });
15961
16534
  });
16535
+ bridgeCommand.command("agent-harness-status").description(
16536
+ "Detect installed agent harnesses and their ZAM MCP configuration state (JSON)"
16537
+ ).action(() => {
16538
+ const report = inspectConnectHarnesses();
16539
+ jsonOut2({
16540
+ success: true,
16541
+ zamOnPath: report.zamOnPath,
16542
+ harnesses: report.harnesses
16543
+ });
16544
+ });
16545
+ bridgeCommand.command("agent-connect").description(
16546
+ "Run the idempotent agent-connect flow for one or all detected harnesses (JSON)"
16547
+ ).option("--harness <id>", "Explicit harness id (default: all detected)").option(
16548
+ "--auto-once",
16549
+ "First-run mode: skip when the auto-connect marker is already set; set the marker after a run that detected at least one harness"
16550
+ ).action(async (opts) => {
16551
+ if (opts.harness && !isConnectHarnessId(opts.harness)) {
16552
+ jsonOut2({
16553
+ success: false,
16554
+ error: `Unsupported harness: ${opts.harness}`
16555
+ });
16556
+ return;
16557
+ }
16558
+ const harness = opts.harness;
16559
+ const run = () => {
16560
+ const report = performAgentConnect({ harness });
16561
+ return {
16562
+ success: report.success,
16563
+ detected: report.detected,
16564
+ zamOnPath: report.zamOnPath,
16565
+ results: report.results.map(({ content: _content, ...rest }) => rest),
16566
+ skills: report.skills
16567
+ };
16568
+ };
16569
+ if (opts.autoOnce) {
16570
+ if (getAgentConnectAutoDone()) {
16571
+ jsonOut2({ success: true, skipped: true });
16572
+ return;
16573
+ }
16574
+ const payload = run();
16575
+ if (payload.detected.length > 0) {
16576
+ setAgentConnectAutoDone(true);
16577
+ }
16578
+ jsonOut2(payload);
16579
+ return;
16580
+ }
16581
+ jsonOut2(run());
16582
+ });
15962
16583
  async function readDatabaseUserSummaries(db) {
15963
16584
  return await db.prepare(
15964
16585
  `SELECT user_id AS id, COUNT(*) AS cardCount
@@ -18341,26 +18962,26 @@ var doctorCommand = new Command5("doctor").description(
18341
18962
  // src/cli/commands/git-sync.ts
18342
18963
  init_kernel();
18343
18964
  import { execSync as execSync5 } from "child_process";
18344
- import { chmodSync as chmodSync2, existsSync as existsSync19, writeFileSync as writeFileSync11 } from "fs";
18345
- import { join as join22 } from "path";
18965
+ import { chmodSync as chmodSync2, existsSync as existsSync20, writeFileSync as writeFileSync12 } from "fs";
18966
+ import { join as join23 } from "path";
18346
18967
  import { Command as Command6 } from "commander";
18347
18968
  function installHook2() {
18348
- const gitDir = join22(process.cwd(), ".git");
18349
- if (!existsSync19(gitDir)) {
18969
+ const gitDir = join23(process.cwd(), ".git");
18970
+ if (!existsSync20(gitDir)) {
18350
18971
  console.error(
18351
18972
  "Error: Current directory is not the root of a Git repository."
18352
18973
  );
18353
18974
  process.exit(1);
18354
18975
  }
18355
- const hooksDir = join22(gitDir, "hooks");
18356
- const hookPath = join22(hooksDir, "post-commit");
18976
+ const hooksDir = join23(gitDir, "hooks");
18977
+ const hookPath = join23(hooksDir, "post-commit");
18357
18978
  const hookContent = `#!/bin/sh
18358
18979
  # ZAM Spaced Repetition Auto-Stale Hook
18359
18980
  # Triggered automatically on git commits to decay modified concept cards.
18360
18981
  zam git-sync --commit HEAD --quiet
18361
18982
  `;
18362
18983
  try {
18363
- writeFileSync11(hookPath, hookContent, { encoding: "utf-8", flag: "w" });
18984
+ writeFileSync12(hookPath, hookContent, { encoding: "utf-8", flag: "w" });
18364
18985
  try {
18365
18986
  chmodSync2(hookPath, "755");
18366
18987
  } catch (_e) {
@@ -18463,7 +19084,7 @@ ZAM Auto-Stale Complete: Scanned ${changedFiles.length} file(s).`
18463
19084
 
18464
19085
  // src/cli/commands/goal.ts
18465
19086
  init_kernel();
18466
- import { existsSync as existsSync20, mkdirSync as mkdirSync14 } from "fs";
19087
+ import { existsSync as existsSync21, mkdirSync as mkdirSync15 } from "fs";
18467
19088
  import { resolve as resolve6 } from "path";
18468
19089
  import { input as input2 } from "@inquirer/prompts";
18469
19090
  import { Command as Command7 } from "commander";
@@ -18487,7 +19108,7 @@ goalCommand.command("list").description("List all goals").option(
18487
19108
  "Filter by status (active, completed, paused, abandoned)"
18488
19109
  ).option("--tree", "Show goals as a tree with parent/child relationships").option("--json", "Output as JSON").action(async (opts) => {
18489
19110
  const goalsDir = await resolveGoalsDir();
18490
- if (!existsSync20(goalsDir)) {
19111
+ if (!existsSync21(goalsDir)) {
18491
19112
  console.error(`Goals directory not found: ${goalsDir}`);
18492
19113
  console.error(
18493
19114
  "Set it with: zam settings set personal.goals_dir /path/to/goals"
@@ -18588,8 +19209,8 @@ ${"\u2500".repeat(50)}`);
18588
19209
  });
18589
19210
  goalCommand.command("create").description("Create a new goal").option("--slug <slug>", "Goal slug (used as filename)").option("--title <title>", "Goal title").option("--parent <slug>", "Parent goal slug").option("--description <text>", "Goal description").option("--json", "Output as JSON").action(async (opts) => {
18590
19211
  const goalsDir = await resolveGoalsDir();
18591
- if (!existsSync20(goalsDir)) {
18592
- mkdirSync14(goalsDir, { recursive: true });
19212
+ if (!existsSync21(goalsDir)) {
19213
+ mkdirSync15(goalsDir, { recursive: true });
18593
19214
  }
18594
19215
  let slug = opts.slug;
18595
19216
  let title = opts.title;
@@ -18649,22 +19270,22 @@ goalCommand.command("status <slug> <status>").description("Update a goal's statu
18649
19270
 
18650
19271
  // src/cli/commands/init.ts
18651
19272
  init_kernel();
18652
- import { existsSync as existsSync21, mkdirSync as mkdirSync15, writeFileSync as writeFileSync12 } from "fs";
18653
- import { homedir as homedir13 } from "os";
18654
- import { join as join23, resolve as resolve7 } from "path";
19273
+ import { existsSync as existsSync22, mkdirSync as mkdirSync16, writeFileSync as writeFileSync13 } from "fs";
19274
+ import { homedir as homedir14 } from "os";
19275
+ import { join as join24, resolve as resolve7 } from "path";
18655
19276
  import { confirm, input as input3 } from "@inquirer/prompts";
18656
19277
  import { Command as Command8 } from "commander";
18657
- var HOME2 = homedir13();
19278
+ var HOME2 = homedir14();
18658
19279
  function printLine(char = "\u2550", len = 60, color = "\x1B[36m") {
18659
19280
  console.log(`${color}${char.repeat(len)}\x1B[0m`);
18660
19281
  }
18661
19282
  function bootstrapSandboxWorkspace(workspaceDir) {
18662
- mkdirSync15(join23(workspaceDir, "beliefs"), { recursive: true });
18663
- mkdirSync15(join23(workspaceDir, "goals"), { recursive: true });
18664
- mkdirSync15(join23(workspaceDir, "skills"), { recursive: true });
18665
- const worldviewFile = join23(workspaceDir, "beliefs", "worldview.md");
18666
- if (!existsSync21(worldviewFile)) {
18667
- writeFileSync12(
19283
+ mkdirSync16(join24(workspaceDir, "beliefs"), { recursive: true });
19284
+ mkdirSync16(join24(workspaceDir, "goals"), { recursive: true });
19285
+ mkdirSync16(join24(workspaceDir, "skills"), { recursive: true });
19286
+ const worldviewFile = join24(workspaceDir, "beliefs", "worldview.md");
19287
+ if (!existsSync22(worldviewFile)) {
19288
+ writeFileSync13(
18668
19289
  worldviewFile,
18669
19290
  `# Personal Worldview
18670
19291
 
@@ -18676,9 +19297,9 @@ Here, I declare the core concepts and principles I want to master.
18676
19297
  "utf8"
18677
19298
  );
18678
19299
  }
18679
- const goalsFile = join23(workspaceDir, "goals", "goals.md");
18680
- if (!existsSync21(goalsFile)) {
18681
- writeFileSync12(
19300
+ const goalsFile = join24(workspaceDir, "goals", "goals.md");
19301
+ if (!existsSync22(goalsFile)) {
19302
+ writeFileSync13(
18682
19303
  goalsFile,
18683
19304
  `# Personal Goals
18684
19305
 
@@ -18700,7 +19321,7 @@ var initCommand = new Command8("init").description("Launch the guided interactiv
18700
19321
  );
18701
19322
  printLine();
18702
19323
  console.log("\n\x1B[1m[1/5] Setting up Local Workspace Sandbox\x1B[0m");
18703
- const defaultWorkspace = join23(HOME2, "Documents", "zam");
19324
+ const defaultWorkspace = join24(HOME2, "Documents", "zam");
18704
19325
  const workspacePath = resolve7(
18705
19326
  await input3({
18706
19327
  message: "Choose your ZAM workspace directory:",
@@ -19902,7 +20523,7 @@ observerCommand.command("revoke <process>").description("Remove a process from o
19902
20523
 
19903
20524
  // src/cli/commands/profile.ts
19904
20525
  init_kernel();
19905
- import { dirname as dirname9, resolve as resolve8 } from "path";
20526
+ import { dirname as dirname10, resolve as resolve8 } from "path";
19906
20527
  import { Command as Command13 } from "commander";
19907
20528
  var C2 = {
19908
20529
  reset: "\x1B[0m",
@@ -19943,7 +20564,7 @@ var profileCommand = new Command13("profile").description("Show or change this m
19943
20564
  mode: getInstallMode(),
19944
20565
  personalDir,
19945
20566
  syncProvider: detectSyncProvider(personalDir),
19946
- dataDir: dirname9(dbPath),
20567
+ dataDir: dirname10(dbPath),
19947
20568
  dbPath
19948
20569
  };
19949
20570
  if (opts.json) {
@@ -20332,7 +20953,7 @@ ${"\u2550".repeat(50)}`);
20332
20953
 
20333
20954
  // src/cli/commands/session.ts
20334
20955
  init_kernel();
20335
- import { readFileSync as readFileSync16 } from "fs";
20956
+ import { readFileSync as readFileSync17 } from "fs";
20336
20957
  import { input as input6, select as select2 } from "@inquirer/prompts";
20337
20958
  import { Command as Command16 } from "commander";
20338
20959
  var sessionCommand = new Command16("session").description(
@@ -20523,7 +21144,7 @@ function loadPatternFile(path) {
20523
21144
  if (!path) return [];
20524
21145
  let parsed;
20525
21146
  try {
20526
- parsed = JSON.parse(readFileSync16(path, "utf-8"));
21147
+ parsed = JSON.parse(readFileSync17(path, "utf-8"));
20527
21148
  } catch (err) {
20528
21149
  throw new Error(
20529
21150
  `Cannot read synthesis patterns from ${path}: ${err.message}`
@@ -20714,7 +21335,7 @@ sessionCommand.command("end").description("End a session and show summary").requ
20714
21335
 
20715
21336
  // src/cli/commands/settings.ts
20716
21337
  init_kernel();
20717
- import { existsSync as existsSync22 } from "fs";
21338
+ import { existsSync as existsSync23 } from "fs";
20718
21339
  import { Command as Command17 } from "commander";
20719
21340
  var settingsCommand = new Command17("settings").description(
20720
21341
  "Manage user settings"
@@ -20879,7 +21500,7 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
20879
21500
  console.log("\nValidation:");
20880
21501
  for (const [name, path] of Object.entries(paths)) {
20881
21502
  if (path) {
20882
- const exists = existsSync22(path);
21503
+ const exists = existsSync23(path);
20883
21504
  console.log(
20884
21505
  ` ${name.padEnd(9)}: ${exists ? "\x1B[32m\u2713 Valid folder\x1B[0m" : "\x1B[31m\u2717 Directory does not exist\x1B[0m"}`
20885
21506
  );
@@ -20934,9 +21555,49 @@ async function activateMachineProviderConfig(db) {
20934
21555
  ` activate ${providerCount} machine-local provider(s) from ~/.zam/config.json`
20935
21556
  );
20936
21557
  }
21558
+ function connectDetectedAgentHarnesses(skip, dryRun) {
21559
+ if (skip) return;
21560
+ try {
21561
+ const report = performAgentConnect({ dryRun });
21562
+ if (report.detected.length === 0) {
21563
+ console.log(
21564
+ " skip no supported agent harness detected (zam agent connect <harness> configures one explicitly)"
21565
+ );
21566
+ return;
21567
+ }
21568
+ for (const result of report.results) {
21569
+ if (result.error) {
21570
+ console.warn(` warn ${result.harness}: ${result.error}`);
21571
+ } else if (dryRun) {
21572
+ console.log(
21573
+ ` plan ${result.harness}: would ensure MCP config at ${result.path}`
21574
+ );
21575
+ } else if (result.alreadyConfigured) {
21576
+ console.log(
21577
+ ` skip ${result.harness}: MCP already configured (${result.path})`
21578
+ );
21579
+ } else {
21580
+ console.log(
21581
+ ` wire ${result.harness}: MCP configured (${result.path})`
21582
+ );
21583
+ }
21584
+ }
21585
+ if (report.skills) {
21586
+ console.log(
21587
+ ` wire global ZAM skill: ${report.skills.refreshed}/${report.skills.total} locations`
21588
+ );
21589
+ }
21590
+ } catch (err) {
21591
+ console.warn(` warn agent connect: ${err.message}`);
21592
+ }
21593
+ }
20937
21594
  var setupCommand = new Command18("setup").description(
20938
21595
  "Link ZAM skill directories into this workspace and initialize the database"
20939
- ).option("--force", "replace an unmanaged existing ZAM skill directory", false).option("--skip-init", "skip database initialization", false).option("--skip-claude-md", "skip CLAUDE.md generation", false).option("--skip-agents-md", "skip AGENTS.md generation", false).option("--target <path>", "repository/workspace directory to set up").option(
21596
+ ).option("--force", "replace an unmanaged existing ZAM skill directory", false).option("--skip-init", "skip database initialization", false).option("--skip-claude-md", "skip CLAUDE.md generation", false).option("--skip-agents-md", "skip AGENTS.md generation", false).option(
21597
+ "--skip-agent-connect",
21598
+ "skip wiring detected agent harnesses via MCP",
21599
+ false
21600
+ ).option("--target <path>", "repository/workspace directory to set up").option(
20940
21601
  "--agents <list>",
20941
21602
  "comma-separated agents to wire: all, claude, copilot, codex, agent"
20942
21603
  ).option(
@@ -20963,6 +21624,7 @@ var setupCommand = new Command18("setup").description(
20963
21624
  dryRun: opts.dryRun
20964
21625
  });
20965
21626
  await initDatabase(opts.skipInit || opts.dryRun);
21627
+ connectDetectedAgentHarnesses(opts.skipAgentConnect, opts.dryRun);
20966
21628
  if (agents.has("claude")) {
20967
21629
  writeClaudeMd(opts.skipClaudeMd, target, {
20968
21630
  dryRun: opts.dryRun,
@@ -21076,8 +21738,8 @@ skillCommand.command("add").description("Register a new agent skill").requiredOp
21076
21738
 
21077
21739
  // src/cli/commands/snapshot.ts
21078
21740
  init_kernel();
21079
- import { existsSync as existsSync23, mkdirSync as mkdirSync16, readFileSync as readFileSync17, writeFileSync as writeFileSync13 } from "fs";
21080
- import { dirname as dirname10, join as join24 } from "path";
21741
+ import { existsSync as existsSync24, mkdirSync as mkdirSync17, readFileSync as readFileSync18, writeFileSync as writeFileSync14 } from "fs";
21742
+ import { dirname as dirname11, join as join25 } from "path";
21081
21743
  import { Command as Command20 } from "commander";
21082
21744
  function defaultOutName() {
21083
21745
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
@@ -21106,12 +21768,12 @@ var exportCmd = new Command20("export").description("Write a portable SQL-text s
21106
21768
  process.stdout.write(snapshot);
21107
21769
  return;
21108
21770
  }
21109
- const out = opts.out ?? join24(personalDir, "snapshots", defaultOutName());
21110
- const dir = dirname10(out);
21111
- if (dir && dir !== "." && !existsSync23(dir)) {
21112
- mkdirSync16(dir, { recursive: true });
21771
+ const out = opts.out ?? join25(personalDir, "snapshots", defaultOutName());
21772
+ const dir = dirname11(out);
21773
+ if (dir && dir !== "." && !existsSync24(dir)) {
21774
+ mkdirSync17(dir, { recursive: true });
21113
21775
  }
21114
- writeFileSync13(out, snapshot, "utf-8");
21776
+ writeFileSync14(out, snapshot, "utf-8");
21115
21777
  console.log(`Snapshot written: ${out}`);
21116
21778
  console.log(
21117
21779
  ` ${total} row(s)${nonEmpty.length ? ` \u2014 ${nonEmpty.join(", ")}` : ""}`
@@ -21125,11 +21787,11 @@ var exportCmd = new Command20("export").description("Write a portable SQL-text s
21125
21787
  var importCmd = new Command20("import").description("Restore a snapshot into the database").argument("<file>", "Snapshot file to restore").option("--force", "Overwrite a non-empty database", false).action(async (file, opts) => {
21126
21788
  let db;
21127
21789
  try {
21128
- if (!existsSync23(file)) {
21790
+ if (!existsSync24(file)) {
21129
21791
  console.error(`Error: Snapshot file not found: ${file}`);
21130
21792
  process.exit(1);
21131
21793
  }
21132
- const snapshot = readFileSync17(file, "utf-8");
21794
+ const snapshot = readFileSync18(file, "utf-8");
21133
21795
  db = await openDatabaseWithSync({ initialize: true });
21134
21796
  const result = await importSnapshot(db, snapshot, { force: opts.force });
21135
21797
  await db.close();
@@ -21147,11 +21809,11 @@ var importCmd = new Command20("import").description("Restore a snapshot into the
21147
21809
  });
21148
21810
  var verifyCmd = new Command20("verify").description("Check a snapshot's manifest and checksum without importing").argument("<file>", "Snapshot file to verify").action((file) => {
21149
21811
  try {
21150
- if (!existsSync23(file)) {
21812
+ if (!existsSync24(file)) {
21151
21813
  console.error(`Error: Snapshot file not found: ${file}`);
21152
21814
  process.exit(1);
21153
21815
  }
21154
- const manifest = verifySnapshot(readFileSync17(file, "utf-8"));
21816
+ const manifest = verifySnapshot(readFileSync18(file, "utf-8"));
21155
21817
  const { total, nonEmpty } = summarize(manifest.tables);
21156
21818
  console.log(`Valid snapshot (format v${manifest.version})`);
21157
21819
  console.log(` created: ${manifest.createdAt}`);
@@ -21700,10 +22362,10 @@ tokenCommand.command("reembed").description("Backfill or refresh semantic-search
21700
22362
  // src/cli/commands/ui.ts
21701
22363
  init_kernel();
21702
22364
  import { spawn as spawn3, spawnSync } from "child_process";
21703
- import { existsSync as existsSync24 } from "fs";
21704
- import { homedir as homedir14 } from "os";
21705
- import { dirname as dirname11, join as join25 } from "path";
21706
- import { fileURLToPath as fileURLToPath5 } from "url";
22365
+ import { existsSync as existsSync25 } from "fs";
22366
+ import { homedir as homedir15 } from "os";
22367
+ import { dirname as dirname12, join as join26 } from "path";
22368
+ import { fileURLToPath as fileURLToPath6 } from "url";
21707
22369
  import { Command as Command23 } from "commander";
21708
22370
  var C3 = {
21709
22371
  reset: "\x1B[0m",
@@ -21714,14 +22376,14 @@ var C3 = {
21714
22376
  dim: "\x1B[2m"
21715
22377
  };
21716
22378
  function findDesktopDir() {
21717
- const starts = [process.cwd(), dirname11(fileURLToPath5(import.meta.url))];
22379
+ const starts = [process.cwd(), dirname12(fileURLToPath6(import.meta.url))];
21718
22380
  for (const start of starts) {
21719
22381
  let dir = start;
21720
22382
  for (let i = 0; i < 10; i++) {
21721
- if (existsSync24(join25(dir, "desktop", "src-tauri", "tauri.conf.json"))) {
21722
- return join25(dir, "desktop");
22383
+ if (existsSync25(join26(dir, "desktop", "src-tauri", "tauri.conf.json"))) {
22384
+ return join26(dir, "desktop");
21723
22385
  }
21724
- const parent = dirname11(dir);
22386
+ const parent = dirname12(dir);
21725
22387
  if (parent === dir) break;
21726
22388
  dir = parent;
21727
22389
  }
@@ -21729,30 +22391,30 @@ function findDesktopDir() {
21729
22391
  return null;
21730
22392
  }
21731
22393
  function findBuiltApp(desktopDir) {
21732
- const releaseDir = join25(desktopDir, "src-tauri", "target", "release");
22394
+ const releaseDir = join26(desktopDir, "src-tauri", "target", "release");
21733
22395
  if (process.platform === "win32") {
21734
22396
  for (const name of ["ZAM.exe", "zam.exe", "zam-desktop.exe"]) {
21735
- const p = join25(releaseDir, name);
21736
- if (existsSync24(p)) return p;
22397
+ const p = join26(releaseDir, name);
22398
+ if (existsSync25(p)) return p;
21737
22399
  }
21738
22400
  } else if (process.platform === "darwin") {
21739
- const app = join25(releaseDir, "bundle", "macos", "ZAM.app");
21740
- if (existsSync24(app)) return app;
22401
+ const app = join26(releaseDir, "bundle", "macos", "ZAM.app");
22402
+ if (existsSync25(app)) return app;
21741
22403
  } else {
21742
22404
  for (const name of ["zam", "ZAM", "zam-desktop"]) {
21743
- const p = join25(releaseDir, name);
21744
- if (existsSync24(p)) return p;
22405
+ const p = join26(releaseDir, name);
22406
+ if (existsSync25(p)) return p;
21745
22407
  }
21746
22408
  }
21747
22409
  return null;
21748
22410
  }
21749
22411
  function findInstalledApp() {
21750
22412
  const candidates = process.platform === "win32" ? [
21751
- process.env.LOCALAPPDATA && join25(process.env.LOCALAPPDATA, "Programs", "ZAM", "ZAM.exe"),
21752
- process.env.ProgramFiles && join25(process.env.ProgramFiles, "ZAM", "ZAM.exe"),
21753
- process.env["ProgramFiles(x86)"] && join25(process.env["ProgramFiles(x86)"], "ZAM", "ZAM.exe")
21754
- ] : process.platform === "darwin" ? ["/Applications/ZAM.app", join25(homedir14(), "Applications", "ZAM.app")] : ["/opt/ZAM/zam", "/usr/bin/zam-desktop"];
21755
- return candidates.find((candidate) => candidate && existsSync24(candidate)) || null;
22413
+ process.env.LOCALAPPDATA && join26(process.env.LOCALAPPDATA, "Programs", "ZAM", "ZAM.exe"),
22414
+ process.env.ProgramFiles && join26(process.env.ProgramFiles, "ZAM", "ZAM.exe"),
22415
+ process.env["ProgramFiles(x86)"] && join26(process.env["ProgramFiles(x86)"], "ZAM", "ZAM.exe")
22416
+ ] : process.platform === "darwin" ? ["/Applications/ZAM.app", join26(homedir15(), "Applications", "ZAM.app")] : ["/opt/ZAM/zam", "/usr/bin/zam-desktop"];
22417
+ return candidates.find((candidate) => candidate && existsSync25(candidate)) || null;
21756
22418
  }
21757
22419
  function runNpm(args, opts) {
21758
22420
  const res = spawnSync("npm", args, {
@@ -21763,7 +22425,7 @@ function runNpm(args, opts) {
21763
22425
  return res.status ?? 1;
21764
22426
  }
21765
22427
  function ensureDesktopDeps(desktopDir) {
21766
- if (existsSync24(join25(desktopDir, "node_modules"))) return true;
22428
+ if (existsSync25(join26(desktopDir, "node_modules"))) return true;
21767
22429
  console.log(
21768
22430
  `${C3.cyan}Installing desktop dependencies (one-time)...${C3.reset}`
21769
22431
  );
@@ -21789,13 +22451,13 @@ function requireRust() {
21789
22451
  function hasMsvcBuildTools() {
21790
22452
  if (process.platform !== "win32") return true;
21791
22453
  const pf86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
21792
- const vswhere = join25(
22454
+ const vswhere = join26(
21793
22455
  pf86,
21794
22456
  "Microsoft Visual Studio",
21795
22457
  "Installer",
21796
22458
  "vswhere.exe"
21797
22459
  );
21798
- if (!existsSync24(vswhere)) return false;
22460
+ if (!existsSync25(vswhere)) return false;
21799
22461
  const res = spawnSync(
21800
22462
  vswhere,
21801
22463
  [
@@ -21827,7 +22489,7 @@ function requireMsvcOnWindows() {
21827
22489
  return false;
21828
22490
  }
21829
22491
  function warnIfCliMissing(repoRoot) {
21830
- if (!existsSync24(join25(repoRoot, "dist", "cli", "index.js"))) {
22492
+ if (!existsSync25(join26(repoRoot, "dist", "cli", "index.js"))) {
21831
22493
  console.warn(
21832
22494
  `${C3.yellow}\u26A0 CLI build not found (dist/cli/index.js). The GUI bridge needs it \u2014 run 'npm run build' at the repo root.${C3.reset}`
21833
22495
  );
@@ -21890,7 +22552,7 @@ var uiCommand = new Command23("ui").description("Launch the ZAM Desktop GUI (Act
21890
22552
  ).option("--shortcut", "Create Desktop + Start-menu shortcuts to the GUI").action((opts) => {
21891
22553
  const installedApp = findInstalledApp();
21892
22554
  if (!opts.dev && !opts.build && !opts.shortcut && installedApp) {
21893
- launchApp(installedApp, homedir14());
22555
+ launchApp(installedApp, homedir15());
21894
22556
  return;
21895
22557
  }
21896
22558
  const desktopDir = findDesktopDir();
@@ -21900,7 +22562,7 @@ var uiCommand = new Command23("ui").description("Launch the ZAM Desktop GUI (Act
21900
22562
  );
21901
22563
  process.exit(1);
21902
22564
  }
21903
- const repoRoot = dirname11(desktopDir);
22565
+ const repoRoot = dirname12(desktopDir);
21904
22566
  if (opts.build) {
21905
22567
  if (!requireRust()) process.exit(1);
21906
22568
  if (!requireMsvcOnWindows()) process.exit(1);
@@ -21911,7 +22573,7 @@ var uiCommand = new Command23("ui").description("Launch the ZAM Desktop GUI (Act
21911
22573
  );
21912
22574
  const code = runNpm(["run", "tauri", "build"], { cwd: desktopDir });
21913
22575
  if (code === 0) {
21914
- const bundle = join25(
22576
+ const bundle = join26(
21915
22577
  desktopDir,
21916
22578
  "src-tauri",
21917
22579
  "target",
@@ -21951,7 +22613,7 @@ ${C3.green}\u2713 Done. Installer is in:${C3.reset}
21951
22613
  );
21952
22614
  process.exit(1);
21953
22615
  }
21954
- createShortcuts(shortcutTarget, dirname11(shortcutTarget));
22616
+ createShortcuts(shortcutTarget, dirname12(shortcutTarget));
21955
22617
  return;
21956
22618
  }
21957
22619
  if (builtApp) {
@@ -21989,9 +22651,9 @@ ${C3.dim}After that, 'zam ui' launches it directly.${C3.reset}`
21989
22651
  // src/cli/commands/update.ts
21990
22652
  init_kernel();
21991
22653
  import { spawnSync as spawnSync2 } from "child_process";
21992
- import { existsSync as existsSync25, readFileSync as readFileSync18, realpathSync as realpathSync2 } from "fs";
21993
- import { dirname as dirname12, join as join26 } from "path";
21994
- import { fileURLToPath as fileURLToPath6 } from "url";
22654
+ import { existsSync as existsSync26, readFileSync as readFileSync19, realpathSync as realpathSync2 } from "fs";
22655
+ import { dirname as dirname13, join as join27 } from "path";
22656
+ import { fileURLToPath as fileURLToPath7 } from "url";
21995
22657
  import { confirm as confirm3 } from "@inquirer/prompts";
21996
22658
  import { Command as Command24 } from "commander";
21997
22659
  var CHANNELS = [
@@ -22012,7 +22674,7 @@ var C4 = {
22012
22674
  function versionAt(dir) {
22013
22675
  try {
22014
22676
  const pkg2 = JSON.parse(
22015
- readFileSync18(join26(dir, "package.json"), "utf-8")
22677
+ readFileSync19(join27(dir, "package.json"), "utf-8")
22016
22678
  );
22017
22679
  return pkg2.version ?? "unknown";
22018
22680
  } catch {
@@ -22070,14 +22732,14 @@ var checkCmd = new Command24("check").description("Check whether a newer ZAM has
22070
22732
  }
22071
22733
  );
22072
22734
  function findSourceRepo() {
22073
- let dir = realpathSync2(dirname12(fileURLToPath6(import.meta.url)));
22074
- let parent = dirname12(dir);
22735
+ let dir = realpathSync2(dirname13(fileURLToPath7(import.meta.url)));
22736
+ let parent = dirname13(dir);
22075
22737
  while (parent !== dir) {
22076
- if (existsSync25(join26(dir, ".git"))) return dir;
22738
+ if (existsSync26(join27(dir, ".git"))) return dir;
22077
22739
  dir = parent;
22078
- parent = dirname12(dir);
22740
+ parent = dirname13(dir);
22079
22741
  }
22080
- return existsSync25(join26(dir, ".git")) ? dir : null;
22742
+ return existsSync26(join27(dir, ".git")) ? dir : null;
22081
22743
  }
22082
22744
  function runGit(cwd, args, capture) {
22083
22745
  const res = spawnSync2("git", args, {
@@ -22098,7 +22760,7 @@ function runNpm2(args, cwd) {
22098
22760
  function smokeTestBuild(src) {
22099
22761
  const res = spawnSync2(
22100
22762
  process.execPath,
22101
- [join26(src, "dist", "cli", "index.js"), "--version"],
22763
+ [join27(src, "dist", "cli", "index.js"), "--version"],
22102
22764
  {
22103
22765
  cwd: src,
22104
22766
  encoding: "utf8",
@@ -22178,7 +22840,7 @@ Fix it manually in ${src} (try \`npm ci && npm run build\`, and check your Node
22178
22840
  console.log(`${C4.dim}\u2192 zam setup --force${C4.reset}`);
22179
22841
  const setup = spawnSync2(
22180
22842
  process.execPath,
22181
- [join26(src, "dist", "cli", "index.js"), "setup", "--force"],
22843
+ [join27(src, "dist", "cli", "index.js"), "setup", "--force"],
22182
22844
  { cwd: process.cwd(), stdio: "inherit" }
22183
22845
  );
22184
22846
  if (setup.status !== 0) {
@@ -22290,15 +22952,15 @@ var whoamiCommand = new Command25("whoami").description("Show or set the default
22290
22952
 
22291
22953
  // src/cli/commands/workspace.ts
22292
22954
  init_kernel();
22293
- import { execFileSync as execFileSync4 } from "child_process";
22294
- import { existsSync as existsSync26, writeFileSync as writeFileSync14 } from "fs";
22295
- import { homedir as homedir15 } from "os";
22296
- import { join as join27, resolve as resolve10 } from "path";
22955
+ import { execFileSync as execFileSync5 } from "child_process";
22956
+ import { existsSync as existsSync27, writeFileSync as writeFileSync15 } from "fs";
22957
+ import { homedir as homedir16 } from "os";
22958
+ import { join as join28, resolve as resolve10 } from "path";
22297
22959
  import { confirm as confirm4, input as input7 } from "@inquirer/prompts";
22298
22960
  import { Command as Command26 } from "commander";
22299
22961
  function runGit2(cwd, args) {
22300
22962
  try {
22301
- return execFileSync4("git", args, {
22963
+ return execFileSync5("git", args, {
22302
22964
  cwd,
22303
22965
  stdio: "pipe",
22304
22966
  encoding: "utf8"
@@ -22377,7 +23039,7 @@ workspaceCommand.command("list").description("List configured ZAM workspaces").o
22377
23039
  const workspaces = getConfiguredWorkspaces();
22378
23040
  const agents = parseSetupAgents();
22379
23041
  const linkHealth = Object.fromEntries(
22380
- workspaces.filter((workspace) => existsSync26(workspace.path)).map((workspace) => [
23042
+ workspaces.filter((workspace) => existsSync27(workspace.path)).map((workspace) => [
22381
23043
  workspace.id,
22382
23044
  summarizeSkillLinkHealth(inspectSkillLinks(workspace.path, agents))
22383
23045
  ])
@@ -22420,7 +23082,7 @@ workspaceCommand.command("add <id>").description("Register an existing directory
22420
23082
  ).option("--scopes <list>", "Comma-separated knowledge scopes").option("--default-agent <id>", "Default agent harness for this workspace").action((id, opts) => {
22421
23083
  try {
22422
23084
  const path = resolve10(String(opts.path));
22423
- if (!existsSync26(path)) {
23085
+ if (!existsSync27(path)) {
22424
23086
  console.error(`Workspace path does not exist: ${path}`);
22425
23087
  process.exit(1);
22426
23088
  }
@@ -22514,7 +23176,7 @@ workspaceCommand.command("publish").description("Publish your local workspace sa
22514
23176
  );
22515
23177
  process.exit(1);
22516
23178
  }
22517
- if (!existsSync26(workspaceDir)) {
23179
+ if (!existsSync27(workspaceDir)) {
22518
23180
  console.error(
22519
23181
  `\x1B[31m\u2717 Workspace directory does not exist: ${workspaceDir}\x1B[0m`
22520
23182
  );
@@ -22528,15 +23190,15 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
22528
23190
  );
22529
23191
  process.exit(1);
22530
23192
  }
22531
- const gitignorePath = join27(workspaceDir, ".gitignore");
22532
- if (!existsSync26(gitignorePath)) {
22533
- writeFileSync14(
23193
+ const gitignorePath = join28(workspaceDir, ".gitignore");
23194
+ if (!existsSync27(gitignorePath)) {
23195
+ writeFileSync15(
22534
23196
  gitignorePath,
22535
23197
  "node_modules/\n.agent/\n.agents/\n.claude/\n.gemini/\n.goose/\n*.log\n",
22536
23198
  "utf8"
22537
23199
  );
22538
23200
  }
22539
- const hasGitRepo = existsSync26(join27(workspaceDir, ".git"));
23201
+ const hasGitRepo = existsSync27(join28(workspaceDir, ".git"));
22540
23202
  if (!hasGitRepo) {
22541
23203
  console.log("Initializing local Git repository...");
22542
23204
  runGit2(workspaceDir, ["init", "-b", "main"]);
@@ -22568,7 +23230,7 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
22568
23230
  if (proceedGh) {
22569
23231
  try {
22570
23232
  console.log(`Creating GitHub repository ${repoName}...`);
22571
- execFileSync4("gh", ghRepoCreateArgs(repoName, repoVisibility), {
23233
+ execFileSync5("gh", ghRepoCreateArgs(repoName, repoVisibility), {
22572
23234
  cwd: workspaceDir,
22573
23235
  stdio: "inherit"
22574
23236
  });
@@ -22623,7 +23285,7 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
22623
23285
  }
22624
23286
  });
22625
23287
  workspaceCommand.command("data-dir").description("Print the ZAM data directory (database, credentials, config)").option("--json", "Output as JSON").action((opts) => {
22626
- const dir = join27(homedir15(), ".zam");
23288
+ const dir = join28(homedir16(), ".zam");
22627
23289
  console.log(opts.json ? JSON.stringify({ dataDir: dir }) : dir);
22628
23290
  });
22629
23291
  workspaceCommand.command("backup").description("Back up the local ZAM database into your workspace").option(
@@ -22664,9 +23326,9 @@ workspaceCommand.command("backup").description("Back up the local ZAM database i
22664
23326
  });
22665
23327
 
22666
23328
  // src/cli/app.ts
22667
- var __dirname = dirname13(fileURLToPath7(import.meta.url));
23329
+ var __dirname = dirname14(fileURLToPath8(import.meta.url));
22668
23330
  var pkg = JSON.parse(
22669
- readFileSync19(join28(__dirname, "..", "..", "package.json"), "utf-8")
23331
+ readFileSync20(join29(__dirname, "..", "..", "package.json"), "utf-8")
22670
23332
  );
22671
23333
  var program = new Command27();
22672
23334
  program.name("zam").description(