zam-core 0.10.7 → 0.10.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/app.js +1362 -1136
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/commands/mcp.js +551 -325
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/copilot-extension/host.bundle.js +1 -1
- package/dist/copilot-extension/manifest.json +1 -1
- package/dist/copilot-extension/mcp-client.bundle.mjs +1 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/ui/recall-panel.html +1 -1
- package/dist/ui/studio-panel.html +1 -1
- package/dist/vscode-extension/{ZAM_Companion_0.10.7.vsix → ZAM_Companion_0.10.9.vsix} +0 -0
- package/dist/vscode-extension/manifest.json +2 -2
- package/package.json +1 -1
package/dist/cli/app.js
CHANGED
|
@@ -6122,6 +6122,14 @@ function setAgentConnectAutoDone(done, path = defaultConfigPath()) {
|
|
|
6122
6122
|
}
|
|
6123
6123
|
saveInstallConfig(config, path);
|
|
6124
6124
|
}
|
|
6125
|
+
function getLastRepairedVersion(path = defaultConfigPath()) {
|
|
6126
|
+
return loadInstallConfig(path).lastRepairedVersion;
|
|
6127
|
+
}
|
|
6128
|
+
function setLastRepairedVersion(version, path = defaultConfigPath()) {
|
|
6129
|
+
const config = loadInstallConfig(path);
|
|
6130
|
+
config.lastRepairedVersion = version;
|
|
6131
|
+
saveInstallConfig(config, path);
|
|
6132
|
+
}
|
|
6125
6133
|
function getConfiguredWorkspaces(path = defaultConfigPath()) {
|
|
6126
6134
|
return loadInstallConfig(path).workspaces ?? [];
|
|
6127
6135
|
}
|
|
@@ -6844,6 +6852,7 @@ __export(kernel_exports, {
|
|
|
6844
6852
|
getInstallMode: () => getInstallMode,
|
|
6845
6853
|
getKnowledgeContextById: () => getKnowledgeContextById,
|
|
6846
6854
|
getKnowledgeContextByName: () => getKnowledgeContextByName,
|
|
6855
|
+
getLastRepairedVersion: () => getLastRepairedVersion,
|
|
6847
6856
|
getMachineAiConfig: () => getMachineAiConfig,
|
|
6848
6857
|
getMachineAiModels: () => getMachineAiModels,
|
|
6849
6858
|
getMonitorDir: () => getMonitorDir,
|
|
@@ -6939,6 +6948,7 @@ __export(kernel_exports, {
|
|
|
6939
6948
|
setAgentConnectAutoDone: () => setAgentConnectAutoDone,
|
|
6940
6949
|
setInstallChannel: () => setInstallChannel,
|
|
6941
6950
|
setInstallMode: () => setInstallMode,
|
|
6951
|
+
setLastRepairedVersion: () => setLastRepairedVersion,
|
|
6942
6952
|
setProviderApiKey: () => setProviderApiKey,
|
|
6943
6953
|
setSetting: () => setSetting,
|
|
6944
6954
|
setTursoCredentials: () => setTursoCredentials,
|
|
@@ -7015,8 +7025,8 @@ var init_kernel = __esm({
|
|
|
7015
7025
|
});
|
|
7016
7026
|
|
|
7017
7027
|
// src/cli/app.ts
|
|
7018
|
-
import { readFileSync as
|
|
7019
|
-
import { dirname as dirname14, join as
|
|
7028
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
7029
|
+
import { dirname as dirname14, join as join30 } from "path";
|
|
7020
7030
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
7021
7031
|
import { Command as Command27 } from "commander";
|
|
7022
7032
|
|
|
@@ -7435,6 +7445,7 @@ function detectInstalledConnectHarnesses(options = {}) {
|
|
|
7435
7445
|
return detected;
|
|
7436
7446
|
}
|
|
7437
7447
|
function parseMcpJsonConfig(path, content) {
|
|
7448
|
+
if (content.trim() === "") return {};
|
|
7438
7449
|
let parsed;
|
|
7439
7450
|
try {
|
|
7440
7451
|
parsed = JSON.parse(content);
|
|
@@ -7480,17 +7491,21 @@ function connectHarnessMcp(harnessId, opts) {
|
|
|
7480
7491
|
if (!existing.mcpServers) {
|
|
7481
7492
|
existing.mcpServers = {};
|
|
7482
7493
|
}
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7494
|
+
const expected = opts.zamPath.endsWith(".js") ? {
|
|
7495
|
+
command: process.execPath,
|
|
7496
|
+
args: [opts.zamPath, "mcp"]
|
|
7497
|
+
} : {
|
|
7498
|
+
command: opts.zamPath,
|
|
7499
|
+
args: ["mcp"]
|
|
7500
|
+
};
|
|
7501
|
+
const current = existing.mcpServers.zam;
|
|
7502
|
+
if (typeof current === "object" && current !== null && !Array.isArray(current)) {
|
|
7503
|
+
const c = current;
|
|
7504
|
+
if (c.command === expected.command && JSON.stringify(c.args) === JSON.stringify(expected.args)) {
|
|
7505
|
+
alreadyConfigured = true;
|
|
7506
|
+
}
|
|
7493
7507
|
}
|
|
7508
|
+
existing.mcpServers.zam = expected;
|
|
7494
7509
|
return JSON.stringify(existing, null, 2);
|
|
7495
7510
|
};
|
|
7496
7511
|
if (harnessId === "claude-code") {
|
|
@@ -7517,25 +7532,7 @@ function connectHarnessMcp(harnessId, opts) {
|
|
|
7517
7532
|
} else if (harnessId === "antigravity") {
|
|
7518
7533
|
targetPath = join12(opts.home, ".gemini", "config", "mcp_config.json");
|
|
7519
7534
|
hint = "Shared config read by Antigravity CLI and IDE (2.0+); older IDE builds read ~/.gemini/antigravity/mcp_config.json instead. Refresh Installed MCP Servers; the first tool call may still require approval.";
|
|
7520
|
-
|
|
7521
|
-
if (exists(targetPath)) {
|
|
7522
|
-
existing = parseMcpJsonConfig(targetPath, read(targetPath));
|
|
7523
|
-
}
|
|
7524
|
-
if (!existing.mcpServers) {
|
|
7525
|
-
existing.mcpServers = {};
|
|
7526
|
-
}
|
|
7527
|
-
if (opts.zamPath.endsWith(".js")) {
|
|
7528
|
-
existing.mcpServers.zam = {
|
|
7529
|
-
command: process.execPath,
|
|
7530
|
-
args: [opts.zamPath, "mcp"]
|
|
7531
|
-
};
|
|
7532
|
-
} else {
|
|
7533
|
-
existing.mcpServers.zam = {
|
|
7534
|
-
command: opts.zamPath,
|
|
7535
|
-
args: ["mcp"]
|
|
7536
|
-
};
|
|
7537
|
-
}
|
|
7538
|
-
content = JSON.stringify(existing, null, 2);
|
|
7535
|
+
content = mergeMcpServersJson(targetPath);
|
|
7539
7536
|
} else if (harnessId === "opencode") {
|
|
7540
7537
|
targetPath = join12(opts.home, ".config", "opencode", "opencode.json");
|
|
7541
7538
|
hint = "OpenCode will load the enabled 'zam' MCP server on next launch.";
|
|
@@ -7549,11 +7546,19 @@ function connectHarnessMcp(harnessId, opts) {
|
|
|
7549
7546
|
}
|
|
7550
7547
|
const servers = mcp ?? {};
|
|
7551
7548
|
const cmd = opts.zamPath.endsWith(".js") ? [process.execPath, opts.zamPath, "mcp"] : [opts.zamPath, "mcp"];
|
|
7552
|
-
|
|
7549
|
+
const expected = {
|
|
7553
7550
|
type: "local",
|
|
7554
7551
|
command: cmd,
|
|
7555
7552
|
enabled: true
|
|
7556
7553
|
};
|
|
7554
|
+
const current = servers.zam;
|
|
7555
|
+
if (typeof current === "object" && current !== null && !Array.isArray(current)) {
|
|
7556
|
+
const c = current;
|
|
7557
|
+
if (c.type === expected.type && c.enabled === expected.enabled && JSON.stringify(c.command) === JSON.stringify(expected.command)) {
|
|
7558
|
+
alreadyConfigured = true;
|
|
7559
|
+
}
|
|
7560
|
+
}
|
|
7561
|
+
servers.zam = expected;
|
|
7557
7562
|
existing.mcp = servers;
|
|
7558
7563
|
content = JSON.stringify(existing, null, 2);
|
|
7559
7564
|
} else if (harnessId === "codex") {
|
|
@@ -7664,21 +7669,25 @@ ${zamExtension}
|
|
|
7664
7669
|
if (!existing.mcpServers) {
|
|
7665
7670
|
existing.mcpServers = {};
|
|
7666
7671
|
}
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
7670
|
-
|
|
7671
|
-
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7672
|
+
const expected = opts.zamPath.endsWith(".js") ? {
|
|
7673
|
+
type: "local",
|
|
7674
|
+
command: process.execPath,
|
|
7675
|
+
args: [opts.zamPath, "mcp"],
|
|
7676
|
+
tools: ["*"]
|
|
7677
|
+
} : {
|
|
7678
|
+
type: "local",
|
|
7679
|
+
command: opts.zamPath,
|
|
7680
|
+
args: ["mcp"],
|
|
7681
|
+
tools: ["*"]
|
|
7682
|
+
};
|
|
7683
|
+
const current = existing.mcpServers.zam;
|
|
7684
|
+
if (typeof current === "object" && current !== null && !Array.isArray(current)) {
|
|
7685
|
+
const c = current;
|
|
7686
|
+
if (c.type === expected.type && c.command === expected.command && JSON.stringify(c.args) === JSON.stringify(expected.args) && JSON.stringify(c.tools) === JSON.stringify(expected.tools)) {
|
|
7687
|
+
alreadyConfigured = true;
|
|
7688
|
+
}
|
|
7681
7689
|
}
|
|
7690
|
+
existing.mcpServers.zam = expected;
|
|
7682
7691
|
content = JSON.stringify(existing, null, 2);
|
|
7683
7692
|
}
|
|
7684
7693
|
return {
|
|
@@ -8310,11 +8319,11 @@ var agentCommand = new Command("agent").description("Provision and inspect the a
|
|
|
8310
8319
|
|
|
8311
8320
|
// src/cli/commands/bridge.ts
|
|
8312
8321
|
init_kernel();
|
|
8313
|
-
import { execFileSync as
|
|
8322
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
8314
8323
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
8315
|
-
import { existsSync as
|
|
8316
|
-
import { homedir as
|
|
8317
|
-
import { join as
|
|
8324
|
+
import { existsSync as existsSync21, readdirSync as readdirSync2, readFileSync as readFileSync17, rmSync as rmSync3 } from "fs";
|
|
8325
|
+
import { homedir as homedir14, tmpdir as tmpdir3 } from "os";
|
|
8326
|
+
import { join as join23, resolve as resolve6 } from "path";
|
|
8318
8327
|
import { Command as Command2 } from "commander";
|
|
8319
8328
|
import { ulid as ulid10 } from "ulid";
|
|
8320
8329
|
|
|
@@ -9401,7 +9410,7 @@ async function prepareRecallChain(db, opts) {
|
|
|
9401
9410
|
} else {
|
|
9402
9411
|
spawnLocalRunner(endpoint.url, endpoint.model, endpoint.runner);
|
|
9403
9412
|
while (Date.now() < deadline) {
|
|
9404
|
-
await new Promise((
|
|
9413
|
+
await new Promise((resolve12) => setTimeout(resolve12, 1e3));
|
|
9405
9414
|
if (await isLlmOnline(endpoint.url)) {
|
|
9406
9415
|
online = true;
|
|
9407
9416
|
break;
|
|
@@ -9694,7 +9703,7 @@ async function startLocalRunner(url, model, locale, hint) {
|
|
|
9694
9703
|
let attempts = 0;
|
|
9695
9704
|
const dotsPerLine = 30;
|
|
9696
9705
|
while (true) {
|
|
9697
|
-
await new Promise((
|
|
9706
|
+
await new Promise((resolve12) => setTimeout(resolve12, 500));
|
|
9698
9707
|
if (await isLlmOnline(url)) {
|
|
9699
9708
|
if (attempts > 0) process.stdout.write("\n");
|
|
9700
9709
|
return true;
|
|
@@ -9783,8 +9792,8 @@ async function fetchWithInteractiveTimeout(url, options = {}) {
|
|
|
9783
9792
|
const dotsPerLine = 30;
|
|
9784
9793
|
while (true) {
|
|
9785
9794
|
let timeoutId;
|
|
9786
|
-
const timeoutPromise = new Promise((
|
|
9787
|
-
timeoutId = setTimeout(() =>
|
|
9795
|
+
const timeoutPromise = new Promise((resolve12) => {
|
|
9796
|
+
timeoutId = setTimeout(() => resolve12("timeout"), timeoutMs);
|
|
9788
9797
|
});
|
|
9789
9798
|
const dotsInterval = setInterval(() => {
|
|
9790
9799
|
process.stdout.write(".");
|
|
@@ -10051,7 +10060,7 @@ async function readWebLink(url) {
|
|
|
10051
10060
|
redirect: "manual",
|
|
10052
10061
|
signal: controller.signal,
|
|
10053
10062
|
headers: {
|
|
10054
|
-
"User-Agent": "ZAM-Content-Studio/0.10.
|
|
10063
|
+
"User-Agent": "ZAM-Content-Studio/0.10.9"
|
|
10055
10064
|
}
|
|
10056
10065
|
});
|
|
10057
10066
|
if (res.status >= 300 && res.status < 400) {
|
|
@@ -11314,6 +11323,127 @@ async function updateCheck(params) {
|
|
|
11314
11323
|
});
|
|
11315
11324
|
}
|
|
11316
11325
|
|
|
11326
|
+
// src/cli/cli-install.ts
|
|
11327
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
11328
|
+
import {
|
|
11329
|
+
chmodSync as chmodSync2,
|
|
11330
|
+
existsSync as existsSync18,
|
|
11331
|
+
mkdirSync as mkdirSync13,
|
|
11332
|
+
readFileSync as readFileSync14,
|
|
11333
|
+
writeFileSync as writeFileSync11
|
|
11334
|
+
} from "fs";
|
|
11335
|
+
import { homedir as homedir13 } from "os";
|
|
11336
|
+
import { delimiter as delimiter2, join as join19, resolve as resolve5 } from "path";
|
|
11337
|
+
var BUILT_CLI_PATTERN = /[\\/]dist[\\/]cli[\\/]index\.js$/;
|
|
11338
|
+
function normalizeForCompare(path, platform) {
|
|
11339
|
+
const resolved = resolve5(path);
|
|
11340
|
+
return platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
11341
|
+
}
|
|
11342
|
+
function windowsShimContent(nodePath, cliPath) {
|
|
11343
|
+
return `@echo off\r
|
|
11344
|
+
"${nodePath}" "${cliPath}" %*\r
|
|
11345
|
+
`;
|
|
11346
|
+
}
|
|
11347
|
+
function unixShimContent(nodePath, cliPath) {
|
|
11348
|
+
return `#!/bin/sh
|
|
11349
|
+
exec "${nodePath}" "${cliPath}" "$@"
|
|
11350
|
+
`;
|
|
11351
|
+
}
|
|
11352
|
+
function psQuote(value) {
|
|
11353
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
11354
|
+
}
|
|
11355
|
+
function ensureWindowsUserPath(binDir) {
|
|
11356
|
+
const script = [
|
|
11357
|
+
"$ErrorActionPreference = 'Stop'",
|
|
11358
|
+
"$key = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey('Environment', $true)",
|
|
11359
|
+
"$old = [string]$key.GetValue('Path', '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)",
|
|
11360
|
+
`$target = ${psQuote(binDir)}`,
|
|
11361
|
+
"$expanded = ($old -split ';') | Where-Object { $_ -ne '' } | ForEach-Object { [Environment]::ExpandEnvironmentVariables($_).TrimEnd('\\') }",
|
|
11362
|
+
"if ($expanded -contains $target.TrimEnd('\\')) { 'present'; exit 0 }",
|
|
11363
|
+
"$new = if ($old -and -not $old.EndsWith(';')) { $old + ';' + $target } else { $old + $target }",
|
|
11364
|
+
"$key.SetValue('Path', $new, [Microsoft.Win32.RegistryValueKind]::ExpandString)",
|
|
11365
|
+
`$sig = '[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);'`,
|
|
11366
|
+
"$type = Add-Type -MemberDefinition $sig -Name 'ZamEnvBroadcast' -Namespace 'ZamInstall' -PassThru",
|
|
11367
|
+
"[UIntPtr]$result = [UIntPtr]::Zero",
|
|
11368
|
+
"$null = $type::SendMessageTimeout([IntPtr]0xffff, 0x001A, [UIntPtr]::Zero, 'Environment', 2, 5000, [ref]$result)",
|
|
11369
|
+
"'updated'"
|
|
11370
|
+
].join("; ");
|
|
11371
|
+
const output = execFileSync4(
|
|
11372
|
+
"powershell.exe",
|
|
11373
|
+
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
11374
|
+
{ stdio: ["ignore", "pipe", "pipe"], windowsHide: true }
|
|
11375
|
+
).toString().trim();
|
|
11376
|
+
return output.endsWith("updated");
|
|
11377
|
+
}
|
|
11378
|
+
function ensureUnixUserPath(home, platform) {
|
|
11379
|
+
const profile = join19(home, platform === "darwin" ? ".zprofile" : ".profile");
|
|
11380
|
+
const existing = existsSync18(profile) ? readFileSync14(profile, "utf8") : "";
|
|
11381
|
+
if (existing.includes(".zam/bin")) return false;
|
|
11382
|
+
const block = `
|
|
11383
|
+
# Added by ZAM: keep the zam CLI on PATH
|
|
11384
|
+
export PATH="$HOME/.zam/bin:$PATH"
|
|
11385
|
+
`;
|
|
11386
|
+
writeFileSync11(profile, existing + block, "utf8");
|
|
11387
|
+
return true;
|
|
11388
|
+
}
|
|
11389
|
+
function installCliShim(options = {}) {
|
|
11390
|
+
const home = options.home ?? homedir13();
|
|
11391
|
+
const platform = options.platform ?? process.platform;
|
|
11392
|
+
const env = options.env ?? process.env;
|
|
11393
|
+
const find = options.find ?? findExecutable;
|
|
11394
|
+
const nodePath = resolve5(options.nodePath ?? process.execPath);
|
|
11395
|
+
const cliPath = resolve5(options.cliPath ?? process.argv[1] ?? "");
|
|
11396
|
+
const binDir = join19(home, ".zam", "bin");
|
|
11397
|
+
const shimPath = join19(binDir, platform === "win32" ? "zam.cmd" : "zam");
|
|
11398
|
+
const report = {
|
|
11399
|
+
status: "ok",
|
|
11400
|
+
binDir,
|
|
11401
|
+
shimPath,
|
|
11402
|
+
nodePath,
|
|
11403
|
+
cliPath,
|
|
11404
|
+
onPath: false,
|
|
11405
|
+
pathUpdated: false,
|
|
11406
|
+
needsNewTerminal: false
|
|
11407
|
+
};
|
|
11408
|
+
if (!BUILT_CLI_PATTERN.test(cliPath) || !existsSync18(cliPath)) {
|
|
11409
|
+
report.status = "skipped";
|
|
11410
|
+
report.detail = `Not running from a built CLI entry (${cliPath}); nothing to link.`;
|
|
11411
|
+
return report;
|
|
11412
|
+
}
|
|
11413
|
+
try {
|
|
11414
|
+
const existing = find("zam");
|
|
11415
|
+
if (existing && normalizeForCompare(existing, platform) !== normalizeForCompare(shimPath, platform)) {
|
|
11416
|
+
report.status = "external";
|
|
11417
|
+
report.onPath = true;
|
|
11418
|
+
report.detail = existing;
|
|
11419
|
+
return report;
|
|
11420
|
+
}
|
|
11421
|
+
const content = platform === "win32" ? windowsShimContent(nodePath, cliPath) : unixShimContent(nodePath, cliPath);
|
|
11422
|
+
const existed = existsSync18(shimPath);
|
|
11423
|
+
const changed = !existed || readFileSync14(shimPath, "utf8") !== content;
|
|
11424
|
+
if (changed) {
|
|
11425
|
+
mkdirSync13(binDir, { recursive: true });
|
|
11426
|
+
writeFileSync11(shimPath, content, "utf8");
|
|
11427
|
+
if (platform !== "win32") chmodSync2(shimPath, 493);
|
|
11428
|
+
}
|
|
11429
|
+
report.status = !existed ? "installed" : changed ? "refreshed" : "ok";
|
|
11430
|
+
const processPathHasBin = (env.PATH ?? "").split(delimiter2).map((entry) => normalizeForCompare(entry.trim() || ".", platform)).includes(normalizeForCompare(binDir, platform));
|
|
11431
|
+
if (processPathHasBin) {
|
|
11432
|
+
report.onPath = true;
|
|
11433
|
+
return report;
|
|
11434
|
+
}
|
|
11435
|
+
const ensureUserPath = options.ensureUserPath ?? (platform === "win32" ? ensureWindowsUserPath : () => ensureUnixUserPath(home, platform));
|
|
11436
|
+
report.pathUpdated = ensureUserPath(binDir);
|
|
11437
|
+
report.onPath = true;
|
|
11438
|
+
report.needsNewTerminal = true;
|
|
11439
|
+
return report;
|
|
11440
|
+
} catch (error) {
|
|
11441
|
+
report.status = "error";
|
|
11442
|
+
report.detail = error instanceof Error ? error.message : String(error);
|
|
11443
|
+
return report;
|
|
11444
|
+
}
|
|
11445
|
+
}
|
|
11446
|
+
|
|
11317
11447
|
// src/cli/curriculum/breadcrumb.ts
|
|
11318
11448
|
init_kernel();
|
|
11319
11449
|
var LAST_SELECTION_KEY = "curriculum.lastSelection";
|
|
@@ -28000,1054 +28130,1132 @@ function getCurriculumProvider(id) {
|
|
|
28000
28130
|
return CURRICULUM_PROVIDERS.find((provider) => provider.id === id);
|
|
28001
28131
|
}
|
|
28002
28132
|
|
|
28003
|
-
// src/cli/
|
|
28133
|
+
// src/cli/install-repair.ts
|
|
28004
28134
|
init_kernel();
|
|
28005
|
-
|
|
28006
|
-
|
|
28007
|
-
|
|
28008
|
-
|
|
28009
|
-
|
|
28010
|
-
|
|
28011
|
-
|
|
28012
|
-
|
|
28013
|
-
|
|
28014
|
-
|
|
28015
|
-
|
|
28016
|
-
|
|
28017
|
-
|
|
28018
|
-
|
|
28019
|
-
|
|
28020
|
-
|
|
28021
|
-
"
|
|
28022
|
-
"
|
|
28023
|
-
|
|
28024
|
-
|
|
28025
|
-
|
|
28026
|
-
|
|
28027
|
-
|
|
28028
|
-
|
|
28029
|
-
|
|
28030
|
-
|
|
28135
|
+
import { existsSync as existsSync20 } from "fs";
|
|
28136
|
+
|
|
28137
|
+
// src/cli/provisioning/index.ts
|
|
28138
|
+
import {
|
|
28139
|
+
existsSync as existsSync19,
|
|
28140
|
+
lstatSync as lstatSync2,
|
|
28141
|
+
mkdirSync as mkdirSync14,
|
|
28142
|
+
readFileSync as readFileSync15,
|
|
28143
|
+
realpathSync,
|
|
28144
|
+
rmSync as rmSync2,
|
|
28145
|
+
symlinkSync,
|
|
28146
|
+
writeFileSync as writeFileSync12
|
|
28147
|
+
} from "fs";
|
|
28148
|
+
import { basename as basename4, dirname as dirname9, join as join20 } from "path";
|
|
28149
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
28150
|
+
var packageRoot3 = [
|
|
28151
|
+
fileURLToPath5(new URL("../..", import.meta.url)),
|
|
28152
|
+
fileURLToPath5(new URL("../../..", import.meta.url))
|
|
28153
|
+
].find((candidate) => existsSync19(join20(candidate, "package.json"))) ?? fileURLToPath5(new URL("../..", import.meta.url));
|
|
28154
|
+
var ALL_SETUP_AGENTS = ["claude", "copilot", "codex", "agent"];
|
|
28155
|
+
var SKILL_PAIRS = [
|
|
28156
|
+
{
|
|
28157
|
+
from: join20(packageRoot3, ".claude", "skills", "zam", "SKILL.md"),
|
|
28158
|
+
to: join20(".claude", "skills", "zam", "SKILL.md"),
|
|
28159
|
+
agents: ["claude", "copilot"]
|
|
28160
|
+
},
|
|
28161
|
+
{
|
|
28162
|
+
from: join20(packageRoot3, ".agent", "skills", "zam", "SKILL.md"),
|
|
28163
|
+
to: join20(".agent", "skills", "zam", "SKILL.md"),
|
|
28164
|
+
agents: ["agent"]
|
|
28165
|
+
},
|
|
28166
|
+
{
|
|
28167
|
+
from: join20(packageRoot3, ".agents", "skills", "zam", "SKILL.md"),
|
|
28168
|
+
to: join20(".agents", "skills", "zam", "SKILL.md"),
|
|
28169
|
+
agents: ["codex"]
|
|
28170
|
+
}
|
|
28031
28171
|
];
|
|
28032
|
-
function
|
|
28033
|
-
|
|
28034
|
-
|
|
28172
|
+
function parseSetupAgents(value) {
|
|
28173
|
+
if (!value || value.trim().toLowerCase() === "all") {
|
|
28174
|
+
return new Set(ALL_SETUP_AGENTS);
|
|
28175
|
+
}
|
|
28176
|
+
const aliases = {
|
|
28177
|
+
claude: ["claude"],
|
|
28178
|
+
copilot: ["copilot"],
|
|
28179
|
+
codex: ["codex"],
|
|
28180
|
+
agent: ["agent"],
|
|
28181
|
+
opencode: ["agent"]
|
|
28182
|
+
};
|
|
28183
|
+
const selected = /* @__PURE__ */ new Set();
|
|
28184
|
+
for (const raw of value.split(",")) {
|
|
28185
|
+
const key = raw.trim().toLowerCase();
|
|
28186
|
+
const mapped = aliases[key];
|
|
28187
|
+
if (!mapped) {
|
|
28188
|
+
throw new Error(
|
|
28189
|
+
`Unknown agent "${raw}". Use: all, claude, copilot, codex, agent.`
|
|
28190
|
+
);
|
|
28191
|
+
}
|
|
28192
|
+
for (const item of mapped) selected.add(item);
|
|
28193
|
+
}
|
|
28194
|
+
return selected;
|
|
28035
28195
|
}
|
|
28036
|
-
function
|
|
28037
|
-
|
|
28038
|
-
|
|
28196
|
+
function pathExists(path) {
|
|
28197
|
+
try {
|
|
28198
|
+
lstatSync2(path);
|
|
28199
|
+
return true;
|
|
28200
|
+
} catch {
|
|
28201
|
+
return false;
|
|
28202
|
+
}
|
|
28039
28203
|
}
|
|
28040
|
-
function
|
|
28041
|
-
|
|
28042
|
-
|
|
28043
|
-
|
|
28044
|
-
|
|
28045
|
-
return detected;
|
|
28204
|
+
function isSymbolicLink(path) {
|
|
28205
|
+
try {
|
|
28206
|
+
return lstatSync2(path).isSymbolicLink();
|
|
28207
|
+
} catch {
|
|
28208
|
+
return false;
|
|
28046
28209
|
}
|
|
28047
|
-
const looksEmbedding = matchesAny(entry.model, EMBEDDING_MODEL_HINTS);
|
|
28048
|
-
const looksVision = matchesAny(entry.model, VISION_MODEL_HINTS);
|
|
28049
|
-
const inCatalog = catalogHasModel(catalog, entry.model);
|
|
28050
|
-
detected.embedding = looksEmbedding || dimProbeEmbedding;
|
|
28051
|
-
detected.image = looksVision;
|
|
28052
|
-
detected.text = !detected.embedding && (inCatalog || !catalogKnown);
|
|
28053
|
-
return detected;
|
|
28054
28210
|
}
|
|
28055
|
-
function
|
|
28056
|
-
|
|
28057
|
-
return
|
|
28211
|
+
function comparableRealPath(path) {
|
|
28212
|
+
const real = realpathSync(path);
|
|
28213
|
+
return process.platform === "win32" ? real.toLowerCase() : real;
|
|
28058
28214
|
}
|
|
28059
|
-
|
|
28060
|
-
|
|
28061
|
-
|
|
28062
|
-
|
|
28063
|
-
return
|
|
28064
|
-
reachable: online2,
|
|
28065
|
-
catalog: [],
|
|
28066
|
-
detected: classifyCapabilities(entry, [], false)
|
|
28067
|
-
};
|
|
28068
|
-
}
|
|
28069
|
-
const online = await isLlmOnline(entry.url);
|
|
28070
|
-
if (!online) {
|
|
28071
|
-
return { reachable: false, catalog: [], detected: emptyCapabilityFlags() };
|
|
28072
|
-
}
|
|
28073
|
-
const catalog = await getAvailableModels(entry.url, apiKey);
|
|
28074
|
-
const catalogKnown = catalog.length > 0;
|
|
28075
|
-
let dimProbeEmbedding = false;
|
|
28076
|
-
const looksEmbedding = matchesAny(entry.model, EMBEDDING_MODEL_HINTS);
|
|
28077
|
-
if (opts.embeddingDimProbe && !catalogKnown && !looksEmbedding) {
|
|
28078
|
-
try {
|
|
28079
|
-
const [vector] = await embedTexts(
|
|
28080
|
-
{ url: entry.url, model: entry.model, apiKey },
|
|
28081
|
-
["capability probe"]
|
|
28082
|
-
);
|
|
28083
|
-
dimProbeEmbedding = Array.isArray(vector) && vector.length > 0;
|
|
28084
|
-
} catch {
|
|
28085
|
-
dimProbeEmbedding = false;
|
|
28086
|
-
}
|
|
28215
|
+
function pathsResolveToSameDirectory(sourceDir, destinationDir) {
|
|
28216
|
+
try {
|
|
28217
|
+
return comparableRealPath(destinationDir) === comparableRealPath(sourceDir);
|
|
28218
|
+
} catch {
|
|
28219
|
+
return false;
|
|
28087
28220
|
}
|
|
28088
|
-
return {
|
|
28089
|
-
reachable: true,
|
|
28090
|
-
catalog,
|
|
28091
|
-
detected: classifyCapabilities(
|
|
28092
|
-
entry,
|
|
28093
|
-
catalog,
|
|
28094
|
-
catalogKnown,
|
|
28095
|
-
dimProbeEmbedding
|
|
28096
|
-
)
|
|
28097
|
-
};
|
|
28098
28221
|
}
|
|
28099
|
-
function
|
|
28100
|
-
|
|
28101
|
-
for (const key of Object.keys(result)) {
|
|
28102
|
-
result[key] = userSelected[key] && detected[key];
|
|
28103
|
-
}
|
|
28104
|
-
return result;
|
|
28222
|
+
function linkPointsTo(sourceDir, destinationDir) {
|
|
28223
|
+
return isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir);
|
|
28105
28224
|
}
|
|
28106
|
-
function
|
|
28107
|
-
|
|
28108
|
-
return
|
|
28109
|
-
|
|
28110
|
-
|
|
28111
|
-
|
|
28225
|
+
function isZamSkillCopy(destinationDir) {
|
|
28226
|
+
try {
|
|
28227
|
+
if (!lstatSync2(destinationDir).isDirectory()) return false;
|
|
28228
|
+
const skillFile = join20(destinationDir, "SKILL.md");
|
|
28229
|
+
if (!existsSync19(skillFile)) return false;
|
|
28230
|
+
return /^name:\s*zam\s*$/m.test(readFileSync15(skillFile, "utf8"));
|
|
28231
|
+
} catch {
|
|
28232
|
+
return false;
|
|
28112
28233
|
}
|
|
28113
|
-
return {
|
|
28114
|
-
ok: true,
|
|
28115
|
-
entry: {
|
|
28116
|
-
...entry,
|
|
28117
|
-
capabilities: reconcileCapabilities(entry.capabilities, probe.detected),
|
|
28118
|
-
detectedCapabilities: probe.detected,
|
|
28119
|
-
probedAt: now()
|
|
28120
|
-
}
|
|
28121
|
-
};
|
|
28122
28234
|
}
|
|
28123
|
-
|
|
28124
|
-
|
|
28125
|
-
|
|
28126
|
-
|
|
28127
|
-
import { readFileSync as readFileSync14 } from "fs";
|
|
28128
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
28129
|
-
import { basename as basename4, join as join19 } from "path";
|
|
28130
|
-
var LANGUAGE_NAMES2 = {
|
|
28131
|
-
en: "English",
|
|
28132
|
-
de: "German",
|
|
28133
|
-
es: "Spanish",
|
|
28134
|
-
fr: "French",
|
|
28135
|
-
pt: "Portuguese",
|
|
28136
|
-
zh: "Chinese",
|
|
28137
|
-
ja: "Japanese"
|
|
28138
|
-
};
|
|
28139
|
-
var OBSERVATION_KINDS2 = /* @__PURE__ */ new Set([
|
|
28140
|
-
"progress",
|
|
28141
|
-
"step-completed",
|
|
28142
|
-
"error",
|
|
28143
|
-
"help-seeking",
|
|
28144
|
-
"uncertain",
|
|
28145
|
-
"privacy-pause",
|
|
28146
|
-
"heartbeat"
|
|
28147
|
-
]);
|
|
28148
|
-
var ACTION_TYPES2 = /* @__PURE__ */ new Set([
|
|
28149
|
-
"click",
|
|
28150
|
-
"shortcut",
|
|
28151
|
-
"typing",
|
|
28152
|
-
"scroll",
|
|
28153
|
-
"window-change"
|
|
28154
|
-
]);
|
|
28155
|
-
async function observeUiSnapshotViaLLM(db, input8) {
|
|
28156
|
-
const cfg = await getProviderForRole(db, "vision");
|
|
28157
|
-
if (!cfg.enabled) {
|
|
28158
|
-
throw new Error(
|
|
28159
|
-
"Vision observation is disabled in settings (llm.vision.enabled)"
|
|
28160
|
-
);
|
|
28235
|
+
function classifySkillDestination(sourceDir, destinationDir) {
|
|
28236
|
+
if (!existsSync19(sourceDir)) return "source-missing";
|
|
28237
|
+
if (!isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir)) {
|
|
28238
|
+
return "source-directory";
|
|
28161
28239
|
}
|
|
28162
|
-
|
|
28163
|
-
|
|
28164
|
-
if (
|
|
28165
|
-
|
|
28166
|
-
|
|
28167
|
-
|
|
28168
|
-
|
|
28169
|
-
|
|
28170
|
-
|
|
28171
|
-
|
|
28172
|
-
|
|
28173
|
-
|
|
28174
|
-
|
|
28175
|
-
|
|
28176
|
-
|
|
28177
|
-
|
|
28178
|
-
|
|
28179
|
-
|
|
28180
|
-
|
|
28181
|
-
|
|
28182
|
-
);
|
|
28183
|
-
files = readdirSync3(tempDir).filter((f) => f.endsWith(".png")).sort();
|
|
28184
|
-
}
|
|
28185
|
-
const maxFrames = cfg.maxFrames ?? 100;
|
|
28186
|
-
let sampledFiles = files;
|
|
28187
|
-
if (files.length > maxFrames) {
|
|
28188
|
-
if (maxFrames <= 1) {
|
|
28189
|
-
sampledFiles = [files[0]];
|
|
28190
|
-
} else {
|
|
28191
|
-
const step = (files.length - 1) / (maxFrames - 1);
|
|
28192
|
-
sampledFiles = [];
|
|
28193
|
-
for (let i = 0; i < maxFrames; i++) {
|
|
28194
|
-
const index = Math.round(i * step);
|
|
28195
|
-
sampledFiles.push(files[index]);
|
|
28196
|
-
}
|
|
28197
|
-
}
|
|
28198
|
-
}
|
|
28199
|
-
for (const file of sampledFiles) {
|
|
28200
|
-
const bytes = readFileSync14(join19(tempDir, file));
|
|
28201
|
-
imageUrls.push(`data:image/png;base64,${bytes.toString("base64")}`);
|
|
28202
|
-
}
|
|
28203
|
-
} finally {
|
|
28204
|
-
try {
|
|
28205
|
-
rmSync4(tempDir, { recursive: true, force: true });
|
|
28206
|
-
} catch {
|
|
28240
|
+
if (linkPointsTo(sourceDir, destinationDir)) return "linked";
|
|
28241
|
+
if (!pathExists(destinationDir)) return "missing";
|
|
28242
|
+
if (isSymbolicLink(destinationDir)) return "broken";
|
|
28243
|
+
if (isZamSkillCopy(destinationDir)) return "stale-copy";
|
|
28244
|
+
return "unmanaged";
|
|
28245
|
+
}
|
|
28246
|
+
function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {}) {
|
|
28247
|
+
const results = [];
|
|
28248
|
+
const log = (message) => {
|
|
28249
|
+
if (!opts.quiet) console.log(message);
|
|
28250
|
+
};
|
|
28251
|
+
for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
|
|
28252
|
+
if (!pairAgents.some((agent) => agents.has(agent))) continue;
|
|
28253
|
+
const sourceDir = dirname9(from);
|
|
28254
|
+
const destinationDir = dirname9(join20(cwd, to));
|
|
28255
|
+
const label = dirname9(to);
|
|
28256
|
+
const state = classifySkillDestination(sourceDir, destinationDir);
|
|
28257
|
+
if (state === "source-missing") {
|
|
28258
|
+
if (!opts.quiet) {
|
|
28259
|
+
console.warn(` warn source not found, skipping: ${sourceDir}`);
|
|
28207
28260
|
}
|
|
28261
|
+
results.push({
|
|
28262
|
+
source: sourceDir,
|
|
28263
|
+
destination: destinationDir,
|
|
28264
|
+
action: "skipped",
|
|
28265
|
+
reason: "source-not-found"
|
|
28266
|
+
});
|
|
28267
|
+
continue;
|
|
28208
28268
|
}
|
|
28209
|
-
|
|
28210
|
-
|
|
28211
|
-
|
|
28212
|
-
|
|
28213
|
-
|
|
28214
|
-
|
|
28215
|
-
|
|
28216
|
-
|
|
28217
|
-
|
|
28218
|
-
const endpoints = [
|
|
28219
|
-
{
|
|
28220
|
-
url: cfg.url,
|
|
28221
|
-
apiKey: cfg.apiKey || DEFAULT_LLM_API_KEY,
|
|
28222
|
-
model: input8.model ?? cfg.model,
|
|
28223
|
-
apiFlavor: cfg.apiFlavor
|
|
28269
|
+
if (state === "source-directory") {
|
|
28270
|
+
log(` skip ${label} (package source)`);
|
|
28271
|
+
results.push({
|
|
28272
|
+
source: sourceDir,
|
|
28273
|
+
destination: destinationDir,
|
|
28274
|
+
action: "skipped",
|
|
28275
|
+
reason: "source-directory"
|
|
28276
|
+
});
|
|
28277
|
+
continue;
|
|
28224
28278
|
}
|
|
28225
|
-
|
|
28226
|
-
|
|
28227
|
-
|
|
28228
|
-
|
|
28229
|
-
|
|
28230
|
-
|
|
28231
|
-
|
|
28232
|
-
});
|
|
28233
|
-
}
|
|
28234
|
-
let lastRequestError;
|
|
28235
|
-
let sawUnparseableDraft = false;
|
|
28236
|
-
let sawInvalidDraft = false;
|
|
28237
|
-
for (const endpoint of endpoints) {
|
|
28238
|
-
let content;
|
|
28239
|
-
try {
|
|
28240
|
-
content = await requestVisionDraft({
|
|
28241
|
-
...endpoint,
|
|
28242
|
-
locale: cfg.locale,
|
|
28243
|
-
imageUrls,
|
|
28244
|
-
input: input8
|
|
28279
|
+
if (state === "linked") {
|
|
28280
|
+
log(` skip ${label} (already linked)`);
|
|
28281
|
+
results.push({
|
|
28282
|
+
source: sourceDir,
|
|
28283
|
+
destination: destinationDir,
|
|
28284
|
+
action: "skipped",
|
|
28285
|
+
reason: "already-linked"
|
|
28245
28286
|
});
|
|
28246
|
-
} catch (err) {
|
|
28247
|
-
lastRequestError = err;
|
|
28248
28287
|
continue;
|
|
28249
28288
|
}
|
|
28250
|
-
|
|
28251
|
-
|
|
28252
|
-
|
|
28253
|
-
|
|
28254
|
-
|
|
28289
|
+
const destinationExists = state !== "missing";
|
|
28290
|
+
const replaceExisting = destinationExists && (Boolean(opts.force) || state === "broken" || state === "stale-copy");
|
|
28291
|
+
if (destinationExists && !replaceExisting) {
|
|
28292
|
+
if (!opts.quiet) {
|
|
28293
|
+
console.warn(
|
|
28294
|
+
` warn ${label} already exists and is not managed by ZAM; use --force to replace it`
|
|
28295
|
+
);
|
|
28296
|
+
}
|
|
28297
|
+
results.push({
|
|
28298
|
+
source: sourceDir,
|
|
28299
|
+
destination: destinationDir,
|
|
28300
|
+
action: "skipped",
|
|
28301
|
+
reason: "unmanaged-destination"
|
|
28302
|
+
});
|
|
28255
28303
|
continue;
|
|
28256
28304
|
}
|
|
28257
|
-
const
|
|
28258
|
-
if (
|
|
28259
|
-
|
|
28305
|
+
const action = destinationExists ? "relinked" : "linked";
|
|
28306
|
+
if (opts.dryRun) {
|
|
28307
|
+
log(` would ${action === "linked" ? "link" : "relink"} ${label}`);
|
|
28308
|
+
} else {
|
|
28309
|
+
if (destinationExists) {
|
|
28310
|
+
rmSync2(destinationDir, { recursive: true, force: true });
|
|
28311
|
+
}
|
|
28312
|
+
mkdirSync14(dirname9(destinationDir), { recursive: true });
|
|
28313
|
+
symlinkSync(
|
|
28314
|
+
sourceDir,
|
|
28315
|
+
destinationDir,
|
|
28316
|
+
process.platform === "win32" ? "junction" : "dir"
|
|
28317
|
+
);
|
|
28318
|
+
log(` ${action === "linked" ? "link" : "relink"} ${label}`);
|
|
28260
28319
|
}
|
|
28261
|
-
|
|
28262
|
-
}
|
|
28263
|
-
if (lastRequestError && !sawUnparseableDraft && !sawInvalidDraft) {
|
|
28264
|
-
throw lastRequestError;
|
|
28265
|
-
}
|
|
28266
|
-
if (sawInvalidDraft) {
|
|
28267
|
-
return uncertainReport(
|
|
28268
|
-
input8,
|
|
28269
|
-
"Vision model returned an invalid UI report draft."
|
|
28270
|
-
);
|
|
28320
|
+
results.push({ source: sourceDir, destination: destinationDir, action });
|
|
28271
28321
|
}
|
|
28272
|
-
return
|
|
28273
|
-
input8,
|
|
28274
|
-
"Vision model returned output that could not be parsed as JSON."
|
|
28275
|
-
);
|
|
28276
|
-
}
|
|
28277
|
-
var VISION_SYSTEM_PROMPT = "You are ZAM's UI observer. Return only strict JSON. Do not include markdown, prose, or fields outside the requested schema.";
|
|
28278
|
-
function visionSchema(language) {
|
|
28279
|
-
return `{
|
|
28280
|
-
"kind": "progress | step-completed | error | help-seeking | uncertain",
|
|
28281
|
-
"summary": "short factual UI summary in ${language}",
|
|
28282
|
-
"actions": [{"type": "click | shortcut | typing | scroll | window-change", "target": "optional UI target", "result": "optional visible result"}],
|
|
28283
|
-
"candidateTokens": [{"slug": "optional-kebab-case-skill-token", "confidence": 0.0, "rationale": "why this token may matter"}],
|
|
28284
|
-
"confidence": 0.0
|
|
28285
|
-
}`;
|
|
28322
|
+
return results;
|
|
28286
28323
|
}
|
|
28287
|
-
function
|
|
28288
|
-
const
|
|
28289
|
-
|
|
28290
|
-
|
|
28291
|
-
|
|
28292
|
-
|
|
28293
|
-
|
|
28294
|
-
|
|
28324
|
+
function inspectSkillLinks(cwd = process.cwd(), agents = parseSetupAgents()) {
|
|
28325
|
+
const results = [];
|
|
28326
|
+
for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
|
|
28327
|
+
if (!pairAgents.some((agent) => agents.has(agent))) continue;
|
|
28328
|
+
const sourceDir = dirname9(from);
|
|
28329
|
+
const destinationDir = dirname9(join20(cwd, to));
|
|
28330
|
+
results.push({
|
|
28331
|
+
agents: pairAgents,
|
|
28332
|
+
source: sourceDir,
|
|
28333
|
+
destination: destinationDir,
|
|
28334
|
+
state: classifySkillDestination(sourceDir, destinationDir)
|
|
28335
|
+
});
|
|
28336
|
+
}
|
|
28337
|
+
return results;
|
|
28295
28338
|
}
|
|
28296
|
-
|
|
28297
|
-
if (
|
|
28298
|
-
return
|
|
28339
|
+
function summarizeSkillLinkHealth(inspections) {
|
|
28340
|
+
if (inspections.some((item) => item.state === "unmanaged")) {
|
|
28341
|
+
return "unmanaged";
|
|
28299
28342
|
}
|
|
28300
|
-
|
|
28343
|
+
const repairable = ["missing", "broken", "stale-copy"];
|
|
28344
|
+
if (inspections.some((item) => repairable.includes(item.state))) {
|
|
28345
|
+
return "needs-repair";
|
|
28346
|
+
}
|
|
28347
|
+
return "healthy";
|
|
28301
28348
|
}
|
|
28302
|
-
|
|
28303
|
-
|
|
28304
|
-
|
|
28305
|
-
|
|
28306
|
-
|
|
28307
|
-
|
|
28308
|
-
|
|
28309
|
-
|
|
28310
|
-
|
|
28311
|
-
}
|
|
28312
|
-
|
|
28313
|
-
model: args.model,
|
|
28314
|
-
messages: [
|
|
28315
|
-
{ role: "system", content: VISION_SYSTEM_PROMPT },
|
|
28316
|
-
{
|
|
28317
|
-
role: "user",
|
|
28318
|
-
content: [
|
|
28319
|
-
{ type: "text", text: visionUserText(args, language) },
|
|
28320
|
-
...args.imageUrls.map((url) => ({
|
|
28321
|
-
type: "image_url",
|
|
28322
|
-
image_url: { url }
|
|
28323
|
-
}))
|
|
28324
|
-
]
|
|
28325
|
-
}
|
|
28326
|
-
],
|
|
28327
|
-
temperature: 0,
|
|
28328
|
-
max_tokens: args.input.maxTokens ?? DEFAULT_LLM_MAX_TOKENS,
|
|
28329
|
-
...args.url.includes("11434") || args.url.includes("localhost") || args.url.includes("127.0.0.1") ? { options: { num_ctx: 32768 } } : {}
|
|
28330
|
-
}),
|
|
28331
|
-
locale: args.locale,
|
|
28332
|
-
hardTimeoutMs: args.input.hardTimeoutMs ?? 18e4
|
|
28349
|
+
var ZAM_BLOCK_START = "<!-- ZAM:START -->";
|
|
28350
|
+
var ZAM_BLOCK_END = "<!-- ZAM:END -->";
|
|
28351
|
+
function upsertMarkedBlock(dest, blockBody, dryRun) {
|
|
28352
|
+
const block = `${ZAM_BLOCK_START}
|
|
28353
|
+
${blockBody.trim()}
|
|
28354
|
+
${ZAM_BLOCK_END}`;
|
|
28355
|
+
if (!existsSync19(dest)) {
|
|
28356
|
+
if (!dryRun) {
|
|
28357
|
+
mkdirSync14(dirname9(dest), { recursive: true });
|
|
28358
|
+
writeFileSync12(dest, `${block}
|
|
28359
|
+
`, "utf8");
|
|
28333
28360
|
}
|
|
28334
|
-
|
|
28335
|
-
|
|
28336
|
-
|
|
28337
|
-
|
|
28338
|
-
|
|
28339
|
-
|
|
28340
|
-
|
|
28361
|
+
return "write";
|
|
28362
|
+
}
|
|
28363
|
+
const existing = readFileSync15(dest, "utf8");
|
|
28364
|
+
if (existing.includes(block)) return "skip";
|
|
28365
|
+
const start = existing.indexOf(ZAM_BLOCK_START);
|
|
28366
|
+
const end = existing.indexOf(ZAM_BLOCK_END);
|
|
28367
|
+
const next = start >= 0 && end > start ? `${existing.slice(0, start)}${block}${existing.slice(end + ZAM_BLOCK_END.length)}` : `${existing.trimEnd()}
|
|
28368
|
+
|
|
28369
|
+
${block}
|
|
28370
|
+
`;
|
|
28371
|
+
if (!dryRun) writeFileSync12(dest, next, "utf8");
|
|
28372
|
+
return start >= 0 && end > start ? "update" : "write";
|
|
28373
|
+
}
|
|
28374
|
+
function logInstructionAction(action, label, dryRun) {
|
|
28375
|
+
if (action === "skip") {
|
|
28376
|
+
console.log(` skip ${label} (ZAM block already present)`);
|
|
28377
|
+
} else {
|
|
28378
|
+
console.log(` ${dryRun ? "would " : ""}${action} ${label}`);
|
|
28379
|
+
}
|
|
28380
|
+
}
|
|
28381
|
+
function writeClaudeMd(skipClaudeMd, cwd = process.cwd(), opts = {}) {
|
|
28382
|
+
if (skipClaudeMd) return;
|
|
28383
|
+
const dest = join20(cwd, "CLAUDE.md");
|
|
28384
|
+
if (existsSync19(dest)) {
|
|
28385
|
+
if (!opts.updateExisting) {
|
|
28386
|
+
console.log(` skip CLAUDE.md (already present)`);
|
|
28387
|
+
return;
|
|
28341
28388
|
}
|
|
28342
|
-
|
|
28343
|
-
|
|
28389
|
+
const action = upsertMarkedBlock(
|
|
28390
|
+
dest,
|
|
28391
|
+
`## ZAM learning sessions
|
|
28392
|
+
|
|
28393
|
+
ZAM is available in this repository. Use the \`zam\` skill in Claude Code to turn real work into an observed learning session with active recall and FSRS scheduling.
|
|
28394
|
+
|
|
28395
|
+
- Skill files live under \`.claude/skills/zam/\`.
|
|
28396
|
+
- Fast-changing review data lives in \`~/.zam/\`, not in this repository.
|
|
28397
|
+
- The skill directory is linked to this ZAM installation and updates with it.`,
|
|
28398
|
+
Boolean(opts.dryRun)
|
|
28344
28399
|
);
|
|
28400
|
+
logInstructionAction(action, "CLAUDE.md", Boolean(opts.dryRun));
|
|
28401
|
+
return;
|
|
28345
28402
|
}
|
|
28346
|
-
const
|
|
28347
|
-
|
|
28348
|
-
|
|
28349
|
-
|
|
28350
|
-
|
|
28351
|
-
|
|
28352
|
-
|
|
28403
|
+
const name = basename4(cwd);
|
|
28404
|
+
const content = `# ZAM Personal Kernel \u2014 ${name}
|
|
28405
|
+
|
|
28406
|
+
This is a ZAM personal instance. ZAM builds lasting skills through spaced
|
|
28407
|
+
repetition during real work \u2014 not separate study sessions.
|
|
28408
|
+
|
|
28409
|
+
## First time here?
|
|
28410
|
+
Run \`/setup\` in Claude Code or Gemini CLI to complete first-time setup.
|
|
28411
|
+
|
|
28412
|
+
## Regular use
|
|
28413
|
+
Run \`/zam\` to start a learning session on whatever you are working on.
|
|
28414
|
+
|
|
28415
|
+
## What lives here
|
|
28416
|
+
- \`beliefs/\` \u2014 your worldview, approved by git commit
|
|
28417
|
+
- \`goals/\` \u2014 your objectives, decomposed into tasks and learning tokens
|
|
28418
|
+
|
|
28419
|
+
## Fast-changing data
|
|
28420
|
+
Learning tokens, cards, and review history live in local SQLite by default.
|
|
28421
|
+
Use \`zam connector setup turso\` to store cloud credentials in
|
|
28422
|
+
\`~/.zam/credentials.json\` and use a Turso database across machines.
|
|
28423
|
+
`;
|
|
28424
|
+
if (opts.dryRun) {
|
|
28425
|
+
console.log(` would write CLAUDE.md`);
|
|
28426
|
+
} else {
|
|
28427
|
+
writeFileSync12(dest, content, "utf8");
|
|
28428
|
+
console.log(` write CLAUDE.md`);
|
|
28429
|
+
}
|
|
28430
|
+
}
|
|
28431
|
+
function writeAgentsMd(skipAgentsMd, cwd = process.cwd(), opts = {}) {
|
|
28432
|
+
if (skipAgentsMd) return;
|
|
28433
|
+
const dest = join20(cwd, "AGENTS.md");
|
|
28434
|
+
if (existsSync19(dest)) {
|
|
28435
|
+
if (!opts.updateExisting) {
|
|
28436
|
+
console.log(` skip AGENTS.md (already present)`);
|
|
28437
|
+
return;
|
|
28353
28438
|
}
|
|
28354
|
-
|
|
28439
|
+
const action = upsertMarkedBlock(
|
|
28440
|
+
dest,
|
|
28441
|
+
`## ZAM learning sessions
|
|
28442
|
+
|
|
28443
|
+
ZAM is available in this repository. Select the \`zam\` skill through \`/skills\` or invoke \`$zam\` where supported to turn real work into an observed learning session with active recall and FSRS scheduling.
|
|
28444
|
+
|
|
28445
|
+
- Skill files live under \`.agents/skills/zam/\`.
|
|
28446
|
+
- Fast-changing review data lives in \`~/.zam/\`, not in this repository.
|
|
28447
|
+
- The skill directory is linked to this ZAM installation and updates with it.`,
|
|
28448
|
+
Boolean(opts.dryRun)
|
|
28449
|
+
);
|
|
28450
|
+
logInstructionAction(action, "AGENTS.md", Boolean(opts.dryRun));
|
|
28451
|
+
return;
|
|
28355
28452
|
}
|
|
28356
|
-
const
|
|
28357
|
-
|
|
28358
|
-
|
|
28453
|
+
const name = basename4(cwd);
|
|
28454
|
+
const content = `# ZAM Personal Kernel - ${name}
|
|
28455
|
+
|
|
28456
|
+
This is a ZAM personal instance. ZAM builds lasting skills through spaced
|
|
28457
|
+
repetition during real work, not separate study sessions.
|
|
28458
|
+
|
|
28459
|
+
## First time here?
|
|
28460
|
+
Run \`zam setup\` from the shell. When this repository includes
|
|
28461
|
+
\`.agents/skills/setup/\`, you can instead select \`setup\` through \`/skills\`
|
|
28462
|
+
or invoke \`$setup\`.
|
|
28463
|
+
|
|
28464
|
+
## Regular use
|
|
28465
|
+
Select the \`zam\` skill through \`/skills\` or invoke \`$zam\` to start a
|
|
28466
|
+
learning session on whatever you are working on.
|
|
28467
|
+
|
|
28468
|
+
## What lives here
|
|
28469
|
+
- \`beliefs/\` - your worldview, approved by git commit
|
|
28470
|
+
- \`goals/\` - your objectives, decomposed into tasks and learning tokens
|
|
28471
|
+
|
|
28472
|
+
## Fast-changing data
|
|
28473
|
+
Learning tokens, cards, and review history live in local SQLite by default.
|
|
28474
|
+
Use \`zam connector setup turso\` to store cloud credentials in
|
|
28475
|
+
\`~/.zam/credentials.json\` and use a Turso database across machines.
|
|
28476
|
+
|
|
28477
|
+
## Codex skills
|
|
28478
|
+
Codex discovers repository skills under \`.agents/skills/\`. Run
|
|
28479
|
+
\`zam setup --force\` after upgrading \`zam-core\` to refresh them.
|
|
28480
|
+
`;
|
|
28481
|
+
if (opts.dryRun) {
|
|
28482
|
+
console.log(` would write AGENTS.md`);
|
|
28483
|
+
} else {
|
|
28484
|
+
writeFileSync12(dest, content, "utf8");
|
|
28485
|
+
console.log(` write AGENTS.md`);
|
|
28359
28486
|
}
|
|
28360
|
-
return content.trim();
|
|
28361
28487
|
}
|
|
28362
|
-
function
|
|
28363
|
-
const
|
|
28364
|
-
|
|
28365
|
-
|
|
28488
|
+
function writeCopilotInstructions(cwd = process.cwd(), opts = {}) {
|
|
28489
|
+
const dest = join20(cwd, ".github", "copilot-instructions.md");
|
|
28490
|
+
const action = upsertMarkedBlock(
|
|
28491
|
+
dest,
|
|
28492
|
+
`## ZAM learning sessions
|
|
28493
|
+
|
|
28494
|
+
ZAM is available in this repository through \`.claude/skills/zam/\`. Use the \`zam\` project skill from Copilot-compatible skill selection surfaces to turn real work into an observed learning session with active recall and FSRS scheduling.
|
|
28495
|
+
|
|
28496
|
+
- Fast-changing review data lives in \`~/.zam/\`, not in this repository.
|
|
28497
|
+
- The skill directory is linked to this ZAM installation and updates with it.`,
|
|
28498
|
+
Boolean(opts.dryRun)
|
|
28499
|
+
);
|
|
28500
|
+
logInstructionAction(
|
|
28501
|
+
action,
|
|
28502
|
+
".github/copilot-instructions.md",
|
|
28503
|
+
Boolean(opts.dryRun)
|
|
28504
|
+
);
|
|
28505
|
+
}
|
|
28506
|
+
|
|
28507
|
+
// src/cli/install-repair.ts
|
|
28508
|
+
function defaultRepairWorkspaces() {
|
|
28509
|
+
const summary = {
|
|
28510
|
+
provisioned: 0,
|
|
28511
|
+
missing: 0,
|
|
28512
|
+
relinked: 0
|
|
28513
|
+
};
|
|
28514
|
+
try {
|
|
28515
|
+
const agents = parseSetupAgents();
|
|
28516
|
+
for (const workspace of getConfiguredWorkspaces()) {
|
|
28517
|
+
if (!existsSync20(workspace.path)) {
|
|
28518
|
+
summary.missing += 1;
|
|
28519
|
+
continue;
|
|
28520
|
+
}
|
|
28521
|
+
const results = wireSkills(workspace.path, agents, { quiet: true });
|
|
28522
|
+
summary.provisioned += 1;
|
|
28523
|
+
summary.relinked += results.filter(
|
|
28524
|
+
(result) => result.action === "linked" || result.action === "relinked"
|
|
28525
|
+
).length;
|
|
28526
|
+
}
|
|
28527
|
+
} catch (error) {
|
|
28528
|
+
summary.error = error instanceof Error ? error.message : String(error);
|
|
28366
28529
|
}
|
|
28530
|
+
return summary;
|
|
28531
|
+
}
|
|
28532
|
+
function defaultConnectAgents(deps) {
|
|
28533
|
+
const report = performAgentConnect({}, deps);
|
|
28534
|
+
const companion = report.results.find((result) => result.extension?.kind === "vscode")?.extension?.action ?? null;
|
|
28367
28535
|
return {
|
|
28368
|
-
|
|
28369
|
-
|
|
28536
|
+
success: report.success,
|
|
28537
|
+
detected: report.detected,
|
|
28538
|
+
connected: report.results.filter(
|
|
28539
|
+
(result) => !result.error && (result.alreadyConfigured || result.wrote)
|
|
28540
|
+
).length,
|
|
28541
|
+
companion,
|
|
28542
|
+
errors: report.results.flatMap(
|
|
28543
|
+
(result) => result.error ? [`${result.label}: ${result.error}`] : []
|
|
28544
|
+
)
|
|
28370
28545
|
};
|
|
28371
28546
|
}
|
|
28372
|
-
|
|
28373
|
-
const
|
|
28374
|
-
const
|
|
28375
|
-
|
|
28376
|
-
|
|
28377
|
-
|
|
28378
|
-
|
|
28379
|
-
|
|
28380
|
-
|
|
28381
|
-
|
|
28382
|
-
|
|
28383
|
-
model: args.model,
|
|
28384
|
-
max_tokens: args.input.maxTokens ?? DEFAULT_LLM_MAX_TOKENS,
|
|
28385
|
-
system: VISION_SYSTEM_PROMPT,
|
|
28386
|
-
messages: [
|
|
28387
|
-
{
|
|
28388
|
-
role: "user",
|
|
28389
|
-
content: [
|
|
28390
|
-
{ type: "text", text: visionUserText(args, language) },
|
|
28391
|
-
...args.imageUrls.map(dataUrlToAnthropicImage)
|
|
28392
|
-
]
|
|
28393
|
-
}
|
|
28394
|
-
]
|
|
28395
|
-
}),
|
|
28396
|
-
locale: args.locale,
|
|
28397
|
-
hardTimeoutMs: args.input.hardTimeoutMs ?? 18e4
|
|
28398
|
-
});
|
|
28399
|
-
if (!res.ok) {
|
|
28400
|
-
const errorText = await res.text().catch(() => "");
|
|
28401
|
-
throw new Error(
|
|
28402
|
-
`Vision LLM request failed: ${res.statusText} (${res.status}) - ${errorText}`
|
|
28403
|
-
);
|
|
28404
|
-
}
|
|
28405
|
-
const data = await res.json();
|
|
28406
|
-
if (data.error !== void 0) {
|
|
28407
|
-
throw new Error(`Vision model failed: ${formatModelError(data.error)}`);
|
|
28547
|
+
function performInstallRepair(opts = {}, deps = {}) {
|
|
28548
|
+
const version = (deps.version ?? currentVersion)();
|
|
28549
|
+
const getLastRepaired = deps.getLastRepaired ?? getLastRepairedVersion;
|
|
28550
|
+
if (opts.ifVersionChanged && getLastRepaired() === version) {
|
|
28551
|
+
return {
|
|
28552
|
+
version,
|
|
28553
|
+
skipped: true,
|
|
28554
|
+
cli: null,
|
|
28555
|
+
workspaces: null,
|
|
28556
|
+
agents: null
|
|
28557
|
+
};
|
|
28408
28558
|
}
|
|
28409
|
-
|
|
28410
|
-
|
|
28559
|
+
const cli = (deps.installCli ?? installCliShim)();
|
|
28560
|
+
const workspaces = (deps.repairWorkspaces ?? defaultRepairWorkspaces)();
|
|
28561
|
+
const agentDeps = {};
|
|
28562
|
+
if (cli.status === "installed" || cli.status === "refreshed" || cli.status === "ok") {
|
|
28563
|
+
agentDeps.findZam = () => cli.shimPath;
|
|
28411
28564
|
}
|
|
28412
|
-
|
|
28413
|
-
|
|
28414
|
-
|
|
28415
|
-
|
|
28416
|
-
|
|
28565
|
+
let agents;
|
|
28566
|
+
try {
|
|
28567
|
+
agents = (deps.connectAgents ?? defaultConnectAgents)(agentDeps);
|
|
28568
|
+
} catch (error) {
|
|
28569
|
+
agents = {
|
|
28570
|
+
success: false,
|
|
28571
|
+
detected: [],
|
|
28572
|
+
connected: 0,
|
|
28573
|
+
companion: null,
|
|
28574
|
+
errors: [error instanceof Error ? error.message : String(error)]
|
|
28575
|
+
};
|
|
28417
28576
|
}
|
|
28418
|
-
|
|
28577
|
+
(deps.setLastRepaired ?? setLastRepairedVersion)(version);
|
|
28578
|
+
return { version, skipped: false, cli, workspaces, agents };
|
|
28419
28579
|
}
|
|
28420
|
-
|
|
28421
|
-
|
|
28422
|
-
|
|
28423
|
-
|
|
28424
|
-
|
|
28425
|
-
|
|
28426
|
-
|
|
28427
|
-
|
|
28428
|
-
|
|
28429
|
-
|
|
28430
|
-
|
|
28431
|
-
|
|
28580
|
+
|
|
28581
|
+
// src/cli/llm/capability-probe.ts
|
|
28582
|
+
init_kernel();
|
|
28583
|
+
var EMBEDDING_MODEL_HINTS = [
|
|
28584
|
+
"embed",
|
|
28585
|
+
"text-embedding",
|
|
28586
|
+
"bge-",
|
|
28587
|
+
"gte-",
|
|
28588
|
+
"nomic",
|
|
28589
|
+
"mxbai"
|
|
28590
|
+
];
|
|
28591
|
+
var VISION_MODEL_HINTS = [
|
|
28592
|
+
"vision",
|
|
28593
|
+
"-vl",
|
|
28594
|
+
"vl-",
|
|
28595
|
+
"vlm",
|
|
28596
|
+
"llava",
|
|
28597
|
+
"gpt-4o",
|
|
28598
|
+
"gpt-4.1",
|
|
28599
|
+
"gpt-5",
|
|
28600
|
+
"gemini",
|
|
28601
|
+
"pixtral",
|
|
28602
|
+
"minicpm-v",
|
|
28603
|
+
"internvl",
|
|
28604
|
+
"moondream",
|
|
28605
|
+
"llama-3.2",
|
|
28606
|
+
"llama3.2",
|
|
28607
|
+
// Xiaomi MiMo(-VL) is multimodal; the plain "mimo-v*" tag carries no "-vl".
|
|
28608
|
+
"mimo"
|
|
28609
|
+
];
|
|
28610
|
+
function matchesAny(id, hints) {
|
|
28611
|
+
const lower = id.toLowerCase();
|
|
28612
|
+
return hints.some((hint) => lower.includes(hint));
|
|
28613
|
+
}
|
|
28614
|
+
function catalogHasModel(catalog, model) {
|
|
28615
|
+
const lower = model.toLowerCase();
|
|
28616
|
+
return catalog.some((id) => id.toLowerCase() === lower);
|
|
28617
|
+
}
|
|
28618
|
+
function classifyCapabilities(entry, catalog, catalogKnown, dimProbeEmbedding = false) {
|
|
28619
|
+
const detected = emptyCapabilityFlags();
|
|
28620
|
+
if (entry.apiFlavor === "anthropic-messages") {
|
|
28621
|
+
detected.text = true;
|
|
28622
|
+
detected.image = true;
|
|
28623
|
+
return detected;
|
|
28432
28624
|
}
|
|
28625
|
+
const looksEmbedding = matchesAny(entry.model, EMBEDDING_MODEL_HINTS);
|
|
28626
|
+
const looksVision = matchesAny(entry.model, VISION_MODEL_HINTS);
|
|
28627
|
+
const inCatalog = catalogHasModel(catalog, entry.model);
|
|
28628
|
+
detected.embedding = looksEmbedding || dimProbeEmbedding;
|
|
28629
|
+
detected.image = looksVision;
|
|
28630
|
+
detected.text = !detected.embedding && (inCatalog || !catalogKnown);
|
|
28631
|
+
return detected;
|
|
28433
28632
|
}
|
|
28434
|
-
function
|
|
28435
|
-
return
|
|
28436
|
-
|
|
28437
|
-
sessionId: input8.sessionId,
|
|
28438
|
-
sequence: input8.sequence,
|
|
28439
|
-
observedFrom: input8.observedFrom,
|
|
28440
|
-
observedTo: input8.observedTo,
|
|
28441
|
-
kind: parseKind(draft.kind),
|
|
28442
|
-
application: input8.application,
|
|
28443
|
-
summary: parseSummary(draft.summary),
|
|
28444
|
-
actions: parseActions(draft.actions),
|
|
28445
|
-
evidence: [
|
|
28446
|
-
{
|
|
28447
|
-
type: "keyframe",
|
|
28448
|
-
ref: input8.evidenceRef ?? basename4(input8.imagePath),
|
|
28449
|
-
redacted: input8.redacted ?? false
|
|
28450
|
-
}
|
|
28451
|
-
],
|
|
28452
|
-
candidateTokens: parseCandidateTokens(draft.candidateTokens),
|
|
28453
|
-
confidence: parseConfidence(draft.confidence, 0.35)
|
|
28454
|
-
};
|
|
28633
|
+
function resolveApiKey(apiKeyRef) {
|
|
28634
|
+
if (!apiKeyRef) return DEFAULT_LLM_API_KEY;
|
|
28635
|
+
return getProviderApiKey(apiKeyRef) ?? DEFAULT_LLM_API_KEY;
|
|
28455
28636
|
}
|
|
28456
|
-
function
|
|
28637
|
+
async function probeModelCapabilities(entry, opts = {}) {
|
|
28638
|
+
const apiKey = resolveApiKey(entry.apiKeyRef);
|
|
28639
|
+
if (entry.apiFlavor === "anthropic-messages") {
|
|
28640
|
+
const online2 = await isLlmOnline(entry.url);
|
|
28641
|
+
return {
|
|
28642
|
+
reachable: online2,
|
|
28643
|
+
catalog: [],
|
|
28644
|
+
detected: classifyCapabilities(entry, [], false)
|
|
28645
|
+
};
|
|
28646
|
+
}
|
|
28647
|
+
const online = await isLlmOnline(entry.url);
|
|
28648
|
+
if (!online) {
|
|
28649
|
+
return { reachable: false, catalog: [], detected: emptyCapabilityFlags() };
|
|
28650
|
+
}
|
|
28651
|
+
const catalog = await getAvailableModels(entry.url, apiKey);
|
|
28652
|
+
const catalogKnown = catalog.length > 0;
|
|
28653
|
+
let dimProbeEmbedding = false;
|
|
28654
|
+
const looksEmbedding = matchesAny(entry.model, EMBEDDING_MODEL_HINTS);
|
|
28655
|
+
if (opts.embeddingDimProbe && !catalogKnown && !looksEmbedding) {
|
|
28656
|
+
try {
|
|
28657
|
+
const [vector] = await embedTexts(
|
|
28658
|
+
{ url: entry.url, model: entry.model, apiKey },
|
|
28659
|
+
["capability probe"]
|
|
28660
|
+
);
|
|
28661
|
+
dimProbeEmbedding = Array.isArray(vector) && vector.length > 0;
|
|
28662
|
+
} catch {
|
|
28663
|
+
dimProbeEmbedding = false;
|
|
28664
|
+
}
|
|
28665
|
+
}
|
|
28457
28666
|
return {
|
|
28458
|
-
|
|
28459
|
-
|
|
28460
|
-
|
|
28461
|
-
|
|
28462
|
-
|
|
28463
|
-
|
|
28464
|
-
|
|
28465
|
-
|
|
28466
|
-
actions: [],
|
|
28467
|
-
evidence: [
|
|
28468
|
-
{
|
|
28469
|
-
type: "keyframe",
|
|
28470
|
-
ref: input8.evidenceRef ?? basename4(input8.imagePath),
|
|
28471
|
-
redacted: input8.redacted ?? false
|
|
28472
|
-
}
|
|
28473
|
-
],
|
|
28474
|
-
candidateTokens: [],
|
|
28475
|
-
confidence: 0.1
|
|
28667
|
+
reachable: true,
|
|
28668
|
+
catalog,
|
|
28669
|
+
detected: classifyCapabilities(
|
|
28670
|
+
entry,
|
|
28671
|
+
catalog,
|
|
28672
|
+
catalogKnown,
|
|
28673
|
+
dimProbeEmbedding
|
|
28674
|
+
)
|
|
28476
28675
|
};
|
|
28477
28676
|
}
|
|
28478
|
-
function
|
|
28479
|
-
|
|
28480
|
-
|
|
28677
|
+
function reconcileCapabilities(userSelected, detected) {
|
|
28678
|
+
const result = emptyCapabilityFlags();
|
|
28679
|
+
for (const key of Object.keys(result)) {
|
|
28680
|
+
result[key] = userSelected[key] && detected[key];
|
|
28481
28681
|
}
|
|
28482
|
-
return
|
|
28682
|
+
return result;
|
|
28483
28683
|
}
|
|
28484
|
-
function
|
|
28485
|
-
if (
|
|
28486
|
-
return
|
|
28684
|
+
function validateModelSave(entry, probe, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
|
|
28685
|
+
if (!probe.reachable) {
|
|
28686
|
+
return {
|
|
28687
|
+
ok: false,
|
|
28688
|
+
error: "Endpoint is unreachable \u2014 cannot verify capabilities. Bring it online and retry."
|
|
28689
|
+
};
|
|
28487
28690
|
}
|
|
28488
|
-
return
|
|
28489
|
-
|
|
28490
|
-
|
|
28491
|
-
|
|
28492
|
-
|
|
28493
|
-
|
|
28494
|
-
|
|
28495
|
-
const action = { type: item.type };
|
|
28496
|
-
if (typeof item.target === "string" && item.target.trim()) {
|
|
28497
|
-
action.target = item.target.trim();
|
|
28498
|
-
}
|
|
28499
|
-
if (typeof item.result === "string" && item.result.trim()) {
|
|
28500
|
-
action.result = item.result.trim();
|
|
28501
|
-
}
|
|
28502
|
-
return [action];
|
|
28503
|
-
});
|
|
28504
|
-
}
|
|
28505
|
-
function parseCandidateTokens(value) {
|
|
28506
|
-
if (!Array.isArray(value)) return [];
|
|
28507
|
-
return value.flatMap((item) => {
|
|
28508
|
-
if (!isRecord2(item)) return [];
|
|
28509
|
-
if (typeof item.slug !== "string" || !/^[A-Za-z0-9._-]+$/.test(item.slug)) {
|
|
28510
|
-
return [];
|
|
28511
|
-
}
|
|
28512
|
-
if (typeof item.rationale !== "string" || !item.rationale.trim()) {
|
|
28513
|
-
return [];
|
|
28691
|
+
return {
|
|
28692
|
+
ok: true,
|
|
28693
|
+
entry: {
|
|
28694
|
+
...entry,
|
|
28695
|
+
capabilities: reconcileCapabilities(entry.capabilities, probe.detected),
|
|
28696
|
+
detectedCapabilities: probe.detected,
|
|
28697
|
+
probedAt: now()
|
|
28514
28698
|
}
|
|
28515
|
-
|
|
28516
|
-
{
|
|
28517
|
-
slug: item.slug,
|
|
28518
|
-
confidence: parseConfidence(item.confidence, 0.2),
|
|
28519
|
-
rationale: item.rationale.trim()
|
|
28520
|
-
}
|
|
28521
|
-
];
|
|
28522
|
-
});
|
|
28523
|
-
}
|
|
28524
|
-
function parseConfidence(value, fallback) {
|
|
28525
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
28526
|
-
return Math.max(0, Math.min(1, value));
|
|
28527
|
-
}
|
|
28528
|
-
function formatModelError(error) {
|
|
28529
|
-
if (typeof error === "string") return error;
|
|
28530
|
-
if (isRecord2(error) && typeof error.message === "string") {
|
|
28531
|
-
return error.message;
|
|
28532
|
-
}
|
|
28533
|
-
return JSON.stringify(error);
|
|
28534
|
-
}
|
|
28535
|
-
function isRecord2(value) {
|
|
28536
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
28699
|
+
};
|
|
28537
28700
|
}
|
|
28538
28701
|
|
|
28539
|
-
// src/cli/
|
|
28540
|
-
init_kernel();
|
|
28541
|
-
|
|
28542
|
-
// src/cli/commands/shared/db.ts
|
|
28702
|
+
// src/cli/llm/vision.ts
|
|
28543
28703
|
init_kernel();
|
|
28544
|
-
|
|
28545
|
-
|
|
28546
|
-
|
|
28547
|
-
}
|
|
28548
|
-
|
|
28549
|
-
|
|
28550
|
-
|
|
28551
|
-
|
|
28552
|
-
|
|
28553
|
-
|
|
28554
|
-
|
|
28555
|
-
|
|
28556
|
-
|
|
28704
|
+
import { randomBytes } from "crypto";
|
|
28705
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
28706
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
28707
|
+
import { basename as basename5, join as join21 } from "path";
|
|
28708
|
+
var LANGUAGE_NAMES2 = {
|
|
28709
|
+
en: "English",
|
|
28710
|
+
de: "German",
|
|
28711
|
+
es: "Spanish",
|
|
28712
|
+
fr: "French",
|
|
28713
|
+
pt: "Portuguese",
|
|
28714
|
+
zh: "Chinese",
|
|
28715
|
+
ja: "Japanese"
|
|
28716
|
+
};
|
|
28717
|
+
var OBSERVATION_KINDS2 = /* @__PURE__ */ new Set([
|
|
28718
|
+
"progress",
|
|
28719
|
+
"step-completed",
|
|
28720
|
+
"error",
|
|
28721
|
+
"help-seeking",
|
|
28722
|
+
"uncertain",
|
|
28723
|
+
"privacy-pause",
|
|
28724
|
+
"heartbeat"
|
|
28725
|
+
]);
|
|
28726
|
+
var ACTION_TYPES2 = /* @__PURE__ */ new Set([
|
|
28727
|
+
"click",
|
|
28728
|
+
"shortcut",
|
|
28729
|
+
"typing",
|
|
28730
|
+
"scroll",
|
|
28731
|
+
"window-change"
|
|
28732
|
+
]);
|
|
28733
|
+
async function observeUiSnapshotViaLLM(db, input8) {
|
|
28734
|
+
const cfg = await getProviderForRole(db, "vision");
|
|
28735
|
+
if (!cfg.enabled) {
|
|
28736
|
+
throw new Error(
|
|
28737
|
+
"Vision observation is disabled in settings (llm.vision.enabled)"
|
|
28738
|
+
);
|
|
28557
28739
|
}
|
|
28558
|
-
|
|
28559
|
-
|
|
28560
|
-
|
|
28561
|
-
}
|
|
28562
|
-
|
|
28563
|
-
|
|
28564
|
-
|
|
28565
|
-
|
|
28566
|
-
|
|
28567
|
-
|
|
28568
|
-
|
|
28569
|
-
|
|
28570
|
-
|
|
28571
|
-
|
|
28572
|
-
|
|
28573
|
-
|
|
28574
|
-
|
|
28575
|
-
|
|
28576
|
-
|
|
28577
|
-
|
|
28578
|
-
|
|
28579
|
-
|
|
28580
|
-
|
|
28581
|
-
|
|
28582
|
-
|
|
28583
|
-
|
|
28584
|
-
|
|
28585
|
-
|
|
28586
|
-
|
|
28587
|
-
|
|
28588
|
-
|
|
28589
|
-
|
|
28590
|
-
|
|
28591
|
-
|
|
28592
|
-
|
|
28593
|
-
|
|
28594
|
-
|
|
28595
|
-
|
|
28596
|
-
|
|
28597
|
-
|
|
28598
|
-
|
|
28599
|
-
|
|
28600
|
-
|
|
28601
|
-
|
|
28602
|
-
}
|
|
28603
|
-
|
|
28604
|
-
|
|
28605
|
-
}
|
|
28606
|
-
|
|
28607
|
-
|
|
28608
|
-
const
|
|
28609
|
-
|
|
28610
|
-
|
|
28611
|
-
|
|
28612
|
-
|
|
28613
|
-
|
|
28614
|
-
|
|
28615
|
-
|
|
28616
|
-
|
|
28617
|
-
|
|
28618
|
-
|
|
28619
|
-
|
|
28620
|
-
|
|
28621
|
-
|
|
28622
|
-
|
|
28623
|
-
|
|
28624
|
-
|
|
28625
|
-
|
|
28626
|
-
|
|
28627
|
-
|
|
28628
|
-
|
|
28629
|
-
|
|
28630
|
-
|
|
28631
|
-
|
|
28632
|
-
|
|
28633
|
-
const
|
|
28634
|
-
|
|
28635
|
-
|
|
28636
|
-
|
|
28637
|
-
|
|
28638
|
-
|
|
28740
|
+
const imageUrls = [];
|
|
28741
|
+
const isVideo = /\.(mp4|mov|m4v|avi|mkv|webm)$/i.test(input8.imagePath);
|
|
28742
|
+
if (isVideo) {
|
|
28743
|
+
const { mkdirSync: mkdirSync19, readdirSync: readdirSync3, rmSync: rmSync4 } = await import("fs");
|
|
28744
|
+
const { execSync: execSync6 } = await import("child_process");
|
|
28745
|
+
const tempDir = join21(
|
|
28746
|
+
tmpdir2(),
|
|
28747
|
+
`zam-frames-${randomBytes(4).toString("hex")}`
|
|
28748
|
+
);
|
|
28749
|
+
mkdirSync19(tempDir, { recursive: true });
|
|
28750
|
+
try {
|
|
28751
|
+
execSync6(
|
|
28752
|
+
`ffmpeg -i "${input8.imagePath}" -vf "fps=1/3,scale=1280:-1" -vsync vfr "${tempDir}/frame_%03d.png"`,
|
|
28753
|
+
{ stdio: "ignore" }
|
|
28754
|
+
);
|
|
28755
|
+
let files = readdirSync3(tempDir).filter((f) => f.endsWith(".png")).sort();
|
|
28756
|
+
if (files.length === 0) {
|
|
28757
|
+
execSync6(
|
|
28758
|
+
`ffmpeg -i "${input8.imagePath}" -vframes 1 "${tempDir}/frame_001.png"`,
|
|
28759
|
+
{ stdio: "ignore" }
|
|
28760
|
+
);
|
|
28761
|
+
files = readdirSync3(tempDir).filter((f) => f.endsWith(".png")).sort();
|
|
28762
|
+
}
|
|
28763
|
+
const maxFrames = cfg.maxFrames ?? 100;
|
|
28764
|
+
let sampledFiles = files;
|
|
28765
|
+
if (files.length > maxFrames) {
|
|
28766
|
+
if (maxFrames <= 1) {
|
|
28767
|
+
sampledFiles = [files[0]];
|
|
28768
|
+
} else {
|
|
28769
|
+
const step = (files.length - 1) / (maxFrames - 1);
|
|
28770
|
+
sampledFiles = [];
|
|
28771
|
+
for (let i = 0; i < maxFrames; i++) {
|
|
28772
|
+
const index = Math.round(i * step);
|
|
28773
|
+
sampledFiles.push(files[index]);
|
|
28774
|
+
}
|
|
28775
|
+
}
|
|
28776
|
+
}
|
|
28777
|
+
for (const file of sampledFiles) {
|
|
28778
|
+
const bytes = readFileSync16(join21(tempDir, file));
|
|
28779
|
+
imageUrls.push(`data:image/png;base64,${bytes.toString("base64")}`);
|
|
28780
|
+
}
|
|
28781
|
+
} finally {
|
|
28782
|
+
try {
|
|
28783
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
28784
|
+
} catch {
|
|
28785
|
+
}
|
|
28786
|
+
}
|
|
28787
|
+
} else {
|
|
28788
|
+
const imageBytes = readFileSync16(input8.imagePath);
|
|
28789
|
+
const ext = input8.imagePath.split(".").pop()?.toLowerCase();
|
|
28790
|
+
const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
|
|
28791
|
+
imageUrls.push(`data:${mime};base64,${imageBytes.toString("base64")}`);
|
|
28792
|
+
}
|
|
28793
|
+
if (imageUrls.length === 0) {
|
|
28794
|
+
throw new Error("No image data available for vision analysis");
|
|
28795
|
+
}
|
|
28796
|
+
const endpoints = [
|
|
28797
|
+
{
|
|
28798
|
+
url: cfg.url,
|
|
28799
|
+
apiKey: cfg.apiKey || DEFAULT_LLM_API_KEY,
|
|
28800
|
+
model: input8.model ?? cfg.model,
|
|
28801
|
+
apiFlavor: cfg.apiFlavor
|
|
28802
|
+
}
|
|
28803
|
+
];
|
|
28804
|
+
if (cfg.fallback) {
|
|
28805
|
+
endpoints.push({
|
|
28806
|
+
url: cfg.fallback.url,
|
|
28807
|
+
apiKey: cfg.fallback.apiKey || DEFAULT_LLM_API_KEY,
|
|
28808
|
+
model: cfg.fallback.model,
|
|
28809
|
+
apiFlavor: cfg.fallback.apiFlavor
|
|
28810
|
+
});
|
|
28811
|
+
}
|
|
28812
|
+
let lastRequestError;
|
|
28813
|
+
let sawUnparseableDraft = false;
|
|
28814
|
+
let sawInvalidDraft = false;
|
|
28815
|
+
for (const endpoint of endpoints) {
|
|
28816
|
+
let content;
|
|
28817
|
+
try {
|
|
28818
|
+
content = await requestVisionDraft({
|
|
28819
|
+
...endpoint,
|
|
28820
|
+
locale: cfg.locale,
|
|
28821
|
+
imageUrls,
|
|
28822
|
+
input: input8
|
|
28823
|
+
});
|
|
28824
|
+
} catch (err) {
|
|
28825
|
+
lastRequestError = err;
|
|
28826
|
+
continue;
|
|
28827
|
+
}
|
|
28828
|
+
let draft;
|
|
28829
|
+
try {
|
|
28830
|
+
draft = extractDraft(content);
|
|
28831
|
+
} catch {
|
|
28832
|
+
sawUnparseableDraft = true;
|
|
28833
|
+
continue;
|
|
28834
|
+
}
|
|
28835
|
+
const report = buildReport(input8, draft);
|
|
28836
|
+
if (isUiObservationReport(report)) {
|
|
28837
|
+
return report;
|
|
28838
|
+
}
|
|
28839
|
+
sawInvalidDraft = true;
|
|
28639
28840
|
}
|
|
28640
|
-
|
|
28641
|
-
|
|
28642
|
-
var readRoles = (db) => readJson(db, "llm.roles", {});
|
|
28643
|
-
async function readScopedProviders(db, machine) {
|
|
28644
|
-
if (machine) return getMachineAiConfig().providers ?? {};
|
|
28645
|
-
if (!db)
|
|
28646
|
-
throw new Error("Database is required for shared provider settings.");
|
|
28647
|
-
return readProviders(db);
|
|
28648
|
-
}
|
|
28649
|
-
async function readScopedRoles(db, machine) {
|
|
28650
|
-
if (machine) return getMachineAiConfig().roles ?? {};
|
|
28651
|
-
if (!db)
|
|
28652
|
-
throw new Error("Database is required for shared provider settings.");
|
|
28653
|
-
return readRoles(db);
|
|
28654
|
-
}
|
|
28655
|
-
async function writeScopedProviders(db, machine, p) {
|
|
28656
|
-
if (machine) {
|
|
28657
|
-
const config = getMachineAiConfig();
|
|
28658
|
-
saveMachineAiConfig({ ...config, providers: p });
|
|
28659
|
-
return;
|
|
28841
|
+
if (lastRequestError && !sawUnparseableDraft && !sawInvalidDraft) {
|
|
28842
|
+
throw lastRequestError;
|
|
28660
28843
|
}
|
|
28661
|
-
if (
|
|
28662
|
-
|
|
28663
|
-
|
|
28664
|
-
|
|
28665
|
-
|
|
28666
|
-
if (machine) {
|
|
28667
|
-
const config = getMachineAiConfig();
|
|
28668
|
-
saveMachineAiConfig({ ...config, roles: r });
|
|
28669
|
-
return;
|
|
28844
|
+
if (sawInvalidDraft) {
|
|
28845
|
+
return uncertainReport(
|
|
28846
|
+
input8,
|
|
28847
|
+
"Vision model returned an invalid UI report draft."
|
|
28848
|
+
);
|
|
28670
28849
|
}
|
|
28671
|
-
|
|
28672
|
-
|
|
28673
|
-
|
|
28850
|
+
return uncertainReport(
|
|
28851
|
+
input8,
|
|
28852
|
+
"Vision model returned output that could not be parsed as JSON."
|
|
28853
|
+
);
|
|
28674
28854
|
}
|
|
28675
|
-
|
|
28676
|
-
|
|
28677
|
-
|
|
28678
|
-
|
|
28679
|
-
}
|
|
28680
|
-
|
|
28855
|
+
var VISION_SYSTEM_PROMPT = "You are ZAM's UI observer. Return only strict JSON. Do not include markdown, prose, or fields outside the requested schema.";
|
|
28856
|
+
function visionSchema(language) {
|
|
28857
|
+
return `{
|
|
28858
|
+
"kind": "progress | step-completed | error | help-seeking | uncertain",
|
|
28859
|
+
"summary": "short factual UI summary in ${language}",
|
|
28860
|
+
"actions": [{"type": "click | shortcut | typing | scroll | window-change", "target": "optional UI target", "result": "optional visible result"}],
|
|
28861
|
+
"candidateTokens": [{"slug": "optional-kebab-case-skill-token", "confidence": 0.0, "rationale": "why this token may matter"}],
|
|
28862
|
+
"confidence": 0.0
|
|
28863
|
+
}`;
|
|
28681
28864
|
}
|
|
28865
|
+
function visionUserText(args, language) {
|
|
28866
|
+
const intro = args.imageUrls.length > 1 ? "Observe this sequence of Windows/macOS application snapshots showing a task performed over time." : "Observe this Windows/macOS application snapshot for a learning session.";
|
|
28867
|
+
return `${intro}
|
|
28868
|
+
Application process: ${args.input.application.processName}
|
|
28869
|
+
Window title: ${args.input.application.windowTitle ?? "(unknown)"}
|
|
28682
28870
|
|
|
28683
|
-
|
|
28684
|
-
|
|
28685
|
-
|
|
28686
|
-
|
|
28687
|
-
|
|
28688
|
-
|
|
28689
|
-
realpathSync,
|
|
28690
|
-
rmSync as rmSync2,
|
|
28691
|
-
symlinkSync,
|
|
28692
|
-
writeFileSync as writeFileSync11
|
|
28693
|
-
} from "fs";
|
|
28694
|
-
import { basename as basename5, dirname as dirname9, join as join20 } from "path";
|
|
28695
|
-
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
28696
|
-
var packageRoot3 = [
|
|
28697
|
-
fileURLToPath5(new URL("../..", import.meta.url)),
|
|
28698
|
-
fileURLToPath5(new URL("../../..", import.meta.url))
|
|
28699
|
-
].find((candidate) => existsSync18(join20(candidate, "package.json"))) ?? fileURLToPath5(new URL("../..", import.meta.url));
|
|
28700
|
-
var ALL_SETUP_AGENTS = ["claude", "copilot", "codex", "agent"];
|
|
28701
|
-
var SKILL_PAIRS = [
|
|
28702
|
-
{
|
|
28703
|
-
from: join20(packageRoot3, ".claude", "skills", "zam", "SKILL.md"),
|
|
28704
|
-
to: join20(".claude", "skills", "zam", "SKILL.md"),
|
|
28705
|
-
agents: ["claude", "copilot"]
|
|
28706
|
-
},
|
|
28707
|
-
{
|
|
28708
|
-
from: join20(packageRoot3, ".agent", "skills", "zam", "SKILL.md"),
|
|
28709
|
-
to: join20(".agent", "skills", "zam", "SKILL.md"),
|
|
28710
|
-
agents: ["agent"]
|
|
28711
|
-
},
|
|
28712
|
-
{
|
|
28713
|
-
from: join20(packageRoot3, ".agents", "skills", "zam", "SKILL.md"),
|
|
28714
|
-
to: join20(".agents", "skills", "zam", "SKILL.md"),
|
|
28715
|
-
agents: ["codex"]
|
|
28871
|
+
Return this JSON draft only:
|
|
28872
|
+
${visionSchema(language)}`;
|
|
28873
|
+
}
|
|
28874
|
+
async function requestVisionDraft(args) {
|
|
28875
|
+
if (args.apiFlavor === "anthropic-messages") {
|
|
28876
|
+
return requestAnthropicVisionDraft(args);
|
|
28716
28877
|
}
|
|
28717
|
-
|
|
28718
|
-
|
|
28719
|
-
|
|
28720
|
-
|
|
28878
|
+
return requestChatCompletionsVisionDraft(args);
|
|
28879
|
+
}
|
|
28880
|
+
async function requestChatCompletionsVisionDraft(args) {
|
|
28881
|
+
const language = LANGUAGE_NAMES2[args.locale] ?? "English";
|
|
28882
|
+
const res = await fetchWithInteractiveTimeout(
|
|
28883
|
+
`${args.url}/chat/completions`,
|
|
28884
|
+
{
|
|
28885
|
+
method: "POST",
|
|
28886
|
+
headers: {
|
|
28887
|
+
"Content-Type": "application/json",
|
|
28888
|
+
Authorization: `Bearer ${args.apiKey}`
|
|
28889
|
+
},
|
|
28890
|
+
body: JSON.stringify({
|
|
28891
|
+
model: args.model,
|
|
28892
|
+
messages: [
|
|
28893
|
+
{ role: "system", content: VISION_SYSTEM_PROMPT },
|
|
28894
|
+
{
|
|
28895
|
+
role: "user",
|
|
28896
|
+
content: [
|
|
28897
|
+
{ type: "text", text: visionUserText(args, language) },
|
|
28898
|
+
...args.imageUrls.map((url) => ({
|
|
28899
|
+
type: "image_url",
|
|
28900
|
+
image_url: { url }
|
|
28901
|
+
}))
|
|
28902
|
+
]
|
|
28903
|
+
}
|
|
28904
|
+
],
|
|
28905
|
+
temperature: 0,
|
|
28906
|
+
max_tokens: args.input.maxTokens ?? DEFAULT_LLM_MAX_TOKENS,
|
|
28907
|
+
...args.url.includes("11434") || args.url.includes("localhost") || args.url.includes("127.0.0.1") ? { options: { num_ctx: 32768 } } : {}
|
|
28908
|
+
}),
|
|
28909
|
+
locale: args.locale,
|
|
28910
|
+
hardTimeoutMs: args.input.hardTimeoutMs ?? 18e4
|
|
28911
|
+
}
|
|
28912
|
+
);
|
|
28913
|
+
if (!res.ok) {
|
|
28914
|
+
const errorText = await res.text().catch(() => "");
|
|
28915
|
+
if (errorText.includes("image") && (errorText.includes("not support") || errorText.includes("unsupported"))) {
|
|
28916
|
+
throw new Error(
|
|
28917
|
+
`Vision model "${args.model}" does not support image input. Set a multimodal model: zam settings set llm.vision.model <model>`
|
|
28918
|
+
);
|
|
28919
|
+
}
|
|
28920
|
+
throw new Error(
|
|
28921
|
+
`Vision LLM request failed: ${res.statusText} (${res.status}) - ${errorText}`
|
|
28922
|
+
);
|
|
28721
28923
|
}
|
|
28722
|
-
const
|
|
28723
|
-
|
|
28724
|
-
|
|
28725
|
-
|
|
28726
|
-
agent: ["agent"],
|
|
28727
|
-
opencode: ["agent"]
|
|
28728
|
-
};
|
|
28729
|
-
const selected = /* @__PURE__ */ new Set();
|
|
28730
|
-
for (const raw of value.split(",")) {
|
|
28731
|
-
const key = raw.trim().toLowerCase();
|
|
28732
|
-
const mapped = aliases[key];
|
|
28733
|
-
if (!mapped) {
|
|
28924
|
+
const data = await res.json();
|
|
28925
|
+
if (data.error !== void 0) {
|
|
28926
|
+
const errorMsg = formatModelError(data.error);
|
|
28927
|
+
if (errorMsg.includes("image") && (errorMsg.includes("not support") || errorMsg.includes("unsupported"))) {
|
|
28734
28928
|
throw new Error(
|
|
28735
|
-
`
|
|
28929
|
+
`Vision model "${args.model}" does not support image input. Set a multimodal model: zam settings set llm.vision.model <model>`
|
|
28736
28930
|
);
|
|
28737
28931
|
}
|
|
28738
|
-
|
|
28932
|
+
throw new Error(`Vision model failed: ${errorMsg}`);
|
|
28739
28933
|
}
|
|
28740
|
-
|
|
28934
|
+
const content = data.choices?.[0]?.message?.content;
|
|
28935
|
+
if (!content || typeof content !== "string") {
|
|
28936
|
+
throw new Error("Empty response from vision model");
|
|
28937
|
+
}
|
|
28938
|
+
return content.trim();
|
|
28741
28939
|
}
|
|
28742
|
-
function
|
|
28743
|
-
|
|
28744
|
-
|
|
28745
|
-
|
|
28746
|
-
}
|
|
28747
|
-
|
|
28940
|
+
function dataUrlToAnthropicImage(dataUrl) {
|
|
28941
|
+
const match = dataUrl.match(/^data:(image\/[a-zA-Z]+);base64,(.+)$/);
|
|
28942
|
+
if (!match) {
|
|
28943
|
+
throw new Error("Unsupported image data for Anthropic vision request");
|
|
28944
|
+
}
|
|
28945
|
+
return {
|
|
28946
|
+
type: "image",
|
|
28947
|
+
source: { type: "base64", media_type: match[1], data: match[2] }
|
|
28948
|
+
};
|
|
28949
|
+
}
|
|
28950
|
+
async function requestAnthropicVisionDraft(args) {
|
|
28951
|
+
const language = LANGUAGE_NAMES2[args.locale] ?? "English";
|
|
28952
|
+
const base = args.url.replace(/\/+$/, "").replace(/\/v1$/, "");
|
|
28953
|
+
const res = await fetchWithInteractiveTimeout(`${base}/v1/messages`, {
|
|
28954
|
+
method: "POST",
|
|
28955
|
+
headers: {
|
|
28956
|
+
"Content-Type": "application/json",
|
|
28957
|
+
"x-api-key": args.apiKey,
|
|
28958
|
+
"anthropic-version": "2023-06-01"
|
|
28959
|
+
},
|
|
28960
|
+
body: JSON.stringify({
|
|
28961
|
+
model: args.model,
|
|
28962
|
+
max_tokens: args.input.maxTokens ?? DEFAULT_LLM_MAX_TOKENS,
|
|
28963
|
+
system: VISION_SYSTEM_PROMPT,
|
|
28964
|
+
messages: [
|
|
28965
|
+
{
|
|
28966
|
+
role: "user",
|
|
28967
|
+
content: [
|
|
28968
|
+
{ type: "text", text: visionUserText(args, language) },
|
|
28969
|
+
...args.imageUrls.map(dataUrlToAnthropicImage)
|
|
28970
|
+
]
|
|
28971
|
+
}
|
|
28972
|
+
]
|
|
28973
|
+
}),
|
|
28974
|
+
locale: args.locale,
|
|
28975
|
+
hardTimeoutMs: args.input.hardTimeoutMs ?? 18e4
|
|
28976
|
+
});
|
|
28977
|
+
if (!res.ok) {
|
|
28978
|
+
const errorText = await res.text().catch(() => "");
|
|
28979
|
+
throw new Error(
|
|
28980
|
+
`Vision LLM request failed: ${res.statusText} (${res.status}) - ${errorText}`
|
|
28981
|
+
);
|
|
28982
|
+
}
|
|
28983
|
+
const data = await res.json();
|
|
28984
|
+
if (data.error !== void 0) {
|
|
28985
|
+
throw new Error(`Vision model failed: ${formatModelError(data.error)}`);
|
|
28986
|
+
}
|
|
28987
|
+
if (data.stop_reason === "refusal") {
|
|
28988
|
+
throw new Error("Vision model refused the request (safety classifier).");
|
|
28989
|
+
}
|
|
28990
|
+
const text = data.content?.find(
|
|
28991
|
+
(b) => b.type === "text" && typeof b.text === "string"
|
|
28992
|
+
)?.text;
|
|
28993
|
+
if (!text) {
|
|
28994
|
+
throw new Error("Empty response from vision model");
|
|
28748
28995
|
}
|
|
28996
|
+
return text.trim();
|
|
28749
28997
|
}
|
|
28750
|
-
function
|
|
28998
|
+
function extractDraft(content) {
|
|
28999
|
+
const fenced = content.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
29000
|
+
const candidate = (fenced?.[1] ?? content).trim();
|
|
28751
29001
|
try {
|
|
28752
|
-
return
|
|
29002
|
+
return JSON.parse(candidate);
|
|
28753
29003
|
} catch {
|
|
28754
|
-
|
|
29004
|
+
const start = candidate.indexOf("{");
|
|
29005
|
+
const end = candidate.lastIndexOf("}");
|
|
29006
|
+
if (start === -1 || end <= start) throw new Error("no JSON object found");
|
|
29007
|
+
return JSON.parse(
|
|
29008
|
+
candidate.slice(start, end + 1)
|
|
29009
|
+
);
|
|
28755
29010
|
}
|
|
28756
29011
|
}
|
|
28757
|
-
function
|
|
28758
|
-
|
|
28759
|
-
|
|
28760
|
-
|
|
28761
|
-
|
|
28762
|
-
|
|
28763
|
-
|
|
28764
|
-
|
|
28765
|
-
|
|
28766
|
-
|
|
29012
|
+
function buildReport(input8, draft) {
|
|
29013
|
+
return {
|
|
29014
|
+
version: UI_OBSERVATION_PROTOCOL_VERSION,
|
|
29015
|
+
sessionId: input8.sessionId,
|
|
29016
|
+
sequence: input8.sequence,
|
|
29017
|
+
observedFrom: input8.observedFrom,
|
|
29018
|
+
observedTo: input8.observedTo,
|
|
29019
|
+
kind: parseKind(draft.kind),
|
|
29020
|
+
application: input8.application,
|
|
29021
|
+
summary: parseSummary(draft.summary),
|
|
29022
|
+
actions: parseActions(draft.actions),
|
|
29023
|
+
evidence: [
|
|
29024
|
+
{
|
|
29025
|
+
type: "keyframe",
|
|
29026
|
+
ref: input8.evidenceRef ?? basename5(input8.imagePath),
|
|
29027
|
+
redacted: input8.redacted ?? false
|
|
29028
|
+
}
|
|
29029
|
+
],
|
|
29030
|
+
candidateTokens: parseCandidateTokens(draft.candidateTokens),
|
|
29031
|
+
confidence: parseConfidence(draft.confidence, 0.35)
|
|
29032
|
+
};
|
|
28767
29033
|
}
|
|
28768
|
-
function
|
|
28769
|
-
return
|
|
29034
|
+
function uncertainReport(input8, summary) {
|
|
29035
|
+
return {
|
|
29036
|
+
version: UI_OBSERVATION_PROTOCOL_VERSION,
|
|
29037
|
+
sessionId: input8.sessionId,
|
|
29038
|
+
sequence: input8.sequence,
|
|
29039
|
+
observedFrom: input8.observedFrom,
|
|
29040
|
+
observedTo: input8.observedTo,
|
|
29041
|
+
kind: "uncertain",
|
|
29042
|
+
application: input8.application,
|
|
29043
|
+
summary,
|
|
29044
|
+
actions: [],
|
|
29045
|
+
evidence: [
|
|
29046
|
+
{
|
|
29047
|
+
type: "keyframe",
|
|
29048
|
+
ref: input8.evidenceRef ?? basename5(input8.imagePath),
|
|
29049
|
+
redacted: input8.redacted ?? false
|
|
29050
|
+
}
|
|
29051
|
+
],
|
|
29052
|
+
candidateTokens: [],
|
|
29053
|
+
confidence: 0.1
|
|
29054
|
+
};
|
|
28770
29055
|
}
|
|
28771
|
-
function
|
|
28772
|
-
|
|
28773
|
-
|
|
28774
|
-
const skillFile = join20(destinationDir, "SKILL.md");
|
|
28775
|
-
if (!existsSync18(skillFile)) return false;
|
|
28776
|
-
return /^name:\s*zam\s*$/m.test(readFileSync15(skillFile, "utf8"));
|
|
28777
|
-
} catch {
|
|
28778
|
-
return false;
|
|
29056
|
+
function parseKind(value) {
|
|
29057
|
+
if (typeof value === "string" && OBSERVATION_KINDS2.has(value)) {
|
|
29058
|
+
return value;
|
|
28779
29059
|
}
|
|
29060
|
+
return "uncertain";
|
|
28780
29061
|
}
|
|
28781
|
-
function
|
|
28782
|
-
if (
|
|
28783
|
-
|
|
28784
|
-
return "source-directory";
|
|
29062
|
+
function parseSummary(value) {
|
|
29063
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
29064
|
+
return value.trim();
|
|
28785
29065
|
}
|
|
28786
|
-
|
|
28787
|
-
if (!pathExists(destinationDir)) return "missing";
|
|
28788
|
-
if (isSymbolicLink(destinationDir)) return "broken";
|
|
28789
|
-
if (isZamSkillCopy(destinationDir)) return "stale-copy";
|
|
28790
|
-
return "unmanaged";
|
|
29066
|
+
return "Vision model did not provide a factual UI summary.";
|
|
28791
29067
|
}
|
|
28792
|
-
function
|
|
28793
|
-
|
|
28794
|
-
|
|
28795
|
-
if (!
|
|
28796
|
-
|
|
28797
|
-
|
|
28798
|
-
if (
|
|
28799
|
-
|
|
28800
|
-
const destinationDir = dirname9(join20(cwd, to));
|
|
28801
|
-
const label = dirname9(to);
|
|
28802
|
-
const state = classifySkillDestination(sourceDir, destinationDir);
|
|
28803
|
-
if (state === "source-missing") {
|
|
28804
|
-
if (!opts.quiet) {
|
|
28805
|
-
console.warn(` warn source not found, skipping: ${sourceDir}`);
|
|
28806
|
-
}
|
|
28807
|
-
results.push({
|
|
28808
|
-
source: sourceDir,
|
|
28809
|
-
destination: destinationDir,
|
|
28810
|
-
action: "skipped",
|
|
28811
|
-
reason: "source-not-found"
|
|
28812
|
-
});
|
|
28813
|
-
continue;
|
|
29068
|
+
function parseActions(value) {
|
|
29069
|
+
if (!Array.isArray(value)) return [];
|
|
29070
|
+
return value.flatMap((item) => {
|
|
29071
|
+
if (!isRecord2(item) || typeof item.type !== "string") return [];
|
|
29072
|
+
if (!ACTION_TYPES2.has(item.type)) return [];
|
|
29073
|
+
const action = { type: item.type };
|
|
29074
|
+
if (typeof item.target === "string" && item.target.trim()) {
|
|
29075
|
+
action.target = item.target.trim();
|
|
28814
29076
|
}
|
|
28815
|
-
if (
|
|
28816
|
-
|
|
28817
|
-
results.push({
|
|
28818
|
-
source: sourceDir,
|
|
28819
|
-
destination: destinationDir,
|
|
28820
|
-
action: "skipped",
|
|
28821
|
-
reason: "source-directory"
|
|
28822
|
-
});
|
|
28823
|
-
continue;
|
|
29077
|
+
if (typeof item.result === "string" && item.result.trim()) {
|
|
29078
|
+
action.result = item.result.trim();
|
|
28824
29079
|
}
|
|
28825
|
-
|
|
28826
|
-
|
|
28827
|
-
|
|
28828
|
-
|
|
28829
|
-
|
|
28830
|
-
|
|
28831
|
-
|
|
28832
|
-
|
|
28833
|
-
|
|
29080
|
+
return [action];
|
|
29081
|
+
});
|
|
29082
|
+
}
|
|
29083
|
+
function parseCandidateTokens(value) {
|
|
29084
|
+
if (!Array.isArray(value)) return [];
|
|
29085
|
+
return value.flatMap((item) => {
|
|
29086
|
+
if (!isRecord2(item)) return [];
|
|
29087
|
+
if (typeof item.slug !== "string" || !/^[A-Za-z0-9._-]+$/.test(item.slug)) {
|
|
29088
|
+
return [];
|
|
28834
29089
|
}
|
|
28835
|
-
|
|
28836
|
-
|
|
28837
|
-
if (destinationExists && !replaceExisting) {
|
|
28838
|
-
if (!opts.quiet) {
|
|
28839
|
-
console.warn(
|
|
28840
|
-
` warn ${label} already exists and is not managed by ZAM; use --force to replace it`
|
|
28841
|
-
);
|
|
28842
|
-
}
|
|
28843
|
-
results.push({
|
|
28844
|
-
source: sourceDir,
|
|
28845
|
-
destination: destinationDir,
|
|
28846
|
-
action: "skipped",
|
|
28847
|
-
reason: "unmanaged-destination"
|
|
28848
|
-
});
|
|
28849
|
-
continue;
|
|
29090
|
+
if (typeof item.rationale !== "string" || !item.rationale.trim()) {
|
|
29091
|
+
return [];
|
|
28850
29092
|
}
|
|
28851
|
-
|
|
28852
|
-
|
|
28853
|
-
|
|
28854
|
-
|
|
28855
|
-
|
|
28856
|
-
rmSync2(destinationDir, { recursive: true, force: true });
|
|
29093
|
+
return [
|
|
29094
|
+
{
|
|
29095
|
+
slug: item.slug,
|
|
29096
|
+
confidence: parseConfidence(item.confidence, 0.2),
|
|
29097
|
+
rationale: item.rationale.trim()
|
|
28857
29098
|
}
|
|
28858
|
-
|
|
28859
|
-
|
|
28860
|
-
|
|
28861
|
-
|
|
28862
|
-
|
|
28863
|
-
|
|
28864
|
-
|
|
28865
|
-
|
|
28866
|
-
|
|
29099
|
+
];
|
|
29100
|
+
});
|
|
29101
|
+
}
|
|
29102
|
+
function parseConfidence(value, fallback) {
|
|
29103
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
29104
|
+
return Math.max(0, Math.min(1, value));
|
|
29105
|
+
}
|
|
29106
|
+
function formatModelError(error) {
|
|
29107
|
+
if (typeof error === "string") return error;
|
|
29108
|
+
if (isRecord2(error) && typeof error.message === "string") {
|
|
29109
|
+
return error.message;
|
|
28867
29110
|
}
|
|
28868
|
-
return
|
|
29111
|
+
return JSON.stringify(error);
|
|
29112
|
+
}
|
|
29113
|
+
function isRecord2(value) {
|
|
29114
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
29115
|
+
}
|
|
29116
|
+
|
|
29117
|
+
// src/cli/providers/config.ts
|
|
29118
|
+
init_kernel();
|
|
29119
|
+
|
|
29120
|
+
// src/cli/commands/shared/db.ts
|
|
29121
|
+
init_kernel();
|
|
29122
|
+
function defaultErrorHandler(message) {
|
|
29123
|
+
console.error("Error:", message);
|
|
29124
|
+
process.exit(1);
|
|
29125
|
+
}
|
|
29126
|
+
async function withDb(fn, onError = defaultErrorHandler) {
|
|
29127
|
+
let db;
|
|
29128
|
+
try {
|
|
29129
|
+
db = await openDatabase();
|
|
29130
|
+
await fn(db);
|
|
29131
|
+
} catch (err) {
|
|
29132
|
+
onError(err.message);
|
|
29133
|
+
} finally {
|
|
29134
|
+
await db?.close();
|
|
29135
|
+
}
|
|
29136
|
+
}
|
|
29137
|
+
function jsonOut(data) {
|
|
29138
|
+
console.log(JSON.stringify(data, null, 2));
|
|
29139
|
+
}
|
|
29140
|
+
|
|
29141
|
+
// src/cli/providers/config.ts
|
|
29142
|
+
var VALID_API_FLAVORS = [
|
|
29143
|
+
"chat-completions",
|
|
29144
|
+
"anthropic-messages"
|
|
29145
|
+
];
|
|
29146
|
+
var VALID_ROLES = ["vision", "recall", "text", "embedding"];
|
|
29147
|
+
function upsertProviderRecord(providers, name, patch) {
|
|
29148
|
+
const merged = { ...providers[name] ?? {} };
|
|
29149
|
+
if (patch.url !== void 0) merged.url = patch.url;
|
|
29150
|
+
if (patch.model !== void 0) merged.model = patch.model;
|
|
29151
|
+
if (patch.apiFlavor !== void 0) merged.apiFlavor = patch.apiFlavor;
|
|
29152
|
+
if (patch.apiKeyRef !== void 0) merged.apiKeyRef = patch.apiKeyRef;
|
|
29153
|
+
if (patch.label !== void 0) merged.label = patch.label;
|
|
29154
|
+
if (patch.local !== void 0) merged.local = patch.local;
|
|
29155
|
+
if (patch.runner !== void 0) merged.runner = patch.runner;
|
|
29156
|
+
return { ...providers, [name]: merged };
|
|
29157
|
+
}
|
|
29158
|
+
function removeProviderRecord(providers, name) {
|
|
29159
|
+
if (!(name in providers)) return { providers, removed: false };
|
|
29160
|
+
const next = { ...providers };
|
|
29161
|
+
delete next[name];
|
|
29162
|
+
return { providers: next, removed: true };
|
|
29163
|
+
}
|
|
29164
|
+
function rolesReferencing(roles, name) {
|
|
29165
|
+
return VALID_ROLES.filter((role) => {
|
|
29166
|
+
const binding = roles[role];
|
|
29167
|
+
return binding?.primary === name || binding?.fallback === name;
|
|
29168
|
+
});
|
|
29169
|
+
}
|
|
29170
|
+
function bindRoleProviders(roles, role, primary, fallback) {
|
|
29171
|
+
const binding = { primary };
|
|
29172
|
+
if (fallback) binding.fallback = fallback;
|
|
29173
|
+
return { ...roles, [role]: binding };
|
|
28869
29174
|
}
|
|
28870
|
-
function
|
|
28871
|
-
|
|
28872
|
-
|
|
28873
|
-
|
|
28874
|
-
|
|
28875
|
-
const destinationDir = dirname9(join20(cwd, to));
|
|
28876
|
-
results.push({
|
|
28877
|
-
agents: pairAgents,
|
|
28878
|
-
source: sourceDir,
|
|
28879
|
-
destination: destinationDir,
|
|
28880
|
-
state: classifySkillDestination(sourceDir, destinationDir)
|
|
28881
|
-
});
|
|
28882
|
-
}
|
|
28883
|
-
return results;
|
|
29175
|
+
function unbindRole(roles, role) {
|
|
29176
|
+
if (!(role in roles)) return roles;
|
|
29177
|
+
const next = { ...roles };
|
|
29178
|
+
delete next[role];
|
|
29179
|
+
return next;
|
|
28884
29180
|
}
|
|
28885
|
-
function
|
|
28886
|
-
|
|
28887
|
-
return "unmanaged";
|
|
28888
|
-
}
|
|
28889
|
-
const repairable = ["missing", "broken", "stale-copy"];
|
|
28890
|
-
if (inspections.some((item) => repairable.includes(item.state))) {
|
|
28891
|
-
return "needs-repair";
|
|
28892
|
-
}
|
|
28893
|
-
return "healthy";
|
|
29181
|
+
function maskSecret(key) {
|
|
29182
|
+
return key.length <= 4 ? "\u2022\u2022\u2022\u2022" : `\u2026${key.slice(-4)}`;
|
|
28894
29183
|
}
|
|
28895
|
-
|
|
28896
|
-
|
|
28897
|
-
|
|
28898
|
-
|
|
28899
|
-
|
|
28900
|
-
|
|
28901
|
-
|
|
28902
|
-
|
|
28903
|
-
|
|
28904
|
-
|
|
28905
|
-
|
|
28906
|
-
|
|
28907
|
-
|
|
28908
|
-
|
|
28909
|
-
|
|
28910
|
-
|
|
28911
|
-
|
|
28912
|
-
|
|
28913
|
-
|
|
28914
|
-
|
|
28915
|
-
${block}
|
|
28916
|
-
`;
|
|
28917
|
-
if (!dryRun) writeFileSync11(dest, next, "utf8");
|
|
28918
|
-
return start >= 0 && end > start ? "update" : "write";
|
|
29184
|
+
function buildProviderListing(providers, hasKey) {
|
|
29185
|
+
return Object.entries(providers).map(([name, rec]) => {
|
|
29186
|
+
const apiFlavor = rec.apiFlavor ?? (rec.url ? inferApiFlavor(rec.url) : "chat-completions");
|
|
29187
|
+
let keyState;
|
|
29188
|
+
if (!rec.apiKeyRef) keyState = "none";
|
|
29189
|
+
else keyState = hasKey(rec.apiKeyRef) ? "set" : "missing";
|
|
29190
|
+
const row = {
|
|
29191
|
+
name,
|
|
29192
|
+
url: rec.url,
|
|
29193
|
+
model: rec.model,
|
|
29194
|
+
apiFlavor,
|
|
29195
|
+
apiKeyRef: rec.apiKeyRef,
|
|
29196
|
+
keyState
|
|
29197
|
+
};
|
|
29198
|
+
if (rec.label !== void 0) row.label = rec.label;
|
|
29199
|
+
if (rec.local !== void 0) row.local = rec.local;
|
|
29200
|
+
if (rec.runner !== void 0) row.runner = rec.runner;
|
|
29201
|
+
return row;
|
|
29202
|
+
});
|
|
28919
29203
|
}
|
|
28920
|
-
function
|
|
28921
|
-
|
|
28922
|
-
|
|
28923
|
-
|
|
28924
|
-
|
|
29204
|
+
function findOrphanKeyRefs(storedRefs, providers) {
|
|
29205
|
+
const used = new Set(
|
|
29206
|
+
Object.values(providers).map((rec) => rec.apiKeyRef).filter((ref) => !!ref)
|
|
29207
|
+
);
|
|
29208
|
+
return storedRefs.filter((ref) => !used.has(ref));
|
|
29209
|
+
}
|
|
29210
|
+
async function readJson(db, key, fallback) {
|
|
29211
|
+
const raw = await getSetting(db, key);
|
|
29212
|
+
if (!raw) return fallback;
|
|
29213
|
+
try {
|
|
29214
|
+
return JSON.parse(raw);
|
|
29215
|
+
} catch {
|
|
29216
|
+
return fallback;
|
|
28925
29217
|
}
|
|
28926
29218
|
}
|
|
28927
|
-
|
|
28928
|
-
|
|
28929
|
-
|
|
28930
|
-
if (
|
|
28931
|
-
|
|
28932
|
-
|
|
28933
|
-
|
|
28934
|
-
|
|
28935
|
-
|
|
28936
|
-
|
|
28937
|
-
|
|
28938
|
-
|
|
28939
|
-
|
|
28940
|
-
|
|
28941
|
-
|
|
28942
|
-
|
|
28943
|
-
|
|
28944
|
-
|
|
28945
|
-
);
|
|
28946
|
-
logInstructionAction(action, "CLAUDE.md", Boolean(opts.dryRun));
|
|
29219
|
+
var readProviders = (db) => readJson(db, "llm.providers", {});
|
|
29220
|
+
var readRoles = (db) => readJson(db, "llm.roles", {});
|
|
29221
|
+
async function readScopedProviders(db, machine) {
|
|
29222
|
+
if (machine) return getMachineAiConfig().providers ?? {};
|
|
29223
|
+
if (!db)
|
|
29224
|
+
throw new Error("Database is required for shared provider settings.");
|
|
29225
|
+
return readProviders(db);
|
|
29226
|
+
}
|
|
29227
|
+
async function readScopedRoles(db, machine) {
|
|
29228
|
+
if (machine) return getMachineAiConfig().roles ?? {};
|
|
29229
|
+
if (!db)
|
|
29230
|
+
throw new Error("Database is required for shared provider settings.");
|
|
29231
|
+
return readRoles(db);
|
|
29232
|
+
}
|
|
29233
|
+
async function writeScopedProviders(db, machine, p) {
|
|
29234
|
+
if (machine) {
|
|
29235
|
+
const config = getMachineAiConfig();
|
|
29236
|
+
saveMachineAiConfig({ ...config, providers: p });
|
|
28947
29237
|
return;
|
|
28948
29238
|
}
|
|
28949
|
-
|
|
28950
|
-
|
|
28951
|
-
|
|
28952
|
-
This is a ZAM personal instance. ZAM builds lasting skills through spaced
|
|
28953
|
-
repetition during real work \u2014 not separate study sessions.
|
|
28954
|
-
|
|
28955
|
-
## First time here?
|
|
28956
|
-
Run \`/setup\` in Claude Code or Gemini CLI to complete first-time setup.
|
|
28957
|
-
|
|
28958
|
-
## Regular use
|
|
28959
|
-
Run \`/zam\` to start a learning session on whatever you are working on.
|
|
28960
|
-
|
|
28961
|
-
## What lives here
|
|
28962
|
-
- \`beliefs/\` \u2014 your worldview, approved by git commit
|
|
28963
|
-
- \`goals/\` \u2014 your objectives, decomposed into tasks and learning tokens
|
|
28964
|
-
|
|
28965
|
-
## Fast-changing data
|
|
28966
|
-
Learning tokens, cards, and review history live in local SQLite by default.
|
|
28967
|
-
Use \`zam connector setup turso\` to store cloud credentials in
|
|
28968
|
-
\`~/.zam/credentials.json\` and use a Turso database across machines.
|
|
28969
|
-
`;
|
|
28970
|
-
if (opts.dryRun) {
|
|
28971
|
-
console.log(` would write CLAUDE.md`);
|
|
28972
|
-
} else {
|
|
28973
|
-
writeFileSync11(dest, content, "utf8");
|
|
28974
|
-
console.log(` write CLAUDE.md`);
|
|
28975
|
-
}
|
|
29239
|
+
if (!db)
|
|
29240
|
+
throw new Error("Database is required for shared provider settings.");
|
|
29241
|
+
await setSetting(db, "llm.providers", JSON.stringify(p));
|
|
28976
29242
|
}
|
|
28977
|
-
function
|
|
28978
|
-
if (
|
|
28979
|
-
|
|
28980
|
-
|
|
28981
|
-
if (!opts.updateExisting) {
|
|
28982
|
-
console.log(` skip AGENTS.md (already present)`);
|
|
28983
|
-
return;
|
|
28984
|
-
}
|
|
28985
|
-
const action = upsertMarkedBlock(
|
|
28986
|
-
dest,
|
|
28987
|
-
`## ZAM learning sessions
|
|
28988
|
-
|
|
28989
|
-
ZAM is available in this repository. Select the \`zam\` skill through \`/skills\` or invoke \`$zam\` where supported to turn real work into an observed learning session with active recall and FSRS scheduling.
|
|
28990
|
-
|
|
28991
|
-
- Skill files live under \`.agents/skills/zam/\`.
|
|
28992
|
-
- Fast-changing review data lives in \`~/.zam/\`, not in this repository.
|
|
28993
|
-
- The skill directory is linked to this ZAM installation and updates with it.`,
|
|
28994
|
-
Boolean(opts.dryRun)
|
|
28995
|
-
);
|
|
28996
|
-
logInstructionAction(action, "AGENTS.md", Boolean(opts.dryRun));
|
|
29243
|
+
async function writeScopedRoles(db, machine, r) {
|
|
29244
|
+
if (machine) {
|
|
29245
|
+
const config = getMachineAiConfig();
|
|
29246
|
+
saveMachineAiConfig({ ...config, roles: r });
|
|
28997
29247
|
return;
|
|
28998
29248
|
}
|
|
28999
|
-
|
|
29000
|
-
|
|
29001
|
-
|
|
29002
|
-
This is a ZAM personal instance. ZAM builds lasting skills through spaced
|
|
29003
|
-
repetition during real work, not separate study sessions.
|
|
29004
|
-
|
|
29005
|
-
## First time here?
|
|
29006
|
-
Run \`zam setup\` from the shell. When this repository includes
|
|
29007
|
-
\`.agents/skills/setup/\`, you can instead select \`setup\` through \`/skills\`
|
|
29008
|
-
or invoke \`$setup\`.
|
|
29009
|
-
|
|
29010
|
-
## Regular use
|
|
29011
|
-
Select the \`zam\` skill through \`/skills\` or invoke \`$zam\` to start a
|
|
29012
|
-
learning session on whatever you are working on.
|
|
29013
|
-
|
|
29014
|
-
## What lives here
|
|
29015
|
-
- \`beliefs/\` - your worldview, approved by git commit
|
|
29016
|
-
- \`goals/\` - your objectives, decomposed into tasks and learning tokens
|
|
29017
|
-
|
|
29018
|
-
## Fast-changing data
|
|
29019
|
-
Learning tokens, cards, and review history live in local SQLite by default.
|
|
29020
|
-
Use \`zam connector setup turso\` to store cloud credentials in
|
|
29021
|
-
\`~/.zam/credentials.json\` and use a Turso database across machines.
|
|
29022
|
-
|
|
29023
|
-
## Codex skills
|
|
29024
|
-
Codex discovers repository skills under \`.agents/skills/\`. Run
|
|
29025
|
-
\`zam setup --force\` after upgrading \`zam-core\` to refresh them.
|
|
29026
|
-
`;
|
|
29027
|
-
if (opts.dryRun) {
|
|
29028
|
-
console.log(` would write AGENTS.md`);
|
|
29029
|
-
} else {
|
|
29030
|
-
writeFileSync11(dest, content, "utf8");
|
|
29031
|
-
console.log(` write AGENTS.md`);
|
|
29032
|
-
}
|
|
29249
|
+
if (!db)
|
|
29250
|
+
throw new Error("Database is required for shared provider settings.");
|
|
29251
|
+
await setSetting(db, "llm.roles", JSON.stringify(r));
|
|
29033
29252
|
}
|
|
29034
|
-
function
|
|
29035
|
-
|
|
29036
|
-
|
|
29037
|
-
|
|
29038
|
-
|
|
29039
|
-
|
|
29040
|
-
ZAM is available in this repository through \`.claude/skills/zam/\`. Use the \`zam\` project skill from Copilot-compatible skill selection surfaces to turn real work into an observed learning session with active recall and FSRS scheduling.
|
|
29041
|
-
|
|
29042
|
-
- Fast-changing review data lives in \`~/.zam/\`, not in this repository.
|
|
29043
|
-
- The skill directory is linked to this ZAM installation and updates with it.`,
|
|
29044
|
-
Boolean(opts.dryRun)
|
|
29045
|
-
);
|
|
29046
|
-
logInstructionAction(
|
|
29047
|
-
action,
|
|
29048
|
-
".github/copilot-instructions.md",
|
|
29049
|
-
Boolean(opts.dryRun)
|
|
29050
|
-
);
|
|
29253
|
+
async function withProviderScope(machine, action) {
|
|
29254
|
+
if (machine) {
|
|
29255
|
+
await action(void 0);
|
|
29256
|
+
return;
|
|
29257
|
+
}
|
|
29258
|
+
await withDb(action);
|
|
29051
29259
|
}
|
|
29052
29260
|
|
|
29053
29261
|
// src/cli/users/identity.ts
|
|
@@ -29073,13 +29281,13 @@ async function resolveUser(opts, db, resolveOpts) {
|
|
|
29073
29281
|
}
|
|
29074
29282
|
|
|
29075
29283
|
// src/cli/workspaces/backup.ts
|
|
29076
|
-
import { mkdirSync as
|
|
29077
|
-
import { join as
|
|
29284
|
+
import { mkdirSync as mkdirSync15 } from "fs";
|
|
29285
|
+
import { join as join22 } from "path";
|
|
29078
29286
|
async function backupDatabaseTo(db, targetDir) {
|
|
29079
|
-
const backupDir =
|
|
29080
|
-
|
|
29287
|
+
const backupDir = join22(targetDir, "zam-backups");
|
|
29288
|
+
mkdirSync15(backupDir, { recursive: true });
|
|
29081
29289
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
29082
|
-
const dest =
|
|
29290
|
+
const dest = join22(backupDir, `zam-${stamp}.db`);
|
|
29083
29291
|
await db.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
|
|
29084
29292
|
return dest;
|
|
29085
29293
|
}
|
|
@@ -29210,20 +29418,20 @@ bridgeCommand.command("workspace-info").description("Report the workspace dir, i
|
|
|
29210
29418
|
activeWorkspace,
|
|
29211
29419
|
workspaceDir: activeWorkspace.path,
|
|
29212
29420
|
defaultWorkspaceDir: defaultWorkspaceDir(),
|
|
29213
|
-
dataDir:
|
|
29421
|
+
dataDir: join23(homedir14(), ".zam")
|
|
29214
29422
|
});
|
|
29215
29423
|
});
|
|
29216
29424
|
});
|
|
29217
29425
|
function provisionConfiguredWorkspaces() {
|
|
29218
29426
|
return getConfiguredWorkspaces().flatMap(
|
|
29219
|
-
(workspace) =>
|
|
29427
|
+
(workspace) => existsSync21(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
|
|
29220
29428
|
);
|
|
29221
29429
|
}
|
|
29222
29430
|
function buildWorkspaceLinkHealth(workspaces) {
|
|
29223
29431
|
const agents = parseSetupAgents();
|
|
29224
29432
|
const map = {};
|
|
29225
29433
|
for (const workspace of workspaces) {
|
|
29226
|
-
if (!
|
|
29434
|
+
if (!existsSync21(workspace.path)) continue;
|
|
29227
29435
|
const links = inspectSkillLinks(workspace.path, agents);
|
|
29228
29436
|
map[workspace.id] = {
|
|
29229
29437
|
health: summarizeSkillLinkHealth(links),
|
|
@@ -29253,7 +29461,7 @@ bridgeCommand.command("workspace-list").description("List configured ZAM workspa
|
|
|
29253
29461
|
activeWorkspace,
|
|
29254
29462
|
workspaceDir: activeWorkspace.path,
|
|
29255
29463
|
defaultWorkspaceDir: defaultWorkspaceDir(),
|
|
29256
|
-
dataDir:
|
|
29464
|
+
dataDir: join23(homedir14(), ".zam"),
|
|
29257
29465
|
linkHealth: buildWorkspaceLinkHealth(workspaces)
|
|
29258
29466
|
});
|
|
29259
29467
|
});
|
|
@@ -29268,7 +29476,7 @@ bridgeCommand.command("workspace-repair-links").description(
|
|
|
29268
29476
|
if (!id) jsonError("A non-empty --id is required");
|
|
29269
29477
|
const workspace = getConfiguredWorkspaces().find((item) => item.id === id);
|
|
29270
29478
|
if (!workspace) jsonError(`Workspace "${id}" is not configured`);
|
|
29271
|
-
if (!
|
|
29479
|
+
if (!existsSync21(workspace.path)) {
|
|
29272
29480
|
jsonError(`Workspace path does not exist: ${workspace.path}`);
|
|
29273
29481
|
}
|
|
29274
29482
|
const agents = parseSetupAgents(opts.agents);
|
|
@@ -29299,8 +29507,8 @@ function parseBridgeWorkspaceKind(value) {
|
|
|
29299
29507
|
bridgeCommand.command("workspace-add").description("Register an existing directory as a ZAM workspace (JSON)").requiredOption("--path <dir>", "Existing workspace/repository directory").option("--id <id>", "Workspace id").option("--label <label>", "Human-readable label").option("--kind <kind>", "Workspace kind", "custom").action(async (opts) => {
|
|
29300
29508
|
const raw = String(opts.path ?? "").trim();
|
|
29301
29509
|
if (!raw) jsonError("A non-empty --path is required");
|
|
29302
|
-
const path =
|
|
29303
|
-
if (!
|
|
29510
|
+
const path = resolve6(raw);
|
|
29511
|
+
if (!existsSync21(path)) jsonError(`Workspace path does not exist: ${path}`);
|
|
29304
29512
|
const id = opts.id ? String(opts.id).trim() : void 0;
|
|
29305
29513
|
if (opts.id && !id) jsonError("A non-empty --id is required");
|
|
29306
29514
|
const kind = parseBridgeWorkspaceKind(opts.kind);
|
|
@@ -29344,8 +29552,8 @@ bridgeCommand.command("workspace-remove").description("Unregister a ZAM workspac
|
|
|
29344
29552
|
bridgeCommand.command("set-workspace-dir").description("Set the personal workspace directory (JSON)").requiredOption("--dir <path>", "Path to the workspace directory").action(async (opts) => {
|
|
29345
29553
|
const raw = String(opts.dir ?? "").trim();
|
|
29346
29554
|
if (!raw) jsonError("A non-empty --dir is required");
|
|
29347
|
-
const dir =
|
|
29348
|
-
if (!
|
|
29555
|
+
const dir = resolve6(raw);
|
|
29556
|
+
if (!existsSync21(dir)) jsonError(`Workspace path does not exist: ${dir}`);
|
|
29349
29557
|
const skillLinks = wireSkills(dir, parseSetupAgents(), { quiet: true });
|
|
29350
29558
|
await withDb2(async (db) => {
|
|
29351
29559
|
const workspace = await activateWorkspacePath(db, dir);
|
|
@@ -29408,7 +29616,7 @@ bridgeCommand.command("agent-open").description("Launch an agent harness in the
|
|
|
29408
29616
|
jsonError(`Workspace is not configured: ${opts.workspace}`);
|
|
29409
29617
|
}
|
|
29410
29618
|
const activeWorkspace = await ensureActiveWorkspace(db);
|
|
29411
|
-
const workspace = opts.dir ?
|
|
29619
|
+
const workspace = opts.dir ? existsSync21(opts.dir) ? opts.dir : homedir14() : existingWorkspaceDirOrHome(configuredWorkspace ?? activeWorkspace);
|
|
29412
29620
|
launchHarness(harness, {
|
|
29413
29621
|
executable,
|
|
29414
29622
|
workspace,
|
|
@@ -29780,7 +29988,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
29780
29988
|
"20"
|
|
29781
29989
|
).action(async (opts) => {
|
|
29782
29990
|
try {
|
|
29783
|
-
const monitorDir =
|
|
29991
|
+
const monitorDir = join23(homedir14(), ".zam", "monitor");
|
|
29784
29992
|
let files;
|
|
29785
29993
|
try {
|
|
29786
29994
|
files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
|
|
@@ -29793,7 +30001,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
29793
30001
|
return;
|
|
29794
30002
|
}
|
|
29795
30003
|
const limit = Number(opts.limit);
|
|
29796
|
-
const sorted = files.map((f) => ({ name: f, path:
|
|
30004
|
+
const sorted = files.map((f) => ({ name: f, path: join23(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
|
|
29797
30005
|
const sessionCommands = /* @__PURE__ */ new Map();
|
|
29798
30006
|
for (const file of sorted) {
|
|
29799
30007
|
const sessionId = file.name.replace(".jsonl", "");
|
|
@@ -29905,7 +30113,7 @@ bridgeCommand.command("observe-ui-snapshot").description(
|
|
|
29905
30113
|
});
|
|
29906
30114
|
function resolveWindowsPowerShell() {
|
|
29907
30115
|
try {
|
|
29908
|
-
|
|
30116
|
+
execFileSync5("where.exe", ["pwsh.exe"], { stdio: "ignore" });
|
|
29909
30117
|
return "pwsh";
|
|
29910
30118
|
} catch {
|
|
29911
30119
|
return "powershell";
|
|
@@ -29920,7 +30128,7 @@ function captureScreenshot(outputPath, hwnd, processName) {
|
|
|
29920
30128
|
throw new Error(`Invalid process name format: ${processName}`);
|
|
29921
30129
|
}
|
|
29922
30130
|
if (platform === "win32") {
|
|
29923
|
-
const stdout =
|
|
30131
|
+
const stdout = execFileSync5(
|
|
29924
30132
|
resolveWindowsPowerShell(),
|
|
29925
30133
|
[
|
|
29926
30134
|
"-NoProfile",
|
|
@@ -30233,13 +30441,13 @@ Write-CaptureResult "fullscreen" $hwndVal $matchedBy
|
|
|
30233
30441
|
} else if (platform === "darwin") {
|
|
30234
30442
|
if (hwnd) {
|
|
30235
30443
|
const parsedHwnd = hwnd.startsWith("0x") ? parseInt(hwnd, 16) : parseInt(hwnd, 10);
|
|
30236
|
-
|
|
30444
|
+
execFileSync5("screencapture", ["-l", String(parsedHwnd), outputPath], {
|
|
30237
30445
|
stdio: "pipe"
|
|
30238
30446
|
});
|
|
30239
30447
|
return { method: "screencapture-window", target: null };
|
|
30240
30448
|
} else if (processName) {
|
|
30241
30449
|
try {
|
|
30242
|
-
const windowId =
|
|
30450
|
+
const windowId = execFileSync5(
|
|
30243
30451
|
"osascript",
|
|
30244
30452
|
[
|
|
30245
30453
|
"-e",
|
|
@@ -30248,17 +30456,17 @@ Write-CaptureResult "fullscreen" $hwndVal $matchedBy
|
|
|
30248
30456
|
{ encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }
|
|
30249
30457
|
).trim();
|
|
30250
30458
|
if (windowId && /^\d+$/.test(windowId)) {
|
|
30251
|
-
|
|
30459
|
+
execFileSync5("screencapture", ["-l", windowId, outputPath], {
|
|
30252
30460
|
stdio: "pipe"
|
|
30253
30461
|
});
|
|
30254
30462
|
return { method: "screencapture-window", target: null };
|
|
30255
30463
|
}
|
|
30256
30464
|
} catch {
|
|
30257
30465
|
}
|
|
30258
|
-
|
|
30466
|
+
execFileSync5("screencapture", ["-x", outputPath], { stdio: "pipe" });
|
|
30259
30467
|
return { method: "screencapture-full", target: null };
|
|
30260
30468
|
} else {
|
|
30261
|
-
|
|
30469
|
+
execFileSync5("screencapture", ["-x", outputPath], { stdio: "pipe" });
|
|
30262
30470
|
return { method: "screencapture-full", target: null };
|
|
30263
30471
|
}
|
|
30264
30472
|
} else {
|
|
@@ -30295,7 +30503,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
|
|
|
30295
30503
|
return;
|
|
30296
30504
|
}
|
|
30297
30505
|
}
|
|
30298
|
-
const outputPath = opts.image ?? opts.output ??
|
|
30506
|
+
const outputPath = opts.image ?? opts.output ?? join23(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
|
|
30299
30507
|
const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
|
|
30300
30508
|
if (!isProvided) {
|
|
30301
30509
|
const post = decidePostCapture(policy, {
|
|
@@ -30323,7 +30531,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
|
|
|
30323
30531
|
return;
|
|
30324
30532
|
}
|
|
30325
30533
|
}
|
|
30326
|
-
const imageBytes =
|
|
30534
|
+
const imageBytes = readFileSync17(outputPath);
|
|
30327
30535
|
const base64 = imageBytes.toString("base64");
|
|
30328
30536
|
jsonOut2({
|
|
30329
30537
|
sessionId: opts.session ?? null,
|
|
@@ -30350,11 +30558,11 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
30350
30558
|
return;
|
|
30351
30559
|
}
|
|
30352
30560
|
const sessionId = opts.session;
|
|
30353
|
-
const statePath =
|
|
30561
|
+
const statePath = join23(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
30354
30562
|
const defaultExt = platform === "win32" ? ".mkv" : ".mov";
|
|
30355
|
-
const outputPath = opts.output ??
|
|
30356
|
-
const { existsSync:
|
|
30357
|
-
if (
|
|
30563
|
+
const outputPath = opts.output ?? join23(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
|
|
30564
|
+
const { existsSync: existsSync30, writeFileSync: writeFileSync17, openSync, closeSync } = await import("fs");
|
|
30565
|
+
if (existsSync30(statePath)) {
|
|
30358
30566
|
jsonOut2({
|
|
30359
30567
|
sessionId,
|
|
30360
30568
|
started: false,
|
|
@@ -30362,7 +30570,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
30362
30570
|
});
|
|
30363
30571
|
return;
|
|
30364
30572
|
}
|
|
30365
|
-
const logPath =
|
|
30573
|
+
const logPath = join23(tmpdir3(), `zam-recording-${sessionId}.log`);
|
|
30366
30574
|
let logFd;
|
|
30367
30575
|
try {
|
|
30368
30576
|
logFd = openSync(logPath, "w");
|
|
@@ -30408,7 +30616,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
30408
30616
|
}
|
|
30409
30617
|
child.unref();
|
|
30410
30618
|
if (child.pid) {
|
|
30411
|
-
|
|
30619
|
+
writeFileSync17(
|
|
30412
30620
|
statePath,
|
|
30413
30621
|
JSON.stringify({
|
|
30414
30622
|
pid: child.pid,
|
|
@@ -30444,9 +30652,9 @@ bridgeCommand.command("stop-recording").description(
|
|
|
30444
30652
|
return;
|
|
30445
30653
|
}
|
|
30446
30654
|
const sessionId = opts.session;
|
|
30447
|
-
const statePath =
|
|
30448
|
-
const { existsSync:
|
|
30449
|
-
if (!
|
|
30655
|
+
const statePath = join23(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
30656
|
+
const { existsSync: existsSync30, readFileSync: readFileSync22, rmSync: rmSync4 } = await import("fs");
|
|
30657
|
+
if (!existsSync30(statePath)) {
|
|
30450
30658
|
jsonOut2({
|
|
30451
30659
|
sessionId,
|
|
30452
30660
|
stopped: false,
|
|
@@ -30454,7 +30662,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
30454
30662
|
});
|
|
30455
30663
|
return;
|
|
30456
30664
|
}
|
|
30457
|
-
const state = JSON.parse(
|
|
30665
|
+
const state = JSON.parse(readFileSync22(statePath, "utf8"));
|
|
30458
30666
|
const { pid, outputPath } = state;
|
|
30459
30667
|
try {
|
|
30460
30668
|
process.kill(pid, "SIGINT");
|
|
@@ -30470,7 +30678,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
30470
30678
|
};
|
|
30471
30679
|
let attempts = 0;
|
|
30472
30680
|
while (isProcessRunning(pid) && attempts < 20) {
|
|
30473
|
-
await new Promise((
|
|
30681
|
+
await new Promise((resolve12) => setTimeout(resolve12, 250));
|
|
30474
30682
|
attempts++;
|
|
30475
30683
|
}
|
|
30476
30684
|
if (isProcessRunning(pid)) {
|
|
@@ -30483,7 +30691,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
30483
30691
|
rmSync4(statePath, { force: true });
|
|
30484
30692
|
} catch {
|
|
30485
30693
|
}
|
|
30486
|
-
if (!
|
|
30694
|
+
if (!existsSync30(outputPath)) {
|
|
30487
30695
|
jsonOut2({
|
|
30488
30696
|
sessionId,
|
|
30489
30697
|
stopped: false,
|
|
@@ -30913,7 +31121,7 @@ bridgeCommand.command("cloud-model-hint").description("Suggest a cloud model for
|
|
|
30913
31121
|
});
|
|
30914
31122
|
bridgeCommand.command("local-llm-hints").description("Detect installed local LLM servers and suggest defaults (JSON)").action(() => {
|
|
30915
31123
|
const profile = getSystemProfile();
|
|
30916
|
-
const flmInstalled = hasCommand("flm") ||
|
|
31124
|
+
const flmInstalled = hasCommand("flm") || existsSync21("C:\\Program Files\\flm\\flm.exe");
|
|
30917
31125
|
const ollamaInstalled = isOllamaInstalled();
|
|
30918
31126
|
const runners = [
|
|
30919
31127
|
{ id: "flm", label: "FastFlowLM", installed: flmInstalled },
|
|
@@ -31145,13 +31353,15 @@ bridgeCommand.command("desktop-bootstrap").description("Initialize first-run des
|
|
|
31145
31353
|
const userId = await ensureDefaultUser(db, opts.user);
|
|
31146
31354
|
const { enabled, url, model, locale } = await getLlmConfig(db);
|
|
31147
31355
|
const { workspaceDir, activeWorkspaceId, skillLinks } = await ensureDesktopWorkspace(db);
|
|
31356
|
+
const cli = installCliShim();
|
|
31148
31357
|
jsonOut2({
|
|
31149
31358
|
userId,
|
|
31150
31359
|
locale,
|
|
31151
31360
|
llm: { enabled, url, model },
|
|
31152
31361
|
activeWorkspaceId,
|
|
31153
31362
|
workspaceDir,
|
|
31154
|
-
skillLinks
|
|
31363
|
+
skillLinks,
|
|
31364
|
+
cli
|
|
31155
31365
|
});
|
|
31156
31366
|
});
|
|
31157
31367
|
});
|
|
@@ -31168,6 +31378,22 @@ bridgeCommand.command("get-settings").description("Get active ZAM settings (JSON
|
|
|
31168
31378
|
});
|
|
31169
31379
|
});
|
|
31170
31380
|
});
|
|
31381
|
+
bridgeCommand.command("install-repair").description(
|
|
31382
|
+
"Verify and repair this machine's ZAM installation: CLI shim + PATH, workspace skill links, agent configs and companion extensions (JSON)"
|
|
31383
|
+
).option(
|
|
31384
|
+
"--if-version-changed",
|
|
31385
|
+
"Only run when the app version differs from the last repaired one; reports skipped:true otherwise"
|
|
31386
|
+
).action((opts) => {
|
|
31387
|
+
try {
|
|
31388
|
+
jsonOut2(
|
|
31389
|
+
performInstallRepair({
|
|
31390
|
+
ifVersionChanged: Boolean(opts.ifVersionChanged)
|
|
31391
|
+
})
|
|
31392
|
+
);
|
|
31393
|
+
} catch (err) {
|
|
31394
|
+
jsonError(err.message);
|
|
31395
|
+
}
|
|
31396
|
+
});
|
|
31171
31397
|
bridgeCommand.command("agent-harness-status").description(
|
|
31172
31398
|
"Detect installed agent harnesses and their ZAM MCP configuration state (JSON)"
|
|
31173
31399
|
).action(() => {
|
|
@@ -34100,28 +34326,28 @@ var doctorCommand = new Command5("doctor").description(
|
|
|
34100
34326
|
// src/cli/commands/git-sync.ts
|
|
34101
34327
|
init_kernel();
|
|
34102
34328
|
import { execSync as execSync5 } from "child_process";
|
|
34103
|
-
import { chmodSync as
|
|
34104
|
-
import { join as
|
|
34329
|
+
import { chmodSync as chmodSync3, existsSync as existsSync22, writeFileSync as writeFileSync13 } from "fs";
|
|
34330
|
+
import { join as join24 } from "path";
|
|
34105
34331
|
import { Command as Command6 } from "commander";
|
|
34106
34332
|
function installHook2() {
|
|
34107
|
-
const gitDir =
|
|
34108
|
-
if (!
|
|
34333
|
+
const gitDir = join24(process.cwd(), ".git");
|
|
34334
|
+
if (!existsSync22(gitDir)) {
|
|
34109
34335
|
console.error(
|
|
34110
34336
|
"Error: Current directory is not the root of a Git repository."
|
|
34111
34337
|
);
|
|
34112
34338
|
process.exit(1);
|
|
34113
34339
|
}
|
|
34114
|
-
const hooksDir =
|
|
34115
|
-
const hookPath =
|
|
34340
|
+
const hooksDir = join24(gitDir, "hooks");
|
|
34341
|
+
const hookPath = join24(hooksDir, "post-commit");
|
|
34116
34342
|
const hookContent = `#!/bin/sh
|
|
34117
34343
|
# ZAM Spaced Repetition Auto-Stale Hook
|
|
34118
34344
|
# Triggered automatically on git commits to decay modified concept cards.
|
|
34119
34345
|
zam git-sync --commit HEAD --quiet
|
|
34120
34346
|
`;
|
|
34121
34347
|
try {
|
|
34122
|
-
|
|
34348
|
+
writeFileSync13(hookPath, hookContent, { encoding: "utf-8", flag: "w" });
|
|
34123
34349
|
try {
|
|
34124
|
-
|
|
34350
|
+
chmodSync3(hookPath, "755");
|
|
34125
34351
|
} catch (_e) {
|
|
34126
34352
|
}
|
|
34127
34353
|
console.log(
|
|
@@ -34222,8 +34448,8 @@ ZAM Auto-Stale Complete: Scanned ${changedFiles.length} file(s).`
|
|
|
34222
34448
|
|
|
34223
34449
|
// src/cli/commands/goal.ts
|
|
34224
34450
|
init_kernel();
|
|
34225
|
-
import { existsSync as
|
|
34226
|
-
import { resolve as
|
|
34451
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync16 } from "fs";
|
|
34452
|
+
import { resolve as resolve7 } from "path";
|
|
34227
34453
|
import { input as input2 } from "@inquirer/prompts";
|
|
34228
34454
|
import { Command as Command7 } from "commander";
|
|
34229
34455
|
async function resolveGoalsDir() {
|
|
@@ -34236,7 +34462,7 @@ async function resolveGoalsDir() {
|
|
|
34236
34462
|
} finally {
|
|
34237
34463
|
await db?.close();
|
|
34238
34464
|
}
|
|
34239
|
-
return goalsDir ?
|
|
34465
|
+
return goalsDir ? resolve7(goalsDir) : resolve7("goals");
|
|
34240
34466
|
}
|
|
34241
34467
|
var goalCommand = new Command7("goal").description(
|
|
34242
34468
|
"Manage learning goals (markdown files)"
|
|
@@ -34246,7 +34472,7 @@ goalCommand.command("list").description("List all goals").option(
|
|
|
34246
34472
|
"Filter by status (active, completed, paused, abandoned)"
|
|
34247
34473
|
).option("--tree", "Show goals as a tree with parent/child relationships").option("--json", "Output as JSON").action(async (opts) => {
|
|
34248
34474
|
const goalsDir = await resolveGoalsDir();
|
|
34249
|
-
if (!
|
|
34475
|
+
if (!existsSync23(goalsDir)) {
|
|
34250
34476
|
console.error(`Goals directory not found: ${goalsDir}`);
|
|
34251
34477
|
console.error(
|
|
34252
34478
|
"Set it with: zam settings set personal.goals_dir /path/to/goals"
|
|
@@ -34347,8 +34573,8 @@ ${"\u2500".repeat(50)}`);
|
|
|
34347
34573
|
});
|
|
34348
34574
|
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) => {
|
|
34349
34575
|
const goalsDir = await resolveGoalsDir();
|
|
34350
|
-
if (!
|
|
34351
|
-
|
|
34576
|
+
if (!existsSync23(goalsDir)) {
|
|
34577
|
+
mkdirSync16(goalsDir, { recursive: true });
|
|
34352
34578
|
}
|
|
34353
34579
|
let slug = opts.slug;
|
|
34354
34580
|
let title = opts.title;
|
|
@@ -34408,22 +34634,22 @@ goalCommand.command("status <slug> <status>").description("Update a goal's statu
|
|
|
34408
34634
|
|
|
34409
34635
|
// src/cli/commands/init.ts
|
|
34410
34636
|
init_kernel();
|
|
34411
|
-
import { existsSync as
|
|
34412
|
-
import { homedir as
|
|
34413
|
-
import { join as
|
|
34637
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync17, writeFileSync as writeFileSync14 } from "fs";
|
|
34638
|
+
import { homedir as homedir15 } from "os";
|
|
34639
|
+
import { join as join25, resolve as resolve8 } from "path";
|
|
34414
34640
|
import { confirm, input as input3 } from "@inquirer/prompts";
|
|
34415
34641
|
import { Command as Command8 } from "commander";
|
|
34416
|
-
var HOME2 =
|
|
34642
|
+
var HOME2 = homedir15();
|
|
34417
34643
|
function printLine(char = "\u2550", len = 60, color = "\x1B[36m") {
|
|
34418
34644
|
console.log(`${color}${char.repeat(len)}\x1B[0m`);
|
|
34419
34645
|
}
|
|
34420
34646
|
function bootstrapSandboxWorkspace(workspaceDir) {
|
|
34421
|
-
|
|
34422
|
-
|
|
34423
|
-
|
|
34424
|
-
const worldviewFile =
|
|
34425
|
-
if (!
|
|
34426
|
-
|
|
34647
|
+
mkdirSync17(join25(workspaceDir, "beliefs"), { recursive: true });
|
|
34648
|
+
mkdirSync17(join25(workspaceDir, "goals"), { recursive: true });
|
|
34649
|
+
mkdirSync17(join25(workspaceDir, "skills"), { recursive: true });
|
|
34650
|
+
const worldviewFile = join25(workspaceDir, "beliefs", "worldview.md");
|
|
34651
|
+
if (!existsSync24(worldviewFile)) {
|
|
34652
|
+
writeFileSync14(
|
|
34427
34653
|
worldviewFile,
|
|
34428
34654
|
`# Personal Worldview
|
|
34429
34655
|
|
|
@@ -34435,9 +34661,9 @@ Here, I declare the core concepts and principles I want to master.
|
|
|
34435
34661
|
"utf8"
|
|
34436
34662
|
);
|
|
34437
34663
|
}
|
|
34438
|
-
const goalsFile =
|
|
34439
|
-
if (!
|
|
34440
|
-
|
|
34664
|
+
const goalsFile = join25(workspaceDir, "goals", "goals.md");
|
|
34665
|
+
if (!existsSync24(goalsFile)) {
|
|
34666
|
+
writeFileSync14(
|
|
34441
34667
|
goalsFile,
|
|
34442
34668
|
`# Personal Goals
|
|
34443
34669
|
|
|
@@ -34459,8 +34685,8 @@ var initCommand = new Command8("init").description("Launch the guided interactiv
|
|
|
34459
34685
|
);
|
|
34460
34686
|
printLine();
|
|
34461
34687
|
console.log("\n\x1B[1m[1/5] Setting up Local Workspace Sandbox\x1B[0m");
|
|
34462
|
-
const defaultWorkspace =
|
|
34463
|
-
const workspacePath =
|
|
34688
|
+
const defaultWorkspace = join25(HOME2, "Documents", "zam");
|
|
34689
|
+
const workspacePath = resolve8(
|
|
34464
34690
|
await input3({
|
|
34465
34691
|
message: "Choose your ZAM workspace directory:",
|
|
34466
34692
|
default: defaultWorkspace
|
|
@@ -35661,7 +35887,7 @@ observerCommand.command("revoke <process>").description("Remove a process from o
|
|
|
35661
35887
|
|
|
35662
35888
|
// src/cli/commands/profile.ts
|
|
35663
35889
|
init_kernel();
|
|
35664
|
-
import { dirname as dirname10, resolve as
|
|
35890
|
+
import { dirname as dirname10, resolve as resolve9 } from "path";
|
|
35665
35891
|
import { Command as Command13 } from "commander";
|
|
35666
35892
|
var C2 = {
|
|
35667
35893
|
reset: "\x1B[0m",
|
|
@@ -35689,7 +35915,7 @@ var profileCommand = new Command13("profile").description("Show or change this m
|
|
|
35689
35915
|
if (opts.mode) setInstallMode(opts.mode);
|
|
35690
35916
|
db = await openDatabaseWithSync({ initialize: true });
|
|
35691
35917
|
if (opts.dir) {
|
|
35692
|
-
await activateWorkspacePath(db,
|
|
35918
|
+
await activateWorkspacePath(db, resolve9(opts.dir), {
|
|
35693
35919
|
kind: "personal",
|
|
35694
35920
|
label: "Personal"
|
|
35695
35921
|
});
|
|
@@ -36091,7 +36317,7 @@ ${"\u2550".repeat(50)}`);
|
|
|
36091
36317
|
|
|
36092
36318
|
// src/cli/commands/session.ts
|
|
36093
36319
|
init_kernel();
|
|
36094
|
-
import { readFileSync as
|
|
36320
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
36095
36321
|
import { input as input6, select as select2 } from "@inquirer/prompts";
|
|
36096
36322
|
import { Command as Command16 } from "commander";
|
|
36097
36323
|
var sessionCommand = new Command16("session").description(
|
|
@@ -36282,7 +36508,7 @@ function loadPatternFile(path) {
|
|
|
36282
36508
|
if (!path) return [];
|
|
36283
36509
|
let parsed;
|
|
36284
36510
|
try {
|
|
36285
|
-
parsed = JSON.parse(
|
|
36511
|
+
parsed = JSON.parse(readFileSync18(path, "utf-8"));
|
|
36286
36512
|
} catch (err) {
|
|
36287
36513
|
throw new Error(
|
|
36288
36514
|
`Cannot read synthesis patterns from ${path}: ${err.message}`
|
|
@@ -36473,7 +36699,7 @@ sessionCommand.command("end").description("End a session and show summary").requ
|
|
|
36473
36699
|
|
|
36474
36700
|
// src/cli/commands/settings.ts
|
|
36475
36701
|
init_kernel();
|
|
36476
|
-
import { existsSync as
|
|
36702
|
+
import { existsSync as existsSync25 } from "fs";
|
|
36477
36703
|
import { Command as Command17 } from "commander";
|
|
36478
36704
|
var settingsCommand = new Command17("settings").description(
|
|
36479
36705
|
"Manage user settings"
|
|
@@ -36638,7 +36864,7 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
|
|
|
36638
36864
|
console.log("\nValidation:");
|
|
36639
36865
|
for (const [name, path] of Object.entries(paths)) {
|
|
36640
36866
|
if (path) {
|
|
36641
|
-
const exists =
|
|
36867
|
+
const exists = existsSync25(path);
|
|
36642
36868
|
console.log(
|
|
36643
36869
|
` ${name.padEnd(9)}: ${exists ? "\x1B[32m\u2713 Valid folder\x1B[0m" : "\x1B[31m\u2717 Directory does not exist\x1B[0m"}`
|
|
36644
36870
|
);
|
|
@@ -36651,7 +36877,7 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
|
|
|
36651
36877
|
|
|
36652
36878
|
// src/cli/commands/setup.ts
|
|
36653
36879
|
init_kernel();
|
|
36654
|
-
import { resolve as
|
|
36880
|
+
import { resolve as resolve10 } from "path";
|
|
36655
36881
|
import { Command as Command18 } from "commander";
|
|
36656
36882
|
function formatDatabaseInitTarget(target) {
|
|
36657
36883
|
switch (target.kind) {
|
|
@@ -36751,7 +36977,7 @@ var setupCommand = new Command18("setup").description(
|
|
|
36751
36977
|
console.error(`Error: ${err.message}`);
|
|
36752
36978
|
process.exit(1);
|
|
36753
36979
|
}
|
|
36754
|
-
const target =
|
|
36980
|
+
const target = resolve10(opts.target ?? process.cwd());
|
|
36755
36981
|
const updateExistingInstructions = Boolean(opts.target) || opts.force;
|
|
36756
36982
|
console.log(
|
|
36757
36983
|
`Setting up ZAM in ${target}${opts.dryRun ? " (dry run)" : ""}
|
|
@@ -36876,8 +37102,8 @@ skillCommand.command("add").description("Register a new agent skill").requiredOp
|
|
|
36876
37102
|
|
|
36877
37103
|
// src/cli/commands/snapshot.ts
|
|
36878
37104
|
init_kernel();
|
|
36879
|
-
import { existsSync as
|
|
36880
|
-
import { dirname as dirname11, join as
|
|
37105
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync18, readFileSync as readFileSync19, writeFileSync as writeFileSync15 } from "fs";
|
|
37106
|
+
import { dirname as dirname11, join as join26 } from "path";
|
|
36881
37107
|
import { Command as Command20 } from "commander";
|
|
36882
37108
|
function defaultOutName() {
|
|
36883
37109
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
|
|
@@ -36906,12 +37132,12 @@ var exportCmd = new Command20("export").description("Write a portable SQL-text s
|
|
|
36906
37132
|
process.stdout.write(snapshot);
|
|
36907
37133
|
return;
|
|
36908
37134
|
}
|
|
36909
|
-
const out = opts.out ??
|
|
37135
|
+
const out = opts.out ?? join26(personalDir, "snapshots", defaultOutName());
|
|
36910
37136
|
const dir = dirname11(out);
|
|
36911
|
-
if (dir && dir !== "." && !
|
|
36912
|
-
|
|
37137
|
+
if (dir && dir !== "." && !existsSync26(dir)) {
|
|
37138
|
+
mkdirSync18(dir, { recursive: true });
|
|
36913
37139
|
}
|
|
36914
|
-
|
|
37140
|
+
writeFileSync15(out, snapshot, "utf-8");
|
|
36915
37141
|
console.log(`Snapshot written: ${out}`);
|
|
36916
37142
|
console.log(
|
|
36917
37143
|
` ${total} row(s)${nonEmpty.length ? ` \u2014 ${nonEmpty.join(", ")}` : ""}`
|
|
@@ -36925,11 +37151,11 @@ var exportCmd = new Command20("export").description("Write a portable SQL-text s
|
|
|
36925
37151
|
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) => {
|
|
36926
37152
|
let db;
|
|
36927
37153
|
try {
|
|
36928
|
-
if (!
|
|
37154
|
+
if (!existsSync26(file)) {
|
|
36929
37155
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
36930
37156
|
process.exit(1);
|
|
36931
37157
|
}
|
|
36932
|
-
const snapshot =
|
|
37158
|
+
const snapshot = readFileSync19(file, "utf-8");
|
|
36933
37159
|
db = await openDatabaseWithSync({ initialize: true });
|
|
36934
37160
|
const result = await importSnapshot(db, snapshot, { force: opts.force });
|
|
36935
37161
|
await db.close();
|
|
@@ -36947,11 +37173,11 @@ var importCmd = new Command20("import").description("Restore a snapshot into the
|
|
|
36947
37173
|
});
|
|
36948
37174
|
var verifyCmd = new Command20("verify").description("Check a snapshot's manifest and checksum without importing").argument("<file>", "Snapshot file to verify").action((file) => {
|
|
36949
37175
|
try {
|
|
36950
|
-
if (!
|
|
37176
|
+
if (!existsSync26(file)) {
|
|
36951
37177
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
36952
37178
|
process.exit(1);
|
|
36953
37179
|
}
|
|
36954
|
-
const manifest = verifySnapshot(
|
|
37180
|
+
const manifest = verifySnapshot(readFileSync19(file, "utf-8"));
|
|
36955
37181
|
const { total, nonEmpty } = summarize(manifest.tables);
|
|
36956
37182
|
console.log(`Valid snapshot (format v${manifest.version})`);
|
|
36957
37183
|
console.log(` created: ${manifest.createdAt}`);
|
|
@@ -37500,9 +37726,9 @@ tokenCommand.command("reembed").description("Backfill or refresh semantic-search
|
|
|
37500
37726
|
// src/cli/commands/ui.ts
|
|
37501
37727
|
init_kernel();
|
|
37502
37728
|
import { spawn as spawn3, spawnSync } from "child_process";
|
|
37503
|
-
import { existsSync as
|
|
37504
|
-
import { homedir as
|
|
37505
|
-
import { dirname as dirname12, join as
|
|
37729
|
+
import { existsSync as existsSync27 } from "fs";
|
|
37730
|
+
import { homedir as homedir16 } from "os";
|
|
37731
|
+
import { dirname as dirname12, join as join27 } from "path";
|
|
37506
37732
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
37507
37733
|
import { Command as Command23 } from "commander";
|
|
37508
37734
|
var C3 = {
|
|
@@ -37518,8 +37744,8 @@ function findDesktopDir() {
|
|
|
37518
37744
|
for (const start of starts) {
|
|
37519
37745
|
let dir = start;
|
|
37520
37746
|
for (let i = 0; i < 10; i++) {
|
|
37521
|
-
if (
|
|
37522
|
-
return
|
|
37747
|
+
if (existsSync27(join27(dir, "desktop", "src-tauri", "tauri.conf.json"))) {
|
|
37748
|
+
return join27(dir, "desktop");
|
|
37523
37749
|
}
|
|
37524
37750
|
const parent = dirname12(dir);
|
|
37525
37751
|
if (parent === dir) break;
|
|
@@ -37529,30 +37755,30 @@ function findDesktopDir() {
|
|
|
37529
37755
|
return null;
|
|
37530
37756
|
}
|
|
37531
37757
|
function findBuiltApp(desktopDir) {
|
|
37532
|
-
const releaseDir =
|
|
37758
|
+
const releaseDir = join27(desktopDir, "src-tauri", "target", "release");
|
|
37533
37759
|
if (process.platform === "win32") {
|
|
37534
37760
|
for (const name of ["ZAM.exe", "zam.exe", "zam-desktop.exe"]) {
|
|
37535
|
-
const p =
|
|
37536
|
-
if (
|
|
37761
|
+
const p = join27(releaseDir, name);
|
|
37762
|
+
if (existsSync27(p)) return p;
|
|
37537
37763
|
}
|
|
37538
37764
|
} else if (process.platform === "darwin") {
|
|
37539
|
-
const app =
|
|
37540
|
-
if (
|
|
37765
|
+
const app = join27(releaseDir, "bundle", "macos", "ZAM.app");
|
|
37766
|
+
if (existsSync27(app)) return app;
|
|
37541
37767
|
} else {
|
|
37542
37768
|
for (const name of ["zam", "ZAM", "zam-desktop"]) {
|
|
37543
|
-
const p =
|
|
37544
|
-
if (
|
|
37769
|
+
const p = join27(releaseDir, name);
|
|
37770
|
+
if (existsSync27(p)) return p;
|
|
37545
37771
|
}
|
|
37546
37772
|
}
|
|
37547
37773
|
return null;
|
|
37548
37774
|
}
|
|
37549
37775
|
function findInstalledApp() {
|
|
37550
37776
|
const candidates = process.platform === "win32" ? [
|
|
37551
|
-
process.env.LOCALAPPDATA &&
|
|
37552
|
-
process.env.ProgramFiles &&
|
|
37553
|
-
process.env["ProgramFiles(x86)"] &&
|
|
37554
|
-
] : process.platform === "darwin" ? ["/Applications/ZAM.app",
|
|
37555
|
-
return candidates.find((candidate) => candidate &&
|
|
37777
|
+
process.env.LOCALAPPDATA && join27(process.env.LOCALAPPDATA, "Programs", "ZAM", "ZAM.exe"),
|
|
37778
|
+
process.env.ProgramFiles && join27(process.env.ProgramFiles, "ZAM", "ZAM.exe"),
|
|
37779
|
+
process.env["ProgramFiles(x86)"] && join27(process.env["ProgramFiles(x86)"], "ZAM", "ZAM.exe")
|
|
37780
|
+
] : process.platform === "darwin" ? ["/Applications/ZAM.app", join27(homedir16(), "Applications", "ZAM.app")] : ["/opt/ZAM/zam", "/usr/bin/zam-desktop"];
|
|
37781
|
+
return candidates.find((candidate) => candidate && existsSync27(candidate)) || null;
|
|
37556
37782
|
}
|
|
37557
37783
|
function runNpm(args, opts) {
|
|
37558
37784
|
const res = spawnSync("npm", args, {
|
|
@@ -37563,7 +37789,7 @@ function runNpm(args, opts) {
|
|
|
37563
37789
|
return res.status ?? 1;
|
|
37564
37790
|
}
|
|
37565
37791
|
function ensureDesktopDeps(desktopDir) {
|
|
37566
|
-
if (
|
|
37792
|
+
if (existsSync27(join27(desktopDir, "node_modules"))) return true;
|
|
37567
37793
|
console.log(
|
|
37568
37794
|
`${C3.cyan}Installing desktop dependencies (one-time)...${C3.reset}`
|
|
37569
37795
|
);
|
|
@@ -37589,13 +37815,13 @@ function requireRust() {
|
|
|
37589
37815
|
function hasMsvcBuildTools() {
|
|
37590
37816
|
if (process.platform !== "win32") return true;
|
|
37591
37817
|
const pf86 = process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)";
|
|
37592
|
-
const vswhere =
|
|
37818
|
+
const vswhere = join27(
|
|
37593
37819
|
pf86,
|
|
37594
37820
|
"Microsoft Visual Studio",
|
|
37595
37821
|
"Installer",
|
|
37596
37822
|
"vswhere.exe"
|
|
37597
37823
|
);
|
|
37598
|
-
if (!
|
|
37824
|
+
if (!existsSync27(vswhere)) return false;
|
|
37599
37825
|
const res = spawnSync(
|
|
37600
37826
|
vswhere,
|
|
37601
37827
|
[
|
|
@@ -37627,7 +37853,7 @@ function requireMsvcOnWindows() {
|
|
|
37627
37853
|
return false;
|
|
37628
37854
|
}
|
|
37629
37855
|
function warnIfCliMissing(repoRoot) {
|
|
37630
|
-
if (!
|
|
37856
|
+
if (!existsSync27(join27(repoRoot, "dist", "cli", "index.js"))) {
|
|
37631
37857
|
console.warn(
|
|
37632
37858
|
`${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}`
|
|
37633
37859
|
);
|
|
@@ -37690,7 +37916,7 @@ var uiCommand = new Command23("ui").description("Launch the ZAM Desktop GUI (Act
|
|
|
37690
37916
|
).option("--shortcut", "Create Desktop + Start-menu shortcuts to the GUI").action((opts) => {
|
|
37691
37917
|
const installedApp = findInstalledApp();
|
|
37692
37918
|
if (!opts.dev && !opts.build && !opts.shortcut && installedApp) {
|
|
37693
|
-
launchApp(installedApp,
|
|
37919
|
+
launchApp(installedApp, homedir16());
|
|
37694
37920
|
return;
|
|
37695
37921
|
}
|
|
37696
37922
|
const desktopDir = findDesktopDir();
|
|
@@ -37711,7 +37937,7 @@ var uiCommand = new Command23("ui").description("Launch the ZAM Desktop GUI (Act
|
|
|
37711
37937
|
);
|
|
37712
37938
|
const code = runNpm(["run", "tauri", "build"], { cwd: desktopDir });
|
|
37713
37939
|
if (code === 0) {
|
|
37714
|
-
const bundle =
|
|
37940
|
+
const bundle = join27(
|
|
37715
37941
|
desktopDir,
|
|
37716
37942
|
"src-tauri",
|
|
37717
37943
|
"target",
|
|
@@ -37789,8 +38015,8 @@ ${C3.dim}After that, 'zam ui' launches it directly.${C3.reset}`
|
|
|
37789
38015
|
// src/cli/commands/update.ts
|
|
37790
38016
|
init_kernel();
|
|
37791
38017
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
37792
|
-
import { existsSync as
|
|
37793
|
-
import { dirname as dirname13, join as
|
|
38018
|
+
import { existsSync as existsSync28, readFileSync as readFileSync20, realpathSync as realpathSync2 } from "fs";
|
|
38019
|
+
import { dirname as dirname13, join as join28 } from "path";
|
|
37794
38020
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
37795
38021
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
37796
38022
|
import { Command as Command24 } from "commander";
|
|
@@ -37812,7 +38038,7 @@ var C4 = {
|
|
|
37812
38038
|
function versionAt(dir) {
|
|
37813
38039
|
try {
|
|
37814
38040
|
const pkg2 = JSON.parse(
|
|
37815
|
-
|
|
38041
|
+
readFileSync20(join28(dir, "package.json"), "utf-8")
|
|
37816
38042
|
);
|
|
37817
38043
|
return pkg2.version ?? "unknown";
|
|
37818
38044
|
} catch {
|
|
@@ -37873,11 +38099,11 @@ function findSourceRepo() {
|
|
|
37873
38099
|
let dir = realpathSync2(dirname13(fileURLToPath7(import.meta.url)));
|
|
37874
38100
|
let parent = dirname13(dir);
|
|
37875
38101
|
while (parent !== dir) {
|
|
37876
|
-
if (
|
|
38102
|
+
if (existsSync28(join28(dir, ".git"))) return dir;
|
|
37877
38103
|
dir = parent;
|
|
37878
38104
|
parent = dirname13(dir);
|
|
37879
38105
|
}
|
|
37880
|
-
return
|
|
38106
|
+
return existsSync28(join28(dir, ".git")) ? dir : null;
|
|
37881
38107
|
}
|
|
37882
38108
|
function runGit(cwd, args, capture) {
|
|
37883
38109
|
const res = spawnSync2("git", args, {
|
|
@@ -37898,7 +38124,7 @@ function runNpm2(args, cwd) {
|
|
|
37898
38124
|
function smokeTestBuild(src) {
|
|
37899
38125
|
const res = spawnSync2(
|
|
37900
38126
|
process.execPath,
|
|
37901
|
-
[
|
|
38127
|
+
[join28(src, "dist", "cli", "index.js"), "--version"],
|
|
37902
38128
|
{
|
|
37903
38129
|
cwd: src,
|
|
37904
38130
|
encoding: "utf8",
|
|
@@ -37978,7 +38204,7 @@ Fix it manually in ${src} (try \`npm ci && npm run build\`, and check your Node
|
|
|
37978
38204
|
console.log(`${C4.dim}\u2192 zam setup --force${C4.reset}`);
|
|
37979
38205
|
const setup = spawnSync2(
|
|
37980
38206
|
process.execPath,
|
|
37981
|
-
[
|
|
38207
|
+
[join28(src, "dist", "cli", "index.js"), "setup", "--force"],
|
|
37982
38208
|
{ cwd: process.cwd(), stdio: "inherit" }
|
|
37983
38209
|
);
|
|
37984
38210
|
if (setup.status !== 0) {
|
|
@@ -38090,15 +38316,15 @@ var whoamiCommand = new Command25("whoami").description("Show or set the default
|
|
|
38090
38316
|
|
|
38091
38317
|
// src/cli/commands/workspace.ts
|
|
38092
38318
|
init_kernel();
|
|
38093
|
-
import { execFileSync as
|
|
38094
|
-
import { existsSync as
|
|
38095
|
-
import { homedir as
|
|
38096
|
-
import { join as
|
|
38319
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
38320
|
+
import { existsSync as existsSync29, writeFileSync as writeFileSync16 } from "fs";
|
|
38321
|
+
import { homedir as homedir17 } from "os";
|
|
38322
|
+
import { join as join29, resolve as resolve11 } from "path";
|
|
38097
38323
|
import { confirm as confirm4, input as input7 } from "@inquirer/prompts";
|
|
38098
38324
|
import { Command as Command26 } from "commander";
|
|
38099
38325
|
function runGit2(cwd, args) {
|
|
38100
38326
|
try {
|
|
38101
|
-
return
|
|
38327
|
+
return execFileSync6("git", args, {
|
|
38102
38328
|
cwd,
|
|
38103
38329
|
stdio: "pipe",
|
|
38104
38330
|
encoding: "utf8"
|
|
@@ -38177,7 +38403,7 @@ workspaceCommand.command("list").description("List configured ZAM workspaces").o
|
|
|
38177
38403
|
const workspaces = getConfiguredWorkspaces();
|
|
38178
38404
|
const agents = parseSetupAgents();
|
|
38179
38405
|
const linkHealth = Object.fromEntries(
|
|
38180
|
-
workspaces.filter((workspace) =>
|
|
38406
|
+
workspaces.filter((workspace) => existsSync29(workspace.path)).map((workspace) => [
|
|
38181
38407
|
workspace.id,
|
|
38182
38408
|
summarizeSkillLinkHealth(inspectSkillLinks(workspace.path, agents))
|
|
38183
38409
|
])
|
|
@@ -38219,8 +38445,8 @@ workspaceCommand.command("add <id>").description("Register an existing directory
|
|
|
38219
38445
|
`Source-control provider (${WORKSPACE_SOURCE_CONTROLS.join(" | ")})`
|
|
38220
38446
|
).option("--scopes <list>", "Comma-separated knowledge scopes").option("--default-agent <id>", "Default agent harness for this workspace").action((id, opts) => {
|
|
38221
38447
|
try {
|
|
38222
|
-
const path =
|
|
38223
|
-
if (!
|
|
38448
|
+
const path = resolve11(String(opts.path));
|
|
38449
|
+
if (!existsSync29(path)) {
|
|
38224
38450
|
console.error(`Workspace path does not exist: ${path}`);
|
|
38225
38451
|
process.exit(1);
|
|
38226
38452
|
}
|
|
@@ -38314,7 +38540,7 @@ workspaceCommand.command("publish").description("Publish your local workspace sa
|
|
|
38314
38540
|
);
|
|
38315
38541
|
process.exit(1);
|
|
38316
38542
|
}
|
|
38317
|
-
if (!
|
|
38543
|
+
if (!existsSync29(workspaceDir)) {
|
|
38318
38544
|
console.error(
|
|
38319
38545
|
`\x1B[31m\u2717 Workspace directory does not exist: ${workspaceDir}\x1B[0m`
|
|
38320
38546
|
);
|
|
@@ -38328,15 +38554,15 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
|
38328
38554
|
);
|
|
38329
38555
|
process.exit(1);
|
|
38330
38556
|
}
|
|
38331
|
-
const gitignorePath =
|
|
38332
|
-
if (!
|
|
38333
|
-
|
|
38557
|
+
const gitignorePath = join29(workspaceDir, ".gitignore");
|
|
38558
|
+
if (!existsSync29(gitignorePath)) {
|
|
38559
|
+
writeFileSync16(
|
|
38334
38560
|
gitignorePath,
|
|
38335
38561
|
"node_modules/\n.agent/\n.agents/\n.claude/\n.gemini/\n.goose/\n*.log\n",
|
|
38336
38562
|
"utf8"
|
|
38337
38563
|
);
|
|
38338
38564
|
}
|
|
38339
|
-
const hasGitRepo =
|
|
38565
|
+
const hasGitRepo = existsSync29(join29(workspaceDir, ".git"));
|
|
38340
38566
|
if (!hasGitRepo) {
|
|
38341
38567
|
console.log("Initializing local Git repository...");
|
|
38342
38568
|
runGit2(workspaceDir, ["init", "-b", "main"]);
|
|
@@ -38368,7 +38594,7 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
|
38368
38594
|
if (proceedGh) {
|
|
38369
38595
|
try {
|
|
38370
38596
|
console.log(`Creating GitHub repository ${repoName}...`);
|
|
38371
|
-
|
|
38597
|
+
execFileSync6("gh", ghRepoCreateArgs(repoName, repoVisibility), {
|
|
38372
38598
|
cwd: workspaceDir,
|
|
38373
38599
|
stdio: "inherit"
|
|
38374
38600
|
});
|
|
@@ -38423,7 +38649,7 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
|
38423
38649
|
}
|
|
38424
38650
|
});
|
|
38425
38651
|
workspaceCommand.command("data-dir").description("Print the ZAM data directory (database, credentials, config)").option("--json", "Output as JSON").action((opts) => {
|
|
38426
|
-
const dir =
|
|
38652
|
+
const dir = join29(homedir17(), ".zam");
|
|
38427
38653
|
console.log(opts.json ? JSON.stringify({ dataDir: dir }) : dir);
|
|
38428
38654
|
});
|
|
38429
38655
|
workspaceCommand.command("backup").description("Back up the local ZAM database into your workspace").option(
|
|
@@ -38466,7 +38692,7 @@ workspaceCommand.command("backup").description("Back up the local ZAM database i
|
|
|
38466
38692
|
// src/cli/app.ts
|
|
38467
38693
|
var __dirname = dirname14(fileURLToPath8(import.meta.url));
|
|
38468
38694
|
var pkg = JSON.parse(
|
|
38469
|
-
|
|
38695
|
+
readFileSync21(join30(__dirname, "..", "..", "package.json"), "utf-8")
|
|
38470
38696
|
);
|
|
38471
38697
|
var program = new Command27();
|
|
38472
38698
|
program.name("zam").description(
|