zam-core 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/cli/index.ts
4
4
  import { readFileSync as readFileSync14 } from "fs";
5
- import { dirname as dirname10, join as join22 } from "path";
5
+ import { dirname as dirname10, join as join23 } from "path";
6
6
  import { fileURLToPath as fileURLToPath5 } from "url";
7
7
  import { Command as Command24 } from "commander";
8
8
 
@@ -4932,10 +4932,10 @@ var agentCommand = new Command("agent").description("Provision and inspect the a
4932
4932
 
4933
4933
  // src/cli/commands/bridge.ts
4934
4934
  import { execFileSync as execFileSync2 } from "child_process";
4935
- import { randomBytes } from "crypto";
4935
+ import { randomBytes as randomBytes2 } from "crypto";
4936
4936
  import { readdirSync as readdirSync2, readFileSync as readFileSync10, rmSync as rmSync2 } from "fs";
4937
- import { homedir as homedir8, tmpdir } from "os";
4938
- import { join as join12 } from "path";
4937
+ import { homedir as homedir8, tmpdir as tmpdir2 } from "os";
4938
+ import { join as join13 } from "path";
4939
4939
  import { Command as Command2 } from "commander";
4940
4940
 
4941
4941
  // src/cli/llm/client.ts
@@ -4955,12 +4955,15 @@ async function getLlmConfig(db) {
4955
4955
  }
4956
4956
  async function getVisionConfig(db) {
4957
4957
  const base = await getLlmConfig(db);
4958
+ const maxFramesStr = await getSetting(db, "llm.vision.max_frames");
4959
+ const maxFrames = maxFramesStr ? parseInt(maxFramesStr, 10) : 100;
4958
4960
  return {
4959
4961
  enabled: await getSetting(db, "llm.vision.enabled") === "true",
4960
4962
  url: await getSetting(db, "llm.vision.url") || base.url,
4961
4963
  model: await getSetting(db, "llm.vision.model") || base.model,
4962
4964
  apiKey: await getSetting(db, "llm.vision.api_key") || base.apiKey,
4963
- locale: base.locale
4965
+ locale: base.locale,
4966
+ maxFrames: Number.isNaN(maxFrames) ? 100 : maxFrames
4964
4967
  };
4965
4968
  }
4966
4969
  var LANGUAGE_NAMES = {
@@ -5497,8 +5500,10 @@ async function ensureHighQualityQuestion(db, token) {
5497
5500
  }
5498
5501
 
5499
5502
  // src/cli/llm/vision.ts
5503
+ import { randomBytes } from "crypto";
5500
5504
  import { readFileSync as readFileSync9 } from "fs";
5501
- import { basename as basename2 } from "path";
5505
+ import { tmpdir } from "os";
5506
+ import { basename as basename2, join as join12 } from "path";
5502
5507
  var LANGUAGE_NAMES2 = {
5503
5508
  en: "English",
5504
5509
  de: "German",
@@ -5531,15 +5536,69 @@ async function observeUiSnapshotViaLLM(db, input8) {
5531
5536
  "Vision observation is disabled in settings (llm.vision.enabled)"
5532
5537
  );
5533
5538
  }
5534
- const imageBytes = readFileSync9(input8.imagePath);
5535
- const imageUrl = `data:image/png;base64,${imageBytes.toString("base64")}`;
5539
+ const imageUrls = [];
5540
+ const isVideo = /\.(mp4|mov|m4v|avi|mkv|webm)$/i.test(input8.imagePath);
5541
+ if (isVideo) {
5542
+ const { mkdirSync: mkdirSync12, readdirSync: readdirSync3, rmSync: rmSync3 } = await import("fs");
5543
+ const { execSync: execSync7 } = await import("child_process");
5544
+ const tempDir = join12(
5545
+ tmpdir(),
5546
+ `zam-frames-${randomBytes(4).toString("hex")}`
5547
+ );
5548
+ mkdirSync12(tempDir, { recursive: true });
5549
+ try {
5550
+ execSync7(
5551
+ `ffmpeg -i "${input8.imagePath}" -vf "fps=1/3,scale=1280:-1" -vsync vfr "${tempDir}/frame_%03d.png"`,
5552
+ { stdio: "ignore" }
5553
+ );
5554
+ let files = readdirSync3(tempDir).filter((f) => f.endsWith(".png")).sort();
5555
+ if (files.length === 0) {
5556
+ execSync7(
5557
+ `ffmpeg -i "${input8.imagePath}" -vframes 1 "${tempDir}/frame_001.png"`,
5558
+ { stdio: "ignore" }
5559
+ );
5560
+ files = readdirSync3(tempDir).filter((f) => f.endsWith(".png")).sort();
5561
+ }
5562
+ const maxFrames = cfg.maxFrames ?? 100;
5563
+ let sampledFiles = files;
5564
+ if (files.length > maxFrames) {
5565
+ if (maxFrames <= 1) {
5566
+ sampledFiles = [files[0]];
5567
+ } else {
5568
+ const step = (files.length - 1) / (maxFrames - 1);
5569
+ sampledFiles = [];
5570
+ for (let i = 0; i < maxFrames; i++) {
5571
+ const index = Math.round(i * step);
5572
+ sampledFiles.push(files[index]);
5573
+ }
5574
+ }
5575
+ }
5576
+ for (const file of sampledFiles) {
5577
+ const bytes = readFileSync9(join12(tempDir, file));
5578
+ imageUrls.push(`data:image/png;base64,${bytes.toString("base64")}`);
5579
+ }
5580
+ } finally {
5581
+ try {
5582
+ rmSync3(tempDir, { recursive: true, force: true });
5583
+ } catch {
5584
+ }
5585
+ }
5586
+ } else {
5587
+ const imageBytes = readFileSync9(input8.imagePath);
5588
+ const ext = input8.imagePath.split(".").pop()?.toLowerCase();
5589
+ const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
5590
+ imageUrls.push(`data:${mime};base64,${imageBytes.toString("base64")}`);
5591
+ }
5592
+ if (imageUrls.length === 0) {
5593
+ throw new Error("No image data available for vision analysis");
5594
+ }
5536
5595
  const model = input8.model ?? cfg.model;
5537
5596
  const content = await requestVisionDraft({
5538
5597
  url: cfg.url,
5539
5598
  apiKey: cfg.apiKey || DEFAULT_LLM_API_KEY,
5540
5599
  model,
5541
5600
  locale: cfg.locale,
5542
- imageUrl,
5601
+ imageUrls,
5543
5602
  input: input8
5544
5603
  });
5545
5604
  let draft;
@@ -5589,22 +5648,28 @@ async function requestVisionDraft(args) {
5589
5648
  content: [
5590
5649
  {
5591
5650
  type: "text",
5592
- text: `Observe this Windows application snapshot for a learning session.
5651
+ text: args.imageUrls.length > 1 ? `Observe this sequence of Windows/macOS application snapshots showing a task performed over time.
5652
+ Application process: ${args.input.application.processName}
5653
+ Window title: ${args.input.application.windowTitle ?? "(unknown)"}
5654
+
5655
+ Return this JSON draft only:
5656
+ ${schema}` : `Observe this Windows/macOS application snapshot for a learning session.
5593
5657
  Application process: ${args.input.application.processName}
5594
5658
  Window title: ${args.input.application.windowTitle ?? "(unknown)"}
5595
5659
 
5596
5660
  Return this JSON draft only:
5597
5661
  ${schema}`
5598
5662
  },
5599
- {
5663
+ ...args.imageUrls.map((url) => ({
5600
5664
  type: "image_url",
5601
- image_url: { url: args.imageUrl }
5602
- }
5665
+ image_url: { url }
5666
+ }))
5603
5667
  ]
5604
5668
  }
5605
5669
  ],
5606
5670
  temperature: 0,
5607
- max_tokens: args.input.maxTokens ?? 450
5671
+ max_tokens: args.input.maxTokens ?? 450,
5672
+ ...args.url.includes("11434") || args.url.includes("localhost") || args.url.includes("127.0.0.1") ? { options: { num_ctx: 32768 } } : {}
5608
5673
  }),
5609
5674
  locale: args.locale,
5610
5675
  hardTimeoutMs: args.input.hardTimeoutMs ?? 18e4
@@ -6239,7 +6304,7 @@ bridgeCommand.command("discover-skills").description(
6239
6304
  "20"
6240
6305
  ).action(async (opts) => {
6241
6306
  try {
6242
- const monitorDir = join12(homedir8(), ".zam", "monitor");
6307
+ const monitorDir = join13(homedir8(), ".zam", "monitor");
6243
6308
  let files;
6244
6309
  try {
6245
6310
  files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
@@ -6252,7 +6317,7 @@ bridgeCommand.command("discover-skills").description(
6252
6317
  return;
6253
6318
  }
6254
6319
  const limit = Number(opts.limit);
6255
- const sorted = files.map((f) => ({ name: f, path: join12(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
6320
+ const sorted = files.map((f) => ({ name: f, path: join13(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
6256
6321
  const sessionCommands = /* @__PURE__ */ new Map();
6257
6322
  for (const file of sorted) {
6258
6323
  const sessionId = file.name.replace(".jsonl", "");
@@ -6332,8 +6397,8 @@ bridgeCommand.command("get-observations").description("Read UI observer reports
6332
6397
  }
6333
6398
  });
6334
6399
  bridgeCommand.command("observe-ui-snapshot").description(
6335
- "Analyze a captured UI snapshot with the configured vision LLM (JSON)"
6336
- ).requiredOption("--session <id>", "Observer session ID").requiredOption("--sequence <n>", "Monotonic observation sequence number").requiredOption("--image <path>", "PNG snapshot path").requiredOption("--observed-from <iso>", "Observation window start time").requiredOption("--observed-to <iso>", "Observation window end time").requiredOption("--process-name <name>", "Observed application process name").option("--process-id <n>", "Observed application process ID").option("--window-title <title>", "Observed window title").option("--evidence-ref <ref>", "Evidence reference to put in the report").option("--model <model>", "Override configured LLM model for this request").option("--max-tokens <n>", "Model response token budget").option("--timeout <ms>", "Hard request timeout in milliseconds").option("--redacted", "Mark the snapshot evidence as redacted").option("--write-log", "Append the generated report to the session JSONL").action(async (opts) => {
6400
+ "Analyze a captured UI snapshot or video recording with the configured vision LLM (JSON)"
6401
+ ).requiredOption("--session <id>", "Observer session ID").requiredOption("--sequence <n>", "Monotonic observation sequence number").requiredOption("--image <path>", "PNG snapshot or video recording path").requiredOption("--observed-from <iso>", "Observation window start time").requiredOption("--observed-to <iso>", "Observation window end time").requiredOption("--process-name <name>", "Observed application process name").option("--process-id <n>", "Observed application process ID").option("--window-title <title>", "Observed window title").option("--evidence-ref <ref>", "Evidence reference to put in the report").option("--model <model>", "Override configured LLM model for this request").option("--max-tokens <n>", "Model response token budget").option("--timeout <ms>", "Hard request timeout in milliseconds").option("--redacted", "Mark the snapshot evidence as redacted").option("--write-log", "Append the generated report to the session JSONL").action(async (opts) => {
6337
6402
  const sequence = parseNonNegativeIntegerOption("sequence", opts.sequence);
6338
6403
  const processId = opts.processId === void 0 ? void 0 : parseNonNegativeIntegerOption("process-id", opts.processId);
6339
6404
  const maxTokens = opts.maxTokens === void 0 ? void 0 : parseNonNegativeIntegerOption("max-tokens", opts.maxTokens);
@@ -6754,7 +6819,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
6754
6819
  return;
6755
6820
  }
6756
6821
  }
6757
- const outputPath = opts.image ?? opts.output ?? join12(tmpdir(), `zam-capture-${randomBytes(4).toString("hex")}.png`);
6822
+ const outputPath = opts.image ?? opts.output ?? join13(tmpdir2(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
6758
6823
  const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
6759
6824
  if (!isProvided) {
6760
6825
  const post = decidePostCapture(policy, {
@@ -6798,6 +6863,177 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
6798
6863
  });
6799
6864
  });
6800
6865
  });
6866
+ bridgeCommand.command("start-recording").description("Start screen recording in the background (macOS only) (JSON)").requiredOption("--session <id>", "ZAM session ID").option("--output <path>", "Video output path").action(async (opts) => {
6867
+ const platform = process.platform;
6868
+ if (platform !== "darwin") {
6869
+ jsonOut2({
6870
+ sessionId: opts.session,
6871
+ started: false,
6872
+ error: "Screen recording is only supported on macOS (darwin)"
6873
+ });
6874
+ return;
6875
+ }
6876
+ const sessionId = opts.session;
6877
+ const statePath = join13(tmpdir2(), `zam-recording-${sessionId}.json`);
6878
+ const outputPath = opts.output ?? join13(tmpdir2(), `zam-recording-${sessionId}.mov`);
6879
+ const { existsSync: existsSync22, writeFileSync: writeFileSync12, openSync, closeSync } = await import("fs");
6880
+ if (existsSync22(statePath)) {
6881
+ jsonOut2({
6882
+ sessionId,
6883
+ started: false,
6884
+ error: `Recording is already active for session ${sessionId}`
6885
+ });
6886
+ return;
6887
+ }
6888
+ const logPath = join13(tmpdir2(), `zam-recording-${sessionId}.log`);
6889
+ let logFd;
6890
+ try {
6891
+ logFd = openSync(logPath, "w");
6892
+ } catch (e) {
6893
+ jsonOut2({
6894
+ sessionId,
6895
+ started: false,
6896
+ error: `Failed to open log file at ${logPath}: ${e.message}`
6897
+ });
6898
+ return;
6899
+ }
6900
+ const { spawn: spawn3 } = await import("child_process");
6901
+ const child = spawn3(
6902
+ "ffmpeg",
6903
+ [
6904
+ "-y",
6905
+ "-f",
6906
+ "avfoundation",
6907
+ "-r",
6908
+ "5",
6909
+ "-i",
6910
+ "0",
6911
+ "-pix_fmt",
6912
+ "yuv420p",
6913
+ outputPath
6914
+ ],
6915
+ {
6916
+ detached: true,
6917
+ stdio: ["pipe", logFd, logFd]
6918
+ }
6919
+ );
6920
+ try {
6921
+ closeSync(logFd);
6922
+ } catch {
6923
+ }
6924
+ child.unref();
6925
+ if (child.pid) {
6926
+ writeFileSync12(
6927
+ statePath,
6928
+ JSON.stringify({
6929
+ pid: child.pid,
6930
+ outputPath,
6931
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
6932
+ }),
6933
+ "utf8"
6934
+ );
6935
+ jsonOut2({
6936
+ sessionId,
6937
+ started: true,
6938
+ outputPath,
6939
+ pid: child.pid
6940
+ });
6941
+ } else {
6942
+ jsonOut2({
6943
+ sessionId,
6944
+ started: false,
6945
+ error: "Failed to spawn ffmpeg process"
6946
+ });
6947
+ }
6948
+ });
6949
+ bridgeCommand.command("stop-recording").description(
6950
+ "Stop active screen recording and apply idle-frame compression (macOS only) (JSON)"
6951
+ ).requiredOption("--session <id>", "ZAM session ID").action(async (opts) => {
6952
+ const platform = process.platform;
6953
+ if (platform !== "darwin") {
6954
+ jsonOut2({
6955
+ sessionId: opts.session,
6956
+ stopped: false,
6957
+ error: "Screen recording is only supported on macOS (darwin)"
6958
+ });
6959
+ return;
6960
+ }
6961
+ const sessionId = opts.session;
6962
+ const statePath = join13(tmpdir2(), `zam-recording-${sessionId}.json`);
6963
+ const { existsSync: existsSync22, readFileSync: readFileSync15, rmSync: rmSync3 } = await import("fs");
6964
+ if (!existsSync22(statePath)) {
6965
+ jsonOut2({
6966
+ sessionId,
6967
+ stopped: false,
6968
+ error: `No active recording found for session ${sessionId}`
6969
+ });
6970
+ return;
6971
+ }
6972
+ const state = JSON.parse(readFileSync15(statePath, "utf8"));
6973
+ const { pid, outputPath } = state;
6974
+ try {
6975
+ process.kill(pid, "SIGINT");
6976
+ } catch (_e) {
6977
+ }
6978
+ const isProcessRunning = (pId) => {
6979
+ try {
6980
+ process.kill(pId, 0);
6981
+ return true;
6982
+ } catch (_e) {
6983
+ return false;
6984
+ }
6985
+ };
6986
+ let attempts = 0;
6987
+ while (isProcessRunning(pid) && attempts < 20) {
6988
+ await new Promise((resolve5) => setTimeout(resolve5, 250));
6989
+ attempts++;
6990
+ }
6991
+ if (isProcessRunning(pid)) {
6992
+ try {
6993
+ process.kill(pid, "SIGKILL");
6994
+ } catch (_e) {
6995
+ }
6996
+ }
6997
+ try {
6998
+ rmSync3(statePath, { force: true });
6999
+ } catch {
7000
+ }
7001
+ if (!existsSync22(outputPath)) {
7002
+ jsonOut2({
7003
+ sessionId,
7004
+ stopped: false,
7005
+ error: `Recording file not found at ${outputPath}`
7006
+ });
7007
+ return;
7008
+ }
7009
+ const decimatedPath = outputPath.replace(/\.[^.]+$/, "-decimated.mp4");
7010
+ const { execSync: execSync7 } = await import("child_process");
7011
+ try {
7012
+ execSync7(
7013
+ `ffmpeg -y -i "${outputPath}" -vf "mpdecimate,setpts=N/FRAME_RATE/TB" -an -pix_fmt yuv420p "${decimatedPath}"`,
7014
+ { stdio: "ignore" }
7015
+ );
7016
+ } catch (ffmpegErr) {
7017
+ jsonOut2({
7018
+ sessionId,
7019
+ stopped: true,
7020
+ videoPath: outputPath,
7021
+ decimated: false,
7022
+ warning: `mpdecimate post-processing failed: ${ffmpegErr.message}`
7023
+ });
7024
+ return;
7025
+ }
7026
+ try {
7027
+ rmSync3(outputPath, { force: true });
7028
+ } catch {
7029
+ }
7030
+ jsonOut2({
7031
+ sessionId,
7032
+ stopped: true,
7033
+ videoPath: decimatedPath,
7034
+ decimated: true
7035
+ });
7036
+ });
6801
7037
  bridgeCommand.command("get-observer-policy").description(
6802
7038
  "Report the resolved observer policy so an agent can check before capturing (JSON)"
6803
7039
  ).action(async () => {
@@ -7506,18 +7742,18 @@ async function setupTurso(urlArg, tokenArg, mode) {
7506
7742
  // src/cli/commands/git-sync.ts
7507
7743
  import { execSync as execSync4 } from "child_process";
7508
7744
  import { chmodSync as chmodSync2, existsSync as existsSync13, writeFileSync as writeFileSync6 } from "fs";
7509
- import { join as join13 } from "path";
7745
+ import { join as join14 } from "path";
7510
7746
  import { Command as Command5 } from "commander";
7511
7747
  function installHook2() {
7512
- const gitDir = join13(process.cwd(), ".git");
7748
+ const gitDir = join14(process.cwd(), ".git");
7513
7749
  if (!existsSync13(gitDir)) {
7514
7750
  console.error(
7515
7751
  "Error: Current directory is not the root of a Git repository."
7516
7752
  );
7517
7753
  process.exit(1);
7518
7754
  }
7519
- const hooksDir = join13(gitDir, "hooks");
7520
- const hookPath = join13(hooksDir, "post-commit");
7755
+ const hooksDir = join14(gitDir, "hooks");
7756
+ const hookPath = join14(hooksDir, "post-commit");
7521
7757
  const hookContent = `#!/bin/sh
7522
7758
  # ZAM Spaced Repetition Auto-Stale Hook
7523
7759
  # Triggered automatically on git commits to decay modified concept cards.
@@ -7813,7 +8049,7 @@ goalCommand.command("status <slug> <status>").description("Update a goal's statu
7813
8049
  // src/cli/commands/init.ts
7814
8050
  import { existsSync as existsSync15, mkdirSync as mkdirSync9, writeFileSync as writeFileSync7 } from "fs";
7815
8051
  import { homedir as homedir9 } from "os";
7816
- import { join as join14 } from "path";
8052
+ import { join as join15 } from "path";
7817
8053
  import { confirm, input as input3 } from "@inquirer/prompts";
7818
8054
  import { Command as Command7 } from "commander";
7819
8055
  var HOME2 = homedir9();
@@ -7821,10 +8057,10 @@ function printLine(char = "\u2550", len = 60, color = "\x1B[36m") {
7821
8057
  console.log(`${color}${char.repeat(len)}\x1B[0m`);
7822
8058
  }
7823
8059
  function bootstrapSandboxWorkspace(workspaceDir) {
7824
- mkdirSync9(join14(workspaceDir, "beliefs"), { recursive: true });
7825
- mkdirSync9(join14(workspaceDir, "goals"), { recursive: true });
7826
- mkdirSync9(join14(workspaceDir, "skills"), { recursive: true });
7827
- const worldviewFile = join14(workspaceDir, "beliefs", "worldview.md");
8060
+ mkdirSync9(join15(workspaceDir, "beliefs"), { recursive: true });
8061
+ mkdirSync9(join15(workspaceDir, "goals"), { recursive: true });
8062
+ mkdirSync9(join15(workspaceDir, "skills"), { recursive: true });
8063
+ const worldviewFile = join15(workspaceDir, "beliefs", "worldview.md");
7828
8064
  if (!existsSync15(worldviewFile)) {
7829
8065
  writeFileSync7(
7830
8066
  worldviewFile,
@@ -7838,7 +8074,7 @@ Here, I declare the core concepts and principles I want to master.
7838
8074
  "utf8"
7839
8075
  );
7840
8076
  }
7841
- const goalsFile = join14(workspaceDir, "goals", "goals.md");
8077
+ const goalsFile = join15(workspaceDir, "goals", "goals.md");
7842
8078
  if (!existsSync15(goalsFile)) {
7843
8079
  writeFileSync7(
7844
8080
  goalsFile,
@@ -7862,7 +8098,7 @@ var initCommand = new Command7("init").description("Launch the guided interactiv
7862
8098
  );
7863
8099
  printLine();
7864
8100
  console.log("\n\x1B[1m[1/5] Setting up Local Workspace Sandbox\x1B[0m");
7865
- const defaultWorkspace = join14(HOME2, "Documents", "zam");
8101
+ const defaultWorkspace = join15(HOME2, "Documents", "zam");
7866
8102
  const workspacePath = await input3({
7867
8103
  message: "Choose your ZAM workspace directory:",
7868
8104
  default: defaultWorkspace
@@ -8561,8 +8797,8 @@ ${"\u2550".repeat(50)}`);
8561
8797
  // src/cli/commands/monitor.ts
8562
8798
  import { execFileSync as execFileSync3, execSync as execSync5 } from "child_process";
8563
8799
  import { unlinkSync, writeFileSync as writeFileSync8 } from "fs";
8564
- import { tmpdir as tmpdir2 } from "os";
8565
- import { basename as basename3, join as join15 } from "path";
8800
+ import { tmpdir as tmpdir3 } from "os";
8801
+ import { basename as basename3, join as join16 } from "path";
8566
8802
  import { Command as Command9 } from "commander";
8567
8803
  function isPowerShellShell(shell) {
8568
8804
  return shell === "pwsh" || shell === "powershell";
@@ -8739,8 +8975,8 @@ function resolveZamInvocation(shell) {
8739
8975
  if (installed) {
8740
8976
  return isPowerShellShell(shell) ? `& ${psSingleQuoted2(installed)}` : installed;
8741
8977
  }
8742
- const projectRoot = join15(import.meta.dirname, "..", "..", "..");
8743
- const cliSource = join15(projectRoot, "src/cli/index.ts");
8978
+ const projectRoot = join16(import.meta.dirname, "..", "..", "..");
8979
+ const cliSource = join16(projectRoot, "src/cli/index.ts");
8744
8980
  if (isPowerShellShell(shell)) {
8745
8981
  return `& npx --prefix ${psSingleQuoted2(projectRoot)} tsx ${psSingleQuoted2(cliSource)}`;
8746
8982
  }
@@ -8830,7 +9066,7 @@ end tell` : `tell application "Terminal"
8830
9066
  activate
8831
9067
  do script "${escaped}"
8832
9068
  end tell`;
8833
- const tmpFile = join15(tmpdir2(), `zam-monitor-${sessionId}.scpt`);
9069
+ const tmpFile = join16(tmpdir3(), `zam-monitor-${sessionId}.scpt`);
8834
9070
  try {
8835
9071
  writeFileSync8(tmpFile, appleScript);
8836
9072
  execSync5(`osascript ${JSON.stringify(tmpFile)}`, { stdio: "ignore" });
@@ -8946,7 +9182,7 @@ observerCommand.command("revoke <process>").description("Remove a process from o
8946
9182
 
8947
9183
  // src/cli/commands/profile.ts
8948
9184
  import { homedir as homedir10 } from "os";
8949
- import { dirname as dirname5, join as join16, resolve as resolve4 } from "path";
9185
+ import { dirname as dirname5, join as join17, resolve as resolve4 } from "path";
8950
9186
  import { Command as Command11 } from "commander";
8951
9187
  var C2 = {
8952
9188
  reset: "\x1B[0m",
@@ -8956,7 +9192,7 @@ var C2 = {
8956
9192
  green: "\x1B[32m"
8957
9193
  };
8958
9194
  function defaultPersonalDir() {
8959
- return join16(homedir10(), "Documents", "zam");
9195
+ return join17(homedir10(), "Documents", "zam");
8960
9196
  }
8961
9197
  function render(profile) {
8962
9198
  const sync = profile.syncProvider ? `${C2.green}${profile.syncProvider}${C2.reset} ${C2.dim}(good for cross-device snapshots)${C2.reset}` : `${C2.dim}local folder (use a synced folder or snapshots to move between machines)${C2.reset}`;
@@ -9677,31 +9913,31 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
9677
9913
 
9678
9914
  // src/cli/commands/setup.ts
9679
9915
  import { copyFileSync as copyFileSync2, existsSync as existsSync17, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
9680
- import { basename as basename4, dirname as dirname6, join as join17 } from "path";
9916
+ import { basename as basename4, dirname as dirname6, join as join18 } from "path";
9681
9917
  import { fileURLToPath as fileURLToPath2 } from "url";
9682
9918
  import { Command as Command15 } from "commander";
9683
9919
  var packageRoot = [
9684
9920
  fileURLToPath2(new URL("../..", import.meta.url)),
9685
9921
  fileURLToPath2(new URL("../../..", import.meta.url))
9686
- ].find((candidate) => existsSync17(join17(candidate, "package.json"))) ?? fileURLToPath2(new URL("../..", import.meta.url));
9922
+ ].find((candidate) => existsSync17(join18(candidate, "package.json"))) ?? fileURLToPath2(new URL("../..", import.meta.url));
9687
9923
  var SKILL_PAIRS = [
9688
9924
  {
9689
- from: join17(packageRoot, ".claude", "skills", "zam", "SKILL.md"),
9690
- to: join17(".claude", "skills", "zam", "SKILL.md")
9925
+ from: join18(packageRoot, ".claude", "skills", "zam", "SKILL.md"),
9926
+ to: join18(".claude", "skills", "zam", "SKILL.md")
9691
9927
  },
9692
9928
  {
9693
- from: join17(packageRoot, ".agent", "skills", "zam", "SKILL.md"),
9694
- to: join17(".agent", "skills", "zam", "SKILL.md")
9929
+ from: join18(packageRoot, ".agent", "skills", "zam", "SKILL.md"),
9930
+ to: join18(".agent", "skills", "zam", "SKILL.md")
9695
9931
  },
9696
9932
  {
9697
- from: join17(packageRoot, ".agents", "skills", "zam", "SKILL.md"),
9698
- to: join17(".agents", "skills", "zam", "SKILL.md")
9933
+ from: join18(packageRoot, ".agents", "skills", "zam", "SKILL.md"),
9934
+ to: join18(".agents", "skills", "zam", "SKILL.md")
9699
9935
  }
9700
9936
  ];
9701
9937
  function copySkills(force, cwd = process.cwd()) {
9702
9938
  let anyAction = false;
9703
9939
  for (const { from, to } of SKILL_PAIRS) {
9704
- const dest = join17(cwd, to);
9940
+ const dest = join18(cwd, to);
9705
9941
  if (!existsSync17(from)) {
9706
9942
  console.warn(` warn source not found, skipping: ${from}`);
9707
9943
  continue;
@@ -9751,7 +9987,7 @@ async function initDatabase(skipInit) {
9751
9987
  }
9752
9988
  function writeClaudeMd(skipClaudeMd, cwd = process.cwd()) {
9753
9989
  if (skipClaudeMd) return;
9754
- const dest = join17(cwd, "CLAUDE.md");
9990
+ const dest = join18(cwd, "CLAUDE.md");
9755
9991
  if (existsSync17(dest)) {
9756
9992
  console.log(` skip CLAUDE.md (already present)`);
9757
9993
  return;
@@ -9785,7 +10021,7 @@ Use \`zam connector setup turso\` to store cloud credentials in
9785
10021
  }
9786
10022
  function writeAgentsMd(skipAgentsMd, cwd = process.cwd()) {
9787
10023
  if (skipAgentsMd) return;
9788
- const dest = join17(cwd, "AGENTS.md");
10024
+ const dest = join18(cwd, "AGENTS.md");
9789
10025
  if (existsSync17(dest)) {
9790
10026
  console.log(` skip AGENTS.md (already present)`);
9791
10027
  return;
@@ -9933,7 +10169,7 @@ skillCommand.command("add").description("Register a new agent skill").requiredOp
9933
10169
  // src/cli/commands/snapshot.ts
9934
10170
  import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "fs";
9935
10171
  import { homedir as homedir11 } from "os";
9936
- import { dirname as dirname7, join as join18 } from "path";
10172
+ import { dirname as dirname7, join as join19 } from "path";
9937
10173
  import { Command as Command17 } from "commander";
9938
10174
  function defaultOutName() {
9939
10175
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
@@ -9953,7 +10189,7 @@ var exportCmd = new Command17("export").description("Write a portable SQL-text s
9953
10189
  try {
9954
10190
  db = await openDatabaseWithSync({ initialize: true });
9955
10191
  const snapshot = await exportSnapshot(db);
9956
- const personalDir = await getSetting(db, "personal.workspace_dir") || join18(homedir11(), "Documents", "zam");
10192
+ const personalDir = await getSetting(db, "personal.workspace_dir") || join19(homedir11(), "Documents", "zam");
9957
10193
  await db.close();
9958
10194
  db = void 0;
9959
10195
  const manifest = verifySnapshot(snapshot);
@@ -9962,7 +10198,7 @@ var exportCmd = new Command17("export").description("Write a portable SQL-text s
9962
10198
  process.stdout.write(snapshot);
9963
10199
  return;
9964
10200
  }
9965
- const out = opts.out ?? join18(personalDir, "snapshots", defaultOutName());
10201
+ const out = opts.out ?? join19(personalDir, "snapshots", defaultOutName());
9966
10202
  const dir = dirname7(out);
9967
10203
  if (dir && dir !== "." && !existsSync18(dir)) {
9968
10204
  mkdirSync11(dir, { recursive: true });
@@ -10349,7 +10585,7 @@ tokenCommand.command("status").description("Show full status of a token for a us
10349
10585
  import { spawn as spawn2, spawnSync } from "child_process";
10350
10586
  import { existsSync as existsSync19 } from "fs";
10351
10587
  import { homedir as homedir12 } from "os";
10352
- import { dirname as dirname8, join as join19 } from "path";
10588
+ import { dirname as dirname8, join as join20 } from "path";
10353
10589
  import { fileURLToPath as fileURLToPath3 } from "url";
10354
10590
  import { Command as Command20 } from "commander";
10355
10591
  var C3 = {
@@ -10365,8 +10601,8 @@ function findDesktopDir() {
10365
10601
  for (const start of starts) {
10366
10602
  let dir = start;
10367
10603
  for (let i = 0; i < 10; i++) {
10368
- if (existsSync19(join19(dir, "desktop", "src-tauri", "tauri.conf.json"))) {
10369
- return join19(dir, "desktop");
10604
+ if (existsSync19(join20(dir, "desktop", "src-tauri", "tauri.conf.json"))) {
10605
+ return join20(dir, "desktop");
10370
10606
  }
10371
10607
  const parent = dirname8(dir);
10372
10608
  if (parent === dir) break;
@@ -10376,18 +10612,18 @@ function findDesktopDir() {
10376
10612
  return null;
10377
10613
  }
10378
10614
  function findBuiltApp(desktopDir) {
10379
- const releaseDir = join19(desktopDir, "src-tauri", "target", "release");
10615
+ const releaseDir = join20(desktopDir, "src-tauri", "target", "release");
10380
10616
  if (process.platform === "win32") {
10381
10617
  for (const name of ["ZAM.exe", "zam.exe", "zam-desktop.exe"]) {
10382
- const p = join19(releaseDir, name);
10618
+ const p = join20(releaseDir, name);
10383
10619
  if (existsSync19(p)) return p;
10384
10620
  }
10385
10621
  } else if (process.platform === "darwin") {
10386
- const app = join19(releaseDir, "bundle", "macos", "ZAM.app");
10622
+ const app = join20(releaseDir, "bundle", "macos", "ZAM.app");
10387
10623
  if (existsSync19(app)) return app;
10388
10624
  } else {
10389
10625
  for (const name of ["zam", "ZAM", "zam-desktop"]) {
10390
- const p = join19(releaseDir, name);
10626
+ const p = join20(releaseDir, name);
10391
10627
  if (existsSync19(p)) return p;
10392
10628
  }
10393
10629
  }
@@ -10395,10 +10631,10 @@ function findBuiltApp(desktopDir) {
10395
10631
  }
10396
10632
  function findInstalledApp() {
10397
10633
  const candidates = process.platform === "win32" ? [
10398
- process.env.LOCALAPPDATA && join19(process.env.LOCALAPPDATA, "Programs", "ZAM", "ZAM.exe"),
10399
- process.env.ProgramFiles && join19(process.env.ProgramFiles, "ZAM", "ZAM.exe"),
10400
- process.env["ProgramFiles(x86)"] && join19(process.env["ProgramFiles(x86)"], "ZAM", "ZAM.exe")
10401
- ] : process.platform === "darwin" ? ["/Applications/ZAM.app", join19(homedir12(), "Applications", "ZAM.app")] : ["/opt/ZAM/zam", "/usr/bin/zam-desktop"];
10634
+ process.env.LOCALAPPDATA && join20(process.env.LOCALAPPDATA, "Programs", "ZAM", "ZAM.exe"),
10635
+ process.env.ProgramFiles && join20(process.env.ProgramFiles, "ZAM", "ZAM.exe"),
10636
+ process.env["ProgramFiles(x86)"] && join20(process.env["ProgramFiles(x86)"], "ZAM", "ZAM.exe")
10637
+ ] : process.platform === "darwin" ? ["/Applications/ZAM.app", join20(homedir12(), "Applications", "ZAM.app")] : ["/opt/ZAM/zam", "/usr/bin/zam-desktop"];
10402
10638
  return candidates.find((candidate) => candidate && existsSync19(candidate)) || null;
10403
10639
  }
10404
10640
  function runNpm(args, opts) {
@@ -10410,7 +10646,7 @@ function runNpm(args, opts) {
10410
10646
  return res.status ?? 1;
10411
10647
  }
10412
10648
  function ensureDesktopDeps(desktopDir) {
10413
- if (existsSync19(join19(desktopDir, "node_modules"))) return true;
10649
+ if (existsSync19(join20(desktopDir, "node_modules"))) return true;
10414
10650
  console.log(
10415
10651
  `${C3.cyan}Installing desktop dependencies (one-time)...${C3.reset}`
10416
10652
  );
@@ -10436,7 +10672,7 @@ function requireRust() {
10436
10672
  function hasMsvcBuildTools() {
10437
10673
  if (process.platform !== "win32") return true;
10438
10674
  const pf86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
10439
- const vswhere = join19(
10675
+ const vswhere = join20(
10440
10676
  pf86,
10441
10677
  "Microsoft Visual Studio",
10442
10678
  "Installer",
@@ -10474,7 +10710,7 @@ function requireMsvcOnWindows() {
10474
10710
  return false;
10475
10711
  }
10476
10712
  function warnIfCliMissing(repoRoot) {
10477
- if (!existsSync19(join19(repoRoot, "dist", "cli", "index.js"))) {
10713
+ if (!existsSync19(join20(repoRoot, "dist", "cli", "index.js"))) {
10478
10714
  console.warn(
10479
10715
  `${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}`
10480
10716
  );
@@ -10558,7 +10794,7 @@ var uiCommand = new Command20("ui").description("Launch the ZAM Desktop GUI (Act
10558
10794
  );
10559
10795
  const code = runNpm(["run", "tauri", "build"], { cwd: desktopDir });
10560
10796
  if (code === 0) {
10561
- const bundle = join19(
10797
+ const bundle = join20(
10562
10798
  desktopDir,
10563
10799
  "src-tauri",
10564
10800
  "target",
@@ -10636,7 +10872,7 @@ ${C3.dim}After that, 'zam ui' launches it directly.${C3.reset}`
10636
10872
  // src/cli/commands/update.ts
10637
10873
  import { spawnSync as spawnSync2 } from "child_process";
10638
10874
  import { existsSync as existsSync20, readFileSync as readFileSync13, realpathSync } from "fs";
10639
- import { dirname as dirname9, join as join20 } from "path";
10875
+ import { dirname as dirname9, join as join21 } from "path";
10640
10876
  import { fileURLToPath as fileURLToPath4 } from "url";
10641
10877
  import { confirm as confirm3 } from "@inquirer/prompts";
10642
10878
  import { Command as Command21 } from "commander";
@@ -10661,7 +10897,7 @@ function currentVersion() {
10661
10897
  for (const up of ["..", "../..", "../../.."]) {
10662
10898
  try {
10663
10899
  const pkg2 = JSON.parse(
10664
- readFileSync13(join20(here, up, "package.json"), "utf-8")
10900
+ readFileSync13(join21(here, up, "package.json"), "utf-8")
10665
10901
  );
10666
10902
  if (pkg2.version) return pkg2.version;
10667
10903
  } catch {
@@ -10672,7 +10908,7 @@ function currentVersion() {
10672
10908
  function versionAt(dir) {
10673
10909
  try {
10674
10910
  const pkg2 = JSON.parse(
10675
- readFileSync13(join20(dir, "package.json"), "utf-8")
10911
+ readFileSync13(join21(dir, "package.json"), "utf-8")
10676
10912
  );
10677
10913
  return pkg2.version ?? "unknown";
10678
10914
  } catch {
@@ -10752,11 +10988,11 @@ function findSourceRepo() {
10752
10988
  let dir = realpathSync(dirname9(fileURLToPath4(import.meta.url)));
10753
10989
  let parent = dirname9(dir);
10754
10990
  while (parent !== dir) {
10755
- if (existsSync20(join20(dir, ".git"))) return dir;
10991
+ if (existsSync20(join21(dir, ".git"))) return dir;
10756
10992
  dir = parent;
10757
10993
  parent = dirname9(dir);
10758
10994
  }
10759
- return existsSync20(join20(dir, ".git")) ? dir : null;
10995
+ return existsSync20(join21(dir, ".git")) ? dir : null;
10760
10996
  }
10761
10997
  function runGit(cwd, args, capture) {
10762
10998
  const res = spawnSync2("git", args, {
@@ -10821,7 +11057,7 @@ Commit or stash them, or re-run with ${C4.cyan}--force${C4.reset}.`
10821
11057
  console.log(`${C4.dim}\u2192 zam setup --force${C4.reset}`);
10822
11058
  const setup = spawnSync2(
10823
11059
  process.execPath,
10824
- [join20(src, "dist", "cli", "index.js"), "setup", "--force"],
11060
+ [join21(src, "dist", "cli", "index.js"), "setup", "--force"],
10825
11061
  { cwd: process.cwd(), stdio: "inherit" }
10826
11062
  );
10827
11063
  if (setup.status !== 0) {
@@ -10933,7 +11169,7 @@ var whoamiCommand = new Command22("whoami").description("Show or set the default
10933
11169
  // src/cli/commands/workspace.ts
10934
11170
  import { execSync as execSync6 } from "child_process";
10935
11171
  import { existsSync as existsSync21, writeFileSync as writeFileSync11 } from "fs";
10936
- import { join as join21 } from "path";
11172
+ import { join as join22 } from "path";
10937
11173
  import { confirm as confirm4, input as input7 } from "@inquirer/prompts";
10938
11174
  import { Command as Command23 } from "commander";
10939
11175
  function runGit2(cwd, args) {
@@ -10980,7 +11216,7 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
10980
11216
  );
10981
11217
  process.exit(1);
10982
11218
  }
10983
- const gitignorePath = join21(workspaceDir, ".gitignore");
11219
+ const gitignorePath = join22(workspaceDir, ".gitignore");
10984
11220
  if (!existsSync21(gitignorePath)) {
10985
11221
  writeFileSync11(
10986
11222
  gitignorePath,
@@ -10988,7 +11224,7 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
10988
11224
  "utf8"
10989
11225
  );
10990
11226
  }
10991
- const hasGitRepo = existsSync21(join21(workspaceDir, ".git"));
11227
+ const hasGitRepo = existsSync21(join22(workspaceDir, ".git"));
10992
11228
  if (!hasGitRepo) {
10993
11229
  console.log("Initializing local Git repository...");
10994
11230
  runGit2(workspaceDir, "init -b main");
@@ -11084,7 +11320,7 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
11084
11320
  // src/cli/index.ts
11085
11321
  var __dirname = dirname10(fileURLToPath5(import.meta.url));
11086
11322
  var pkg = JSON.parse(
11087
- readFileSync14(join22(__dirname, "..", "..", "package.json"), "utf-8")
11323
+ readFileSync14(join23(__dirname, "..", "..", "package.json"), "utf-8")
11088
11324
  );
11089
11325
  var program = new Command24();
11090
11326
  program.name("zam").description(