zam-core 0.4.4 → 0.5.1
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 +1258 -387
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +41 -1
- package/dist/index.js +47 -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,57 @@ 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
|
+
}
|
|
4480
|
+
function removeConfiguredWorkspace(id, path = defaultConfigPath()) {
|
|
4481
|
+
const next = getConfiguredWorkspaces(path).filter(
|
|
4482
|
+
(workspace) => workspace.id !== id
|
|
4483
|
+
);
|
|
4484
|
+
saveConfiguredWorkspaces(next, path);
|
|
4485
|
+
return next;
|
|
4486
|
+
}
|
|
4453
4487
|
function detectSyncProvider(dir) {
|
|
4454
4488
|
const p = dir.toLowerCase();
|
|
4455
4489
|
if (p.includes("onedrive")) return "OneDrive";
|
|
@@ -5031,7 +5065,7 @@ function isItermRunning() {
|
|
|
5031
5065
|
return false;
|
|
5032
5066
|
}
|
|
5033
5067
|
}
|
|
5034
|
-
function openMacTerminal(shellSetup, label, dir) {
|
|
5068
|
+
function openMacTerminal(shellSetup, label, dir, silent) {
|
|
5035
5069
|
const useIterm = isItermRunning();
|
|
5036
5070
|
const escaped = shellSetup.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
5037
5071
|
const appleScript = useIterm ? `tell application "iTerm2"
|
|
@@ -5048,16 +5082,20 @@ end tell`;
|
|
|
5048
5082
|
try {
|
|
5049
5083
|
writeFileSync6(tmpFile, appleScript);
|
|
5050
5084
|
execSync4(`osascript ${JSON.stringify(tmpFile)}`, { stdio: "ignore" });
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5085
|
+
if (!silent) {
|
|
5086
|
+
console.log(
|
|
5087
|
+
`Opened ${useIterm ? "iTerm2" : "Terminal.app"} window (${label})`
|
|
5088
|
+
);
|
|
5089
|
+
console.log(` Directory: ${dir}`);
|
|
5090
|
+
}
|
|
5055
5091
|
} catch (err) {
|
|
5056
|
-
|
|
5057
|
-
|
|
5092
|
+
if (!silent) {
|
|
5093
|
+
console.error(`Failed to open terminal: ${err.message}`);
|
|
5094
|
+
console.log(`
|
|
5058
5095
|
Run this manually in a new terminal:
|
|
5059
5096
|
`);
|
|
5060
|
-
|
|
5097
|
+
console.log(` ${shellSetup}`);
|
|
5098
|
+
}
|
|
5061
5099
|
} finally {
|
|
5062
5100
|
try {
|
|
5063
5101
|
unlinkSync(tmpFile);
|
|
@@ -5065,7 +5103,7 @@ Run this manually in a new terminal:
|
|
|
5065
5103
|
}
|
|
5066
5104
|
}
|
|
5067
5105
|
}
|
|
5068
|
-
function openWindowsPowerShell(shellSetup, label, dir, requestedShell) {
|
|
5106
|
+
function openWindowsPowerShell(shellSetup, label, dir, requestedShell, silent) {
|
|
5069
5107
|
const requestedExecutable = requestedShell === "powershell" ? "powershell.exe" : "pwsh.exe";
|
|
5070
5108
|
const executable = findExecutable(requestedExecutable) ? requestedExecutable : "powershell.exe";
|
|
5071
5109
|
const startCommand = [
|
|
@@ -5082,35 +5120,42 @@ function openWindowsPowerShell(shellSetup, label, dir, requestedShell) {
|
|
|
5082
5120
|
stdio: "ignore"
|
|
5083
5121
|
}
|
|
5084
5122
|
);
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5123
|
+
if (!silent) {
|
|
5124
|
+
console.log(
|
|
5125
|
+
`Opened ${executable === "pwsh.exe" ? "PowerShell" : "Windows PowerShell"} window (${label})`
|
|
5126
|
+
);
|
|
5127
|
+
console.log(` Directory: ${dir}`);
|
|
5128
|
+
}
|
|
5089
5129
|
} catch (err) {
|
|
5090
|
-
|
|
5091
|
-
|
|
5130
|
+
if (!silent) {
|
|
5131
|
+
console.error(`Failed to open PowerShell: ${err.message}`);
|
|
5132
|
+
console.log(`
|
|
5092
5133
|
Run this manually in a new PowerShell terminal:
|
|
5093
5134
|
`);
|
|
5094
|
-
|
|
5135
|
+
console.log(` ${shellSetup}`);
|
|
5136
|
+
}
|
|
5095
5137
|
}
|
|
5096
5138
|
}
|
|
5097
5139
|
function openTerminalWindow(opts) {
|
|
5098
|
-
const { shellSetup, label, dir, shell } = opts;
|
|
5099
|
-
|
|
5100
|
-
|
|
5140
|
+
const { shellSetup, label, dir, shell, silent = false } = opts;
|
|
5141
|
+
const platform = opts.platform ?? process.platform;
|
|
5142
|
+
if (platform === "darwin" && !isPowerShellShell(shell)) {
|
|
5143
|
+
openMacTerminal(shellSetup, label, dir, silent);
|
|
5101
5144
|
return;
|
|
5102
5145
|
}
|
|
5103
|
-
if (
|
|
5104
|
-
openWindowsPowerShell(shellSetup, label, dir, shell);
|
|
5146
|
+
if (platform === "win32" && isPowerShellShell(shell)) {
|
|
5147
|
+
openWindowsPowerShell(shellSetup, label, dir, shell, silent);
|
|
5105
5148
|
return;
|
|
5106
5149
|
}
|
|
5107
|
-
|
|
5150
|
+
if (!silent) {
|
|
5151
|
+
console.log(`Run this in a new terminal:
|
|
5108
5152
|
`);
|
|
5109
|
-
|
|
5153
|
+
console.log(` ${shellSetup}
|
|
5110
5154
|
`);
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5155
|
+
console.log(
|
|
5156
|
+
`(Automatic terminal opening is only supported on macOS Terminal/iTerm2 and Windows PowerShell for now.)`
|
|
5157
|
+
);
|
|
5158
|
+
}
|
|
5114
5159
|
}
|
|
5115
5160
|
|
|
5116
5161
|
// src/cli/agent-harness.ts
|
|
@@ -5176,7 +5221,9 @@ function launchHarness(harness, opts) {
|
|
|
5176
5221
|
shellSetup: plan.shellSetup,
|
|
5177
5222
|
label: `agent-${harness.id}`,
|
|
5178
5223
|
dir: opts.workspace,
|
|
5179
|
-
shell: plan.shell
|
|
5224
|
+
shell: plan.shell,
|
|
5225
|
+
silent: opts.silent,
|
|
5226
|
+
platform: opts.platform
|
|
5180
5227
|
});
|
|
5181
5228
|
return;
|
|
5182
5229
|
}
|
|
@@ -5185,7 +5232,9 @@ function launchHarness(harness, opts) {
|
|
|
5185
5232
|
stdio: "ignore",
|
|
5186
5233
|
windowsHide: true
|
|
5187
5234
|
}).unref();
|
|
5188
|
-
|
|
5235
|
+
if (!opts.silent) {
|
|
5236
|
+
console.log(`Launched ${harness.label} in ${opts.workspace}`);
|
|
5237
|
+
}
|
|
5189
5238
|
}
|
|
5190
5239
|
|
|
5191
5240
|
// src/cli/commands/agent.ts
|
|
@@ -5310,10 +5359,16 @@ var agentCommand = new Command("agent").description("Provision and inspect the a
|
|
|
5310
5359
|
// src/cli/commands/bridge.ts
|
|
5311
5360
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
5312
5361
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
5313
|
-
import {
|
|
5362
|
+
import {
|
|
5363
|
+
existsSync as existsSync17,
|
|
5364
|
+
mkdirSync as mkdirSync10,
|
|
5365
|
+
readdirSync as readdirSync3,
|
|
5366
|
+
readFileSync as readFileSync11,
|
|
5367
|
+
rmSync as rmSync3
|
|
5368
|
+
} from "fs";
|
|
5314
5369
|
import { homedir as homedir10, tmpdir as tmpdir3 } from "os";
|
|
5315
|
-
import { join as
|
|
5316
|
-
import { Command as
|
|
5370
|
+
import { basename as basename5, join as join17, resolve as resolve5 } from "path";
|
|
5371
|
+
import { Command as Command4 } from "commander";
|
|
5317
5372
|
|
|
5318
5373
|
// src/cli/llm/client.ts
|
|
5319
5374
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -5379,14 +5434,44 @@ async function getProviderForRole(db, role) {
|
|
|
5379
5434
|
const providers = await readJsonSetting(db, "llm.providers");
|
|
5380
5435
|
const roles = await readJsonSetting(db, "llm.roles");
|
|
5381
5436
|
const binding = roles?.[role];
|
|
5437
|
+
let resolved = base;
|
|
5382
5438
|
if (providers && binding?.primary && providers[binding.primary]) {
|
|
5383
|
-
const primary = materializeProvider(
|
|
5384
|
-
|
|
5439
|
+
const primary = materializeProvider(
|
|
5440
|
+
providers[binding.primary],
|
|
5441
|
+
base,
|
|
5442
|
+
role,
|
|
5443
|
+
{
|
|
5444
|
+
providerName: binding.primary,
|
|
5445
|
+
source: "shared"
|
|
5446
|
+
}
|
|
5447
|
+
);
|
|
5448
|
+
const fallback = binding.fallback && providers[binding.fallback] ? materializeProvider(providers[binding.fallback], base, role, {
|
|
5449
|
+
providerName: binding.fallback,
|
|
5450
|
+
source: "shared"
|
|
5451
|
+
}) : void 0;
|
|
5452
|
+
resolved = { ...primary, fallback };
|
|
5453
|
+
}
|
|
5454
|
+
const machine = getMachineAiConfig();
|
|
5455
|
+
const machineProviders = machine.providers;
|
|
5456
|
+
const machineBinding = machine.roles?.[role];
|
|
5457
|
+
if (machineProviders && machineBinding?.primary && machineProviders[machineBinding.primary]) {
|
|
5458
|
+
const primary = materializeProvider(
|
|
5459
|
+
machineProviders[machineBinding.primary],
|
|
5460
|
+
resolved,
|
|
5461
|
+
role,
|
|
5462
|
+
{ providerName: machineBinding.primary, source: "machine" }
|
|
5463
|
+
);
|
|
5464
|
+
const fallback = machineBinding.fallback && machineProviders[machineBinding.fallback] ? materializeProvider(
|
|
5465
|
+
machineProviders[machineBinding.fallback],
|
|
5466
|
+
resolved,
|
|
5467
|
+
role,
|
|
5468
|
+
{ providerName: machineBinding.fallback, source: "machine" }
|
|
5469
|
+
) : void 0;
|
|
5385
5470
|
return { ...primary, fallback };
|
|
5386
5471
|
}
|
|
5387
|
-
return
|
|
5472
|
+
return resolved;
|
|
5388
5473
|
}
|
|
5389
|
-
function materializeProvider(rec, base, role) {
|
|
5474
|
+
function materializeProvider(rec, base, role, meta) {
|
|
5390
5475
|
const url = rec.url || base.url;
|
|
5391
5476
|
return {
|
|
5392
5477
|
enabled: base.enabled,
|
|
@@ -5395,6 +5480,10 @@ function materializeProvider(rec, base, role) {
|
|
|
5395
5480
|
apiKey: resolveProviderApiKey(rec),
|
|
5396
5481
|
apiFlavor: rec.apiFlavor || inferApiFlavor(url),
|
|
5397
5482
|
locale: base.locale,
|
|
5483
|
+
providerName: meta.providerName,
|
|
5484
|
+
label: rec.label,
|
|
5485
|
+
source: meta.source,
|
|
5486
|
+
local: rec.local ?? isLocalEndpoint(url),
|
|
5398
5487
|
...role === "vision" ? { maxFrames: base.maxFrames } : {}
|
|
5399
5488
|
};
|
|
5400
5489
|
}
|
|
@@ -5413,6 +5502,8 @@ async function getLegacyRoleConfig(db, role, enabled) {
|
|
|
5413
5502
|
apiKey: await getSetting(db, "llm.vision.api_key") || base.apiKey,
|
|
5414
5503
|
apiFlavor: inferApiFlavor(url),
|
|
5415
5504
|
locale: base.locale,
|
|
5505
|
+
source: "legacy",
|
|
5506
|
+
local: isLocalEndpoint(url),
|
|
5416
5507
|
maxFrames: Number.isNaN(parsed) ? 100 : parsed
|
|
5417
5508
|
};
|
|
5418
5509
|
}
|
|
@@ -5422,7 +5513,9 @@ async function getLegacyRoleConfig(db, role, enabled) {
|
|
|
5422
5513
|
model: base.model,
|
|
5423
5514
|
apiKey: base.apiKey,
|
|
5424
5515
|
apiFlavor: inferApiFlavor(base.url),
|
|
5425
|
-
locale: base.locale
|
|
5516
|
+
locale: base.locale,
|
|
5517
|
+
source: "legacy",
|
|
5518
|
+
local: isLocalEndpoint(base.url)
|
|
5426
5519
|
};
|
|
5427
5520
|
}
|
|
5428
5521
|
function assertChatCompletions(cfg) {
|
|
@@ -5672,10 +5765,17 @@ async function isVisionProviderModelExplicit(db, active) {
|
|
|
5672
5765
|
const providers = await readJsonSetting(db, "llm.providers");
|
|
5673
5766
|
const roles = await readJsonSetting(db, "llm.roles");
|
|
5674
5767
|
const binding = roles?.vision;
|
|
5675
|
-
|
|
5676
|
-
|
|
5768
|
+
const sharedExplicit = [binding?.primary, binding?.fallback].some((id) => {
|
|
5769
|
+
if (!id) return false;
|
|
5770
|
+
const rec = providers?.[id];
|
|
5771
|
+
return rec?.model === active.model && (rec.url === void 0 || rec.url === active.url);
|
|
5772
|
+
});
|
|
5773
|
+
if (sharedExplicit) return true;
|
|
5774
|
+
const machine = getMachineAiConfig();
|
|
5775
|
+
const machineBinding = machine.roles?.vision;
|
|
5776
|
+
return [machineBinding?.primary, machineBinding?.fallback].some((id) => {
|
|
5677
5777
|
if (!id) return false;
|
|
5678
|
-
const rec = providers[id];
|
|
5778
|
+
const rec = machine.providers?.[id];
|
|
5679
5779
|
return rec?.model === active.model && (rec.url === void 0 || rec.url === active.url);
|
|
5680
5780
|
});
|
|
5681
5781
|
}
|
|
@@ -5708,6 +5808,87 @@ async function checkVisionReadiness(db) {
|
|
|
5708
5808
|
warning
|
|
5709
5809
|
};
|
|
5710
5810
|
}
|
|
5811
|
+
function summarizeFallback(provider) {
|
|
5812
|
+
if (!provider) return void 0;
|
|
5813
|
+
return {
|
|
5814
|
+
providerName: provider.providerName,
|
|
5815
|
+
label: provider.label,
|
|
5816
|
+
source: provider.source,
|
|
5817
|
+
url: provider.url,
|
|
5818
|
+
model: provider.model,
|
|
5819
|
+
apiFlavor: provider.apiFlavor,
|
|
5820
|
+
local: provider.local
|
|
5821
|
+
};
|
|
5822
|
+
}
|
|
5823
|
+
async function getProviderRoleStatus(db, role) {
|
|
5824
|
+
const cfg = await getProviderForRole(db, role);
|
|
5825
|
+
const unsupportedProvider = role !== "vision" && cfg.apiFlavor !== "chat-completions";
|
|
5826
|
+
if (!cfg.enabled) {
|
|
5827
|
+
return {
|
|
5828
|
+
role,
|
|
5829
|
+
enabled: false,
|
|
5830
|
+
providerName: cfg.providerName,
|
|
5831
|
+
label: cfg.label,
|
|
5832
|
+
source: cfg.source,
|
|
5833
|
+
url: cfg.url,
|
|
5834
|
+
model: cfg.model,
|
|
5835
|
+
apiFlavor: cfg.apiFlavor,
|
|
5836
|
+
local: cfg.local,
|
|
5837
|
+
online: false,
|
|
5838
|
+
modelAvailable: false,
|
|
5839
|
+
availableModels: [],
|
|
5840
|
+
usable: false,
|
|
5841
|
+
reason: "disabled",
|
|
5842
|
+
fallback: summarizeFallback(cfg.fallback)
|
|
5843
|
+
};
|
|
5844
|
+
}
|
|
5845
|
+
if (unsupportedProvider) {
|
|
5846
|
+
return {
|
|
5847
|
+
role,
|
|
5848
|
+
enabled: true,
|
|
5849
|
+
providerName: cfg.providerName,
|
|
5850
|
+
label: cfg.label,
|
|
5851
|
+
source: cfg.source,
|
|
5852
|
+
url: cfg.url,
|
|
5853
|
+
model: cfg.model,
|
|
5854
|
+
apiFlavor: cfg.apiFlavor,
|
|
5855
|
+
local: cfg.local,
|
|
5856
|
+
online: false,
|
|
5857
|
+
modelAvailable: false,
|
|
5858
|
+
availableModels: [],
|
|
5859
|
+
usable: false,
|
|
5860
|
+
reason: "unsupported-provider",
|
|
5861
|
+
fallback: summarizeFallback(cfg.fallback)
|
|
5862
|
+
};
|
|
5863
|
+
}
|
|
5864
|
+
let selected;
|
|
5865
|
+
if (role === "vision") {
|
|
5866
|
+
const chain = await checkProviderChain(cfg);
|
|
5867
|
+
selected = chain.firstUsable ?? chain.primary;
|
|
5868
|
+
} else {
|
|
5869
|
+
selected = await checkProviderEndpoint(cfg);
|
|
5870
|
+
}
|
|
5871
|
+
const active = selected.endpoint;
|
|
5872
|
+
const usable = selected.online && selected.modelAvailable;
|
|
5873
|
+
const reason = usable ? void 0 : selected.online ? "model-not-found" : "offline";
|
|
5874
|
+
return {
|
|
5875
|
+
role,
|
|
5876
|
+
enabled: true,
|
|
5877
|
+
providerName: active.providerName,
|
|
5878
|
+
label: active.label,
|
|
5879
|
+
source: active.source,
|
|
5880
|
+
url: active.url,
|
|
5881
|
+
model: active.model,
|
|
5882
|
+
apiFlavor: active.apiFlavor,
|
|
5883
|
+
local: active.local,
|
|
5884
|
+
online: selected.online,
|
|
5885
|
+
modelAvailable: selected.modelAvailable,
|
|
5886
|
+
availableModels: selected.availableModels,
|
|
5887
|
+
usable,
|
|
5888
|
+
reason,
|
|
5889
|
+
fallback: summarizeFallback(cfg.fallback)
|
|
5890
|
+
};
|
|
5891
|
+
}
|
|
5711
5892
|
function detectRunner(url, model) {
|
|
5712
5893
|
let runner = "unknown";
|
|
5713
5894
|
let port = "8000";
|
|
@@ -5806,7 +5987,7 @@ async function startLocalRunner(url, model, locale) {
|
|
|
5806
5987
|
let attempts = 0;
|
|
5807
5988
|
const dotsPerLine = 30;
|
|
5808
5989
|
while (true) {
|
|
5809
|
-
await new Promise((
|
|
5990
|
+
await new Promise((resolve9) => setTimeout(resolve9, 500));
|
|
5810
5991
|
if (await isLlmOnline(url)) {
|
|
5811
5992
|
if (attempts > 0) process.stdout.write("\n");
|
|
5812
5993
|
return true;
|
|
@@ -5961,8 +6142,8 @@ async function fetchWithInteractiveTimeout(url, options = {}) {
|
|
|
5961
6142
|
const dotsPerLine = 30;
|
|
5962
6143
|
while (true) {
|
|
5963
6144
|
let timeoutId;
|
|
5964
|
-
const timeoutPromise = new Promise((
|
|
5965
|
-
timeoutId = setTimeout(() =>
|
|
6145
|
+
const timeoutPromise = new Promise((resolve9) => {
|
|
6146
|
+
timeoutId = setTimeout(() => resolve9("timeout"), timeoutMs);
|
|
5966
6147
|
});
|
|
5967
6148
|
const dotsInterval = setInterval(() => {
|
|
5968
6149
|
process.stdout.write(".");
|
|
@@ -6072,25 +6253,25 @@ async function observeUiSnapshotViaLLM(db, input8) {
|
|
|
6072
6253
|
const imageUrls = [];
|
|
6073
6254
|
const isVideo = /\.(mp4|mov|m4v|avi|mkv|webm)$/i.test(input8.imagePath);
|
|
6074
6255
|
if (isVideo) {
|
|
6075
|
-
const { mkdirSync:
|
|
6256
|
+
const { mkdirSync: mkdirSync14, readdirSync: readdirSync4, rmSync: rmSync4 } = await import("fs");
|
|
6076
6257
|
const { execSync: execSync6 } = await import("child_process");
|
|
6077
6258
|
const tempDir = join14(
|
|
6078
6259
|
tmpdir2(),
|
|
6079
6260
|
`zam-frames-${randomBytes(4).toString("hex")}`
|
|
6080
6261
|
);
|
|
6081
|
-
|
|
6262
|
+
mkdirSync14(tempDir, { recursive: true });
|
|
6082
6263
|
try {
|
|
6083
6264
|
execSync6(
|
|
6084
6265
|
`ffmpeg -i "${input8.imagePath}" -vf "fps=1/3,scale=1280:-1" -vsync vfr "${tempDir}/frame_%03d.png"`,
|
|
6085
6266
|
{ stdio: "ignore" }
|
|
6086
6267
|
);
|
|
6087
|
-
let files =
|
|
6268
|
+
let files = readdirSync4(tempDir).filter((f) => f.endsWith(".png")).sort();
|
|
6088
6269
|
if (files.length === 0) {
|
|
6089
6270
|
execSync6(
|
|
6090
6271
|
`ffmpeg -i "${input8.imagePath}" -vframes 1 "${tempDir}/frame_001.png"`,
|
|
6091
6272
|
{ stdio: "ignore" }
|
|
6092
6273
|
);
|
|
6093
|
-
files =
|
|
6274
|
+
files = readdirSync4(tempDir).filter((f) => f.endsWith(".png")).sort();
|
|
6094
6275
|
}
|
|
6095
6276
|
const maxFrames = cfg.maxFrames ?? 100;
|
|
6096
6277
|
let sampledFiles = files;
|
|
@@ -6112,7 +6293,7 @@ async function observeUiSnapshotViaLLM(db, input8) {
|
|
|
6112
6293
|
}
|
|
6113
6294
|
} finally {
|
|
6114
6295
|
try {
|
|
6115
|
-
|
|
6296
|
+
rmSync4(tempDir, { recursive: true, force: true });
|
|
6116
6297
|
} catch {
|
|
6117
6298
|
}
|
|
6118
6299
|
}
|
|
@@ -6467,6 +6648,419 @@ async function resolveUser(opts, db, resolveOpts) {
|
|
|
6467
6648
|
process.exit(1);
|
|
6468
6649
|
}
|
|
6469
6650
|
|
|
6651
|
+
// src/cli/commands/setup.ts
|
|
6652
|
+
import {
|
|
6653
|
+
existsSync as existsSync15,
|
|
6654
|
+
lstatSync,
|
|
6655
|
+
mkdirSync as mkdirSync8,
|
|
6656
|
+
readdirSync as readdirSync2,
|
|
6657
|
+
readFileSync as readFileSync10,
|
|
6658
|
+
realpathSync,
|
|
6659
|
+
rmSync as rmSync2,
|
|
6660
|
+
symlinkSync,
|
|
6661
|
+
writeFileSync as writeFileSync7
|
|
6662
|
+
} from "fs";
|
|
6663
|
+
import { basename as basename4, dirname as dirname5, join as join15, resolve as resolve3 } from "path";
|
|
6664
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6665
|
+
import { Command as Command2 } from "commander";
|
|
6666
|
+
var packageRoot = [
|
|
6667
|
+
fileURLToPath2(new URL("../..", import.meta.url)),
|
|
6668
|
+
fileURLToPath2(new URL("../../..", import.meta.url))
|
|
6669
|
+
].find((candidate) => existsSync15(join15(candidate, "package.json"))) ?? fileURLToPath2(new URL("../..", import.meta.url));
|
|
6670
|
+
var ALL_SETUP_AGENTS = ["claude", "copilot", "codex", "agent"];
|
|
6671
|
+
var SKILL_PAIRS = [
|
|
6672
|
+
{
|
|
6673
|
+
from: join15(packageRoot, ".claude", "skills", "zam", "SKILL.md"),
|
|
6674
|
+
to: join15(".claude", "skills", "zam", "SKILL.md"),
|
|
6675
|
+
agents: ["claude", "copilot"]
|
|
6676
|
+
},
|
|
6677
|
+
{
|
|
6678
|
+
from: join15(packageRoot, ".agent", "skills", "zam", "SKILL.md"),
|
|
6679
|
+
to: join15(".agent", "skills", "zam", "SKILL.md"),
|
|
6680
|
+
agents: ["agent"]
|
|
6681
|
+
},
|
|
6682
|
+
{
|
|
6683
|
+
from: join15(packageRoot, ".agents", "skills", "zam", "SKILL.md"),
|
|
6684
|
+
to: join15(".agents", "skills", "zam", "SKILL.md"),
|
|
6685
|
+
agents: ["codex"]
|
|
6686
|
+
}
|
|
6687
|
+
];
|
|
6688
|
+
function parseSetupAgents(value) {
|
|
6689
|
+
if (!value || value.trim().toLowerCase() === "all") {
|
|
6690
|
+
return new Set(ALL_SETUP_AGENTS);
|
|
6691
|
+
}
|
|
6692
|
+
const aliases = {
|
|
6693
|
+
claude: ["claude"],
|
|
6694
|
+
copilot: ["copilot"],
|
|
6695
|
+
codex: ["codex"],
|
|
6696
|
+
agent: ["agent"],
|
|
6697
|
+
opencode: ["agent"]
|
|
6698
|
+
};
|
|
6699
|
+
const selected = /* @__PURE__ */ new Set();
|
|
6700
|
+
for (const raw of value.split(",")) {
|
|
6701
|
+
const key = raw.trim().toLowerCase();
|
|
6702
|
+
const mapped = aliases[key];
|
|
6703
|
+
if (!mapped) {
|
|
6704
|
+
throw new Error(
|
|
6705
|
+
`Unknown agent "${raw}". Use: all, claude, copilot, codex, agent.`
|
|
6706
|
+
);
|
|
6707
|
+
}
|
|
6708
|
+
for (const item of mapped) selected.add(item);
|
|
6709
|
+
}
|
|
6710
|
+
return selected;
|
|
6711
|
+
}
|
|
6712
|
+
function pathExists(path) {
|
|
6713
|
+
try {
|
|
6714
|
+
lstatSync(path);
|
|
6715
|
+
return true;
|
|
6716
|
+
} catch {
|
|
6717
|
+
return false;
|
|
6718
|
+
}
|
|
6719
|
+
}
|
|
6720
|
+
function comparableRealPath(path) {
|
|
6721
|
+
const real = realpathSync(path);
|
|
6722
|
+
return process.platform === "win32" ? real.toLowerCase() : real;
|
|
6723
|
+
}
|
|
6724
|
+
function pathsResolveToSameDirectory(sourceDir, destinationDir) {
|
|
6725
|
+
try {
|
|
6726
|
+
return comparableRealPath(destinationDir) === comparableRealPath(sourceDir);
|
|
6727
|
+
} catch {
|
|
6728
|
+
return false;
|
|
6729
|
+
}
|
|
6730
|
+
}
|
|
6731
|
+
function linkPointsTo(sourceDir, destinationDir) {
|
|
6732
|
+
try {
|
|
6733
|
+
return lstatSync(destinationDir).isSymbolicLink() && pathsResolveToSameDirectory(sourceDir, destinationDir);
|
|
6734
|
+
} catch {
|
|
6735
|
+
return false;
|
|
6736
|
+
}
|
|
6737
|
+
}
|
|
6738
|
+
function isReplaceableCopiedSkill(destinationDir) {
|
|
6739
|
+
try {
|
|
6740
|
+
if (!lstatSync(destinationDir).isDirectory()) return false;
|
|
6741
|
+
const entries = readdirSync2(destinationDir);
|
|
6742
|
+
if (entries.length !== 1 || entries[0] !== "SKILL.md") return false;
|
|
6743
|
+
return /^name:\s*zam\s*$/m.test(
|
|
6744
|
+
readFileSync10(join15(destinationDir, "SKILL.md"), "utf8")
|
|
6745
|
+
);
|
|
6746
|
+
} catch {
|
|
6747
|
+
return false;
|
|
6748
|
+
}
|
|
6749
|
+
}
|
|
6750
|
+
function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {}) {
|
|
6751
|
+
const results = [];
|
|
6752
|
+
const log = (message) => {
|
|
6753
|
+
if (!opts.quiet) console.log(message);
|
|
6754
|
+
};
|
|
6755
|
+
for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
|
|
6756
|
+
if (!pairAgents.some((agent) => agents.has(agent))) continue;
|
|
6757
|
+
const sourceDir = dirname5(from);
|
|
6758
|
+
const destinationDir = dirname5(join15(cwd, to));
|
|
6759
|
+
if (!existsSync15(sourceDir)) {
|
|
6760
|
+
if (!opts.quiet) {
|
|
6761
|
+
console.warn(` warn source not found, skipping: ${sourceDir}`);
|
|
6762
|
+
}
|
|
6763
|
+
results.push({
|
|
6764
|
+
source: sourceDir,
|
|
6765
|
+
destination: destinationDir,
|
|
6766
|
+
action: "skipped",
|
|
6767
|
+
reason: "source-not-found"
|
|
6768
|
+
});
|
|
6769
|
+
continue;
|
|
6770
|
+
}
|
|
6771
|
+
if (pathsResolveToSameDirectory(sourceDir, destinationDir) && !lstatSync(destinationDir).isSymbolicLink()) {
|
|
6772
|
+
log(` skip ${dirname5(to)} (package source)`);
|
|
6773
|
+
results.push({
|
|
6774
|
+
source: sourceDir,
|
|
6775
|
+
destination: destinationDir,
|
|
6776
|
+
action: "skipped",
|
|
6777
|
+
reason: "source-directory"
|
|
6778
|
+
});
|
|
6779
|
+
continue;
|
|
6780
|
+
}
|
|
6781
|
+
if (linkPointsTo(sourceDir, destinationDir)) {
|
|
6782
|
+
log(` skip ${dirname5(to)} (already linked)`);
|
|
6783
|
+
results.push({
|
|
6784
|
+
source: sourceDir,
|
|
6785
|
+
destination: destinationDir,
|
|
6786
|
+
action: "skipped",
|
|
6787
|
+
reason: "already-linked"
|
|
6788
|
+
});
|
|
6789
|
+
continue;
|
|
6790
|
+
}
|
|
6791
|
+
const destinationExists = pathExists(destinationDir);
|
|
6792
|
+
const replaceExisting = destinationExists && (Boolean(opts.force) || isReplaceableCopiedSkill(destinationDir));
|
|
6793
|
+
if (destinationExists && !replaceExisting) {
|
|
6794
|
+
if (!opts.quiet) {
|
|
6795
|
+
console.warn(
|
|
6796
|
+
` warn ${dirname5(to)} already exists and is not managed by ZAM; use --force to replace it`
|
|
6797
|
+
);
|
|
6798
|
+
}
|
|
6799
|
+
results.push({
|
|
6800
|
+
source: sourceDir,
|
|
6801
|
+
destination: destinationDir,
|
|
6802
|
+
action: "skipped",
|
|
6803
|
+
reason: "unmanaged-destination"
|
|
6804
|
+
});
|
|
6805
|
+
continue;
|
|
6806
|
+
}
|
|
6807
|
+
const action = destinationExists ? "relinked" : "linked";
|
|
6808
|
+
if (opts.dryRun) {
|
|
6809
|
+
log(` would ${action === "linked" ? "link" : "relink"} ${dirname5(to)}`);
|
|
6810
|
+
} else {
|
|
6811
|
+
if (destinationExists) {
|
|
6812
|
+
rmSync2(destinationDir, { recursive: true, force: true });
|
|
6813
|
+
}
|
|
6814
|
+
mkdirSync8(dirname5(destinationDir), { recursive: true });
|
|
6815
|
+
symlinkSync(
|
|
6816
|
+
sourceDir,
|
|
6817
|
+
destinationDir,
|
|
6818
|
+
process.platform === "win32" ? "junction" : "dir"
|
|
6819
|
+
);
|
|
6820
|
+
log(` ${action === "linked" ? "link" : "relink"} ${dirname5(to)}`);
|
|
6821
|
+
}
|
|
6822
|
+
results.push({ source: sourceDir, destination: destinationDir, action });
|
|
6823
|
+
}
|
|
6824
|
+
return results;
|
|
6825
|
+
}
|
|
6826
|
+
function formatDatabaseInitTarget(target) {
|
|
6827
|
+
switch (target.kind) {
|
|
6828
|
+
case "local":
|
|
6829
|
+
return `ZAM database at ${target.location} (local SQLite)`;
|
|
6830
|
+
case "turso-remote":
|
|
6831
|
+
return `ZAM database via Turso remote at ${target.location}`;
|
|
6832
|
+
case "turso-native":
|
|
6833
|
+
return `ZAM database via Turso native driver at ${target.location}`;
|
|
6834
|
+
case "turso-replica":
|
|
6835
|
+
return `ZAM database replica at ${target.location} syncing from ${target.syncUrl}`;
|
|
6836
|
+
}
|
|
6837
|
+
}
|
|
6838
|
+
async function initDatabase(skipInit) {
|
|
6839
|
+
if (skipInit) return;
|
|
6840
|
+
try {
|
|
6841
|
+
const target = getDatabaseTargetInfo();
|
|
6842
|
+
const db = await openDatabaseWithSync({ initialize: true });
|
|
6843
|
+
await db.close();
|
|
6844
|
+
console.log(` init ${formatDatabaseInitTarget(target)}`);
|
|
6845
|
+
} catch (err) {
|
|
6846
|
+
const msg = err.message;
|
|
6847
|
+
if (!msg.includes("already")) {
|
|
6848
|
+
console.warn(` warn database init: ${msg}`);
|
|
6849
|
+
} else {
|
|
6850
|
+
console.log(` skip database already initialized`);
|
|
6851
|
+
}
|
|
6852
|
+
}
|
|
6853
|
+
}
|
|
6854
|
+
var ZAM_BLOCK_START = "<!-- ZAM:START -->";
|
|
6855
|
+
var ZAM_BLOCK_END = "<!-- ZAM:END -->";
|
|
6856
|
+
function upsertMarkedBlock(dest, blockBody, dryRun) {
|
|
6857
|
+
const block = `${ZAM_BLOCK_START}
|
|
6858
|
+
${blockBody.trim()}
|
|
6859
|
+
${ZAM_BLOCK_END}`;
|
|
6860
|
+
if (!existsSync15(dest)) {
|
|
6861
|
+
if (!dryRun) {
|
|
6862
|
+
mkdirSync8(dirname5(dest), { recursive: true });
|
|
6863
|
+
writeFileSync7(dest, `${block}
|
|
6864
|
+
`, "utf8");
|
|
6865
|
+
}
|
|
6866
|
+
return "write";
|
|
6867
|
+
}
|
|
6868
|
+
const existing = readFileSync10(dest, "utf8");
|
|
6869
|
+
if (existing.includes(block)) return "skip";
|
|
6870
|
+
const start = existing.indexOf(ZAM_BLOCK_START);
|
|
6871
|
+
const end = existing.indexOf(ZAM_BLOCK_END);
|
|
6872
|
+
const next = start >= 0 && end > start ? `${existing.slice(0, start)}${block}${existing.slice(end + ZAM_BLOCK_END.length)}` : `${existing.trimEnd()}
|
|
6873
|
+
|
|
6874
|
+
${block}
|
|
6875
|
+
`;
|
|
6876
|
+
if (!dryRun) writeFileSync7(dest, next, "utf8");
|
|
6877
|
+
return start >= 0 && end > start ? "update" : "write";
|
|
6878
|
+
}
|
|
6879
|
+
function logInstructionAction(action, label, dryRun) {
|
|
6880
|
+
if (action === "skip") {
|
|
6881
|
+
console.log(` skip ${label} (ZAM block already present)`);
|
|
6882
|
+
} else {
|
|
6883
|
+
console.log(` ${dryRun ? "would " : ""}${action} ${label}`);
|
|
6884
|
+
}
|
|
6885
|
+
}
|
|
6886
|
+
function writeClaudeMd(skipClaudeMd, cwd = process.cwd(), opts = {}) {
|
|
6887
|
+
if (skipClaudeMd) return;
|
|
6888
|
+
const dest = join15(cwd, "CLAUDE.md");
|
|
6889
|
+
if (existsSync15(dest)) {
|
|
6890
|
+
if (!opts.updateExisting) {
|
|
6891
|
+
console.log(` skip CLAUDE.md (already present)`);
|
|
6892
|
+
return;
|
|
6893
|
+
}
|
|
6894
|
+
const action = upsertMarkedBlock(
|
|
6895
|
+
dest,
|
|
6896
|
+
`## ZAM learning sessions
|
|
6897
|
+
|
|
6898
|
+
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.
|
|
6899
|
+
|
|
6900
|
+
- Skill files live under \`.claude/skills/zam/\`.
|
|
6901
|
+
- Fast-changing review data lives in \`~/.zam/\`, not in this repository.
|
|
6902
|
+
- The skill directory is linked to this ZAM installation and updates with it.`,
|
|
6903
|
+
Boolean(opts.dryRun)
|
|
6904
|
+
);
|
|
6905
|
+
logInstructionAction(action, "CLAUDE.md", Boolean(opts.dryRun));
|
|
6906
|
+
return;
|
|
6907
|
+
}
|
|
6908
|
+
const name = basename4(cwd);
|
|
6909
|
+
const content = `# ZAM Personal Kernel \xE2\u20AC\u201D ${name}
|
|
6910
|
+
|
|
6911
|
+
This is a ZAM personal instance. ZAM builds lasting skills through spaced
|
|
6912
|
+
repetition during real work \xE2\u20AC\u201D not separate study sessions.
|
|
6913
|
+
|
|
6914
|
+
## First time here?
|
|
6915
|
+
Run \`/setup\` in Claude Code or Gemini CLI to complete first-time setup.
|
|
6916
|
+
|
|
6917
|
+
## Regular use
|
|
6918
|
+
Run \`/zam\` to start a learning session on whatever you are working on.
|
|
6919
|
+
|
|
6920
|
+
## What lives here
|
|
6921
|
+
- \`beliefs/\` \xE2\u20AC\u201D your worldview, approved by git commit
|
|
6922
|
+
- \`goals/\` \xE2\u20AC\u201D your objectives, decomposed into tasks and learning tokens
|
|
6923
|
+
|
|
6924
|
+
## Fast-changing data
|
|
6925
|
+
Learning tokens, cards, and review history live in local SQLite by default.
|
|
6926
|
+
Use \`zam connector setup turso\` to store cloud credentials in
|
|
6927
|
+
\`~/.zam/credentials.json\` and use a Turso database across machines.
|
|
6928
|
+
`;
|
|
6929
|
+
if (opts.dryRun) {
|
|
6930
|
+
console.log(` would write CLAUDE.md`);
|
|
6931
|
+
} else {
|
|
6932
|
+
writeFileSync7(dest, content, "utf8");
|
|
6933
|
+
console.log(` write CLAUDE.md`);
|
|
6934
|
+
}
|
|
6935
|
+
}
|
|
6936
|
+
function writeAgentsMd(skipAgentsMd, cwd = process.cwd(), opts = {}) {
|
|
6937
|
+
if (skipAgentsMd) return;
|
|
6938
|
+
const dest = join15(cwd, "AGENTS.md");
|
|
6939
|
+
if (existsSync15(dest)) {
|
|
6940
|
+
if (!opts.updateExisting) {
|
|
6941
|
+
console.log(` skip AGENTS.md (already present)`);
|
|
6942
|
+
return;
|
|
6943
|
+
}
|
|
6944
|
+
const action = upsertMarkedBlock(
|
|
6945
|
+
dest,
|
|
6946
|
+
`## ZAM learning sessions
|
|
6947
|
+
|
|
6948
|
+
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.
|
|
6949
|
+
|
|
6950
|
+
- Skill files live under \`.agents/skills/zam/\`.
|
|
6951
|
+
- Fast-changing review data lives in \`~/.zam/\`, not in this repository.
|
|
6952
|
+
- The skill directory is linked to this ZAM installation and updates with it.`,
|
|
6953
|
+
Boolean(opts.dryRun)
|
|
6954
|
+
);
|
|
6955
|
+
logInstructionAction(action, "AGENTS.md", Boolean(opts.dryRun));
|
|
6956
|
+
return;
|
|
6957
|
+
}
|
|
6958
|
+
const name = basename4(cwd);
|
|
6959
|
+
const content = `# ZAM Personal Kernel - ${name}
|
|
6960
|
+
|
|
6961
|
+
This is a ZAM personal instance. ZAM builds lasting skills through spaced
|
|
6962
|
+
repetition during real work, not separate study sessions.
|
|
6963
|
+
|
|
6964
|
+
## First time here?
|
|
6965
|
+
Run \`zam setup\` from the shell. When this repository includes
|
|
6966
|
+
\`.agents/skills/setup/\`, you can instead select \`setup\` through \`/skills\`
|
|
6967
|
+
or invoke \`$setup\`.
|
|
6968
|
+
|
|
6969
|
+
## Regular use
|
|
6970
|
+
Select the \`zam\` skill through \`/skills\` or invoke \`$zam\` to start a
|
|
6971
|
+
learning session on whatever you are working on.
|
|
6972
|
+
|
|
6973
|
+
## What lives here
|
|
6974
|
+
- \`beliefs/\` - your worldview, approved by git commit
|
|
6975
|
+
- \`goals/\` - your objectives, decomposed into tasks and learning tokens
|
|
6976
|
+
|
|
6977
|
+
## Fast-changing data
|
|
6978
|
+
Learning tokens, cards, and review history live in local SQLite by default.
|
|
6979
|
+
Use \`zam connector setup turso\` to store cloud credentials in
|
|
6980
|
+
\`~/.zam/credentials.json\` and use a Turso database across machines.
|
|
6981
|
+
|
|
6982
|
+
## Codex skills
|
|
6983
|
+
Codex discovers repository skills under \`.agents/skills/\`. Run
|
|
6984
|
+
\`zam setup --force\` after upgrading \`zam-core\` to refresh them.
|
|
6985
|
+
`;
|
|
6986
|
+
if (opts.dryRun) {
|
|
6987
|
+
console.log(` would write AGENTS.md`);
|
|
6988
|
+
} else {
|
|
6989
|
+
writeFileSync7(dest, content, "utf8");
|
|
6990
|
+
console.log(` write AGENTS.md`);
|
|
6991
|
+
}
|
|
6992
|
+
}
|
|
6993
|
+
function writeCopilotInstructions(cwd = process.cwd(), opts = {}) {
|
|
6994
|
+
const dest = join15(cwd, ".github", "copilot-instructions.md");
|
|
6995
|
+
const action = upsertMarkedBlock(
|
|
6996
|
+
dest,
|
|
6997
|
+
`## ZAM learning sessions
|
|
6998
|
+
|
|
6999
|
+
ZAM is available in this repository through \`.claude/skills/zam/\`. Use the \`zam\` project skill from Copilot-compatible skill selection surfaces to turn real work into an observed learning session with active recall and FSRS scheduling.
|
|
7000
|
+
|
|
7001
|
+
- Fast-changing review data lives in \`~/.zam/\`, not in this repository.
|
|
7002
|
+
- The skill directory is linked to this ZAM installation and updates with it.`,
|
|
7003
|
+
Boolean(opts.dryRun)
|
|
7004
|
+
);
|
|
7005
|
+
logInstructionAction(
|
|
7006
|
+
action,
|
|
7007
|
+
".github/copilot-instructions.md",
|
|
7008
|
+
Boolean(opts.dryRun)
|
|
7009
|
+
);
|
|
7010
|
+
}
|
|
7011
|
+
var setupCommand = new Command2("setup").description(
|
|
7012
|
+
"Link ZAM skill directories into this workspace and initialize the database"
|
|
7013
|
+
).option("--force", "replace an unmanaged existing ZAM skill directory", false).option("--skip-init", "skip database initialization", false).option("--skip-claude-md", "skip CLAUDE.md generation", false).option("--skip-agents-md", "skip AGENTS.md generation", false).option("--target <path>", "repository/workspace directory to set up").option(
|
|
7014
|
+
"--agents <list>",
|
|
7015
|
+
"comma-separated agents to wire: all, claude, copilot, codex, agent"
|
|
7016
|
+
).option(
|
|
7017
|
+
"--dry-run",
|
|
7018
|
+
"show what would be written without changing files",
|
|
7019
|
+
false
|
|
7020
|
+
).action(
|
|
7021
|
+
async (opts) => {
|
|
7022
|
+
let agents;
|
|
7023
|
+
try {
|
|
7024
|
+
agents = parseSetupAgents(opts.agents);
|
|
7025
|
+
} catch (err) {
|
|
7026
|
+
console.error(`Error: ${err.message}`);
|
|
7027
|
+
process.exit(1);
|
|
7028
|
+
}
|
|
7029
|
+
const target = resolve3(opts.target ?? process.cwd());
|
|
7030
|
+
const updateExistingInstructions = Boolean(opts.target) || opts.force;
|
|
7031
|
+
console.log(
|
|
7032
|
+
`Setting up ZAM in ${target}${opts.dryRun ? " (dry run)" : ""}
|
|
7033
|
+
`
|
|
7034
|
+
);
|
|
7035
|
+
wireSkills(target, agents, {
|
|
7036
|
+
force: opts.force,
|
|
7037
|
+
dryRun: opts.dryRun
|
|
7038
|
+
});
|
|
7039
|
+
await initDatabase(opts.skipInit || opts.dryRun);
|
|
7040
|
+
if (agents.has("claude")) {
|
|
7041
|
+
writeClaudeMd(opts.skipClaudeMd, target, {
|
|
7042
|
+
dryRun: opts.dryRun,
|
|
7043
|
+
updateExisting: updateExistingInstructions
|
|
7044
|
+
});
|
|
7045
|
+
}
|
|
7046
|
+
if (agents.has("codex") || agents.has("agent")) {
|
|
7047
|
+
writeAgentsMd(opts.skipAgentsMd, target, {
|
|
7048
|
+
dryRun: opts.dryRun,
|
|
7049
|
+
updateExisting: updateExistingInstructions
|
|
7050
|
+
});
|
|
7051
|
+
}
|
|
7052
|
+
if (agents.has("copilot") && (opts.target || opts.agents)) {
|
|
7053
|
+
writeCopilotInstructions(target, {
|
|
7054
|
+
dryRun: opts.dryRun,
|
|
7055
|
+
updateExisting: updateExistingInstructions
|
|
7056
|
+
});
|
|
7057
|
+
}
|
|
7058
|
+
console.log(
|
|
7059
|
+
"\nDone. Run `zam whoami --set <your-id>` to set your identity. Start the `zam` skill with `/zam` in Claude/Copilot/Gemini-compatible clients or `$zam` (or `/skills`) in Codex."
|
|
7060
|
+
);
|
|
7061
|
+
}
|
|
7062
|
+
);
|
|
7063
|
+
|
|
6470
7064
|
// src/cli/commands/shared/db.ts
|
|
6471
7065
|
function defaultErrorHandler(message) {
|
|
6472
7066
|
console.error("Error:", message);
|
|
@@ -6489,11 +7083,11 @@ function jsonOut(data) {
|
|
|
6489
7083
|
|
|
6490
7084
|
// src/cli/commands/workspace.ts
|
|
6491
7085
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
6492
|
-
import { existsSync as
|
|
7086
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
6493
7087
|
import { homedir as homedir9 } from "os";
|
|
6494
|
-
import { join as
|
|
7088
|
+
import { join as join16, resolve as resolve4 } from "path";
|
|
6495
7089
|
import { confirm, input } from "@inquirer/prompts";
|
|
6496
|
-
import { Command as
|
|
7090
|
+
import { Command as Command3 } from "commander";
|
|
6497
7091
|
function runGit(cwd, args) {
|
|
6498
7092
|
try {
|
|
6499
7093
|
return execFileSync3("git", args, {
|
|
@@ -6502,18 +7096,179 @@ function runGit(cwd, args) {
|
|
|
6502
7096
|
encoding: "utf8"
|
|
6503
7097
|
}).trim();
|
|
6504
7098
|
} catch (err) {
|
|
6505
|
-
throw new Error(`Git command failed: ${err.message}`);
|
|
7099
|
+
throw new Error(`Git command failed: ${err.message}`);
|
|
7100
|
+
}
|
|
7101
|
+
}
|
|
7102
|
+
function ghRepoCreateArgs(repoName, repoVisibility) {
|
|
7103
|
+
return ["repo", "create", repoName, repoVisibility, "--source=.", "--push"];
|
|
7104
|
+
}
|
|
7105
|
+
function gitRemoteArgs(githubUrl, hasOrigin) {
|
|
7106
|
+
return hasOrigin ? ["remote", "set-url", "origin", githubUrl] : ["remote", "add", "origin", githubUrl];
|
|
7107
|
+
}
|
|
7108
|
+
var workspaceCommand = new Command3("workspace").description(
|
|
7109
|
+
"Manage your ZAM learning workspace"
|
|
7110
|
+
);
|
|
7111
|
+
var WORKSPACE_KINDS = [
|
|
7112
|
+
"personal",
|
|
7113
|
+
"team",
|
|
7114
|
+
"family",
|
|
7115
|
+
"community",
|
|
7116
|
+
"organization",
|
|
7117
|
+
"custom"
|
|
7118
|
+
];
|
|
7119
|
+
var WORKSPACE_SOURCE_CONTROLS = [
|
|
7120
|
+
"github",
|
|
7121
|
+
"azure-devops",
|
|
7122
|
+
"git",
|
|
7123
|
+
"none"
|
|
7124
|
+
];
|
|
7125
|
+
function parseWorkspaceKind(value) {
|
|
7126
|
+
const kind = (value ?? "custom").toLowerCase();
|
|
7127
|
+
if (WORKSPACE_KINDS.includes(kind)) {
|
|
7128
|
+
return kind;
|
|
7129
|
+
}
|
|
7130
|
+
throw new Error(
|
|
7131
|
+
`Invalid workspace kind: ${value}. Use ${WORKSPACE_KINDS.join(", ")}.`
|
|
7132
|
+
);
|
|
7133
|
+
}
|
|
7134
|
+
function parseWorkspaceSourceControl(value) {
|
|
7135
|
+
if (!value) return void 0;
|
|
7136
|
+
const source = value.toLowerCase();
|
|
7137
|
+
if (WORKSPACE_SOURCE_CONTROLS.includes(source)) {
|
|
7138
|
+
return source;
|
|
7139
|
+
}
|
|
7140
|
+
throw new Error(
|
|
7141
|
+
`Invalid source control: ${value}. Use ${WORKSPACE_SOURCE_CONTROLS.join(", ")}.`
|
|
7142
|
+
);
|
|
7143
|
+
}
|
|
7144
|
+
function parseScopes(value) {
|
|
7145
|
+
if (!value) return void 0;
|
|
7146
|
+
const scopes = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
7147
|
+
return scopes.length > 0 ? scopes : void 0;
|
|
7148
|
+
}
|
|
7149
|
+
function requireWorkspace(id) {
|
|
7150
|
+
const workspace = getConfiguredWorkspaces().find((item) => item.id === id);
|
|
7151
|
+
if (!workspace) {
|
|
7152
|
+
throw new Error(
|
|
7153
|
+
`Workspace "${id}" is not configured. Add it with: zam workspace add ${id} --path <dir>`
|
|
7154
|
+
);
|
|
7155
|
+
}
|
|
7156
|
+
return workspace;
|
|
7157
|
+
}
|
|
7158
|
+
workspaceCommand.command("list").description("List configured ZAM workspaces").option("--json", "Output as JSON").action((opts) => {
|
|
7159
|
+
const workspaces = getConfiguredWorkspaces();
|
|
7160
|
+
if (opts.json) {
|
|
7161
|
+
console.log(JSON.stringify({ workspaces }, null, 2));
|
|
7162
|
+
return;
|
|
7163
|
+
}
|
|
7164
|
+
console.log("Configured ZAM workspaces:\n");
|
|
7165
|
+
if (workspaces.length === 0) {
|
|
7166
|
+
console.log(
|
|
7167
|
+
" (none) \u2014 add one: zam workspace add personal --path <dir>"
|
|
7168
|
+
);
|
|
7169
|
+
return;
|
|
7170
|
+
}
|
|
7171
|
+
for (const workspace of workspaces) {
|
|
7172
|
+
const label = workspace.label ? ` (${workspace.label})` : "";
|
|
7173
|
+
console.log(` ${workspace.id}${label}`);
|
|
7174
|
+
console.log(` kind: ${workspace.kind}`);
|
|
7175
|
+
console.log(` path: ${workspace.path}`);
|
|
7176
|
+
if (workspace.sourceControl) {
|
|
7177
|
+
console.log(` source: ${workspace.sourceControl}`);
|
|
7178
|
+
}
|
|
7179
|
+
if (workspace.knowledgeScopes?.length) {
|
|
7180
|
+
console.log(` scopes: ${workspace.knowledgeScopes.join(", ")}`);
|
|
7181
|
+
}
|
|
7182
|
+
}
|
|
7183
|
+
});
|
|
7184
|
+
workspaceCommand.command("add <id>").description("Register an existing directory as a ZAM workspace").requiredOption("--path <dir>", "Existing workspace/repository directory").option(
|
|
7185
|
+
"--kind <kind>",
|
|
7186
|
+
`Workspace kind (${WORKSPACE_KINDS.join(" | ")})`,
|
|
7187
|
+
"custom"
|
|
7188
|
+
).option("--label <label>", "Human-readable label").option(
|
|
7189
|
+
"--source-control <provider>",
|
|
7190
|
+
`Source-control provider (${WORKSPACE_SOURCE_CONTROLS.join(" | ")})`
|
|
7191
|
+
).option("--scopes <list>", "Comma-separated knowledge scopes").option("--default-agent <id>", "Default agent harness for this workspace").action((id, opts) => {
|
|
7192
|
+
try {
|
|
7193
|
+
const path = resolve4(String(opts.path));
|
|
7194
|
+
if (!existsSync16(path)) {
|
|
7195
|
+
console.error(`Workspace path does not exist: ${path}`);
|
|
7196
|
+
process.exit(1);
|
|
7197
|
+
}
|
|
7198
|
+
const workspace = {
|
|
7199
|
+
id,
|
|
7200
|
+
kind: parseWorkspaceKind(opts.kind),
|
|
7201
|
+
path,
|
|
7202
|
+
...opts.label ? { label: opts.label } : {},
|
|
7203
|
+
...opts.sourceControl ? { sourceControl: parseWorkspaceSourceControl(opts.sourceControl) } : {},
|
|
7204
|
+
...parseScopes(opts.scopes) ? { knowledgeScopes: parseScopes(opts.scopes) } : {},
|
|
7205
|
+
...opts.defaultAgent ? { defaultAgent: opts.defaultAgent } : {}
|
|
7206
|
+
};
|
|
7207
|
+
wireSkills(path, parseSetupAgents());
|
|
7208
|
+
upsertConfiguredWorkspace(workspace);
|
|
7209
|
+
console.log(`Registered and linked workspace "${id}" at ${path}.`);
|
|
7210
|
+
} catch (err) {
|
|
7211
|
+
console.error(`Error: ${err.message}`);
|
|
7212
|
+
process.exit(1);
|
|
7213
|
+
}
|
|
7214
|
+
});
|
|
7215
|
+
workspaceCommand.command("remove <id>").description("Unregister a ZAM workspace without deleting its files").action((id) => {
|
|
7216
|
+
const existing = getConfiguredWorkspaces().find((item) => item.id === id);
|
|
7217
|
+
if (!existing) {
|
|
7218
|
+
console.error(`Workspace "${id}" is not configured.`);
|
|
7219
|
+
process.exit(1);
|
|
7220
|
+
}
|
|
7221
|
+
removeConfiguredWorkspace(id);
|
|
7222
|
+
console.log(
|
|
7223
|
+
`Unregistered workspace "${id}". Files in ${existing.path} were not changed.`
|
|
7224
|
+
);
|
|
7225
|
+
});
|
|
7226
|
+
workspaceCommand.command("setup <id>").description("Install ZAM skills into a configured workspace").option(
|
|
7227
|
+
"--agents <list>",
|
|
7228
|
+
"comma-separated agents to wire: all, claude, copilot, codex, agent"
|
|
7229
|
+
).option(
|
|
7230
|
+
"--force",
|
|
7231
|
+
"overwrite existing skill files and refresh ZAM blocks",
|
|
7232
|
+
false
|
|
7233
|
+
).option(
|
|
7234
|
+
"--dry-run",
|
|
7235
|
+
"show what would be written without changing files",
|
|
7236
|
+
false
|
|
7237
|
+
).action((id, opts) => {
|
|
7238
|
+
try {
|
|
7239
|
+
const workspace = requireWorkspace(id);
|
|
7240
|
+
const agents = parseSetupAgents(opts.agents);
|
|
7241
|
+
console.log(
|
|
7242
|
+
`Setting up workspace "${workspace.id}" in ${workspace.path}${opts.dryRun ? " (dry run)" : ""}
|
|
7243
|
+
`
|
|
7244
|
+
);
|
|
7245
|
+
wireSkills(workspace.path, agents, {
|
|
7246
|
+
force: Boolean(opts.force),
|
|
7247
|
+
dryRun: Boolean(opts.dryRun)
|
|
7248
|
+
});
|
|
7249
|
+
if (agents.has("claude")) {
|
|
7250
|
+
writeClaudeMd(false, workspace.path, {
|
|
7251
|
+
dryRun: Boolean(opts.dryRun),
|
|
7252
|
+
updateExisting: true
|
|
7253
|
+
});
|
|
7254
|
+
}
|
|
7255
|
+
if (agents.has("codex") || agents.has("agent")) {
|
|
7256
|
+
writeAgentsMd(false, workspace.path, {
|
|
7257
|
+
dryRun: Boolean(opts.dryRun),
|
|
7258
|
+
updateExisting: true
|
|
7259
|
+
});
|
|
7260
|
+
}
|
|
7261
|
+
if (agents.has("copilot")) {
|
|
7262
|
+
writeCopilotInstructions(workspace.path, {
|
|
7263
|
+
dryRun: Boolean(opts.dryRun),
|
|
7264
|
+
updateExisting: true
|
|
7265
|
+
});
|
|
7266
|
+
}
|
|
7267
|
+
} catch (err) {
|
|
7268
|
+
console.error(`Error: ${err.message}`);
|
|
7269
|
+
process.exit(1);
|
|
6506
7270
|
}
|
|
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
|
-
);
|
|
7271
|
+
});
|
|
6517
7272
|
workspaceCommand.command("publish").description("Publish your local workspace sandbox to GitHub").action(async () => {
|
|
6518
7273
|
let db;
|
|
6519
7274
|
let workspaceDir = "";
|
|
@@ -6530,7 +7285,7 @@ workspaceCommand.command("publish").description("Publish your local workspace sa
|
|
|
6530
7285
|
);
|
|
6531
7286
|
process.exit(1);
|
|
6532
7287
|
}
|
|
6533
|
-
if (!
|
|
7288
|
+
if (!existsSync16(workspaceDir)) {
|
|
6534
7289
|
console.error(
|
|
6535
7290
|
`\x1B[31m\u2717 Workspace directory does not exist: ${workspaceDir}\x1B[0m`
|
|
6536
7291
|
);
|
|
@@ -6544,15 +7299,15 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
|
6544
7299
|
);
|
|
6545
7300
|
process.exit(1);
|
|
6546
7301
|
}
|
|
6547
|
-
const gitignorePath =
|
|
6548
|
-
if (!
|
|
6549
|
-
|
|
7302
|
+
const gitignorePath = join16(workspaceDir, ".gitignore");
|
|
7303
|
+
if (!existsSync16(gitignorePath)) {
|
|
7304
|
+
writeFileSync8(
|
|
6550
7305
|
gitignorePath,
|
|
6551
7306
|
"node_modules/\n.agent/\n.agents/\n.claude/\n.gemini/\n.goose/\n*.log\n",
|
|
6552
7307
|
"utf8"
|
|
6553
7308
|
);
|
|
6554
7309
|
}
|
|
6555
|
-
const hasGitRepo =
|
|
7310
|
+
const hasGitRepo = existsSync16(join16(workspaceDir, ".git"));
|
|
6556
7311
|
if (!hasGitRepo) {
|
|
6557
7312
|
console.log("Initializing local Git repository...");
|
|
6558
7313
|
runGit(workspaceDir, ["init", "-b", "main"]);
|
|
@@ -6639,15 +7394,15 @@ Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
|
6639
7394
|
}
|
|
6640
7395
|
});
|
|
6641
7396
|
async function backupDatabaseTo(db, targetDir) {
|
|
6642
|
-
const backupDir =
|
|
6643
|
-
|
|
7397
|
+
const backupDir = join16(targetDir, "zam-backups");
|
|
7398
|
+
mkdirSync9(backupDir, { recursive: true });
|
|
6644
7399
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6645
|
-
const dest =
|
|
7400
|
+
const dest = join16(backupDir, `zam-${stamp}.db`);
|
|
6646
7401
|
await db.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
|
|
6647
7402
|
return dest;
|
|
6648
7403
|
}
|
|
6649
7404
|
workspaceCommand.command("data-dir").description("Print the ZAM data directory (database, credentials, config)").option("--json", "Output as JSON").action((opts) => {
|
|
6650
|
-
const dir =
|
|
7405
|
+
const dir = join16(homedir9(), ".zam");
|
|
6651
7406
|
console.log(opts.json ? JSON.stringify({ dataDir: dir }) : dir);
|
|
6652
7407
|
});
|
|
6653
7408
|
workspaceCommand.command("backup").description("Back up the local ZAM database into your workspace").option(
|
|
@@ -6667,7 +7422,7 @@ workspaceCommand.command("backup").description("Back up the local ZAM database i
|
|
|
6667
7422
|
let db;
|
|
6668
7423
|
try {
|
|
6669
7424
|
db = await openDatabase();
|
|
6670
|
-
const workspaceDir = opts.dir || await getSetting(db, "personal.workspace_dir") ||
|
|
7425
|
+
const workspaceDir = opts.dir || await getSetting(db, "personal.workspace_dir") || join16(homedir9(), "Documents", "zam");
|
|
6671
7426
|
const dest = await backupDatabaseTo(db, workspaceDir);
|
|
6672
7427
|
if (opts.json) {
|
|
6673
7428
|
console.log(JSON.stringify({ ok: true, path: dest }));
|
|
@@ -6743,7 +7498,7 @@ function parseTokenUpdates(opts) {
|
|
|
6743
7498
|
}
|
|
6744
7499
|
return updates;
|
|
6745
7500
|
}
|
|
6746
|
-
var bridgeCommand = new
|
|
7501
|
+
var bridgeCommand = new Command4("bridge").description(
|
|
6747
7502
|
"Machine-readable JSON protocol for AI integration"
|
|
6748
7503
|
);
|
|
6749
7504
|
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 +7541,7 @@ bridgeCommand.command("backup-db").description("Back up the local database into
|
|
|
6786
7541
|
return;
|
|
6787
7542
|
}
|
|
6788
7543
|
await withDb2(async (db) => {
|
|
6789
|
-
const workspaceDir = opts.dir || await getSetting(db, "personal.workspace_dir") ||
|
|
7544
|
+
const workspaceDir = opts.dir || await getSetting(db, "personal.workspace_dir") || join17(homedir10(), "Documents", "zam");
|
|
6790
7545
|
const path = await backupDatabaseTo(db, workspaceDir);
|
|
6791
7546
|
jsonOut2({ ok: true, path });
|
|
6792
7547
|
});
|
|
@@ -6796,18 +7551,199 @@ bridgeCommand.command("workspace-info").description("Report the workspace dir, i
|
|
|
6796
7551
|
const workspaceDir = await getSetting(db, "personal.workspace_dir") || null;
|
|
6797
7552
|
jsonOut2({
|
|
6798
7553
|
workspaceDir,
|
|
6799
|
-
defaultWorkspaceDir:
|
|
6800
|
-
dataDir:
|
|
7554
|
+
defaultWorkspaceDir: join17(homedir10(), "Documents", "zam"),
|
|
7555
|
+
dataDir: join17(homedir10(), ".zam")
|
|
7556
|
+
});
|
|
7557
|
+
});
|
|
7558
|
+
});
|
|
7559
|
+
function sameWorkspacePath(left, right) {
|
|
7560
|
+
const normalize = (value) => {
|
|
7561
|
+
const path = resolve5(value);
|
|
7562
|
+
return process.platform === "win32" ? path.toLowerCase() : path;
|
|
7563
|
+
};
|
|
7564
|
+
return normalize(left) === normalize(right);
|
|
7565
|
+
}
|
|
7566
|
+
async function ensureDesktopWorkspace(db) {
|
|
7567
|
+
const configured = getConfiguredWorkspaces();
|
|
7568
|
+
const savedWorkspaceDir = await getSetting(db, "personal.workspace_dir");
|
|
7569
|
+
const workspaceDir = savedWorkspaceDir || configured.find((workspace) => workspace.kind === "personal")?.path || join17(homedir10(), "Documents", "zam");
|
|
7570
|
+
mkdirSync10(workspaceDir, { recursive: true });
|
|
7571
|
+
if (!savedWorkspaceDir) {
|
|
7572
|
+
await setSetting(db, "personal.workspace_dir", workspaceDir);
|
|
7573
|
+
}
|
|
7574
|
+
if (!configured.some(
|
|
7575
|
+
(workspace) => sameWorkspacePath(workspace.path, workspaceDir)
|
|
7576
|
+
)) {
|
|
7577
|
+
const id = configured.some((workspace) => workspace.id === "personal") ? workspaceIdFromPath(workspaceDir) : "personal";
|
|
7578
|
+
upsertConfiguredWorkspace({
|
|
7579
|
+
id,
|
|
7580
|
+
label: basename5(workspaceDir) || "ZAM",
|
|
7581
|
+
kind: "personal",
|
|
7582
|
+
path: workspaceDir
|
|
7583
|
+
});
|
|
7584
|
+
}
|
|
7585
|
+
const skillLinks = getConfiguredWorkspaces().flatMap(
|
|
7586
|
+
(workspace) => existsSync17(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
|
|
7587
|
+
);
|
|
7588
|
+
return { workspaceDir, skillLinks };
|
|
7589
|
+
}
|
|
7590
|
+
bridgeCommand.command("workspace-list").description("List configured ZAM workspaces (JSON)").action(async () => {
|
|
7591
|
+
await withDb2(async (db) => {
|
|
7592
|
+
const workspaceDir = await getSetting(db, "personal.workspace_dir") || null;
|
|
7593
|
+
jsonOut2({
|
|
7594
|
+
workspaces: getConfiguredWorkspaces(),
|
|
7595
|
+
workspaceDir,
|
|
7596
|
+
defaultWorkspaceDir: join17(homedir10(), "Documents", "zam"),
|
|
7597
|
+
dataDir: join17(homedir10(), ".zam")
|
|
7598
|
+
});
|
|
7599
|
+
});
|
|
7600
|
+
});
|
|
7601
|
+
function workspaceIdFromPath(dir) {
|
|
7602
|
+
const base = basename5(dir).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
7603
|
+
const prefix = base || "workspace";
|
|
7604
|
+
const existing = new Set(getConfiguredWorkspaces().map((item) => item.id));
|
|
7605
|
+
if (!existing.has(prefix)) return prefix;
|
|
7606
|
+
let index = 2;
|
|
7607
|
+
while (existing.has(`${prefix}-${index}`)) index++;
|
|
7608
|
+
return `${prefix}-${index}`;
|
|
7609
|
+
}
|
|
7610
|
+
function parseBridgeWorkspaceKind(value) {
|
|
7611
|
+
const kind = (value || "custom").toLowerCase();
|
|
7612
|
+
if (kind === "personal" || kind === "team" || kind === "family" || kind === "community" || kind === "organization" || kind === "custom") {
|
|
7613
|
+
return kind;
|
|
7614
|
+
}
|
|
7615
|
+
jsonError(`Invalid workspace kind: ${value}`);
|
|
7616
|
+
}
|
|
7617
|
+
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) => {
|
|
7618
|
+
const raw = String(opts.path ?? "").trim();
|
|
7619
|
+
if (!raw) jsonError("A non-empty --path is required");
|
|
7620
|
+
const path = resolve5(raw);
|
|
7621
|
+
if (!existsSync17(path)) jsonError(`Workspace path does not exist: ${path}`);
|
|
7622
|
+
const id = String(opts.id || workspaceIdFromPath(path)).trim();
|
|
7623
|
+
if (!id) jsonError("A non-empty --id is required");
|
|
7624
|
+
const workspace = {
|
|
7625
|
+
id,
|
|
7626
|
+
label: opts.label || basename5(path) || id,
|
|
7627
|
+
kind: parseBridgeWorkspaceKind(opts.kind),
|
|
7628
|
+
path
|
|
7629
|
+
};
|
|
7630
|
+
const skillLinks = wireSkills(path, parseSetupAgents(), { quiet: true });
|
|
7631
|
+
await withDb2(async (db) => {
|
|
7632
|
+
await setSetting(db, "personal.workspace_dir", path);
|
|
7633
|
+
upsertConfiguredWorkspace(workspace);
|
|
7634
|
+
jsonOut2({
|
|
7635
|
+
ok: true,
|
|
7636
|
+
workspace,
|
|
7637
|
+
workspaces: getConfiguredWorkspaces(),
|
|
7638
|
+
workspaceDir: path,
|
|
7639
|
+
skillLinks
|
|
7640
|
+
});
|
|
7641
|
+
});
|
|
7642
|
+
});
|
|
7643
|
+
bridgeCommand.command("workspace-remove").description("Unregister a ZAM workspace without deleting its files (JSON)").requiredOption("--id <id>", "Workspace id").action(async (opts) => {
|
|
7644
|
+
const id = String(opts.id ?? "").trim();
|
|
7645
|
+
if (!id) jsonError("A non-empty --id is required");
|
|
7646
|
+
const workspace = getConfiguredWorkspaces().find((item) => item.id === id);
|
|
7647
|
+
if (!workspace) jsonError(`Workspace "${id}" is not configured`);
|
|
7648
|
+
await withDb2(async (db) => {
|
|
7649
|
+
const activeWorkspaceDir = await getSetting(db, "personal.workspace_dir") || null;
|
|
7650
|
+
const remaining = removeConfiguredWorkspace(id);
|
|
7651
|
+
let workspaceDir = activeWorkspaceDir;
|
|
7652
|
+
let skillLinks = [];
|
|
7653
|
+
if (activeWorkspaceDir && sameWorkspacePath(activeWorkspaceDir, workspace.path)) {
|
|
7654
|
+
if (remaining.length > 0) {
|
|
7655
|
+
workspaceDir = remaining[0].path;
|
|
7656
|
+
await setSetting(db, "personal.workspace_dir", workspaceDir);
|
|
7657
|
+
} else {
|
|
7658
|
+
workspaceDir = join17(homedir10(), "Documents", "zam");
|
|
7659
|
+
await setSetting(db, "personal.workspace_dir", workspaceDir);
|
|
7660
|
+
const ensured = await ensureDesktopWorkspace(db);
|
|
7661
|
+
workspaceDir = ensured.workspaceDir;
|
|
7662
|
+
skillLinks = ensured.skillLinks;
|
|
7663
|
+
}
|
|
7664
|
+
}
|
|
7665
|
+
jsonOut2({
|
|
7666
|
+
ok: true,
|
|
7667
|
+
removed: workspace,
|
|
7668
|
+
workspaces: getConfiguredWorkspaces(),
|
|
7669
|
+
workspaceDir,
|
|
7670
|
+
skillLinks
|
|
6801
7671
|
});
|
|
6802
7672
|
});
|
|
6803
7673
|
});
|
|
6804
7674
|
bridgeCommand.command("set-workspace-dir").description("Set the personal workspace directory (JSON)").requiredOption("--dir <path>", "Path to the workspace directory").action(async (opts) => {
|
|
6805
7675
|
const raw = String(opts.dir ?? "").trim();
|
|
6806
7676
|
if (!raw) jsonError("A non-empty --dir is required");
|
|
6807
|
-
const dir =
|
|
7677
|
+
const dir = resolve5(raw);
|
|
7678
|
+
if (!existsSync17(dir)) jsonError(`Workspace path does not exist: ${dir}`);
|
|
7679
|
+
const skillLinks = wireSkills(dir, parseSetupAgents(), { quiet: true });
|
|
6808
7680
|
await withDb2(async (db) => {
|
|
6809
7681
|
await setSetting(db, "personal.workspace_dir", dir);
|
|
6810
|
-
jsonOut2({ ok: true, workspaceDir: dir });
|
|
7682
|
+
jsonOut2({ ok: true, workspaceDir: dir, skillLinks });
|
|
7683
|
+
});
|
|
7684
|
+
});
|
|
7685
|
+
bridgeCommand.command("agent-list").description("List agent harnesses with detection state + the default (JSON)").action(async () => {
|
|
7686
|
+
await withDb2(async (db) => {
|
|
7687
|
+
const configuredDefault = await getSetting(db, "agent.default") || null;
|
|
7688
|
+
const harnesses = await Promise.all(
|
|
7689
|
+
AGENT_HARNESSES.map(async (h) => {
|
|
7690
|
+
const override = await getSetting(db, `agent.${h.id}.command`) || void 0;
|
|
7691
|
+
return {
|
|
7692
|
+
id: h.id,
|
|
7693
|
+
label: h.label,
|
|
7694
|
+
kind: h.kind,
|
|
7695
|
+
detected: resolveHarnessExecutable(h, override) !== null
|
|
7696
|
+
};
|
|
7697
|
+
})
|
|
7698
|
+
);
|
|
7699
|
+
const fallbackDefault = harnesses.find((h) => h.detected)?.id ?? null;
|
|
7700
|
+
jsonOut2({ harnesses, default: configuredDefault ?? fallbackDefault });
|
|
7701
|
+
});
|
|
7702
|
+
});
|
|
7703
|
+
bridgeCommand.command("agent-open").description("Launch an agent harness in the workspace (JSON)").option(
|
|
7704
|
+
"--id <id>",
|
|
7705
|
+
"Harness id (default: agent.default setting, else first detected)"
|
|
7706
|
+
).option("--workspace <id>", "Configured workspace id to open").option("--dir <path>", "Explicit workspace directory to open").action(async (opts) => {
|
|
7707
|
+
await withDb2(async (db) => {
|
|
7708
|
+
let id = opts.id || await getSetting(db, "agent.default") || void 0;
|
|
7709
|
+
if (!id) {
|
|
7710
|
+
id = AGENT_HARNESSES.find((h) => resolveHarnessExecutable(h))?.id;
|
|
7711
|
+
}
|
|
7712
|
+
if (!id) {
|
|
7713
|
+
jsonError(
|
|
7714
|
+
"No agent harness configured or detected. Install one (Claude Code, Codex, opencode) or set agent.default."
|
|
7715
|
+
);
|
|
7716
|
+
}
|
|
7717
|
+
const harness = getHarness(id);
|
|
7718
|
+
if (!harness) {
|
|
7719
|
+
jsonError(`Unknown harness: ${id}`);
|
|
7720
|
+
}
|
|
7721
|
+
const override = await getSetting(db, `agent.${harness.id}.command`) || void 0;
|
|
7722
|
+
const executable = resolveHarnessExecutable(harness, override);
|
|
7723
|
+
if (!executable) {
|
|
7724
|
+
jsonError(
|
|
7725
|
+
`${harness.label} was not detected. Set its path: zam settings set agent.${harness.id}.command <path>`
|
|
7726
|
+
);
|
|
7727
|
+
}
|
|
7728
|
+
const configuredWorkspace = opts.workspace ? getConfiguredWorkspaces().find((item) => item.id === opts.workspace) : void 0;
|
|
7729
|
+
if (opts.workspace && !configuredWorkspace) {
|
|
7730
|
+
jsonError(`Workspace is not configured: ${opts.workspace}`);
|
|
7731
|
+
}
|
|
7732
|
+
let workspace = opts.dir || configuredWorkspace?.path || await getSetting(db, "personal.workspace_dir") || join17(homedir10(), "Documents", "zam");
|
|
7733
|
+
if (!existsSync17(workspace)) workspace = homedir10();
|
|
7734
|
+
launchHarness(harness, {
|
|
7735
|
+
executable,
|
|
7736
|
+
workspace,
|
|
7737
|
+
shell: normalizeShell(void 0),
|
|
7738
|
+
silent: true
|
|
7739
|
+
});
|
|
7740
|
+
jsonOut2({
|
|
7741
|
+
ok: true,
|
|
7742
|
+
id: harness.id,
|
|
7743
|
+
label: harness.label,
|
|
7744
|
+
kind: harness.kind,
|
|
7745
|
+
workspace
|
|
7746
|
+
});
|
|
6811
7747
|
});
|
|
6812
7748
|
});
|
|
6813
7749
|
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) => {
|
|
@@ -7168,10 +8104,10 @@ bridgeCommand.command("discover-skills").description(
|
|
|
7168
8104
|
"20"
|
|
7169
8105
|
).action(async (opts) => {
|
|
7170
8106
|
try {
|
|
7171
|
-
const monitorDir =
|
|
8107
|
+
const monitorDir = join17(homedir10(), ".zam", "monitor");
|
|
7172
8108
|
let files;
|
|
7173
8109
|
try {
|
|
7174
|
-
files =
|
|
8110
|
+
files = readdirSync3(monitorDir).filter((f) => f.endsWith(".jsonl"));
|
|
7175
8111
|
} catch {
|
|
7176
8112
|
jsonOut2({ proposals: [], message: "No monitor logs found." });
|
|
7177
8113
|
return;
|
|
@@ -7181,7 +8117,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
7181
8117
|
return;
|
|
7182
8118
|
}
|
|
7183
8119
|
const limit = Number(opts.limit);
|
|
7184
|
-
const sorted = files.map((f) => ({ name: f, path:
|
|
8120
|
+
const sorted = files.map((f) => ({ name: f, path: join17(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
|
|
7185
8121
|
const sessionCommands = /* @__PURE__ */ new Map();
|
|
7186
8122
|
for (const file of sorted) {
|
|
7187
8123
|
const sessionId = file.name.replace(".jsonl", "");
|
|
@@ -7683,7 +8619,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
|
|
|
7683
8619
|
return;
|
|
7684
8620
|
}
|
|
7685
8621
|
}
|
|
7686
|
-
const outputPath = opts.image ?? opts.output ??
|
|
8622
|
+
const outputPath = opts.image ?? opts.output ?? join17(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
|
|
7687
8623
|
const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
|
|
7688
8624
|
if (!isProvided) {
|
|
7689
8625
|
const post = decidePostCapture(policy, {
|
|
@@ -7694,7 +8630,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
|
|
|
7694
8630
|
if (!post.allowed) {
|
|
7695
8631
|
if (!opts.output) {
|
|
7696
8632
|
try {
|
|
7697
|
-
|
|
8633
|
+
rmSync3(outputPath, { force: true });
|
|
7698
8634
|
} catch {
|
|
7699
8635
|
}
|
|
7700
8636
|
}
|
|
@@ -7711,7 +8647,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
|
|
|
7711
8647
|
return;
|
|
7712
8648
|
}
|
|
7713
8649
|
}
|
|
7714
|
-
const imageBytes =
|
|
8650
|
+
const imageBytes = readFileSync11(outputPath);
|
|
7715
8651
|
const base64 = imageBytes.toString("base64");
|
|
7716
8652
|
jsonOut2({
|
|
7717
8653
|
sessionId: opts.session ?? null,
|
|
@@ -7738,11 +8674,11 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
7738
8674
|
return;
|
|
7739
8675
|
}
|
|
7740
8676
|
const sessionId = opts.session;
|
|
7741
|
-
const statePath =
|
|
8677
|
+
const statePath = join17(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
7742
8678
|
const defaultExt = platform === "win32" ? ".mkv" : ".mov";
|
|
7743
|
-
const outputPath = opts.output ??
|
|
7744
|
-
const { existsSync:
|
|
7745
|
-
if (
|
|
8679
|
+
const outputPath = opts.output ?? join17(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
|
|
8680
|
+
const { existsSync: existsSync25, writeFileSync: writeFileSync12, openSync, closeSync } = await import("fs");
|
|
8681
|
+
if (existsSync25(statePath)) {
|
|
7746
8682
|
jsonOut2({
|
|
7747
8683
|
sessionId,
|
|
7748
8684
|
started: false,
|
|
@@ -7750,7 +8686,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
7750
8686
|
});
|
|
7751
8687
|
return;
|
|
7752
8688
|
}
|
|
7753
|
-
const logPath =
|
|
8689
|
+
const logPath = join17(tmpdir3(), `zam-recording-${sessionId}.log`);
|
|
7754
8690
|
let logFd;
|
|
7755
8691
|
try {
|
|
7756
8692
|
logFd = openSync(logPath, "w");
|
|
@@ -7832,9 +8768,9 @@ bridgeCommand.command("stop-recording").description(
|
|
|
7832
8768
|
return;
|
|
7833
8769
|
}
|
|
7834
8770
|
const sessionId = opts.session;
|
|
7835
|
-
const statePath =
|
|
7836
|
-
const { existsSync:
|
|
7837
|
-
if (!
|
|
8771
|
+
const statePath = join17(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
8772
|
+
const { existsSync: existsSync25, readFileSync: readFileSync16, rmSync: rmSync4 } = await import("fs");
|
|
8773
|
+
if (!existsSync25(statePath)) {
|
|
7838
8774
|
jsonOut2({
|
|
7839
8775
|
sessionId,
|
|
7840
8776
|
stopped: false,
|
|
@@ -7842,7 +8778,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
7842
8778
|
});
|
|
7843
8779
|
return;
|
|
7844
8780
|
}
|
|
7845
|
-
const state = JSON.parse(
|
|
8781
|
+
const state = JSON.parse(readFileSync16(statePath, "utf8"));
|
|
7846
8782
|
const { pid, outputPath } = state;
|
|
7847
8783
|
try {
|
|
7848
8784
|
process.kill(pid, "SIGINT");
|
|
@@ -7858,7 +8794,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
7858
8794
|
};
|
|
7859
8795
|
let attempts = 0;
|
|
7860
8796
|
while (isProcessRunning(pid) && attempts < 20) {
|
|
7861
|
-
await new Promise((
|
|
8797
|
+
await new Promise((resolve9) => setTimeout(resolve9, 250));
|
|
7862
8798
|
attempts++;
|
|
7863
8799
|
}
|
|
7864
8800
|
if (isProcessRunning(pid)) {
|
|
@@ -7868,10 +8804,10 @@ bridgeCommand.command("stop-recording").description(
|
|
|
7868
8804
|
}
|
|
7869
8805
|
}
|
|
7870
8806
|
try {
|
|
7871
|
-
|
|
8807
|
+
rmSync4(statePath, { force: true });
|
|
7872
8808
|
} catch {
|
|
7873
8809
|
}
|
|
7874
|
-
if (!
|
|
8810
|
+
if (!existsSync25(outputPath)) {
|
|
7875
8811
|
jsonOut2({
|
|
7876
8812
|
sessionId,
|
|
7877
8813
|
stopped: false,
|
|
@@ -7897,7 +8833,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
7897
8833
|
return;
|
|
7898
8834
|
}
|
|
7899
8835
|
try {
|
|
7900
|
-
|
|
8836
|
+
rmSync4(outputPath, { force: true });
|
|
7901
8837
|
} catch {
|
|
7902
8838
|
}
|
|
7903
8839
|
jsonOut2({
|
|
@@ -7962,6 +8898,16 @@ bridgeCommand.command("check-llm").description("Check if LLM is enabled and onli
|
|
|
7962
8898
|
});
|
|
7963
8899
|
});
|
|
7964
8900
|
});
|
|
8901
|
+
bridgeCommand.command("provider-status").description("Show secret-safe provider status for LLM roles (JSON)").action(async () => {
|
|
8902
|
+
await withDb2(async (db) => {
|
|
8903
|
+
const [recall, vision, text] = await Promise.all([
|
|
8904
|
+
getProviderRoleStatus(db, "recall"),
|
|
8905
|
+
getProviderRoleStatus(db, "vision"),
|
|
8906
|
+
getProviderRoleStatus(db, "text")
|
|
8907
|
+
]);
|
|
8908
|
+
jsonOut2({ roles: { recall, vision, text } });
|
|
8909
|
+
});
|
|
8910
|
+
});
|
|
7965
8911
|
bridgeCommand.command("check-vision").description(
|
|
7966
8912
|
"Check if UI observer vision analysis is enabled and ready (JSON)"
|
|
7967
8913
|
).action(async () => {
|
|
@@ -8052,10 +8998,13 @@ bridgeCommand.command("desktop-bootstrap").description("Initialize first-run des
|
|
|
8052
8998
|
await withDb2(async (db) => {
|
|
8053
8999
|
const userId = await ensureDefaultUser(db, opts.user);
|
|
8054
9000
|
const { enabled, url, model, locale } = await getLlmConfig(db);
|
|
9001
|
+
const { workspaceDir, skillLinks } = await ensureDesktopWorkspace(db);
|
|
8055
9002
|
jsonOut2({
|
|
8056
9003
|
userId,
|
|
8057
9004
|
locale,
|
|
8058
|
-
llm: { enabled, url, model }
|
|
9005
|
+
llm: { enabled, url, model },
|
|
9006
|
+
workspaceDir,
|
|
9007
|
+
skillLinks
|
|
8059
9008
|
});
|
|
8060
9009
|
});
|
|
8061
9010
|
});
|
|
@@ -8274,8 +9223,8 @@ bridgeCommand.command("serve").description("Start the persistent JSON-RPC stdin/
|
|
|
8274
9223
|
});
|
|
8275
9224
|
|
|
8276
9225
|
// src/cli/commands/card.ts
|
|
8277
|
-
import { Command as
|
|
8278
|
-
var cardCommand = new
|
|
9226
|
+
import { Command as Command5 } from "commander";
|
|
9227
|
+
var cardCommand = new Command5("card").description(
|
|
8279
9228
|
"Manage spaced-repetition cards"
|
|
8280
9229
|
);
|
|
8281
9230
|
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 +9423,8 @@ cardCommand.command("delete").description("Delete one user's card for a token").
|
|
|
8474
9423
|
|
|
8475
9424
|
// src/cli/commands/connector.ts
|
|
8476
9425
|
import { input as input2, password } from "@inquirer/prompts";
|
|
8477
|
-
import { Command as
|
|
8478
|
-
var connectorCommand = new
|
|
9426
|
+
import { Command as Command6 } from "commander";
|
|
9427
|
+
var connectorCommand = new Command6("connector").description(
|
|
8479
9428
|
"Manage external service connectors"
|
|
8480
9429
|
);
|
|
8481
9430
|
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 +9567,26 @@ async function setupTurso(urlArg, tokenArg, mode) {
|
|
|
8618
9567
|
|
|
8619
9568
|
// src/cli/commands/git-sync.ts
|
|
8620
9569
|
import { execSync as execSync5 } from "child_process";
|
|
8621
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
8622
|
-
import { join as
|
|
8623
|
-
import { Command as
|
|
9570
|
+
import { chmodSync as chmodSync2, existsSync as existsSync18, writeFileSync as writeFileSync9 } from "fs";
|
|
9571
|
+
import { join as join18 } from "path";
|
|
9572
|
+
import { Command as Command7 } from "commander";
|
|
8624
9573
|
function installHook2() {
|
|
8625
|
-
const gitDir =
|
|
8626
|
-
if (!
|
|
9574
|
+
const gitDir = join18(process.cwd(), ".git");
|
|
9575
|
+
if (!existsSync18(gitDir)) {
|
|
8627
9576
|
console.error(
|
|
8628
9577
|
"Error: Current directory is not the root of a Git repository."
|
|
8629
9578
|
);
|
|
8630
9579
|
process.exit(1);
|
|
8631
9580
|
}
|
|
8632
|
-
const hooksDir =
|
|
8633
|
-
const hookPath =
|
|
9581
|
+
const hooksDir = join18(gitDir, "hooks");
|
|
9582
|
+
const hookPath = join18(hooksDir, "post-commit");
|
|
8634
9583
|
const hookContent = `#!/bin/sh
|
|
8635
9584
|
# ZAM Spaced Repetition Auto-Stale Hook
|
|
8636
9585
|
# Triggered automatically on git commits to decay modified concept cards.
|
|
8637
9586
|
zam git-sync --commit HEAD --quiet
|
|
8638
9587
|
`;
|
|
8639
9588
|
try {
|
|
8640
|
-
|
|
9589
|
+
writeFileSync9(hookPath, hookContent, { encoding: "utf-8", flag: "w" });
|
|
8641
9590
|
try {
|
|
8642
9591
|
chmodSync2(hookPath, "755");
|
|
8643
9592
|
} catch (_e) {
|
|
@@ -8650,7 +9599,7 @@ zam git-sync --commit HEAD --quiet
|
|
|
8650
9599
|
process.exit(1);
|
|
8651
9600
|
}
|
|
8652
9601
|
}
|
|
8653
|
-
var gitSyncCommand = new
|
|
9602
|
+
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
9603
|
if (opts.install) {
|
|
8655
9604
|
installHook2();
|
|
8656
9605
|
return;
|
|
@@ -8739,10 +9688,10 @@ ZAM Auto-Stale Complete: Scanned ${changedFiles.length} file(s).`
|
|
|
8739
9688
|
});
|
|
8740
9689
|
|
|
8741
9690
|
// src/cli/commands/goal.ts
|
|
8742
|
-
import { existsSync as
|
|
8743
|
-
import { resolve as
|
|
9691
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync11 } from "fs";
|
|
9692
|
+
import { resolve as resolve6 } from "path";
|
|
8744
9693
|
import { input as input3 } from "@inquirer/prompts";
|
|
8745
|
-
import { Command as
|
|
9694
|
+
import { Command as Command8 } from "commander";
|
|
8746
9695
|
async function resolveGoalsDir() {
|
|
8747
9696
|
let goalsDir;
|
|
8748
9697
|
let db;
|
|
@@ -8753,9 +9702,9 @@ async function resolveGoalsDir() {
|
|
|
8753
9702
|
} finally {
|
|
8754
9703
|
await db?.close();
|
|
8755
9704
|
}
|
|
8756
|
-
return goalsDir ?
|
|
9705
|
+
return goalsDir ? resolve6(goalsDir) : resolve6("goals");
|
|
8757
9706
|
}
|
|
8758
|
-
var goalCommand = new
|
|
9707
|
+
var goalCommand = new Command8("goal").description(
|
|
8759
9708
|
"Manage learning goals (markdown files)"
|
|
8760
9709
|
);
|
|
8761
9710
|
goalCommand.command("list").description("List all goals").option(
|
|
@@ -8763,7 +9712,7 @@ goalCommand.command("list").description("List all goals").option(
|
|
|
8763
9712
|
"Filter by status (active, completed, paused, abandoned)"
|
|
8764
9713
|
).option("--tree", "Show goals as a tree with parent/child relationships").option("--json", "Output as JSON").action(async (opts) => {
|
|
8765
9714
|
const goalsDir = await resolveGoalsDir();
|
|
8766
|
-
if (!
|
|
9715
|
+
if (!existsSync19(goalsDir)) {
|
|
8767
9716
|
console.error(`Goals directory not found: ${goalsDir}`);
|
|
8768
9717
|
console.error(
|
|
8769
9718
|
"Set it with: zam settings set personal.goals_dir /path/to/goals"
|
|
@@ -8864,8 +9813,8 @@ ${"\u2500".repeat(50)}`);
|
|
|
8864
9813
|
});
|
|
8865
9814
|
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
9815
|
const goalsDir = await resolveGoalsDir();
|
|
8867
|
-
if (!
|
|
8868
|
-
|
|
9816
|
+
if (!existsSync19(goalsDir)) {
|
|
9817
|
+
mkdirSync11(goalsDir, { recursive: true });
|
|
8869
9818
|
}
|
|
8870
9819
|
let slug = opts.slug;
|
|
8871
9820
|
let title = opts.title;
|
|
@@ -8924,22 +9873,22 @@ goalCommand.command("status <slug> <status>").description("Update a goal's statu
|
|
|
8924
9873
|
});
|
|
8925
9874
|
|
|
8926
9875
|
// src/cli/commands/init.ts
|
|
8927
|
-
import { existsSync as
|
|
9876
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync12, writeFileSync as writeFileSync10 } from "fs";
|
|
8928
9877
|
import { homedir as homedir11 } from "os";
|
|
8929
|
-
import { join as
|
|
9878
|
+
import { join as join19, resolve as resolve7 } from "path";
|
|
8930
9879
|
import { confirm as confirm2, input as input4 } from "@inquirer/prompts";
|
|
8931
|
-
import { Command as
|
|
9880
|
+
import { Command as Command9 } from "commander";
|
|
8932
9881
|
var HOME2 = homedir11();
|
|
8933
9882
|
function printLine(char = "\u2550", len = 60, color = "\x1B[36m") {
|
|
8934
9883
|
console.log(`${color}${char.repeat(len)}\x1B[0m`);
|
|
8935
9884
|
}
|
|
8936
9885
|
function bootstrapSandboxWorkspace(workspaceDir) {
|
|
8937
|
-
|
|
8938
|
-
|
|
8939
|
-
|
|
8940
|
-
const worldviewFile =
|
|
8941
|
-
if (!
|
|
8942
|
-
|
|
9886
|
+
mkdirSync12(join19(workspaceDir, "beliefs"), { recursive: true });
|
|
9887
|
+
mkdirSync12(join19(workspaceDir, "goals"), { recursive: true });
|
|
9888
|
+
mkdirSync12(join19(workspaceDir, "skills"), { recursive: true });
|
|
9889
|
+
const worldviewFile = join19(workspaceDir, "beliefs", "worldview.md");
|
|
9890
|
+
if (!existsSync20(worldviewFile)) {
|
|
9891
|
+
writeFileSync10(
|
|
8943
9892
|
worldviewFile,
|
|
8944
9893
|
`# Personal Worldview
|
|
8945
9894
|
|
|
@@ -8951,9 +9900,9 @@ Here, I declare the core concepts and principles I want to master.
|
|
|
8951
9900
|
"utf8"
|
|
8952
9901
|
);
|
|
8953
9902
|
}
|
|
8954
|
-
const goalsFile =
|
|
8955
|
-
if (!
|
|
8956
|
-
|
|
9903
|
+
const goalsFile = join19(workspaceDir, "goals", "goals.md");
|
|
9904
|
+
if (!existsSync20(goalsFile)) {
|
|
9905
|
+
writeFileSync10(
|
|
8957
9906
|
goalsFile,
|
|
8958
9907
|
`# Personal Goals
|
|
8959
9908
|
|
|
@@ -8965,7 +9914,7 @@ Here, I declare the core concepts and principles I want to master.
|
|
|
8965
9914
|
);
|
|
8966
9915
|
}
|
|
8967
9916
|
}
|
|
8968
|
-
var initCommand = new
|
|
9917
|
+
var initCommand = new Command9("init").description("Launch the guided interactive onboarding wizard").action(async () => {
|
|
8969
9918
|
printLine();
|
|
8970
9919
|
console.log(
|
|
8971
9920
|
"\x1B[1m\x1B[32m ZAM \u2014 The Symbiotic Learning Agent Onboarding\x1B[0m"
|
|
@@ -8975,13 +9924,22 @@ var initCommand = new Command8("init").description("Launch the guided interactiv
|
|
|
8975
9924
|
);
|
|
8976
9925
|
printLine();
|
|
8977
9926
|
console.log("\n\x1B[1m[1/5] Setting up Local Workspace Sandbox\x1B[0m");
|
|
8978
|
-
const defaultWorkspace =
|
|
8979
|
-
const workspacePath =
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
|
|
9927
|
+
const defaultWorkspace = join19(HOME2, "Documents", "zam");
|
|
9928
|
+
const workspacePath = resolve7(
|
|
9929
|
+
await input4({
|
|
9930
|
+
message: "Choose your ZAM workspace directory:",
|
|
9931
|
+
default: defaultWorkspace
|
|
9932
|
+
})
|
|
9933
|
+
);
|
|
8983
9934
|
try {
|
|
8984
9935
|
bootstrapSandboxWorkspace(workspacePath);
|
|
9936
|
+
wireSkills(workspacePath, parseSetupAgents());
|
|
9937
|
+
upsertConfiguredWorkspace({
|
|
9938
|
+
id: "personal",
|
|
9939
|
+
label: "Personal",
|
|
9940
|
+
kind: "personal",
|
|
9941
|
+
path: workspacePath
|
|
9942
|
+
});
|
|
8985
9943
|
console.log(
|
|
8986
9944
|
`\x1B[32m\u2713 Local Sandbox created at: ${workspacePath}\x1B[0m`
|
|
8987
9945
|
);
|
|
@@ -9132,7 +10090,7 @@ var initCommand = new Command8("init").description("Launch the guided interactiv
|
|
|
9132
10090
|
|
|
9133
10091
|
// src/cli/commands/learn.ts
|
|
9134
10092
|
import { input as input6 } from "@inquirer/prompts";
|
|
9135
|
-
import { Command as
|
|
10093
|
+
import { Command as Command10 } from "commander";
|
|
9136
10094
|
|
|
9137
10095
|
// src/cli/learn-format.ts
|
|
9138
10096
|
var BLOOM_VERBS3 = {
|
|
@@ -9470,7 +10428,7 @@ var STOP_WORDS = /* @__PURE__ */ new Set(["q", ":q", "quit", "stop"]);
|
|
|
9470
10428
|
function isExitPrompt(err) {
|
|
9471
10429
|
return err instanceof Error && err.name === "ExitPromptError";
|
|
9472
10430
|
}
|
|
9473
|
-
var learnCommand = new
|
|
10431
|
+
var learnCommand = new Command10("learn").description(
|
|
9474
10432
|
"Run a spoiler-free, in-process learning session (recall \u2192 reveal \u2192 self-rate)"
|
|
9475
10433
|
).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
10434
|
let db;
|
|
@@ -9714,8 +10672,8 @@ learnCommand.command("open").description("Open a new terminal window running zam
|
|
|
9714
10672
|
});
|
|
9715
10673
|
|
|
9716
10674
|
// src/cli/commands/monitor.ts
|
|
9717
|
-
import { Command as
|
|
9718
|
-
var monitorCommand = new
|
|
10675
|
+
import { Command as Command11 } from "commander";
|
|
10676
|
+
var monitorCommand = new Command11("monitor").description(
|
|
9719
10677
|
"Shell observation for real-time task monitoring"
|
|
9720
10678
|
);
|
|
9721
10679
|
monitorCommand.command("start").description("Output shell hook code to install monitoring").requiredOption("--session <id>", "Session ID to monitor").option(
|
|
@@ -9897,8 +10855,8 @@ monitorCommand.command("open").description("Open a new monitored terminal window
|
|
|
9897
10855
|
});
|
|
9898
10856
|
|
|
9899
10857
|
// src/cli/commands/observer.ts
|
|
9900
|
-
import { Command as
|
|
9901
|
-
var observerCommand = new
|
|
10858
|
+
import { Command as Command12 } from "commander";
|
|
10859
|
+
var observerCommand = new Command12("observer").description(
|
|
9902
10860
|
"Configure what the UI observer may capture (Layer 2 policy)"
|
|
9903
10861
|
);
|
|
9904
10862
|
function applyObserverListChange(current, entry, op) {
|
|
@@ -9961,8 +10919,8 @@ observerCommand.command("revoke <process>").description("Remove a process from o
|
|
|
9961
10919
|
|
|
9962
10920
|
// src/cli/commands/profile.ts
|
|
9963
10921
|
import { homedir as homedir12 } from "os";
|
|
9964
|
-
import { dirname as
|
|
9965
|
-
import { Command as
|
|
10922
|
+
import { dirname as dirname6, join as join20, resolve as resolve8 } from "path";
|
|
10923
|
+
import { Command as Command13 } from "commander";
|
|
9966
10924
|
var C2 = {
|
|
9967
10925
|
reset: "\x1B[0m",
|
|
9968
10926
|
bold: "\x1B[1m",
|
|
@@ -9971,7 +10929,7 @@ var C2 = {
|
|
|
9971
10929
|
green: "\x1B[32m"
|
|
9972
10930
|
};
|
|
9973
10931
|
function defaultPersonalDir() {
|
|
9974
|
-
return
|
|
10932
|
+
return join20(homedir12(), "Documents", "zam");
|
|
9975
10933
|
}
|
|
9976
10934
|
function render(profile) {
|
|
9977
10935
|
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 +10940,7 @@ function render(profile) {
|
|
|
9982
10940
|
console.log(` data dir: ${C2.cyan}${profile.dataDir}${C2.reset}`);
|
|
9983
10941
|
console.log(` database: ${C2.cyan}${profile.dbPath}${C2.reset}`);
|
|
9984
10942
|
}
|
|
9985
|
-
var profileCommand = new
|
|
10943
|
+
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
10944
|
if (opts.mode && opts.mode !== "developer" && opts.mode !== "default") {
|
|
9987
10945
|
console.error(`Invalid --mode: ${opts.mode}. Use developer or default.`);
|
|
9988
10946
|
process.exit(1);
|
|
@@ -9992,7 +10950,7 @@ var profileCommand = new Command12("profile").description("Show or change this m
|
|
|
9992
10950
|
if (opts.mode) setInstallMode(opts.mode);
|
|
9993
10951
|
db = await openDatabaseWithSync({ initialize: true });
|
|
9994
10952
|
if (opts.dir) {
|
|
9995
|
-
await setSetting(db, "personal.workspace_dir",
|
|
10953
|
+
await setSetting(db, "personal.workspace_dir", resolve8(opts.dir));
|
|
9996
10954
|
}
|
|
9997
10955
|
const personalDir = await getSetting(db, "personal.workspace_dir") || defaultPersonalDir();
|
|
9998
10956
|
await db.close();
|
|
@@ -10002,7 +10960,7 @@ var profileCommand = new Command12("profile").description("Show or change this m
|
|
|
10002
10960
|
mode: getInstallMode(),
|
|
10003
10961
|
personalDir,
|
|
10004
10962
|
syncProvider: detectSyncProvider(personalDir),
|
|
10005
|
-
dataDir:
|
|
10963
|
+
dataDir: dirname6(dbPath),
|
|
10006
10964
|
dbPath
|
|
10007
10965
|
};
|
|
10008
10966
|
if (opts.json) {
|
|
@@ -10019,7 +10977,7 @@ var profileCommand = new Command12("profile").description("Show or change this m
|
|
|
10019
10977
|
|
|
10020
10978
|
// src/cli/commands/provider.ts
|
|
10021
10979
|
import { password as password2 } from "@inquirer/prompts";
|
|
10022
|
-
import { Command as
|
|
10980
|
+
import { Command as Command14 } from "commander";
|
|
10023
10981
|
var VALID_API_FLAVORS = [
|
|
10024
10982
|
"chat-completions",
|
|
10025
10983
|
"anthropic-messages"
|
|
@@ -10031,6 +10989,9 @@ function upsertProviderRecord(providers, name, patch) {
|
|
|
10031
10989
|
if (patch.model !== void 0) merged.model = patch.model;
|
|
10032
10990
|
if (patch.apiFlavor !== void 0) merged.apiFlavor = patch.apiFlavor;
|
|
10033
10991
|
if (patch.apiKeyRef !== void 0) merged.apiKeyRef = patch.apiKeyRef;
|
|
10992
|
+
if (patch.label !== void 0) merged.label = patch.label;
|
|
10993
|
+
if (patch.local !== void 0) merged.local = patch.local;
|
|
10994
|
+
if (patch.runner !== void 0) merged.runner = patch.runner;
|
|
10034
10995
|
return { ...providers, [name]: merged };
|
|
10035
10996
|
}
|
|
10036
10997
|
function removeProviderRecord(providers, name) {
|
|
@@ -10059,7 +11020,7 @@ function buildProviderListing(providers, hasKey) {
|
|
|
10059
11020
|
let keyState;
|
|
10060
11021
|
if (!rec.apiKeyRef) keyState = "none";
|
|
10061
11022
|
else keyState = hasKey(rec.apiKeyRef) ? "set" : "missing";
|
|
10062
|
-
|
|
11023
|
+
const row = {
|
|
10063
11024
|
name,
|
|
10064
11025
|
url: rec.url,
|
|
10065
11026
|
model: rec.model,
|
|
@@ -10067,6 +11028,10 @@ function buildProviderListing(providers, hasKey) {
|
|
|
10067
11028
|
apiKeyRef: rec.apiKeyRef,
|
|
10068
11029
|
keyState
|
|
10069
11030
|
};
|
|
11031
|
+
if (rec.label !== void 0) row.label = rec.label;
|
|
11032
|
+
if (rec.local !== void 0) row.local = rec.local;
|
|
11033
|
+
if (rec.runner !== void 0) row.runner = rec.runner;
|
|
11034
|
+
return row;
|
|
10070
11035
|
});
|
|
10071
11036
|
}
|
|
10072
11037
|
function findOrphanKeyRefs(storedRefs, providers) {
|
|
@@ -10086,19 +11051,53 @@ async function readJson(db, key, fallback) {
|
|
|
10086
11051
|
}
|
|
10087
11052
|
var readProviders = (db) => readJson(db, "llm.providers", {});
|
|
10088
11053
|
var readRoles = (db) => readJson(db, "llm.roles", {});
|
|
10089
|
-
async function
|
|
11054
|
+
async function readScopedProviders(db, machine) {
|
|
11055
|
+
if (machine) return getMachineAiConfig().providers ?? {};
|
|
11056
|
+
if (!db)
|
|
11057
|
+
throw new Error("Database is required for shared provider settings.");
|
|
11058
|
+
return readProviders(db);
|
|
11059
|
+
}
|
|
11060
|
+
async function readScopedRoles(db, machine) {
|
|
11061
|
+
if (machine) return getMachineAiConfig().roles ?? {};
|
|
11062
|
+
if (!db)
|
|
11063
|
+
throw new Error("Database is required for shared provider settings.");
|
|
11064
|
+
return readRoles(db);
|
|
11065
|
+
}
|
|
11066
|
+
async function writeScopedProviders(db, machine, p) {
|
|
11067
|
+
if (machine) {
|
|
11068
|
+
const config = getMachineAiConfig();
|
|
11069
|
+
saveMachineAiConfig({ ...config, providers: p });
|
|
11070
|
+
return;
|
|
11071
|
+
}
|
|
11072
|
+
if (!db)
|
|
11073
|
+
throw new Error("Database is required for shared provider settings.");
|
|
10090
11074
|
await setSetting(db, "llm.providers", JSON.stringify(p));
|
|
10091
11075
|
}
|
|
10092
|
-
async function
|
|
11076
|
+
async function writeScopedRoles(db, machine, r) {
|
|
11077
|
+
if (machine) {
|
|
11078
|
+
const config = getMachineAiConfig();
|
|
11079
|
+
saveMachineAiConfig({ ...config, roles: r });
|
|
11080
|
+
return;
|
|
11081
|
+
}
|
|
11082
|
+
if (!db)
|
|
11083
|
+
throw new Error("Database is required for shared provider settings.");
|
|
10093
11084
|
await setSetting(db, "llm.roles", JSON.stringify(r));
|
|
10094
11085
|
}
|
|
10095
|
-
|
|
11086
|
+
async function withProviderScope(machine, action) {
|
|
11087
|
+
if (machine) {
|
|
11088
|
+
await action(void 0);
|
|
11089
|
+
return;
|
|
11090
|
+
}
|
|
11091
|
+
await withDb(action);
|
|
11092
|
+
}
|
|
11093
|
+
var providerCommand = new Command14("provider").description(
|
|
10096
11094
|
"Configure role-based AI providers (url/model/flavor/key, per role)"
|
|
10097
11095
|
);
|
|
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
|
|
11096
|
+
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) => {
|
|
11097
|
+
const machine = Boolean(opts.machine);
|
|
11098
|
+
await withProviderScope(machine, async (db) => {
|
|
11099
|
+
const providers = await readScopedProviders(db, machine);
|
|
11100
|
+
const roles = await readScopedRoles(db, machine);
|
|
10102
11101
|
const rows = buildProviderListing(
|
|
10103
11102
|
providers,
|
|
10104
11103
|
(ref) => getProviderApiKey(ref) !== null
|
|
@@ -10106,11 +11105,23 @@ providerCommand.command("list").description("Show configured providers, role bin
|
|
|
10106
11105
|
const orphans = findOrphanKeyRefs(listProviderApiKeyRefs(), providers);
|
|
10107
11106
|
if (opts.json) {
|
|
10108
11107
|
console.log(
|
|
10109
|
-
JSON.stringify(
|
|
11108
|
+
JSON.stringify(
|
|
11109
|
+
{
|
|
11110
|
+
scope: machine ? "machine" : "shared",
|
|
11111
|
+
providers: rows,
|
|
11112
|
+
roles,
|
|
11113
|
+
orphans
|
|
11114
|
+
},
|
|
11115
|
+
null,
|
|
11116
|
+
2
|
|
11117
|
+
)
|
|
10110
11118
|
);
|
|
10111
11119
|
return;
|
|
10112
11120
|
}
|
|
10113
|
-
console.log(
|
|
11121
|
+
console.log(
|
|
11122
|
+
`Providers (${opts.machine ? "~/.zam/config.json ai.providers" : "llm.providers"}):
|
|
11123
|
+
`
|
|
11124
|
+
);
|
|
10114
11125
|
if (rows.length === 0) {
|
|
10115
11126
|
console.log(
|
|
10116
11127
|
" (none) \u2014 add one: zam provider add <name> --url <url> --model <model>"
|
|
@@ -10122,6 +11133,11 @@ providerCommand.command("list").description("Show configured providers, role bin
|
|
|
10122
11133
|
console.log(` url: ${row.url ?? "(inherits llm.url)"}`);
|
|
10123
11134
|
console.log(` model: ${row.model ?? "(inherits llm.model)"}`);
|
|
10124
11135
|
console.log(` flavor: ${row.apiFlavor}`);
|
|
11136
|
+
if (row.label) console.log(` label: ${row.label}`);
|
|
11137
|
+
if (row.local !== void 0) {
|
|
11138
|
+
console.log(` local: ${row.local ? "yes" : "no"}`);
|
|
11139
|
+
}
|
|
11140
|
+
if (row.runner) console.log(` runner: ${row.runner}`);
|
|
10125
11141
|
if (row.apiKeyRef) console.log(` key-ref: ${row.apiKeyRef}`);
|
|
10126
11142
|
}
|
|
10127
11143
|
}
|
|
@@ -10143,7 +11159,10 @@ providerCommand.command("list").description("Show configured providers, role bin
|
|
|
10143
11159
|
}
|
|
10144
11160
|
});
|
|
10145
11161
|
});
|
|
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(
|
|
11162
|
+
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(
|
|
11163
|
+
"--runner <runner>",
|
|
11164
|
+
"Local runner hint (flm, foundry-local, ollama, ...)"
|
|
11165
|
+
).option(
|
|
10147
11166
|
"--flavor <flavor>",
|
|
10148
11167
|
`Wire protocol: ${VALID_API_FLAVORS.join(" | ")} (default: inferred from URL)`
|
|
10149
11168
|
).option(
|
|
@@ -10152,7 +11171,7 @@ providerCommand.command("add <name>").description("Add or update a provider reco
|
|
|
10152
11171
|
).option(
|
|
10153
11172
|
"--key <value>",
|
|
10154
11173
|
"Store this API key now (prefer `set-key` to keep it out of shell history)"
|
|
10155
|
-
).action(async (name, opts) => {
|
|
11174
|
+
).option("--machine", "Store this provider in ~/.zam/config.json").action(async (name, opts) => {
|
|
10156
11175
|
let apiFlavor;
|
|
10157
11176
|
if (opts.flavor) {
|
|
10158
11177
|
if (!VALID_API_FLAVORS.includes(opts.flavor)) {
|
|
@@ -10164,20 +11183,31 @@ providerCommand.command("add <name>").description("Add or update a provider reco
|
|
|
10164
11183
|
apiFlavor = opts.flavor;
|
|
10165
11184
|
}
|
|
10166
11185
|
const apiKeyRef = opts.keyRef ?? (opts.key ? name : void 0);
|
|
10167
|
-
|
|
10168
|
-
|
|
11186
|
+
const machine = Boolean(opts.machine);
|
|
11187
|
+
await withProviderScope(machine, async (db) => {
|
|
11188
|
+
const providers = await readScopedProviders(db, machine);
|
|
10169
11189
|
const next = upsertProviderRecord(providers, name, {
|
|
11190
|
+
label: opts.label,
|
|
10170
11191
|
url: opts.url,
|
|
10171
11192
|
model: opts.model,
|
|
10172
11193
|
apiFlavor,
|
|
10173
|
-
apiKeyRef
|
|
11194
|
+
apiKeyRef,
|
|
11195
|
+
local: opts.local ? true : void 0,
|
|
11196
|
+
runner: opts.runner
|
|
10174
11197
|
});
|
|
10175
|
-
await
|
|
11198
|
+
await writeScopedProviders(db, machine, next);
|
|
10176
11199
|
if (opts.key && apiKeyRef) setProviderApiKey(apiKeyRef, opts.key);
|
|
10177
11200
|
const rec = next[name];
|
|
10178
|
-
console.log(
|
|
11201
|
+
console.log(
|
|
11202
|
+
`Provider "${name}" saved (${machine ? "machine-local" : "shared"}):`
|
|
11203
|
+
);
|
|
10179
11204
|
console.log(` url: ${rec.url ?? "(inherits llm.url)"}`);
|
|
10180
11205
|
console.log(` model: ${rec.model ?? "(inherits llm.model)"}`);
|
|
11206
|
+
if (rec.label) console.log(` label: ${rec.label}`);
|
|
11207
|
+
if (rec.local !== void 0) {
|
|
11208
|
+
console.log(` local: ${rec.local ? "yes" : "no"}`);
|
|
11209
|
+
}
|
|
11210
|
+
if (rec.runner) console.log(` runner: ${rec.runner}`);
|
|
10181
11211
|
console.log(
|
|
10182
11212
|
` flavor: ${rec.apiFlavor ?? `${rec.url ? inferApiFlavor(rec.url) : "chat-completions"} (inferred)`}`
|
|
10183
11213
|
);
|
|
@@ -10201,9 +11231,10 @@ Bind it to a role: zam provider use recall --primary ${name}`
|
|
|
10201
11231
|
);
|
|
10202
11232
|
});
|
|
10203
11233
|
});
|
|
10204
|
-
providerCommand.command("remove <name>").description("Remove a provider record from llm.providers").action(async (name) => {
|
|
10205
|
-
|
|
10206
|
-
|
|
11234
|
+
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) => {
|
|
11235
|
+
const machine = Boolean(opts.machine);
|
|
11236
|
+
await withProviderScope(machine, async (db) => {
|
|
11237
|
+
const providers = await readScopedProviders(db, machine);
|
|
10207
11238
|
const { providers: next, removed } = removeProviderRecord(
|
|
10208
11239
|
providers,
|
|
10209
11240
|
name
|
|
@@ -10212,9 +11243,14 @@ providerCommand.command("remove <name>").description("Remove a provider record f
|
|
|
10212
11243
|
console.log(`No such provider: ${name}`);
|
|
10213
11244
|
return;
|
|
10214
11245
|
}
|
|
10215
|
-
await
|
|
10216
|
-
console.log(
|
|
10217
|
-
|
|
11246
|
+
await writeScopedProviders(db, machine, next);
|
|
11247
|
+
console.log(
|
|
11248
|
+
`Removed provider "${name}" (${machine ? "machine-local" : "shared"}).`
|
|
11249
|
+
);
|
|
11250
|
+
const referencing = rolesReferencing(
|
|
11251
|
+
await readScopedRoles(db, machine),
|
|
11252
|
+
name
|
|
11253
|
+
);
|
|
10218
11254
|
if (referencing.length > 0) {
|
|
10219
11255
|
console.log(
|
|
10220
11256
|
` \u26A0 Still referenced by role(s): ${referencing.join(", ")} \u2014 rebind with: zam provider use <role> --primary <name>`
|
|
@@ -10222,7 +11258,7 @@ providerCommand.command("remove <name>").description("Remove a provider record f
|
|
|
10222
11258
|
}
|
|
10223
11259
|
});
|
|
10224
11260
|
});
|
|
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) => {
|
|
11261
|
+
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
11262
|
if (!VALID_ROLES.includes(role)) {
|
|
10227
11263
|
console.error(`Invalid role: ${role}. Use ${VALID_ROLES.join(", ")}.`);
|
|
10228
11264
|
process.exit(1);
|
|
@@ -10231,19 +11267,23 @@ providerCommand.command("use <role>").description(`Bind providers to a role (${V
|
|
|
10231
11267
|
console.error("--primary is required.");
|
|
10232
11268
|
process.exit(1);
|
|
10233
11269
|
}
|
|
10234
|
-
|
|
10235
|
-
|
|
10236
|
-
await
|
|
11270
|
+
const machine = Boolean(opts.machine);
|
|
11271
|
+
await withProviderScope(machine, async (db) => {
|
|
11272
|
+
const providers = await readScopedProviders(db, machine);
|
|
11273
|
+
await writeScopedRoles(
|
|
10237
11274
|
db,
|
|
11275
|
+
machine,
|
|
10238
11276
|
bindRoleProviders(
|
|
10239
|
-
await
|
|
11277
|
+
await readScopedRoles(db, machine),
|
|
10240
11278
|
role,
|
|
10241
11279
|
opts.primary,
|
|
10242
11280
|
opts.fallback
|
|
10243
11281
|
)
|
|
10244
11282
|
);
|
|
10245
11283
|
const fb = opts.fallback ? `, fallback: ${opts.fallback}` : "";
|
|
10246
|
-
console.log(
|
|
11284
|
+
console.log(
|
|
11285
|
+
`Role "${role}" \u2192 primary: ${opts.primary}${fb} (${machine ? "machine-local" : "shared"})`
|
|
11286
|
+
);
|
|
10247
11287
|
for (const [label, ref] of [
|
|
10248
11288
|
["primary", opts.primary],
|
|
10249
11289
|
["fallback", opts.fallback]
|
|
@@ -10285,8 +11325,8 @@ providerCommand.command("clear-key <ref>").description("Remove a stored API key"
|
|
|
10285
11325
|
});
|
|
10286
11326
|
|
|
10287
11327
|
// src/cli/commands/review.ts
|
|
10288
|
-
import { Command as
|
|
10289
|
-
var reviewCommand = new
|
|
11328
|
+
import { Command as Command15 } from "commander";
|
|
11329
|
+
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
11330
|
let db;
|
|
10291
11331
|
try {
|
|
10292
11332
|
db = await openDatabase();
|
|
@@ -10399,10 +11439,10 @@ ${"\u2550".repeat(50)}`);
|
|
|
10399
11439
|
});
|
|
10400
11440
|
|
|
10401
11441
|
// src/cli/commands/session.ts
|
|
10402
|
-
import { readFileSync as
|
|
11442
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
10403
11443
|
import { input as input7, select as select2 } from "@inquirer/prompts";
|
|
10404
|
-
import { Command as
|
|
10405
|
-
var sessionCommand = new
|
|
11444
|
+
import { Command as Command16 } from "commander";
|
|
11445
|
+
var sessionCommand = new Command16("session").description(
|
|
10406
11446
|
"Manage learning sessions"
|
|
10407
11447
|
);
|
|
10408
11448
|
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 +11631,7 @@ function loadPatternFile(path) {
|
|
|
10591
11631
|
if (!path) return [];
|
|
10592
11632
|
let parsed;
|
|
10593
11633
|
try {
|
|
10594
|
-
parsed = JSON.parse(
|
|
11634
|
+
parsed = JSON.parse(readFileSync12(path, "utf-8"));
|
|
10595
11635
|
} catch (err) {
|
|
10596
11636
|
throw new Error(
|
|
10597
11637
|
`Cannot read synthesis patterns from ${path}: ${err.message}`
|
|
@@ -10781,9 +11821,9 @@ sessionCommand.command("end").description("End a session and show summary").requ
|
|
|
10781
11821
|
});
|
|
10782
11822
|
|
|
10783
11823
|
// src/cli/commands/settings.ts
|
|
10784
|
-
import { existsSync as
|
|
10785
|
-
import { Command as
|
|
10786
|
-
var settingsCommand = new
|
|
11824
|
+
import { existsSync as existsSync21 } from "fs";
|
|
11825
|
+
import { Command as Command17 } from "commander";
|
|
11826
|
+
var settingsCommand = new Command17("settings").description(
|
|
10787
11827
|
"Manage user settings"
|
|
10788
11828
|
);
|
|
10789
11829
|
var BOOLEAN_SETTING_KEYS = /* @__PURE__ */ new Set(["llm.enabled", "llm.vision.enabled"]);
|
|
@@ -10946,7 +11986,7 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
|
|
|
10946
11986
|
console.log("\nValidation:");
|
|
10947
11987
|
for (const [name, path] of Object.entries(paths)) {
|
|
10948
11988
|
if (path) {
|
|
10949
|
-
const exists =
|
|
11989
|
+
const exists = existsSync21(path);
|
|
10950
11990
|
console.log(
|
|
10951
11991
|
` ${name.padEnd(9)}: ${exists ? "\x1B[32m\u2713 Valid folder\x1B[0m" : "\x1B[31m\u2717 Directory does not exist\x1B[0m"}`
|
|
10952
11992
|
);
|
|
@@ -10957,175 +11997,6 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
|
|
|
10957
11997
|
});
|
|
10958
11998
|
});
|
|
10959
11999
|
|
|
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
12000
|
// src/cli/commands/skill.ts
|
|
11130
12001
|
import { Command as Command18 } from "commander";
|
|
11131
12002
|
var skillCommand = new Command18("skill").description(
|
|
@@ -11213,7 +12084,7 @@ skillCommand.command("add").description("Register a new agent skill").requiredOp
|
|
|
11213
12084
|
});
|
|
11214
12085
|
|
|
11215
12086
|
// src/cli/commands/snapshot.ts
|
|
11216
|
-
import { existsSync as
|
|
12087
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "fs";
|
|
11217
12088
|
import { homedir as homedir13 } from "os";
|
|
11218
12089
|
import { dirname as dirname7, join as join21 } from "path";
|
|
11219
12090
|
import { Command as Command19 } from "commander";
|
|
@@ -11246,8 +12117,8 @@ var exportCmd = new Command19("export").description("Write a portable SQL-text s
|
|
|
11246
12117
|
}
|
|
11247
12118
|
const out = opts.out ?? join21(personalDir, "snapshots", defaultOutName());
|
|
11248
12119
|
const dir = dirname7(out);
|
|
11249
|
-
if (dir && dir !== "." && !
|
|
11250
|
-
|
|
12120
|
+
if (dir && dir !== "." && !existsSync22(dir)) {
|
|
12121
|
+
mkdirSync13(dir, { recursive: true });
|
|
11251
12122
|
}
|
|
11252
12123
|
writeFileSync11(out, snapshot, "utf-8");
|
|
11253
12124
|
console.log(`Snapshot written: ${out}`);
|
|
@@ -11263,11 +12134,11 @@ var exportCmd = new Command19("export").description("Write a portable SQL-text s
|
|
|
11263
12134
|
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
12135
|
let db;
|
|
11265
12136
|
try {
|
|
11266
|
-
if (!
|
|
12137
|
+
if (!existsSync22(file)) {
|
|
11267
12138
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
11268
12139
|
process.exit(1);
|
|
11269
12140
|
}
|
|
11270
|
-
const snapshot =
|
|
12141
|
+
const snapshot = readFileSync13(file, "utf-8");
|
|
11271
12142
|
db = await openDatabaseWithSync({ initialize: true });
|
|
11272
12143
|
const result = await importSnapshot(db, snapshot, { force: opts.force });
|
|
11273
12144
|
await db.close();
|
|
@@ -11285,11 +12156,11 @@ var importCmd = new Command19("import").description("Restore a snapshot into the
|
|
|
11285
12156
|
});
|
|
11286
12157
|
var verifyCmd = new Command19("verify").description("Check a snapshot's manifest and checksum without importing").argument("<file>", "Snapshot file to verify").action((file) => {
|
|
11287
12158
|
try {
|
|
11288
|
-
if (!
|
|
12159
|
+
if (!existsSync22(file)) {
|
|
11289
12160
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
11290
12161
|
process.exit(1);
|
|
11291
12162
|
}
|
|
11292
|
-
const manifest = verifySnapshot(
|
|
12163
|
+
const manifest = verifySnapshot(readFileSync13(file, "utf-8"));
|
|
11293
12164
|
const { total, nonEmpty } = summarize(manifest.tables);
|
|
11294
12165
|
console.log(`Valid snapshot (format v${manifest.version})`);
|
|
11295
12166
|
console.log(` created: ${manifest.createdAt}`);
|
|
@@ -11629,7 +12500,7 @@ tokenCommand.command("status").description("Show full status of a token for a us
|
|
|
11629
12500
|
|
|
11630
12501
|
// src/cli/commands/ui.ts
|
|
11631
12502
|
import { spawn as spawn3, spawnSync } from "child_process";
|
|
11632
|
-
import { existsSync as
|
|
12503
|
+
import { existsSync as existsSync23 } from "fs";
|
|
11633
12504
|
import { homedir as homedir14 } from "os";
|
|
11634
12505
|
import { dirname as dirname8, join as join22 } from "path";
|
|
11635
12506
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -11647,7 +12518,7 @@ function findDesktopDir() {
|
|
|
11647
12518
|
for (const start of starts) {
|
|
11648
12519
|
let dir = start;
|
|
11649
12520
|
for (let i = 0; i < 10; i++) {
|
|
11650
|
-
if (
|
|
12521
|
+
if (existsSync23(join22(dir, "desktop", "src-tauri", "tauri.conf.json"))) {
|
|
11651
12522
|
return join22(dir, "desktop");
|
|
11652
12523
|
}
|
|
11653
12524
|
const parent = dirname8(dir);
|
|
@@ -11662,15 +12533,15 @@ function findBuiltApp(desktopDir) {
|
|
|
11662
12533
|
if (process.platform === "win32") {
|
|
11663
12534
|
for (const name of ["ZAM.exe", "zam.exe", "zam-desktop.exe"]) {
|
|
11664
12535
|
const p = join22(releaseDir, name);
|
|
11665
|
-
if (
|
|
12536
|
+
if (existsSync23(p)) return p;
|
|
11666
12537
|
}
|
|
11667
12538
|
} else if (process.platform === "darwin") {
|
|
11668
12539
|
const app = join22(releaseDir, "bundle", "macos", "ZAM.app");
|
|
11669
|
-
if (
|
|
12540
|
+
if (existsSync23(app)) return app;
|
|
11670
12541
|
} else {
|
|
11671
12542
|
for (const name of ["zam", "ZAM", "zam-desktop"]) {
|
|
11672
12543
|
const p = join22(releaseDir, name);
|
|
11673
|
-
if (
|
|
12544
|
+
if (existsSync23(p)) return p;
|
|
11674
12545
|
}
|
|
11675
12546
|
}
|
|
11676
12547
|
return null;
|
|
@@ -11681,7 +12552,7 @@ function findInstalledApp() {
|
|
|
11681
12552
|
process.env.ProgramFiles && join22(process.env.ProgramFiles, "ZAM", "ZAM.exe"),
|
|
11682
12553
|
process.env["ProgramFiles(x86)"] && join22(process.env["ProgramFiles(x86)"], "ZAM", "ZAM.exe")
|
|
11683
12554
|
] : process.platform === "darwin" ? ["/Applications/ZAM.app", join22(homedir14(), "Applications", "ZAM.app")] : ["/opt/ZAM/zam", "/usr/bin/zam-desktop"];
|
|
11684
|
-
return candidates.find((candidate) => candidate &&
|
|
12555
|
+
return candidates.find((candidate) => candidate && existsSync23(candidate)) || null;
|
|
11685
12556
|
}
|
|
11686
12557
|
function runNpm(args, opts) {
|
|
11687
12558
|
const res = spawnSync("npm", args, {
|
|
@@ -11692,7 +12563,7 @@ function runNpm(args, opts) {
|
|
|
11692
12563
|
return res.status ?? 1;
|
|
11693
12564
|
}
|
|
11694
12565
|
function ensureDesktopDeps(desktopDir) {
|
|
11695
|
-
if (
|
|
12566
|
+
if (existsSync23(join22(desktopDir, "node_modules"))) return true;
|
|
11696
12567
|
console.log(
|
|
11697
12568
|
`${C3.cyan}Installing desktop dependencies (one-time)...${C3.reset}`
|
|
11698
12569
|
);
|
|
@@ -11724,7 +12595,7 @@ function hasMsvcBuildTools() {
|
|
|
11724
12595
|
"Installer",
|
|
11725
12596
|
"vswhere.exe"
|
|
11726
12597
|
);
|
|
11727
|
-
if (!
|
|
12598
|
+
if (!existsSync23(vswhere)) return false;
|
|
11728
12599
|
const res = spawnSync(
|
|
11729
12600
|
vswhere,
|
|
11730
12601
|
[
|
|
@@ -11756,7 +12627,7 @@ function requireMsvcOnWindows() {
|
|
|
11756
12627
|
return false;
|
|
11757
12628
|
}
|
|
11758
12629
|
function warnIfCliMissing(repoRoot) {
|
|
11759
|
-
if (!
|
|
12630
|
+
if (!existsSync23(join22(repoRoot, "dist", "cli", "index.js"))) {
|
|
11760
12631
|
console.warn(
|
|
11761
12632
|
`${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
12633
|
);
|
|
@@ -11917,7 +12788,7 @@ ${C3.dim}After that, 'zam ui' launches it directly.${C3.reset}`
|
|
|
11917
12788
|
|
|
11918
12789
|
// src/cli/commands/update.ts
|
|
11919
12790
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
11920
|
-
import { existsSync as
|
|
12791
|
+
import { existsSync as existsSync24, readFileSync as readFileSync14, realpathSync as realpathSync2 } from "fs";
|
|
11921
12792
|
import { dirname as dirname9, join as join23 } from "path";
|
|
11922
12793
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
11923
12794
|
import { confirm as confirm4 } from "@inquirer/prompts";
|
|
@@ -11943,7 +12814,7 @@ function currentVersion() {
|
|
|
11943
12814
|
for (const up of ["..", "../..", "../../.."]) {
|
|
11944
12815
|
try {
|
|
11945
12816
|
const pkg2 = JSON.parse(
|
|
11946
|
-
|
|
12817
|
+
readFileSync14(join23(here, up, "package.json"), "utf-8")
|
|
11947
12818
|
);
|
|
11948
12819
|
if (pkg2.version) return pkg2.version;
|
|
11949
12820
|
} catch {
|
|
@@ -11954,7 +12825,7 @@ function currentVersion() {
|
|
|
11954
12825
|
function versionAt(dir) {
|
|
11955
12826
|
try {
|
|
11956
12827
|
const pkg2 = JSON.parse(
|
|
11957
|
-
|
|
12828
|
+
readFileSync14(join23(dir, "package.json"), "utf-8")
|
|
11958
12829
|
);
|
|
11959
12830
|
return pkg2.version ?? "unknown";
|
|
11960
12831
|
} catch {
|
|
@@ -12031,14 +12902,14 @@ var checkCmd = new Command23("check").description("Check whether a newer ZAM has
|
|
|
12031
12902
|
}
|
|
12032
12903
|
);
|
|
12033
12904
|
function findSourceRepo() {
|
|
12034
|
-
let dir =
|
|
12905
|
+
let dir = realpathSync2(dirname9(fileURLToPath4(import.meta.url)));
|
|
12035
12906
|
let parent = dirname9(dir);
|
|
12036
12907
|
while (parent !== dir) {
|
|
12037
|
-
if (
|
|
12908
|
+
if (existsSync24(join23(dir, ".git"))) return dir;
|
|
12038
12909
|
dir = parent;
|
|
12039
12910
|
parent = dirname9(dir);
|
|
12040
12911
|
}
|
|
12041
|
-
return
|
|
12912
|
+
return existsSync24(join23(dir, ".git")) ? dir : null;
|
|
12042
12913
|
}
|
|
12043
12914
|
function runGit2(cwd, args, capture) {
|
|
12044
12915
|
const res = spawnSync2("git", args, {
|
|
@@ -12215,7 +13086,7 @@ var whoamiCommand = new Command24("whoami").description("Show or set the default
|
|
|
12215
13086
|
// src/cli/index.ts
|
|
12216
13087
|
var __dirname = dirname10(fileURLToPath5(import.meta.url));
|
|
12217
13088
|
var pkg = JSON.parse(
|
|
12218
|
-
|
|
13089
|
+
readFileSync15(join24(__dirname, "..", "..", "package.json"), "utf-8")
|
|
12219
13090
|
);
|
|
12220
13091
|
var program = new Command25();
|
|
12221
13092
|
program.name("zam").description(
|