zam-core 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1765 -1521
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +10 -5
- package/dist/index.js +35 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { readFileSync as readFileSync15 } from "fs";
|
|
5
|
-
import { dirname as dirname10, join as
|
|
5
|
+
import { dirname as dirname10, join as join25 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
7
7
|
import { Command as Command25 } from "commander";
|
|
8
8
|
|
|
@@ -4478,25 +4478,41 @@ function saveMachineAiConfig(ai, path = defaultConfigPath()) {
|
|
|
4478
4478
|
function getConfiguredWorkspaces(path = defaultConfigPath()) {
|
|
4479
4479
|
return loadInstallConfig(path).workspaces ?? [];
|
|
4480
4480
|
}
|
|
4481
|
-
function
|
|
4481
|
+
function setActiveWorkspaceId(id, path = defaultConfigPath()) {
|
|
4482
4482
|
const config = loadInstallConfig(path);
|
|
4483
|
-
|
|
4483
|
+
if (id) {
|
|
4484
|
+
config.activeWorkspaceId = id;
|
|
4485
|
+
} else {
|
|
4486
|
+
delete config.activeWorkspaceId;
|
|
4487
|
+
}
|
|
4484
4488
|
saveInstallConfig(config, path);
|
|
4485
4489
|
}
|
|
4490
|
+
function getActiveWorkspace(path = defaultConfigPath()) {
|
|
4491
|
+
const config = loadInstallConfig(path);
|
|
4492
|
+
const id = config.activeWorkspaceId;
|
|
4493
|
+
return id ? config.workspaces?.find((workspace) => workspace.id === id) : void 0;
|
|
4494
|
+
}
|
|
4486
4495
|
function upsertConfiguredWorkspace(workspace, path = defaultConfigPath()) {
|
|
4487
|
-
const
|
|
4496
|
+
const config = loadInstallConfig(path);
|
|
4497
|
+
const current = config.workspaces ?? [];
|
|
4488
4498
|
const next = [
|
|
4489
4499
|
...current.filter((candidate) => candidate.id !== workspace.id),
|
|
4490
4500
|
workspace
|
|
4491
4501
|
];
|
|
4492
|
-
|
|
4502
|
+
config.workspaces = next;
|
|
4503
|
+
saveInstallConfig(config, path);
|
|
4493
4504
|
return next;
|
|
4494
4505
|
}
|
|
4495
4506
|
function removeConfiguredWorkspace(id, path = defaultConfigPath()) {
|
|
4496
|
-
const
|
|
4507
|
+
const config = loadInstallConfig(path);
|
|
4508
|
+
const next = (config.workspaces ?? []).filter(
|
|
4497
4509
|
(workspace) => workspace.id !== id
|
|
4498
4510
|
);
|
|
4499
|
-
|
|
4511
|
+
config.workspaces = next;
|
|
4512
|
+
if (config.activeWorkspaceId === id) {
|
|
4513
|
+
config.activeWorkspaceId = next[0]?.id;
|
|
4514
|
+
}
|
|
4515
|
+
saveInstallConfig(config, path);
|
|
4500
4516
|
return next;
|
|
4501
4517
|
}
|
|
4502
4518
|
function detectSyncProvider(dir) {
|
|
@@ -4828,7 +4844,7 @@ function getSystemProfile() {
|
|
|
4828
4844
|
import { existsSync as existsSync10 } from "fs";
|
|
4829
4845
|
import { resolve as resolve2 } from "path";
|
|
4830
4846
|
async function getRepoPaths(db) {
|
|
4831
|
-
const personalSetting = await getSetting(db, "repo.personal") ||
|
|
4847
|
+
const personalSetting = await getSetting(db, "repo.personal") || getActiveWorkspace()?.path;
|
|
4832
4848
|
const teamSetting = await getSetting(db, "repo.team");
|
|
4833
4849
|
const orgSetting = await getSetting(db, "repo.org");
|
|
4834
4850
|
return {
|
|
@@ -5372,18 +5388,12 @@ var openCmd = new Command("open").description("Open an agent harness in the work
|
|
|
5372
5388
|
var agentCommand = new Command("agent").description("Provision and inspect the agent that drives ZAM sessions").addCommand(installCmd).addCommand(statusCmd).addCommand(openCmd).addCommand(listCmd).action(printStatus);
|
|
5373
5389
|
|
|
5374
5390
|
// src/cli/commands/bridge.ts
|
|
5375
|
-
import { execFileSync as
|
|
5391
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
5376
5392
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
5377
|
-
import {
|
|
5378
|
-
existsSync as existsSync17,
|
|
5379
|
-
mkdirSync as mkdirSync10,
|
|
5380
|
-
readdirSync as readdirSync3,
|
|
5381
|
-
readFileSync as readFileSync11,
|
|
5382
|
-
rmSync as rmSync3
|
|
5383
|
-
} from "fs";
|
|
5393
|
+
import { existsSync as existsSync17, readdirSync as readdirSync2, readFileSync as readFileSync11, rmSync as rmSync3 } from "fs";
|
|
5384
5394
|
import { homedir as homedir10, tmpdir as tmpdir3 } from "os";
|
|
5385
|
-
import {
|
|
5386
|
-
import { Command as
|
|
5395
|
+
import { join as join18, resolve as resolve4 } from "path";
|
|
5396
|
+
import { Command as Command2 } from "commander";
|
|
5387
5397
|
|
|
5388
5398
|
// src/cli/llm/client.ts
|
|
5389
5399
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -5419,19 +5429,25 @@ async function getLlmConfig(db) {
|
|
|
5419
5429
|
function getCloudModelRecommendation(url) {
|
|
5420
5430
|
const lowercase = url.toLowerCase();
|
|
5421
5431
|
if (lowercase.includes("openrouter.ai")) {
|
|
5422
|
-
return "openrouter/free";
|
|
5432
|
+
return { model: "openrouter/free", flavor: "chat-completions" };
|
|
5423
5433
|
}
|
|
5424
5434
|
if (lowercase.includes("openai.com") || lowercase.includes("api.openai")) {
|
|
5425
|
-
return "gpt-5-mini";
|
|
5435
|
+
return { model: "gpt-5-mini", flavor: "chat-completions" };
|
|
5426
5436
|
}
|
|
5427
5437
|
if (lowercase.includes("googleapis.com") || lowercase.includes("google")) {
|
|
5428
|
-
return "gemini-3.5-flash";
|
|
5438
|
+
return { model: "gemini-3.5-flash", flavor: "chat-completions" };
|
|
5429
5439
|
}
|
|
5430
5440
|
if (lowercase.includes("deepseek.com")) {
|
|
5431
|
-
return "deepseek-v4-flash";
|
|
5441
|
+
return { model: "deepseek-v4-flash", flavor: "chat-completions" };
|
|
5432
5442
|
}
|
|
5433
5443
|
if (lowercase.includes("mimo")) {
|
|
5434
|
-
return "mimo-v2.5";
|
|
5444
|
+
return { model: "mimo-v2.5", flavor: "chat-completions" };
|
|
5445
|
+
}
|
|
5446
|
+
if (lowercase.includes("anthropic.com")) {
|
|
5447
|
+
return {
|
|
5448
|
+
model: "claude-haiku-4-5-20251001",
|
|
5449
|
+
flavor: "anthropic-messages"
|
|
5450
|
+
};
|
|
5435
5451
|
}
|
|
5436
5452
|
return null;
|
|
5437
5453
|
}
|
|
@@ -5525,14 +5541,15 @@ async function getLegacyRoleConfig(db, role, enabled) {
|
|
|
5525
5541
|
const maxFramesStr = await getSetting(db, "llm.vision.max_frames");
|
|
5526
5542
|
const parsed = maxFramesStr ? parseInt(maxFramesStr, 10) : 100;
|
|
5527
5543
|
const url = await getSetting(db, "llm.vision.url") || base.url;
|
|
5544
|
+
const recommendation = getCloudModelRecommendation(url);
|
|
5528
5545
|
let model = await getSetting(db, "llm.vision.model");
|
|
5529
|
-
if (!model) model =
|
|
5546
|
+
if (!model) model = recommendation?.model || base.model;
|
|
5530
5547
|
return {
|
|
5531
5548
|
enabled,
|
|
5532
5549
|
url,
|
|
5533
5550
|
model,
|
|
5534
5551
|
apiKey: await getSetting(db, "llm.vision.api_key") || base.apiKey,
|
|
5535
|
-
apiFlavor: inferApiFlavor(url),
|
|
5552
|
+
apiFlavor: recommendation?.flavor || inferApiFlavor(url),
|
|
5536
5553
|
locale: base.locale,
|
|
5537
5554
|
source: "legacy",
|
|
5538
5555
|
local: isLocalEndpoint(url),
|
|
@@ -5860,7 +5877,7 @@ async function prepareRecallChain(db, opts) {
|
|
|
5860
5877
|
} else {
|
|
5861
5878
|
spawnLocalRunner(endpoint.url, endpoint.model, endpoint.runner);
|
|
5862
5879
|
while (Date.now() < deadline) {
|
|
5863
|
-
await new Promise((
|
|
5880
|
+
await new Promise((resolve10) => setTimeout(resolve10, 1e3));
|
|
5864
5881
|
if (await isLlmOnline(endpoint.url)) {
|
|
5865
5882
|
online = true;
|
|
5866
5883
|
break;
|
|
@@ -5934,7 +5951,7 @@ async function checkVisionReadiness(db) {
|
|
|
5934
5951
|
let warning;
|
|
5935
5952
|
if (cfg.enabled && online && modelAvailable && !visionModelExplicit) {
|
|
5936
5953
|
const cloudRec = getCloudModelRecommendation(active.url);
|
|
5937
|
-
if (cloudRec && active.model === cloudRec) {
|
|
5954
|
+
if (cloudRec && active.model === cloudRec.model) {
|
|
5938
5955
|
} else {
|
|
5939
5956
|
warning = `No explicit vision model configured (llm.vision.model). Falling back to base model "${active.model}", which may not support image input. Set a multimodal model: zam settings set llm.vision.model <model>`;
|
|
5940
5957
|
}
|
|
@@ -6153,7 +6170,7 @@ async function startLocalRunner(url, model, locale, hint) {
|
|
|
6153
6170
|
let attempts = 0;
|
|
6154
6171
|
const dotsPerLine = 30;
|
|
6155
6172
|
while (true) {
|
|
6156
|
-
await new Promise((
|
|
6173
|
+
await new Promise((resolve10) => setTimeout(resolve10, 500));
|
|
6157
6174
|
if (await isLlmOnline(url)) {
|
|
6158
6175
|
if (attempts > 0) process.stdout.write("\n");
|
|
6159
6176
|
return true;
|
|
@@ -6242,8 +6259,8 @@ async function fetchWithInteractiveTimeout(url, options = {}) {
|
|
|
6242
6259
|
const dotsPerLine = 30;
|
|
6243
6260
|
while (true) {
|
|
6244
6261
|
let timeoutId;
|
|
6245
|
-
const timeoutPromise = new Promise((
|
|
6246
|
-
timeoutId = setTimeout(() =>
|
|
6262
|
+
const timeoutPromise = new Promise((resolve10) => {
|
|
6263
|
+
timeoutId = setTimeout(() => resolve10("timeout"), timeoutMs);
|
|
6247
6264
|
});
|
|
6248
6265
|
const dotsInterval = setInterval(() => {
|
|
6249
6266
|
process.stdout.write(".");
|
|
@@ -6360,7 +6377,7 @@ async function observeUiSnapshotViaLLM(db, input8) {
|
|
|
6360
6377
|
const imageUrls = [];
|
|
6361
6378
|
const isVideo = /\.(mp4|mov|m4v|avi|mkv|webm)$/i.test(input8.imagePath);
|
|
6362
6379
|
if (isVideo) {
|
|
6363
|
-
const { mkdirSync: mkdirSync14, readdirSync:
|
|
6380
|
+
const { mkdirSync: mkdirSync14, readdirSync: readdirSync3, rmSync: rmSync4 } = await import("fs");
|
|
6364
6381
|
const { execSync: execSync6 } = await import("child_process");
|
|
6365
6382
|
const tempDir = join14(
|
|
6366
6383
|
tmpdir2(),
|
|
@@ -6372,13 +6389,13 @@ async function observeUiSnapshotViaLLM(db, input8) {
|
|
|
6372
6389
|
`ffmpeg -i "${input8.imagePath}" -vf "fps=1/3,scale=1280:-1" -vsync vfr "${tempDir}/frame_%03d.png"`,
|
|
6373
6390
|
{ stdio: "ignore" }
|
|
6374
6391
|
);
|
|
6375
|
-
let files =
|
|
6392
|
+
let files = readdirSync3(tempDir).filter((f) => f.endsWith(".png")).sort();
|
|
6376
6393
|
if (files.length === 0) {
|
|
6377
6394
|
execSync6(
|
|
6378
6395
|
`ffmpeg -i "${input8.imagePath}" -vframes 1 "${tempDir}/frame_001.png"`,
|
|
6379
6396
|
{ stdio: "ignore" }
|
|
6380
6397
|
);
|
|
6381
|
-
files =
|
|
6398
|
+
files = readdirSync3(tempDir).filter((f) => f.endsWith(".png")).sort();
|
|
6382
6399
|
}
|
|
6383
6400
|
const maxFrames = cfg.maxFrames ?? 100;
|
|
6384
6401
|
let sampledFiles = files;
|
|
@@ -6734,10 +6751,6 @@ function isRecord2(value) {
|
|
|
6734
6751
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6735
6752
|
}
|
|
6736
6753
|
|
|
6737
|
-
// src/cli/commands/provider.ts
|
|
6738
|
-
import { password } from "@inquirer/prompts";
|
|
6739
|
-
import { Command as Command2 } from "commander";
|
|
6740
|
-
|
|
6741
6754
|
// src/cli/commands/shared/db.ts
|
|
6742
6755
|
function defaultErrorHandler(message) {
|
|
6743
6756
|
console.error("Error:", message);
|
|
@@ -6758,7 +6771,7 @@ function jsonOut(data) {
|
|
|
6758
6771
|
console.log(JSON.stringify(data, null, 2));
|
|
6759
6772
|
}
|
|
6760
6773
|
|
|
6761
|
-
// src/cli/
|
|
6774
|
+
// src/cli/providers/config.ts
|
|
6762
6775
|
var VALID_API_FLAVORS = [
|
|
6763
6776
|
"chat-completions",
|
|
6764
6777
|
"anthropic-messages"
|
|
@@ -6871,1034 +6884,528 @@ async function withProviderScope(machine, action) {
|
|
|
6871
6884
|
}
|
|
6872
6885
|
await withDb(action);
|
|
6873
6886
|
}
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6887
|
+
|
|
6888
|
+
// src/cli/provisioning/index.ts
|
|
6889
|
+
import {
|
|
6890
|
+
existsSync as existsSync15,
|
|
6891
|
+
lstatSync,
|
|
6892
|
+
mkdirSync as mkdirSync8,
|
|
6893
|
+
readFileSync as readFileSync10,
|
|
6894
|
+
realpathSync,
|
|
6895
|
+
rmSync as rmSync2,
|
|
6896
|
+
symlinkSync,
|
|
6897
|
+
writeFileSync as writeFileSync7
|
|
6898
|
+
} from "fs";
|
|
6899
|
+
import { basename as basename4, dirname as dirname5, join as join15 } from "path";
|
|
6900
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6901
|
+
var packageRoot = [
|
|
6902
|
+
fileURLToPath2(new URL("../..", import.meta.url)),
|
|
6903
|
+
fileURLToPath2(new URL("../../..", import.meta.url))
|
|
6904
|
+
].find((candidate) => existsSync15(join15(candidate, "package.json"))) ?? fileURLToPath2(new URL("../..", import.meta.url));
|
|
6905
|
+
var ALL_SETUP_AGENTS = ["claude", "copilot", "codex", "agent"];
|
|
6906
|
+
var SKILL_PAIRS = [
|
|
6907
|
+
{
|
|
6908
|
+
from: join15(packageRoot, ".claude", "skills", "zam", "SKILL.md"),
|
|
6909
|
+
to: join15(".claude", "skills", "zam", "SKILL.md"),
|
|
6910
|
+
agents: ["claude", "copilot"]
|
|
6911
|
+
},
|
|
6912
|
+
{
|
|
6913
|
+
from: join15(packageRoot, ".agent", "skills", "zam", "SKILL.md"),
|
|
6914
|
+
to: join15(".agent", "skills", "zam", "SKILL.md"),
|
|
6915
|
+
agents: ["agent"]
|
|
6916
|
+
},
|
|
6917
|
+
{
|
|
6918
|
+
from: join15(packageRoot, ".agents", "skills", "zam", "SKILL.md"),
|
|
6919
|
+
to: join15(".agents", "skills", "zam", "SKILL.md"),
|
|
6920
|
+
agents: ["codex"]
|
|
6921
|
+
}
|
|
6922
|
+
];
|
|
6923
|
+
function parseSetupAgents(value) {
|
|
6924
|
+
if (!value || value.trim().toLowerCase() === "all") {
|
|
6925
|
+
return new Set(ALL_SETUP_AGENTS);
|
|
6926
|
+
}
|
|
6927
|
+
const aliases = {
|
|
6928
|
+
claude: ["claude"],
|
|
6929
|
+
copilot: ["copilot"],
|
|
6930
|
+
codex: ["codex"],
|
|
6931
|
+
agent: ["agent"],
|
|
6932
|
+
opencode: ["agent"]
|
|
6933
|
+
};
|
|
6934
|
+
const selected = /* @__PURE__ */ new Set();
|
|
6935
|
+
for (const raw of value.split(",")) {
|
|
6936
|
+
const key = raw.trim().toLowerCase();
|
|
6937
|
+
const mapped = aliases[key];
|
|
6938
|
+
if (!mapped) {
|
|
6939
|
+
throw new Error(
|
|
6940
|
+
`Unknown agent "${raw}". Use: all, claude, copilot, codex, agent.`
|
|
6899
6941
|
);
|
|
6900
|
-
return;
|
|
6901
6942
|
}
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6943
|
+
for (const item of mapped) selected.add(item);
|
|
6944
|
+
}
|
|
6945
|
+
return selected;
|
|
6946
|
+
}
|
|
6947
|
+
function pathExists(path) {
|
|
6948
|
+
try {
|
|
6949
|
+
lstatSync(path);
|
|
6950
|
+
return true;
|
|
6951
|
+
} catch {
|
|
6952
|
+
return false;
|
|
6953
|
+
}
|
|
6954
|
+
}
|
|
6955
|
+
function isSymbolicLink(path) {
|
|
6956
|
+
try {
|
|
6957
|
+
return lstatSync(path).isSymbolicLink();
|
|
6958
|
+
} catch {
|
|
6959
|
+
return false;
|
|
6960
|
+
}
|
|
6961
|
+
}
|
|
6962
|
+
function comparableRealPath(path) {
|
|
6963
|
+
const real = realpathSync(path);
|
|
6964
|
+
return process.platform === "win32" ? real.toLowerCase() : real;
|
|
6965
|
+
}
|
|
6966
|
+
function pathsResolveToSameDirectory(sourceDir, destinationDir) {
|
|
6967
|
+
try {
|
|
6968
|
+
return comparableRealPath(destinationDir) === comparableRealPath(sourceDir);
|
|
6969
|
+
} catch {
|
|
6970
|
+
return false;
|
|
6971
|
+
}
|
|
6972
|
+
}
|
|
6973
|
+
function linkPointsTo(sourceDir, destinationDir) {
|
|
6974
|
+
return isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir);
|
|
6975
|
+
}
|
|
6976
|
+
function isZamSkillCopy(destinationDir) {
|
|
6977
|
+
try {
|
|
6978
|
+
if (!lstatSync(destinationDir).isDirectory()) return false;
|
|
6979
|
+
const skillFile = join15(destinationDir, "SKILL.md");
|
|
6980
|
+
if (!existsSync15(skillFile)) return false;
|
|
6981
|
+
return /^name:\s*zam\s*$/m.test(readFileSync10(skillFile, "utf8"));
|
|
6982
|
+
} catch {
|
|
6983
|
+
return false;
|
|
6984
|
+
}
|
|
6985
|
+
}
|
|
6986
|
+
function classifySkillDestination(sourceDir, destinationDir) {
|
|
6987
|
+
if (!existsSync15(sourceDir)) return "source-missing";
|
|
6988
|
+
if (!isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir)) {
|
|
6989
|
+
return "source-directory";
|
|
6990
|
+
}
|
|
6991
|
+
if (linkPointsTo(sourceDir, destinationDir)) return "linked";
|
|
6992
|
+
if (!pathExists(destinationDir)) return "missing";
|
|
6993
|
+
if (isSymbolicLink(destinationDir)) return "broken";
|
|
6994
|
+
if (isZamSkillCopy(destinationDir)) return "stale-copy";
|
|
6995
|
+
return "unmanaged";
|
|
6996
|
+
}
|
|
6997
|
+
function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {}) {
|
|
6998
|
+
const results = [];
|
|
6999
|
+
const log = (message) => {
|
|
7000
|
+
if (!opts.quiet) console.log(message);
|
|
7001
|
+
};
|
|
7002
|
+
for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
|
|
7003
|
+
if (!pairAgents.some((agent) => agents.has(agent))) continue;
|
|
7004
|
+
const sourceDir = dirname5(from);
|
|
7005
|
+
const destinationDir = dirname5(join15(cwd, to));
|
|
7006
|
+
const label = dirname5(to);
|
|
7007
|
+
const state = classifySkillDestination(sourceDir, destinationDir);
|
|
7008
|
+
if (state === "source-missing") {
|
|
7009
|
+
if (!opts.quiet) {
|
|
7010
|
+
console.warn(` warn source not found, skipping: ${sourceDir}`);
|
|
6923
7011
|
}
|
|
7012
|
+
results.push({
|
|
7013
|
+
source: sourceDir,
|
|
7014
|
+
destination: destinationDir,
|
|
7015
|
+
action: "skipped",
|
|
7016
|
+
reason: "source-not-found"
|
|
7017
|
+
});
|
|
7018
|
+
continue;
|
|
6924
7019
|
}
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
7020
|
+
if (state === "source-directory") {
|
|
7021
|
+
log(` skip ${label} (package source)`);
|
|
7022
|
+
results.push({
|
|
7023
|
+
source: sourceDir,
|
|
7024
|
+
destination: destinationDir,
|
|
7025
|
+
action: "skipped",
|
|
7026
|
+
reason: "source-directory"
|
|
7027
|
+
});
|
|
7028
|
+
continue;
|
|
6934
7029
|
}
|
|
6935
|
-
if (
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
7030
|
+
if (state === "linked") {
|
|
7031
|
+
log(` skip ${label} (already linked)`);
|
|
7032
|
+
results.push({
|
|
7033
|
+
source: sourceDir,
|
|
7034
|
+
destination: destinationDir,
|
|
7035
|
+
action: "skipped",
|
|
7036
|
+
reason: "already-linked"
|
|
7037
|
+
});
|
|
7038
|
+
continue;
|
|
6940
7039
|
}
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
if (
|
|
6959
|
-
|
|
6960
|
-
|
|
7040
|
+
const destinationExists = state !== "missing";
|
|
7041
|
+
const replaceExisting = destinationExists && (Boolean(opts.force) || state === "broken" || state === "stale-copy");
|
|
7042
|
+
if (destinationExists && !replaceExisting) {
|
|
7043
|
+
if (!opts.quiet) {
|
|
7044
|
+
console.warn(
|
|
7045
|
+
` warn ${label} already exists and is not managed by ZAM; use --force to replace it`
|
|
7046
|
+
);
|
|
7047
|
+
}
|
|
7048
|
+
results.push({
|
|
7049
|
+
source: sourceDir,
|
|
7050
|
+
destination: destinationDir,
|
|
7051
|
+
action: "skipped",
|
|
7052
|
+
reason: "unmanaged-destination"
|
|
7053
|
+
});
|
|
7054
|
+
continue;
|
|
7055
|
+
}
|
|
7056
|
+
const action = destinationExists ? "relinked" : "linked";
|
|
7057
|
+
if (opts.dryRun) {
|
|
7058
|
+
log(` would ${action === "linked" ? "link" : "relink"} ${label}`);
|
|
7059
|
+
} else {
|
|
7060
|
+
if (destinationExists) {
|
|
7061
|
+
rmSync2(destinationDir, { recursive: true, force: true });
|
|
7062
|
+
}
|
|
7063
|
+
mkdirSync8(dirname5(destinationDir), { recursive: true });
|
|
7064
|
+
symlinkSync(
|
|
7065
|
+
sourceDir,
|
|
7066
|
+
destinationDir,
|
|
7067
|
+
process.platform === "win32" ? "junction" : "dir"
|
|
6961
7068
|
);
|
|
6962
|
-
|
|
7069
|
+
log(` ${action === "linked" ? "link" : "relink"} ${label}`);
|
|
6963
7070
|
}
|
|
6964
|
-
|
|
7071
|
+
results.push({ source: sourceDir, destination: destinationDir, action });
|
|
6965
7072
|
}
|
|
6966
|
-
|
|
6967
|
-
const machine = Boolean(opts.machine);
|
|
6968
|
-
await withProviderScope(machine, async (db) => {
|
|
6969
|
-
const providers = await readScopedProviders(db, machine);
|
|
6970
|
-
const next = upsertProviderRecord(providers, name, {
|
|
6971
|
-
label: opts.label,
|
|
6972
|
-
url: opts.url,
|
|
6973
|
-
model: opts.model,
|
|
6974
|
-
apiFlavor,
|
|
6975
|
-
apiKeyRef,
|
|
6976
|
-
local: opts.local ? true : void 0,
|
|
6977
|
-
runner: opts.runner
|
|
6978
|
-
});
|
|
6979
|
-
await writeScopedProviders(db, machine, next);
|
|
6980
|
-
if (opts.key && apiKeyRef) setProviderApiKey(apiKeyRef, opts.key);
|
|
6981
|
-
const rec = next[name];
|
|
6982
|
-
console.log(
|
|
6983
|
-
`Provider "${name}" saved (${machine ? "machine-local" : "shared"}):`
|
|
6984
|
-
);
|
|
6985
|
-
console.log(` url: ${rec.url ?? "(inherits llm.url)"}`);
|
|
6986
|
-
console.log(` model: ${rec.model ?? "(inherits llm.model)"}`);
|
|
6987
|
-
if (rec.label) console.log(` label: ${rec.label}`);
|
|
6988
|
-
if (rec.local !== void 0) {
|
|
6989
|
-
console.log(` local: ${rec.local ? "yes" : "no"}`);
|
|
6990
|
-
}
|
|
6991
|
-
if (rec.runner) console.log(` runner: ${rec.runner}`);
|
|
6992
|
-
console.log(
|
|
6993
|
-
` flavor: ${rec.apiFlavor ?? `${rec.url ? inferApiFlavor(rec.url) : "chat-completions"} (inferred)`}`
|
|
6994
|
-
);
|
|
6995
|
-
console.log(` key-ref: ${rec.apiKeyRef ?? "(none \u2014 uses default key)"}`);
|
|
6996
|
-
if (opts.key) {
|
|
6997
|
-
console.log(` key: stored (${maskSecret(opts.key)})`);
|
|
6998
|
-
} else if (rec.apiKeyRef && getProviderApiKey(rec.apiKeyRef) === null) {
|
|
6999
|
-
console.log(
|
|
7000
|
-
`
|
|
7001
|
-
\u26A0 No key stored for "${rec.apiKeyRef}". Run: zam provider set-key ${rec.apiKeyRef}`
|
|
7002
|
-
);
|
|
7003
|
-
}
|
|
7004
|
-
if (!rec.url) {
|
|
7005
|
-
console.log(
|
|
7006
|
-
"\n \u26A0 No --url set; this provider inherits the base llm.url."
|
|
7007
|
-
);
|
|
7008
|
-
}
|
|
7009
|
-
console.log(
|
|
7010
|
-
`
|
|
7011
|
-
Bind it to a role: zam provider use recall --primary ${name}`
|
|
7012
|
-
);
|
|
7013
|
-
});
|
|
7014
|
-
});
|
|
7015
|
-
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) => {
|
|
7016
|
-
const machine = Boolean(opts.machine);
|
|
7017
|
-
await withProviderScope(machine, async (db) => {
|
|
7018
|
-
const providers = await readScopedProviders(db, machine);
|
|
7019
|
-
const { providers: next, removed } = removeProviderRecord(
|
|
7020
|
-
providers,
|
|
7021
|
-
name
|
|
7022
|
-
);
|
|
7023
|
-
if (!removed) {
|
|
7024
|
-
console.log(`No such provider: ${name}`);
|
|
7025
|
-
return;
|
|
7026
|
-
}
|
|
7027
|
-
await writeScopedProviders(db, machine, next);
|
|
7028
|
-
console.log(
|
|
7029
|
-
`Removed provider "${name}" (${machine ? "machine-local" : "shared"}).`
|
|
7030
|
-
);
|
|
7031
|
-
const referencing = rolesReferencing(
|
|
7032
|
-
await readScopedRoles(db, machine),
|
|
7033
|
-
name
|
|
7034
|
-
);
|
|
7035
|
-
if (referencing.length > 0) {
|
|
7036
|
-
console.log(
|
|
7037
|
-
` \u26A0 Still referenced by role(s): ${referencing.join(", ")} \u2014 rebind with: zam provider use <role> --primary <name>`
|
|
7038
|
-
);
|
|
7039
|
-
}
|
|
7040
|
-
});
|
|
7041
|
-
});
|
|
7042
|
-
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) => {
|
|
7043
|
-
if (!VALID_ROLES.includes(role)) {
|
|
7044
|
-
console.error(`Invalid role: ${role}. Use ${VALID_ROLES.join(", ")}.`);
|
|
7045
|
-
process.exit(1);
|
|
7046
|
-
}
|
|
7047
|
-
if (!opts.primary) {
|
|
7048
|
-
console.error("--primary is required.");
|
|
7049
|
-
process.exit(1);
|
|
7050
|
-
}
|
|
7051
|
-
const machine = Boolean(opts.machine);
|
|
7052
|
-
await withProviderScope(machine, async (db) => {
|
|
7053
|
-
const providers = await readScopedProviders(db, machine);
|
|
7054
|
-
await writeScopedRoles(
|
|
7055
|
-
db,
|
|
7056
|
-
machine,
|
|
7057
|
-
bindRoleProviders(
|
|
7058
|
-
await readScopedRoles(db, machine),
|
|
7059
|
-
role,
|
|
7060
|
-
opts.primary,
|
|
7061
|
-
opts.fallback
|
|
7062
|
-
)
|
|
7063
|
-
);
|
|
7064
|
-
const fb = opts.fallback ? `, fallback: ${opts.fallback}` : "";
|
|
7065
|
-
console.log(
|
|
7066
|
-
`Role "${role}" \u2192 primary: ${opts.primary}${fb} (${machine ? "machine-local" : "shared"})`
|
|
7067
|
-
);
|
|
7068
|
-
for (const [label, ref] of [
|
|
7069
|
-
["primary", opts.primary],
|
|
7070
|
-
["fallback", opts.fallback]
|
|
7071
|
-
]) {
|
|
7072
|
-
if (ref && !(ref in providers)) {
|
|
7073
|
-
console.log(
|
|
7074
|
-
` \u26A0 ${label} "${ref}" is not a defined provider yet. Add it: zam provider add ${ref} --url <url> --model <model>`
|
|
7075
|
-
);
|
|
7076
|
-
}
|
|
7077
|
-
}
|
|
7078
|
-
});
|
|
7079
|
-
});
|
|
7080
|
-
providerCommand.command("set-key <ref>").description(
|
|
7081
|
-
"Store an API key for a provider reference (in credentials.json)"
|
|
7082
|
-
).option(
|
|
7083
|
-
"--key <value>",
|
|
7084
|
-
"The API key (omit to enter it interactively, hidden)"
|
|
7085
|
-
).action(async (ref, opts) => {
|
|
7086
|
-
try {
|
|
7087
|
-
const key = opts.key ?? await password({ message: `API key for "${ref}":` });
|
|
7088
|
-
if (!key || key.trim().length === 0) {
|
|
7089
|
-
console.error("No key provided.");
|
|
7090
|
-
process.exit(1);
|
|
7091
|
-
}
|
|
7092
|
-
setProviderApiKey(ref, key.trim());
|
|
7093
|
-
console.log(`Stored API key for "${ref}" (${maskSecret(key.trim())}).`);
|
|
7094
|
-
} catch (err) {
|
|
7095
|
-
if (err.name === "ExitPromptError") {
|
|
7096
|
-
console.log("\nCancelled.");
|
|
7097
|
-
process.exit(0);
|
|
7098
|
-
}
|
|
7099
|
-
console.error("Error:", err.message);
|
|
7100
|
-
process.exit(1);
|
|
7101
|
-
}
|
|
7102
|
-
});
|
|
7103
|
-
providerCommand.command("clear-key <ref>").description("Remove a stored API key").action((ref) => {
|
|
7104
|
-
clearProviderApiKey(ref);
|
|
7105
|
-
console.log(`Cleared API key for "${ref}".`);
|
|
7106
|
-
});
|
|
7107
|
-
|
|
7108
|
-
// src/cli/commands/resolve-user.ts
|
|
7109
|
-
async function ensureDefaultUser(db, preferredUserId) {
|
|
7110
|
-
const stored = await getSetting(db, "user.id");
|
|
7111
|
-
if (stored) return stored;
|
|
7112
|
-
const userId = preferredUserId?.trim() || process.env.ZAM_USER?.trim() || process.env.USERNAME?.trim() || process.env.USER?.trim() || "default";
|
|
7113
|
-
await setSetting(db, "user.id", userId);
|
|
7114
|
-
return userId;
|
|
7115
|
-
}
|
|
7116
|
-
async function resolveUser(opts, db, resolveOpts) {
|
|
7117
|
-
if (opts.user) return opts.user;
|
|
7118
|
-
const stored = await getSetting(db, "user.id");
|
|
7119
|
-
if (stored) return stored;
|
|
7120
|
-
const message = "No user specified. Set a default with: zam whoami --set <id>";
|
|
7121
|
-
if (resolveOpts?.json) {
|
|
7122
|
-
console.log(JSON.stringify({ error: message }, null, 2));
|
|
7123
|
-
} else {
|
|
7124
|
-
console.error(message);
|
|
7125
|
-
}
|
|
7126
|
-
process.exit(1);
|
|
7127
|
-
}
|
|
7128
|
-
|
|
7129
|
-
// src/cli/commands/setup.ts
|
|
7130
|
-
import {
|
|
7131
|
-
existsSync as existsSync15,
|
|
7132
|
-
lstatSync,
|
|
7133
|
-
mkdirSync as mkdirSync8,
|
|
7134
|
-
readdirSync as readdirSync2,
|
|
7135
|
-
readFileSync as readFileSync10,
|
|
7136
|
-
realpathSync,
|
|
7137
|
-
rmSync as rmSync2,
|
|
7138
|
-
symlinkSync,
|
|
7139
|
-
writeFileSync as writeFileSync7
|
|
7140
|
-
} from "fs";
|
|
7141
|
-
import { basename as basename4, dirname as dirname5, join as join15, resolve as resolve3 } from "path";
|
|
7142
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7143
|
-
import { Command as Command3 } from "commander";
|
|
7144
|
-
var packageRoot = [
|
|
7145
|
-
fileURLToPath2(new URL("../..", import.meta.url)),
|
|
7146
|
-
fileURLToPath2(new URL("../../..", import.meta.url))
|
|
7147
|
-
].find((candidate) => existsSync15(join15(candidate, "package.json"))) ?? fileURLToPath2(new URL("../..", import.meta.url));
|
|
7148
|
-
var ALL_SETUP_AGENTS = ["claude", "copilot", "codex", "agent"];
|
|
7149
|
-
var SKILL_PAIRS = [
|
|
7150
|
-
{
|
|
7151
|
-
from: join15(packageRoot, ".claude", "skills", "zam", "SKILL.md"),
|
|
7152
|
-
to: join15(".claude", "skills", "zam", "SKILL.md"),
|
|
7153
|
-
agents: ["claude", "copilot"]
|
|
7154
|
-
},
|
|
7155
|
-
{
|
|
7156
|
-
from: join15(packageRoot, ".agent", "skills", "zam", "SKILL.md"),
|
|
7157
|
-
to: join15(".agent", "skills", "zam", "SKILL.md"),
|
|
7158
|
-
agents: ["agent"]
|
|
7159
|
-
},
|
|
7160
|
-
{
|
|
7161
|
-
from: join15(packageRoot, ".agents", "skills", "zam", "SKILL.md"),
|
|
7162
|
-
to: join15(".agents", "skills", "zam", "SKILL.md"),
|
|
7163
|
-
agents: ["codex"]
|
|
7164
|
-
}
|
|
7165
|
-
];
|
|
7166
|
-
function parseSetupAgents(value) {
|
|
7167
|
-
if (!value || value.trim().toLowerCase() === "all") {
|
|
7168
|
-
return new Set(ALL_SETUP_AGENTS);
|
|
7169
|
-
}
|
|
7170
|
-
const aliases = {
|
|
7171
|
-
claude: ["claude"],
|
|
7172
|
-
copilot: ["copilot"],
|
|
7173
|
-
codex: ["codex"],
|
|
7174
|
-
agent: ["agent"],
|
|
7175
|
-
opencode: ["agent"]
|
|
7176
|
-
};
|
|
7177
|
-
const selected = /* @__PURE__ */ new Set();
|
|
7178
|
-
for (const raw of value.split(",")) {
|
|
7179
|
-
const key = raw.trim().toLowerCase();
|
|
7180
|
-
const mapped = aliases[key];
|
|
7181
|
-
if (!mapped) {
|
|
7182
|
-
throw new Error(
|
|
7183
|
-
`Unknown agent "${raw}". Use: all, claude, copilot, codex, agent.`
|
|
7184
|
-
);
|
|
7185
|
-
}
|
|
7186
|
-
for (const item of mapped) selected.add(item);
|
|
7187
|
-
}
|
|
7188
|
-
return selected;
|
|
7189
|
-
}
|
|
7190
|
-
function pathExists(path) {
|
|
7191
|
-
try {
|
|
7192
|
-
lstatSync(path);
|
|
7193
|
-
return true;
|
|
7194
|
-
} catch {
|
|
7195
|
-
return false;
|
|
7196
|
-
}
|
|
7197
|
-
}
|
|
7198
|
-
function comparableRealPath(path) {
|
|
7199
|
-
const real = realpathSync(path);
|
|
7200
|
-
return process.platform === "win32" ? real.toLowerCase() : real;
|
|
7201
|
-
}
|
|
7202
|
-
function pathsResolveToSameDirectory(sourceDir, destinationDir) {
|
|
7203
|
-
try {
|
|
7204
|
-
return comparableRealPath(destinationDir) === comparableRealPath(sourceDir);
|
|
7205
|
-
} catch {
|
|
7206
|
-
return false;
|
|
7207
|
-
}
|
|
7208
|
-
}
|
|
7209
|
-
function linkPointsTo(sourceDir, destinationDir) {
|
|
7210
|
-
try {
|
|
7211
|
-
return lstatSync(destinationDir).isSymbolicLink() && pathsResolveToSameDirectory(sourceDir, destinationDir);
|
|
7212
|
-
} catch {
|
|
7213
|
-
return false;
|
|
7214
|
-
}
|
|
7215
|
-
}
|
|
7216
|
-
function isReplaceableCopiedSkill(destinationDir) {
|
|
7217
|
-
try {
|
|
7218
|
-
if (!lstatSync(destinationDir).isDirectory()) return false;
|
|
7219
|
-
const entries = readdirSync2(destinationDir);
|
|
7220
|
-
if (entries.length !== 1 || entries[0] !== "SKILL.md") return false;
|
|
7221
|
-
return /^name:\s*zam\s*$/m.test(
|
|
7222
|
-
readFileSync10(join15(destinationDir, "SKILL.md"), "utf8")
|
|
7223
|
-
);
|
|
7224
|
-
} catch {
|
|
7225
|
-
return false;
|
|
7226
|
-
}
|
|
7227
|
-
}
|
|
7228
|
-
function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {}) {
|
|
7229
|
-
const results = [];
|
|
7230
|
-
const log = (message) => {
|
|
7231
|
-
if (!opts.quiet) console.log(message);
|
|
7232
|
-
};
|
|
7233
|
-
for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
|
|
7234
|
-
if (!pairAgents.some((agent) => agents.has(agent))) continue;
|
|
7235
|
-
const sourceDir = dirname5(from);
|
|
7236
|
-
const destinationDir = dirname5(join15(cwd, to));
|
|
7237
|
-
if (!existsSync15(sourceDir)) {
|
|
7238
|
-
if (!opts.quiet) {
|
|
7239
|
-
console.warn(` warn source not found, skipping: ${sourceDir}`);
|
|
7240
|
-
}
|
|
7241
|
-
results.push({
|
|
7242
|
-
source: sourceDir,
|
|
7243
|
-
destination: destinationDir,
|
|
7244
|
-
action: "skipped",
|
|
7245
|
-
reason: "source-not-found"
|
|
7246
|
-
});
|
|
7247
|
-
continue;
|
|
7248
|
-
}
|
|
7249
|
-
if (pathsResolveToSameDirectory(sourceDir, destinationDir) && !lstatSync(destinationDir).isSymbolicLink()) {
|
|
7250
|
-
log(` skip ${dirname5(to)} (package source)`);
|
|
7251
|
-
results.push({
|
|
7252
|
-
source: sourceDir,
|
|
7253
|
-
destination: destinationDir,
|
|
7254
|
-
action: "skipped",
|
|
7255
|
-
reason: "source-directory"
|
|
7256
|
-
});
|
|
7257
|
-
continue;
|
|
7258
|
-
}
|
|
7259
|
-
if (linkPointsTo(sourceDir, destinationDir)) {
|
|
7260
|
-
log(` skip ${dirname5(to)} (already linked)`);
|
|
7261
|
-
results.push({
|
|
7262
|
-
source: sourceDir,
|
|
7263
|
-
destination: destinationDir,
|
|
7264
|
-
action: "skipped",
|
|
7265
|
-
reason: "already-linked"
|
|
7266
|
-
});
|
|
7267
|
-
continue;
|
|
7268
|
-
}
|
|
7269
|
-
const destinationExists = pathExists(destinationDir);
|
|
7270
|
-
const replaceExisting = destinationExists && (Boolean(opts.force) || isReplaceableCopiedSkill(destinationDir));
|
|
7271
|
-
if (destinationExists && !replaceExisting) {
|
|
7272
|
-
if (!opts.quiet) {
|
|
7273
|
-
console.warn(
|
|
7274
|
-
` warn ${dirname5(to)} already exists and is not managed by ZAM; use --force to replace it`
|
|
7275
|
-
);
|
|
7276
|
-
}
|
|
7277
|
-
results.push({
|
|
7278
|
-
source: sourceDir,
|
|
7279
|
-
destination: destinationDir,
|
|
7280
|
-
action: "skipped",
|
|
7281
|
-
reason: "unmanaged-destination"
|
|
7282
|
-
});
|
|
7283
|
-
continue;
|
|
7284
|
-
}
|
|
7285
|
-
const action = destinationExists ? "relinked" : "linked";
|
|
7286
|
-
if (opts.dryRun) {
|
|
7287
|
-
log(` would ${action === "linked" ? "link" : "relink"} ${dirname5(to)}`);
|
|
7288
|
-
} else {
|
|
7289
|
-
if (destinationExists) {
|
|
7290
|
-
rmSync2(destinationDir, { recursive: true, force: true });
|
|
7291
|
-
}
|
|
7292
|
-
mkdirSync8(dirname5(destinationDir), { recursive: true });
|
|
7293
|
-
symlinkSync(
|
|
7294
|
-
sourceDir,
|
|
7295
|
-
destinationDir,
|
|
7296
|
-
process.platform === "win32" ? "junction" : "dir"
|
|
7297
|
-
);
|
|
7298
|
-
log(` ${action === "linked" ? "link" : "relink"} ${dirname5(to)}`);
|
|
7299
|
-
}
|
|
7300
|
-
results.push({ source: sourceDir, destination: destinationDir, action });
|
|
7301
|
-
}
|
|
7302
|
-
return results;
|
|
7303
|
-
}
|
|
7304
|
-
function formatDatabaseInitTarget(target) {
|
|
7305
|
-
switch (target.kind) {
|
|
7306
|
-
case "local":
|
|
7307
|
-
return `ZAM database at ${target.location} (local SQLite)`;
|
|
7308
|
-
case "turso-remote":
|
|
7309
|
-
return `ZAM database via Turso remote at ${target.location}`;
|
|
7310
|
-
case "turso-native":
|
|
7311
|
-
return `ZAM database via Turso native driver at ${target.location}`;
|
|
7312
|
-
case "turso-replica":
|
|
7313
|
-
return `ZAM database replica at ${target.location} syncing from ${target.syncUrl}`;
|
|
7314
|
-
}
|
|
7315
|
-
}
|
|
7316
|
-
async function initDatabase(skipInit) {
|
|
7317
|
-
if (skipInit) return;
|
|
7318
|
-
try {
|
|
7319
|
-
const target = getDatabaseTargetInfo();
|
|
7320
|
-
const db = await openDatabaseWithSync({ initialize: true });
|
|
7321
|
-
await db.close();
|
|
7322
|
-
console.log(` init ${formatDatabaseInitTarget(target)}`);
|
|
7323
|
-
} catch (err) {
|
|
7324
|
-
const msg = err.message;
|
|
7325
|
-
if (!msg.includes("already")) {
|
|
7326
|
-
console.warn(` warn database init: ${msg}`);
|
|
7327
|
-
} else {
|
|
7328
|
-
console.log(` skip database already initialized`);
|
|
7329
|
-
}
|
|
7330
|
-
}
|
|
7331
|
-
}
|
|
7332
|
-
var ZAM_BLOCK_START = "<!-- ZAM:START -->";
|
|
7333
|
-
var ZAM_BLOCK_END = "<!-- ZAM:END -->";
|
|
7334
|
-
function upsertMarkedBlock(dest, blockBody, dryRun) {
|
|
7335
|
-
const block = `${ZAM_BLOCK_START}
|
|
7336
|
-
${blockBody.trim()}
|
|
7337
|
-
${ZAM_BLOCK_END}`;
|
|
7338
|
-
if (!existsSync15(dest)) {
|
|
7339
|
-
if (!dryRun) {
|
|
7340
|
-
mkdirSync8(dirname5(dest), { recursive: true });
|
|
7341
|
-
writeFileSync7(dest, `${block}
|
|
7342
|
-
`, "utf8");
|
|
7343
|
-
}
|
|
7344
|
-
return "write";
|
|
7345
|
-
}
|
|
7346
|
-
const existing = readFileSync10(dest, "utf8");
|
|
7347
|
-
if (existing.includes(block)) return "skip";
|
|
7348
|
-
const start = existing.indexOf(ZAM_BLOCK_START);
|
|
7349
|
-
const end = existing.indexOf(ZAM_BLOCK_END);
|
|
7350
|
-
const next = start >= 0 && end > start ? `${existing.slice(0, start)}${block}${existing.slice(end + ZAM_BLOCK_END.length)}` : `${existing.trimEnd()}
|
|
7351
|
-
|
|
7352
|
-
${block}
|
|
7353
|
-
`;
|
|
7354
|
-
if (!dryRun) writeFileSync7(dest, next, "utf8");
|
|
7355
|
-
return start >= 0 && end > start ? "update" : "write";
|
|
7356
|
-
}
|
|
7357
|
-
function logInstructionAction(action, label, dryRun) {
|
|
7358
|
-
if (action === "skip") {
|
|
7359
|
-
console.log(` skip ${label} (ZAM block already present)`);
|
|
7360
|
-
} else {
|
|
7361
|
-
console.log(` ${dryRun ? "would " : ""}${action} ${label}`);
|
|
7362
|
-
}
|
|
7363
|
-
}
|
|
7364
|
-
function writeClaudeMd(skipClaudeMd, cwd = process.cwd(), opts = {}) {
|
|
7365
|
-
if (skipClaudeMd) return;
|
|
7366
|
-
const dest = join15(cwd, "CLAUDE.md");
|
|
7367
|
-
if (existsSync15(dest)) {
|
|
7368
|
-
if (!opts.updateExisting) {
|
|
7369
|
-
console.log(` skip CLAUDE.md (already present)`);
|
|
7370
|
-
return;
|
|
7371
|
-
}
|
|
7372
|
-
const action = upsertMarkedBlock(
|
|
7373
|
-
dest,
|
|
7374
|
-
`## ZAM learning sessions
|
|
7375
|
-
|
|
7376
|
-
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.
|
|
7377
|
-
|
|
7378
|
-
- Skill files live under \`.claude/skills/zam/\`.
|
|
7379
|
-
- Fast-changing review data lives in \`~/.zam/\`, not in this repository.
|
|
7380
|
-
- The skill directory is linked to this ZAM installation and updates with it.`,
|
|
7381
|
-
Boolean(opts.dryRun)
|
|
7382
|
-
);
|
|
7383
|
-
logInstructionAction(action, "CLAUDE.md", Boolean(opts.dryRun));
|
|
7384
|
-
return;
|
|
7385
|
-
}
|
|
7386
|
-
const name = basename4(cwd);
|
|
7387
|
-
const content = `# ZAM Personal Kernel \xE2\u20AC\u201D ${name}
|
|
7388
|
-
|
|
7389
|
-
This is a ZAM personal instance. ZAM builds lasting skills through spaced
|
|
7390
|
-
repetition during real work \xE2\u20AC\u201D not separate study sessions.
|
|
7391
|
-
|
|
7392
|
-
## First time here?
|
|
7393
|
-
Run \`/setup\` in Claude Code or Gemini CLI to complete first-time setup.
|
|
7394
|
-
|
|
7395
|
-
## Regular use
|
|
7396
|
-
Run \`/zam\` to start a learning session on whatever you are working on.
|
|
7397
|
-
|
|
7398
|
-
## What lives here
|
|
7399
|
-
- \`beliefs/\` \xE2\u20AC\u201D your worldview, approved by git commit
|
|
7400
|
-
- \`goals/\` \xE2\u20AC\u201D your objectives, decomposed into tasks and learning tokens
|
|
7401
|
-
|
|
7402
|
-
## Fast-changing data
|
|
7403
|
-
Learning tokens, cards, and review history live in local SQLite by default.
|
|
7404
|
-
Use \`zam connector setup turso\` to store cloud credentials in
|
|
7405
|
-
\`~/.zam/credentials.json\` and use a Turso database across machines.
|
|
7406
|
-
`;
|
|
7407
|
-
if (opts.dryRun) {
|
|
7408
|
-
console.log(` would write CLAUDE.md`);
|
|
7409
|
-
} else {
|
|
7410
|
-
writeFileSync7(dest, content, "utf8");
|
|
7411
|
-
console.log(` write CLAUDE.md`);
|
|
7412
|
-
}
|
|
7413
|
-
}
|
|
7414
|
-
function writeAgentsMd(skipAgentsMd, cwd = process.cwd(), opts = {}) {
|
|
7415
|
-
if (skipAgentsMd) return;
|
|
7416
|
-
const dest = join15(cwd, "AGENTS.md");
|
|
7417
|
-
if (existsSync15(dest)) {
|
|
7418
|
-
if (!opts.updateExisting) {
|
|
7419
|
-
console.log(` skip AGENTS.md (already present)`);
|
|
7420
|
-
return;
|
|
7421
|
-
}
|
|
7422
|
-
const action = upsertMarkedBlock(
|
|
7423
|
-
dest,
|
|
7424
|
-
`## ZAM learning sessions
|
|
7425
|
-
|
|
7426
|
-
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.
|
|
7427
|
-
|
|
7428
|
-
- Skill files live under \`.agents/skills/zam/\`.
|
|
7429
|
-
- Fast-changing review data lives in \`~/.zam/\`, not in this repository.
|
|
7430
|
-
- The skill directory is linked to this ZAM installation and updates with it.`,
|
|
7431
|
-
Boolean(opts.dryRun)
|
|
7432
|
-
);
|
|
7433
|
-
logInstructionAction(action, "AGENTS.md", Boolean(opts.dryRun));
|
|
7434
|
-
return;
|
|
7435
|
-
}
|
|
7436
|
-
const name = basename4(cwd);
|
|
7437
|
-
const content = `# ZAM Personal Kernel - ${name}
|
|
7438
|
-
|
|
7439
|
-
This is a ZAM personal instance. ZAM builds lasting skills through spaced
|
|
7440
|
-
repetition during real work, not separate study sessions.
|
|
7441
|
-
|
|
7442
|
-
## First time here?
|
|
7443
|
-
Run \`zam setup\` from the shell. When this repository includes
|
|
7444
|
-
\`.agents/skills/setup/\`, you can instead select \`setup\` through \`/skills\`
|
|
7445
|
-
or invoke \`$setup\`.
|
|
7446
|
-
|
|
7447
|
-
## Regular use
|
|
7448
|
-
Select the \`zam\` skill through \`/skills\` or invoke \`$zam\` to start a
|
|
7449
|
-
learning session on whatever you are working on.
|
|
7450
|
-
|
|
7451
|
-
## What lives here
|
|
7452
|
-
- \`beliefs/\` - your worldview, approved by git commit
|
|
7453
|
-
- \`goals/\` - your objectives, decomposed into tasks and learning tokens
|
|
7454
|
-
|
|
7455
|
-
## Fast-changing data
|
|
7456
|
-
Learning tokens, cards, and review history live in local SQLite by default.
|
|
7457
|
-
Use \`zam connector setup turso\` to store cloud credentials in
|
|
7458
|
-
\`~/.zam/credentials.json\` and use a Turso database across machines.
|
|
7459
|
-
|
|
7460
|
-
## Codex skills
|
|
7461
|
-
Codex discovers repository skills under \`.agents/skills/\`. Run
|
|
7462
|
-
\`zam setup --force\` after upgrading \`zam-core\` to refresh them.
|
|
7463
|
-
`;
|
|
7464
|
-
if (opts.dryRun) {
|
|
7465
|
-
console.log(` would write AGENTS.md`);
|
|
7466
|
-
} else {
|
|
7467
|
-
writeFileSync7(dest, content, "utf8");
|
|
7468
|
-
console.log(` write AGENTS.md`);
|
|
7469
|
-
}
|
|
7470
|
-
}
|
|
7471
|
-
function writeCopilotInstructions(cwd = process.cwd(), opts = {}) {
|
|
7472
|
-
const dest = join15(cwd, ".github", "copilot-instructions.md");
|
|
7473
|
-
const action = upsertMarkedBlock(
|
|
7474
|
-
dest,
|
|
7475
|
-
`## ZAM learning sessions
|
|
7476
|
-
|
|
7477
|
-
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.
|
|
7478
|
-
|
|
7479
|
-
- Fast-changing review data lives in \`~/.zam/\`, not in this repository.
|
|
7480
|
-
- The skill directory is linked to this ZAM installation and updates with it.`,
|
|
7481
|
-
Boolean(opts.dryRun)
|
|
7482
|
-
);
|
|
7483
|
-
logInstructionAction(
|
|
7484
|
-
action,
|
|
7485
|
-
".github/copilot-instructions.md",
|
|
7486
|
-
Boolean(opts.dryRun)
|
|
7487
|
-
);
|
|
7488
|
-
}
|
|
7489
|
-
var setupCommand = new Command3("setup").description(
|
|
7490
|
-
"Link ZAM skill directories into this workspace and initialize the database"
|
|
7491
|
-
).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(
|
|
7492
|
-
"--agents <list>",
|
|
7493
|
-
"comma-separated agents to wire: all, claude, copilot, codex, agent"
|
|
7494
|
-
).option(
|
|
7495
|
-
"--dry-run",
|
|
7496
|
-
"show what would be written without changing files",
|
|
7497
|
-
false
|
|
7498
|
-
).action(
|
|
7499
|
-
async (opts) => {
|
|
7500
|
-
let agents;
|
|
7501
|
-
try {
|
|
7502
|
-
agents = parseSetupAgents(opts.agents);
|
|
7503
|
-
} catch (err) {
|
|
7504
|
-
console.error(`Error: ${err.message}`);
|
|
7505
|
-
process.exit(1);
|
|
7506
|
-
}
|
|
7507
|
-
const target = resolve3(opts.target ?? process.cwd());
|
|
7508
|
-
const updateExistingInstructions = Boolean(opts.target) || opts.force;
|
|
7509
|
-
console.log(
|
|
7510
|
-
`Setting up ZAM in ${target}${opts.dryRun ? " (dry run)" : ""}
|
|
7511
|
-
`
|
|
7512
|
-
);
|
|
7513
|
-
wireSkills(target, agents, {
|
|
7514
|
-
force: opts.force,
|
|
7515
|
-
dryRun: opts.dryRun
|
|
7516
|
-
});
|
|
7517
|
-
await initDatabase(opts.skipInit || opts.dryRun);
|
|
7518
|
-
if (agents.has("claude")) {
|
|
7519
|
-
writeClaudeMd(opts.skipClaudeMd, target, {
|
|
7520
|
-
dryRun: opts.dryRun,
|
|
7521
|
-
updateExisting: updateExistingInstructions
|
|
7522
|
-
});
|
|
7523
|
-
}
|
|
7524
|
-
if (agents.has("codex") || agents.has("agent")) {
|
|
7525
|
-
writeAgentsMd(opts.skipAgentsMd, target, {
|
|
7526
|
-
dryRun: opts.dryRun,
|
|
7527
|
-
updateExisting: updateExistingInstructions
|
|
7528
|
-
});
|
|
7529
|
-
}
|
|
7530
|
-
if (agents.has("copilot") && (opts.target || opts.agents)) {
|
|
7531
|
-
writeCopilotInstructions(target, {
|
|
7532
|
-
dryRun: opts.dryRun,
|
|
7533
|
-
updateExisting: updateExistingInstructions
|
|
7534
|
-
});
|
|
7535
|
-
}
|
|
7536
|
-
console.log(
|
|
7537
|
-
"\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."
|
|
7538
|
-
);
|
|
7539
|
-
}
|
|
7540
|
-
);
|
|
7541
|
-
|
|
7542
|
-
// src/cli/commands/workspace.ts
|
|
7543
|
-
import { execFileSync as execFileSync3 } from "child_process";
|
|
7544
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync9, writeFileSync as writeFileSync8 } from "fs";
|
|
7545
|
-
import { homedir as homedir9 } from "os";
|
|
7546
|
-
import { join as join16, resolve as resolve4 } from "path";
|
|
7547
|
-
import { confirm, input } from "@inquirer/prompts";
|
|
7548
|
-
import { Command as Command4 } from "commander";
|
|
7549
|
-
function runGit(cwd, args) {
|
|
7550
|
-
try {
|
|
7551
|
-
return execFileSync3("git", args, {
|
|
7552
|
-
cwd,
|
|
7553
|
-
stdio: "pipe",
|
|
7554
|
-
encoding: "utf8"
|
|
7555
|
-
}).trim();
|
|
7556
|
-
} catch (err) {
|
|
7557
|
-
throw new Error(`Git command failed: ${err.message}`);
|
|
7558
|
-
}
|
|
7559
|
-
}
|
|
7560
|
-
function ghRepoCreateArgs(repoName, repoVisibility) {
|
|
7561
|
-
return ["repo", "create", repoName, repoVisibility, "--source=.", "--push"];
|
|
7562
|
-
}
|
|
7563
|
-
function gitRemoteArgs(githubUrl, hasOrigin) {
|
|
7564
|
-
return hasOrigin ? ["remote", "set-url", "origin", githubUrl] : ["remote", "add", "origin", githubUrl];
|
|
7565
|
-
}
|
|
7566
|
-
var workspaceCommand = new Command4("workspace").description(
|
|
7567
|
-
"Manage your ZAM learning workspace"
|
|
7568
|
-
);
|
|
7569
|
-
var WORKSPACE_KINDS = [
|
|
7570
|
-
"personal",
|
|
7571
|
-
"team",
|
|
7572
|
-
"family",
|
|
7573
|
-
"community",
|
|
7574
|
-
"organization",
|
|
7575
|
-
"custom"
|
|
7576
|
-
];
|
|
7577
|
-
var WORKSPACE_SOURCE_CONTROLS = [
|
|
7578
|
-
"github",
|
|
7579
|
-
"azure-devops",
|
|
7580
|
-
"git",
|
|
7581
|
-
"none"
|
|
7582
|
-
];
|
|
7583
|
-
function parseWorkspaceKind(value) {
|
|
7584
|
-
const kind = (value ?? "custom").toLowerCase();
|
|
7585
|
-
if (WORKSPACE_KINDS.includes(kind)) {
|
|
7586
|
-
return kind;
|
|
7587
|
-
}
|
|
7588
|
-
throw new Error(
|
|
7589
|
-
`Invalid workspace kind: ${value}. Use ${WORKSPACE_KINDS.join(", ")}.`
|
|
7590
|
-
);
|
|
7591
|
-
}
|
|
7592
|
-
function parseWorkspaceSourceControl(value) {
|
|
7593
|
-
if (!value) return void 0;
|
|
7594
|
-
const source = value.toLowerCase();
|
|
7595
|
-
if (WORKSPACE_SOURCE_CONTROLS.includes(source)) {
|
|
7596
|
-
return source;
|
|
7597
|
-
}
|
|
7598
|
-
throw new Error(
|
|
7599
|
-
`Invalid source control: ${value}. Use ${WORKSPACE_SOURCE_CONTROLS.join(", ")}.`
|
|
7600
|
-
);
|
|
7601
|
-
}
|
|
7602
|
-
function parseScopes(value) {
|
|
7603
|
-
if (!value) return void 0;
|
|
7604
|
-
const scopes = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
7605
|
-
return scopes.length > 0 ? scopes : void 0;
|
|
7073
|
+
return results;
|
|
7606
7074
|
}
|
|
7607
|
-
function
|
|
7608
|
-
const
|
|
7609
|
-
|
|
7610
|
-
|
|
7611
|
-
|
|
7612
|
-
);
|
|
7075
|
+
function inspectSkillLinks(cwd = process.cwd(), agents = parseSetupAgents()) {
|
|
7076
|
+
const results = [];
|
|
7077
|
+
for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
|
|
7078
|
+
if (!pairAgents.some((agent) => agents.has(agent))) continue;
|
|
7079
|
+
const sourceDir = dirname5(from);
|
|
7080
|
+
const destinationDir = dirname5(join15(cwd, to));
|
|
7081
|
+
results.push({
|
|
7082
|
+
agents: pairAgents,
|
|
7083
|
+
source: sourceDir,
|
|
7084
|
+
destination: destinationDir,
|
|
7085
|
+
state: classifySkillDestination(sourceDir, destinationDir)
|
|
7086
|
+
});
|
|
7613
7087
|
}
|
|
7614
|
-
return
|
|
7088
|
+
return results;
|
|
7615
7089
|
}
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
console.log(JSON.stringify({ workspaces }, null, 2));
|
|
7620
|
-
return;
|
|
7090
|
+
function summarizeSkillLinkHealth(inspections) {
|
|
7091
|
+
if (inspections.some((item) => item.state === "unmanaged")) {
|
|
7092
|
+
return "unmanaged";
|
|
7621
7093
|
}
|
|
7622
|
-
|
|
7623
|
-
if (
|
|
7624
|
-
|
|
7625
|
-
" (none) \u2014 add one: zam workspace add personal --path <dir>"
|
|
7626
|
-
);
|
|
7627
|
-
return;
|
|
7628
|
-
}
|
|
7629
|
-
for (const workspace of workspaces) {
|
|
7630
|
-
const label = workspace.label ? ` (${workspace.label})` : "";
|
|
7631
|
-
console.log(` ${workspace.id}${label}`);
|
|
7632
|
-
console.log(` kind: ${workspace.kind}`);
|
|
7633
|
-
console.log(` path: ${workspace.path}`);
|
|
7634
|
-
if (workspace.sourceControl) {
|
|
7635
|
-
console.log(` source: ${workspace.sourceControl}`);
|
|
7636
|
-
}
|
|
7637
|
-
if (workspace.knowledgeScopes?.length) {
|
|
7638
|
-
console.log(` scopes: ${workspace.knowledgeScopes.join(", ")}`);
|
|
7639
|
-
}
|
|
7094
|
+
const repairable = ["missing", "broken", "stale-copy"];
|
|
7095
|
+
if (inspections.some((item) => repairable.includes(item.state))) {
|
|
7096
|
+
return "needs-repair";
|
|
7640
7097
|
}
|
|
7641
|
-
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
|
|
7651
|
-
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
process.exit(1);
|
|
7098
|
+
return "healthy";
|
|
7099
|
+
}
|
|
7100
|
+
var ZAM_BLOCK_START = "<!-- ZAM:START -->";
|
|
7101
|
+
var ZAM_BLOCK_END = "<!-- ZAM:END -->";
|
|
7102
|
+
function upsertMarkedBlock(dest, blockBody, dryRun) {
|
|
7103
|
+
const block = `${ZAM_BLOCK_START}
|
|
7104
|
+
${blockBody.trim()}
|
|
7105
|
+
${ZAM_BLOCK_END}`;
|
|
7106
|
+
if (!existsSync15(dest)) {
|
|
7107
|
+
if (!dryRun) {
|
|
7108
|
+
mkdirSync8(dirname5(dest), { recursive: true });
|
|
7109
|
+
writeFileSync7(dest, `${block}
|
|
7110
|
+
`, "utf8");
|
|
7655
7111
|
}
|
|
7656
|
-
|
|
7657
|
-
id,
|
|
7658
|
-
kind: parseWorkspaceKind(opts.kind),
|
|
7659
|
-
path,
|
|
7660
|
-
...opts.label ? { label: opts.label } : {},
|
|
7661
|
-
...opts.sourceControl ? { sourceControl: parseWorkspaceSourceControl(opts.sourceControl) } : {},
|
|
7662
|
-
...parseScopes(opts.scopes) ? { knowledgeScopes: parseScopes(opts.scopes) } : {},
|
|
7663
|
-
...opts.defaultAgent ? { defaultAgent: opts.defaultAgent } : {}
|
|
7664
|
-
};
|
|
7665
|
-
wireSkills(path, parseSetupAgents());
|
|
7666
|
-
upsertConfiguredWorkspace(workspace);
|
|
7667
|
-
console.log(`Registered and linked workspace "${id}" at ${path}.`);
|
|
7668
|
-
} catch (err) {
|
|
7669
|
-
console.error(`Error: ${err.message}`);
|
|
7670
|
-
process.exit(1);
|
|
7112
|
+
return "write";
|
|
7671
7113
|
}
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
const
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7114
|
+
const existing = readFileSync10(dest, "utf8");
|
|
7115
|
+
if (existing.includes(block)) return "skip";
|
|
7116
|
+
const start = existing.indexOf(ZAM_BLOCK_START);
|
|
7117
|
+
const end = existing.indexOf(ZAM_BLOCK_END);
|
|
7118
|
+
const next = start >= 0 && end > start ? `${existing.slice(0, start)}${block}${existing.slice(end + ZAM_BLOCK_END.length)}` : `${existing.trimEnd()}
|
|
7119
|
+
|
|
7120
|
+
${block}
|
|
7121
|
+
`;
|
|
7122
|
+
if (!dryRun) writeFileSync7(dest, next, "utf8");
|
|
7123
|
+
return start >= 0 && end > start ? "update" : "write";
|
|
7124
|
+
}
|
|
7125
|
+
function logInstructionAction(action, label, dryRun) {
|
|
7126
|
+
if (action === "skip") {
|
|
7127
|
+
console.log(` skip ${label} (ZAM block already present)`);
|
|
7128
|
+
} else {
|
|
7129
|
+
console.log(` ${dryRun ? "would " : ""}${action} ${label}`);
|
|
7678
7130
|
}
|
|
7679
|
-
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
);
|
|
7683
|
-
|
|
7684
|
-
|
|
7685
|
-
|
|
7686
|
-
|
|
7687
|
-
).option(
|
|
7688
|
-
"--force",
|
|
7689
|
-
"overwrite existing skill files and refresh ZAM blocks",
|
|
7690
|
-
false
|
|
7691
|
-
).option(
|
|
7692
|
-
"--dry-run",
|
|
7693
|
-
"show what would be written without changing files",
|
|
7694
|
-
false
|
|
7695
|
-
).action((id, opts) => {
|
|
7696
|
-
try {
|
|
7697
|
-
const workspace = requireWorkspace(id);
|
|
7698
|
-
const agents = parseSetupAgents(opts.agents);
|
|
7699
|
-
console.log(
|
|
7700
|
-
`Setting up workspace "${workspace.id}" in ${workspace.path}${opts.dryRun ? " (dry run)" : ""}
|
|
7701
|
-
`
|
|
7702
|
-
);
|
|
7703
|
-
wireSkills(workspace.path, agents, {
|
|
7704
|
-
force: Boolean(opts.force),
|
|
7705
|
-
dryRun: Boolean(opts.dryRun)
|
|
7706
|
-
});
|
|
7707
|
-
if (agents.has("claude")) {
|
|
7708
|
-
writeClaudeMd(false, workspace.path, {
|
|
7709
|
-
dryRun: Boolean(opts.dryRun),
|
|
7710
|
-
updateExisting: true
|
|
7711
|
-
});
|
|
7712
|
-
}
|
|
7713
|
-
if (agents.has("codex") || agents.has("agent")) {
|
|
7714
|
-
writeAgentsMd(false, workspace.path, {
|
|
7715
|
-
dryRun: Boolean(opts.dryRun),
|
|
7716
|
-
updateExisting: true
|
|
7717
|
-
});
|
|
7718
|
-
}
|
|
7719
|
-
if (agents.has("copilot")) {
|
|
7720
|
-
writeCopilotInstructions(workspace.path, {
|
|
7721
|
-
dryRun: Boolean(opts.dryRun),
|
|
7722
|
-
updateExisting: true
|
|
7723
|
-
});
|
|
7131
|
+
}
|
|
7132
|
+
function writeClaudeMd(skipClaudeMd, cwd = process.cwd(), opts = {}) {
|
|
7133
|
+
if (skipClaudeMd) return;
|
|
7134
|
+
const dest = join15(cwd, "CLAUDE.md");
|
|
7135
|
+
if (existsSync15(dest)) {
|
|
7136
|
+
if (!opts.updateExisting) {
|
|
7137
|
+
console.log(` skip CLAUDE.md (already present)`);
|
|
7138
|
+
return;
|
|
7724
7139
|
}
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
workspaceDir = await getSetting(db, "personal.workspace_dir") || "";
|
|
7736
|
-
await db.close();
|
|
7737
|
-
} catch {
|
|
7738
|
-
await db?.close();
|
|
7739
|
-
}
|
|
7740
|
-
if (!workspaceDir) {
|
|
7741
|
-
console.error(
|
|
7742
|
-
"\x1B[31m\u2717 No active workspace configured. Please run `zam init` first.\x1B[0m"
|
|
7743
|
-
);
|
|
7744
|
-
process.exit(1);
|
|
7745
|
-
}
|
|
7746
|
-
if (!existsSync16(workspaceDir)) {
|
|
7747
|
-
console.error(
|
|
7748
|
-
`\x1B[31m\u2717 Workspace directory does not exist: ${workspaceDir}\x1B[0m`
|
|
7140
|
+
const action = upsertMarkedBlock(
|
|
7141
|
+
dest,
|
|
7142
|
+
`## ZAM learning sessions
|
|
7143
|
+
|
|
7144
|
+
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.
|
|
7145
|
+
|
|
7146
|
+
- Skill files live under \`.claude/skills/zam/\`.
|
|
7147
|
+
- Fast-changing review data lives in \`~/.zam/\`, not in this repository.
|
|
7148
|
+
- The skill directory is linked to this ZAM installation and updates with it.`,
|
|
7149
|
+
Boolean(opts.dryRun)
|
|
7749
7150
|
);
|
|
7750
|
-
|
|
7151
|
+
logInstructionAction(action, "CLAUDE.md", Boolean(opts.dryRun));
|
|
7152
|
+
return;
|
|
7751
7153
|
}
|
|
7752
|
-
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
|
|
7154
|
+
const name = basename4(cwd);
|
|
7155
|
+
const content = `# ZAM Personal Kernel \u2014 ${name}
|
|
7156
|
+
|
|
7157
|
+
This is a ZAM personal instance. ZAM builds lasting skills through spaced
|
|
7158
|
+
repetition during real work \u2014 not separate study sessions.
|
|
7159
|
+
|
|
7160
|
+
## First time here?
|
|
7161
|
+
Run \`/setup\` in Claude Code or Gemini CLI to complete first-time setup.
|
|
7162
|
+
|
|
7163
|
+
## Regular use
|
|
7164
|
+
Run \`/zam\` to start a learning session on whatever you are working on.
|
|
7165
|
+
|
|
7166
|
+
## What lives here
|
|
7167
|
+
- \`beliefs/\` \u2014 your worldview, approved by git commit
|
|
7168
|
+
- \`goals/\` \u2014 your objectives, decomposed into tasks and learning tokens
|
|
7169
|
+
|
|
7170
|
+
## Fast-changing data
|
|
7171
|
+
Learning tokens, cards, and review history live in local SQLite by default.
|
|
7172
|
+
Use \`zam connector setup turso\` to store cloud credentials in
|
|
7173
|
+
\`~/.zam/credentials.json\` and use a Turso database across machines.
|
|
7174
|
+
`;
|
|
7175
|
+
if (opts.dryRun) {
|
|
7176
|
+
console.log(` would write CLAUDE.md`);
|
|
7177
|
+
} else {
|
|
7178
|
+
writeFileSync7(dest, content, "utf8");
|
|
7179
|
+
console.log(` write CLAUDE.md`);
|
|
7759
7180
|
}
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7181
|
+
}
|
|
7182
|
+
function writeAgentsMd(skipAgentsMd, cwd = process.cwd(), opts = {}) {
|
|
7183
|
+
if (skipAgentsMd) return;
|
|
7184
|
+
const dest = join15(cwd, "AGENTS.md");
|
|
7185
|
+
if (existsSync15(dest)) {
|
|
7186
|
+
if (!opts.updateExisting) {
|
|
7187
|
+
console.log(` skip AGENTS.md (already present)`);
|
|
7188
|
+
return;
|
|
7189
|
+
}
|
|
7190
|
+
const action = upsertMarkedBlock(
|
|
7191
|
+
dest,
|
|
7192
|
+
`## ZAM learning sessions
|
|
7193
|
+
|
|
7194
|
+
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.
|
|
7195
|
+
|
|
7196
|
+
- Skill files live under \`.agents/skills/zam/\`.
|
|
7197
|
+
- Fast-changing review data lives in \`~/.zam/\`, not in this repository.
|
|
7198
|
+
- The skill directory is linked to this ZAM installation and updates with it.`,
|
|
7199
|
+
Boolean(opts.dryRun)
|
|
7766
7200
|
);
|
|
7201
|
+
logInstructionAction(action, "AGENTS.md", Boolean(opts.dryRun));
|
|
7202
|
+
return;
|
|
7767
7203
|
}
|
|
7768
|
-
const
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7204
|
+
const name = basename4(cwd);
|
|
7205
|
+
const content = `# ZAM Personal Kernel - ${name}
|
|
7206
|
+
|
|
7207
|
+
This is a ZAM personal instance. ZAM builds lasting skills through spaced
|
|
7208
|
+
repetition during real work, not separate study sessions.
|
|
7209
|
+
|
|
7210
|
+
## First time here?
|
|
7211
|
+
Run \`zam setup\` from the shell. When this repository includes
|
|
7212
|
+
\`.agents/skills/setup/\`, you can instead select \`setup\` through \`/skills\`
|
|
7213
|
+
or invoke \`$setup\`.
|
|
7214
|
+
|
|
7215
|
+
## Regular use
|
|
7216
|
+
Select the \`zam\` skill through \`/skills\` or invoke \`$zam\` to start a
|
|
7217
|
+
learning session on whatever you are working on.
|
|
7218
|
+
|
|
7219
|
+
## What lives here
|
|
7220
|
+
- \`beliefs/\` - your worldview, approved by git commit
|
|
7221
|
+
- \`goals/\` - your objectives, decomposed into tasks and learning tokens
|
|
7222
|
+
|
|
7223
|
+
## Fast-changing data
|
|
7224
|
+
Learning tokens, cards, and review history live in local SQLite by default.
|
|
7225
|
+
Use \`zam connector setup turso\` to store cloud credentials in
|
|
7226
|
+
\`~/.zam/credentials.json\` and use a Turso database across machines.
|
|
7227
|
+
|
|
7228
|
+
## Codex skills
|
|
7229
|
+
Codex discovers repository skills under \`.agents/skills/\`. Run
|
|
7230
|
+
\`zam setup --force\` after upgrading \`zam-core\` to refresh them.
|
|
7231
|
+
`;
|
|
7232
|
+
if (opts.dryRun) {
|
|
7233
|
+
console.log(` would write AGENTS.md`);
|
|
7779
7234
|
} else {
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
const repoName = await input({
|
|
7783
|
-
message: "Choose a name for your GitHub repository:",
|
|
7784
|
-
default: "zam-personal"
|
|
7785
|
-
});
|
|
7786
|
-
const isPrivate = await confirm({
|
|
7787
|
-
message: "Should the repository be private?",
|
|
7788
|
-
default: true
|
|
7789
|
-
});
|
|
7790
|
-
const repoVisibility = isPrivate ? "--private" : "--public";
|
|
7791
|
-
if (hasCommand("gh")) {
|
|
7792
|
-
console.log("GitHub CLI detected! Automating repository creation...");
|
|
7793
|
-
const proceedGh = await confirm({
|
|
7794
|
-
message: "Would you like ZAM to create the repository using the GitHub CLI?",
|
|
7795
|
-
default: true
|
|
7796
|
-
});
|
|
7797
|
-
if (proceedGh) {
|
|
7798
|
-
try {
|
|
7799
|
-
console.log(`Creating GitHub repository ${repoName}...`);
|
|
7800
|
-
execFileSync3("gh", ghRepoCreateArgs(repoName, repoVisibility), {
|
|
7801
|
-
cwd: workspaceDir,
|
|
7802
|
-
stdio: "inherit"
|
|
7803
|
-
});
|
|
7804
|
-
console.log(
|
|
7805
|
-
"\n\x1B[32m\u2713 Successfully published workspace to GitHub!\x1B[0m"
|
|
7806
|
-
);
|
|
7807
|
-
process.exit(0);
|
|
7808
|
-
} catch (err) {
|
|
7809
|
-
console.warn(
|
|
7810
|
-
`\x1B[33m\u26A0 GitHub CLI creation failed: ${err.message}\x1B[0m`
|
|
7811
|
-
);
|
|
7812
|
-
}
|
|
7813
|
-
}
|
|
7235
|
+
writeFileSync7(dest, content, "utf8");
|
|
7236
|
+
console.log(` write AGENTS.md`);
|
|
7814
7237
|
}
|
|
7815
|
-
|
|
7816
|
-
|
|
7238
|
+
}
|
|
7239
|
+
function writeCopilotInstructions(cwd = process.cwd(), opts = {}) {
|
|
7240
|
+
const dest = join15(cwd, ".github", "copilot-instructions.md");
|
|
7241
|
+
const action = upsertMarkedBlock(
|
|
7242
|
+
dest,
|
|
7243
|
+
`## ZAM learning sessions
|
|
7244
|
+
|
|
7245
|
+
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.
|
|
7246
|
+
|
|
7247
|
+
- Fast-changing review data lives in \`~/.zam/\`, not in this repository.
|
|
7248
|
+
- The skill directory is linked to this ZAM installation and updates with it.`,
|
|
7249
|
+
Boolean(opts.dryRun)
|
|
7817
7250
|
);
|
|
7818
|
-
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
7251
|
+
logInstructionAction(
|
|
7252
|
+
action,
|
|
7253
|
+
".github/copilot-instructions.md",
|
|
7254
|
+
Boolean(opts.dryRun)
|
|
7822
7255
|
);
|
|
7823
|
-
|
|
7824
|
-
|
|
7256
|
+
}
|
|
7257
|
+
|
|
7258
|
+
// src/cli/users/identity.ts
|
|
7259
|
+
async function ensureDefaultUser(db, preferredUserId) {
|
|
7260
|
+
const stored = await getSetting(db, "user.id");
|
|
7261
|
+
if (stored) return stored;
|
|
7262
|
+
const userId = preferredUserId?.trim() || process.env.ZAM_USER?.trim() || process.env.USERNAME?.trim() || process.env.USER?.trim() || "default";
|
|
7263
|
+
await setSetting(db, "user.id", userId);
|
|
7264
|
+
return userId;
|
|
7265
|
+
}
|
|
7266
|
+
async function resolveUser(opts, db, resolveOpts) {
|
|
7267
|
+
if (opts.user) return opts.user;
|
|
7268
|
+
const stored = await getSetting(db, "user.id");
|
|
7269
|
+
if (stored) return stored;
|
|
7270
|
+
const message = "No user specified. Set a default with: zam whoami --set <id>";
|
|
7271
|
+
if (resolveOpts?.json) {
|
|
7272
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
7273
|
+
} else {
|
|
7274
|
+
console.error(message);
|
|
7275
|
+
}
|
|
7276
|
+
process.exit(1);
|
|
7277
|
+
}
|
|
7278
|
+
|
|
7279
|
+
// src/cli/workspaces/active.ts
|
|
7280
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync9 } from "fs";
|
|
7281
|
+
import { homedir as homedir9 } from "os";
|
|
7282
|
+
import { basename as basename5, join as join16, resolve as resolve3 } from "path";
|
|
7283
|
+
var LEGACY_WORKSPACE_DIR_KEY = "personal.workspace_dir";
|
|
7284
|
+
var DEFAULT_WORKSPACE_ID = "personal";
|
|
7285
|
+
function defaultWorkspaceDir() {
|
|
7286
|
+
return join16(homedir9(), "Documents", "zam");
|
|
7287
|
+
}
|
|
7288
|
+
function normalizeWorkspacePath(path) {
|
|
7289
|
+
const resolved = resolve3(path);
|
|
7290
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
7291
|
+
}
|
|
7292
|
+
function sameWorkspacePath(left, right) {
|
|
7293
|
+
return normalizeWorkspacePath(left) === normalizeWorkspacePath(right);
|
|
7294
|
+
}
|
|
7295
|
+
function labelFromPath(path) {
|
|
7296
|
+
return basename5(path) || "ZAM";
|
|
7297
|
+
}
|
|
7298
|
+
function workspaceIdFromPath(dir) {
|
|
7299
|
+
const base = basename5(dir).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
7300
|
+
const prefix = base || "workspace";
|
|
7301
|
+
const existing = new Set(getConfiguredWorkspaces().map((item) => item.id));
|
|
7302
|
+
if (!existing.has(prefix)) return prefix;
|
|
7303
|
+
let index = 2;
|
|
7304
|
+
while (existing.has(`${prefix}-${index}`)) index++;
|
|
7305
|
+
return `${prefix}-${index}`;
|
|
7306
|
+
}
|
|
7307
|
+
function findWorkspaceByPath(dir) {
|
|
7308
|
+
return getConfiguredWorkspaces().find(
|
|
7309
|
+
(workspace) => sameWorkspacePath(workspace.path, dir)
|
|
7825
7310
|
);
|
|
7826
|
-
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
console.error(
|
|
7846
|
-
`\x1B[31m\u2717 Push failed: ${err.message}\x1B[0m`
|
|
7847
|
-
);
|
|
7848
|
-
console.log(
|
|
7849
|
-
"You can push manually later using: git push -u origin main"
|
|
7850
|
-
);
|
|
7851
|
-
}
|
|
7311
|
+
}
|
|
7312
|
+
async function clearLegacyWorkspaceDir(db) {
|
|
7313
|
+
await deleteSetting(db, LEGACY_WORKSPACE_DIR_KEY);
|
|
7314
|
+
}
|
|
7315
|
+
async function migrateLegacyWorkspaceDir(db) {
|
|
7316
|
+
const legacyDir = await getSetting(db, LEGACY_WORKSPACE_DIR_KEY);
|
|
7317
|
+
if (!legacyDir) return void 0;
|
|
7318
|
+
const path = resolve3(legacyDir);
|
|
7319
|
+
const existing = findWorkspaceByPath(path);
|
|
7320
|
+
const migrated = existing ?? {
|
|
7321
|
+
id: getConfiguredWorkspaces().some(
|
|
7322
|
+
(workspace) => workspace.id === DEFAULT_WORKSPACE_ID
|
|
7323
|
+
) ? workspaceIdFromPath(path) : DEFAULT_WORKSPACE_ID,
|
|
7324
|
+
label: labelFromPath(path),
|
|
7325
|
+
kind: "personal",
|
|
7326
|
+
path
|
|
7327
|
+
};
|
|
7328
|
+
if (!existing) {
|
|
7329
|
+
upsertConfiguredWorkspace(migrated);
|
|
7852
7330
|
}
|
|
7853
|
-
|
|
7331
|
+
if (!getActiveWorkspace()) {
|
|
7332
|
+
setActiveWorkspaceId(migrated.id);
|
|
7333
|
+
}
|
|
7334
|
+
await clearLegacyWorkspaceDir(db);
|
|
7335
|
+
return migrated;
|
|
7336
|
+
}
|
|
7337
|
+
async function ensureActiveWorkspace(db) {
|
|
7338
|
+
await migrateLegacyWorkspaceDir(db);
|
|
7339
|
+
const active = getActiveWorkspace();
|
|
7340
|
+
if (active) {
|
|
7341
|
+
mkdirSync9(active.path, { recursive: true });
|
|
7342
|
+
return active;
|
|
7343
|
+
}
|
|
7344
|
+
const configured = getConfiguredWorkspaces()[0];
|
|
7345
|
+
if (configured) {
|
|
7346
|
+
setActiveWorkspaceId(configured.id);
|
|
7347
|
+
mkdirSync9(configured.path, { recursive: true });
|
|
7348
|
+
return configured;
|
|
7349
|
+
}
|
|
7350
|
+
const workspace = {
|
|
7351
|
+
id: DEFAULT_WORKSPACE_ID,
|
|
7352
|
+
label: "Personal",
|
|
7353
|
+
kind: "personal",
|
|
7354
|
+
path: defaultWorkspaceDir()
|
|
7355
|
+
};
|
|
7356
|
+
mkdirSync9(workspace.path, { recursive: true });
|
|
7357
|
+
upsertConfiguredWorkspace(workspace);
|
|
7358
|
+
setActiveWorkspaceId(workspace.id);
|
|
7359
|
+
await clearLegacyWorkspaceDir(db);
|
|
7360
|
+
return workspace;
|
|
7361
|
+
}
|
|
7362
|
+
async function activateWorkspace(db, workspace) {
|
|
7363
|
+
upsertConfiguredWorkspace(workspace);
|
|
7364
|
+
setActiveWorkspaceId(workspace.id);
|
|
7365
|
+
await clearLegacyWorkspaceDir(db);
|
|
7366
|
+
return workspace;
|
|
7367
|
+
}
|
|
7368
|
+
async function activateWorkspacePath(db, dir, opts = {}) {
|
|
7369
|
+
await migrateLegacyWorkspaceDir(db);
|
|
7370
|
+
const path = resolve3(dir);
|
|
7371
|
+
const existing = opts.id ? void 0 : findWorkspaceByPath(path);
|
|
7372
|
+
if (existing) {
|
|
7373
|
+
setActiveWorkspaceId(existing.id);
|
|
7374
|
+
await clearLegacyWorkspaceDir(db);
|
|
7375
|
+
return existing;
|
|
7376
|
+
}
|
|
7377
|
+
const workspace = {
|
|
7378
|
+
id: opts.id || workspaceIdFromPath(path),
|
|
7379
|
+
label: opts.label || labelFromPath(path),
|
|
7380
|
+
kind: opts.kind || "personal",
|
|
7381
|
+
path
|
|
7382
|
+
};
|
|
7383
|
+
return activateWorkspace(db, workspace);
|
|
7384
|
+
}
|
|
7385
|
+
async function removeWorkspaceAndResolveActive(db, id) {
|
|
7386
|
+
removeConfiguredWorkspace(id);
|
|
7387
|
+
await clearLegacyWorkspaceDir(db);
|
|
7388
|
+
const activeWorkspace = await ensureActiveWorkspace(db);
|
|
7389
|
+
return {
|
|
7390
|
+
activeWorkspace,
|
|
7391
|
+
workspaces: getConfiguredWorkspaces()
|
|
7392
|
+
};
|
|
7393
|
+
}
|
|
7394
|
+
function existingWorkspaceDirOrHome(workspace) {
|
|
7395
|
+
return existsSync16(workspace.path) ? workspace.path : homedir9();
|
|
7396
|
+
}
|
|
7397
|
+
|
|
7398
|
+
// src/cli/workspaces/backup.ts
|
|
7399
|
+
import { mkdirSync as mkdirSync10 } from "fs";
|
|
7400
|
+
import { join as join17 } from "path";
|
|
7854
7401
|
async function backupDatabaseTo(db, targetDir) {
|
|
7855
|
-
const backupDir =
|
|
7856
|
-
|
|
7402
|
+
const backupDir = join17(targetDir, "zam-backups");
|
|
7403
|
+
mkdirSync10(backupDir, { recursive: true });
|
|
7857
7404
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
7858
|
-
const dest =
|
|
7405
|
+
const dest = join17(backupDir, `zam-${stamp}.db`);
|
|
7859
7406
|
await db.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
|
|
7860
7407
|
return dest;
|
|
7861
7408
|
}
|
|
7862
|
-
workspaceCommand.command("data-dir").description("Print the ZAM data directory (database, credentials, config)").option("--json", "Output as JSON").action((opts) => {
|
|
7863
|
-
const dir = join16(homedir9(), ".zam");
|
|
7864
|
-
console.log(opts.json ? JSON.stringify({ dataDir: dir }) : dir);
|
|
7865
|
-
});
|
|
7866
|
-
workspaceCommand.command("backup").description("Back up the local ZAM database into your workspace").option(
|
|
7867
|
-
"--dir <path>",
|
|
7868
|
-
"Target directory (default: workspace dir, else ~/Documents/zam)"
|
|
7869
|
-
).option("--json", "Output as JSON").action(async (opts) => {
|
|
7870
|
-
const target = getDatabaseTargetInfo();
|
|
7871
|
-
if (target.kind !== "local") {
|
|
7872
|
-
const reason = `The database is ${target.kind} (${target.location}); file backup applies only to a local database \u2014 your Turso remote is already the cloud backup.`;
|
|
7873
|
-
if (opts.json) {
|
|
7874
|
-
console.log(JSON.stringify({ ok: false, reason }));
|
|
7875
|
-
return;
|
|
7876
|
-
}
|
|
7877
|
-
console.error(`\x1B[33m\u26A0 ${reason}\x1B[0m`);
|
|
7878
|
-
process.exit(1);
|
|
7879
|
-
}
|
|
7880
|
-
let db;
|
|
7881
|
-
try {
|
|
7882
|
-
db = await openDatabase();
|
|
7883
|
-
const workspaceDir = opts.dir || await getSetting(db, "personal.workspace_dir") || join16(homedir9(), "Documents", "zam");
|
|
7884
|
-
const dest = await backupDatabaseTo(db, workspaceDir);
|
|
7885
|
-
if (opts.json) {
|
|
7886
|
-
console.log(JSON.stringify({ ok: true, path: dest }));
|
|
7887
|
-
} else {
|
|
7888
|
-
console.log(`\x1B[32m\u2713 Database backed up to ${dest}\x1B[0m`);
|
|
7889
|
-
}
|
|
7890
|
-
} catch (err) {
|
|
7891
|
-
const reason = err.message;
|
|
7892
|
-
if (opts.json) {
|
|
7893
|
-
console.log(JSON.stringify({ ok: false, reason }));
|
|
7894
|
-
} else {
|
|
7895
|
-
console.error(`\x1B[31m\u2717 Backup failed: ${reason}\x1B[0m`);
|
|
7896
|
-
}
|
|
7897
|
-
process.exit(1);
|
|
7898
|
-
} finally {
|
|
7899
|
-
await db?.close();
|
|
7900
|
-
}
|
|
7901
|
-
});
|
|
7902
7409
|
|
|
7903
7410
|
// src/cli/commands/bridge.ts
|
|
7904
7411
|
var isServeMode = false;
|
|
@@ -7962,7 +7469,7 @@ function parseTokenUpdates(opts) {
|
|
|
7962
7469
|
}
|
|
7963
7470
|
return updates;
|
|
7964
7471
|
}
|
|
7965
|
-
var bridgeCommand = new
|
|
7472
|
+
var bridgeCommand = new Command2("bridge").description(
|
|
7966
7473
|
"Machine-readable JSON protocol for AI integration"
|
|
7967
7474
|
);
|
|
7968
7475
|
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) => {
|
|
@@ -8005,72 +7512,98 @@ bridgeCommand.command("backup-db").description("Back up the local database into
|
|
|
8005
7512
|
return;
|
|
8006
7513
|
}
|
|
8007
7514
|
await withDb2(async (db) => {
|
|
8008
|
-
const workspaceDir = opts.dir || await
|
|
7515
|
+
const workspaceDir = opts.dir || (await ensureActiveWorkspace(db)).path;
|
|
8009
7516
|
const path = await backupDatabaseTo(db, workspaceDir);
|
|
8010
7517
|
jsonOut2({ ok: true, path });
|
|
8011
7518
|
});
|
|
8012
7519
|
});
|
|
8013
7520
|
bridgeCommand.command("workspace-info").description("Report the workspace dir, its default, and the data dir (JSON)").action(async () => {
|
|
8014
7521
|
await withDb2(async (db) => {
|
|
8015
|
-
const
|
|
7522
|
+
const activeWorkspace = await ensureActiveWorkspace(db);
|
|
8016
7523
|
jsonOut2({
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
7524
|
+
activeWorkspaceId: activeWorkspace.id,
|
|
7525
|
+
activeWorkspace,
|
|
7526
|
+
workspaceDir: activeWorkspace.path,
|
|
7527
|
+
defaultWorkspaceDir: defaultWorkspaceDir(),
|
|
7528
|
+
dataDir: join18(homedir10(), ".zam")
|
|
8020
7529
|
});
|
|
8021
7530
|
});
|
|
8022
7531
|
});
|
|
8023
|
-
function
|
|
8024
|
-
|
|
8025
|
-
const path = resolve5(value);
|
|
8026
|
-
return process.platform === "win32" ? path.toLowerCase() : path;
|
|
8027
|
-
};
|
|
8028
|
-
return normalize(left) === normalize(right);
|
|
8029
|
-
}
|
|
8030
|
-
async function ensureDesktopWorkspace(db) {
|
|
8031
|
-
const configured = getConfiguredWorkspaces();
|
|
8032
|
-
const savedWorkspaceDir = await getSetting(db, "personal.workspace_dir");
|
|
8033
|
-
const workspaceDir = savedWorkspaceDir || configured.find((workspace) => workspace.kind === "personal")?.path || join17(homedir10(), "Documents", "zam");
|
|
8034
|
-
mkdirSync10(workspaceDir, { recursive: true });
|
|
8035
|
-
if (!savedWorkspaceDir) {
|
|
8036
|
-
await setSetting(db, "personal.workspace_dir", workspaceDir);
|
|
8037
|
-
}
|
|
8038
|
-
if (!configured.some(
|
|
8039
|
-
(workspace) => sameWorkspacePath(workspace.path, workspaceDir)
|
|
8040
|
-
)) {
|
|
8041
|
-
const id = configured.some((workspace) => workspace.id === "personal") ? workspaceIdFromPath(workspaceDir) : "personal";
|
|
8042
|
-
upsertConfiguredWorkspace({
|
|
8043
|
-
id,
|
|
8044
|
-
label: basename5(workspaceDir) || "ZAM",
|
|
8045
|
-
kind: "personal",
|
|
8046
|
-
path: workspaceDir
|
|
8047
|
-
});
|
|
8048
|
-
}
|
|
8049
|
-
const skillLinks = getConfiguredWorkspaces().flatMap(
|
|
7532
|
+
function provisionConfiguredWorkspaces() {
|
|
7533
|
+
return getConfiguredWorkspaces().flatMap(
|
|
8050
7534
|
(workspace) => existsSync17(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
|
|
8051
7535
|
);
|
|
8052
|
-
|
|
7536
|
+
}
|
|
7537
|
+
function buildWorkspaceLinkHealth(workspaces) {
|
|
7538
|
+
const agents = parseSetupAgents();
|
|
7539
|
+
const map = {};
|
|
7540
|
+
for (const workspace of workspaces) {
|
|
7541
|
+
if (!existsSync17(workspace.path)) continue;
|
|
7542
|
+
const links = inspectSkillLinks(workspace.path, agents);
|
|
7543
|
+
map[workspace.id] = {
|
|
7544
|
+
health: summarizeSkillLinkHealth(links),
|
|
7545
|
+
states: Object.fromEntries(
|
|
7546
|
+
links.map((link) => [link.agents.join("+"), link.state])
|
|
7547
|
+
)
|
|
7548
|
+
};
|
|
7549
|
+
}
|
|
7550
|
+
return map;
|
|
7551
|
+
}
|
|
7552
|
+
async function ensureDesktopWorkspace(db) {
|
|
7553
|
+
const activeWorkspace = await ensureActiveWorkspace(db);
|
|
7554
|
+
const skillLinks = provisionConfiguredWorkspaces();
|
|
7555
|
+
return {
|
|
7556
|
+
workspaceDir: activeWorkspace.path,
|
|
7557
|
+
activeWorkspaceId: activeWorkspace.id,
|
|
7558
|
+
skillLinks
|
|
7559
|
+
};
|
|
8053
7560
|
}
|
|
8054
7561
|
bridgeCommand.command("workspace-list").description("List configured ZAM workspaces (JSON)").action(async () => {
|
|
8055
7562
|
await withDb2(async (db) => {
|
|
8056
|
-
const
|
|
7563
|
+
const activeWorkspace = await ensureActiveWorkspace(db);
|
|
7564
|
+
const workspaces = getConfiguredWorkspaces();
|
|
8057
7565
|
jsonOut2({
|
|
8058
|
-
workspaces
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
7566
|
+
workspaces,
|
|
7567
|
+
activeWorkspaceId: activeWorkspace.id,
|
|
7568
|
+
activeWorkspace,
|
|
7569
|
+
workspaceDir: activeWorkspace.path,
|
|
7570
|
+
defaultWorkspaceDir: defaultWorkspaceDir(),
|
|
7571
|
+
dataDir: join18(homedir10(), ".zam"),
|
|
7572
|
+
linkHealth: buildWorkspaceLinkHealth(workspaces)
|
|
8062
7573
|
});
|
|
8063
7574
|
});
|
|
8064
7575
|
});
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
7576
|
+
bridgeCommand.command("workspace-repair-links").description(
|
|
7577
|
+
"Relink ZAM skill junctions for a configured workspace, replacing broken links and outdated copies (JSON)"
|
|
7578
|
+
).requiredOption("--id <id>", "Workspace id").option(
|
|
7579
|
+
"--agents <list>",
|
|
7580
|
+
"comma-separated agents to wire: all, claude, copilot, codex, agent"
|
|
7581
|
+
).action(async (opts) => {
|
|
7582
|
+
const id = String(opts.id ?? "").trim();
|
|
7583
|
+
if (!id) jsonError("A non-empty --id is required");
|
|
7584
|
+
const workspace = getConfiguredWorkspaces().find((item) => item.id === id);
|
|
7585
|
+
if (!workspace) jsonError(`Workspace "${id}" is not configured`);
|
|
7586
|
+
if (!existsSync17(workspace.path)) {
|
|
7587
|
+
jsonError(`Workspace path does not exist: ${workspace.path}`);
|
|
7588
|
+
}
|
|
7589
|
+
const agents = parseSetupAgents(opts.agents);
|
|
7590
|
+
const skillLinks = wireSkills(workspace.path, agents, {
|
|
7591
|
+
force: true,
|
|
7592
|
+
quiet: true
|
|
7593
|
+
});
|
|
7594
|
+
const links = inspectSkillLinks(workspace.path, agents);
|
|
7595
|
+
jsonOut2({
|
|
7596
|
+
ok: true,
|
|
7597
|
+
workspace,
|
|
7598
|
+
skillLinks,
|
|
7599
|
+
linkHealth: {
|
|
7600
|
+
health: summarizeSkillLinkHealth(links),
|
|
7601
|
+
states: Object.fromEntries(
|
|
7602
|
+
links.map((link) => [link.agents.join("+"), link.state])
|
|
7603
|
+
)
|
|
7604
|
+
}
|
|
7605
|
+
});
|
|
7606
|
+
});
|
|
8074
7607
|
function parseBridgeWorkspaceKind(value) {
|
|
8075
7608
|
const kind = (value || "custom").toLowerCase();
|
|
8076
7609
|
if (kind === "personal" || kind === "team" || kind === "family" || kind === "community" || kind === "organization" || kind === "custom") {
|
|
@@ -8081,25 +7614,25 @@ function parseBridgeWorkspaceKind(value) {
|
|
|
8081
7614
|
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) => {
|
|
8082
7615
|
const raw = String(opts.path ?? "").trim();
|
|
8083
7616
|
if (!raw) jsonError("A non-empty --path is required");
|
|
8084
|
-
const path =
|
|
7617
|
+
const path = resolve4(raw);
|
|
8085
7618
|
if (!existsSync17(path)) jsonError(`Workspace path does not exist: ${path}`);
|
|
8086
|
-
const id =
|
|
8087
|
-
if (!id) jsonError("A non-empty --id is required");
|
|
8088
|
-
const
|
|
8089
|
-
id,
|
|
8090
|
-
label: opts.label || basename5(path) || id,
|
|
8091
|
-
kind: parseBridgeWorkspaceKind(opts.kind),
|
|
8092
|
-
path
|
|
8093
|
-
};
|
|
7619
|
+
const id = opts.id ? String(opts.id).trim() : void 0;
|
|
7620
|
+
if (opts.id && !id) jsonError("A non-empty --id is required");
|
|
7621
|
+
const kind = parseBridgeWorkspaceKind(opts.kind);
|
|
8094
7622
|
const skillLinks = wireSkills(path, parseSetupAgents(), { quiet: true });
|
|
8095
7623
|
await withDb2(async (db) => {
|
|
8096
|
-
await
|
|
8097
|
-
|
|
7624
|
+
const workspace = await activateWorkspacePath(db, path, {
|
|
7625
|
+
...id ? { id } : {},
|
|
7626
|
+
...opts.label ? { label: opts.label } : {},
|
|
7627
|
+
kind
|
|
7628
|
+
});
|
|
8098
7629
|
jsonOut2({
|
|
8099
7630
|
ok: true,
|
|
8100
7631
|
workspace,
|
|
8101
7632
|
workspaces: getConfiguredWorkspaces(),
|
|
8102
|
-
|
|
7633
|
+
activeWorkspaceId: workspace.id,
|
|
7634
|
+
activeWorkspace: workspace,
|
|
7635
|
+
workspaceDir: workspace.path,
|
|
8103
7636
|
skillLinks
|
|
8104
7637
|
});
|
|
8105
7638
|
});
|
|
@@ -8110,27 +7643,15 @@ bridgeCommand.command("workspace-remove").description("Unregister a ZAM workspac
|
|
|
8110
7643
|
const workspace = getConfiguredWorkspaces().find((item) => item.id === id);
|
|
8111
7644
|
if (!workspace) jsonError(`Workspace "${id}" is not configured`);
|
|
8112
7645
|
await withDb2(async (db) => {
|
|
8113
|
-
const
|
|
8114
|
-
const
|
|
8115
|
-
let workspaceDir = activeWorkspaceDir;
|
|
8116
|
-
let skillLinks = [];
|
|
8117
|
-
if (activeWorkspaceDir && sameWorkspacePath(activeWorkspaceDir, workspace.path)) {
|
|
8118
|
-
if (remaining.length > 0) {
|
|
8119
|
-
workspaceDir = remaining[0].path;
|
|
8120
|
-
await setSetting(db, "personal.workspace_dir", workspaceDir);
|
|
8121
|
-
} else {
|
|
8122
|
-
workspaceDir = join17(homedir10(), "Documents", "zam");
|
|
8123
|
-
await setSetting(db, "personal.workspace_dir", workspaceDir);
|
|
8124
|
-
const ensured = await ensureDesktopWorkspace(db);
|
|
8125
|
-
workspaceDir = ensured.workspaceDir;
|
|
8126
|
-
skillLinks = ensured.skillLinks;
|
|
8127
|
-
}
|
|
8128
|
-
}
|
|
7646
|
+
const { activeWorkspace, workspaces } = await removeWorkspaceAndResolveActive(db, id);
|
|
7647
|
+
const skillLinks = provisionConfiguredWorkspaces();
|
|
8129
7648
|
jsonOut2({
|
|
8130
7649
|
ok: true,
|
|
8131
7650
|
removed: workspace,
|
|
8132
|
-
workspaces
|
|
8133
|
-
|
|
7651
|
+
workspaces,
|
|
7652
|
+
activeWorkspaceId: activeWorkspace.id,
|
|
7653
|
+
activeWorkspace,
|
|
7654
|
+
workspaceDir: activeWorkspace.path,
|
|
8134
7655
|
skillLinks
|
|
8135
7656
|
});
|
|
8136
7657
|
});
|
|
@@ -8138,12 +7659,20 @@ bridgeCommand.command("workspace-remove").description("Unregister a ZAM workspac
|
|
|
8138
7659
|
bridgeCommand.command("set-workspace-dir").description("Set the personal workspace directory (JSON)").requiredOption("--dir <path>", "Path to the workspace directory").action(async (opts) => {
|
|
8139
7660
|
const raw = String(opts.dir ?? "").trim();
|
|
8140
7661
|
if (!raw) jsonError("A non-empty --dir is required");
|
|
8141
|
-
const dir =
|
|
7662
|
+
const dir = resolve4(raw);
|
|
8142
7663
|
if (!existsSync17(dir)) jsonError(`Workspace path does not exist: ${dir}`);
|
|
8143
7664
|
const skillLinks = wireSkills(dir, parseSetupAgents(), { quiet: true });
|
|
8144
7665
|
await withDb2(async (db) => {
|
|
8145
|
-
await
|
|
8146
|
-
jsonOut2({
|
|
7666
|
+
const workspace = await activateWorkspacePath(db, dir);
|
|
7667
|
+
jsonOut2({
|
|
7668
|
+
ok: true,
|
|
7669
|
+
workspace,
|
|
7670
|
+
workspaces: getConfiguredWorkspaces(),
|
|
7671
|
+
activeWorkspaceId: workspace.id,
|
|
7672
|
+
activeWorkspace: workspace,
|
|
7673
|
+
workspaceDir: workspace.path,
|
|
7674
|
+
skillLinks
|
|
7675
|
+
});
|
|
8147
7676
|
});
|
|
8148
7677
|
});
|
|
8149
7678
|
bridgeCommand.command("agent-list").description("List agent harnesses with detection state + the default (JSON)").action(async () => {
|
|
@@ -8193,8 +7722,8 @@ bridgeCommand.command("agent-open").description("Launch an agent harness in the
|
|
|
8193
7722
|
if (opts.workspace && !configuredWorkspace) {
|
|
8194
7723
|
jsonError(`Workspace is not configured: ${opts.workspace}`);
|
|
8195
7724
|
}
|
|
8196
|
-
|
|
8197
|
-
|
|
7725
|
+
const activeWorkspace = await ensureActiveWorkspace(db);
|
|
7726
|
+
const workspace = opts.dir ? existsSync17(opts.dir) ? opts.dir : homedir10() : existingWorkspaceDirOrHome(configuredWorkspace ?? activeWorkspace);
|
|
8198
7727
|
launchHarness(harness, {
|
|
8199
7728
|
executable,
|
|
8200
7729
|
workspace,
|
|
@@ -8574,10 +8103,10 @@ bridgeCommand.command("discover-skills").description(
|
|
|
8574
8103
|
"20"
|
|
8575
8104
|
).action(async (opts) => {
|
|
8576
8105
|
try {
|
|
8577
|
-
const monitorDir =
|
|
8106
|
+
const monitorDir = join18(homedir10(), ".zam", "monitor");
|
|
8578
8107
|
let files;
|
|
8579
8108
|
try {
|
|
8580
|
-
files =
|
|
8109
|
+
files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
|
|
8581
8110
|
} catch {
|
|
8582
8111
|
jsonOut2({ proposals: [], message: "No monitor logs found." });
|
|
8583
8112
|
return;
|
|
@@ -8587,7 +8116,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
8587
8116
|
return;
|
|
8588
8117
|
}
|
|
8589
8118
|
const limit = Number(opts.limit);
|
|
8590
|
-
const sorted = files.map((f) => ({ name: f, path:
|
|
8119
|
+
const sorted = files.map((f) => ({ name: f, path: join18(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
|
|
8591
8120
|
const sessionCommands = /* @__PURE__ */ new Map();
|
|
8592
8121
|
for (const file of sorted) {
|
|
8593
8122
|
const sessionId = file.name.replace(".jsonl", "");
|
|
@@ -8699,7 +8228,7 @@ bridgeCommand.command("observe-ui-snapshot").description(
|
|
|
8699
8228
|
});
|
|
8700
8229
|
function resolveWindowsPowerShell() {
|
|
8701
8230
|
try {
|
|
8702
|
-
|
|
8231
|
+
execFileSync3("where.exe", ["pwsh.exe"], { stdio: "ignore" });
|
|
8703
8232
|
return "pwsh";
|
|
8704
8233
|
} catch {
|
|
8705
8234
|
return "powershell";
|
|
@@ -8714,7 +8243,7 @@ function captureScreenshot(outputPath, hwnd, processName) {
|
|
|
8714
8243
|
throw new Error(`Invalid process name format: ${processName}`);
|
|
8715
8244
|
}
|
|
8716
8245
|
if (platform === "win32") {
|
|
8717
|
-
const stdout =
|
|
8246
|
+
const stdout = execFileSync3(
|
|
8718
8247
|
resolveWindowsPowerShell(),
|
|
8719
8248
|
[
|
|
8720
8249
|
"-NoProfile",
|
|
@@ -9027,13 +8556,13 @@ Write-CaptureResult "fullscreen" $hwndVal $matchedBy
|
|
|
9027
8556
|
} else if (platform === "darwin") {
|
|
9028
8557
|
if (hwnd) {
|
|
9029
8558
|
const parsedHwnd = hwnd.startsWith("0x") ? parseInt(hwnd, 16) : parseInt(hwnd, 10);
|
|
9030
|
-
|
|
8559
|
+
execFileSync3("screencapture", ["-l", String(parsedHwnd), outputPath], {
|
|
9031
8560
|
stdio: "pipe"
|
|
9032
8561
|
});
|
|
9033
8562
|
return { method: "screencapture-window", target: null };
|
|
9034
8563
|
} else if (processName) {
|
|
9035
8564
|
try {
|
|
9036
|
-
const windowId =
|
|
8565
|
+
const windowId = execFileSync3(
|
|
9037
8566
|
"osascript",
|
|
9038
8567
|
[
|
|
9039
8568
|
"-e",
|
|
@@ -9042,17 +8571,17 @@ Write-CaptureResult "fullscreen" $hwndVal $matchedBy
|
|
|
9042
8571
|
{ encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }
|
|
9043
8572
|
).trim();
|
|
9044
8573
|
if (windowId && /^\d+$/.test(windowId)) {
|
|
9045
|
-
|
|
8574
|
+
execFileSync3("screencapture", ["-l", windowId, outputPath], {
|
|
9046
8575
|
stdio: "pipe"
|
|
9047
8576
|
});
|
|
9048
8577
|
return { method: "screencapture-window", target: null };
|
|
9049
8578
|
}
|
|
9050
8579
|
} catch {
|
|
9051
8580
|
}
|
|
9052
|
-
|
|
8581
|
+
execFileSync3("screencapture", ["-x", outputPath], { stdio: "pipe" });
|
|
9053
8582
|
return { method: "screencapture-full", target: null };
|
|
9054
8583
|
} else {
|
|
9055
|
-
|
|
8584
|
+
execFileSync3("screencapture", ["-x", outputPath], { stdio: "pipe" });
|
|
9056
8585
|
return { method: "screencapture-full", target: null };
|
|
9057
8586
|
}
|
|
9058
8587
|
} else {
|
|
@@ -9089,7 +8618,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
|
|
|
9089
8618
|
return;
|
|
9090
8619
|
}
|
|
9091
8620
|
}
|
|
9092
|
-
const outputPath = opts.image ?? opts.output ??
|
|
8621
|
+
const outputPath = opts.image ?? opts.output ?? join18(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
|
|
9093
8622
|
const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
|
|
9094
8623
|
if (!isProvided) {
|
|
9095
8624
|
const post = decidePostCapture(policy, {
|
|
@@ -9144,11 +8673,11 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
9144
8673
|
return;
|
|
9145
8674
|
}
|
|
9146
8675
|
const sessionId = opts.session;
|
|
9147
|
-
const statePath =
|
|
8676
|
+
const statePath = join18(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
9148
8677
|
const defaultExt = platform === "win32" ? ".mkv" : ".mov";
|
|
9149
|
-
const outputPath = opts.output ??
|
|
9150
|
-
const { existsSync:
|
|
9151
|
-
if (
|
|
8678
|
+
const outputPath = opts.output ?? join18(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
|
|
8679
|
+
const { existsSync: existsSync26, writeFileSync: writeFileSync12, openSync, closeSync } = await import("fs");
|
|
8680
|
+
if (existsSync26(statePath)) {
|
|
9152
8681
|
jsonOut2({
|
|
9153
8682
|
sessionId,
|
|
9154
8683
|
started: false,
|
|
@@ -9156,7 +8685,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
9156
8685
|
});
|
|
9157
8686
|
return;
|
|
9158
8687
|
}
|
|
9159
|
-
const logPath =
|
|
8688
|
+
const logPath = join18(tmpdir3(), `zam-recording-${sessionId}.log`);
|
|
9160
8689
|
let logFd;
|
|
9161
8690
|
try {
|
|
9162
8691
|
logFd = openSync(logPath, "w");
|
|
@@ -9238,9 +8767,9 @@ bridgeCommand.command("stop-recording").description(
|
|
|
9238
8767
|
return;
|
|
9239
8768
|
}
|
|
9240
8769
|
const sessionId = opts.session;
|
|
9241
|
-
const statePath =
|
|
9242
|
-
const { existsSync:
|
|
9243
|
-
if (!
|
|
8770
|
+
const statePath = join18(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
8771
|
+
const { existsSync: existsSync26, readFileSync: readFileSync16, rmSync: rmSync4 } = await import("fs");
|
|
8772
|
+
if (!existsSync26(statePath)) {
|
|
9244
8773
|
jsonOut2({
|
|
9245
8774
|
sessionId,
|
|
9246
8775
|
stopped: false,
|
|
@@ -9264,7 +8793,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
9264
8793
|
};
|
|
9265
8794
|
let attempts = 0;
|
|
9266
8795
|
while (isProcessRunning(pid) && attempts < 20) {
|
|
9267
|
-
await new Promise((
|
|
8796
|
+
await new Promise((resolve10) => setTimeout(resolve10, 250));
|
|
9268
8797
|
attempts++;
|
|
9269
8798
|
}
|
|
9270
8799
|
if (isProcessRunning(pid)) {
|
|
@@ -9277,7 +8806,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
9277
8806
|
rmSync4(statePath, { force: true });
|
|
9278
8807
|
} catch {
|
|
9279
8808
|
}
|
|
9280
|
-
if (!
|
|
8809
|
+
if (!existsSync26(outputPath)) {
|
|
9281
8810
|
jsonOut2({
|
|
9282
8811
|
sessionId,
|
|
9283
8812
|
stopped: false,
|
|
@@ -9657,11 +9186,12 @@ bridgeCommand.command("desktop-bootstrap").description("Initialize first-run des
|
|
|
9657
9186
|
await withDb2(async (db) => {
|
|
9658
9187
|
const userId = await ensureDefaultUser(db, opts.user);
|
|
9659
9188
|
const { enabled, url, model, locale } = await getLlmConfig(db);
|
|
9660
|
-
const { workspaceDir, skillLinks } = await ensureDesktopWorkspace(db);
|
|
9189
|
+
const { workspaceDir, activeWorkspaceId, skillLinks } = await ensureDesktopWorkspace(db);
|
|
9661
9190
|
jsonOut2({
|
|
9662
9191
|
userId,
|
|
9663
9192
|
locale,
|
|
9664
9193
|
llm: { enabled, url, model },
|
|
9194
|
+
activeWorkspaceId,
|
|
9665
9195
|
workspaceDir,
|
|
9666
9196
|
skillLinks
|
|
9667
9197
|
});
|
|
@@ -9882,8 +9412,8 @@ bridgeCommand.command("serve").description("Start the persistent JSON-RPC stdin/
|
|
|
9882
9412
|
});
|
|
9883
9413
|
|
|
9884
9414
|
// src/cli/commands/card.ts
|
|
9885
|
-
import { Command as
|
|
9886
|
-
var cardCommand = new
|
|
9415
|
+
import { Command as Command3 } from "commander";
|
|
9416
|
+
var cardCommand = new Command3("card").description(
|
|
9887
9417
|
"Manage spaced-repetition cards"
|
|
9888
9418
|
);
|
|
9889
9419
|
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) => {
|
|
@@ -10081,9 +9611,9 @@ cardCommand.command("delete").description("Delete one user's card for a token").
|
|
|
10081
9611
|
});
|
|
10082
9612
|
|
|
10083
9613
|
// src/cli/commands/connector.ts
|
|
10084
|
-
import { input
|
|
10085
|
-
import { Command as
|
|
10086
|
-
var connectorCommand = new
|
|
9614
|
+
import { input, password } from "@inquirer/prompts";
|
|
9615
|
+
import { Command as Command4 } from "commander";
|
|
9616
|
+
var connectorCommand = new Command4("connector").description(
|
|
10087
9617
|
"Manage external service connectors"
|
|
10088
9618
|
);
|
|
10089
9619
|
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(
|
|
@@ -10102,13 +9632,13 @@ connectorCommand.command("setup").description("Configure a connector").argument(
|
|
|
10102
9632
|
process.exit(1);
|
|
10103
9633
|
}
|
|
10104
9634
|
try {
|
|
10105
|
-
const orgUrl = await
|
|
9635
|
+
const orgUrl = await input({
|
|
10106
9636
|
message: "Organization URL (e.g. https://dev.azure.com/myorg):"
|
|
10107
9637
|
});
|
|
10108
|
-
const project = await
|
|
9638
|
+
const project = await input({
|
|
10109
9639
|
message: "Project name:"
|
|
10110
9640
|
});
|
|
10111
|
-
const pat = await
|
|
9641
|
+
const pat = await password({
|
|
10112
9642
|
message: "Personal Access Token:"
|
|
10113
9643
|
});
|
|
10114
9644
|
if (!orgUrl || !project || !pat) {
|
|
@@ -10195,23 +9725,31 @@ connectorCommand.command("sync").description("Verify the Turso cloud database co
|
|
|
10195
9725
|
});
|
|
10196
9726
|
async function setupTurso(urlArg, tokenArg, mode) {
|
|
10197
9727
|
let db;
|
|
9728
|
+
const arch = getSystemProfile().arch;
|
|
9729
|
+
const isWindowsArm64 = process.platform === "win32" && arch === "arm64";
|
|
9730
|
+
const effectiveMode = mode ?? (isWindowsArm64 ? "remote" : void 0);
|
|
9731
|
+
if (!mode && isWindowsArm64) {
|
|
9732
|
+
console.log(
|
|
9733
|
+
"Detected Windows ARM64 \u2014 defaulting to remote (HTTP) mode.\n The native libsql driver is not available on this architecture.\n Remote mode uses the Turso HTTP API and works everywhere.\n"
|
|
9734
|
+
);
|
|
9735
|
+
}
|
|
10198
9736
|
try {
|
|
10199
|
-
const url = urlArg ?? await
|
|
9737
|
+
const url = urlArg ?? await input({
|
|
10200
9738
|
message: "Turso database URL (e.g. libsql://my-db-user.turso.io):"
|
|
10201
9739
|
});
|
|
10202
|
-
const token = tokenArg ?? await
|
|
9740
|
+
const token = tokenArg ?? await password({
|
|
10203
9741
|
message: "Auth token:"
|
|
10204
9742
|
});
|
|
10205
9743
|
if (!url || !token) {
|
|
10206
9744
|
console.error("Both URL and token are required.");
|
|
10207
9745
|
process.exit(1);
|
|
10208
9746
|
}
|
|
10209
|
-
setTursoCredentials(url, token, void 0,
|
|
9747
|
+
setTursoCredentials(url, token, void 0, effectiveMode);
|
|
10210
9748
|
db = await openDatabaseWithSync({ initialize: true });
|
|
10211
9749
|
await db.prepare("SELECT 1").get();
|
|
10212
9750
|
await db.close();
|
|
10213
9751
|
console.log(
|
|
10214
|
-
`Turso cloud database configured and verified: ${url}` + (
|
|
9752
|
+
`Turso cloud database configured and verified: ${url}` + (effectiveMode ? ` (mode: ${effectiveMode})` : "")
|
|
10215
9753
|
);
|
|
10216
9754
|
} catch (err) {
|
|
10217
9755
|
await db?.close();
|
|
@@ -10226,26 +9764,26 @@ async function setupTurso(urlArg, tokenArg, mode) {
|
|
|
10226
9764
|
|
|
10227
9765
|
// src/cli/commands/git-sync.ts
|
|
10228
9766
|
import { execSync as execSync5 } from "child_process";
|
|
10229
|
-
import { chmodSync as chmodSync2, existsSync as existsSync18, writeFileSync as
|
|
10230
|
-
import { join as
|
|
10231
|
-
import { Command as
|
|
9767
|
+
import { chmodSync as chmodSync2, existsSync as existsSync18, writeFileSync as writeFileSync8 } from "fs";
|
|
9768
|
+
import { join as join19 } from "path";
|
|
9769
|
+
import { Command as Command5 } from "commander";
|
|
10232
9770
|
function installHook2() {
|
|
10233
|
-
const gitDir =
|
|
9771
|
+
const gitDir = join19(process.cwd(), ".git");
|
|
10234
9772
|
if (!existsSync18(gitDir)) {
|
|
10235
9773
|
console.error(
|
|
10236
9774
|
"Error: Current directory is not the root of a Git repository."
|
|
10237
9775
|
);
|
|
10238
9776
|
process.exit(1);
|
|
10239
9777
|
}
|
|
10240
|
-
const hooksDir =
|
|
10241
|
-
const hookPath =
|
|
9778
|
+
const hooksDir = join19(gitDir, "hooks");
|
|
9779
|
+
const hookPath = join19(hooksDir, "post-commit");
|
|
10242
9780
|
const hookContent = `#!/bin/sh
|
|
10243
9781
|
# ZAM Spaced Repetition Auto-Stale Hook
|
|
10244
9782
|
# Triggered automatically on git commits to decay modified concept cards.
|
|
10245
9783
|
zam git-sync --commit HEAD --quiet
|
|
10246
9784
|
`;
|
|
10247
9785
|
try {
|
|
10248
|
-
|
|
9786
|
+
writeFileSync8(hookPath, hookContent, { encoding: "utf-8", flag: "w" });
|
|
10249
9787
|
try {
|
|
10250
9788
|
chmodSync2(hookPath, "755");
|
|
10251
9789
|
} catch (_e) {
|
|
@@ -10258,7 +9796,7 @@ zam git-sync --commit HEAD --quiet
|
|
|
10258
9796
|
process.exit(1);
|
|
10259
9797
|
}
|
|
10260
9798
|
}
|
|
10261
|
-
var gitSyncCommand = new
|
|
9799
|
+
var gitSyncCommand = new Command5("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) => {
|
|
10262
9800
|
if (opts.install) {
|
|
10263
9801
|
installHook2();
|
|
10264
9802
|
return;
|
|
@@ -10348,9 +9886,9 @@ ZAM Auto-Stale Complete: Scanned ${changedFiles.length} file(s).`
|
|
|
10348
9886
|
|
|
10349
9887
|
// src/cli/commands/goal.ts
|
|
10350
9888
|
import { existsSync as existsSync19, mkdirSync as mkdirSync11 } from "fs";
|
|
10351
|
-
import { resolve as
|
|
10352
|
-
import { input as
|
|
10353
|
-
import { Command as
|
|
9889
|
+
import { resolve as resolve5 } from "path";
|
|
9890
|
+
import { input as input2 } from "@inquirer/prompts";
|
|
9891
|
+
import { Command as Command6 } from "commander";
|
|
10354
9892
|
async function resolveGoalsDir() {
|
|
10355
9893
|
let goalsDir;
|
|
10356
9894
|
let db;
|
|
@@ -10361,9 +9899,9 @@ async function resolveGoalsDir() {
|
|
|
10361
9899
|
} finally {
|
|
10362
9900
|
await db?.close();
|
|
10363
9901
|
}
|
|
10364
|
-
return goalsDir ?
|
|
9902
|
+
return goalsDir ? resolve5(goalsDir) : resolve5("goals");
|
|
10365
9903
|
}
|
|
10366
|
-
var goalCommand = new
|
|
9904
|
+
var goalCommand = new Command6("goal").description(
|
|
10367
9905
|
"Manage learning goals (markdown files)"
|
|
10368
9906
|
);
|
|
10369
9907
|
goalCommand.command("list").description("List all goals").option(
|
|
@@ -10482,11 +10020,11 @@ goalCommand.command("create").description("Create a new goal").option("--slug <s
|
|
|
10482
10020
|
if (!slug || !title) {
|
|
10483
10021
|
try {
|
|
10484
10022
|
if (!title) {
|
|
10485
|
-
title = await
|
|
10023
|
+
title = await input2({ message: "Goal title:" });
|
|
10486
10024
|
}
|
|
10487
10025
|
if (!slug) {
|
|
10488
10026
|
const suggested = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
10489
|
-
slug = await
|
|
10027
|
+
slug = await input2({
|
|
10490
10028
|
message: "Goal slug (filename):",
|
|
10491
10029
|
default: suggested
|
|
10492
10030
|
});
|
|
@@ -10532,22 +10070,22 @@ goalCommand.command("status <slug> <status>").description("Update a goal's statu
|
|
|
10532
10070
|
});
|
|
10533
10071
|
|
|
10534
10072
|
// src/cli/commands/init.ts
|
|
10535
|
-
import { existsSync as existsSync20, mkdirSync as mkdirSync12, writeFileSync as
|
|
10073
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync12, writeFileSync as writeFileSync9 } from "fs";
|
|
10536
10074
|
import { homedir as homedir11 } from "os";
|
|
10537
|
-
import { join as
|
|
10538
|
-
import { confirm
|
|
10539
|
-
import { Command as
|
|
10075
|
+
import { join as join20, resolve as resolve6 } from "path";
|
|
10076
|
+
import { confirm, input as input3 } from "@inquirer/prompts";
|
|
10077
|
+
import { Command as Command7 } from "commander";
|
|
10540
10078
|
var HOME2 = homedir11();
|
|
10541
10079
|
function printLine(char = "\u2550", len = 60, color = "\x1B[36m") {
|
|
10542
10080
|
console.log(`${color}${char.repeat(len)}\x1B[0m`);
|
|
10543
10081
|
}
|
|
10544
10082
|
function bootstrapSandboxWorkspace(workspaceDir) {
|
|
10545
|
-
mkdirSync12(
|
|
10546
|
-
mkdirSync12(
|
|
10547
|
-
mkdirSync12(
|
|
10548
|
-
const worldviewFile =
|
|
10083
|
+
mkdirSync12(join20(workspaceDir, "beliefs"), { recursive: true });
|
|
10084
|
+
mkdirSync12(join20(workspaceDir, "goals"), { recursive: true });
|
|
10085
|
+
mkdirSync12(join20(workspaceDir, "skills"), { recursive: true });
|
|
10086
|
+
const worldviewFile = join20(workspaceDir, "beliefs", "worldview.md");
|
|
10549
10087
|
if (!existsSync20(worldviewFile)) {
|
|
10550
|
-
|
|
10088
|
+
writeFileSync9(
|
|
10551
10089
|
worldviewFile,
|
|
10552
10090
|
`# Personal Worldview
|
|
10553
10091
|
|
|
@@ -10559,9 +10097,9 @@ Here, I declare the core concepts and principles I want to master.
|
|
|
10559
10097
|
"utf8"
|
|
10560
10098
|
);
|
|
10561
10099
|
}
|
|
10562
|
-
const goalsFile =
|
|
10100
|
+
const goalsFile = join20(workspaceDir, "goals", "goals.md");
|
|
10563
10101
|
if (!existsSync20(goalsFile)) {
|
|
10564
|
-
|
|
10102
|
+
writeFileSync9(
|
|
10565
10103
|
goalsFile,
|
|
10566
10104
|
`# Personal Goals
|
|
10567
10105
|
|
|
@@ -10573,7 +10111,7 @@ Here, I declare the core concepts and principles I want to master.
|
|
|
10573
10111
|
);
|
|
10574
10112
|
}
|
|
10575
10113
|
}
|
|
10576
|
-
var initCommand = new
|
|
10114
|
+
var initCommand = new Command7("init").description("Launch the guided interactive onboarding wizard").action(async () => {
|
|
10577
10115
|
printLine();
|
|
10578
10116
|
console.log(
|
|
10579
10117
|
"\x1B[1m\x1B[32m ZAM \u2014 The Symbiotic Learning Agent Onboarding\x1B[0m"
|
|
@@ -10583,9 +10121,9 @@ var initCommand = new Command10("init").description("Launch the guided interacti
|
|
|
10583
10121
|
);
|
|
10584
10122
|
printLine();
|
|
10585
10123
|
console.log("\n\x1B[1m[1/5] Setting up Local Workspace Sandbox\x1B[0m");
|
|
10586
|
-
const defaultWorkspace =
|
|
10587
|
-
const workspacePath =
|
|
10588
|
-
await
|
|
10124
|
+
const defaultWorkspace = join20(HOME2, "Documents", "zam");
|
|
10125
|
+
const workspacePath = resolve6(
|
|
10126
|
+
await input3({
|
|
10589
10127
|
message: "Choose your ZAM workspace directory:",
|
|
10590
10128
|
default: defaultWorkspace
|
|
10591
10129
|
})
|
|
@@ -10599,6 +10137,7 @@ var initCommand = new Command10("init").description("Launch the guided interacti
|
|
|
10599
10137
|
kind: "personal",
|
|
10600
10138
|
path: workspacePath
|
|
10601
10139
|
});
|
|
10140
|
+
setActiveWorkspaceId("personal");
|
|
10602
10141
|
console.log(
|
|
10603
10142
|
`\x1B[32m\u2713 Local Sandbox created at: ${workspacePath}\x1B[0m`
|
|
10604
10143
|
);
|
|
@@ -10629,7 +10168,7 @@ var initCommand = new Command10("init").description("Launch the guided interacti
|
|
|
10629
10168
|
\x1B[1mRecommendation:\x1B[0m ZAM suggests installing \x1B[32m${runnerLabel}\x1B[0m with \x1B[36m${profile.recommendedModel}\x1B[0m.`
|
|
10630
10169
|
);
|
|
10631
10170
|
console.log("\n\x1B[1m[3/5] Setting up Local LLM Runner\x1B[0m");
|
|
10632
|
-
const proceedInstall = await
|
|
10171
|
+
const proceedInstall = await confirm({
|
|
10633
10172
|
message: `Would you like ZAM to install and configure ${runnerLabel} automatically?`,
|
|
10634
10173
|
default: true
|
|
10635
10174
|
});
|
|
@@ -10666,7 +10205,7 @@ var initCommand = new Command10("init").description("Launch the guided interacti
|
|
|
10666
10205
|
let db;
|
|
10667
10206
|
try {
|
|
10668
10207
|
db = await openDatabaseWithSync({ initialize: true });
|
|
10669
|
-
await
|
|
10208
|
+
await deleteSetting(db, "personal.workspace_dir");
|
|
10670
10209
|
const detectedLocale = detectSystemLocale();
|
|
10671
10210
|
await setSetting(db, "system.locale", detectedLocale);
|
|
10672
10211
|
console.log(
|
|
@@ -10697,7 +10236,7 @@ var initCommand = new Command10("init").description("Launch the guided interacti
|
|
|
10697
10236
|
console.log(
|
|
10698
10237
|
"\n\x1B[1m[5/5] Wiring Developer Agents & Terminal Helpers\x1B[0m"
|
|
10699
10238
|
);
|
|
10700
|
-
const proceedHooks = await
|
|
10239
|
+
const proceedHooks = await confirm({
|
|
10701
10240
|
message: "Distribute ZAM skills and install optional monitored-session shell helpers?",
|
|
10702
10241
|
default: true
|
|
10703
10242
|
});
|
|
@@ -10748,8 +10287,8 @@ var initCommand = new Command10("init").description("Launch the guided interacti
|
|
|
10748
10287
|
});
|
|
10749
10288
|
|
|
10750
10289
|
// src/cli/commands/learn.ts
|
|
10751
|
-
import { input as
|
|
10752
|
-
import { Command as
|
|
10290
|
+
import { input as input5 } from "@inquirer/prompts";
|
|
10291
|
+
import { Command as Command8 } from "commander";
|
|
10753
10292
|
|
|
10754
10293
|
// src/cli/learn-format.ts
|
|
10755
10294
|
var BLOOM_VERBS3 = {
|
|
@@ -10798,7 +10337,7 @@ function formatReveal(input8) {
|
|
|
10798
10337
|
}
|
|
10799
10338
|
|
|
10800
10339
|
// src/cli/review-actions.ts
|
|
10801
|
-
import { confirm as
|
|
10340
|
+
import { confirm as confirm2, input as input4, select } from "@inquirer/prompts";
|
|
10802
10341
|
async function runInteractiveReviewAction(inputData) {
|
|
10803
10342
|
let currentItem = { ...inputData.item };
|
|
10804
10343
|
while (true) {
|
|
@@ -10913,7 +10452,7 @@ async function runInteractiveReviewAction(inputData) {
|
|
|
10913
10452
|
continue;
|
|
10914
10453
|
}
|
|
10915
10454
|
if (choice === "deprecate-token") {
|
|
10916
|
-
const approved = await
|
|
10455
|
+
const approved = await confirm2({
|
|
10917
10456
|
message: `Deprecate ${currentItem.slug} so it stops appearing in review queues?`,
|
|
10918
10457
|
default: false
|
|
10919
10458
|
});
|
|
@@ -10944,7 +10483,7 @@ async function runInteractiveReviewAction(inputData) {
|
|
|
10944
10483
|
console.log(` Session steps: ${impact.session_steps}`);
|
|
10945
10484
|
console.log(` Sessions touched: ${impact.sessions_touched}`);
|
|
10946
10485
|
console.log(` Agent skills updated: ${impact.agent_skills}`);
|
|
10947
|
-
const approved = await
|
|
10486
|
+
const approved = await confirm2({
|
|
10948
10487
|
message: "Permanently delete this token and its dependent learning data?",
|
|
10949
10488
|
default: false
|
|
10950
10489
|
});
|
|
@@ -10969,7 +10508,7 @@ async function runInteractiveReviewAction(inputData) {
|
|
|
10969
10508
|
);
|
|
10970
10509
|
console.log(`Delete your card for ${currentItem.slug}?`);
|
|
10971
10510
|
console.log(` Review logs removed: ${impact.review_logs}`);
|
|
10972
|
-
const approved = await
|
|
10511
|
+
const approved = await confirm2({
|
|
10973
10512
|
message: "Delete only your card for this token?",
|
|
10974
10513
|
default: false
|
|
10975
10514
|
});
|
|
@@ -11007,21 +10546,21 @@ async function promptTokenEdit(token) {
|
|
|
11007
10546
|
}
|
|
11008
10547
|
switch (field) {
|
|
11009
10548
|
case "concept": {
|
|
11010
|
-
const concept = await
|
|
10549
|
+
const concept = await input4({
|
|
11011
10550
|
message: "Concept:",
|
|
11012
10551
|
default: token.concept
|
|
11013
10552
|
});
|
|
11014
10553
|
return concept === token.concept ? null : { concept };
|
|
11015
10554
|
}
|
|
11016
10555
|
case "domain": {
|
|
11017
|
-
const domain = await
|
|
10556
|
+
const domain = await input4({
|
|
11018
10557
|
message: "Domain (blank allowed):",
|
|
11019
10558
|
default: token.domain
|
|
11020
10559
|
});
|
|
11021
10560
|
return domain === token.domain ? null : { domain };
|
|
11022
10561
|
}
|
|
11023
10562
|
case "context": {
|
|
11024
|
-
const context = await
|
|
10563
|
+
const context = await input4({
|
|
11025
10564
|
message: "Context (blank allowed):",
|
|
11026
10565
|
default: token.context
|
|
11027
10566
|
});
|
|
@@ -11062,14 +10601,14 @@ async function promptTokenEdit(token) {
|
|
|
11062
10601
|
return mode === token.symbiosis_mode ? null : { symbiosis_mode: mode };
|
|
11063
10602
|
}
|
|
11064
10603
|
case "source_link": {
|
|
11065
|
-
const link = await
|
|
10604
|
+
const link = await input4({
|
|
11066
10605
|
message: "Source link (blank allowed):",
|
|
11067
10606
|
default: token.source_link ?? ""
|
|
11068
10607
|
});
|
|
11069
10608
|
return link === token.source_link ? null : { source_link: link || null };
|
|
11070
10609
|
}
|
|
11071
10610
|
case "question": {
|
|
11072
|
-
const question = await
|
|
10611
|
+
const question = await input4({
|
|
11073
10612
|
message: "Recall question (blank allowed):",
|
|
11074
10613
|
default: token.question ?? ""
|
|
11075
10614
|
});
|
|
@@ -11087,7 +10626,7 @@ var STOP_WORDS = /* @__PURE__ */ new Set(["q", ":q", "quit", "stop"]);
|
|
|
11087
10626
|
function isExitPrompt(err) {
|
|
11088
10627
|
return err instanceof Error && err.name === "ExitPromptError";
|
|
11089
10628
|
}
|
|
11090
|
-
var learnCommand = new
|
|
10629
|
+
var learnCommand = new Command8("learn").description(
|
|
11091
10630
|
"Run a spoiler-free, in-process learning session (recall \u2192 reveal \u2192 self-rate)"
|
|
11092
10631
|
).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) => {
|
|
11093
10632
|
let db;
|
|
@@ -11167,7 +10706,7 @@ ${"\u2500".repeat(50)}`);
|
|
|
11167
10706
|
${prompt.question}`);
|
|
11168
10707
|
let answer;
|
|
11169
10708
|
try {
|
|
11170
|
-
answer = await
|
|
10709
|
+
answer = await input5({
|
|
11171
10710
|
message: t(locale, "prompt_answer")
|
|
11172
10711
|
});
|
|
11173
10712
|
} catch (err) {
|
|
@@ -11332,8 +10871,8 @@ learnCommand.command("open").description("Open a new terminal window running zam
|
|
|
11332
10871
|
});
|
|
11333
10872
|
|
|
11334
10873
|
// src/cli/commands/monitor.ts
|
|
11335
|
-
import { Command as
|
|
11336
|
-
var monitorCommand = new
|
|
10874
|
+
import { Command as Command9 } from "commander";
|
|
10875
|
+
var monitorCommand = new Command9("monitor").description(
|
|
11337
10876
|
"Shell observation for real-time task monitoring"
|
|
11338
10877
|
);
|
|
11339
10878
|
monitorCommand.command("start").description("Output shell hook code to install monitoring").requiredOption("--session <id>", "Session ID to monitor").option(
|
|
@@ -11515,8 +11054,8 @@ monitorCommand.command("open").description("Open a new monitored terminal window
|
|
|
11515
11054
|
});
|
|
11516
11055
|
|
|
11517
11056
|
// src/cli/commands/observer.ts
|
|
11518
|
-
import { Command as
|
|
11519
|
-
var observerCommand = new
|
|
11057
|
+
import { Command as Command10 } from "commander";
|
|
11058
|
+
var observerCommand = new Command10("observer").description(
|
|
11520
11059
|
"Configure what the UI observer may capture (Layer 2 policy)"
|
|
11521
11060
|
);
|
|
11522
11061
|
function applyObserverListChange(current, entry, op) {
|
|
@@ -11537,107 +11076,343 @@ observerCommand.command("status").description("Show the active observer policy")
|
|
|
11537
11076
|
console.log(` Consent: ${policy.consent}`);
|
|
11538
11077
|
console.log(` Retention: ${policy.retention}`);
|
|
11539
11078
|
console.log(
|
|
11540
|
-
` Allowlist: ${policy.allowlist.length ? policy.allowlist.join(", ") : "(any targeted window)"}`
|
|
11079
|
+
` Allowlist: ${policy.allowlist.length ? policy.allowlist.join(", ") : "(any targeted window)"}`
|
|
11080
|
+
);
|
|
11081
|
+
console.log(
|
|
11082
|
+
` Denylist: ${policy.denylist.length ? policy.denylist.join(", ") : "(none)"}`
|
|
11083
|
+
);
|
|
11084
|
+
console.log(` Redact titles: ${policy.redactWindowTitles}`);
|
|
11085
|
+
console.log(
|
|
11086
|
+
"\nBuilt-in sensitive surfaces (password managers, auth/UAC dialogs, banking) are always refused and cannot be allowlisted."
|
|
11087
|
+
);
|
|
11088
|
+
});
|
|
11089
|
+
});
|
|
11090
|
+
observerCommand.command("grant <process>").description(
|
|
11091
|
+
"Allow the observer to capture a process (adds it to observer.allowlist)"
|
|
11092
|
+
).action(async (processName) => {
|
|
11093
|
+
await withDb(async (db) => {
|
|
11094
|
+
const next = applyObserverListChange(
|
|
11095
|
+
await getSetting(db, "observer.allowlist"),
|
|
11096
|
+
processName,
|
|
11097
|
+
"add"
|
|
11098
|
+
);
|
|
11099
|
+
await setSetting(db, "observer.allowlist", next);
|
|
11100
|
+
await syncObserverSidecarPolicy(db);
|
|
11101
|
+
console.log(`Granted: ${processName.trim().toLowerCase()}`);
|
|
11102
|
+
console.log(`observer.allowlist = ${next || "(empty)"}`);
|
|
11103
|
+
});
|
|
11104
|
+
});
|
|
11105
|
+
observerCommand.command("revoke <process>").description("Remove a process from observer.allowlist").action(async (processName) => {
|
|
11106
|
+
await withDb(async (db) => {
|
|
11107
|
+
const next = applyObserverListChange(
|
|
11108
|
+
await getSetting(db, "observer.allowlist"),
|
|
11109
|
+
processName,
|
|
11110
|
+
"remove"
|
|
11111
|
+
);
|
|
11112
|
+
await setSetting(db, "observer.allowlist", next);
|
|
11113
|
+
await syncObserverSidecarPolicy(db);
|
|
11114
|
+
console.log(`Revoked: ${processName.trim().toLowerCase()}`);
|
|
11115
|
+
console.log(`observer.allowlist = ${next || "(empty)"}`);
|
|
11116
|
+
});
|
|
11117
|
+
});
|
|
11118
|
+
|
|
11119
|
+
// src/cli/commands/profile.ts
|
|
11120
|
+
import { dirname as dirname6, resolve as resolve7 } from "path";
|
|
11121
|
+
import { Command as Command11 } from "commander";
|
|
11122
|
+
var C2 = {
|
|
11123
|
+
reset: "\x1B[0m",
|
|
11124
|
+
bold: "\x1B[1m",
|
|
11125
|
+
cyan: "\x1B[36m",
|
|
11126
|
+
dim: "\x1B[2m",
|
|
11127
|
+
green: "\x1B[32m"
|
|
11128
|
+
};
|
|
11129
|
+
function render(profile) {
|
|
11130
|
+
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}`;
|
|
11131
|
+
console.log(`${C2.bold}ZAM install profile${C2.reset}`);
|
|
11132
|
+
console.log(` mode: ${C2.cyan}${profile.mode}${C2.reset}`);
|
|
11133
|
+
console.log(` personal dir: ${C2.cyan}${profile.personalDir}${C2.reset}`);
|
|
11134
|
+
console.log(` sync: ${sync}`);
|
|
11135
|
+
console.log(` data dir: ${C2.cyan}${profile.dataDir}${C2.reset}`);
|
|
11136
|
+
console.log(` database: ${C2.cyan}${profile.dbPath}${C2.reset}`);
|
|
11137
|
+
}
|
|
11138
|
+
var profileCommand = new Command11("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) => {
|
|
11139
|
+
if (opts.mode && opts.mode !== "developer" && opts.mode !== "default") {
|
|
11140
|
+
console.error(`Invalid --mode: ${opts.mode}. Use developer or default.`);
|
|
11141
|
+
process.exit(1);
|
|
11142
|
+
}
|
|
11143
|
+
let db;
|
|
11144
|
+
try {
|
|
11145
|
+
if (opts.mode) setInstallMode(opts.mode);
|
|
11146
|
+
db = await openDatabaseWithSync({ initialize: true });
|
|
11147
|
+
if (opts.dir) {
|
|
11148
|
+
await activateWorkspacePath(db, resolve7(opts.dir), {
|
|
11149
|
+
kind: "personal",
|
|
11150
|
+
label: "Personal"
|
|
11151
|
+
});
|
|
11152
|
+
}
|
|
11153
|
+
const personalDir = (await ensureActiveWorkspace(db)).path;
|
|
11154
|
+
await db.close();
|
|
11155
|
+
db = void 0;
|
|
11156
|
+
const dbPath = getDefaultDbPath();
|
|
11157
|
+
const profile = {
|
|
11158
|
+
mode: getInstallMode(),
|
|
11159
|
+
personalDir,
|
|
11160
|
+
syncProvider: detectSyncProvider(personalDir),
|
|
11161
|
+
dataDir: dirname6(dbPath),
|
|
11162
|
+
dbPath
|
|
11163
|
+
};
|
|
11164
|
+
if (opts.json) {
|
|
11165
|
+
console.log(JSON.stringify(profile, null, 2));
|
|
11166
|
+
return;
|
|
11167
|
+
}
|
|
11168
|
+
render(profile);
|
|
11169
|
+
} catch (err) {
|
|
11170
|
+
await db?.close();
|
|
11171
|
+
console.error("Error:", err.message);
|
|
11172
|
+
process.exit(1);
|
|
11173
|
+
}
|
|
11174
|
+
});
|
|
11175
|
+
|
|
11176
|
+
// src/cli/commands/provider.ts
|
|
11177
|
+
import { password as password2 } from "@inquirer/prompts";
|
|
11178
|
+
import { Command as Command12 } from "commander";
|
|
11179
|
+
var providerCommand = new Command12("provider").description(
|
|
11180
|
+
"Configure role-based AI providers (url/model/flavor/key, per role)"
|
|
11181
|
+
);
|
|
11182
|
+
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) => {
|
|
11183
|
+
const machine = Boolean(opts.machine);
|
|
11184
|
+
await withProviderScope(machine, async (db) => {
|
|
11185
|
+
const providers = await readScopedProviders(db, machine);
|
|
11186
|
+
const roles = await readScopedRoles(db, machine);
|
|
11187
|
+
const rows = buildProviderListing(
|
|
11188
|
+
providers,
|
|
11189
|
+
(ref) => getProviderApiKey(ref) !== null
|
|
11190
|
+
);
|
|
11191
|
+
const orphans = findOrphanKeyRefs(listProviderApiKeyRefs(), providers);
|
|
11192
|
+
if (opts.json) {
|
|
11193
|
+
console.log(
|
|
11194
|
+
JSON.stringify(
|
|
11195
|
+
{
|
|
11196
|
+
scope: machine ? "machine" : "shared",
|
|
11197
|
+
providers: rows,
|
|
11198
|
+
roles,
|
|
11199
|
+
orphans
|
|
11200
|
+
},
|
|
11201
|
+
null,
|
|
11202
|
+
2
|
|
11203
|
+
)
|
|
11204
|
+
);
|
|
11205
|
+
return;
|
|
11206
|
+
}
|
|
11207
|
+
console.log(
|
|
11208
|
+
`Providers (${opts.machine ? "~/.zam/config.json ai.providers" : "llm.providers"}):
|
|
11209
|
+
`
|
|
11210
|
+
);
|
|
11211
|
+
if (rows.length === 0) {
|
|
11212
|
+
console.log(
|
|
11213
|
+
" (none) \u2014 add one: zam provider add <name> --url <url> --model <model>"
|
|
11214
|
+
);
|
|
11215
|
+
} else {
|
|
11216
|
+
for (const row of rows) {
|
|
11217
|
+
const key = row.keyState === "set" ? "\x1B[32mkey \u2713\x1B[0m" : row.keyState === "missing" ? `\x1B[31mkey \u2717 (set: zam provider set-key ${row.apiKeyRef})\x1B[0m` : "\x1B[90mno key\x1B[0m";
|
|
11218
|
+
console.log(` \x1B[36m${row.name.padEnd(12)}\x1B[0m ${key}`);
|
|
11219
|
+
console.log(` url: ${row.url ?? "(inherits llm.url)"}`);
|
|
11220
|
+
console.log(` model: ${row.model ?? "(inherits llm.model)"}`);
|
|
11221
|
+
console.log(` flavor: ${row.apiFlavor}`);
|
|
11222
|
+
if (row.label) console.log(` label: ${row.label}`);
|
|
11223
|
+
if (row.local !== void 0) {
|
|
11224
|
+
console.log(` local: ${row.local ? "yes" : "no"}`);
|
|
11225
|
+
}
|
|
11226
|
+
if (row.runner) console.log(` runner: ${row.runner}`);
|
|
11227
|
+
if (row.apiKeyRef) console.log(` key-ref: ${row.apiKeyRef}`);
|
|
11228
|
+
}
|
|
11229
|
+
}
|
|
11230
|
+
console.log("\nRoles (llm.roles):\n");
|
|
11231
|
+
for (const role of VALID_ROLES) {
|
|
11232
|
+
const binding = roles[role];
|
|
11233
|
+
if (!binding?.primary) {
|
|
11234
|
+
console.log(` ${role.padEnd(7)} \x1B[90m(unset)\x1B[0m`);
|
|
11235
|
+
} else {
|
|
11236
|
+
const fb = binding.fallback ? ` \u2192 fallback: ${binding.fallback}` : "";
|
|
11237
|
+
console.log(` ${role.padEnd(7)} primary: ${binding.primary}${fb}`);
|
|
11238
|
+
}
|
|
11239
|
+
}
|
|
11240
|
+
if (orphans.length > 0) {
|
|
11241
|
+
console.log(
|
|
11242
|
+
`
|
|
11243
|
+
\x1B[33mOrphan keys (stored but unreferenced):\x1B[0m ${orphans.join(", ")}`
|
|
11244
|
+
);
|
|
11245
|
+
}
|
|
11246
|
+
});
|
|
11247
|
+
});
|
|
11248
|
+
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(
|
|
11249
|
+
"--runner <runner>",
|
|
11250
|
+
"Local runner hint (flm, foundry-local, ollama, ...)"
|
|
11251
|
+
).option(
|
|
11252
|
+
"--flavor <flavor>",
|
|
11253
|
+
`Wire protocol: ${VALID_API_FLAVORS.join(" | ")} (default: inferred from URL)`
|
|
11254
|
+
).option(
|
|
11255
|
+
"--key-ref <ref>",
|
|
11256
|
+
"Credential reference for the API key (default: <name> when --key is given)"
|
|
11257
|
+
).option(
|
|
11258
|
+
"--key <value>",
|
|
11259
|
+
"Store this API key now (prefer `set-key` to keep it out of shell history)"
|
|
11260
|
+
).option("--machine", "Store this provider in ~/.zam/config.json").action(async (name, opts) => {
|
|
11261
|
+
let apiFlavor;
|
|
11262
|
+
if (opts.flavor) {
|
|
11263
|
+
if (!VALID_API_FLAVORS.includes(opts.flavor)) {
|
|
11264
|
+
console.error(
|
|
11265
|
+
`Invalid --flavor: ${opts.flavor}. Use ${VALID_API_FLAVORS.join(" or ")}.`
|
|
11266
|
+
);
|
|
11267
|
+
process.exit(1);
|
|
11268
|
+
}
|
|
11269
|
+
apiFlavor = opts.flavor;
|
|
11270
|
+
}
|
|
11271
|
+
const apiKeyRef = opts.keyRef ?? (opts.key ? name : void 0);
|
|
11272
|
+
const machine = Boolean(opts.machine);
|
|
11273
|
+
await withProviderScope(machine, async (db) => {
|
|
11274
|
+
const providers = await readScopedProviders(db, machine);
|
|
11275
|
+
const next = upsertProviderRecord(providers, name, {
|
|
11276
|
+
label: opts.label,
|
|
11277
|
+
url: opts.url,
|
|
11278
|
+
model: opts.model,
|
|
11279
|
+
apiFlavor,
|
|
11280
|
+
apiKeyRef,
|
|
11281
|
+
local: opts.local ? true : void 0,
|
|
11282
|
+
runner: opts.runner
|
|
11283
|
+
});
|
|
11284
|
+
await writeScopedProviders(db, machine, next);
|
|
11285
|
+
if (opts.key && apiKeyRef) setProviderApiKey(apiKeyRef, opts.key);
|
|
11286
|
+
const rec = next[name];
|
|
11287
|
+
console.log(
|
|
11288
|
+
`Provider "${name}" saved (${machine ? "machine-local" : "shared"}):`
|
|
11541
11289
|
);
|
|
11290
|
+
console.log(` url: ${rec.url ?? "(inherits llm.url)"}`);
|
|
11291
|
+
console.log(` model: ${rec.model ?? "(inherits llm.model)"}`);
|
|
11292
|
+
if (rec.label) console.log(` label: ${rec.label}`);
|
|
11293
|
+
if (rec.local !== void 0) {
|
|
11294
|
+
console.log(` local: ${rec.local ? "yes" : "no"}`);
|
|
11295
|
+
}
|
|
11296
|
+
if (rec.runner) console.log(` runner: ${rec.runner}`);
|
|
11542
11297
|
console.log(
|
|
11543
|
-
`
|
|
11298
|
+
` flavor: ${rec.apiFlavor ?? `${rec.url ? inferApiFlavor(rec.url) : "chat-completions"} (inferred)`}`
|
|
11544
11299
|
);
|
|
11545
|
-
console.log(`
|
|
11300
|
+
console.log(` key-ref: ${rec.apiKeyRef ?? "(none \u2014 uses default key)"}`);
|
|
11301
|
+
if (opts.key) {
|
|
11302
|
+
console.log(` key: stored (${maskSecret(opts.key)})`);
|
|
11303
|
+
} else if (rec.apiKeyRef && getProviderApiKey(rec.apiKeyRef) === null) {
|
|
11304
|
+
console.log(
|
|
11305
|
+
`
|
|
11306
|
+
\u26A0 No key stored for "${rec.apiKeyRef}". Run: zam provider set-key ${rec.apiKeyRef}`
|
|
11307
|
+
);
|
|
11308
|
+
}
|
|
11309
|
+
if (!rec.url) {
|
|
11310
|
+
console.log(
|
|
11311
|
+
"\n \u26A0 No --url set; this provider inherits the base llm.url."
|
|
11312
|
+
);
|
|
11313
|
+
}
|
|
11546
11314
|
console.log(
|
|
11547
|
-
|
|
11315
|
+
`
|
|
11316
|
+
Bind it to a role: zam provider use recall --primary ${name}`
|
|
11548
11317
|
);
|
|
11549
11318
|
});
|
|
11550
11319
|
});
|
|
11551
|
-
|
|
11552
|
-
|
|
11553
|
-
|
|
11554
|
-
|
|
11555
|
-
const next =
|
|
11556
|
-
|
|
11557
|
-
|
|
11558
|
-
"add"
|
|
11320
|
+
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) => {
|
|
11321
|
+
const machine = Boolean(opts.machine);
|
|
11322
|
+
await withProviderScope(machine, async (db) => {
|
|
11323
|
+
const providers = await readScopedProviders(db, machine);
|
|
11324
|
+
const { providers: next, removed } = removeProviderRecord(
|
|
11325
|
+
providers,
|
|
11326
|
+
name
|
|
11559
11327
|
);
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11563
|
-
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
await withDb(async (db) => {
|
|
11568
|
-
const next = applyObserverListChange(
|
|
11569
|
-
await getSetting(db, "observer.allowlist"),
|
|
11570
|
-
processName,
|
|
11571
|
-
"remove"
|
|
11328
|
+
if (!removed) {
|
|
11329
|
+
console.log(`No such provider: ${name}`);
|
|
11330
|
+
return;
|
|
11331
|
+
}
|
|
11332
|
+
await writeScopedProviders(db, machine, next);
|
|
11333
|
+
console.log(
|
|
11334
|
+
`Removed provider "${name}" (${machine ? "machine-local" : "shared"}).`
|
|
11572
11335
|
);
|
|
11573
|
-
|
|
11574
|
-
|
|
11575
|
-
|
|
11576
|
-
|
|
11336
|
+
const referencing = rolesReferencing(
|
|
11337
|
+
await readScopedRoles(db, machine),
|
|
11338
|
+
name
|
|
11339
|
+
);
|
|
11340
|
+
if (referencing.length > 0) {
|
|
11341
|
+
console.log(
|
|
11342
|
+
` \u26A0 Still referenced by role(s): ${referencing.join(", ")} \u2014 rebind with: zam provider use <role> --primary <name>`
|
|
11343
|
+
);
|
|
11344
|
+
}
|
|
11577
11345
|
});
|
|
11578
11346
|
});
|
|
11579
|
-
|
|
11580
|
-
|
|
11581
|
-
|
|
11582
|
-
import { dirname as dirname6, join as join20, resolve as resolve8 } from "path";
|
|
11583
|
-
import { Command as Command14 } from "commander";
|
|
11584
|
-
var C2 = {
|
|
11585
|
-
reset: "\x1B[0m",
|
|
11586
|
-
bold: "\x1B[1m",
|
|
11587
|
-
cyan: "\x1B[36m",
|
|
11588
|
-
dim: "\x1B[2m",
|
|
11589
|
-
green: "\x1B[32m"
|
|
11590
|
-
};
|
|
11591
|
-
function defaultPersonalDir() {
|
|
11592
|
-
return join20(homedir12(), "Documents", "zam");
|
|
11593
|
-
}
|
|
11594
|
-
function render(profile) {
|
|
11595
|
-
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}`;
|
|
11596
|
-
console.log(`${C2.bold}ZAM install profile${C2.reset}`);
|
|
11597
|
-
console.log(` mode: ${C2.cyan}${profile.mode}${C2.reset}`);
|
|
11598
|
-
console.log(` personal dir: ${C2.cyan}${profile.personalDir}${C2.reset}`);
|
|
11599
|
-
console.log(` sync: ${sync}`);
|
|
11600
|
-
console.log(` data dir: ${C2.cyan}${profile.dataDir}${C2.reset}`);
|
|
11601
|
-
console.log(` database: ${C2.cyan}${profile.dbPath}${C2.reset}`);
|
|
11602
|
-
}
|
|
11603
|
-
var profileCommand = new Command14("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) => {
|
|
11604
|
-
if (opts.mode && opts.mode !== "developer" && opts.mode !== "default") {
|
|
11605
|
-
console.error(`Invalid --mode: ${opts.mode}. Use developer or default.`);
|
|
11347
|
+
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) => {
|
|
11348
|
+
if (!VALID_ROLES.includes(role)) {
|
|
11349
|
+
console.error(`Invalid role: ${role}. Use ${VALID_ROLES.join(", ")}.`);
|
|
11606
11350
|
process.exit(1);
|
|
11607
11351
|
}
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11352
|
+
if (!opts.primary) {
|
|
11353
|
+
console.error("--primary is required.");
|
|
11354
|
+
process.exit(1);
|
|
11355
|
+
}
|
|
11356
|
+
const machine = Boolean(opts.machine);
|
|
11357
|
+
await withProviderScope(machine, async (db) => {
|
|
11358
|
+
const providers = await readScopedProviders(db, machine);
|
|
11359
|
+
await writeScopedRoles(
|
|
11360
|
+
db,
|
|
11361
|
+
machine,
|
|
11362
|
+
bindRoleProviders(
|
|
11363
|
+
await readScopedRoles(db, machine),
|
|
11364
|
+
role,
|
|
11365
|
+
opts.primary,
|
|
11366
|
+
opts.fallback
|
|
11367
|
+
)
|
|
11368
|
+
);
|
|
11369
|
+
const fb = opts.fallback ? `, fallback: ${opts.fallback}` : "";
|
|
11370
|
+
console.log(
|
|
11371
|
+
`Role "${role}" \u2192 primary: ${opts.primary}${fb} (${machine ? "machine-local" : "shared"})`
|
|
11372
|
+
);
|
|
11373
|
+
for (const [label, ref] of [
|
|
11374
|
+
["primary", opts.primary],
|
|
11375
|
+
["fallback", opts.fallback]
|
|
11376
|
+
]) {
|
|
11377
|
+
if (ref && !(ref in providers)) {
|
|
11378
|
+
console.log(
|
|
11379
|
+
` \u26A0 ${label} "${ref}" is not a defined provider yet. Add it: zam provider add ${ref} --url <url> --model <model>`
|
|
11380
|
+
);
|
|
11381
|
+
}
|
|
11614
11382
|
}
|
|
11615
|
-
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
11620
|
-
|
|
11621
|
-
|
|
11622
|
-
|
|
11623
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
11628
|
-
return;
|
|
11383
|
+
});
|
|
11384
|
+
});
|
|
11385
|
+
providerCommand.command("set-key <ref>").description(
|
|
11386
|
+
"Store an API key for a provider reference (in credentials.json)"
|
|
11387
|
+
).option(
|
|
11388
|
+
"--key <value>",
|
|
11389
|
+
"The API key (omit to enter it interactively, hidden)"
|
|
11390
|
+
).action(async (ref, opts) => {
|
|
11391
|
+
try {
|
|
11392
|
+
const key = opts.key ?? await password2({ message: `API key for "${ref}":` });
|
|
11393
|
+
if (!key || key.trim().length === 0) {
|
|
11394
|
+
console.error("No key provided.");
|
|
11395
|
+
process.exit(1);
|
|
11629
11396
|
}
|
|
11630
|
-
|
|
11397
|
+
setProviderApiKey(ref, key.trim());
|
|
11398
|
+
console.log(`Stored API key for "${ref}" (${maskSecret(key.trim())}).`);
|
|
11631
11399
|
} catch (err) {
|
|
11632
|
-
|
|
11400
|
+
if (err.name === "ExitPromptError") {
|
|
11401
|
+
console.log("\nCancelled.");
|
|
11402
|
+
process.exit(0);
|
|
11403
|
+
}
|
|
11633
11404
|
console.error("Error:", err.message);
|
|
11634
11405
|
process.exit(1);
|
|
11635
11406
|
}
|
|
11636
11407
|
});
|
|
11408
|
+
providerCommand.command("clear-key <ref>").description("Remove a stored API key").action((ref) => {
|
|
11409
|
+
clearProviderApiKey(ref);
|
|
11410
|
+
console.log(`Cleared API key for "${ref}".`);
|
|
11411
|
+
});
|
|
11637
11412
|
|
|
11638
11413
|
// src/cli/commands/review.ts
|
|
11639
|
-
import { Command as
|
|
11640
|
-
var reviewCommand = new
|
|
11414
|
+
import { Command as Command13 } from "commander";
|
|
11415
|
+
var reviewCommand = new Command13("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) => {
|
|
11641
11416
|
let db;
|
|
11642
11417
|
try {
|
|
11643
11418
|
db = await openDatabase();
|
|
@@ -11751,9 +11526,9 @@ ${"\u2550".repeat(50)}`);
|
|
|
11751
11526
|
|
|
11752
11527
|
// src/cli/commands/session.ts
|
|
11753
11528
|
import { readFileSync as readFileSync12 } from "fs";
|
|
11754
|
-
import { input as
|
|
11755
|
-
import { Command as
|
|
11756
|
-
var sessionCommand = new
|
|
11529
|
+
import { input as input6, select as select2 } from "@inquirer/prompts";
|
|
11530
|
+
import { Command as Command14 } from "commander";
|
|
11531
|
+
var sessionCommand = new Command14("session").description(
|
|
11757
11532
|
"Manage learning sessions"
|
|
11758
11533
|
);
|
|
11759
11534
|
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(
|
|
@@ -11930,7 +11705,7 @@ async function selectTask() {
|
|
|
11930
11705
|
console.log("No active work items found in Azure DevOps.");
|
|
11931
11706
|
}
|
|
11932
11707
|
}
|
|
11933
|
-
return
|
|
11708
|
+
return input6({ message: "Task description:" });
|
|
11934
11709
|
}
|
|
11935
11710
|
var RATING_LABELS = {
|
|
11936
11711
|
1: "Again",
|
|
@@ -12133,8 +11908,8 @@ sessionCommand.command("end").description("End a session and show summary").requ
|
|
|
12133
11908
|
|
|
12134
11909
|
// src/cli/commands/settings.ts
|
|
12135
11910
|
import { existsSync as existsSync21 } from "fs";
|
|
12136
|
-
import { Command as
|
|
12137
|
-
var settingsCommand = new
|
|
11911
|
+
import { Command as Command15 } from "commander";
|
|
11912
|
+
var settingsCommand = new Command15("settings").description(
|
|
12138
11913
|
"Manage user settings"
|
|
12139
11914
|
);
|
|
12140
11915
|
var BOOLEAN_SETTING_KEYS = /* @__PURE__ */ new Set(["llm.enabled", "llm.vision.enabled"]);
|
|
@@ -12305,12 +12080,108 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
|
|
|
12305
12080
|
console.log(` ${name.padEnd(9)}: \x1B[33m- Not Set\x1B[0m`);
|
|
12306
12081
|
}
|
|
12307
12082
|
}
|
|
12308
|
-
});
|
|
12309
|
-
});
|
|
12083
|
+
});
|
|
12084
|
+
});
|
|
12085
|
+
|
|
12086
|
+
// src/cli/commands/setup.ts
|
|
12087
|
+
import { resolve as resolve8 } from "path";
|
|
12088
|
+
import { Command as Command16 } from "commander";
|
|
12089
|
+
function formatDatabaseInitTarget(target) {
|
|
12090
|
+
switch (target.kind) {
|
|
12091
|
+
case "local":
|
|
12092
|
+
return `ZAM database at ${target.location} (local SQLite)`;
|
|
12093
|
+
case "turso-remote":
|
|
12094
|
+
return `ZAM database via Turso remote at ${target.location}`;
|
|
12095
|
+
case "turso-native":
|
|
12096
|
+
return `ZAM database via Turso native driver at ${target.location}`;
|
|
12097
|
+
case "turso-replica":
|
|
12098
|
+
return `ZAM database replica at ${target.location} syncing from ${target.syncUrl}`;
|
|
12099
|
+
}
|
|
12100
|
+
}
|
|
12101
|
+
async function initDatabase(skipInit) {
|
|
12102
|
+
if (skipInit) return;
|
|
12103
|
+
try {
|
|
12104
|
+
const target = getDatabaseTargetInfo();
|
|
12105
|
+
const db = await openDatabaseWithSync({ initialize: true });
|
|
12106
|
+
await activateMachineProviderConfig(db);
|
|
12107
|
+
await db.close();
|
|
12108
|
+
console.log(` init ${formatDatabaseInitTarget(target)}`);
|
|
12109
|
+
} catch (err) {
|
|
12110
|
+
const msg = err.message;
|
|
12111
|
+
if (!msg.includes("already")) {
|
|
12112
|
+
console.warn(` warn database init: ${msg}`);
|
|
12113
|
+
} else {
|
|
12114
|
+
console.log(` skip database already initialized`);
|
|
12115
|
+
}
|
|
12116
|
+
}
|
|
12117
|
+
}
|
|
12118
|
+
async function activateMachineProviderConfig(db) {
|
|
12119
|
+
const machineAi = getMachineAiConfig();
|
|
12120
|
+
const providerCount = Object.keys(machineAi.providers ?? {}).length;
|
|
12121
|
+
const roleCount = Object.keys(machineAi.roles ?? {}).length;
|
|
12122
|
+
if (providerCount === 0 && roleCount === 0) return;
|
|
12123
|
+
if (await getSetting(db, "llm.enabled") !== void 0) return;
|
|
12124
|
+
await setSetting(db, "llm.enabled", "true");
|
|
12125
|
+
console.log(
|
|
12126
|
+
` activate ${providerCount} machine-local provider(s) from ~/.zam/config.json`
|
|
12127
|
+
);
|
|
12128
|
+
}
|
|
12129
|
+
var setupCommand = new Command16("setup").description(
|
|
12130
|
+
"Link ZAM skill directories into this workspace and initialize the database"
|
|
12131
|
+
).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(
|
|
12132
|
+
"--agents <list>",
|
|
12133
|
+
"comma-separated agents to wire: all, claude, copilot, codex, agent"
|
|
12134
|
+
).option(
|
|
12135
|
+
"--dry-run",
|
|
12136
|
+
"show what would be written without changing files",
|
|
12137
|
+
false
|
|
12138
|
+
).action(
|
|
12139
|
+
async (opts) => {
|
|
12140
|
+
let agents;
|
|
12141
|
+
try {
|
|
12142
|
+
agents = parseSetupAgents(opts.agents);
|
|
12143
|
+
} catch (err) {
|
|
12144
|
+
console.error(`Error: ${err.message}`);
|
|
12145
|
+
process.exit(1);
|
|
12146
|
+
}
|
|
12147
|
+
const target = resolve8(opts.target ?? process.cwd());
|
|
12148
|
+
const updateExistingInstructions = Boolean(opts.target) || opts.force;
|
|
12149
|
+
console.log(
|
|
12150
|
+
`Setting up ZAM in ${target}${opts.dryRun ? " (dry run)" : ""}
|
|
12151
|
+
`
|
|
12152
|
+
);
|
|
12153
|
+
wireSkills(target, agents, {
|
|
12154
|
+
force: opts.force,
|
|
12155
|
+
dryRun: opts.dryRun
|
|
12156
|
+
});
|
|
12157
|
+
await initDatabase(opts.skipInit || opts.dryRun);
|
|
12158
|
+
if (agents.has("claude")) {
|
|
12159
|
+
writeClaudeMd(opts.skipClaudeMd, target, {
|
|
12160
|
+
dryRun: opts.dryRun,
|
|
12161
|
+
updateExisting: updateExistingInstructions
|
|
12162
|
+
});
|
|
12163
|
+
}
|
|
12164
|
+
if (agents.has("codex") || agents.has("agent")) {
|
|
12165
|
+
writeAgentsMd(opts.skipAgentsMd, target, {
|
|
12166
|
+
dryRun: opts.dryRun,
|
|
12167
|
+
updateExisting: updateExistingInstructions
|
|
12168
|
+
});
|
|
12169
|
+
}
|
|
12170
|
+
if (agents.has("copilot") && (opts.target || opts.agents)) {
|
|
12171
|
+
writeCopilotInstructions(target, {
|
|
12172
|
+
dryRun: opts.dryRun,
|
|
12173
|
+
updateExisting: updateExistingInstructions
|
|
12174
|
+
});
|
|
12175
|
+
}
|
|
12176
|
+
console.log(
|
|
12177
|
+
"\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."
|
|
12178
|
+
);
|
|
12179
|
+
}
|
|
12180
|
+
);
|
|
12310
12181
|
|
|
12311
12182
|
// src/cli/commands/skill.ts
|
|
12312
|
-
import { Command as
|
|
12313
|
-
var skillCommand = new
|
|
12183
|
+
import { Command as Command17 } from "commander";
|
|
12184
|
+
var skillCommand = new Command17("skill").description(
|
|
12314
12185
|
"Manage agent skill entries (task recipes)"
|
|
12315
12186
|
);
|
|
12316
12187
|
skillCommand.command("list").description("List all agent skills").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -12395,10 +12266,9 @@ skillCommand.command("add").description("Register a new agent skill").requiredOp
|
|
|
12395
12266
|
});
|
|
12396
12267
|
|
|
12397
12268
|
// src/cli/commands/snapshot.ts
|
|
12398
|
-
import { existsSync as existsSync22, mkdirSync as mkdirSync13, readFileSync as readFileSync13, writeFileSync as
|
|
12399
|
-
import { homedir as homedir13 } from "os";
|
|
12269
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync10 } from "fs";
|
|
12400
12270
|
import { dirname as dirname7, join as join21 } from "path";
|
|
12401
|
-
import { Command as
|
|
12271
|
+
import { Command as Command18 } from "commander";
|
|
12402
12272
|
function defaultOutName() {
|
|
12403
12273
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
|
|
12404
12274
|
return `zam-snapshot-${stamp}.sql`;
|
|
@@ -12412,12 +12282,12 @@ function summarize(tables) {
|
|
|
12412
12282
|
}
|
|
12413
12283
|
return { total, nonEmpty };
|
|
12414
12284
|
}
|
|
12415
|
-
var exportCmd = new
|
|
12285
|
+
var exportCmd = new Command18("export").description("Write a portable SQL-text snapshot of the database").option("--out <file>", "Output file (use - for stdout)").action(async (opts) => {
|
|
12416
12286
|
let db;
|
|
12417
12287
|
try {
|
|
12418
12288
|
db = await openDatabaseWithSync({ initialize: true });
|
|
12419
12289
|
const snapshot = await exportSnapshot(db);
|
|
12420
|
-
const personalDir = await
|
|
12290
|
+
const personalDir = (await ensureActiveWorkspace(db)).path;
|
|
12421
12291
|
await db.close();
|
|
12422
12292
|
db = void 0;
|
|
12423
12293
|
const manifest = verifySnapshot(snapshot);
|
|
@@ -12431,7 +12301,7 @@ var exportCmd = new Command19("export").description("Write a portable SQL-text s
|
|
|
12431
12301
|
if (dir && dir !== "." && !existsSync22(dir)) {
|
|
12432
12302
|
mkdirSync13(dir, { recursive: true });
|
|
12433
12303
|
}
|
|
12434
|
-
|
|
12304
|
+
writeFileSync10(out, snapshot, "utf-8");
|
|
12435
12305
|
console.log(`Snapshot written: ${out}`);
|
|
12436
12306
|
console.log(
|
|
12437
12307
|
` ${total} row(s)${nonEmpty.length ? ` \u2014 ${nonEmpty.join(", ")}` : ""}`
|
|
@@ -12442,7 +12312,7 @@ var exportCmd = new Command19("export").description("Write a portable SQL-text s
|
|
|
12442
12312
|
process.exit(1);
|
|
12443
12313
|
}
|
|
12444
12314
|
});
|
|
12445
|
-
var importCmd = new
|
|
12315
|
+
var importCmd = new Command18("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) => {
|
|
12446
12316
|
let db;
|
|
12447
12317
|
try {
|
|
12448
12318
|
if (!existsSync22(file)) {
|
|
@@ -12465,7 +12335,7 @@ var importCmd = new Command19("import").description("Restore a snapshot into the
|
|
|
12465
12335
|
process.exit(1);
|
|
12466
12336
|
}
|
|
12467
12337
|
});
|
|
12468
|
-
var verifyCmd = new
|
|
12338
|
+
var verifyCmd = new Command18("verify").description("Check a snapshot's manifest and checksum without importing").argument("<file>", "Snapshot file to verify").action((file) => {
|
|
12469
12339
|
try {
|
|
12470
12340
|
if (!existsSync22(file)) {
|
|
12471
12341
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
@@ -12483,11 +12353,11 @@ var verifyCmd = new Command19("verify").description("Check a snapshot's manifest
|
|
|
12483
12353
|
process.exit(1);
|
|
12484
12354
|
}
|
|
12485
12355
|
});
|
|
12486
|
-
var snapshotCommand = new
|
|
12356
|
+
var snapshotCommand = new Command18("snapshot").description("Export, import, or verify a portable database snapshot").addCommand(exportCmd).addCommand(importCmd).addCommand(verifyCmd);
|
|
12487
12357
|
|
|
12488
12358
|
// src/cli/commands/stats.ts
|
|
12489
|
-
import { Command as
|
|
12490
|
-
var statsCommand = new
|
|
12359
|
+
import { Command as Command19 } from "commander";
|
|
12360
|
+
var statsCommand = new Command19("stats").description("Show learning dashboard for a user").option("--user <id>", "User ID (default: whoami)").option("--json", "Output as JSON").action(async (opts) => {
|
|
12491
12361
|
await withDb(async (db) => {
|
|
12492
12362
|
const userId = await resolveUser(opts, db);
|
|
12493
12363
|
const stats = await getUserStats(db, userId);
|
|
@@ -12523,8 +12393,8 @@ var statsCommand = new Command20("stats").description("Show learning dashboard f
|
|
|
12523
12393
|
});
|
|
12524
12394
|
|
|
12525
12395
|
// src/cli/commands/token.ts
|
|
12526
|
-
import { Command as
|
|
12527
|
-
var tokenCommand = new
|
|
12396
|
+
import { Command as Command20 } from "commander";
|
|
12397
|
+
var tokenCommand = new Command20("token").description(
|
|
12528
12398
|
"Manage knowledge tokens"
|
|
12529
12399
|
);
|
|
12530
12400
|
tokenCommand.command("register").description("Register a new knowledge token").requiredOption("--slug <slug>", "Unique token slug").requiredOption("--concept <concept>", "Concept description").option("--domain <domain>", "Knowledge domain", "").option("--bloom <level>", "Bloom taxonomy level (1-5)", "1").option("--source-link <link>", "Source file path or reference URL", "").option("--question <question>", "Specific question prompt for recall", "").option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action(async (opts) => {
|
|
@@ -12812,10 +12682,10 @@ tokenCommand.command("status").description("Show full status of a token for a us
|
|
|
12812
12682
|
// src/cli/commands/ui.ts
|
|
12813
12683
|
import { spawn as spawn3, spawnSync } from "child_process";
|
|
12814
12684
|
import { existsSync as existsSync23 } from "fs";
|
|
12815
|
-
import { homedir as
|
|
12685
|
+
import { homedir as homedir12 } from "os";
|
|
12816
12686
|
import { dirname as dirname8, join as join22 } from "path";
|
|
12817
12687
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
12818
|
-
import { Command as
|
|
12688
|
+
import { Command as Command21 } from "commander";
|
|
12819
12689
|
var C3 = {
|
|
12820
12690
|
reset: "\x1B[0m",
|
|
12821
12691
|
red: "\x1B[31m",
|
|
@@ -12862,7 +12732,7 @@ function findInstalledApp() {
|
|
|
12862
12732
|
process.env.LOCALAPPDATA && join22(process.env.LOCALAPPDATA, "Programs", "ZAM", "ZAM.exe"),
|
|
12863
12733
|
process.env.ProgramFiles && join22(process.env.ProgramFiles, "ZAM", "ZAM.exe"),
|
|
12864
12734
|
process.env["ProgramFiles(x86)"] && join22(process.env["ProgramFiles(x86)"], "ZAM", "ZAM.exe")
|
|
12865
|
-
] : process.platform === "darwin" ? ["/Applications/ZAM.app", join22(
|
|
12735
|
+
] : process.platform === "darwin" ? ["/Applications/ZAM.app", join22(homedir12(), "Applications", "ZAM.app")] : ["/opt/ZAM/zam", "/usr/bin/zam-desktop"];
|
|
12866
12736
|
return candidates.find((candidate) => candidate && existsSync23(candidate)) || null;
|
|
12867
12737
|
}
|
|
12868
12738
|
function runNpm(args, opts) {
|
|
@@ -12995,13 +12865,13 @@ function createShortcuts(appPath, repoRoot) {
|
|
|
12995
12865
|
console.error(`${C3.red}\u2717 Could not create shortcuts.${C3.reset}`);
|
|
12996
12866
|
}
|
|
12997
12867
|
}
|
|
12998
|
-
var uiCommand = new
|
|
12868
|
+
var uiCommand = new Command21("ui").description("Launch the ZAM Desktop GUI (Active-Recall Studio)").option("--dev", "Run in hot-reload development mode (needs Rust)").option(
|
|
12999
12869
|
"--build",
|
|
13000
12870
|
"Build the native installer for your OS (needs Rust, one-time)"
|
|
13001
12871
|
).option("--shortcut", "Create Desktop + Start-menu shortcuts to the GUI").action((opts) => {
|
|
13002
12872
|
const installedApp = findInstalledApp();
|
|
13003
12873
|
if (!opts.dev && !opts.build && !opts.shortcut && installedApp) {
|
|
13004
|
-
launchApp(installedApp,
|
|
12874
|
+
launchApp(installedApp, homedir12());
|
|
13005
12875
|
return;
|
|
13006
12876
|
}
|
|
13007
12877
|
const desktopDir = findDesktopDir();
|
|
@@ -13102,8 +12972,8 @@ import { spawnSync as spawnSync2 } from "child_process";
|
|
|
13102
12972
|
import { existsSync as existsSync24, readFileSync as readFileSync14, realpathSync as realpathSync2 } from "fs";
|
|
13103
12973
|
import { dirname as dirname9, join as join23 } from "path";
|
|
13104
12974
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
13105
|
-
import { confirm as
|
|
13106
|
-
import { Command as
|
|
12975
|
+
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
12976
|
+
import { Command as Command22 } from "commander";
|
|
13107
12977
|
var GITHUB_REPO = "zam-os/zam";
|
|
13108
12978
|
var CHANNELS = [
|
|
13109
12979
|
"developer",
|
|
@@ -13131,273 +13001,647 @@ function currentVersion() {
|
|
|
13131
13001
|
} catch {
|
|
13132
13002
|
}
|
|
13133
13003
|
}
|
|
13134
|
-
return "0.0.0";
|
|
13004
|
+
return "0.0.0";
|
|
13005
|
+
}
|
|
13006
|
+
function versionAt(dir) {
|
|
13007
|
+
try {
|
|
13008
|
+
const pkg2 = JSON.parse(
|
|
13009
|
+
readFileSync14(join23(dir, "package.json"), "utf-8")
|
|
13010
|
+
);
|
|
13011
|
+
return pkg2.version ?? "unknown";
|
|
13012
|
+
} catch {
|
|
13013
|
+
return "unknown";
|
|
13014
|
+
}
|
|
13015
|
+
}
|
|
13016
|
+
async function fetchLatestVersion(repo) {
|
|
13017
|
+
const res = await fetch(
|
|
13018
|
+
`https://api.github.com/repos/${repo}/releases/latest`,
|
|
13019
|
+
{
|
|
13020
|
+
headers: {
|
|
13021
|
+
Accept: "application/vnd.github+json",
|
|
13022
|
+
"User-Agent": "zam-cli"
|
|
13023
|
+
}
|
|
13024
|
+
}
|
|
13025
|
+
);
|
|
13026
|
+
if (!res.ok) {
|
|
13027
|
+
throw new Error(
|
|
13028
|
+
`Could not reach the release server (HTTP ${res.status}). Pass --latest <version> to check offline.`
|
|
13029
|
+
);
|
|
13030
|
+
}
|
|
13031
|
+
const data = await res.json();
|
|
13032
|
+
if (!data.tag_name) throw new Error("No published release found yet.");
|
|
13033
|
+
return data.tag_name;
|
|
13034
|
+
}
|
|
13035
|
+
function render2(decision) {
|
|
13036
|
+
if (!decision.updateAvailable) {
|
|
13037
|
+
console.log(
|
|
13038
|
+
`${C4.green}\u2713${C4.reset} ZAM is up to date (${C4.cyan}${decision.currentVersion}${C4.reset}).`
|
|
13039
|
+
);
|
|
13040
|
+
return;
|
|
13041
|
+
}
|
|
13042
|
+
console.log(
|
|
13043
|
+
`${C4.yellow}\u2191${C4.reset} ${C4.bold}Update available${C4.reset}: ${C4.dim}${decision.currentVersion}${C4.reset} \u2192 ${C4.cyan}${decision.latestVersion}${C4.reset}`
|
|
13044
|
+
);
|
|
13045
|
+
console.log(` ${decision.reason}`);
|
|
13046
|
+
if (decision.action === "run-command" || decision.action === "inform") {
|
|
13047
|
+
console.log(` Run: ${C4.cyan}${decision.command}${C4.reset}`);
|
|
13048
|
+
} else if (decision.action === "self-update") {
|
|
13049
|
+
console.log(
|
|
13050
|
+
` ${C4.dim}The desktop app can apply this update for you.${C4.reset}`
|
|
13051
|
+
);
|
|
13052
|
+
}
|
|
13053
|
+
}
|
|
13054
|
+
var checkCmd = new Command22("check").description("Check whether a newer ZAM has been released").option(
|
|
13055
|
+
"--latest <version>",
|
|
13056
|
+
"Compare against this version instead of fetching"
|
|
13057
|
+
).option("--channel <channel>", "Override the detected install channel").option("--json", "Output as JSON").action(
|
|
13058
|
+
async (opts) => {
|
|
13059
|
+
try {
|
|
13060
|
+
if (opts.channel && !CHANNELS.includes(opts.channel)) {
|
|
13061
|
+
console.error(
|
|
13062
|
+
`Invalid --channel: ${opts.channel}. Use ${CHANNELS.join(", ")}.`
|
|
13063
|
+
);
|
|
13064
|
+
process.exit(1);
|
|
13065
|
+
}
|
|
13066
|
+
const current = currentVersion();
|
|
13067
|
+
const latest = opts.latest ?? await fetchLatestVersion(GITHUB_REPO);
|
|
13068
|
+
const channel = opts.channel ?? getInstallChannel();
|
|
13069
|
+
const decision = decideUpdate({
|
|
13070
|
+
currentVersion: current,
|
|
13071
|
+
latestVersion: latest,
|
|
13072
|
+
channel
|
|
13073
|
+
});
|
|
13074
|
+
if (opts.json) {
|
|
13075
|
+
console.log(JSON.stringify(decision, null, 2));
|
|
13076
|
+
return;
|
|
13077
|
+
}
|
|
13078
|
+
render2(decision);
|
|
13079
|
+
} catch (err) {
|
|
13080
|
+
console.error("Error:", err.message);
|
|
13081
|
+
process.exit(1);
|
|
13082
|
+
}
|
|
13083
|
+
}
|
|
13084
|
+
);
|
|
13085
|
+
function findSourceRepo() {
|
|
13086
|
+
let dir = realpathSync2(dirname9(fileURLToPath4(import.meta.url)));
|
|
13087
|
+
let parent = dirname9(dir);
|
|
13088
|
+
while (parent !== dir) {
|
|
13089
|
+
if (existsSync24(join23(dir, ".git"))) return dir;
|
|
13090
|
+
dir = parent;
|
|
13091
|
+
parent = dirname9(dir);
|
|
13092
|
+
}
|
|
13093
|
+
return existsSync24(join23(dir, ".git")) ? dir : null;
|
|
13094
|
+
}
|
|
13095
|
+
function runGit(cwd, args, capture) {
|
|
13096
|
+
const res = spawnSync2("git", args, {
|
|
13097
|
+
cwd,
|
|
13098
|
+
stdio: capture ? ["ignore", "pipe", "pipe"] : "inherit",
|
|
13099
|
+
encoding: "utf8"
|
|
13100
|
+
});
|
|
13101
|
+
return { ok: res.status === 0, out: (res.stdout ?? "").trim() };
|
|
13102
|
+
}
|
|
13103
|
+
function runNpm2(args, cwd) {
|
|
13104
|
+
const res = spawnSync2("npm", args, {
|
|
13105
|
+
cwd,
|
|
13106
|
+
stdio: "inherit",
|
|
13107
|
+
shell: process.platform === "win32"
|
|
13108
|
+
});
|
|
13109
|
+
return res.status ?? 1;
|
|
13110
|
+
}
|
|
13111
|
+
function runShell(command) {
|
|
13112
|
+
const res = spawnSync2(command, { stdio: "inherit", shell: true });
|
|
13113
|
+
return res.status === 0;
|
|
13114
|
+
}
|
|
13115
|
+
function applyDeveloperUpdate(force) {
|
|
13116
|
+
if (!hasCommand("git")) {
|
|
13117
|
+
console.error(`${C4.red}\u2717${C4.reset} git was not found on PATH.`);
|
|
13118
|
+
process.exit(1);
|
|
13119
|
+
}
|
|
13120
|
+
const src = findSourceRepo();
|
|
13121
|
+
if (!src) {
|
|
13122
|
+
console.error(
|
|
13123
|
+
`${C4.red}\u2717${C4.reset} Could not locate the ZAM source checkout to update.`
|
|
13124
|
+
);
|
|
13125
|
+
process.exit(1);
|
|
13126
|
+
}
|
|
13127
|
+
const status = runGit(src, ["status", "--porcelain"], true);
|
|
13128
|
+
if (status.ok && status.out && !force) {
|
|
13129
|
+
console.error(
|
|
13130
|
+
`${C4.red}\u2717${C4.reset} The source checkout has uncommitted changes:
|
|
13131
|
+
${status.out}
|
|
13132
|
+
Commit or stash them, or re-run with ${C4.cyan}--force${C4.reset}.`
|
|
13133
|
+
);
|
|
13134
|
+
process.exit(1);
|
|
13135
|
+
}
|
|
13136
|
+
console.log(
|
|
13137
|
+
`${C4.dim}\u2192 git pull --ff-only${C4.reset} ${C4.dim}(${src})${C4.reset}`
|
|
13138
|
+
);
|
|
13139
|
+
if (!runGit(src, ["pull", "--ff-only"], false).ok) {
|
|
13140
|
+
console.error(
|
|
13141
|
+
`${C4.red}\u2717${C4.reset} git pull failed \u2014 the branch may have diverged or you are offline. Resolve it manually, then retry.`
|
|
13142
|
+
);
|
|
13143
|
+
process.exit(1);
|
|
13144
|
+
}
|
|
13145
|
+
console.log(`${C4.dim}\u2192 npm install${C4.reset}`);
|
|
13146
|
+
if (runNpm2(["install"], src) !== 0) {
|
|
13147
|
+
console.error(`${C4.red}\u2717${C4.reset} npm install failed.`);
|
|
13148
|
+
process.exit(1);
|
|
13149
|
+
}
|
|
13150
|
+
console.log(`${C4.dim}\u2192 npm run build${C4.reset}`);
|
|
13151
|
+
if (runNpm2(["run", "build"], src) !== 0) {
|
|
13152
|
+
console.error(`${C4.red}\u2717${C4.reset} Build failed.`);
|
|
13153
|
+
process.exit(1);
|
|
13154
|
+
}
|
|
13155
|
+
console.log(`${C4.dim}\u2192 zam setup --force${C4.reset}`);
|
|
13156
|
+
const setup = spawnSync2(
|
|
13157
|
+
process.execPath,
|
|
13158
|
+
[join23(src, "dist", "cli", "index.js"), "setup", "--force"],
|
|
13159
|
+
{ cwd: process.cwd(), stdio: "inherit" }
|
|
13160
|
+
);
|
|
13161
|
+
if (setup.status !== 0) {
|
|
13162
|
+
console.warn(
|
|
13163
|
+
`${C4.yellow}\u26A0${C4.reset} Skill refresh reported a problem \u2014 run '${C4.cyan}zam setup --force${C4.reset}' here manually.`
|
|
13164
|
+
);
|
|
13165
|
+
}
|
|
13166
|
+
console.log(
|
|
13167
|
+
`
|
|
13168
|
+
${C4.green}\u2713${C4.reset} Updated to ${C4.cyan}${versionAt(src)}${C4.reset}. Restart your agent client (e.g. Claude Code) to load the refreshed ${C4.cyan}/zam${C4.reset} skill.`
|
|
13169
|
+
);
|
|
13170
|
+
}
|
|
13171
|
+
async function applyUpdate(opts) {
|
|
13172
|
+
try {
|
|
13173
|
+
const current = currentVersion();
|
|
13174
|
+
const latest = await fetchLatestVersion(GITHUB_REPO);
|
|
13175
|
+
const channel = getInstallChannel();
|
|
13176
|
+
const decision = decideUpdate({
|
|
13177
|
+
currentVersion: current,
|
|
13178
|
+
latestVersion: latest,
|
|
13179
|
+
channel
|
|
13180
|
+
});
|
|
13181
|
+
if (!decision.updateAvailable) {
|
|
13182
|
+
console.log(
|
|
13183
|
+
`${C4.green}\u2713${C4.reset} ZAM is up to date (${C4.cyan}${current}${C4.reset}).`
|
|
13184
|
+
);
|
|
13185
|
+
return;
|
|
13186
|
+
}
|
|
13187
|
+
console.log(
|
|
13188
|
+
`${C4.yellow}\u2191${C4.reset} ${C4.bold}Update available${C4.reset}: ${C4.dim}${current}${C4.reset} \u2192 ${C4.cyan}${latest}${C4.reset} ${C4.dim}(${channel} install)${C4.reset}`
|
|
13189
|
+
);
|
|
13190
|
+
for (const step of planUpdate(decision)) {
|
|
13191
|
+
console.log(` ${C4.dim}\u2022${C4.reset} ${step.label}`);
|
|
13192
|
+
}
|
|
13193
|
+
if (channel === "direct") {
|
|
13194
|
+
console.log(
|
|
13195
|
+
`
|
|
13196
|
+
${C4.dim}This install updates through the desktop app's signed updater \u2014 open ZAM Desktop to install ${latest}.${C4.reset}`
|
|
13197
|
+
);
|
|
13198
|
+
return;
|
|
13199
|
+
}
|
|
13200
|
+
if (!opts.yes) {
|
|
13201
|
+
const ok = await confirm3({
|
|
13202
|
+
message: "Apply this update?",
|
|
13203
|
+
default: true
|
|
13204
|
+
});
|
|
13205
|
+
if (!ok) {
|
|
13206
|
+
console.log("Aborted.");
|
|
13207
|
+
return;
|
|
13208
|
+
}
|
|
13209
|
+
}
|
|
13210
|
+
console.log();
|
|
13211
|
+
if (channel === "winget" || channel === "homebrew") {
|
|
13212
|
+
if (!decision.command || !runShell(decision.command)) process.exit(1);
|
|
13213
|
+
return;
|
|
13214
|
+
}
|
|
13215
|
+
applyDeveloperUpdate(opts.force ?? false);
|
|
13216
|
+
} catch (err) {
|
|
13217
|
+
console.error("Error:", err.message);
|
|
13218
|
+
process.exit(1);
|
|
13219
|
+
}
|
|
13220
|
+
}
|
|
13221
|
+
var updateCommand = new Command22("update").description(
|
|
13222
|
+
"Update ZAM to the latest release (use `update check` to only check)"
|
|
13223
|
+
).option("-y, --yes", "Apply without confirmation").option(
|
|
13224
|
+
"--force",
|
|
13225
|
+
"Update even if the source checkout has uncommitted changes"
|
|
13226
|
+
).action(async (opts) => {
|
|
13227
|
+
await applyUpdate(opts);
|
|
13228
|
+
}).addCommand(checkCmd);
|
|
13229
|
+
|
|
13230
|
+
// src/cli/commands/whoami.ts
|
|
13231
|
+
import { Command as Command23 } from "commander";
|
|
13232
|
+
var whoamiCommand = new Command23("whoami").description("Show or set the default user identity").option("--set <id>", "Set the default user ID").option("--clear", "Remove the default user ID").option("--json", "Output as JSON").action(async (opts) => {
|
|
13233
|
+
await withDb(async (db) => {
|
|
13234
|
+
if (opts.set) {
|
|
13235
|
+
await setSetting(db, "user.id", opts.set);
|
|
13236
|
+
if (opts.json) {
|
|
13237
|
+
console.log(JSON.stringify({ userId: opts.set }));
|
|
13238
|
+
} else {
|
|
13239
|
+
console.log(`Default user set to: ${opts.set}`);
|
|
13240
|
+
}
|
|
13241
|
+
return;
|
|
13242
|
+
}
|
|
13243
|
+
if (opts.clear) {
|
|
13244
|
+
const deleted = await deleteSetting(db, "user.id");
|
|
13245
|
+
if (opts.json) {
|
|
13246
|
+
console.log(JSON.stringify({ userId: null, cleared: deleted }));
|
|
13247
|
+
} else if (deleted) {
|
|
13248
|
+
console.log("Default user cleared.");
|
|
13249
|
+
} else {
|
|
13250
|
+
console.log("No default user was set.");
|
|
13251
|
+
}
|
|
13252
|
+
return;
|
|
13253
|
+
}
|
|
13254
|
+
const userId = await getSetting(db, "user.id");
|
|
13255
|
+
if (opts.json) {
|
|
13256
|
+
console.log(JSON.stringify({ userId: userId ?? null }));
|
|
13257
|
+
return;
|
|
13258
|
+
}
|
|
13259
|
+
if (userId) {
|
|
13260
|
+
console.log(userId);
|
|
13261
|
+
} else {
|
|
13262
|
+
console.log("No default user set. Use: zam whoami --set <id>");
|
|
13263
|
+
}
|
|
13264
|
+
});
|
|
13265
|
+
});
|
|
13266
|
+
|
|
13267
|
+
// src/cli/commands/workspace.ts
|
|
13268
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
13269
|
+
import { existsSync as existsSync25, writeFileSync as writeFileSync11 } from "fs";
|
|
13270
|
+
import { homedir as homedir13 } from "os";
|
|
13271
|
+
import { join as join24, resolve as resolve9 } from "path";
|
|
13272
|
+
import { confirm as confirm4, input as input7 } from "@inquirer/prompts";
|
|
13273
|
+
import { Command as Command24 } from "commander";
|
|
13274
|
+
function runGit2(cwd, args) {
|
|
13275
|
+
try {
|
|
13276
|
+
return execFileSync4("git", args, {
|
|
13277
|
+
cwd,
|
|
13278
|
+
stdio: "pipe",
|
|
13279
|
+
encoding: "utf8"
|
|
13280
|
+
}).trim();
|
|
13281
|
+
} catch (err) {
|
|
13282
|
+
throw new Error(`Git command failed: ${err.message}`);
|
|
13283
|
+
}
|
|
13135
13284
|
}
|
|
13136
|
-
function
|
|
13137
|
-
|
|
13138
|
-
|
|
13139
|
-
|
|
13140
|
-
|
|
13141
|
-
|
|
13142
|
-
|
|
13143
|
-
|
|
13285
|
+
function ghRepoCreateArgs(repoName, repoVisibility) {
|
|
13286
|
+
return ["repo", "create", repoName, repoVisibility, "--source=.", "--push"];
|
|
13287
|
+
}
|
|
13288
|
+
function gitRemoteArgs(githubUrl, hasOrigin) {
|
|
13289
|
+
return hasOrigin ? ["remote", "set-url", "origin", githubUrl] : ["remote", "add", "origin", githubUrl];
|
|
13290
|
+
}
|
|
13291
|
+
var workspaceCommand = new Command24("workspace").description(
|
|
13292
|
+
"Manage your ZAM learning workspace"
|
|
13293
|
+
);
|
|
13294
|
+
var WORKSPACE_KINDS = [
|
|
13295
|
+
"personal",
|
|
13296
|
+
"team",
|
|
13297
|
+
"family",
|
|
13298
|
+
"community",
|
|
13299
|
+
"organization",
|
|
13300
|
+
"custom"
|
|
13301
|
+
];
|
|
13302
|
+
var WORKSPACE_SOURCE_CONTROLS = [
|
|
13303
|
+
"github",
|
|
13304
|
+
"azure-devops",
|
|
13305
|
+
"git",
|
|
13306
|
+
"none"
|
|
13307
|
+
];
|
|
13308
|
+
function parseWorkspaceKind(value) {
|
|
13309
|
+
const kind = (value ?? "custom").toLowerCase();
|
|
13310
|
+
if (WORKSPACE_KINDS.includes(kind)) {
|
|
13311
|
+
return kind;
|
|
13144
13312
|
}
|
|
13313
|
+
throw new Error(
|
|
13314
|
+
`Invalid workspace kind: ${value}. Use ${WORKSPACE_KINDS.join(", ")}.`
|
|
13315
|
+
);
|
|
13145
13316
|
}
|
|
13146
|
-
|
|
13147
|
-
|
|
13148
|
-
|
|
13149
|
-
|
|
13150
|
-
|
|
13151
|
-
|
|
13152
|
-
|
|
13153
|
-
|
|
13154
|
-
}
|
|
13317
|
+
function parseWorkspaceSourceControl(value) {
|
|
13318
|
+
if (!value) return void 0;
|
|
13319
|
+
const source = value.toLowerCase();
|
|
13320
|
+
if (WORKSPACE_SOURCE_CONTROLS.includes(source)) {
|
|
13321
|
+
return source;
|
|
13322
|
+
}
|
|
13323
|
+
throw new Error(
|
|
13324
|
+
`Invalid source control: ${value}. Use ${WORKSPACE_SOURCE_CONTROLS.join(", ")}.`
|
|
13155
13325
|
);
|
|
13156
|
-
|
|
13326
|
+
}
|
|
13327
|
+
function parseScopes(value) {
|
|
13328
|
+
if (!value) return void 0;
|
|
13329
|
+
const scopes = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
13330
|
+
return scopes.length > 0 ? scopes : void 0;
|
|
13331
|
+
}
|
|
13332
|
+
function requireWorkspace(id) {
|
|
13333
|
+
const workspace = getConfiguredWorkspaces().find((item) => item.id === id);
|
|
13334
|
+
if (!workspace) {
|
|
13157
13335
|
throw new Error(
|
|
13158
|
-
`
|
|
13336
|
+
`Workspace "${id}" is not configured. Add it with: zam workspace add ${id} --path <dir>`
|
|
13159
13337
|
);
|
|
13160
13338
|
}
|
|
13161
|
-
|
|
13162
|
-
if (!data.tag_name) throw new Error("No published release found yet.");
|
|
13163
|
-
return data.tag_name;
|
|
13339
|
+
return workspace;
|
|
13164
13340
|
}
|
|
13165
|
-
function
|
|
13166
|
-
|
|
13167
|
-
|
|
13168
|
-
|
|
13169
|
-
|
|
13170
|
-
|
|
13341
|
+
function formatLinkHealth(id, health) {
|
|
13342
|
+
switch (health) {
|
|
13343
|
+
case "healthy":
|
|
13344
|
+
return "ok";
|
|
13345
|
+
case "needs-repair":
|
|
13346
|
+
return `needs repair \u2014 run: zam workspace setup ${id}`;
|
|
13347
|
+
case "unmanaged":
|
|
13348
|
+
return `unmanaged skill dir \u2014 run: zam workspace setup ${id} --force`;
|
|
13171
13349
|
}
|
|
13172
|
-
|
|
13173
|
-
|
|
13350
|
+
}
|
|
13351
|
+
workspaceCommand.command("list").description("List configured ZAM workspaces").option("--json", "Output as JSON").action((opts) => {
|
|
13352
|
+
const workspaces = getConfiguredWorkspaces();
|
|
13353
|
+
const agents = parseSetupAgents();
|
|
13354
|
+
const linkHealth = Object.fromEntries(
|
|
13355
|
+
workspaces.filter((workspace) => existsSync25(workspace.path)).map((workspace) => [
|
|
13356
|
+
workspace.id,
|
|
13357
|
+
summarizeSkillLinkHealth(inspectSkillLinks(workspace.path, agents))
|
|
13358
|
+
])
|
|
13174
13359
|
);
|
|
13175
|
-
|
|
13176
|
-
|
|
13177
|
-
|
|
13178
|
-
}
|
|
13360
|
+
if (opts.json) {
|
|
13361
|
+
console.log(JSON.stringify({ workspaces, linkHealth }, null, 2));
|
|
13362
|
+
return;
|
|
13363
|
+
}
|
|
13364
|
+
console.log("Configured ZAM workspaces:\n");
|
|
13365
|
+
if (workspaces.length === 0) {
|
|
13179
13366
|
console.log(
|
|
13180
|
-
|
|
13367
|
+
" (none) \u2014 add one: zam workspace add personal --path <dir>"
|
|
13181
13368
|
);
|
|
13369
|
+
return;
|
|
13182
13370
|
}
|
|
13183
|
-
|
|
13184
|
-
|
|
13185
|
-
|
|
13186
|
-
|
|
13187
|
-
|
|
13188
|
-
|
|
13189
|
-
|
|
13190
|
-
|
|
13191
|
-
|
|
13192
|
-
|
|
13193
|
-
|
|
13194
|
-
|
|
13195
|
-
|
|
13196
|
-
|
|
13197
|
-
|
|
13198
|
-
|
|
13199
|
-
|
|
13200
|
-
|
|
13201
|
-
|
|
13202
|
-
|
|
13203
|
-
|
|
13204
|
-
|
|
13205
|
-
|
|
13206
|
-
|
|
13207
|
-
|
|
13208
|
-
|
|
13209
|
-
|
|
13210
|
-
|
|
13371
|
+
for (const workspace of workspaces) {
|
|
13372
|
+
const label = workspace.label ? ` (${workspace.label})` : "";
|
|
13373
|
+
console.log(` ${workspace.id}${label}`);
|
|
13374
|
+
console.log(` kind: ${workspace.kind}`);
|
|
13375
|
+
console.log(` path: ${workspace.path}`);
|
|
13376
|
+
if (workspace.sourceControl) {
|
|
13377
|
+
console.log(` source: ${workspace.sourceControl}`);
|
|
13378
|
+
}
|
|
13379
|
+
if (workspace.knowledgeScopes?.length) {
|
|
13380
|
+
console.log(` scopes: ${workspace.knowledgeScopes.join(", ")}`);
|
|
13381
|
+
}
|
|
13382
|
+
const health = linkHealth[workspace.id];
|
|
13383
|
+
if (health) {
|
|
13384
|
+
console.log(` links: ${formatLinkHealth(workspace.id, health)}`);
|
|
13385
|
+
}
|
|
13386
|
+
}
|
|
13387
|
+
});
|
|
13388
|
+
workspaceCommand.command("add <id>").description("Register an existing directory as a ZAM workspace").requiredOption("--path <dir>", "Existing workspace/repository directory").option(
|
|
13389
|
+
"--kind <kind>",
|
|
13390
|
+
`Workspace kind (${WORKSPACE_KINDS.join(" | ")})`,
|
|
13391
|
+
"custom"
|
|
13392
|
+
).option("--label <label>", "Human-readable label").option(
|
|
13393
|
+
"--source-control <provider>",
|
|
13394
|
+
`Source-control provider (${WORKSPACE_SOURCE_CONTROLS.join(" | ")})`
|
|
13395
|
+
).option("--scopes <list>", "Comma-separated knowledge scopes").option("--default-agent <id>", "Default agent harness for this workspace").action((id, opts) => {
|
|
13396
|
+
try {
|
|
13397
|
+
const path = resolve9(String(opts.path));
|
|
13398
|
+
if (!existsSync25(path)) {
|
|
13399
|
+
console.error(`Workspace path does not exist: ${path}`);
|
|
13211
13400
|
process.exit(1);
|
|
13212
13401
|
}
|
|
13402
|
+
const workspace = {
|
|
13403
|
+
id,
|
|
13404
|
+
kind: parseWorkspaceKind(opts.kind),
|
|
13405
|
+
path,
|
|
13406
|
+
...opts.label ? { label: opts.label } : {},
|
|
13407
|
+
...opts.sourceControl ? { sourceControl: parseWorkspaceSourceControl(opts.sourceControl) } : {},
|
|
13408
|
+
...parseScopes(opts.scopes) ? { knowledgeScopes: parseScopes(opts.scopes) } : {},
|
|
13409
|
+
...opts.defaultAgent ? { defaultAgent: opts.defaultAgent } : {}
|
|
13410
|
+
};
|
|
13411
|
+
wireSkills(path, parseSetupAgents());
|
|
13412
|
+
upsertConfiguredWorkspace(workspace);
|
|
13413
|
+
console.log(`Registered and linked workspace "${id}" at ${path}.`);
|
|
13414
|
+
} catch (err) {
|
|
13415
|
+
console.error(`Error: ${err.message}`);
|
|
13416
|
+
process.exit(1);
|
|
13213
13417
|
}
|
|
13214
|
-
);
|
|
13215
|
-
|
|
13216
|
-
|
|
13217
|
-
|
|
13218
|
-
|
|
13219
|
-
|
|
13220
|
-
dir = parent;
|
|
13221
|
-
parent = dirname9(dir);
|
|
13418
|
+
});
|
|
13419
|
+
workspaceCommand.command("remove <id>").description("Unregister a ZAM workspace without deleting its files").action((id) => {
|
|
13420
|
+
const existing = getConfiguredWorkspaces().find((item) => item.id === id);
|
|
13421
|
+
if (!existing) {
|
|
13422
|
+
console.error(`Workspace "${id}" is not configured.`);
|
|
13423
|
+
process.exit(1);
|
|
13222
13424
|
}
|
|
13223
|
-
|
|
13224
|
-
|
|
13225
|
-
|
|
13226
|
-
|
|
13227
|
-
|
|
13228
|
-
|
|
13229
|
-
|
|
13230
|
-
|
|
13231
|
-
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
|
|
13237
|
-
|
|
13238
|
-
|
|
13239
|
-
|
|
13240
|
-
|
|
13241
|
-
|
|
13242
|
-
|
|
13243
|
-
|
|
13244
|
-
}
|
|
13245
|
-
|
|
13246
|
-
|
|
13247
|
-
|
|
13425
|
+
removeConfiguredWorkspace(id);
|
|
13426
|
+
console.log(
|
|
13427
|
+
`Unregistered workspace "${id}". Files in ${existing.path} were not changed.`
|
|
13428
|
+
);
|
|
13429
|
+
});
|
|
13430
|
+
workspaceCommand.command("setup <id>").description("Install ZAM skills into a configured workspace").option(
|
|
13431
|
+
"--agents <list>",
|
|
13432
|
+
"comma-separated agents to wire: all, claude, copilot, codex, agent"
|
|
13433
|
+
).option(
|
|
13434
|
+
"--force",
|
|
13435
|
+
"overwrite existing skill files and refresh ZAM blocks",
|
|
13436
|
+
false
|
|
13437
|
+
).option(
|
|
13438
|
+
"--dry-run",
|
|
13439
|
+
"show what would be written without changing files",
|
|
13440
|
+
false
|
|
13441
|
+
).action((id, opts) => {
|
|
13442
|
+
try {
|
|
13443
|
+
const workspace = requireWorkspace(id);
|
|
13444
|
+
const agents = parseSetupAgents(opts.agents);
|
|
13445
|
+
console.log(
|
|
13446
|
+
`Setting up workspace "${workspace.id}" in ${workspace.path}${opts.dryRun ? " (dry run)" : ""}
|
|
13447
|
+
`
|
|
13448
|
+
);
|
|
13449
|
+
wireSkills(workspace.path, agents, {
|
|
13450
|
+
force: Boolean(opts.force),
|
|
13451
|
+
dryRun: Boolean(opts.dryRun)
|
|
13452
|
+
});
|
|
13453
|
+
if (agents.has("claude")) {
|
|
13454
|
+
writeClaudeMd(false, workspace.path, {
|
|
13455
|
+
dryRun: Boolean(opts.dryRun),
|
|
13456
|
+
updateExisting: true
|
|
13457
|
+
});
|
|
13458
|
+
}
|
|
13459
|
+
if (agents.has("codex") || agents.has("agent")) {
|
|
13460
|
+
writeAgentsMd(false, workspace.path, {
|
|
13461
|
+
dryRun: Boolean(opts.dryRun),
|
|
13462
|
+
updateExisting: true
|
|
13463
|
+
});
|
|
13464
|
+
}
|
|
13465
|
+
if (agents.has("copilot")) {
|
|
13466
|
+
writeCopilotInstructions(workspace.path, {
|
|
13467
|
+
dryRun: Boolean(opts.dryRun),
|
|
13468
|
+
updateExisting: true
|
|
13469
|
+
});
|
|
13470
|
+
}
|
|
13471
|
+
} catch (err) {
|
|
13472
|
+
console.error(`Error: ${err.message}`);
|
|
13248
13473
|
process.exit(1);
|
|
13249
13474
|
}
|
|
13250
|
-
|
|
13251
|
-
|
|
13475
|
+
});
|
|
13476
|
+
workspaceCommand.command("publish").description("Publish your local workspace sandbox to GitHub").action(async () => {
|
|
13477
|
+
let db;
|
|
13478
|
+
let workspaceDir = "";
|
|
13479
|
+
try {
|
|
13480
|
+
db = await openDatabase();
|
|
13481
|
+
workspaceDir = (await ensureActiveWorkspace(db)).path;
|
|
13482
|
+
await db.close();
|
|
13483
|
+
} catch {
|
|
13484
|
+
await db?.close();
|
|
13485
|
+
}
|
|
13486
|
+
if (!workspaceDir) {
|
|
13252
13487
|
console.error(
|
|
13253
|
-
|
|
13488
|
+
"\x1B[31m\u2717 No active workspace configured. Please run `zam init` first.\x1B[0m"
|
|
13254
13489
|
);
|
|
13255
13490
|
process.exit(1);
|
|
13256
13491
|
}
|
|
13257
|
-
|
|
13258
|
-
if (status.ok && status.out && !force) {
|
|
13492
|
+
if (!existsSync25(workspaceDir)) {
|
|
13259
13493
|
console.error(
|
|
13260
|
-
|
|
13261
|
-
${status.out}
|
|
13262
|
-
Commit or stash them, or re-run with ${C4.cyan}--force${C4.reset}.`
|
|
13494
|
+
`\x1B[31m\u2717 Workspace directory does not exist: ${workspaceDir}\x1B[0m`
|
|
13263
13495
|
);
|
|
13264
13496
|
process.exit(1);
|
|
13265
13497
|
}
|
|
13266
|
-
console.log(
|
|
13267
|
-
|
|
13268
|
-
)
|
|
13269
|
-
if (!runGit2(src, ["pull", "--ff-only"], false).ok) {
|
|
13498
|
+
console.log(`
|
|
13499
|
+
Active workspace: \x1B[36m${workspaceDir}\x1B[0m`);
|
|
13500
|
+
if (!hasCommand("git")) {
|
|
13270
13501
|
console.error(
|
|
13271
|
-
|
|
13502
|
+
"\x1B[31m\u2717 Git command was not found on this system. Please install Git first.\x1B[0m"
|
|
13272
13503
|
);
|
|
13273
13504
|
process.exit(1);
|
|
13274
13505
|
}
|
|
13275
|
-
|
|
13276
|
-
if (
|
|
13277
|
-
|
|
13278
|
-
|
|
13506
|
+
const gitignorePath = join24(workspaceDir, ".gitignore");
|
|
13507
|
+
if (!existsSync25(gitignorePath)) {
|
|
13508
|
+
writeFileSync11(
|
|
13509
|
+
gitignorePath,
|
|
13510
|
+
"node_modules/\n.agent/\n.agents/\n.claude/\n.gemini/\n.goose/\n*.log\n",
|
|
13511
|
+
"utf8"
|
|
13512
|
+
);
|
|
13279
13513
|
}
|
|
13280
|
-
|
|
13281
|
-
if (
|
|
13282
|
-
console.
|
|
13283
|
-
|
|
13514
|
+
const hasGitRepo = existsSync25(join24(workspaceDir, ".git"));
|
|
13515
|
+
if (!hasGitRepo) {
|
|
13516
|
+
console.log("Initializing local Git repository...");
|
|
13517
|
+
runGit2(workspaceDir, ["init", "-b", "main"]);
|
|
13518
|
+
runGit2(workspaceDir, ["add", "."]);
|
|
13519
|
+
runGit2(workspaceDir, [
|
|
13520
|
+
"commit",
|
|
13521
|
+
"-m",
|
|
13522
|
+
"chore: initial workspace sandbox bootstrap"
|
|
13523
|
+
]);
|
|
13524
|
+
console.log("\x1B[32m\u2713 Local Git repository initialized.\x1B[0m");
|
|
13525
|
+
} else {
|
|
13526
|
+
console.log("Git repository is already initialized.");
|
|
13284
13527
|
}
|
|
13285
|
-
|
|
13286
|
-
|
|
13287
|
-
|
|
13288
|
-
|
|
13289
|
-
|
|
13290
|
-
|
|
13291
|
-
|
|
13292
|
-
|
|
13293
|
-
|
|
13294
|
-
|
|
13528
|
+
const repoName = await input7({
|
|
13529
|
+
message: "Choose a name for your GitHub repository:",
|
|
13530
|
+
default: "zam-personal"
|
|
13531
|
+
});
|
|
13532
|
+
const isPrivate = await confirm4({
|
|
13533
|
+
message: "Should the repository be private?",
|
|
13534
|
+
default: true
|
|
13535
|
+
});
|
|
13536
|
+
const repoVisibility = isPrivate ? "--private" : "--public";
|
|
13537
|
+
if (hasCommand("gh")) {
|
|
13538
|
+
console.log("GitHub CLI detected! Automating repository creation...");
|
|
13539
|
+
const proceedGh = await confirm4({
|
|
13540
|
+
message: "Would you like ZAM to create the repository using the GitHub CLI?",
|
|
13541
|
+
default: true
|
|
13542
|
+
});
|
|
13543
|
+
if (proceedGh) {
|
|
13544
|
+
try {
|
|
13545
|
+
console.log(`Creating GitHub repository ${repoName}...`);
|
|
13546
|
+
execFileSync4("gh", ghRepoCreateArgs(repoName, repoVisibility), {
|
|
13547
|
+
cwd: workspaceDir,
|
|
13548
|
+
stdio: "inherit"
|
|
13549
|
+
});
|
|
13550
|
+
console.log(
|
|
13551
|
+
"\n\x1B[32m\u2713 Successfully published workspace to GitHub!\x1B[0m"
|
|
13552
|
+
);
|
|
13553
|
+
process.exit(0);
|
|
13554
|
+
} catch (err) {
|
|
13555
|
+
console.warn(
|
|
13556
|
+
`\x1B[33m\u26A0 GitHub CLI creation failed: ${err.message}\x1B[0m`
|
|
13557
|
+
);
|
|
13558
|
+
}
|
|
13559
|
+
}
|
|
13295
13560
|
}
|
|
13296
13561
|
console.log(
|
|
13297
|
-
|
|
13298
|
-
${C4.green}\u2713${C4.reset} Updated to ${C4.cyan}${versionAt(src)}${C4.reset}. Restart your agent client (e.g. Claude Code) to load the refreshed ${C4.cyan}/zam${C4.reset} skill.`
|
|
13562
|
+
"\n\x1B[1mPlease create the repository manually on GitHub:\x1B[0m"
|
|
13299
13563
|
);
|
|
13300
|
-
|
|
13301
|
-
|
|
13302
|
-
|
|
13303
|
-
|
|
13304
|
-
|
|
13305
|
-
|
|
13306
|
-
|
|
13307
|
-
|
|
13308
|
-
|
|
13309
|
-
|
|
13310
|
-
|
|
13311
|
-
|
|
13564
|
+
console.log(" 1. Go to https://github.com/new");
|
|
13565
|
+
console.log(` 2. Name it exactly: \x1B[36m${repoName}\x1B[0m`);
|
|
13566
|
+
console.log(
|
|
13567
|
+
` 3. Choose \x1B[36m${isPrivate ? "Private" : "Public"}\x1B[0m`
|
|
13568
|
+
);
|
|
13569
|
+
console.log(
|
|
13570
|
+
" 4. Do NOT initialize it with README, .gitignore, or license"
|
|
13571
|
+
);
|
|
13572
|
+
console.log(" 5. Click 'Create repository'\n");
|
|
13573
|
+
const githubUrl = await input7({
|
|
13574
|
+
message: "Paste the repository Git URL (e.g. git@github.com:user/repo.git):"
|
|
13575
|
+
});
|
|
13576
|
+
if (githubUrl) {
|
|
13577
|
+
try {
|
|
13578
|
+
console.log("Linking remote repository and pushing...");
|
|
13579
|
+
let hasOrigin = false;
|
|
13580
|
+
try {
|
|
13581
|
+
runGit2(workspaceDir, ["remote", "get-url", "origin"]);
|
|
13582
|
+
hasOrigin = true;
|
|
13583
|
+
} catch {
|
|
13584
|
+
}
|
|
13585
|
+
runGit2(workspaceDir, gitRemoteArgs(githubUrl, hasOrigin));
|
|
13586
|
+
runGit2(workspaceDir, ["push", "-u", "origin", "main"]);
|
|
13312
13587
|
console.log(
|
|
13313
|
-
|
|
13588
|
+
"\x1B[32m\u2713 Successfully linked and pushed to GitHub!\x1B[0m"
|
|
13589
|
+
);
|
|
13590
|
+
} catch (err) {
|
|
13591
|
+
console.error(
|
|
13592
|
+
`\x1B[31m\u2717 Push failed: ${err.message}\x1B[0m`
|
|
13314
13593
|
);
|
|
13315
|
-
return;
|
|
13316
|
-
}
|
|
13317
|
-
console.log(
|
|
13318
|
-
`${C4.yellow}\u2191${C4.reset} ${C4.bold}Update available${C4.reset}: ${C4.dim}${current}${C4.reset} \u2192 ${C4.cyan}${latest}${C4.reset} ${C4.dim}(${channel} install)${C4.reset}`
|
|
13319
|
-
);
|
|
13320
|
-
for (const step of planUpdate(decision)) {
|
|
13321
|
-
console.log(` ${C4.dim}\u2022${C4.reset} ${step.label}`);
|
|
13322
|
-
}
|
|
13323
|
-
if (channel === "direct") {
|
|
13324
13594
|
console.log(
|
|
13325
|
-
|
|
13326
|
-
${C4.dim}This install updates through the desktop app's signed updater \u2014 open ZAM Desktop to install ${latest}.${C4.reset}`
|
|
13595
|
+
"You can push manually later using: git push -u origin main"
|
|
13327
13596
|
);
|
|
13328
|
-
return;
|
|
13329
|
-
}
|
|
13330
|
-
if (!opts.yes) {
|
|
13331
|
-
const ok = await confirm4({
|
|
13332
|
-
message: "Apply this update?",
|
|
13333
|
-
default: true
|
|
13334
|
-
});
|
|
13335
|
-
if (!ok) {
|
|
13336
|
-
console.log("Aborted.");
|
|
13337
|
-
return;
|
|
13338
|
-
}
|
|
13339
13597
|
}
|
|
13340
|
-
|
|
13341
|
-
|
|
13342
|
-
|
|
13598
|
+
}
|
|
13599
|
+
});
|
|
13600
|
+
workspaceCommand.command("data-dir").description("Print the ZAM data directory (database, credentials, config)").option("--json", "Output as JSON").action((opts) => {
|
|
13601
|
+
const dir = join24(homedir13(), ".zam");
|
|
13602
|
+
console.log(opts.json ? JSON.stringify({ dataDir: dir }) : dir);
|
|
13603
|
+
});
|
|
13604
|
+
workspaceCommand.command("backup").description("Back up the local ZAM database into your workspace").option(
|
|
13605
|
+
"--dir <path>",
|
|
13606
|
+
"Target directory (default: workspace dir, else ~/Documents/zam)"
|
|
13607
|
+
).option("--json", "Output as JSON").action(async (opts) => {
|
|
13608
|
+
const target = getDatabaseTargetInfo();
|
|
13609
|
+
if (target.kind !== "local") {
|
|
13610
|
+
const reason = `The database is ${target.kind} (${target.location}); file backup applies only to a local database \u2014 your Turso remote is already the cloud backup.`;
|
|
13611
|
+
if (opts.json) {
|
|
13612
|
+
console.log(JSON.stringify({ ok: false, reason }));
|
|
13343
13613
|
return;
|
|
13344
13614
|
}
|
|
13345
|
-
|
|
13346
|
-
} catch (err) {
|
|
13347
|
-
console.error("Error:", err.message);
|
|
13615
|
+
console.error(`\x1B[33m\u26A0 ${reason}\x1B[0m`);
|
|
13348
13616
|
process.exit(1);
|
|
13349
13617
|
}
|
|
13350
|
-
|
|
13351
|
-
|
|
13352
|
-
|
|
13353
|
-
|
|
13354
|
-
|
|
13355
|
-
"Update even if the source checkout has uncommitted changes"
|
|
13356
|
-
).action(async (opts) => {
|
|
13357
|
-
await applyUpdate(opts);
|
|
13358
|
-
}).addCommand(checkCmd);
|
|
13359
|
-
|
|
13360
|
-
// src/cli/commands/whoami.ts
|
|
13361
|
-
import { Command as Command24 } from "commander";
|
|
13362
|
-
var whoamiCommand = new Command24("whoami").description("Show or set the default user identity").option("--set <id>", "Set the default user ID").option("--clear", "Remove the default user ID").option("--json", "Output as JSON").action(async (opts) => {
|
|
13363
|
-
await withDb(async (db) => {
|
|
13364
|
-
if (opts.set) {
|
|
13365
|
-
await setSetting(db, "user.id", opts.set);
|
|
13366
|
-
if (opts.json) {
|
|
13367
|
-
console.log(JSON.stringify({ userId: opts.set }));
|
|
13368
|
-
} else {
|
|
13369
|
-
console.log(`Default user set to: ${opts.set}`);
|
|
13370
|
-
}
|
|
13371
|
-
return;
|
|
13372
|
-
}
|
|
13373
|
-
if (opts.clear) {
|
|
13374
|
-
const deleted = await deleteSetting(db, "user.id");
|
|
13375
|
-
if (opts.json) {
|
|
13376
|
-
console.log(JSON.stringify({ userId: null, cleared: deleted }));
|
|
13377
|
-
} else if (deleted) {
|
|
13378
|
-
console.log("Default user cleared.");
|
|
13379
|
-
} else {
|
|
13380
|
-
console.log("No default user was set.");
|
|
13381
|
-
}
|
|
13382
|
-
return;
|
|
13383
|
-
}
|
|
13384
|
-
const userId = await getSetting(db, "user.id");
|
|
13618
|
+
let db;
|
|
13619
|
+
try {
|
|
13620
|
+
db = await openDatabase();
|
|
13621
|
+
const workspaceDir = opts.dir || (await ensureActiveWorkspace(db)).path;
|
|
13622
|
+
const dest = await backupDatabaseTo(db, workspaceDir);
|
|
13385
13623
|
if (opts.json) {
|
|
13386
|
-
console.log(JSON.stringify({
|
|
13387
|
-
|
|
13624
|
+
console.log(JSON.stringify({ ok: true, path: dest }));
|
|
13625
|
+
} else {
|
|
13626
|
+
console.log(`\x1B[32m\u2713 Database backed up to ${dest}\x1B[0m`);
|
|
13388
13627
|
}
|
|
13389
|
-
|
|
13390
|
-
|
|
13628
|
+
} catch (err) {
|
|
13629
|
+
const reason = err.message;
|
|
13630
|
+
if (opts.json) {
|
|
13631
|
+
console.log(JSON.stringify({ ok: false, reason }));
|
|
13391
13632
|
} else {
|
|
13392
|
-
console.
|
|
13633
|
+
console.error(`\x1B[31m\u2717 Backup failed: ${reason}\x1B[0m`);
|
|
13393
13634
|
}
|
|
13394
|
-
|
|
13635
|
+
process.exit(1);
|
|
13636
|
+
} finally {
|
|
13637
|
+
await db?.close();
|
|
13638
|
+
}
|
|
13395
13639
|
});
|
|
13396
13640
|
|
|
13397
13641
|
// src/cli/index.ts
|
|
13398
13642
|
var __dirname = dirname10(fileURLToPath5(import.meta.url));
|
|
13399
13643
|
var pkg = JSON.parse(
|
|
13400
|
-
readFileSync15(
|
|
13644
|
+
readFileSync15(join25(__dirname, "..", "..", "package.json"), "utf-8")
|
|
13401
13645
|
);
|
|
13402
13646
|
var program = new Command25();
|
|
13403
13647
|
program.name("zam").description(
|