zam-core 0.4.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1053 -369
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +39 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
|
-
import { readFileSync as
|
|
4
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
5
5
|
import { dirname as dirname10, join as join24 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
7
7
|
import { Command as Command25 } from "commander";
|
|
@@ -4422,8 +4422,10 @@ function t(locale, key, params = {}) {
|
|
|
4422
4422
|
import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
4423
4423
|
import { homedir as homedir6 } from "os";
|
|
4424
4424
|
import { dirname as dirname4, join as join9 } from "path";
|
|
4425
|
-
|
|
4426
|
-
|
|
4425
|
+
function defaultConfigPath() {
|
|
4426
|
+
return process.env.ZAM_CONFIG_PATH || join9(homedir6(), ".zam", "config.json");
|
|
4427
|
+
}
|
|
4428
|
+
function loadInstallConfig(path = defaultConfigPath()) {
|
|
4427
4429
|
if (!existsSync8(path)) return {};
|
|
4428
4430
|
try {
|
|
4429
4431
|
return JSON.parse(readFileSync8(path, "utf-8"));
|
|
@@ -4431,25 +4433,50 @@ function loadInstallConfig(path = DEFAULT_CONFIG_PATH) {
|
|
|
4431
4433
|
return {};
|
|
4432
4434
|
}
|
|
4433
4435
|
}
|
|
4434
|
-
function saveInstallConfig(config, path =
|
|
4436
|
+
function saveInstallConfig(config, path = defaultConfigPath()) {
|
|
4435
4437
|
const dir = dirname4(path);
|
|
4436
4438
|
if (!existsSync8(dir)) mkdirSync7(dir, { recursive: true });
|
|
4437
4439
|
writeFileSync5(path, `${JSON.stringify(config, null, 2)}
|
|
4438
4440
|
`, "utf-8");
|
|
4439
4441
|
}
|
|
4440
|
-
function getInstallMode(path =
|
|
4442
|
+
function getInstallMode(path = defaultConfigPath()) {
|
|
4441
4443
|
return loadInstallConfig(path).mode ?? "developer";
|
|
4442
4444
|
}
|
|
4443
|
-
function setInstallMode(mode, path =
|
|
4445
|
+
function setInstallMode(mode, path = defaultConfigPath()) {
|
|
4444
4446
|
const config = loadInstallConfig(path);
|
|
4445
4447
|
config.mode = mode;
|
|
4446
4448
|
saveInstallConfig(config, path);
|
|
4447
4449
|
}
|
|
4448
|
-
function getInstallChannel(path =
|
|
4450
|
+
function getInstallChannel(path = defaultConfigPath()) {
|
|
4449
4451
|
const config = loadInstallConfig(path);
|
|
4450
4452
|
if (config.channel) return config.channel;
|
|
4451
4453
|
return (config.mode ?? "developer") === "developer" ? "developer" : "direct";
|
|
4452
4454
|
}
|
|
4455
|
+
function getMachineAiConfig(path = defaultConfigPath()) {
|
|
4456
|
+
return loadInstallConfig(path).ai ?? {};
|
|
4457
|
+
}
|
|
4458
|
+
function saveMachineAiConfig(ai, path = defaultConfigPath()) {
|
|
4459
|
+
const config = loadInstallConfig(path);
|
|
4460
|
+
config.ai = ai;
|
|
4461
|
+
saveInstallConfig(config, path);
|
|
4462
|
+
}
|
|
4463
|
+
function getConfiguredWorkspaces(path = defaultConfigPath()) {
|
|
4464
|
+
return loadInstallConfig(path).workspaces ?? [];
|
|
4465
|
+
}
|
|
4466
|
+
function saveConfiguredWorkspaces(workspaces, path = defaultConfigPath()) {
|
|
4467
|
+
const config = loadInstallConfig(path);
|
|
4468
|
+
config.workspaces = workspaces;
|
|
4469
|
+
saveInstallConfig(config, path);
|
|
4470
|
+
}
|
|
4471
|
+
function upsertConfiguredWorkspace(workspace, path = defaultConfigPath()) {
|
|
4472
|
+
const current = getConfiguredWorkspaces(path);
|
|
4473
|
+
const next = [
|
|
4474
|
+
...current.filter((candidate) => candidate.id !== workspace.id),
|
|
4475
|
+
workspace
|
|
4476
|
+
];
|
|
4477
|
+
saveConfiguredWorkspaces(next, path);
|
|
4478
|
+
return next;
|
|
4479
|
+
}
|
|
4453
4480
|
function detectSyncProvider(dir) {
|
|
4454
4481
|
const p = dir.toLowerCase();
|
|
4455
4482
|
if (p.includes("onedrive")) return "OneDrive";
|
|
@@ -5031,7 +5058,7 @@ function isItermRunning() {
|
|
|
5031
5058
|
return false;
|
|
5032
5059
|
}
|
|
5033
5060
|
}
|
|
5034
|
-
function openMacTerminal(shellSetup, label, dir) {
|
|
5061
|
+
function openMacTerminal(shellSetup, label, dir, silent) {
|
|
5035
5062
|
const useIterm = isItermRunning();
|
|
5036
5063
|
const escaped = shellSetup.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
5037
5064
|
const appleScript = useIterm ? `tell application "iTerm2"
|
|
@@ -5048,16 +5075,20 @@ end tell`;
|
|
|
5048
5075
|
try {
|
|
5049
5076
|
writeFileSync6(tmpFile, appleScript);
|
|
5050
5077
|
execSync4(`osascript ${JSON.stringify(tmpFile)}`, { stdio: "ignore" });
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5078
|
+
if (!silent) {
|
|
5079
|
+
console.log(
|
|
5080
|
+
`Opened ${useIterm ? "iTerm2" : "Terminal.app"} window (${label})`
|
|
5081
|
+
);
|
|
5082
|
+
console.log(` Directory: ${dir}`);
|
|
5083
|
+
}
|
|
5055
5084
|
} catch (err) {
|
|
5056
|
-
|
|
5057
|
-
|
|
5085
|
+
if (!silent) {
|
|
5086
|
+
console.error(`Failed to open terminal: ${err.message}`);
|
|
5087
|
+
console.log(`
|
|
5058
5088
|
Run this manually in a new terminal:
|
|
5059
5089
|
`);
|
|
5060
|
-
|
|
5090
|
+
console.log(` ${shellSetup}`);
|
|
5091
|
+
}
|
|
5061
5092
|
} finally {
|
|
5062
5093
|
try {
|
|
5063
5094
|
unlinkSync(tmpFile);
|
|
@@ -5065,7 +5096,7 @@ Run this manually in a new terminal:
|
|
|
5065
5096
|
}
|
|
5066
5097
|
}
|
|
5067
5098
|
}
|
|
5068
|
-
function openWindowsPowerShell(shellSetup, label, dir, requestedShell) {
|
|
5099
|
+
function openWindowsPowerShell(shellSetup, label, dir, requestedShell, silent) {
|
|
5069
5100
|
const requestedExecutable = requestedShell === "powershell" ? "powershell.exe" : "pwsh.exe";
|
|
5070
5101
|
const executable = findExecutable(requestedExecutable) ? requestedExecutable : "powershell.exe";
|
|
5071
5102
|
const startCommand = [
|
|
@@ -5082,35 +5113,42 @@ function openWindowsPowerShell(shellSetup, label, dir, requestedShell) {
|
|
|
5082
5113
|
stdio: "ignore"
|
|
5083
5114
|
}
|
|
5084
5115
|
);
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5116
|
+
if (!silent) {
|
|
5117
|
+
console.log(
|
|
5118
|
+
`Opened ${executable === "pwsh.exe" ? "PowerShell" : "Windows PowerShell"} window (${label})`
|
|
5119
|
+
);
|
|
5120
|
+
console.log(` Directory: ${dir}`);
|
|
5121
|
+
}
|
|
5089
5122
|
} catch (err) {
|
|
5090
|
-
|
|
5091
|
-
|
|
5123
|
+
if (!silent) {
|
|
5124
|
+
console.error(`Failed to open PowerShell: ${err.message}`);
|
|
5125
|
+
console.log(`
|
|
5092
5126
|
Run this manually in a new PowerShell terminal:
|
|
5093
5127
|
`);
|
|
5094
|
-
|
|
5128
|
+
console.log(` ${shellSetup}`);
|
|
5129
|
+
}
|
|
5095
5130
|
}
|
|
5096
5131
|
}
|
|
5097
5132
|
function openTerminalWindow(opts) {
|
|
5098
|
-
const { shellSetup, label, dir, shell } = opts;
|
|
5099
|
-
|
|
5100
|
-
|
|
5133
|
+
const { shellSetup, label, dir, shell, silent = false } = opts;
|
|
5134
|
+
const platform = opts.platform ?? process.platform;
|
|
5135
|
+
if (platform === "darwin" && !isPowerShellShell(shell)) {
|
|
5136
|
+
openMacTerminal(shellSetup, label, dir, silent);
|
|
5101
5137
|
return;
|
|
5102
5138
|
}
|
|
5103
|
-
if (
|
|
5104
|
-
openWindowsPowerShell(shellSetup, label, dir, shell);
|
|
5139
|
+
if (platform === "win32" && isPowerShellShell(shell)) {
|
|
5140
|
+
openWindowsPowerShell(shellSetup, label, dir, shell, silent);
|
|
5105
5141
|
return;
|
|
5106
5142
|
}
|
|
5107
|
-
|
|
5143
|
+
if (!silent) {
|
|
5144
|
+
console.log(`Run this in a new terminal:
|
|
5108
5145
|
`);
|
|
5109
|
-
|
|
5146
|
+
console.log(` ${shellSetup}
|
|
5110
5147
|
`);
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5148
|
+
console.log(
|
|
5149
|
+
`(Automatic terminal opening is only supported on macOS Terminal/iTerm2 and Windows PowerShell for now.)`
|
|
5150
|
+
);
|
|
5151
|
+
}
|
|
5114
5152
|
}
|
|
5115
5153
|
|
|
5116
5154
|
// src/cli/agent-harness.ts
|
|
@@ -5176,7 +5214,9 @@ function launchHarness(harness, opts) {
|
|
|
5176
5214
|
shellSetup: plan.shellSetup,
|
|
5177
5215
|
label: `agent-${harness.id}`,
|
|
5178
5216
|
dir: opts.workspace,
|
|
5179
|
-
shell: plan.shell
|
|
5217
|
+
shell: plan.shell,
|
|
5218
|
+
silent: opts.silent,
|
|
5219
|
+
platform: opts.platform
|
|
5180
5220
|
});
|
|
5181
5221
|
return;
|
|
5182
5222
|
}
|
|
@@ -5185,7 +5225,9 @@ function launchHarness(harness, opts) {
|
|
|
5185
5225
|
stdio: "ignore",
|
|
5186
5226
|
windowsHide: true
|
|
5187
5227
|
}).unref();
|
|
5188
|
-
|
|
5228
|
+
if (!opts.silent) {
|
|
5229
|
+
console.log(`Launched ${harness.label} in ${opts.workspace}`);
|
|
5230
|
+
}
|
|
5189
5231
|
}
|
|
5190
5232
|
|
|
5191
5233
|
// src/cli/commands/agent.ts
|
|
@@ -5310,10 +5352,10 @@ var agentCommand = new Command("agent").description("Provision and inspect the a
|
|
|
5310
5352
|
// src/cli/commands/bridge.ts
|
|
5311
5353
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
5312
5354
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
5313
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
5355
|
+
import { existsSync as existsSync17, readdirSync as readdirSync2, readFileSync as readFileSync11, rmSync as rmSync2 } from "fs";
|
|
5314
5356
|
import { homedir as homedir10, tmpdir as tmpdir3 } from "os";
|
|
5315
|
-
import { join as
|
|
5316
|
-
import { Command as
|
|
5357
|
+
import { basename as basename5, join as join17, resolve as resolve5 } from "path";
|
|
5358
|
+
import { Command as Command4 } from "commander";
|
|
5317
5359
|
|
|
5318
5360
|
// src/cli/llm/client.ts
|
|
5319
5361
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -5379,14 +5421,44 @@ async function getProviderForRole(db, role) {
|
|
|
5379
5421
|
const providers = await readJsonSetting(db, "llm.providers");
|
|
5380
5422
|
const roles = await readJsonSetting(db, "llm.roles");
|
|
5381
5423
|
const binding = roles?.[role];
|
|
5424
|
+
let resolved = base;
|
|
5382
5425
|
if (providers && binding?.primary && providers[binding.primary]) {
|
|
5383
|
-
const primary = materializeProvider(
|
|
5384
|
-
|
|
5426
|
+
const primary = materializeProvider(
|
|
5427
|
+
providers[binding.primary],
|
|
5428
|
+
base,
|
|
5429
|
+
role,
|
|
5430
|
+
{
|
|
5431
|
+
providerName: binding.primary,
|
|
5432
|
+
source: "shared"
|
|
5433
|
+
}
|
|
5434
|
+
);
|
|
5435
|
+
const fallback = binding.fallback && providers[binding.fallback] ? materializeProvider(providers[binding.fallback], base, role, {
|
|
5436
|
+
providerName: binding.fallback,
|
|
5437
|
+
source: "shared"
|
|
5438
|
+
}) : void 0;
|
|
5439
|
+
resolved = { ...primary, fallback };
|
|
5440
|
+
}
|
|
5441
|
+
const machine = getMachineAiConfig();
|
|
5442
|
+
const machineProviders = machine.providers;
|
|
5443
|
+
const machineBinding = machine.roles?.[role];
|
|
5444
|
+
if (machineProviders && machineBinding?.primary && machineProviders[machineBinding.primary]) {
|
|
5445
|
+
const primary = materializeProvider(
|
|
5446
|
+
machineProviders[machineBinding.primary],
|
|
5447
|
+
resolved,
|
|
5448
|
+
role,
|
|
5449
|
+
{ providerName: machineBinding.primary, source: "machine" }
|
|
5450
|
+
);
|
|
5451
|
+
const fallback = machineBinding.fallback && machineProviders[machineBinding.fallback] ? materializeProvider(
|
|
5452
|
+
machineProviders[machineBinding.fallback],
|
|
5453
|
+
resolved,
|
|
5454
|
+
role,
|
|
5455
|
+
{ providerName: machineBinding.fallback, source: "machine" }
|
|
5456
|
+
) : void 0;
|
|
5385
5457
|
return { ...primary, fallback };
|
|
5386
5458
|
}
|
|
5387
|
-
return
|
|
5459
|
+
return resolved;
|
|
5388
5460
|
}
|
|
5389
|
-
function materializeProvider(rec, base, role) {
|
|
5461
|
+
function materializeProvider(rec, base, role, meta) {
|
|
5390
5462
|
const url = rec.url || base.url;
|
|
5391
5463
|
return {
|
|
5392
5464
|
enabled: base.enabled,
|
|
@@ -5395,6 +5467,10 @@ function materializeProvider(rec, base, role) {
|
|
|
5395
5467
|
apiKey: resolveProviderApiKey(rec),
|
|
5396
5468
|
apiFlavor: rec.apiFlavor || inferApiFlavor(url),
|
|
5397
5469
|
locale: base.locale,
|
|
5470
|
+
providerName: meta.providerName,
|
|
5471
|
+
label: rec.label,
|
|
5472
|
+
source: meta.source,
|
|
5473
|
+
local: rec.local ?? isLocalEndpoint(url),
|
|
5398
5474
|
...role === "vision" ? { maxFrames: base.maxFrames } : {}
|
|
5399
5475
|
};
|
|
5400
5476
|
}
|
|
@@ -5413,6 +5489,8 @@ async function getLegacyRoleConfig(db, role, enabled) {
|
|
|
5413
5489
|
apiKey: await getSetting(db, "llm.vision.api_key") || base.apiKey,
|
|
5414
5490
|
apiFlavor: inferApiFlavor(url),
|
|
5415
5491
|
locale: base.locale,
|
|
5492
|
+
source: "legacy",
|
|
5493
|
+
local: isLocalEndpoint(url),
|
|
5416
5494
|
maxFrames: Number.isNaN(parsed) ? 100 : parsed
|
|
5417
5495
|
};
|
|
5418
5496
|
}
|
|
@@ -5422,7 +5500,9 @@ async function getLegacyRoleConfig(db, role, enabled) {
|
|
|
5422
5500
|
model: base.model,
|
|
5423
5501
|
apiKey: base.apiKey,
|
|
5424
5502
|
apiFlavor: inferApiFlavor(base.url),
|
|
5425
|
-
locale: base.locale
|
|
5503
|
+
locale: base.locale,
|
|
5504
|
+
source: "legacy",
|
|
5505
|
+
local: isLocalEndpoint(base.url)
|
|
5426
5506
|
};
|
|
5427
5507
|
}
|
|
5428
5508
|
function assertChatCompletions(cfg) {
|
|
@@ -5672,10 +5752,17 @@ async function isVisionProviderModelExplicit(db, active) {
|
|
|
5672
5752
|
const providers = await readJsonSetting(db, "llm.providers");
|
|
5673
5753
|
const roles = await readJsonSetting(db, "llm.roles");
|
|
5674
5754
|
const binding = roles?.vision;
|
|
5675
|
-
|
|
5676
|
-
|
|
5755
|
+
const sharedExplicit = [binding?.primary, binding?.fallback].some((id) => {
|
|
5756
|
+
if (!id) return false;
|
|
5757
|
+
const rec = providers?.[id];
|
|
5758
|
+
return rec?.model === active.model && (rec.url === void 0 || rec.url === active.url);
|
|
5759
|
+
});
|
|
5760
|
+
if (sharedExplicit) return true;
|
|
5761
|
+
const machine = getMachineAiConfig();
|
|
5762
|
+
const machineBinding = machine.roles?.vision;
|
|
5763
|
+
return [machineBinding?.primary, machineBinding?.fallback].some((id) => {
|
|
5677
5764
|
if (!id) return false;
|
|
5678
|
-
const rec = providers[id];
|
|
5765
|
+
const rec = machine.providers?.[id];
|
|
5679
5766
|
return rec?.model === active.model && (rec.url === void 0 || rec.url === active.url);
|
|
5680
5767
|
});
|
|
5681
5768
|
}
|
|
@@ -5708,6 +5795,87 @@ async function checkVisionReadiness(db) {
|
|
|
5708
5795
|
warning
|
|
5709
5796
|
};
|
|
5710
5797
|
}
|
|
5798
|
+
function summarizeFallback(provider) {
|
|
5799
|
+
if (!provider) return void 0;
|
|
5800
|
+
return {
|
|
5801
|
+
providerName: provider.providerName,
|
|
5802
|
+
label: provider.label,
|
|
5803
|
+
source: provider.source,
|
|
5804
|
+
url: provider.url,
|
|
5805
|
+
model: provider.model,
|
|
5806
|
+
apiFlavor: provider.apiFlavor,
|
|
5807
|
+
local: provider.local
|
|
5808
|
+
};
|
|
5809
|
+
}
|
|
5810
|
+
async function getProviderRoleStatus(db, role) {
|
|
5811
|
+
const cfg = await getProviderForRole(db, role);
|
|
5812
|
+
const unsupportedProvider = role !== "vision" && cfg.apiFlavor !== "chat-completions";
|
|
5813
|
+
if (!cfg.enabled) {
|
|
5814
|
+
return {
|
|
5815
|
+
role,
|
|
5816
|
+
enabled: false,
|
|
5817
|
+
providerName: cfg.providerName,
|
|
5818
|
+
label: cfg.label,
|
|
5819
|
+
source: cfg.source,
|
|
5820
|
+
url: cfg.url,
|
|
5821
|
+
model: cfg.model,
|
|
5822
|
+
apiFlavor: cfg.apiFlavor,
|
|
5823
|
+
local: cfg.local,
|
|
5824
|
+
online: false,
|
|
5825
|
+
modelAvailable: false,
|
|
5826
|
+
availableModels: [],
|
|
5827
|
+
usable: false,
|
|
5828
|
+
reason: "disabled",
|
|
5829
|
+
fallback: summarizeFallback(cfg.fallback)
|
|
5830
|
+
};
|
|
5831
|
+
}
|
|
5832
|
+
if (unsupportedProvider) {
|
|
5833
|
+
return {
|
|
5834
|
+
role,
|
|
5835
|
+
enabled: true,
|
|
5836
|
+
providerName: cfg.providerName,
|
|
5837
|
+
label: cfg.label,
|
|
5838
|
+
source: cfg.source,
|
|
5839
|
+
url: cfg.url,
|
|
5840
|
+
model: cfg.model,
|
|
5841
|
+
apiFlavor: cfg.apiFlavor,
|
|
5842
|
+
local: cfg.local,
|
|
5843
|
+
online: false,
|
|
5844
|
+
modelAvailable: false,
|
|
5845
|
+
availableModels: [],
|
|
5846
|
+
usable: false,
|
|
5847
|
+
reason: "unsupported-provider",
|
|
5848
|
+
fallback: summarizeFallback(cfg.fallback)
|
|
5849
|
+
};
|
|
5850
|
+
}
|
|
5851
|
+
let selected;
|
|
5852
|
+
if (role === "vision") {
|
|
5853
|
+
const chain = await checkProviderChain(cfg);
|
|
5854
|
+
selected = chain.firstUsable ?? chain.primary;
|
|
5855
|
+
} else {
|
|
5856
|
+
selected = await checkProviderEndpoint(cfg);
|
|
5857
|
+
}
|
|
5858
|
+
const active = selected.endpoint;
|
|
5859
|
+
const usable = selected.online && selected.modelAvailable;
|
|
5860
|
+
const reason = usable ? void 0 : selected.online ? "model-not-found" : "offline";
|
|
5861
|
+
return {
|
|
5862
|
+
role,
|
|
5863
|
+
enabled: true,
|
|
5864
|
+
providerName: active.providerName,
|
|
5865
|
+
label: active.label,
|
|
5866
|
+
source: active.source,
|
|
5867
|
+
url: active.url,
|
|
5868
|
+
model: active.model,
|
|
5869
|
+
apiFlavor: active.apiFlavor,
|
|
5870
|
+
local: active.local,
|
|
5871
|
+
online: selected.online,
|
|
5872
|
+
modelAvailable: selected.modelAvailable,
|
|
5873
|
+
availableModels: selected.availableModels,
|
|
5874
|
+
usable,
|
|
5875
|
+
reason,
|
|
5876
|
+
fallback: summarizeFallback(cfg.fallback)
|
|
5877
|
+
};
|
|
5878
|
+
}
|
|
5711
5879
|
function detectRunner(url, model) {
|
|
5712
5880
|
let runner = "unknown";
|
|
5713
5881
|
let port = "8000";
|
|
@@ -5806,7 +5974,7 @@ async function startLocalRunner(url, model, locale) {
|
|
|
5806
5974
|
let attempts = 0;
|
|
5807
5975
|
const dotsPerLine = 30;
|
|
5808
5976
|
while (true) {
|
|
5809
|
-
await new Promise((
|
|
5977
|
+
await new Promise((resolve8) => setTimeout(resolve8, 500));
|
|
5810
5978
|
if (await isLlmOnline(url)) {
|
|
5811
5979
|
if (attempts > 0) process.stdout.write("\n");
|
|
5812
5980
|
return true;
|
|
@@ -5961,8 +6129,8 @@ async function fetchWithInteractiveTimeout(url, options = {}) {
|
|
|
5961
6129
|
const dotsPerLine = 30;
|
|
5962
6130
|
while (true) {
|
|
5963
6131
|
let timeoutId;
|
|
5964
|
-
const timeoutPromise = new Promise((
|
|
5965
|
-
timeoutId = setTimeout(() =>
|
|
6132
|
+
const timeoutPromise = new Promise((resolve8) => {
|
|
6133
|
+
timeoutId = setTimeout(() => resolve8("timeout"), timeoutMs);
|
|
5966
6134
|
});
|
|
5967
6135
|
const dotsInterval = setInterval(() => {
|
|
5968
6136
|
process.stdout.write(".");
|
|
@@ -6489,11 +6657,338 @@ function jsonOut(data) {
|
|
|
6489
6657
|
|
|
6490
6658
|
// src/cli/commands/workspace.ts
|
|
6491
6659
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
6492
|
-
import { existsSync as
|
|
6660
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
6493
6661
|
import { homedir as homedir9 } from "os";
|
|
6494
|
-
import { join as
|
|
6662
|
+
import { join as join16, resolve as resolve4 } from "path";
|
|
6495
6663
|
import { confirm, input } from "@inquirer/prompts";
|
|
6664
|
+
import { Command as Command3 } from "commander";
|
|
6665
|
+
|
|
6666
|
+
// src/cli/commands/setup.ts
|
|
6667
|
+
import {
|
|
6668
|
+
copyFileSync as copyFileSync2,
|
|
6669
|
+
existsSync as existsSync15,
|
|
6670
|
+
mkdirSync as mkdirSync8,
|
|
6671
|
+
readFileSync as readFileSync10,
|
|
6672
|
+
writeFileSync as writeFileSync7
|
|
6673
|
+
} from "fs";
|
|
6674
|
+
import { basename as basename4, dirname as dirname5, join as join15, resolve as resolve3 } from "path";
|
|
6675
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6496
6676
|
import { Command as Command2 } from "commander";
|
|
6677
|
+
var packageRoot = [
|
|
6678
|
+
fileURLToPath2(new URL("../..", import.meta.url)),
|
|
6679
|
+
fileURLToPath2(new URL("../../..", import.meta.url))
|
|
6680
|
+
].find((candidate) => existsSync15(join15(candidate, "package.json"))) ?? fileURLToPath2(new URL("../..", import.meta.url));
|
|
6681
|
+
var ALL_SETUP_AGENTS = ["claude", "copilot", "codex", "agent"];
|
|
6682
|
+
var SKILL_PAIRS = [
|
|
6683
|
+
{
|
|
6684
|
+
from: join15(packageRoot, ".claude", "skills", "zam", "SKILL.md"),
|
|
6685
|
+
to: join15(".claude", "skills", "zam", "SKILL.md"),
|
|
6686
|
+
agents: ["claude"]
|
|
6687
|
+
},
|
|
6688
|
+
{
|
|
6689
|
+
from: join15(packageRoot, ".agent", "skills", "zam", "SKILL.md"),
|
|
6690
|
+
to: join15(".agent", "skills", "zam", "SKILL.md"),
|
|
6691
|
+
agents: ["agent"]
|
|
6692
|
+
},
|
|
6693
|
+
{
|
|
6694
|
+
from: join15(packageRoot, ".agents", "skills", "zam", "SKILL.md"),
|
|
6695
|
+
to: join15(".agents", "skills", "zam", "SKILL.md"),
|
|
6696
|
+
agents: ["copilot", "codex"]
|
|
6697
|
+
}
|
|
6698
|
+
];
|
|
6699
|
+
function parseSetupAgents(value) {
|
|
6700
|
+
if (!value || value.trim().toLowerCase() === "all") {
|
|
6701
|
+
return new Set(ALL_SETUP_AGENTS);
|
|
6702
|
+
}
|
|
6703
|
+
const aliases = {
|
|
6704
|
+
claude: ["claude"],
|
|
6705
|
+
copilot: ["copilot"],
|
|
6706
|
+
codex: ["codex"],
|
|
6707
|
+
agent: ["agent"],
|
|
6708
|
+
opencode: ["agent"]
|
|
6709
|
+
};
|
|
6710
|
+
const selected = /* @__PURE__ */ new Set();
|
|
6711
|
+
for (const raw of value.split(",")) {
|
|
6712
|
+
const key = raw.trim().toLowerCase();
|
|
6713
|
+
const mapped = aliases[key];
|
|
6714
|
+
if (!mapped) {
|
|
6715
|
+
throw new Error(
|
|
6716
|
+
`Unknown agent "${raw}". Use: all, claude, copilot, codex, agent.`
|
|
6717
|
+
);
|
|
6718
|
+
}
|
|
6719
|
+
for (const item of mapped) selected.add(item);
|
|
6720
|
+
}
|
|
6721
|
+
return selected;
|
|
6722
|
+
}
|
|
6723
|
+
function copySkills(force, cwd = process.cwd(), agents = parseSetupAgents(), dryRun = false) {
|
|
6724
|
+
let anyAction = false;
|
|
6725
|
+
for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
|
|
6726
|
+
if (!pairAgents.some((agent) => agents.has(agent))) continue;
|
|
6727
|
+
const dest = join15(cwd, to);
|
|
6728
|
+
if (!existsSync15(from)) {
|
|
6729
|
+
console.warn(` warn source not found, skipping: ${from}`);
|
|
6730
|
+
continue;
|
|
6731
|
+
}
|
|
6732
|
+
if (existsSync15(dest) && !force) {
|
|
6733
|
+
console.log(` skip ${to} (already present \xE2\u20AC\u201D use --force to update)`);
|
|
6734
|
+
continue;
|
|
6735
|
+
}
|
|
6736
|
+
if (dryRun) {
|
|
6737
|
+
console.log(` would copy ${to}`);
|
|
6738
|
+
anyAction = true;
|
|
6739
|
+
continue;
|
|
6740
|
+
}
|
|
6741
|
+
mkdirSync8(dirname5(dest), { recursive: true });
|
|
6742
|
+
copyFileSync2(from, dest);
|
|
6743
|
+
console.log(` copy ${to}`);
|
|
6744
|
+
anyAction = true;
|
|
6745
|
+
}
|
|
6746
|
+
if (!anyAction && !force) {
|
|
6747
|
+
console.log(
|
|
6748
|
+
"\nSkill files are already up to date. Run with --force to overwrite."
|
|
6749
|
+
);
|
|
6750
|
+
}
|
|
6751
|
+
}
|
|
6752
|
+
function formatDatabaseInitTarget(target) {
|
|
6753
|
+
switch (target.kind) {
|
|
6754
|
+
case "local":
|
|
6755
|
+
return `ZAM database at ${target.location} (local SQLite)`;
|
|
6756
|
+
case "turso-remote":
|
|
6757
|
+
return `ZAM database via Turso remote at ${target.location}`;
|
|
6758
|
+
case "turso-native":
|
|
6759
|
+
return `ZAM database via Turso native driver at ${target.location}`;
|
|
6760
|
+
case "turso-replica":
|
|
6761
|
+
return `ZAM database replica at ${target.location} syncing from ${target.syncUrl}`;
|
|
6762
|
+
}
|
|
6763
|
+
}
|
|
6764
|
+
async function initDatabase(skipInit) {
|
|
6765
|
+
if (skipInit) return;
|
|
6766
|
+
try {
|
|
6767
|
+
const target = getDatabaseTargetInfo();
|
|
6768
|
+
const db = await openDatabaseWithSync({ initialize: true });
|
|
6769
|
+
await db.close();
|
|
6770
|
+
console.log(` init ${formatDatabaseInitTarget(target)}`);
|
|
6771
|
+
} catch (err) {
|
|
6772
|
+
const msg = err.message;
|
|
6773
|
+
if (!msg.includes("already")) {
|
|
6774
|
+
console.warn(` warn database init: ${msg}`);
|
|
6775
|
+
} else {
|
|
6776
|
+
console.log(` skip database already initialized`);
|
|
6777
|
+
}
|
|
6778
|
+
}
|
|
6779
|
+
}
|
|
6780
|
+
var ZAM_BLOCK_START = "<!-- ZAM:START -->";
|
|
6781
|
+
var ZAM_BLOCK_END = "<!-- ZAM:END -->";
|
|
6782
|
+
function upsertMarkedBlock(dest, blockBody, dryRun) {
|
|
6783
|
+
const block = `${ZAM_BLOCK_START}
|
|
6784
|
+
${blockBody.trim()}
|
|
6785
|
+
${ZAM_BLOCK_END}`;
|
|
6786
|
+
if (!existsSync15(dest)) {
|
|
6787
|
+
if (!dryRun) {
|
|
6788
|
+
mkdirSync8(dirname5(dest), { recursive: true });
|
|
6789
|
+
writeFileSync7(dest, `${block}
|
|
6790
|
+
`, "utf8");
|
|
6791
|
+
}
|
|
6792
|
+
return "write";
|
|
6793
|
+
}
|
|
6794
|
+
const existing = readFileSync10(dest, "utf8");
|
|
6795
|
+
if (existing.includes(block)) return "skip";
|
|
6796
|
+
const start = existing.indexOf(ZAM_BLOCK_START);
|
|
6797
|
+
const end = existing.indexOf(ZAM_BLOCK_END);
|
|
6798
|
+
const next = start >= 0 && end > start ? `${existing.slice(0, start)}${block}${existing.slice(end + ZAM_BLOCK_END.length)}` : `${existing.trimEnd()}
|
|
6799
|
+
|
|
6800
|
+
${block}
|
|
6801
|
+
`;
|
|
6802
|
+
if (!dryRun) writeFileSync7(dest, next, "utf8");
|
|
6803
|
+
return start >= 0 && end > start ? "update" : "write";
|
|
6804
|
+
}
|
|
6805
|
+
function logInstructionAction(action, label, dryRun) {
|
|
6806
|
+
if (action === "skip") {
|
|
6807
|
+
console.log(` skip ${label} (ZAM block already present)`);
|
|
6808
|
+
} else {
|
|
6809
|
+
console.log(` ${dryRun ? "would " : ""}${action} ${label}`);
|
|
6810
|
+
}
|
|
6811
|
+
}
|
|
6812
|
+
function writeClaudeMd(skipClaudeMd, cwd = process.cwd(), opts = {}) {
|
|
6813
|
+
if (skipClaudeMd) return;
|
|
6814
|
+
const dest = join15(cwd, "CLAUDE.md");
|
|
6815
|
+
if (existsSync15(dest)) {
|
|
6816
|
+
if (!opts.updateExisting) {
|
|
6817
|
+
console.log(` skip CLAUDE.md (already present)`);
|
|
6818
|
+
return;
|
|
6819
|
+
}
|
|
6820
|
+
const action = upsertMarkedBlock(
|
|
6821
|
+
dest,
|
|
6822
|
+
`## ZAM learning sessions
|
|
6823
|
+
|
|
6824
|
+
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.
|
|
6825
|
+
|
|
6826
|
+
- Skill files live under \`.claude/skills/zam/\`.
|
|
6827
|
+
- Fast-changing review data lives in \`~/.zam/\`, not in this repository.
|
|
6828
|
+
- Run \`zam setup --target . --agents claude --force\` after upgrading ZAM to refresh the skill.`,
|
|
6829
|
+
Boolean(opts.dryRun)
|
|
6830
|
+
);
|
|
6831
|
+
logInstructionAction(action, "CLAUDE.md", Boolean(opts.dryRun));
|
|
6832
|
+
return;
|
|
6833
|
+
}
|
|
6834
|
+
const name = basename4(cwd);
|
|
6835
|
+
const content = `# ZAM Personal Kernel \xE2\u20AC\u201D ${name}
|
|
6836
|
+
|
|
6837
|
+
This is a ZAM personal instance. ZAM builds lasting skills through spaced
|
|
6838
|
+
repetition during real work \xE2\u20AC\u201D not separate study sessions.
|
|
6839
|
+
|
|
6840
|
+
## First time here?
|
|
6841
|
+
Run \`/setup\` in Claude Code or Gemini CLI to complete first-time setup.
|
|
6842
|
+
|
|
6843
|
+
## Regular use
|
|
6844
|
+
Run \`/zam\` to start a learning session on whatever you are working on.
|
|
6845
|
+
|
|
6846
|
+
## What lives here
|
|
6847
|
+
- \`beliefs/\` \xE2\u20AC\u201D your worldview, approved by git commit
|
|
6848
|
+
- \`goals/\` \xE2\u20AC\u201D your objectives, decomposed into tasks and learning tokens
|
|
6849
|
+
|
|
6850
|
+
## Fast-changing data
|
|
6851
|
+
Learning tokens, cards, and review history live in local SQLite by default.
|
|
6852
|
+
Use \`zam connector setup turso\` to store cloud credentials in
|
|
6853
|
+
\`~/.zam/credentials.json\` and use a Turso database across machines.
|
|
6854
|
+
`;
|
|
6855
|
+
if (opts.dryRun) {
|
|
6856
|
+
console.log(` would write CLAUDE.md`);
|
|
6857
|
+
} else {
|
|
6858
|
+
writeFileSync7(dest, content, "utf8");
|
|
6859
|
+
console.log(` write CLAUDE.md`);
|
|
6860
|
+
}
|
|
6861
|
+
}
|
|
6862
|
+
function writeAgentsMd(skipAgentsMd, cwd = process.cwd(), opts = {}) {
|
|
6863
|
+
if (skipAgentsMd) return;
|
|
6864
|
+
const dest = join15(cwd, "AGENTS.md");
|
|
6865
|
+
if (existsSync15(dest)) {
|
|
6866
|
+
if (!opts.updateExisting) {
|
|
6867
|
+
console.log(` skip AGENTS.md (already present)`);
|
|
6868
|
+
return;
|
|
6869
|
+
}
|
|
6870
|
+
const action = upsertMarkedBlock(
|
|
6871
|
+
dest,
|
|
6872
|
+
`## ZAM learning sessions
|
|
6873
|
+
|
|
6874
|
+
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.
|
|
6875
|
+
|
|
6876
|
+
- Skill files live under \`.agents/skills/zam/\`.
|
|
6877
|
+
- Fast-changing review data lives in \`~/.zam/\`, not in this repository.
|
|
6878
|
+
- Run \`zam setup --target . --agents copilot,codex --force\` after upgrading ZAM to refresh the skill.`,
|
|
6879
|
+
Boolean(opts.dryRun)
|
|
6880
|
+
);
|
|
6881
|
+
logInstructionAction(action, "AGENTS.md", Boolean(opts.dryRun));
|
|
6882
|
+
return;
|
|
6883
|
+
}
|
|
6884
|
+
const name = basename4(cwd);
|
|
6885
|
+
const content = `# ZAM Personal Kernel - ${name}
|
|
6886
|
+
|
|
6887
|
+
This is a ZAM personal instance. ZAM builds lasting skills through spaced
|
|
6888
|
+
repetition during real work, not separate study sessions.
|
|
6889
|
+
|
|
6890
|
+
## First time here?
|
|
6891
|
+
Run \`zam setup\` from the shell. When this repository includes
|
|
6892
|
+
\`.agents/skills/setup/\`, you can instead select \`setup\` through \`/skills\`
|
|
6893
|
+
or invoke \`$setup\`.
|
|
6894
|
+
|
|
6895
|
+
## Regular use
|
|
6896
|
+
Select the \`zam\` skill through \`/skills\` or invoke \`$zam\` to start a
|
|
6897
|
+
learning session on whatever you are working on.
|
|
6898
|
+
|
|
6899
|
+
## What lives here
|
|
6900
|
+
- \`beliefs/\` - your worldview, approved by git commit
|
|
6901
|
+
- \`goals/\` - your objectives, decomposed into tasks and learning tokens
|
|
6902
|
+
|
|
6903
|
+
## Fast-changing data
|
|
6904
|
+
Learning tokens, cards, and review history live in local SQLite by default.
|
|
6905
|
+
Use \`zam connector setup turso\` to store cloud credentials in
|
|
6906
|
+
\`~/.zam/credentials.json\` and use a Turso database across machines.
|
|
6907
|
+
|
|
6908
|
+
## Codex skills
|
|
6909
|
+
Codex discovers repository skills under \`.agents/skills/\`. Run
|
|
6910
|
+
\`zam setup --force\` after upgrading \`zam-core\` to refresh them.
|
|
6911
|
+
`;
|
|
6912
|
+
if (opts.dryRun) {
|
|
6913
|
+
console.log(` would write AGENTS.md`);
|
|
6914
|
+
} else {
|
|
6915
|
+
writeFileSync7(dest, content, "utf8");
|
|
6916
|
+
console.log(` write AGENTS.md`);
|
|
6917
|
+
}
|
|
6918
|
+
}
|
|
6919
|
+
function writeCopilotInstructions(cwd = process.cwd(), opts = {}) {
|
|
6920
|
+
const dest = join15(cwd, ".github", "copilot-instructions.md");
|
|
6921
|
+
const action = upsertMarkedBlock(
|
|
6922
|
+
dest,
|
|
6923
|
+
`## ZAM learning sessions
|
|
6924
|
+
|
|
6925
|
+
ZAM is available in this repository through \`.agents/skills/zam/\`. Use the \`zam\` skill from Copilot-compatible skill selection surfaces to turn real work into an observed learning session with active recall and FSRS scheduling.
|
|
6926
|
+
|
|
6927
|
+
- Fast-changing review data lives in \`~/.zam/\`, not in this repository.
|
|
6928
|
+
- Run \`zam setup --target . --agents copilot --force\` after upgrading ZAM to refresh the skill.`,
|
|
6929
|
+
Boolean(opts.dryRun)
|
|
6930
|
+
);
|
|
6931
|
+
logInstructionAction(
|
|
6932
|
+
action,
|
|
6933
|
+
".github/copilot-instructions.md",
|
|
6934
|
+
Boolean(opts.dryRun)
|
|
6935
|
+
);
|
|
6936
|
+
}
|
|
6937
|
+
var setupCommand = new Command2("setup").description(
|
|
6938
|
+
"Distribute ZAM skill files into this personal instance and initialize the database"
|
|
6939
|
+
).option(
|
|
6940
|
+
"--force",
|
|
6941
|
+
"overwrite existing skill files (use after upgrading zam)",
|
|
6942
|
+
false
|
|
6943
|
+
).option("--skip-init", "skip database initialization", false).option("--skip-claude-md", "skip CLAUDE.md generation", false).option("--skip-agents-md", "skip AGENTS.md generation", false).option("--target <path>", "repository/workspace directory to set up").option(
|
|
6944
|
+
"--agents <list>",
|
|
6945
|
+
"comma-separated agents to wire: all, claude, copilot, codex, agent"
|
|
6946
|
+
).option(
|
|
6947
|
+
"--dry-run",
|
|
6948
|
+
"show what would be written without changing files",
|
|
6949
|
+
false
|
|
6950
|
+
).action(
|
|
6951
|
+
async (opts) => {
|
|
6952
|
+
let agents;
|
|
6953
|
+
try {
|
|
6954
|
+
agents = parseSetupAgents(opts.agents);
|
|
6955
|
+
} catch (err) {
|
|
6956
|
+
console.error(`Error: ${err.message}`);
|
|
6957
|
+
process.exit(1);
|
|
6958
|
+
}
|
|
6959
|
+
const target = resolve3(opts.target ?? process.cwd());
|
|
6960
|
+
const updateExistingInstructions = Boolean(opts.target) || opts.force;
|
|
6961
|
+
console.log(
|
|
6962
|
+
`Setting up ZAM in ${target}${opts.dryRun ? " (dry run)" : ""}
|
|
6963
|
+
`
|
|
6964
|
+
);
|
|
6965
|
+
copySkills(opts.force, target, agents, opts.dryRun);
|
|
6966
|
+
await initDatabase(opts.skipInit || opts.dryRun);
|
|
6967
|
+
if (agents.has("claude")) {
|
|
6968
|
+
writeClaudeMd(opts.skipClaudeMd, target, {
|
|
6969
|
+
dryRun: opts.dryRun,
|
|
6970
|
+
updateExisting: updateExistingInstructions
|
|
6971
|
+
});
|
|
6972
|
+
}
|
|
6973
|
+
if (agents.has("copilot") || agents.has("codex") || agents.has("agent")) {
|
|
6974
|
+
writeAgentsMd(opts.skipAgentsMd, target, {
|
|
6975
|
+
dryRun: opts.dryRun,
|
|
6976
|
+
updateExisting: updateExistingInstructions
|
|
6977
|
+
});
|
|
6978
|
+
}
|
|
6979
|
+
if (agents.has("copilot") && (opts.target || opts.agents)) {
|
|
6980
|
+
writeCopilotInstructions(target, {
|
|
6981
|
+
dryRun: opts.dryRun,
|
|
6982
|
+
updateExisting: updateExistingInstructions
|
|
6983
|
+
});
|
|
6984
|
+
}
|
|
6985
|
+
console.log(
|
|
6986
|
+
"\nDone. Run `zam whoami --set <your-id>` to set your identity. Start the `zam` skill with `/zam` in Claude/Gemini-compatible clients or `$zam` (or `/skills`) in Codex."
|
|
6987
|
+
);
|
|
6988
|
+
}
|
|
6989
|
+
);
|
|
6990
|
+
|
|
6991
|
+
// src/cli/commands/workspace.ts
|
|
6497
6992
|
function runGit(cwd, args) {
|
|
6498
6993
|
try {
|
|
6499
6994
|
return execFileSync3("git", args, {
|
|
@@ -6502,18 +6997,169 @@ function runGit(cwd, args) {
|
|
|
6502
6997
|
encoding: "utf8"
|
|
6503
6998
|
}).trim();
|
|
6504
6999
|
} catch (err) {
|
|
6505
|
-
throw new Error(`Git command failed: ${err.message}`);
|
|
7000
|
+
throw new Error(`Git command failed: ${err.message}`);
|
|
7001
|
+
}
|
|
7002
|
+
}
|
|
7003
|
+
function ghRepoCreateArgs(repoName, repoVisibility) {
|
|
7004
|
+
return ["repo", "create", repoName, repoVisibility, "--source=.", "--push"];
|
|
7005
|
+
}
|
|
7006
|
+
function gitRemoteArgs(githubUrl, hasOrigin) {
|
|
7007
|
+
return hasOrigin ? ["remote", "set-url", "origin", githubUrl] : ["remote", "add", "origin", githubUrl];
|
|
7008
|
+
}
|
|
7009
|
+
var workspaceCommand = new Command3("workspace").description(
|
|
7010
|
+
"Manage your ZAM learning workspace"
|
|
7011
|
+
);
|
|
7012
|
+
var WORKSPACE_KINDS = [
|
|
7013
|
+
"personal",
|
|
7014
|
+
"team",
|
|
7015
|
+
"family",
|
|
7016
|
+
"community",
|
|
7017
|
+
"organization",
|
|
7018
|
+
"custom"
|
|
7019
|
+
];
|
|
7020
|
+
var WORKSPACE_SOURCE_CONTROLS = [
|
|
7021
|
+
"github",
|
|
7022
|
+
"azure-devops",
|
|
7023
|
+
"git",
|
|
7024
|
+
"none"
|
|
7025
|
+
];
|
|
7026
|
+
function parseWorkspaceKind(value) {
|
|
7027
|
+
const kind = (value ?? "custom").toLowerCase();
|
|
7028
|
+
if (WORKSPACE_KINDS.includes(kind)) {
|
|
7029
|
+
return kind;
|
|
7030
|
+
}
|
|
7031
|
+
throw new Error(
|
|
7032
|
+
`Invalid workspace kind: ${value}. Use ${WORKSPACE_KINDS.join(", ")}.`
|
|
7033
|
+
);
|
|
7034
|
+
}
|
|
7035
|
+
function parseWorkspaceSourceControl(value) {
|
|
7036
|
+
if (!value) return void 0;
|
|
7037
|
+
const source = value.toLowerCase();
|
|
7038
|
+
if (WORKSPACE_SOURCE_CONTROLS.includes(source)) {
|
|
7039
|
+
return source;
|
|
7040
|
+
}
|
|
7041
|
+
throw new Error(
|
|
7042
|
+
`Invalid source control: ${value}. Use ${WORKSPACE_SOURCE_CONTROLS.join(", ")}.`
|
|
7043
|
+
);
|
|
7044
|
+
}
|
|
7045
|
+
function parseScopes(value) {
|
|
7046
|
+
if (!value) return void 0;
|
|
7047
|
+
const scopes = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
7048
|
+
return scopes.length > 0 ? scopes : void 0;
|
|
7049
|
+
}
|
|
7050
|
+
function requireWorkspace(id) {
|
|
7051
|
+
const workspace = getConfiguredWorkspaces().find((item) => item.id === id);
|
|
7052
|
+
if (!workspace) {
|
|
7053
|
+
throw new Error(
|
|
7054
|
+
`Workspace "${id}" is not configured. Add it with: zam workspace add ${id} --path <dir>`
|
|
7055
|
+
);
|
|
7056
|
+
}
|
|
7057
|
+
return workspace;
|
|
7058
|
+
}
|
|
7059
|
+
workspaceCommand.command("list").description("List configured ZAM workspaces").option("--json", "Output as JSON").action((opts) => {
|
|
7060
|
+
const workspaces = getConfiguredWorkspaces();
|
|
7061
|
+
if (opts.json) {
|
|
7062
|
+
console.log(JSON.stringify({ workspaces }, null, 2));
|
|
7063
|
+
return;
|
|
7064
|
+
}
|
|
7065
|
+
console.log("Configured ZAM workspaces:\n");
|
|
7066
|
+
if (workspaces.length === 0) {
|
|
7067
|
+
console.log(
|
|
7068
|
+
" (none) \u2014 add one: zam workspace add personal --path <dir>"
|
|
7069
|
+
);
|
|
7070
|
+
return;
|
|
7071
|
+
}
|
|
7072
|
+
for (const workspace of workspaces) {
|
|
7073
|
+
const label = workspace.label ? ` (${workspace.label})` : "";
|
|
7074
|
+
console.log(` ${workspace.id}${label}`);
|
|
7075
|
+
console.log(` kind: ${workspace.kind}`);
|
|
7076
|
+
console.log(` path: ${workspace.path}`);
|
|
7077
|
+
if (workspace.sourceControl) {
|
|
7078
|
+
console.log(` source: ${workspace.sourceControl}`);
|
|
7079
|
+
}
|
|
7080
|
+
if (workspace.knowledgeScopes?.length) {
|
|
7081
|
+
console.log(` scopes: ${workspace.knowledgeScopes.join(", ")}`);
|
|
7082
|
+
}
|
|
7083
|
+
}
|
|
7084
|
+
});
|
|
7085
|
+
workspaceCommand.command("add <id>").description("Register an existing directory as a ZAM workspace").requiredOption("--path <dir>", "Existing workspace/repository directory").option(
|
|
7086
|
+
"--kind <kind>",
|
|
7087
|
+
`Workspace kind (${WORKSPACE_KINDS.join(" | ")})`,
|
|
7088
|
+
"custom"
|
|
7089
|
+
).option("--label <label>", "Human-readable label").option(
|
|
7090
|
+
"--source-control <provider>",
|
|
7091
|
+
`Source-control provider (${WORKSPACE_SOURCE_CONTROLS.join(" | ")})`
|
|
7092
|
+
).option("--scopes <list>", "Comma-separated knowledge scopes").option("--default-agent <id>", "Default agent harness for this workspace").action((id, opts) => {
|
|
7093
|
+
try {
|
|
7094
|
+
const path = resolve4(String(opts.path));
|
|
7095
|
+
if (!existsSync16(path)) {
|
|
7096
|
+
console.error(`Workspace path does not exist: ${path}`);
|
|
7097
|
+
process.exit(1);
|
|
7098
|
+
}
|
|
7099
|
+
const workspace = {
|
|
7100
|
+
id,
|
|
7101
|
+
kind: parseWorkspaceKind(opts.kind),
|
|
7102
|
+
path,
|
|
7103
|
+
...opts.label ? { label: opts.label } : {},
|
|
7104
|
+
...opts.sourceControl ? { sourceControl: parseWorkspaceSourceControl(opts.sourceControl) } : {},
|
|
7105
|
+
...parseScopes(opts.scopes) ? { knowledgeScopes: parseScopes(opts.scopes) } : {},
|
|
7106
|
+
...opts.defaultAgent ? { defaultAgent: opts.defaultAgent } : {}
|
|
7107
|
+
};
|
|
7108
|
+
upsertConfiguredWorkspace(workspace);
|
|
7109
|
+
console.log(`Registered workspace "${id}" at ${path}.`);
|
|
7110
|
+
} catch (err) {
|
|
7111
|
+
console.error(`Error: ${err.message}`);
|
|
7112
|
+
process.exit(1);
|
|
7113
|
+
}
|
|
7114
|
+
});
|
|
7115
|
+
workspaceCommand.command("setup <id>").description("Install ZAM skills into a configured workspace").option(
|
|
7116
|
+
"--agents <list>",
|
|
7117
|
+
"comma-separated agents to wire: all, claude, copilot, codex, agent"
|
|
7118
|
+
).option(
|
|
7119
|
+
"--force",
|
|
7120
|
+
"overwrite existing skill files and refresh ZAM blocks",
|
|
7121
|
+
false
|
|
7122
|
+
).option(
|
|
7123
|
+
"--dry-run",
|
|
7124
|
+
"show what would be written without changing files",
|
|
7125
|
+
false
|
|
7126
|
+
).action((id, opts) => {
|
|
7127
|
+
try {
|
|
7128
|
+
const workspace = requireWorkspace(id);
|
|
7129
|
+
const agents = parseSetupAgents(opts.agents);
|
|
7130
|
+
console.log(
|
|
7131
|
+
`Setting up workspace "${workspace.id}" in ${workspace.path}${opts.dryRun ? " (dry run)" : ""}
|
|
7132
|
+
`
|
|
7133
|
+
);
|
|
7134
|
+
copySkills(
|
|
7135
|
+
Boolean(opts.force),
|
|
7136
|
+
workspace.path,
|
|
7137
|
+
agents,
|
|
7138
|
+
Boolean(opts.dryRun)
|
|
7139
|
+
);
|
|
7140
|
+
if (agents.has("claude")) {
|
|
7141
|
+
writeClaudeMd(false, workspace.path, {
|
|
7142
|
+
dryRun: Boolean(opts.dryRun),
|
|
7143
|
+
updateExisting: true
|
|
7144
|
+
});
|
|
7145
|
+
}
|
|
7146
|
+
if (agents.has("copilot") || agents.has("codex") || agents.has("agent")) {
|
|
7147
|
+
writeAgentsMd(false, workspace.path, {
|
|
7148
|
+
dryRun: Boolean(opts.dryRun),
|
|
7149
|
+
updateExisting: true
|
|
7150
|
+
});
|
|
7151
|
+
}
|
|
7152
|
+
if (agents.has("copilot")) {
|
|
7153
|
+
writeCopilotInstructions(workspace.path, {
|
|
7154
|
+
dryRun: Boolean(opts.dryRun),
|
|
7155
|
+
updateExisting: true
|
|
7156
|
+
});
|
|
7157
|
+
}
|
|
7158
|
+
} catch (err) {
|
|
7159
|
+
console.error(`Error: ${err.message}`);
|
|
7160
|
+
process.exit(1);
|
|
6506
7161
|
}
|
|
6507
|
-
}
|
|
6508
|
-
function ghRepoCreateArgs(repoName, repoVisibility) {
|
|
6509
|
-
return ["repo", "create", repoName, repoVisibility, "--source=.", "--push"];
|
|
6510
|
-
}
|
|
6511
|
-
function gitRemoteArgs(githubUrl, hasOrigin) {
|
|
6512
|
-
return hasOrigin ? ["remote", "set-url", "origin", githubUrl] : ["remote", "add", "origin", githubUrl];
|
|
6513
|
-
}
|
|
6514
|
-
var workspaceCommand = new Command2("workspace").description(
|
|
6515
|
-
"Manage your ZAM learning workspace"
|
|
6516
|
-
);
|
|
7162
|
+
});
|
|
6517
7163
|
workspaceCommand.command("publish").description("Publish your local workspace sandbox to GitHub").action(async () => {
|
|
6518
7164
|
let db;
|
|
6519
7165
|
let workspaceDir = "";
|
|
@@ -6530,7 +7176,7 @@ workspaceCommand.command("publish").description("Publish your local workspace sa
|
|
|
6530
7176
|
);
|
|
6531
7177
|
process.exit(1);
|
|
6532
7178
|
}
|
|
6533
|
-
if (!
|
|
7179
|
+
if (!existsSync16(workspaceDir)) {
|
|
6534
7180
|
console.error(
|
|
6535
7181
|
`\x1B[31m\u2717 Workspace directory does not exist: ${workspaceDir}\x1B[0m`
|
|
6536
7182
|
);
|
|
@@ -6544,15 +7190,15 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
|
6544
7190
|
);
|
|
6545
7191
|
process.exit(1);
|
|
6546
7192
|
}
|
|
6547
|
-
const gitignorePath =
|
|
6548
|
-
if (!
|
|
6549
|
-
|
|
7193
|
+
const gitignorePath = join16(workspaceDir, ".gitignore");
|
|
7194
|
+
if (!existsSync16(gitignorePath)) {
|
|
7195
|
+
writeFileSync8(
|
|
6550
7196
|
gitignorePath,
|
|
6551
7197
|
"node_modules/\n.agent/\n.agents/\n.claude/\n.gemini/\n.goose/\n*.log\n",
|
|
6552
7198
|
"utf8"
|
|
6553
7199
|
);
|
|
6554
7200
|
}
|
|
6555
|
-
const hasGitRepo =
|
|
7201
|
+
const hasGitRepo = existsSync16(join16(workspaceDir, ".git"));
|
|
6556
7202
|
if (!hasGitRepo) {
|
|
6557
7203
|
console.log("Initializing local Git repository...");
|
|
6558
7204
|
runGit(workspaceDir, ["init", "-b", "main"]);
|
|
@@ -6639,15 +7285,15 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
|
6639
7285
|
}
|
|
6640
7286
|
});
|
|
6641
7287
|
async function backupDatabaseTo(db, targetDir) {
|
|
6642
|
-
const backupDir =
|
|
6643
|
-
|
|
7288
|
+
const backupDir = join16(targetDir, "zam-backups");
|
|
7289
|
+
mkdirSync9(backupDir, { recursive: true });
|
|
6644
7290
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6645
|
-
const dest =
|
|
7291
|
+
const dest = join16(backupDir, `zam-${stamp}.db`);
|
|
6646
7292
|
await db.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
|
|
6647
7293
|
return dest;
|
|
6648
7294
|
}
|
|
6649
7295
|
workspaceCommand.command("data-dir").description("Print the ZAM data directory (database, credentials, config)").option("--json", "Output as JSON").action((opts) => {
|
|
6650
|
-
const dir =
|
|
7296
|
+
const dir = join16(homedir9(), ".zam");
|
|
6651
7297
|
console.log(opts.json ? JSON.stringify({ dataDir: dir }) : dir);
|
|
6652
7298
|
});
|
|
6653
7299
|
workspaceCommand.command("backup").description("Back up the local ZAM database into your workspace").option(
|
|
@@ -6667,7 +7313,7 @@ workspaceCommand.command("backup").description("Back up the local ZAM database i
|
|
|
6667
7313
|
let db;
|
|
6668
7314
|
try {
|
|
6669
7315
|
db = await openDatabase();
|
|
6670
|
-
const workspaceDir = opts.dir || await getSetting(db, "personal.workspace_dir") ||
|
|
7316
|
+
const workspaceDir = opts.dir || await getSetting(db, "personal.workspace_dir") || join16(homedir9(), "Documents", "zam");
|
|
6671
7317
|
const dest = await backupDatabaseTo(db, workspaceDir);
|
|
6672
7318
|
if (opts.json) {
|
|
6673
7319
|
console.log(JSON.stringify({ ok: true, path: dest }));
|
|
@@ -6743,7 +7389,7 @@ function parseTokenUpdates(opts) {
|
|
|
6743
7389
|
}
|
|
6744
7390
|
return updates;
|
|
6745
7391
|
}
|
|
6746
|
-
var bridgeCommand = new
|
|
7392
|
+
var bridgeCommand = new Command4("bridge").description(
|
|
6747
7393
|
"Machine-readable JSON protocol for AI integration"
|
|
6748
7394
|
);
|
|
6749
7395
|
bridgeCommand.command("check-due").description("Check due cards for a user (JSON)").option("--user <id>", "User ID (default: whoami)").option("--domain <domain>", "Filter by knowledge domain").action(async (opts) => {
|
|
@@ -6786,7 +7432,7 @@ bridgeCommand.command("backup-db").description("Back up the local database into
|
|
|
6786
7432
|
return;
|
|
6787
7433
|
}
|
|
6788
7434
|
await withDb2(async (db) => {
|
|
6789
|
-
const workspaceDir = opts.dir || await getSetting(db, "personal.workspace_dir") ||
|
|
7435
|
+
const workspaceDir = opts.dir || await getSetting(db, "personal.workspace_dir") || join17(homedir10(), "Documents", "zam");
|
|
6790
7436
|
const path = await backupDatabaseTo(db, workspaceDir);
|
|
6791
7437
|
jsonOut2({ ok: true, path });
|
|
6792
7438
|
});
|
|
@@ -6796,20 +7442,135 @@ bridgeCommand.command("workspace-info").description("Report the workspace dir, i
|
|
|
6796
7442
|
const workspaceDir = await getSetting(db, "personal.workspace_dir") || null;
|
|
6797
7443
|
jsonOut2({
|
|
6798
7444
|
workspaceDir,
|
|
6799
|
-
defaultWorkspaceDir:
|
|
6800
|
-
dataDir:
|
|
7445
|
+
defaultWorkspaceDir: join17(homedir10(), "Documents", "zam"),
|
|
7446
|
+
dataDir: join17(homedir10(), ".zam")
|
|
7447
|
+
});
|
|
7448
|
+
});
|
|
7449
|
+
});
|
|
7450
|
+
bridgeCommand.command("workspace-list").description("List configured ZAM workspaces (JSON)").action(async () => {
|
|
7451
|
+
await withDb2(async (db) => {
|
|
7452
|
+
const workspaceDir = await getSetting(db, "personal.workspace_dir") || null;
|
|
7453
|
+
jsonOut2({
|
|
7454
|
+
workspaces: getConfiguredWorkspaces(),
|
|
7455
|
+
workspaceDir,
|
|
7456
|
+
defaultWorkspaceDir: join17(homedir10(), "Documents", "zam"),
|
|
7457
|
+
dataDir: join17(homedir10(), ".zam")
|
|
7458
|
+
});
|
|
7459
|
+
});
|
|
7460
|
+
});
|
|
7461
|
+
function workspaceIdFromPath(dir) {
|
|
7462
|
+
const base = basename5(dir).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
7463
|
+
const prefix = base || "workspace";
|
|
7464
|
+
const existing = new Set(getConfiguredWorkspaces().map((item) => item.id));
|
|
7465
|
+
if (!existing.has(prefix)) return prefix;
|
|
7466
|
+
let index = 2;
|
|
7467
|
+
while (existing.has(`${prefix}-${index}`)) index++;
|
|
7468
|
+
return `${prefix}-${index}`;
|
|
7469
|
+
}
|
|
7470
|
+
function parseBridgeWorkspaceKind(value) {
|
|
7471
|
+
const kind = (value || "custom").toLowerCase();
|
|
7472
|
+
if (kind === "personal" || kind === "team" || kind === "family" || kind === "community" || kind === "organization" || kind === "custom") {
|
|
7473
|
+
return kind;
|
|
7474
|
+
}
|
|
7475
|
+
jsonError(`Invalid workspace kind: ${value}`);
|
|
7476
|
+
}
|
|
7477
|
+
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) => {
|
|
7478
|
+
const raw = String(opts.path ?? "").trim();
|
|
7479
|
+
if (!raw) jsonError("A non-empty --path is required");
|
|
7480
|
+
const path = resolve5(raw);
|
|
7481
|
+
if (!existsSync17(path)) jsonError(`Workspace path does not exist: ${path}`);
|
|
7482
|
+
const id = String(opts.id || workspaceIdFromPath(path)).trim();
|
|
7483
|
+
if (!id) jsonError("A non-empty --id is required");
|
|
7484
|
+
const workspace = {
|
|
7485
|
+
id,
|
|
7486
|
+
label: opts.label || basename5(path) || id,
|
|
7487
|
+
kind: parseBridgeWorkspaceKind(opts.kind),
|
|
7488
|
+
path
|
|
7489
|
+
};
|
|
7490
|
+
await withDb2(async (db) => {
|
|
7491
|
+
await setSetting(db, "personal.workspace_dir", path);
|
|
7492
|
+
upsertConfiguredWorkspace(workspace);
|
|
7493
|
+
jsonOut2({
|
|
7494
|
+
ok: true,
|
|
7495
|
+
workspace,
|
|
7496
|
+
workspaces: getConfiguredWorkspaces(),
|
|
7497
|
+
workspaceDir: path
|
|
6801
7498
|
});
|
|
6802
7499
|
});
|
|
6803
7500
|
});
|
|
6804
7501
|
bridgeCommand.command("set-workspace-dir").description("Set the personal workspace directory (JSON)").requiredOption("--dir <path>", "Path to the workspace directory").action(async (opts) => {
|
|
6805
7502
|
const raw = String(opts.dir ?? "").trim();
|
|
6806
7503
|
if (!raw) jsonError("A non-empty --dir is required");
|
|
6807
|
-
const dir =
|
|
7504
|
+
const dir = resolve5(raw);
|
|
6808
7505
|
await withDb2(async (db) => {
|
|
6809
7506
|
await setSetting(db, "personal.workspace_dir", dir);
|
|
6810
7507
|
jsonOut2({ ok: true, workspaceDir: dir });
|
|
6811
7508
|
});
|
|
6812
7509
|
});
|
|
7510
|
+
bridgeCommand.command("agent-list").description("List agent harnesses with detection state + the default (JSON)").action(async () => {
|
|
7511
|
+
await withDb2(async (db) => {
|
|
7512
|
+
const configuredDefault = await getSetting(db, "agent.default") || null;
|
|
7513
|
+
const harnesses = await Promise.all(
|
|
7514
|
+
AGENT_HARNESSES.map(async (h) => {
|
|
7515
|
+
const override = await getSetting(db, `agent.${h.id}.command`) || void 0;
|
|
7516
|
+
return {
|
|
7517
|
+
id: h.id,
|
|
7518
|
+
label: h.label,
|
|
7519
|
+
kind: h.kind,
|
|
7520
|
+
detected: resolveHarnessExecutable(h, override) !== null
|
|
7521
|
+
};
|
|
7522
|
+
})
|
|
7523
|
+
);
|
|
7524
|
+
const fallbackDefault = harnesses.find((h) => h.detected)?.id ?? null;
|
|
7525
|
+
jsonOut2({ harnesses, default: configuredDefault ?? fallbackDefault });
|
|
7526
|
+
});
|
|
7527
|
+
});
|
|
7528
|
+
bridgeCommand.command("agent-open").description("Launch an agent harness in the workspace (JSON)").option(
|
|
7529
|
+
"--id <id>",
|
|
7530
|
+
"Harness id (default: agent.default setting, else first detected)"
|
|
7531
|
+
).option("--workspace <id>", "Configured workspace id to open").option("--dir <path>", "Explicit workspace directory to open").action(async (opts) => {
|
|
7532
|
+
await withDb2(async (db) => {
|
|
7533
|
+
let id = opts.id || await getSetting(db, "agent.default") || void 0;
|
|
7534
|
+
if (!id) {
|
|
7535
|
+
id = AGENT_HARNESSES.find((h) => resolveHarnessExecutable(h))?.id;
|
|
7536
|
+
}
|
|
7537
|
+
if (!id) {
|
|
7538
|
+
jsonError(
|
|
7539
|
+
"No agent harness configured or detected. Install one (Claude Code, Codex, opencode) or set agent.default."
|
|
7540
|
+
);
|
|
7541
|
+
}
|
|
7542
|
+
const harness = getHarness(id);
|
|
7543
|
+
if (!harness) {
|
|
7544
|
+
jsonError(`Unknown harness: ${id}`);
|
|
7545
|
+
}
|
|
7546
|
+
const override = await getSetting(db, `agent.${harness.id}.command`) || void 0;
|
|
7547
|
+
const executable = resolveHarnessExecutable(harness, override);
|
|
7548
|
+
if (!executable) {
|
|
7549
|
+
jsonError(
|
|
7550
|
+
`${harness.label} was not detected. Set its path: zam settings set agent.${harness.id}.command <path>`
|
|
7551
|
+
);
|
|
7552
|
+
}
|
|
7553
|
+
const configuredWorkspace = opts.workspace ? getConfiguredWorkspaces().find((item) => item.id === opts.workspace) : void 0;
|
|
7554
|
+
if (opts.workspace && !configuredWorkspace) {
|
|
7555
|
+
jsonError(`Workspace is not configured: ${opts.workspace}`);
|
|
7556
|
+
}
|
|
7557
|
+
let workspace = opts.dir || configuredWorkspace?.path || await getSetting(db, "personal.workspace_dir") || join17(homedir10(), "Documents", "zam");
|
|
7558
|
+
if (!existsSync17(workspace)) workspace = homedir10();
|
|
7559
|
+
launchHarness(harness, {
|
|
7560
|
+
executable,
|
|
7561
|
+
workspace,
|
|
7562
|
+
shell: normalizeShell(void 0),
|
|
7563
|
+
silent: true
|
|
7564
|
+
});
|
|
7565
|
+
jsonOut2({
|
|
7566
|
+
ok: true,
|
|
7567
|
+
id: harness.id,
|
|
7568
|
+
label: harness.label,
|
|
7569
|
+
kind: harness.kind,
|
|
7570
|
+
workspace
|
|
7571
|
+
});
|
|
7572
|
+
});
|
|
7573
|
+
});
|
|
6813
7574
|
bridgeCommand.command("get-review").description("Get next review card with prompt (JSON)").option("--user <id>", "User ID (default: whoami)").option("--no-resolve", "Skip resolving the token's source_link into context").action(async (opts) => {
|
|
6814
7575
|
await withDb2(async (db) => {
|
|
6815
7576
|
const userId = await resolveUser(opts, db, { json: true });
|
|
@@ -7168,7 +7929,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
7168
7929
|
"20"
|
|
7169
7930
|
).action(async (opts) => {
|
|
7170
7931
|
try {
|
|
7171
|
-
const monitorDir =
|
|
7932
|
+
const monitorDir = join17(homedir10(), ".zam", "monitor");
|
|
7172
7933
|
let files;
|
|
7173
7934
|
try {
|
|
7174
7935
|
files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
|
|
@@ -7181,7 +7942,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
7181
7942
|
return;
|
|
7182
7943
|
}
|
|
7183
7944
|
const limit = Number(opts.limit);
|
|
7184
|
-
const sorted = files.map((f) => ({ name: f, path:
|
|
7945
|
+
const sorted = files.map((f) => ({ name: f, path: join17(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
|
|
7185
7946
|
const sessionCommands = /* @__PURE__ */ new Map();
|
|
7186
7947
|
for (const file of sorted) {
|
|
7187
7948
|
const sessionId = file.name.replace(".jsonl", "");
|
|
@@ -7683,7 +8444,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
|
|
|
7683
8444
|
return;
|
|
7684
8445
|
}
|
|
7685
8446
|
}
|
|
7686
|
-
const outputPath = opts.image ?? opts.output ??
|
|
8447
|
+
const outputPath = opts.image ?? opts.output ?? join17(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
|
|
7687
8448
|
const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
|
|
7688
8449
|
if (!isProvided) {
|
|
7689
8450
|
const post = decidePostCapture(policy, {
|
|
@@ -7711,7 +8472,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
|
|
|
7711
8472
|
return;
|
|
7712
8473
|
}
|
|
7713
8474
|
}
|
|
7714
|
-
const imageBytes =
|
|
8475
|
+
const imageBytes = readFileSync11(outputPath);
|
|
7715
8476
|
const base64 = imageBytes.toString("base64");
|
|
7716
8477
|
jsonOut2({
|
|
7717
8478
|
sessionId: opts.session ?? null,
|
|
@@ -7738,11 +8499,11 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
7738
8499
|
return;
|
|
7739
8500
|
}
|
|
7740
8501
|
const sessionId = opts.session;
|
|
7741
|
-
const statePath =
|
|
8502
|
+
const statePath = join17(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
7742
8503
|
const defaultExt = platform === "win32" ? ".mkv" : ".mov";
|
|
7743
|
-
const outputPath = opts.output ??
|
|
7744
|
-
const { existsSync:
|
|
7745
|
-
if (
|
|
8504
|
+
const outputPath = opts.output ?? join17(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
|
|
8505
|
+
const { existsSync: existsSync25, writeFileSync: writeFileSync12, openSync, closeSync } = await import("fs");
|
|
8506
|
+
if (existsSync25(statePath)) {
|
|
7746
8507
|
jsonOut2({
|
|
7747
8508
|
sessionId,
|
|
7748
8509
|
started: false,
|
|
@@ -7750,7 +8511,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
7750
8511
|
});
|
|
7751
8512
|
return;
|
|
7752
8513
|
}
|
|
7753
|
-
const logPath =
|
|
8514
|
+
const logPath = join17(tmpdir3(), `zam-recording-${sessionId}.log`);
|
|
7754
8515
|
let logFd;
|
|
7755
8516
|
try {
|
|
7756
8517
|
logFd = openSync(logPath, "w");
|
|
@@ -7832,9 +8593,9 @@ bridgeCommand.command("stop-recording").description(
|
|
|
7832
8593
|
return;
|
|
7833
8594
|
}
|
|
7834
8595
|
const sessionId = opts.session;
|
|
7835
|
-
const statePath =
|
|
7836
|
-
const { existsSync:
|
|
7837
|
-
if (!
|
|
8596
|
+
const statePath = join17(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
8597
|
+
const { existsSync: existsSync25, readFileSync: readFileSync16, rmSync: rmSync3 } = await import("fs");
|
|
8598
|
+
if (!existsSync25(statePath)) {
|
|
7838
8599
|
jsonOut2({
|
|
7839
8600
|
sessionId,
|
|
7840
8601
|
stopped: false,
|
|
@@ -7842,7 +8603,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
7842
8603
|
});
|
|
7843
8604
|
return;
|
|
7844
8605
|
}
|
|
7845
|
-
const state = JSON.parse(
|
|
8606
|
+
const state = JSON.parse(readFileSync16(statePath, "utf8"));
|
|
7846
8607
|
const { pid, outputPath } = state;
|
|
7847
8608
|
try {
|
|
7848
8609
|
process.kill(pid, "SIGINT");
|
|
@@ -7858,7 +8619,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
7858
8619
|
};
|
|
7859
8620
|
let attempts = 0;
|
|
7860
8621
|
while (isProcessRunning(pid) && attempts < 20) {
|
|
7861
|
-
await new Promise((
|
|
8622
|
+
await new Promise((resolve8) => setTimeout(resolve8, 250));
|
|
7862
8623
|
attempts++;
|
|
7863
8624
|
}
|
|
7864
8625
|
if (isProcessRunning(pid)) {
|
|
@@ -7871,7 +8632,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
7871
8632
|
rmSync3(statePath, { force: true });
|
|
7872
8633
|
} catch {
|
|
7873
8634
|
}
|
|
7874
|
-
if (!
|
|
8635
|
+
if (!existsSync25(outputPath)) {
|
|
7875
8636
|
jsonOut2({
|
|
7876
8637
|
sessionId,
|
|
7877
8638
|
stopped: false,
|
|
@@ -7962,6 +8723,16 @@ bridgeCommand.command("check-llm").description("Check if LLM is enabled and onli
|
|
|
7962
8723
|
});
|
|
7963
8724
|
});
|
|
7964
8725
|
});
|
|
8726
|
+
bridgeCommand.command("provider-status").description("Show secret-safe provider status for LLM roles (JSON)").action(async () => {
|
|
8727
|
+
await withDb2(async (db) => {
|
|
8728
|
+
const [recall, vision, text] = await Promise.all([
|
|
8729
|
+
getProviderRoleStatus(db, "recall"),
|
|
8730
|
+
getProviderRoleStatus(db, "vision"),
|
|
8731
|
+
getProviderRoleStatus(db, "text")
|
|
8732
|
+
]);
|
|
8733
|
+
jsonOut2({ roles: { recall, vision, text } });
|
|
8734
|
+
});
|
|
8735
|
+
});
|
|
7965
8736
|
bridgeCommand.command("check-vision").description(
|
|
7966
8737
|
"Check if UI observer vision analysis is enabled and ready (JSON)"
|
|
7967
8738
|
).action(async () => {
|
|
@@ -8274,8 +9045,8 @@ bridgeCommand.command("serve").description("Start the persistent JSON-RPC stdin/
|
|
|
8274
9045
|
});
|
|
8275
9046
|
|
|
8276
9047
|
// src/cli/commands/card.ts
|
|
8277
|
-
import { Command as
|
|
8278
|
-
var cardCommand = new
|
|
9048
|
+
import { Command as Command5 } from "commander";
|
|
9049
|
+
var cardCommand = new Command5("card").description(
|
|
8279
9050
|
"Manage spaced-repetition cards"
|
|
8280
9051
|
);
|
|
8281
9052
|
cardCommand.command("due").description("Show due tokens for a user").option("--user <id>", "User ID (default: whoami)").option("--domain <domain>", "Filter by knowledge domain").option("--json", "Output as JSON").option("--summary", "Show only counts per domain (no slugs or concepts)").action(async (opts) => {
|
|
@@ -8474,8 +9245,8 @@ cardCommand.command("delete").description("Delete one user's card for a token").
|
|
|
8474
9245
|
|
|
8475
9246
|
// src/cli/commands/connector.ts
|
|
8476
9247
|
import { input as input2, password } from "@inquirer/prompts";
|
|
8477
|
-
import { Command as
|
|
8478
|
-
var connectorCommand = new
|
|
9248
|
+
import { Command as Command6 } from "commander";
|
|
9249
|
+
var connectorCommand = new Command6("connector").description(
|
|
8479
9250
|
"Manage external service connectors"
|
|
8480
9251
|
);
|
|
8481
9252
|
connectorCommand.command("setup").description("Configure a connector").argument("<type>", "Connector type (ado, turso)").option("--url <url>", "Turso database URL (non-interactive)").option("--token <token>", "Turso auth token (non-interactive)").option(
|
|
@@ -8618,26 +9389,26 @@ async function setupTurso(urlArg, tokenArg, mode) {
|
|
|
8618
9389
|
|
|
8619
9390
|
// src/cli/commands/git-sync.ts
|
|
8620
9391
|
import { execSync as execSync5 } from "child_process";
|
|
8621
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
8622
|
-
import { join as
|
|
8623
|
-
import { Command as
|
|
9392
|
+
import { chmodSync as chmodSync2, existsSync as existsSync18, writeFileSync as writeFileSync9 } from "fs";
|
|
9393
|
+
import { join as join18 } from "path";
|
|
9394
|
+
import { Command as Command7 } from "commander";
|
|
8624
9395
|
function installHook2() {
|
|
8625
|
-
const gitDir =
|
|
8626
|
-
if (!
|
|
9396
|
+
const gitDir = join18(process.cwd(), ".git");
|
|
9397
|
+
if (!existsSync18(gitDir)) {
|
|
8627
9398
|
console.error(
|
|
8628
9399
|
"Error: Current directory is not the root of a Git repository."
|
|
8629
9400
|
);
|
|
8630
9401
|
process.exit(1);
|
|
8631
9402
|
}
|
|
8632
|
-
const hooksDir =
|
|
8633
|
-
const hookPath =
|
|
9403
|
+
const hooksDir = join18(gitDir, "hooks");
|
|
9404
|
+
const hookPath = join18(hooksDir, "post-commit");
|
|
8634
9405
|
const hookContent = `#!/bin/sh
|
|
8635
9406
|
# ZAM Spaced Repetition Auto-Stale Hook
|
|
8636
9407
|
# Triggered automatically on git commits to decay modified concept cards.
|
|
8637
9408
|
zam git-sync --commit HEAD --quiet
|
|
8638
9409
|
`;
|
|
8639
9410
|
try {
|
|
8640
|
-
|
|
9411
|
+
writeFileSync9(hookPath, hookContent, { encoding: "utf-8", flag: "w" });
|
|
8641
9412
|
try {
|
|
8642
9413
|
chmodSync2(hookPath, "755");
|
|
8643
9414
|
} catch (_e) {
|
|
@@ -8650,7 +9421,7 @@ zam git-sync --commit HEAD --quiet
|
|
|
8650
9421
|
process.exit(1);
|
|
8651
9422
|
}
|
|
8652
9423
|
}
|
|
8653
|
-
var gitSyncCommand = new
|
|
9424
|
+
var gitSyncCommand = new Command7("git-sync").description("Sync learning cards with recent Git file modifications").option("--commit <hash>", "Git commit hash to check", "HEAD").option("--user <id>", "User ID (default: whoami)").option("--install", "Install git post-commit hook in current repo").option("--quiet", "Suppress verbose output").action(async (opts) => {
|
|
8654
9425
|
if (opts.install) {
|
|
8655
9426
|
installHook2();
|
|
8656
9427
|
return;
|
|
@@ -8739,10 +9510,10 @@ ZAM Auto-Stale Complete: Scanned ${changedFiles.length} file(s).`
|
|
|
8739
9510
|
});
|
|
8740
9511
|
|
|
8741
9512
|
// src/cli/commands/goal.ts
|
|
8742
|
-
import { existsSync as
|
|
8743
|
-
import { resolve as
|
|
9513
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync10 } from "fs";
|
|
9514
|
+
import { resolve as resolve6 } from "path";
|
|
8744
9515
|
import { input as input3 } from "@inquirer/prompts";
|
|
8745
|
-
import { Command as
|
|
9516
|
+
import { Command as Command8 } from "commander";
|
|
8746
9517
|
async function resolveGoalsDir() {
|
|
8747
9518
|
let goalsDir;
|
|
8748
9519
|
let db;
|
|
@@ -8753,9 +9524,9 @@ async function resolveGoalsDir() {
|
|
|
8753
9524
|
} finally {
|
|
8754
9525
|
await db?.close();
|
|
8755
9526
|
}
|
|
8756
|
-
return goalsDir ?
|
|
9527
|
+
return goalsDir ? resolve6(goalsDir) : resolve6("goals");
|
|
8757
9528
|
}
|
|
8758
|
-
var goalCommand = new
|
|
9529
|
+
var goalCommand = new Command8("goal").description(
|
|
8759
9530
|
"Manage learning goals (markdown files)"
|
|
8760
9531
|
);
|
|
8761
9532
|
goalCommand.command("list").description("List all goals").option(
|
|
@@ -8763,7 +9534,7 @@ goalCommand.command("list").description("List all goals").option(
|
|
|
8763
9534
|
"Filter by status (active, completed, paused, abandoned)"
|
|
8764
9535
|
).option("--tree", "Show goals as a tree with parent/child relationships").option("--json", "Output as JSON").action(async (opts) => {
|
|
8765
9536
|
const goalsDir = await resolveGoalsDir();
|
|
8766
|
-
if (!
|
|
9537
|
+
if (!existsSync19(goalsDir)) {
|
|
8767
9538
|
console.error(`Goals directory not found: ${goalsDir}`);
|
|
8768
9539
|
console.error(
|
|
8769
9540
|
"Set it with: zam settings set personal.goals_dir /path/to/goals"
|
|
@@ -8864,8 +9635,8 @@ ${"\u2500".repeat(50)}`);
|
|
|
8864
9635
|
});
|
|
8865
9636
|
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) => {
|
|
8866
9637
|
const goalsDir = await resolveGoalsDir();
|
|
8867
|
-
if (!
|
|
8868
|
-
|
|
9638
|
+
if (!existsSync19(goalsDir)) {
|
|
9639
|
+
mkdirSync10(goalsDir, { recursive: true });
|
|
8869
9640
|
}
|
|
8870
9641
|
let slug = opts.slug;
|
|
8871
9642
|
let title = opts.title;
|
|
@@ -8924,22 +9695,22 @@ goalCommand.command("status <slug> <status>").description("Update a goal's statu
|
|
|
8924
9695
|
});
|
|
8925
9696
|
|
|
8926
9697
|
// src/cli/commands/init.ts
|
|
8927
|
-
import { existsSync as
|
|
9698
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "fs";
|
|
8928
9699
|
import { homedir as homedir11 } from "os";
|
|
8929
|
-
import { join as
|
|
9700
|
+
import { join as join19 } from "path";
|
|
8930
9701
|
import { confirm as confirm2, input as input4 } from "@inquirer/prompts";
|
|
8931
|
-
import { Command as
|
|
9702
|
+
import { Command as Command9 } from "commander";
|
|
8932
9703
|
var HOME2 = homedir11();
|
|
8933
9704
|
function printLine(char = "\u2550", len = 60, color = "\x1B[36m") {
|
|
8934
9705
|
console.log(`${color}${char.repeat(len)}\x1B[0m`);
|
|
8935
9706
|
}
|
|
8936
9707
|
function bootstrapSandboxWorkspace(workspaceDir) {
|
|
8937
|
-
|
|
8938
|
-
|
|
8939
|
-
|
|
8940
|
-
const worldviewFile =
|
|
8941
|
-
if (!
|
|
8942
|
-
|
|
9708
|
+
mkdirSync11(join19(workspaceDir, "beliefs"), { recursive: true });
|
|
9709
|
+
mkdirSync11(join19(workspaceDir, "goals"), { recursive: true });
|
|
9710
|
+
mkdirSync11(join19(workspaceDir, "skills"), { recursive: true });
|
|
9711
|
+
const worldviewFile = join19(workspaceDir, "beliefs", "worldview.md");
|
|
9712
|
+
if (!existsSync20(worldviewFile)) {
|
|
9713
|
+
writeFileSync10(
|
|
8943
9714
|
worldviewFile,
|
|
8944
9715
|
`# Personal Worldview
|
|
8945
9716
|
|
|
@@ -8951,9 +9722,9 @@ Here, I declare the core concepts and principles I want to master.
|
|
|
8951
9722
|
"utf8"
|
|
8952
9723
|
);
|
|
8953
9724
|
}
|
|
8954
|
-
const goalsFile =
|
|
8955
|
-
if (!
|
|
8956
|
-
|
|
9725
|
+
const goalsFile = join19(workspaceDir, "goals", "goals.md");
|
|
9726
|
+
if (!existsSync20(goalsFile)) {
|
|
9727
|
+
writeFileSync10(
|
|
8957
9728
|
goalsFile,
|
|
8958
9729
|
`# Personal Goals
|
|
8959
9730
|
|
|
@@ -8965,7 +9736,7 @@ Here, I declare the core concepts and principles I want to master.
|
|
|
8965
9736
|
);
|
|
8966
9737
|
}
|
|
8967
9738
|
}
|
|
8968
|
-
var initCommand = new
|
|
9739
|
+
var initCommand = new Command9("init").description("Launch the guided interactive onboarding wizard").action(async () => {
|
|
8969
9740
|
printLine();
|
|
8970
9741
|
console.log(
|
|
8971
9742
|
"\x1B[1m\x1B[32m ZAM \u2014 The Symbiotic Learning Agent Onboarding\x1B[0m"
|
|
@@ -8975,7 +9746,7 @@ var initCommand = new Command8("init").description("Launch the guided interactiv
|
|
|
8975
9746
|
);
|
|
8976
9747
|
printLine();
|
|
8977
9748
|
console.log("\n\x1B[1m[1/5] Setting up Local Workspace Sandbox\x1B[0m");
|
|
8978
|
-
const defaultWorkspace =
|
|
9749
|
+
const defaultWorkspace = join19(HOME2, "Documents", "zam");
|
|
8979
9750
|
const workspacePath = await input4({
|
|
8980
9751
|
message: "Choose your ZAM workspace directory:",
|
|
8981
9752
|
default: defaultWorkspace
|
|
@@ -9132,7 +9903,7 @@ var initCommand = new Command8("init").description("Launch the guided interactiv
|
|
|
9132
9903
|
|
|
9133
9904
|
// src/cli/commands/learn.ts
|
|
9134
9905
|
import { input as input6 } from "@inquirer/prompts";
|
|
9135
|
-
import { Command as
|
|
9906
|
+
import { Command as Command10 } from "commander";
|
|
9136
9907
|
|
|
9137
9908
|
// src/cli/learn-format.ts
|
|
9138
9909
|
var BLOOM_VERBS3 = {
|
|
@@ -9470,7 +10241,7 @@ var STOP_WORDS = /* @__PURE__ */ new Set(["q", ":q", "quit", "stop"]);
|
|
|
9470
10241
|
function isExitPrompt(err) {
|
|
9471
10242
|
return err instanceof Error && err.name === "ExitPromptError";
|
|
9472
10243
|
}
|
|
9473
|
-
var learnCommand = new
|
|
10244
|
+
var learnCommand = new Command10("learn").description(
|
|
9474
10245
|
"Run a spoiler-free, in-process learning session (recall \u2192 reveal \u2192 self-rate)"
|
|
9475
10246
|
).option("--user <id>", "User ID (default: whoami)").option("--max-new <n>", "Maximum new cards", "10").option("--max-reviews <n>", "Maximum review cards", "50").option("--no-resolve", "Skip resolving source_link into the revealed answer").action(async (opts) => {
|
|
9476
10247
|
let db;
|
|
@@ -9714,8 +10485,8 @@ learnCommand.command("open").description("Open a new terminal window running zam
|
|
|
9714
10485
|
});
|
|
9715
10486
|
|
|
9716
10487
|
// src/cli/commands/monitor.ts
|
|
9717
|
-
import { Command as
|
|
9718
|
-
var monitorCommand = new
|
|
10488
|
+
import { Command as Command11 } from "commander";
|
|
10489
|
+
var monitorCommand = new Command11("monitor").description(
|
|
9719
10490
|
"Shell observation for real-time task monitoring"
|
|
9720
10491
|
);
|
|
9721
10492
|
monitorCommand.command("start").description("Output shell hook code to install monitoring").requiredOption("--session <id>", "Session ID to monitor").option(
|
|
@@ -9897,8 +10668,8 @@ monitorCommand.command("open").description("Open a new monitored terminal window
|
|
|
9897
10668
|
});
|
|
9898
10669
|
|
|
9899
10670
|
// src/cli/commands/observer.ts
|
|
9900
|
-
import { Command as
|
|
9901
|
-
var observerCommand = new
|
|
10671
|
+
import { Command as Command12 } from "commander";
|
|
10672
|
+
var observerCommand = new Command12("observer").description(
|
|
9902
10673
|
"Configure what the UI observer may capture (Layer 2 policy)"
|
|
9903
10674
|
);
|
|
9904
10675
|
function applyObserverListChange(current, entry, op) {
|
|
@@ -9961,8 +10732,8 @@ observerCommand.command("revoke <process>").description("Remove a process from o
|
|
|
9961
10732
|
|
|
9962
10733
|
// src/cli/commands/profile.ts
|
|
9963
10734
|
import { homedir as homedir12 } from "os";
|
|
9964
|
-
import { dirname as
|
|
9965
|
-
import { Command as
|
|
10735
|
+
import { dirname as dirname6, join as join20, resolve as resolve7 } from "path";
|
|
10736
|
+
import { Command as Command13 } from "commander";
|
|
9966
10737
|
var C2 = {
|
|
9967
10738
|
reset: "\x1B[0m",
|
|
9968
10739
|
bold: "\x1B[1m",
|
|
@@ -9971,7 +10742,7 @@ var C2 = {
|
|
|
9971
10742
|
green: "\x1B[32m"
|
|
9972
10743
|
};
|
|
9973
10744
|
function defaultPersonalDir() {
|
|
9974
|
-
return
|
|
10745
|
+
return join20(homedir12(), "Documents", "zam");
|
|
9975
10746
|
}
|
|
9976
10747
|
function render(profile) {
|
|
9977
10748
|
const sync = profile.syncProvider ? `${C2.green}${profile.syncProvider}${C2.reset} ${C2.dim}(good for cross-device snapshots)${C2.reset}` : `${C2.dim}local folder (use a synced folder or snapshots to move between machines)${C2.reset}`;
|
|
@@ -9982,7 +10753,7 @@ function render(profile) {
|
|
|
9982
10753
|
console.log(` data dir: ${C2.cyan}${profile.dataDir}${C2.reset}`);
|
|
9983
10754
|
console.log(` database: ${C2.cyan}${profile.dbPath}${C2.reset}`);
|
|
9984
10755
|
}
|
|
9985
|
-
var profileCommand = new
|
|
10756
|
+
var profileCommand = new Command13("profile").description("Show or change this machine's ZAM install profile").option("--mode <mode>", "Set install mode: developer | default").option("--dir <path>", "Set the personal-content folder").option("--json", "Output as JSON").action(async (opts) => {
|
|
9986
10757
|
if (opts.mode && opts.mode !== "developer" && opts.mode !== "default") {
|
|
9987
10758
|
console.error(`Invalid --mode: ${opts.mode}. Use developer or default.`);
|
|
9988
10759
|
process.exit(1);
|
|
@@ -9992,7 +10763,7 @@ var profileCommand = new Command12("profile").description("Show or change this m
|
|
|
9992
10763
|
if (opts.mode) setInstallMode(opts.mode);
|
|
9993
10764
|
db = await openDatabaseWithSync({ initialize: true });
|
|
9994
10765
|
if (opts.dir) {
|
|
9995
|
-
await setSetting(db, "personal.workspace_dir",
|
|
10766
|
+
await setSetting(db, "personal.workspace_dir", resolve7(opts.dir));
|
|
9996
10767
|
}
|
|
9997
10768
|
const personalDir = await getSetting(db, "personal.workspace_dir") || defaultPersonalDir();
|
|
9998
10769
|
await db.close();
|
|
@@ -10002,7 +10773,7 @@ var profileCommand = new Command12("profile").description("Show or change this m
|
|
|
10002
10773
|
mode: getInstallMode(),
|
|
10003
10774
|
personalDir,
|
|
10004
10775
|
syncProvider: detectSyncProvider(personalDir),
|
|
10005
|
-
dataDir:
|
|
10776
|
+
dataDir: dirname6(dbPath),
|
|
10006
10777
|
dbPath
|
|
10007
10778
|
};
|
|
10008
10779
|
if (opts.json) {
|
|
@@ -10019,7 +10790,7 @@ var profileCommand = new Command12("profile").description("Show or change this m
|
|
|
10019
10790
|
|
|
10020
10791
|
// src/cli/commands/provider.ts
|
|
10021
10792
|
import { password as password2 } from "@inquirer/prompts";
|
|
10022
|
-
import { Command as
|
|
10793
|
+
import { Command as Command14 } from "commander";
|
|
10023
10794
|
var VALID_API_FLAVORS = [
|
|
10024
10795
|
"chat-completions",
|
|
10025
10796
|
"anthropic-messages"
|
|
@@ -10031,6 +10802,9 @@ function upsertProviderRecord(providers, name, patch) {
|
|
|
10031
10802
|
if (patch.model !== void 0) merged.model = patch.model;
|
|
10032
10803
|
if (patch.apiFlavor !== void 0) merged.apiFlavor = patch.apiFlavor;
|
|
10033
10804
|
if (patch.apiKeyRef !== void 0) merged.apiKeyRef = patch.apiKeyRef;
|
|
10805
|
+
if (patch.label !== void 0) merged.label = patch.label;
|
|
10806
|
+
if (patch.local !== void 0) merged.local = patch.local;
|
|
10807
|
+
if (patch.runner !== void 0) merged.runner = patch.runner;
|
|
10034
10808
|
return { ...providers, [name]: merged };
|
|
10035
10809
|
}
|
|
10036
10810
|
function removeProviderRecord(providers, name) {
|
|
@@ -10059,7 +10833,7 @@ function buildProviderListing(providers, hasKey) {
|
|
|
10059
10833
|
let keyState;
|
|
10060
10834
|
if (!rec.apiKeyRef) keyState = "none";
|
|
10061
10835
|
else keyState = hasKey(rec.apiKeyRef) ? "set" : "missing";
|
|
10062
|
-
|
|
10836
|
+
const row = {
|
|
10063
10837
|
name,
|
|
10064
10838
|
url: rec.url,
|
|
10065
10839
|
model: rec.model,
|
|
@@ -10067,6 +10841,10 @@ function buildProviderListing(providers, hasKey) {
|
|
|
10067
10841
|
apiKeyRef: rec.apiKeyRef,
|
|
10068
10842
|
keyState
|
|
10069
10843
|
};
|
|
10844
|
+
if (rec.label !== void 0) row.label = rec.label;
|
|
10845
|
+
if (rec.local !== void 0) row.local = rec.local;
|
|
10846
|
+
if (rec.runner !== void 0) row.runner = rec.runner;
|
|
10847
|
+
return row;
|
|
10070
10848
|
});
|
|
10071
10849
|
}
|
|
10072
10850
|
function findOrphanKeyRefs(storedRefs, providers) {
|
|
@@ -10086,19 +10864,53 @@ async function readJson(db, key, fallback) {
|
|
|
10086
10864
|
}
|
|
10087
10865
|
var readProviders = (db) => readJson(db, "llm.providers", {});
|
|
10088
10866
|
var readRoles = (db) => readJson(db, "llm.roles", {});
|
|
10089
|
-
async function
|
|
10867
|
+
async function readScopedProviders(db, machine) {
|
|
10868
|
+
if (machine) return getMachineAiConfig().providers ?? {};
|
|
10869
|
+
if (!db)
|
|
10870
|
+
throw new Error("Database is required for shared provider settings.");
|
|
10871
|
+
return readProviders(db);
|
|
10872
|
+
}
|
|
10873
|
+
async function readScopedRoles(db, machine) {
|
|
10874
|
+
if (machine) return getMachineAiConfig().roles ?? {};
|
|
10875
|
+
if (!db)
|
|
10876
|
+
throw new Error("Database is required for shared provider settings.");
|
|
10877
|
+
return readRoles(db);
|
|
10878
|
+
}
|
|
10879
|
+
async function writeScopedProviders(db, machine, p) {
|
|
10880
|
+
if (machine) {
|
|
10881
|
+
const config = getMachineAiConfig();
|
|
10882
|
+
saveMachineAiConfig({ ...config, providers: p });
|
|
10883
|
+
return;
|
|
10884
|
+
}
|
|
10885
|
+
if (!db)
|
|
10886
|
+
throw new Error("Database is required for shared provider settings.");
|
|
10090
10887
|
await setSetting(db, "llm.providers", JSON.stringify(p));
|
|
10091
10888
|
}
|
|
10092
|
-
async function
|
|
10889
|
+
async function writeScopedRoles(db, machine, r) {
|
|
10890
|
+
if (machine) {
|
|
10891
|
+
const config = getMachineAiConfig();
|
|
10892
|
+
saveMachineAiConfig({ ...config, roles: r });
|
|
10893
|
+
return;
|
|
10894
|
+
}
|
|
10895
|
+
if (!db)
|
|
10896
|
+
throw new Error("Database is required for shared provider settings.");
|
|
10093
10897
|
await setSetting(db, "llm.roles", JSON.stringify(r));
|
|
10094
10898
|
}
|
|
10095
|
-
|
|
10899
|
+
async function withProviderScope(machine, action) {
|
|
10900
|
+
if (machine) {
|
|
10901
|
+
await action(void 0);
|
|
10902
|
+
return;
|
|
10903
|
+
}
|
|
10904
|
+
await withDb(action);
|
|
10905
|
+
}
|
|
10906
|
+
var providerCommand = new Command14("provider").description(
|
|
10096
10907
|
"Configure role-based AI providers (url/model/flavor/key, per role)"
|
|
10097
10908
|
);
|
|
10098
|
-
providerCommand.command("list").description("Show configured providers, role bindings, and key status").option("--json", "Output as JSON").action(async (opts) => {
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
const
|
|
10909
|
+
providerCommand.command("list").description("Show configured providers, role bindings, and key status").option("--json", "Output as JSON").option("--machine", "Read machine-local providers from ~/.zam/config.json").action(async (opts) => {
|
|
10910
|
+
const machine = Boolean(opts.machine);
|
|
10911
|
+
await withProviderScope(machine, async (db) => {
|
|
10912
|
+
const providers = await readScopedProviders(db, machine);
|
|
10913
|
+
const roles = await readScopedRoles(db, machine);
|
|
10102
10914
|
const rows = buildProviderListing(
|
|
10103
10915
|
providers,
|
|
10104
10916
|
(ref) => getProviderApiKey(ref) !== null
|
|
@@ -10106,11 +10918,23 @@ providerCommand.command("list").description("Show configured providers, role bin
|
|
|
10106
10918
|
const orphans = findOrphanKeyRefs(listProviderApiKeyRefs(), providers);
|
|
10107
10919
|
if (opts.json) {
|
|
10108
10920
|
console.log(
|
|
10109
|
-
JSON.stringify(
|
|
10921
|
+
JSON.stringify(
|
|
10922
|
+
{
|
|
10923
|
+
scope: machine ? "machine" : "shared",
|
|
10924
|
+
providers: rows,
|
|
10925
|
+
roles,
|
|
10926
|
+
orphans
|
|
10927
|
+
},
|
|
10928
|
+
null,
|
|
10929
|
+
2
|
|
10930
|
+
)
|
|
10110
10931
|
);
|
|
10111
10932
|
return;
|
|
10112
10933
|
}
|
|
10113
|
-
console.log(
|
|
10934
|
+
console.log(
|
|
10935
|
+
`Providers (${opts.machine ? "~/.zam/config.json ai.providers" : "llm.providers"}):
|
|
10936
|
+
`
|
|
10937
|
+
);
|
|
10114
10938
|
if (rows.length === 0) {
|
|
10115
10939
|
console.log(
|
|
10116
10940
|
" (none) \u2014 add one: zam provider add <name> --url <url> --model <model>"
|
|
@@ -10122,6 +10946,11 @@ providerCommand.command("list").description("Show configured providers, role bin
|
|
|
10122
10946
|
console.log(` url: ${row.url ?? "(inherits llm.url)"}`);
|
|
10123
10947
|
console.log(` model: ${row.model ?? "(inherits llm.model)"}`);
|
|
10124
10948
|
console.log(` flavor: ${row.apiFlavor}`);
|
|
10949
|
+
if (row.label) console.log(` label: ${row.label}`);
|
|
10950
|
+
if (row.local !== void 0) {
|
|
10951
|
+
console.log(` local: ${row.local ? "yes" : "no"}`);
|
|
10952
|
+
}
|
|
10953
|
+
if (row.runner) console.log(` runner: ${row.runner}`);
|
|
10125
10954
|
if (row.apiKeyRef) console.log(` key-ref: ${row.apiKeyRef}`);
|
|
10126
10955
|
}
|
|
10127
10956
|
}
|
|
@@ -10143,7 +10972,10 @@ providerCommand.command("list").description("Show configured providers, role bin
|
|
|
10143
10972
|
}
|
|
10144
10973
|
});
|
|
10145
10974
|
});
|
|
10146
|
-
providerCommand.command("add <name>").description("Add or update a provider record in llm.providers").option("--url <url>", "Endpoint base URL (e.g. https://api.deepseek.com/v1)").option("--model <model>", "Model id (e.g. deepseek-v4-flash)").option(
|
|
10975
|
+
providerCommand.command("add <name>").description("Add or update a provider record in llm.providers").option("--label <label>", "Human-readable provider label").option("--url <url>", "Endpoint base URL (e.g. https://api.deepseek.com/v1)").option("--model <model>", "Model id (e.g. deepseek-v4-flash)").option("--local", "Mark this provider as a local/on-device endpoint").option(
|
|
10976
|
+
"--runner <runner>",
|
|
10977
|
+
"Local runner hint (flm, foundry-local, ollama, ...)"
|
|
10978
|
+
).option(
|
|
10147
10979
|
"--flavor <flavor>",
|
|
10148
10980
|
`Wire protocol: ${VALID_API_FLAVORS.join(" | ")} (default: inferred from URL)`
|
|
10149
10981
|
).option(
|
|
@@ -10152,7 +10984,7 @@ providerCommand.command("add <name>").description("Add or update a provider reco
|
|
|
10152
10984
|
).option(
|
|
10153
10985
|
"--key <value>",
|
|
10154
10986
|
"Store this API key now (prefer `set-key` to keep it out of shell history)"
|
|
10155
|
-
).action(async (name, opts) => {
|
|
10987
|
+
).option("--machine", "Store this provider in ~/.zam/config.json").action(async (name, opts) => {
|
|
10156
10988
|
let apiFlavor;
|
|
10157
10989
|
if (opts.flavor) {
|
|
10158
10990
|
if (!VALID_API_FLAVORS.includes(opts.flavor)) {
|
|
@@ -10164,20 +10996,31 @@ providerCommand.command("add <name>").description("Add or update a provider reco
|
|
|
10164
10996
|
apiFlavor = opts.flavor;
|
|
10165
10997
|
}
|
|
10166
10998
|
const apiKeyRef = opts.keyRef ?? (opts.key ? name : void 0);
|
|
10167
|
-
|
|
10168
|
-
|
|
10999
|
+
const machine = Boolean(opts.machine);
|
|
11000
|
+
await withProviderScope(machine, async (db) => {
|
|
11001
|
+
const providers = await readScopedProviders(db, machine);
|
|
10169
11002
|
const next = upsertProviderRecord(providers, name, {
|
|
11003
|
+
label: opts.label,
|
|
10170
11004
|
url: opts.url,
|
|
10171
11005
|
model: opts.model,
|
|
10172
11006
|
apiFlavor,
|
|
10173
|
-
apiKeyRef
|
|
11007
|
+
apiKeyRef,
|
|
11008
|
+
local: opts.local ? true : void 0,
|
|
11009
|
+
runner: opts.runner
|
|
10174
11010
|
});
|
|
10175
|
-
await
|
|
11011
|
+
await writeScopedProviders(db, machine, next);
|
|
10176
11012
|
if (opts.key && apiKeyRef) setProviderApiKey(apiKeyRef, opts.key);
|
|
10177
11013
|
const rec = next[name];
|
|
10178
|
-
console.log(
|
|
11014
|
+
console.log(
|
|
11015
|
+
`Provider "${name}" saved (${machine ? "machine-local" : "shared"}):`
|
|
11016
|
+
);
|
|
10179
11017
|
console.log(` url: ${rec.url ?? "(inherits llm.url)"}`);
|
|
10180
11018
|
console.log(` model: ${rec.model ?? "(inherits llm.model)"}`);
|
|
11019
|
+
if (rec.label) console.log(` label: ${rec.label}`);
|
|
11020
|
+
if (rec.local !== void 0) {
|
|
11021
|
+
console.log(` local: ${rec.local ? "yes" : "no"}`);
|
|
11022
|
+
}
|
|
11023
|
+
if (rec.runner) console.log(` runner: ${rec.runner}`);
|
|
10181
11024
|
console.log(
|
|
10182
11025
|
` flavor: ${rec.apiFlavor ?? `${rec.url ? inferApiFlavor(rec.url) : "chat-completions"} (inferred)`}`
|
|
10183
11026
|
);
|
|
@@ -10201,9 +11044,10 @@ Bind it to a role: zam provider use recall --primary ${name}`
|
|
|
10201
11044
|
);
|
|
10202
11045
|
});
|
|
10203
11046
|
});
|
|
10204
|
-
providerCommand.command("remove <name>").description("Remove a provider record from llm.providers").action(async (name) => {
|
|
10205
|
-
|
|
10206
|
-
|
|
11047
|
+
providerCommand.command("remove <name>").description("Remove a provider record from llm.providers").option("--machine", "Remove from ~/.zam/config.json instead of the DB").action(async (name, opts) => {
|
|
11048
|
+
const machine = Boolean(opts.machine);
|
|
11049
|
+
await withProviderScope(machine, async (db) => {
|
|
11050
|
+
const providers = await readScopedProviders(db, machine);
|
|
10207
11051
|
const { providers: next, removed } = removeProviderRecord(
|
|
10208
11052
|
providers,
|
|
10209
11053
|
name
|
|
@@ -10212,9 +11056,14 @@ providerCommand.command("remove <name>").description("Remove a provider record f
|
|
|
10212
11056
|
console.log(`No such provider: ${name}`);
|
|
10213
11057
|
return;
|
|
10214
11058
|
}
|
|
10215
|
-
await
|
|
10216
|
-
console.log(
|
|
10217
|
-
|
|
11059
|
+
await writeScopedProviders(db, machine, next);
|
|
11060
|
+
console.log(
|
|
11061
|
+
`Removed provider "${name}" (${machine ? "machine-local" : "shared"}).`
|
|
11062
|
+
);
|
|
11063
|
+
const referencing = rolesReferencing(
|
|
11064
|
+
await readScopedRoles(db, machine),
|
|
11065
|
+
name
|
|
11066
|
+
);
|
|
10218
11067
|
if (referencing.length > 0) {
|
|
10219
11068
|
console.log(
|
|
10220
11069
|
` \u26A0 Still referenced by role(s): ${referencing.join(", ")} \u2014 rebind with: zam provider use <role> --primary <name>`
|
|
@@ -10222,7 +11071,7 @@ providerCommand.command("remove <name>").description("Remove a provider record f
|
|
|
10222
11071
|
}
|
|
10223
11072
|
});
|
|
10224
11073
|
});
|
|
10225
|
-
providerCommand.command("use <role>").description(`Bind providers to a role (${VALID_ROLES.join(" | ")})`).option("--primary <name>", "Primary provider").option("--fallback <name>", "Fallback provider (optional)").action(async (role, opts) => {
|
|
11074
|
+
providerCommand.command("use <role>").description(`Bind providers to a role (${VALID_ROLES.join(" | ")})`).option("--primary <name>", "Primary provider").option("--fallback <name>", "Fallback provider (optional)").option("--machine", "Bind the role in ~/.zam/config.json").action(async (role, opts) => {
|
|
10226
11075
|
if (!VALID_ROLES.includes(role)) {
|
|
10227
11076
|
console.error(`Invalid role: ${role}. Use ${VALID_ROLES.join(", ")}.`);
|
|
10228
11077
|
process.exit(1);
|
|
@@ -10231,19 +11080,23 @@ providerCommand.command("use <role>").description(`Bind providers to a role (${V
|
|
|
10231
11080
|
console.error("--primary is required.");
|
|
10232
11081
|
process.exit(1);
|
|
10233
11082
|
}
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
await
|
|
11083
|
+
const machine = Boolean(opts.machine);
|
|
11084
|
+
await withProviderScope(machine, async (db) => {
|
|
11085
|
+
const providers = await readScopedProviders(db, machine);
|
|
11086
|
+
await writeScopedRoles(
|
|
10237
11087
|
db,
|
|
11088
|
+
machine,
|
|
10238
11089
|
bindRoleProviders(
|
|
10239
|
-
await
|
|
11090
|
+
await readScopedRoles(db, machine),
|
|
10240
11091
|
role,
|
|
10241
11092
|
opts.primary,
|
|
10242
11093
|
opts.fallback
|
|
10243
11094
|
)
|
|
10244
11095
|
);
|
|
10245
11096
|
const fb = opts.fallback ? `, fallback: ${opts.fallback}` : "";
|
|
10246
|
-
console.log(
|
|
11097
|
+
console.log(
|
|
11098
|
+
`Role "${role}" \u2192 primary: ${opts.primary}${fb} (${machine ? "machine-local" : "shared"})`
|
|
11099
|
+
);
|
|
10247
11100
|
for (const [label, ref] of [
|
|
10248
11101
|
["primary", opts.primary],
|
|
10249
11102
|
["fallback", opts.fallback]
|
|
@@ -10285,8 +11138,8 @@ providerCommand.command("clear-key <ref>").description("Remove a stored API key"
|
|
|
10285
11138
|
});
|
|
10286
11139
|
|
|
10287
11140
|
// src/cli/commands/review.ts
|
|
10288
|
-
import { Command as
|
|
10289
|
-
var reviewCommand = new
|
|
11141
|
+
import { Command as Command15 } from "commander";
|
|
11142
|
+
var reviewCommand = new Command15("review").description("Start an interactive review session").option("--user <id>", "User ID (default: whoami)").option("--max-new <n>", "Maximum new cards", "10").option("--max-reviews <n>", "Maximum review cards", "50").option("--no-resolve", "Skip resolving source_link into inline context").action(async (opts) => {
|
|
10290
11143
|
let db;
|
|
10291
11144
|
try {
|
|
10292
11145
|
db = await openDatabase();
|
|
@@ -10399,10 +11252,10 @@ ${"\u2550".repeat(50)}`);
|
|
|
10399
11252
|
});
|
|
10400
11253
|
|
|
10401
11254
|
// src/cli/commands/session.ts
|
|
10402
|
-
import { readFileSync as
|
|
11255
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
10403
11256
|
import { input as input7, select as select2 } from "@inquirer/prompts";
|
|
10404
|
-
import { Command as
|
|
10405
|
-
var sessionCommand = new
|
|
11257
|
+
import { Command as Command16 } from "commander";
|
|
11258
|
+
var sessionCommand = new Command16("session").description(
|
|
10406
11259
|
"Manage learning sessions"
|
|
10407
11260
|
);
|
|
10408
11261
|
sessionCommand.command("start").description("Start a new learning session (review \u2192 task)").option("--user <id>", "User ID (default: whoami)").option("--task <description>", "Task description (interactive if omitted)").option(
|
|
@@ -10591,7 +11444,7 @@ function loadPatternFile(path) {
|
|
|
10591
11444
|
if (!path) return [];
|
|
10592
11445
|
let parsed;
|
|
10593
11446
|
try {
|
|
10594
|
-
parsed = JSON.parse(
|
|
11447
|
+
parsed = JSON.parse(readFileSync12(path, "utf-8"));
|
|
10595
11448
|
} catch (err) {
|
|
10596
11449
|
throw new Error(
|
|
10597
11450
|
`Cannot read synthesis patterns from ${path}: ${err.message}`
|
|
@@ -10781,9 +11634,9 @@ sessionCommand.command("end").description("End a session and show summary").requ
|
|
|
10781
11634
|
});
|
|
10782
11635
|
|
|
10783
11636
|
// src/cli/commands/settings.ts
|
|
10784
|
-
import { existsSync as
|
|
10785
|
-
import { Command as
|
|
10786
|
-
var settingsCommand = new
|
|
11637
|
+
import { existsSync as existsSync21 } from "fs";
|
|
11638
|
+
import { Command as Command17 } from "commander";
|
|
11639
|
+
var settingsCommand = new Command17("settings").description(
|
|
10787
11640
|
"Manage user settings"
|
|
10788
11641
|
);
|
|
10789
11642
|
var BOOLEAN_SETTING_KEYS = /* @__PURE__ */ new Set(["llm.enabled", "llm.vision.enabled"]);
|
|
@@ -10946,7 +11799,7 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
|
|
|
10946
11799
|
console.log("\nValidation:");
|
|
10947
11800
|
for (const [name, path] of Object.entries(paths)) {
|
|
10948
11801
|
if (path) {
|
|
10949
|
-
const exists =
|
|
11802
|
+
const exists = existsSync21(path);
|
|
10950
11803
|
console.log(
|
|
10951
11804
|
` ${name.padEnd(9)}: ${exists ? "\x1B[32m\u2713 Valid folder\x1B[0m" : "\x1B[31m\u2717 Directory does not exist\x1B[0m"}`
|
|
10952
11805
|
);
|
|
@@ -10957,175 +11810,6 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
|
|
|
10957
11810
|
});
|
|
10958
11811
|
});
|
|
10959
11812
|
|
|
10960
|
-
// src/cli/commands/setup.ts
|
|
10961
|
-
import { copyFileSync as copyFileSync2, existsSync as existsSync20, mkdirSync as mkdirSync11, writeFileSync as writeFileSync10 } from "fs";
|
|
10962
|
-
import { basename as basename4, dirname as dirname6, join as join20 } from "path";
|
|
10963
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10964
|
-
import { Command as Command17 } from "commander";
|
|
10965
|
-
var packageRoot = [
|
|
10966
|
-
fileURLToPath2(new URL("../..", import.meta.url)),
|
|
10967
|
-
fileURLToPath2(new URL("../../..", import.meta.url))
|
|
10968
|
-
].find((candidate) => existsSync20(join20(candidate, "package.json"))) ?? fileURLToPath2(new URL("../..", import.meta.url));
|
|
10969
|
-
var SKILL_PAIRS = [
|
|
10970
|
-
{
|
|
10971
|
-
from: join20(packageRoot, ".claude", "skills", "zam", "SKILL.md"),
|
|
10972
|
-
to: join20(".claude", "skills", "zam", "SKILL.md")
|
|
10973
|
-
},
|
|
10974
|
-
{
|
|
10975
|
-
from: join20(packageRoot, ".agent", "skills", "zam", "SKILL.md"),
|
|
10976
|
-
to: join20(".agent", "skills", "zam", "SKILL.md")
|
|
10977
|
-
},
|
|
10978
|
-
{
|
|
10979
|
-
from: join20(packageRoot, ".agents", "skills", "zam", "SKILL.md"),
|
|
10980
|
-
to: join20(".agents", "skills", "zam", "SKILL.md")
|
|
10981
|
-
}
|
|
10982
|
-
];
|
|
10983
|
-
function copySkills(force, cwd = process.cwd()) {
|
|
10984
|
-
let anyAction = false;
|
|
10985
|
-
for (const { from, to } of SKILL_PAIRS) {
|
|
10986
|
-
const dest = join20(cwd, to);
|
|
10987
|
-
if (!existsSync20(from)) {
|
|
10988
|
-
console.warn(` warn source not found, skipping: ${from}`);
|
|
10989
|
-
continue;
|
|
10990
|
-
}
|
|
10991
|
-
if (existsSync20(dest) && !force) {
|
|
10992
|
-
console.log(` skip ${to} (already present \xE2\u20AC\u201D use --force to update)`);
|
|
10993
|
-
continue;
|
|
10994
|
-
}
|
|
10995
|
-
mkdirSync11(dirname6(dest), { recursive: true });
|
|
10996
|
-
copyFileSync2(from, dest);
|
|
10997
|
-
console.log(` copy ${to}`);
|
|
10998
|
-
anyAction = true;
|
|
10999
|
-
}
|
|
11000
|
-
if (!anyAction && !force) {
|
|
11001
|
-
console.log(
|
|
11002
|
-
"\nSkill files are already up to date. Run with --force to overwrite."
|
|
11003
|
-
);
|
|
11004
|
-
}
|
|
11005
|
-
}
|
|
11006
|
-
function formatDatabaseInitTarget(target) {
|
|
11007
|
-
switch (target.kind) {
|
|
11008
|
-
case "local":
|
|
11009
|
-
return `ZAM database at ${target.location} (local SQLite)`;
|
|
11010
|
-
case "turso-remote":
|
|
11011
|
-
return `ZAM database via Turso remote at ${target.location}`;
|
|
11012
|
-
case "turso-native":
|
|
11013
|
-
return `ZAM database via Turso native driver at ${target.location}`;
|
|
11014
|
-
case "turso-replica":
|
|
11015
|
-
return `ZAM database replica at ${target.location} syncing from ${target.syncUrl}`;
|
|
11016
|
-
}
|
|
11017
|
-
}
|
|
11018
|
-
async function initDatabase(skipInit) {
|
|
11019
|
-
if (skipInit) return;
|
|
11020
|
-
try {
|
|
11021
|
-
const target = getDatabaseTargetInfo();
|
|
11022
|
-
const db = await openDatabaseWithSync({ initialize: true });
|
|
11023
|
-
await db.close();
|
|
11024
|
-
console.log(` init ${formatDatabaseInitTarget(target)}`);
|
|
11025
|
-
} catch (err) {
|
|
11026
|
-
const msg = err.message;
|
|
11027
|
-
if (!msg.includes("already")) {
|
|
11028
|
-
console.warn(` warn database init: ${msg}`);
|
|
11029
|
-
} else {
|
|
11030
|
-
console.log(` skip database already initialized`);
|
|
11031
|
-
}
|
|
11032
|
-
}
|
|
11033
|
-
}
|
|
11034
|
-
function writeClaudeMd(skipClaudeMd, cwd = process.cwd()) {
|
|
11035
|
-
if (skipClaudeMd) return;
|
|
11036
|
-
const dest = join20(cwd, "CLAUDE.md");
|
|
11037
|
-
if (existsSync20(dest)) {
|
|
11038
|
-
console.log(` skip CLAUDE.md (already present)`);
|
|
11039
|
-
return;
|
|
11040
|
-
}
|
|
11041
|
-
const name = basename4(cwd);
|
|
11042
|
-
writeFileSync10(
|
|
11043
|
-
dest,
|
|
11044
|
-
`# ZAM Personal Kernel \xE2\u20AC\u201D ${name}
|
|
11045
|
-
|
|
11046
|
-
This is a ZAM personal instance. ZAM builds lasting skills through spaced
|
|
11047
|
-
repetition during real work \xE2\u20AC\u201D not separate study sessions.
|
|
11048
|
-
|
|
11049
|
-
## First time here?
|
|
11050
|
-
Run \`/setup\` in Claude Code or Gemini CLI to complete first-time setup.
|
|
11051
|
-
|
|
11052
|
-
## Regular use
|
|
11053
|
-
Run \`/zam\` to start a learning session on whatever you are working on.
|
|
11054
|
-
|
|
11055
|
-
## What lives here
|
|
11056
|
-
- \`beliefs/\` \xE2\u20AC\u201D your worldview, approved by git commit
|
|
11057
|
-
- \`goals/\` \xE2\u20AC\u201D your objectives, decomposed into tasks and learning tokens
|
|
11058
|
-
|
|
11059
|
-
## Fast-changing data
|
|
11060
|
-
Learning tokens, cards, and review history live in local SQLite by default.
|
|
11061
|
-
Use \`zam connector setup turso\` to store cloud credentials in
|
|
11062
|
-
\`~/.zam/credentials.json\` and use a Turso database across machines.
|
|
11063
|
-
`,
|
|
11064
|
-
"utf8"
|
|
11065
|
-
);
|
|
11066
|
-
console.log(` write CLAUDE.md`);
|
|
11067
|
-
}
|
|
11068
|
-
function writeAgentsMd(skipAgentsMd, cwd = process.cwd()) {
|
|
11069
|
-
if (skipAgentsMd) return;
|
|
11070
|
-
const dest = join20(cwd, "AGENTS.md");
|
|
11071
|
-
if (existsSync20(dest)) {
|
|
11072
|
-
console.log(` skip AGENTS.md (already present)`);
|
|
11073
|
-
return;
|
|
11074
|
-
}
|
|
11075
|
-
const name = basename4(cwd);
|
|
11076
|
-
writeFileSync10(
|
|
11077
|
-
dest,
|
|
11078
|
-
`# ZAM Personal Kernel - ${name}
|
|
11079
|
-
|
|
11080
|
-
This is a ZAM personal instance. ZAM builds lasting skills through spaced
|
|
11081
|
-
repetition during real work, not separate study sessions.
|
|
11082
|
-
|
|
11083
|
-
## First time here?
|
|
11084
|
-
Run \`zam setup\` from the shell. When this repository includes
|
|
11085
|
-
\`.agents/skills/setup/\`, you can instead select \`setup\` through \`/skills\`
|
|
11086
|
-
or invoke \`$setup\`.
|
|
11087
|
-
|
|
11088
|
-
## Regular use
|
|
11089
|
-
Select the \`zam\` skill through \`/skills\` or invoke \`$zam\` to start a
|
|
11090
|
-
learning session on whatever you are working on.
|
|
11091
|
-
|
|
11092
|
-
## What lives here
|
|
11093
|
-
- \`beliefs/\` - your worldview, approved by git commit
|
|
11094
|
-
- \`goals/\` - your objectives, decomposed into tasks and learning tokens
|
|
11095
|
-
|
|
11096
|
-
## Fast-changing data
|
|
11097
|
-
Learning tokens, cards, and review history live in local SQLite by default.
|
|
11098
|
-
Use \`zam connector setup turso\` to store cloud credentials in
|
|
11099
|
-
\`~/.zam/credentials.json\` and use a Turso database across machines.
|
|
11100
|
-
|
|
11101
|
-
## Codex skills
|
|
11102
|
-
Codex discovers repository skills under \`.agents/skills/\`. Run
|
|
11103
|
-
\`zam setup --force\` after upgrading \`zam-core\` to refresh them.
|
|
11104
|
-
`,
|
|
11105
|
-
"utf8"
|
|
11106
|
-
);
|
|
11107
|
-
console.log(` write AGENTS.md`);
|
|
11108
|
-
}
|
|
11109
|
-
var setupCommand = new Command17("setup").description(
|
|
11110
|
-
"Distribute ZAM skill files into this personal instance and initialize the database"
|
|
11111
|
-
).option(
|
|
11112
|
-
"--force",
|
|
11113
|
-
"overwrite existing skill files (use after upgrading zam)",
|
|
11114
|
-
false
|
|
11115
|
-
).option("--skip-init", "skip database initialization", false).option("--skip-claude-md", "skip CLAUDE.md generation", false).option("--skip-agents-md", "skip AGENTS.md generation", false).action(
|
|
11116
|
-
async (opts) => {
|
|
11117
|
-
console.log(`Setting up ZAM in ${process.cwd()}
|
|
11118
|
-
`);
|
|
11119
|
-
copySkills(opts.force);
|
|
11120
|
-
await initDatabase(opts.skipInit);
|
|
11121
|
-
writeClaudeMd(opts.skipClaudeMd);
|
|
11122
|
-
writeAgentsMd(opts.skipAgentsMd);
|
|
11123
|
-
console.log(
|
|
11124
|
-
"\nDone. Run `zam whoami --set <your-id>` to set your identity. Start the `zam` skill with `/zam` in Claude/Gemini-compatible clients or `$zam` (or `/skills`) in Codex."
|
|
11125
|
-
);
|
|
11126
|
-
}
|
|
11127
|
-
);
|
|
11128
|
-
|
|
11129
11813
|
// src/cli/commands/skill.ts
|
|
11130
11814
|
import { Command as Command18 } from "commander";
|
|
11131
11815
|
var skillCommand = new Command18("skill").description(
|
|
@@ -11213,7 +11897,7 @@ skillCommand.command("add").description("Register a new agent skill").requiredOp
|
|
|
11213
11897
|
});
|
|
11214
11898
|
|
|
11215
11899
|
// src/cli/commands/snapshot.ts
|
|
11216
|
-
import { existsSync as
|
|
11900
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "fs";
|
|
11217
11901
|
import { homedir as homedir13 } from "os";
|
|
11218
11902
|
import { dirname as dirname7, join as join21 } from "path";
|
|
11219
11903
|
import { Command as Command19 } from "commander";
|
|
@@ -11246,7 +11930,7 @@ var exportCmd = new Command19("export").description("Write a portable SQL-text s
|
|
|
11246
11930
|
}
|
|
11247
11931
|
const out = opts.out ?? join21(personalDir, "snapshots", defaultOutName());
|
|
11248
11932
|
const dir = dirname7(out);
|
|
11249
|
-
if (dir && dir !== "." && !
|
|
11933
|
+
if (dir && dir !== "." && !existsSync22(dir)) {
|
|
11250
11934
|
mkdirSync12(dir, { recursive: true });
|
|
11251
11935
|
}
|
|
11252
11936
|
writeFileSync11(out, snapshot, "utf-8");
|
|
@@ -11263,11 +11947,11 @@ var exportCmd = new Command19("export").description("Write a portable SQL-text s
|
|
|
11263
11947
|
var importCmd = new Command19("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) => {
|
|
11264
11948
|
let db;
|
|
11265
11949
|
try {
|
|
11266
|
-
if (!
|
|
11950
|
+
if (!existsSync22(file)) {
|
|
11267
11951
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
11268
11952
|
process.exit(1);
|
|
11269
11953
|
}
|
|
11270
|
-
const snapshot =
|
|
11954
|
+
const snapshot = readFileSync13(file, "utf-8");
|
|
11271
11955
|
db = await openDatabaseWithSync({ initialize: true });
|
|
11272
11956
|
const result = await importSnapshot(db, snapshot, { force: opts.force });
|
|
11273
11957
|
await db.close();
|
|
@@ -11285,11 +11969,11 @@ var importCmd = new Command19("import").description("Restore a snapshot into the
|
|
|
11285
11969
|
});
|
|
11286
11970
|
var verifyCmd = new Command19("verify").description("Check a snapshot's manifest and checksum without importing").argument("<file>", "Snapshot file to verify").action((file) => {
|
|
11287
11971
|
try {
|
|
11288
|
-
if (!
|
|
11972
|
+
if (!existsSync22(file)) {
|
|
11289
11973
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
11290
11974
|
process.exit(1);
|
|
11291
11975
|
}
|
|
11292
|
-
const manifest = verifySnapshot(
|
|
11976
|
+
const manifest = verifySnapshot(readFileSync13(file, "utf-8"));
|
|
11293
11977
|
const { total, nonEmpty } = summarize(manifest.tables);
|
|
11294
11978
|
console.log(`Valid snapshot (format v${manifest.version})`);
|
|
11295
11979
|
console.log(` created: ${manifest.createdAt}`);
|
|
@@ -11629,7 +12313,7 @@ tokenCommand.command("status").description("Show full status of a token for a us
|
|
|
11629
12313
|
|
|
11630
12314
|
// src/cli/commands/ui.ts
|
|
11631
12315
|
import { spawn as spawn3, spawnSync } from "child_process";
|
|
11632
|
-
import { existsSync as
|
|
12316
|
+
import { existsSync as existsSync23 } from "fs";
|
|
11633
12317
|
import { homedir as homedir14 } from "os";
|
|
11634
12318
|
import { dirname as dirname8, join as join22 } from "path";
|
|
11635
12319
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -11647,7 +12331,7 @@ function findDesktopDir() {
|
|
|
11647
12331
|
for (const start of starts) {
|
|
11648
12332
|
let dir = start;
|
|
11649
12333
|
for (let i = 0; i < 10; i++) {
|
|
11650
|
-
if (
|
|
12334
|
+
if (existsSync23(join22(dir, "desktop", "src-tauri", "tauri.conf.json"))) {
|
|
11651
12335
|
return join22(dir, "desktop");
|
|
11652
12336
|
}
|
|
11653
12337
|
const parent = dirname8(dir);
|
|
@@ -11662,15 +12346,15 @@ function findBuiltApp(desktopDir) {
|
|
|
11662
12346
|
if (process.platform === "win32") {
|
|
11663
12347
|
for (const name of ["ZAM.exe", "zam.exe", "zam-desktop.exe"]) {
|
|
11664
12348
|
const p = join22(releaseDir, name);
|
|
11665
|
-
if (
|
|
12349
|
+
if (existsSync23(p)) return p;
|
|
11666
12350
|
}
|
|
11667
12351
|
} else if (process.platform === "darwin") {
|
|
11668
12352
|
const app = join22(releaseDir, "bundle", "macos", "ZAM.app");
|
|
11669
|
-
if (
|
|
12353
|
+
if (existsSync23(app)) return app;
|
|
11670
12354
|
} else {
|
|
11671
12355
|
for (const name of ["zam", "ZAM", "zam-desktop"]) {
|
|
11672
12356
|
const p = join22(releaseDir, name);
|
|
11673
|
-
if (
|
|
12357
|
+
if (existsSync23(p)) return p;
|
|
11674
12358
|
}
|
|
11675
12359
|
}
|
|
11676
12360
|
return null;
|
|
@@ -11681,7 +12365,7 @@ function findInstalledApp() {
|
|
|
11681
12365
|
process.env.ProgramFiles && join22(process.env.ProgramFiles, "ZAM", "ZAM.exe"),
|
|
11682
12366
|
process.env["ProgramFiles(x86)"] && join22(process.env["ProgramFiles(x86)"], "ZAM", "ZAM.exe")
|
|
11683
12367
|
] : process.platform === "darwin" ? ["/Applications/ZAM.app", join22(homedir14(), "Applications", "ZAM.app")] : ["/opt/ZAM/zam", "/usr/bin/zam-desktop"];
|
|
11684
|
-
return candidates.find((candidate) => candidate &&
|
|
12368
|
+
return candidates.find((candidate) => candidate && existsSync23(candidate)) || null;
|
|
11685
12369
|
}
|
|
11686
12370
|
function runNpm(args, opts) {
|
|
11687
12371
|
const res = spawnSync("npm", args, {
|
|
@@ -11692,7 +12376,7 @@ function runNpm(args, opts) {
|
|
|
11692
12376
|
return res.status ?? 1;
|
|
11693
12377
|
}
|
|
11694
12378
|
function ensureDesktopDeps(desktopDir) {
|
|
11695
|
-
if (
|
|
12379
|
+
if (existsSync23(join22(desktopDir, "node_modules"))) return true;
|
|
11696
12380
|
console.log(
|
|
11697
12381
|
`${C3.cyan}Installing desktop dependencies (one-time)...${C3.reset}`
|
|
11698
12382
|
);
|
|
@@ -11724,7 +12408,7 @@ function hasMsvcBuildTools() {
|
|
|
11724
12408
|
"Installer",
|
|
11725
12409
|
"vswhere.exe"
|
|
11726
12410
|
);
|
|
11727
|
-
if (!
|
|
12411
|
+
if (!existsSync23(vswhere)) return false;
|
|
11728
12412
|
const res = spawnSync(
|
|
11729
12413
|
vswhere,
|
|
11730
12414
|
[
|
|
@@ -11756,7 +12440,7 @@ function requireMsvcOnWindows() {
|
|
|
11756
12440
|
return false;
|
|
11757
12441
|
}
|
|
11758
12442
|
function warnIfCliMissing(repoRoot) {
|
|
11759
|
-
if (!
|
|
12443
|
+
if (!existsSync23(join22(repoRoot, "dist", "cli", "index.js"))) {
|
|
11760
12444
|
console.warn(
|
|
11761
12445
|
`${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}`
|
|
11762
12446
|
);
|
|
@@ -11917,7 +12601,7 @@ ${C3.dim}After that, 'zam ui' launches it directly.${C3.reset}`
|
|
|
11917
12601
|
|
|
11918
12602
|
// src/cli/commands/update.ts
|
|
11919
12603
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
11920
|
-
import { existsSync as
|
|
12604
|
+
import { existsSync as existsSync24, readFileSync as readFileSync14, realpathSync } from "fs";
|
|
11921
12605
|
import { dirname as dirname9, join as join23 } from "path";
|
|
11922
12606
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
11923
12607
|
import { confirm as confirm4 } from "@inquirer/prompts";
|
|
@@ -11943,7 +12627,7 @@ function currentVersion() {
|
|
|
11943
12627
|
for (const up of ["..", "../..", "../../.."]) {
|
|
11944
12628
|
try {
|
|
11945
12629
|
const pkg2 = JSON.parse(
|
|
11946
|
-
|
|
12630
|
+
readFileSync14(join23(here, up, "package.json"), "utf-8")
|
|
11947
12631
|
);
|
|
11948
12632
|
if (pkg2.version) return pkg2.version;
|
|
11949
12633
|
} catch {
|
|
@@ -11954,7 +12638,7 @@ function currentVersion() {
|
|
|
11954
12638
|
function versionAt(dir) {
|
|
11955
12639
|
try {
|
|
11956
12640
|
const pkg2 = JSON.parse(
|
|
11957
|
-
|
|
12641
|
+
readFileSync14(join23(dir, "package.json"), "utf-8")
|
|
11958
12642
|
);
|
|
11959
12643
|
return pkg2.version ?? "unknown";
|
|
11960
12644
|
} catch {
|
|
@@ -12034,11 +12718,11 @@ function findSourceRepo() {
|
|
|
12034
12718
|
let dir = realpathSync(dirname9(fileURLToPath4(import.meta.url)));
|
|
12035
12719
|
let parent = dirname9(dir);
|
|
12036
12720
|
while (parent !== dir) {
|
|
12037
|
-
if (
|
|
12721
|
+
if (existsSync24(join23(dir, ".git"))) return dir;
|
|
12038
12722
|
dir = parent;
|
|
12039
12723
|
parent = dirname9(dir);
|
|
12040
12724
|
}
|
|
12041
|
-
return
|
|
12725
|
+
return existsSync24(join23(dir, ".git")) ? dir : null;
|
|
12042
12726
|
}
|
|
12043
12727
|
function runGit2(cwd, args, capture) {
|
|
12044
12728
|
const res = spawnSync2("git", args, {
|
|
@@ -12215,7 +12899,7 @@ var whoamiCommand = new Command24("whoami").description("Show or set the default
|
|
|
12215
12899
|
// src/cli/index.ts
|
|
12216
12900
|
var __dirname = dirname10(fileURLToPath5(import.meta.url));
|
|
12217
12901
|
var pkg = JSON.parse(
|
|
12218
|
-
|
|
12902
|
+
readFileSync15(join24(__dirname, "..", "..", "package.json"), "utf-8")
|
|
12219
12903
|
);
|
|
12220
12904
|
var program = new Command25();
|
|
12221
12905
|
program.name("zam").description(
|