zam-core 0.10.3 → 0.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/app.js +470 -101
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/commands/mcp.js +1021 -103
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/copilot-extension/host.bundle.js +1 -1
- package/dist/copilot-extension/manifest.json +1 -1
- package/dist/copilot-extension/mcp-client.bundle.mjs +1 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/ui/recall-panel.html +1 -1
- package/dist/ui/studio-panel.html +1 -1
- package/dist/vscode-extension/{ZAM_Companion_0.10.3.vsix → ZAM_Companion_0.10.4.vsix} +0 -0
- package/dist/vscode-extension/manifest.json +2 -2
- package/package.json +1 -1
package/dist/cli/commands/mcp.js
CHANGED
|
@@ -5481,20 +5481,20 @@ import { homedir as homedir5 } from "os";
|
|
|
5481
5481
|
import { join as join8 } from "path";
|
|
5482
5482
|
import { fileURLToPath } from "url";
|
|
5483
5483
|
function getPackageSkillPath(agent = "default") {
|
|
5484
|
-
const
|
|
5484
|
+
const packageRoot4 = [
|
|
5485
5485
|
fileURLToPath(new URL("../../..", import.meta.url)),
|
|
5486
5486
|
fileURLToPath(new URL("../..", import.meta.url)),
|
|
5487
5487
|
fileURLToPath(new URL("..", import.meta.url))
|
|
5488
5488
|
].find((candidate) => existsSync7(join8(candidate, "package.json"))) ?? "";
|
|
5489
|
-
if (!
|
|
5489
|
+
if (!packageRoot4) return "";
|
|
5490
5490
|
if (agent === "codex") {
|
|
5491
|
-
const codexPath = join8(
|
|
5491
|
+
const codexPath = join8(packageRoot4, ".agents", "skills", "zam", "SKILL.md");
|
|
5492
5492
|
if (existsSync7(codexPath)) return codexPath;
|
|
5493
5493
|
return "";
|
|
5494
5494
|
}
|
|
5495
5495
|
if (agent === "claude") {
|
|
5496
5496
|
const claudePath = join8(
|
|
5497
|
-
|
|
5497
|
+
packageRoot4,
|
|
5498
5498
|
".claude",
|
|
5499
5499
|
"skills",
|
|
5500
5500
|
"zam",
|
|
@@ -5503,9 +5503,9 @@ function getPackageSkillPath(agent = "default") {
|
|
|
5503
5503
|
if (existsSync7(claudePath)) return claudePath;
|
|
5504
5504
|
return "";
|
|
5505
5505
|
}
|
|
5506
|
-
let path = join8(
|
|
5506
|
+
let path = join8(packageRoot4, ".agent", "skills", "zam", "SKILL.md");
|
|
5507
5507
|
if (existsSync7(path)) return path;
|
|
5508
|
-
path = join8(
|
|
5508
|
+
path = join8(packageRoot4, ".claude", "skills", "zam", "SKILL.md");
|
|
5509
5509
|
if (existsSync7(path)) return path;
|
|
5510
5510
|
return "";
|
|
5511
5511
|
}
|
|
@@ -5946,6 +5946,18 @@ function saveMachineAiConfig(ai, path = defaultConfigPath()) {
|
|
|
5946
5946
|
config.ai = ai;
|
|
5947
5947
|
saveInstallConfig(config, path);
|
|
5948
5948
|
}
|
|
5949
|
+
function getAgentConnectAutoDone(path = defaultConfigPath()) {
|
|
5950
|
+
return loadInstallConfig(path).agent?.connectAutoDone === true;
|
|
5951
|
+
}
|
|
5952
|
+
function setAgentConnectAutoDone(done, path = defaultConfigPath()) {
|
|
5953
|
+
const config = loadInstallConfig(path);
|
|
5954
|
+
if (done) {
|
|
5955
|
+
config.agent = { ...config.agent ?? {}, connectAutoDone: true };
|
|
5956
|
+
} else if (config.agent) {
|
|
5957
|
+
delete config.agent.connectAutoDone;
|
|
5958
|
+
}
|
|
5959
|
+
saveInstallConfig(config, path);
|
|
5960
|
+
}
|
|
5949
5961
|
function getConfiguredWorkspaces(path = defaultConfigPath()) {
|
|
5950
5962
|
return loadInstallConfig(path).workspaces ?? [];
|
|
5951
5963
|
}
|
|
@@ -6622,6 +6634,7 @@ __export(kernel_exports, {
|
|
|
6622
6634
|
getActiveWorkspace: () => getActiveWorkspace,
|
|
6623
6635
|
getActiveWorkspaceContext: () => getActiveWorkspaceContext,
|
|
6624
6636
|
getActiveWorkspaceId: () => getActiveWorkspaceId,
|
|
6637
|
+
getAgentConnectAutoDone: () => getAgentConnectAutoDone,
|
|
6625
6638
|
getAgentSkill: () => getAgentSkill,
|
|
6626
6639
|
getAllSettings: () => getAllSettings,
|
|
6627
6640
|
getAllSettingsDetailed: () => getAllSettingsDetailed,
|
|
@@ -6731,6 +6744,7 @@ __export(kernel_exports, {
|
|
|
6731
6744
|
setADOCredentials: () => setADOCredentials,
|
|
6732
6745
|
setActiveWorkspaceContext: () => setActiveWorkspaceContext,
|
|
6733
6746
|
setActiveWorkspaceId: () => setActiveWorkspaceId,
|
|
6747
|
+
setAgentConnectAutoDone: () => setAgentConnectAutoDone,
|
|
6734
6748
|
setInstallChannel: () => setInstallChannel,
|
|
6735
6749
|
setInstallMode: () => setInstallMode,
|
|
6736
6750
|
setProviderApiKey: () => setProviderApiKey,
|
|
@@ -6809,9 +6823,9 @@ var init_kernel = __esm({
|
|
|
6809
6823
|
|
|
6810
6824
|
// src/cli/commands/mcp.ts
|
|
6811
6825
|
init_kernel();
|
|
6812
|
-
import { existsSync as
|
|
6813
|
-
import { dirname as
|
|
6814
|
-
import { fileURLToPath as
|
|
6826
|
+
import { existsSync as existsSync19, readFileSync as readFileSync17 } from "fs";
|
|
6827
|
+
import { dirname as dirname11, join as join23 } from "path";
|
|
6828
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
6815
6829
|
import {
|
|
6816
6830
|
RESOURCE_MIME_TYPE,
|
|
6817
6831
|
registerAppResource,
|
|
@@ -6854,6 +6868,7 @@ var DEFAULT_LLM_URL = "http://localhost:8000/v1";
|
|
|
6854
6868
|
var DEFAULT_LLM_MAX_TOKENS = 1e4;
|
|
6855
6869
|
var RECALL_QUESTION_MAX_OUTPUT_TOKENS = 400;
|
|
6856
6870
|
var RECALL_EVALUATION_MAX_OUTPUT_TOKENS = 1200;
|
|
6871
|
+
var RECALL_DISCUSSION_MAX_OUTPUT_TOKENS = 1200;
|
|
6857
6872
|
var RECALL_ENDPOINT_CACHE_MS = 6e4;
|
|
6858
6873
|
var cachedRecallEndpoint = null;
|
|
6859
6874
|
function recallEndpointSignature(cfg) {
|
|
@@ -7216,6 +7231,64 @@ Evaluation:`;
|
|
|
7216
7231
|
providerName: endpoint.providerName
|
|
7217
7232
|
};
|
|
7218
7233
|
}
|
|
7234
|
+
async function discussReviewViaLLM(db, input) {
|
|
7235
|
+
const cfg = await getProviderForRole(db, "recall");
|
|
7236
|
+
const endpoint = await resolveUsableRecallEndpoint(db);
|
|
7237
|
+
const langName = LANGUAGE_NAMES[cfg.locale] || "English";
|
|
7238
|
+
const systemPrompt = `You are ZAM, a warm, precise, and encouraging skills trainer in a follow-up discussion about one flashcard.
|
|
7239
|
+
The learner has already answered, the reference answer is revealed, and your evaluation feedback was shown \u2014 nothing about this card is a spoiler anymore.
|
|
7240
|
+
|
|
7241
|
+
Guidelines:
|
|
7242
|
+
1. Answer the learner's follow-up directly and concretely in ${langName}, grounded in the card's target concept, context, and source reference.
|
|
7243
|
+
2. Stay scoped to this card and its concept. If the learner drifts to unrelated territory, answer briefly and steer back to the concept.
|
|
7244
|
+
3. Keep replies conversational and short (a few sentences) unless the learner explicitly asks for depth. Plain text only \u2014 no markdown wrapper, headers, or bullet lists.
|
|
7245
|
+
4. The self-rating is the learner's own choice. If asked, explain the FSRS scale (1 forgot, 2 hard, 3 good, 4 easy) but never pressure them toward a specific rating.`;
|
|
7246
|
+
const cardFrame = `The card under discussion:
|
|
7247
|
+
Domain: ${input.domain}
|
|
7248
|
+
Slug: ${input.slug}
|
|
7249
|
+
Recall Question: ${input.question}
|
|
7250
|
+
Learner's Answer: ${input.userAnswer}
|
|
7251
|
+
|
|
7252
|
+
Target Concept (Correct Answer): ${input.concept}
|
|
7253
|
+
Target Context: ${input.context || "(none)"}
|
|
7254
|
+
${input.sourceLinkContent ? `Source Code Reference:
|
|
7255
|
+
${input.sourceLinkContent}` : ""}`;
|
|
7256
|
+
const messages = [
|
|
7257
|
+
{ role: "system", content: systemPrompt },
|
|
7258
|
+
{ role: "user", content: cardFrame }
|
|
7259
|
+
];
|
|
7260
|
+
const feedback = input.feedback?.trim();
|
|
7261
|
+
if (feedback) {
|
|
7262
|
+
messages.push({ role: "assistant", content: feedback });
|
|
7263
|
+
}
|
|
7264
|
+
for (const turn of input.thread) {
|
|
7265
|
+
messages.push({ role: turn.role, content: turn.content });
|
|
7266
|
+
}
|
|
7267
|
+
messages.push({ role: "user", content: input.message });
|
|
7268
|
+
const res = await fetchWithInteractiveTimeout(
|
|
7269
|
+
`${endpoint.url}/chat/completions`,
|
|
7270
|
+
{
|
|
7271
|
+
method: "POST",
|
|
7272
|
+
headers: {
|
|
7273
|
+
"Content-Type": "application/json",
|
|
7274
|
+
Authorization: `Bearer ${endpoint.apiKey}`
|
|
7275
|
+
},
|
|
7276
|
+
body: JSON.stringify({
|
|
7277
|
+
model: endpoint.model,
|
|
7278
|
+
messages,
|
|
7279
|
+
temperature: 0.3,
|
|
7280
|
+
max_tokens: RECALL_DISCUSSION_MAX_OUTPUT_TOKENS
|
|
7281
|
+
}),
|
|
7282
|
+
locale: cfg.locale
|
|
7283
|
+
}
|
|
7284
|
+
);
|
|
7285
|
+
const text = await readChatContent(res, "LLM discussion");
|
|
7286
|
+
return {
|
|
7287
|
+
text,
|
|
7288
|
+
model: endpoint.model,
|
|
7289
|
+
providerName: endpoint.providerName
|
|
7290
|
+
};
|
|
7291
|
+
}
|
|
7219
7292
|
var MAX_IMPORT_TEXT_CHARS = 2e5;
|
|
7220
7293
|
var VALID_GENERATED_MODES = /* @__PURE__ */ new Set(["shadowing", "copilot", "autonomy"]);
|
|
7221
7294
|
function parseGeneratedCardArray(responseText, label, limits) {
|
|
@@ -7803,7 +7876,7 @@ async function prepareRecallChain(db, opts) {
|
|
|
7803
7876
|
} else {
|
|
7804
7877
|
spawnLocalRunner(endpoint.url, endpoint.model, endpoint.runner);
|
|
7805
7878
|
while (Date.now() < deadline) {
|
|
7806
|
-
await new Promise((
|
|
7879
|
+
await new Promise((resolve6) => setTimeout(resolve6, 1e3));
|
|
7807
7880
|
if (await isLlmOnline(endpoint.url)) {
|
|
7808
7881
|
online = true;
|
|
7809
7882
|
break;
|
|
@@ -8096,7 +8169,7 @@ async function startLocalRunner(url, model, locale, hint) {
|
|
|
8096
8169
|
let attempts = 0;
|
|
8097
8170
|
const dotsPerLine = 30;
|
|
8098
8171
|
while (true) {
|
|
8099
|
-
await new Promise((
|
|
8172
|
+
await new Promise((resolve6) => setTimeout(resolve6, 500));
|
|
8100
8173
|
if (await isLlmOnline(url)) {
|
|
8101
8174
|
if (attempts > 0) process.stdout.write("\n");
|
|
8102
8175
|
return true;
|
|
@@ -8156,8 +8229,8 @@ async function fetchWithInteractiveTimeout(url, options = {}) {
|
|
|
8156
8229
|
const dotsPerLine = 30;
|
|
8157
8230
|
while (true) {
|
|
8158
8231
|
let timeoutId;
|
|
8159
|
-
const timeoutPromise = new Promise((
|
|
8160
|
-
timeoutId = setTimeout(() =>
|
|
8232
|
+
const timeoutPromise = new Promise((resolve6) => {
|
|
8233
|
+
timeoutId = setTimeout(() => resolve6("timeout"), timeoutMs);
|
|
8161
8234
|
});
|
|
8162
8235
|
const dotsInterval = setInterval(() => {
|
|
8163
8236
|
process.stdout.write(".");
|
|
@@ -9501,11 +9574,11 @@ async function publishUiIntent(app, input = {}, opts = {}) {
|
|
|
9501
9574
|
|
|
9502
9575
|
// src/cli/commands/bridge.ts
|
|
9503
9576
|
init_kernel();
|
|
9504
|
-
import { execFileSync as
|
|
9577
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
9505
9578
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
9506
|
-
import { existsSync as
|
|
9507
|
-
import { homedir as
|
|
9508
|
-
import { join as
|
|
9579
|
+
import { existsSync as existsSync18, readdirSync as readdirSync2, readFileSync as readFileSync16, rmSync as rmSync3 } from "fs";
|
|
9580
|
+
import { homedir as homedir14, tmpdir as tmpdir3 } from "os";
|
|
9581
|
+
import { join as join22, resolve as resolve5 } from "path";
|
|
9509
9582
|
import { Command } from "commander";
|
|
9510
9583
|
import { ulid as ulid10 } from "ulid";
|
|
9511
9584
|
|
|
@@ -9584,7 +9657,7 @@ async function readWebLink(url) {
|
|
|
9584
9657
|
redirect: "manual",
|
|
9585
9658
|
signal: controller.signal,
|
|
9586
9659
|
headers: {
|
|
9587
|
-
"User-Agent": "ZAM-Content-Studio/0.10.
|
|
9660
|
+
"User-Agent": "ZAM-Content-Studio/0.10.4"
|
|
9588
9661
|
}
|
|
9589
9662
|
});
|
|
9590
9663
|
if (res.status >= 300 && res.status < 400) {
|
|
@@ -9647,6 +9720,12 @@ async function readImageOCR(db, imagePath) {
|
|
|
9647
9720
|
return extractTextFromScanViaLLM(db, imagePath);
|
|
9648
9721
|
}
|
|
9649
9722
|
|
|
9723
|
+
// src/cli/agent-connect.ts
|
|
9724
|
+
init_kernel();
|
|
9725
|
+
import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync10 } from "fs";
|
|
9726
|
+
import { homedir as homedir13 } from "os";
|
|
9727
|
+
import { dirname as dirname9 } from "path";
|
|
9728
|
+
|
|
9650
9729
|
// src/cli/agent-harness.ts
|
|
9651
9730
|
import { spawn as spawn2 } from "child_process";
|
|
9652
9731
|
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
|
|
@@ -9967,6 +10046,710 @@ function launchHarness(harness, opts) {
|
|
|
9967
10046
|
console.log(`Launched ${harness.label} in ${opts.workspace}`);
|
|
9968
10047
|
}
|
|
9969
10048
|
}
|
|
10049
|
+
function detectInstalledConnectHarnesses(options = {}) {
|
|
10050
|
+
const home = options.home ?? homedir10();
|
|
10051
|
+
const platform = options.platform ?? process.platform;
|
|
10052
|
+
const find = options.find ?? findExecutable;
|
|
10053
|
+
const exists = options.exists ?? existsSync14;
|
|
10054
|
+
const detected = [];
|
|
10055
|
+
const hasCommandOrPath = (command, paths = []) => Boolean(find(command)) || paths.some((path) => exists(path));
|
|
10056
|
+
if (hasCommandOrPath("codex", [
|
|
10057
|
+
join16(home, ".codex"),
|
|
10058
|
+
...platform === "darwin" ? ["/Applications/Codex.app"] : platform === "win32" ? [join16(home, "AppData", "Local", "Programs", "Codex")] : []
|
|
10059
|
+
])) {
|
|
10060
|
+
detected.push("codex");
|
|
10061
|
+
}
|
|
10062
|
+
const vscodePaths = platform === "darwin" ? [
|
|
10063
|
+
"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
|
|
10064
|
+
join16(
|
|
10065
|
+
home,
|
|
10066
|
+
"Applications",
|
|
10067
|
+
"Visual Studio Code.app",
|
|
10068
|
+
"Contents",
|
|
10069
|
+
"Resources",
|
|
10070
|
+
"app",
|
|
10071
|
+
"bin",
|
|
10072
|
+
"code"
|
|
10073
|
+
)
|
|
10074
|
+
] : platform === "win32" ? [
|
|
10075
|
+
join16(
|
|
10076
|
+
home,
|
|
10077
|
+
"AppData",
|
|
10078
|
+
"Local",
|
|
10079
|
+
"Programs",
|
|
10080
|
+
"Microsoft VS Code",
|
|
10081
|
+
"bin",
|
|
10082
|
+
"code.cmd"
|
|
10083
|
+
)
|
|
10084
|
+
] : ["/usr/bin/code", "/usr/local/bin/code", "/snap/bin/code"];
|
|
10085
|
+
if (hasCommandOrPath("code", vscodePaths)) detected.push("vscode");
|
|
10086
|
+
const copilotHome = options.copilotHome ?? join16(home, ".copilot");
|
|
10087
|
+
if (hasCommandOrPath("copilot", [
|
|
10088
|
+
copilotHome,
|
|
10089
|
+
...platform === "darwin" ? ["/Applications/GitHub Copilot.app"] : []
|
|
10090
|
+
])) {
|
|
10091
|
+
detected.push("copilot");
|
|
10092
|
+
}
|
|
10093
|
+
if (hasCommandOrPath("opencode", [join16(home, ".config", "opencode")])) {
|
|
10094
|
+
detected.push("opencode");
|
|
10095
|
+
}
|
|
10096
|
+
if (hasCommandOrPath("goose", [join16(home, ".config", "goose")])) {
|
|
10097
|
+
detected.push("goose");
|
|
10098
|
+
}
|
|
10099
|
+
if (hasCommandOrPath("antigravity", [
|
|
10100
|
+
join16(home, ".gemini", "config"),
|
|
10101
|
+
...platform === "darwin" ? [
|
|
10102
|
+
join16(
|
|
10103
|
+
home,
|
|
10104
|
+
".antigravity-ide",
|
|
10105
|
+
"antigravity-ide",
|
|
10106
|
+
"bin",
|
|
10107
|
+
"antigravity-ide"
|
|
10108
|
+
),
|
|
10109
|
+
"/Applications/Antigravity.app",
|
|
10110
|
+
"/Applications/Antigravity IDE.app"
|
|
10111
|
+
] : []
|
|
10112
|
+
]) || find("antigravity-ide")) {
|
|
10113
|
+
detected.push("antigravity");
|
|
10114
|
+
}
|
|
10115
|
+
const claudeDesktopPath = platform === "darwin" ? "/Applications/Claude.app" : platform === "win32" ? join16(home, "AppData", "Local", "AnthropicClaude") : join16(home, ".config", "Claude");
|
|
10116
|
+
if (exists(claudeDesktopPath)) detected.push("claude-desktop");
|
|
10117
|
+
return detected;
|
|
10118
|
+
}
|
|
10119
|
+
function parseMcpJsonConfig(path, content) {
|
|
10120
|
+
let parsed;
|
|
10121
|
+
try {
|
|
10122
|
+
parsed = JSON.parse(content);
|
|
10123
|
+
} catch (error) {
|
|
10124
|
+
throw new Error(
|
|
10125
|
+
`Cannot update ${path}: existing file is not valid JSON (${error instanceof Error ? error.message : String(error)})`
|
|
10126
|
+
);
|
|
10127
|
+
}
|
|
10128
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
10129
|
+
throw new Error(`Cannot update ${path}: expected a JSON object`);
|
|
10130
|
+
}
|
|
10131
|
+
const config = parsed;
|
|
10132
|
+
if (config.mcpServers !== void 0 && (typeof config.mcpServers !== "object" || config.mcpServers === null || Array.isArray(config.mcpServers))) {
|
|
10133
|
+
throw new Error(`Cannot update ${path}: mcpServers must be a JSON object`);
|
|
10134
|
+
}
|
|
10135
|
+
return config;
|
|
10136
|
+
}
|
|
10137
|
+
function connectHarnessMcp(harnessId, opts) {
|
|
10138
|
+
const exists = (p) => {
|
|
10139
|
+
if (opts.readFile) {
|
|
10140
|
+
try {
|
|
10141
|
+
opts.readFile(p);
|
|
10142
|
+
return true;
|
|
10143
|
+
} catch {
|
|
10144
|
+
return false;
|
|
10145
|
+
}
|
|
10146
|
+
}
|
|
10147
|
+
return existsSync14(p);
|
|
10148
|
+
};
|
|
10149
|
+
const read = (p) => {
|
|
10150
|
+
if (opts.readFile) return opts.readFile(p);
|
|
10151
|
+
return readFileSync11(p, "utf-8");
|
|
10152
|
+
};
|
|
10153
|
+
let targetPath = "";
|
|
10154
|
+
let content = "";
|
|
10155
|
+
let alreadyConfigured = false;
|
|
10156
|
+
let hint = "";
|
|
10157
|
+
const mergeMcpServersJson = (path) => {
|
|
10158
|
+
let existing = {};
|
|
10159
|
+
if (exists(path)) {
|
|
10160
|
+
existing = parseMcpJsonConfig(path, read(path));
|
|
10161
|
+
}
|
|
10162
|
+
if (!existing.mcpServers) {
|
|
10163
|
+
existing.mcpServers = {};
|
|
10164
|
+
}
|
|
10165
|
+
existing.mcpServers.zam = {
|
|
10166
|
+
command: opts.zamPath,
|
|
10167
|
+
args: ["mcp"]
|
|
10168
|
+
};
|
|
10169
|
+
return JSON.stringify(existing, null, 2);
|
|
10170
|
+
};
|
|
10171
|
+
if (harnessId === "claude-code") {
|
|
10172
|
+
targetPath = join16(opts.cwd, ".mcp.json");
|
|
10173
|
+
hint = "Claude Code will prompt you to approve the 'zam' MCP server on next launch.";
|
|
10174
|
+
content = mergeMcpServersJson(targetPath);
|
|
10175
|
+
} else if (harnessId === "claude-desktop") {
|
|
10176
|
+
const platform = opts.platform ?? process.platform;
|
|
10177
|
+
targetPath = platform === "win32" ? join16(
|
|
10178
|
+
opts.home,
|
|
10179
|
+
"AppData",
|
|
10180
|
+
"Roaming",
|
|
10181
|
+
"Claude",
|
|
10182
|
+
"claude_desktop_config.json"
|
|
10183
|
+
) : platform === "darwin" ? join16(
|
|
10184
|
+
opts.home,
|
|
10185
|
+
"Library",
|
|
10186
|
+
"Application Support",
|
|
10187
|
+
"Claude",
|
|
10188
|
+
"claude_desktop_config.json"
|
|
10189
|
+
) : join16(opts.home, ".config", "Claude", "claude_desktop_config.json");
|
|
10190
|
+
hint = "Restart Claude Desktop to load the 'zam' MCP server; MCP Apps panels render inline in the chat.";
|
|
10191
|
+
content = mergeMcpServersJson(targetPath);
|
|
10192
|
+
} else if (harnessId === "antigravity") {
|
|
10193
|
+
targetPath = join16(opts.home, ".gemini", "config", "mcp_config.json");
|
|
10194
|
+
hint = "Shared config read by Antigravity CLI and IDE (2.0+); older IDE builds read ~/.gemini/antigravity/mcp_config.json instead. Refresh Installed MCP Servers; the first tool call may still require approval.";
|
|
10195
|
+
let existing = {};
|
|
10196
|
+
if (exists(targetPath)) {
|
|
10197
|
+
existing = parseMcpJsonConfig(targetPath, read(targetPath));
|
|
10198
|
+
}
|
|
10199
|
+
if (!existing.mcpServers) {
|
|
10200
|
+
existing.mcpServers = {};
|
|
10201
|
+
}
|
|
10202
|
+
existing.mcpServers.zam = {
|
|
10203
|
+
command: opts.zamPath,
|
|
10204
|
+
args: ["mcp"]
|
|
10205
|
+
};
|
|
10206
|
+
content = JSON.stringify(existing, null, 2);
|
|
10207
|
+
} else if (harnessId === "opencode") {
|
|
10208
|
+
targetPath = join16(opts.home, ".config", "opencode", "opencode.json");
|
|
10209
|
+
hint = "OpenCode will load the enabled 'zam' MCP server on next launch.";
|
|
10210
|
+
let existing = {};
|
|
10211
|
+
if (exists(targetPath)) {
|
|
10212
|
+
existing = parseMcpJsonConfig(targetPath, read(targetPath));
|
|
10213
|
+
}
|
|
10214
|
+
const mcp = existing.mcp;
|
|
10215
|
+
if (mcp !== void 0 && (typeof mcp !== "object" || mcp === null || Array.isArray(mcp))) {
|
|
10216
|
+
throw new Error(`Cannot update ${targetPath}: mcp must be a JSON object`);
|
|
10217
|
+
}
|
|
10218
|
+
const servers = mcp ?? {};
|
|
10219
|
+
servers.zam = {
|
|
10220
|
+
type: "local",
|
|
10221
|
+
command: [opts.zamPath, "mcp"],
|
|
10222
|
+
enabled: true
|
|
10223
|
+
};
|
|
10224
|
+
existing.mcp = servers;
|
|
10225
|
+
content = JSON.stringify(existing, null, 2);
|
|
10226
|
+
} else if (harnessId === "codex") {
|
|
10227
|
+
targetPath = join16(opts.home, ".codex", "config.toml");
|
|
10228
|
+
hint = "Codex will prompt for tool execution approvals or respect the TOML approval modes.";
|
|
10229
|
+
let existingStr = "";
|
|
10230
|
+
if (exists(targetPath)) {
|
|
10231
|
+
existingStr = read(targetPath);
|
|
10232
|
+
}
|
|
10233
|
+
if (existingStr.includes("[mcp_servers.zam]")) {
|
|
10234
|
+
alreadyConfigured = true;
|
|
10235
|
+
content = existingStr;
|
|
10236
|
+
} else {
|
|
10237
|
+
const block = `
|
|
10238
|
+
[mcp_servers.zam]
|
|
10239
|
+
command = ${JSON.stringify(opts.zamPath)}
|
|
10240
|
+
args = ["mcp"]
|
|
10241
|
+
default_tools_approval_mode = "approve"
|
|
10242
|
+
|
|
10243
|
+
[mcp_servers.zam.tools.zam_review_action]
|
|
10244
|
+
approval_mode = "prompt"
|
|
10245
|
+
`;
|
|
10246
|
+
content = existingStr ? `${existingStr.trimEnd()}
|
|
10247
|
+
${block}` : block;
|
|
10248
|
+
}
|
|
10249
|
+
} else if (harnessId === "vscode") {
|
|
10250
|
+
const platform = opts.platform ?? process.platform;
|
|
10251
|
+
targetPath = platform === "win32" ? join16(opts.home, "AppData", "Roaming", "Code", "User", "mcp.json") : platform === "darwin" ? join16(
|
|
10252
|
+
opts.home,
|
|
10253
|
+
"Library",
|
|
10254
|
+
"Application Support",
|
|
10255
|
+
"Code",
|
|
10256
|
+
"User",
|
|
10257
|
+
"mcp.json"
|
|
10258
|
+
) : join16(opts.home, ".config", "Code", "User", "mcp.json");
|
|
10259
|
+
hint = "Reload VS Code after setup. ZAM Companion stays separate from the Codex chat and can be moved to any panel or sidebar.";
|
|
10260
|
+
let existing = {};
|
|
10261
|
+
if (exists(targetPath)) {
|
|
10262
|
+
existing = parseMcpJsonConfig(targetPath, read(targetPath));
|
|
10263
|
+
}
|
|
10264
|
+
if (existing.servers !== void 0 && (typeof existing.servers !== "object" || existing.servers === null || Array.isArray(existing.servers))) {
|
|
10265
|
+
throw new Error(
|
|
10266
|
+
`Cannot update ${targetPath}: servers must be a JSON object`
|
|
10267
|
+
);
|
|
10268
|
+
}
|
|
10269
|
+
if (existing.inputs !== void 0 && !Array.isArray(existing.inputs)) {
|
|
10270
|
+
throw new Error(`Cannot update ${targetPath}: inputs must be an array`);
|
|
10271
|
+
}
|
|
10272
|
+
if (!existing.servers) existing.servers = {};
|
|
10273
|
+
const expectedServer = { command: opts.zamPath, args: ["mcp"] };
|
|
10274
|
+
const currentServer = existing.servers.zam;
|
|
10275
|
+
alreadyConfigured = Array.isArray(existing.inputs) && typeof currentServer === "object" && currentServer !== null && !Array.isArray(currentServer) && JSON.stringify(currentServer) === JSON.stringify(expectedServer);
|
|
10276
|
+
existing.servers.zam = expectedServer;
|
|
10277
|
+
if (!existing.inputs) existing.inputs = [];
|
|
10278
|
+
content = JSON.stringify(existing, null, 2);
|
|
10279
|
+
} else if (harnessId === "goose") {
|
|
10280
|
+
targetPath = join16(opts.home, ".config", "goose", "config.yaml");
|
|
10281
|
+
hint = "goose will load the 'zam' extension on next session start. Run 'goose configure' to manage extensions.";
|
|
10282
|
+
const zamExtension = [
|
|
10283
|
+
" zam:",
|
|
10284
|
+
" name: ZAM",
|
|
10285
|
+
` cmd: ${opts.zamPath}`,
|
|
10286
|
+
" args:",
|
|
10287
|
+
" - mcp",
|
|
10288
|
+
" enabled: true",
|
|
10289
|
+
" type: stdio",
|
|
10290
|
+
" timeout: 300",
|
|
10291
|
+
" description: Symbiotic learning agent with spaced repetition"
|
|
10292
|
+
].join("\n");
|
|
10293
|
+
let existingStr = "";
|
|
10294
|
+
if (exists(targetPath)) {
|
|
10295
|
+
existingStr = read(targetPath);
|
|
10296
|
+
}
|
|
10297
|
+
if (/^\s+zam:\s*$/m.test(existingStr) && existingStr.includes("- mcp")) {
|
|
10298
|
+
alreadyConfigured = true;
|
|
10299
|
+
content = existingStr;
|
|
10300
|
+
} else if (/^extensions:[ \t]*$/m.test(existingStr)) {
|
|
10301
|
+
content = existingStr.replace(
|
|
10302
|
+
/^extensions:[ \t]*$/m,
|
|
10303
|
+
(line) => `${line}
|
|
10304
|
+
${zamExtension}`
|
|
10305
|
+
);
|
|
10306
|
+
} else if (existingStr.trim()) {
|
|
10307
|
+
content = `${existingStr.trimEnd()}
|
|
10308
|
+
extensions:
|
|
10309
|
+
${zamExtension}
|
|
10310
|
+
`;
|
|
10311
|
+
} else {
|
|
10312
|
+
content = `extensions:
|
|
10313
|
+
${zamExtension}
|
|
10314
|
+
`;
|
|
10315
|
+
}
|
|
10316
|
+
} else if (harnessId === "copilot") {
|
|
10317
|
+
targetPath = join16(
|
|
10318
|
+
opts.copilotHome ?? join16(opts.home, ".copilot"),
|
|
10319
|
+
"mcp-config.json"
|
|
10320
|
+
);
|
|
10321
|
+
hint = "Restart GitHub Copilot or start a new session to load the 'zam' MCP server and its focused Recall, Graph, and Settings canvases.";
|
|
10322
|
+
let existing = {};
|
|
10323
|
+
if (exists(targetPath)) {
|
|
10324
|
+
existing = parseMcpJsonConfig(targetPath, read(targetPath));
|
|
10325
|
+
}
|
|
10326
|
+
if (!existing.mcpServers) {
|
|
10327
|
+
existing.mcpServers = {};
|
|
10328
|
+
}
|
|
10329
|
+
existing.mcpServers.zam = {
|
|
10330
|
+
type: "local",
|
|
10331
|
+
command: opts.zamPath,
|
|
10332
|
+
args: ["mcp"],
|
|
10333
|
+
tools: ["*"]
|
|
10334
|
+
};
|
|
10335
|
+
content = JSON.stringify(existing, null, 2);
|
|
10336
|
+
}
|
|
10337
|
+
return {
|
|
10338
|
+
path: targetPath,
|
|
10339
|
+
content,
|
|
10340
|
+
alreadyConfigured,
|
|
10341
|
+
hint
|
|
10342
|
+
};
|
|
10343
|
+
}
|
|
10344
|
+
|
|
10345
|
+
// src/cli/copilot-extension.ts
|
|
10346
|
+
import {
|
|
10347
|
+
existsSync as existsSync15,
|
|
10348
|
+
lstatSync,
|
|
10349
|
+
mkdirSync as mkdirSync10,
|
|
10350
|
+
readFileSync as readFileSync12,
|
|
10351
|
+
writeFileSync as writeFileSync8
|
|
10352
|
+
} from "fs";
|
|
10353
|
+
import { homedir as homedir11 } from "os";
|
|
10354
|
+
import { dirname as dirname7, join as join17, resolve as resolve4 } from "path";
|
|
10355
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
10356
|
+
var EXTENSION_NAME = "zam-mcp-apps";
|
|
10357
|
+
var EXTENSION_FILES = [
|
|
10358
|
+
"extension.mjs",
|
|
10359
|
+
"host.bundle.js",
|
|
10360
|
+
"mcp-client.bundle.mjs",
|
|
10361
|
+
"manifest.json"
|
|
10362
|
+
];
|
|
10363
|
+
var packageRoot = [
|
|
10364
|
+
fileURLToPath3(new URL("../..", import.meta.url)),
|
|
10365
|
+
fileURLToPath3(new URL("../../..", import.meta.url))
|
|
10366
|
+
].find((candidate) => existsSync15(join17(candidate, "package.json"))) ?? fileURLToPath3(new URL("../..", import.meta.url));
|
|
10367
|
+
function defaultCliEntry() {
|
|
10368
|
+
return join17(dirname7(fileURLToPath3(import.meta.url)), "index.js");
|
|
10369
|
+
}
|
|
10370
|
+
function resolveCopilotHome(home = homedir11(), configuredHome = process.env.COPILOT_HOME) {
|
|
10371
|
+
return configuredHome?.trim() ? resolve4(configuredHome) : join17(home, ".copilot");
|
|
10372
|
+
}
|
|
10373
|
+
function resolveCopilotZamLaunch(zamPath, options = {}) {
|
|
10374
|
+
const cliEntry = options.cliEntry ?? defaultCliEntry();
|
|
10375
|
+
if (existsSync15(cliEntry)) {
|
|
10376
|
+
return {
|
|
10377
|
+
command: options.nodePath ?? process.execPath,
|
|
10378
|
+
args: [cliEntry, "mcp"]
|
|
10379
|
+
};
|
|
10380
|
+
}
|
|
10381
|
+
return {
|
|
10382
|
+
command: zamPath,
|
|
10383
|
+
args: ["mcp"]
|
|
10384
|
+
};
|
|
10385
|
+
}
|
|
10386
|
+
function planCopilotExtensionInstall(options) {
|
|
10387
|
+
const sourceDir = options.assetsDir ?? join17(packageRoot, "dist", "copilot-extension");
|
|
10388
|
+
for (const file of EXTENSION_FILES) {
|
|
10389
|
+
if (!existsSync15(join17(sourceDir, file))) {
|
|
10390
|
+
throw new Error(
|
|
10391
|
+
`Copilot MCP Apps asset is missing: ${join17(sourceDir, file)}. Run \`npm run build\` and retry.`
|
|
10392
|
+
);
|
|
10393
|
+
}
|
|
10394
|
+
}
|
|
10395
|
+
const manifest = JSON.parse(
|
|
10396
|
+
readFileSync12(join17(sourceDir, "manifest.json"), "utf8")
|
|
10397
|
+
);
|
|
10398
|
+
if (manifest.name !== EXTENSION_NAME || typeof manifest.version !== "string" || !manifest.version) {
|
|
10399
|
+
throw new Error(
|
|
10400
|
+
`Invalid Copilot MCP Apps manifest: ${join17(sourceDir, "manifest.json")}`
|
|
10401
|
+
);
|
|
10402
|
+
}
|
|
10403
|
+
const copilotHome = resolveCopilotHome(
|
|
10404
|
+
options.home,
|
|
10405
|
+
options.copilotHome ?? process.env.COPILOT_HOME
|
|
10406
|
+
);
|
|
10407
|
+
return {
|
|
10408
|
+
sourceDir,
|
|
10409
|
+
destinationDir: join17(copilotHome, "extensions", EXTENSION_NAME),
|
|
10410
|
+
launch: resolveCopilotZamLaunch(options.zamPath, options),
|
|
10411
|
+
files: EXTENSION_FILES
|
|
10412
|
+
};
|
|
10413
|
+
}
|
|
10414
|
+
function writeIfChanged(path, content) {
|
|
10415
|
+
const next = Buffer.isBuffer(content) ? content : Buffer.from(content);
|
|
10416
|
+
if (existsSync15(path) && readFileSync12(path).equals(next)) return false;
|
|
10417
|
+
writeFileSync8(path, next);
|
|
10418
|
+
return true;
|
|
10419
|
+
}
|
|
10420
|
+
function installCopilotExtension(options) {
|
|
10421
|
+
const plan = planCopilotExtensionInstall(options);
|
|
10422
|
+
if (options.dryRun) {
|
|
10423
|
+
return { ...plan, action: "planned", changedFiles: [] };
|
|
10424
|
+
}
|
|
10425
|
+
const destinationExisted = existsSync15(plan.destinationDir);
|
|
10426
|
+
if (destinationExisted && lstatSync(plan.destinationDir).isSymbolicLink()) {
|
|
10427
|
+
throw new Error(
|
|
10428
|
+
`Refusing to replace symlinked Copilot extension directory: ${plan.destinationDir}`
|
|
10429
|
+
);
|
|
10430
|
+
}
|
|
10431
|
+
mkdirSync10(plan.destinationDir, { recursive: true });
|
|
10432
|
+
const changedFiles = [];
|
|
10433
|
+
for (const file of plan.files) {
|
|
10434
|
+
const source = join17(plan.sourceDir, file);
|
|
10435
|
+
const destination = join17(plan.destinationDir, file);
|
|
10436
|
+
if (writeIfChanged(destination, readFileSync12(source))) {
|
|
10437
|
+
changedFiles.push(file);
|
|
10438
|
+
}
|
|
10439
|
+
}
|
|
10440
|
+
const launchContent = `${JSON.stringify(
|
|
10441
|
+
{
|
|
10442
|
+
schemaVersion: 1,
|
|
10443
|
+
command: plan.launch.command,
|
|
10444
|
+
args: plan.launch.args
|
|
10445
|
+
},
|
|
10446
|
+
null,
|
|
10447
|
+
2
|
|
10448
|
+
)}
|
|
10449
|
+
`;
|
|
10450
|
+
if (writeIfChanged(join17(plan.destinationDir, "launch.json"), launchContent)) {
|
|
10451
|
+
changedFiles.push("launch.json");
|
|
10452
|
+
}
|
|
10453
|
+
return {
|
|
10454
|
+
...plan,
|
|
10455
|
+
action: !destinationExisted ? "installed" : changedFiles.length > 0 ? "updated" : "unchanged",
|
|
10456
|
+
changedFiles
|
|
10457
|
+
};
|
|
10458
|
+
}
|
|
10459
|
+
|
|
10460
|
+
// src/cli/vscode-extension.ts
|
|
10461
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
10462
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
|
|
10463
|
+
import { homedir as homedir12 } from "os";
|
|
10464
|
+
import { dirname as dirname8, join as join18 } from "path";
|
|
10465
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
10466
|
+
var packageRoot2 = [
|
|
10467
|
+
fileURLToPath4(new URL("../..", import.meta.url)),
|
|
10468
|
+
fileURLToPath4(new URL("../../..", import.meta.url))
|
|
10469
|
+
].find((candidate) => existsSync16(join18(candidate, "package.json"))) ?? fileURLToPath4(new URL("../..", import.meta.url));
|
|
10470
|
+
function resolveVscodeExecutable(options = {}) {
|
|
10471
|
+
const home = options.home ?? homedir12();
|
|
10472
|
+
const platform = options.platform ?? process.platform;
|
|
10473
|
+
const find = options.find ?? findExecutable;
|
|
10474
|
+
const exists = options.exists ?? existsSync16;
|
|
10475
|
+
const found = find("code");
|
|
10476
|
+
if (found) return found;
|
|
10477
|
+
const candidates = platform === "darwin" ? [
|
|
10478
|
+
"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
|
|
10479
|
+
join18(
|
|
10480
|
+
home,
|
|
10481
|
+
"Applications",
|
|
10482
|
+
"Visual Studio Code.app",
|
|
10483
|
+
"Contents",
|
|
10484
|
+
"Resources",
|
|
10485
|
+
"app",
|
|
10486
|
+
"bin",
|
|
10487
|
+
"code"
|
|
10488
|
+
)
|
|
10489
|
+
] : platform === "win32" ? [
|
|
10490
|
+
join18(
|
|
10491
|
+
home,
|
|
10492
|
+
"AppData",
|
|
10493
|
+
"Local",
|
|
10494
|
+
"Programs",
|
|
10495
|
+
"Microsoft VS Code",
|
|
10496
|
+
"bin",
|
|
10497
|
+
"code.cmd"
|
|
10498
|
+
)
|
|
10499
|
+
] : ["/usr/bin/code", "/usr/local/bin/code", "/snap/bin/code"];
|
|
10500
|
+
return candidates.find((candidate) => exists(candidate)) ?? null;
|
|
10501
|
+
}
|
|
10502
|
+
function packageVersion() {
|
|
10503
|
+
const parsed = JSON.parse(
|
|
10504
|
+
readFileSync13(join18(packageRoot2, "package.json"), "utf8")
|
|
10505
|
+
);
|
|
10506
|
+
if (typeof parsed.version !== "string" || !parsed.version) {
|
|
10507
|
+
throw new Error("Cannot determine the ZAM package version");
|
|
10508
|
+
}
|
|
10509
|
+
return parsed.version;
|
|
10510
|
+
}
|
|
10511
|
+
function planVscodeExtensionInstall(options) {
|
|
10512
|
+
const home = options.home ?? homedir12();
|
|
10513
|
+
const version = options.version ?? packageVersion();
|
|
10514
|
+
const assetsDir = options.assetsDir ?? join18(packageRoot2, "dist", "vscode-extension");
|
|
10515
|
+
const vsixPath = join18(assetsDir, `ZAM_Companion_${version}.vsix`);
|
|
10516
|
+
if (!existsSync16(vsixPath)) {
|
|
10517
|
+
throw new Error(
|
|
10518
|
+
`ZAM Companion VSIX asset is missing: ${vsixPath}. Run \`npm run build\` and retry.`
|
|
10519
|
+
);
|
|
10520
|
+
}
|
|
10521
|
+
const codePath = options.codePath ?? resolveVscodeExecutable({
|
|
10522
|
+
home,
|
|
10523
|
+
platform: options.platform,
|
|
10524
|
+
find: options.find,
|
|
10525
|
+
exists: options.exists
|
|
10526
|
+
});
|
|
10527
|
+
if (!codePath) {
|
|
10528
|
+
throw new Error(
|
|
10529
|
+
"Visual Studio Code was not detected. Install VS Code or add its 'code' command to PATH."
|
|
10530
|
+
);
|
|
10531
|
+
}
|
|
10532
|
+
return {
|
|
10533
|
+
version,
|
|
10534
|
+
vsixPath,
|
|
10535
|
+
codePath,
|
|
10536
|
+
launchConfigPath: join18(home, ".zam", "vscode-launch.json"),
|
|
10537
|
+
launch: { command: options.zamPath, args: ["mcp"] }
|
|
10538
|
+
};
|
|
10539
|
+
}
|
|
10540
|
+
function installVscodeExtension(options) {
|
|
10541
|
+
const plan = planVscodeExtensionInstall(options);
|
|
10542
|
+
if (options.dryRun) return { ...plan, action: "planned" };
|
|
10543
|
+
const launchContent = `${JSON.stringify(
|
|
10544
|
+
{
|
|
10545
|
+
schemaVersion: 1,
|
|
10546
|
+
version: plan.version,
|
|
10547
|
+
command: plan.launch.command,
|
|
10548
|
+
args: plan.launch.args
|
|
10549
|
+
},
|
|
10550
|
+
null,
|
|
10551
|
+
2
|
|
10552
|
+
)}
|
|
10553
|
+
`;
|
|
10554
|
+
const existed = existsSync16(plan.launchConfigPath);
|
|
10555
|
+
const changed = !existed || readFileSync13(plan.launchConfigPath, "utf8") !== launchContent;
|
|
10556
|
+
const run = options.run ?? ((command, args) => {
|
|
10557
|
+
execFileSync3(command, args, { stdio: "pipe" });
|
|
10558
|
+
});
|
|
10559
|
+
run(plan.codePath, ["--install-extension", plan.vsixPath, "--force"]);
|
|
10560
|
+
if (changed) {
|
|
10561
|
+
mkdirSync11(dirname8(plan.launchConfigPath), { recursive: true });
|
|
10562
|
+
writeFileSync9(plan.launchConfigPath, launchContent, "utf8");
|
|
10563
|
+
}
|
|
10564
|
+
return {
|
|
10565
|
+
...plan,
|
|
10566
|
+
action: !existed ? "installed" : changed ? "updated" : "unchanged"
|
|
10567
|
+
};
|
|
10568
|
+
}
|
|
10569
|
+
|
|
10570
|
+
// src/cli/agent-connect.ts
|
|
10571
|
+
var CONNECT_HARNESSES = [
|
|
10572
|
+
"claude-code",
|
|
10573
|
+
"claude-desktop",
|
|
10574
|
+
"antigravity",
|
|
10575
|
+
"codex",
|
|
10576
|
+
"vscode",
|
|
10577
|
+
"opencode",
|
|
10578
|
+
"goose",
|
|
10579
|
+
"copilot"
|
|
10580
|
+
];
|
|
10581
|
+
var USER_SCOPED_CONNECT_HARNESSES = [
|
|
10582
|
+
"claude-desktop",
|
|
10583
|
+
"antigravity",
|
|
10584
|
+
"codex",
|
|
10585
|
+
"vscode",
|
|
10586
|
+
"opencode",
|
|
10587
|
+
"goose",
|
|
10588
|
+
"copilot"
|
|
10589
|
+
];
|
|
10590
|
+
var CONNECT_HARNESS_LABELS = {
|
|
10591
|
+
"claude-code": "Claude Code",
|
|
10592
|
+
"claude-desktop": "Claude Desktop",
|
|
10593
|
+
antigravity: "Antigravity",
|
|
10594
|
+
codex: "Codex",
|
|
10595
|
+
vscode: "VS Code",
|
|
10596
|
+
opencode: "OpenCode",
|
|
10597
|
+
goose: "Goose",
|
|
10598
|
+
copilot: "GitHub Copilot"
|
|
10599
|
+
};
|
|
10600
|
+
function isConnectHarnessId(value) {
|
|
10601
|
+
return CONNECT_HARNESSES.includes(value);
|
|
10602
|
+
}
|
|
10603
|
+
function resolveDeps(deps) {
|
|
10604
|
+
const home = deps.home ?? homedir13();
|
|
10605
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
10606
|
+
const copilotHome = deps.copilotHome ?? process.env.COPILOT_HOME;
|
|
10607
|
+
return {
|
|
10608
|
+
home,
|
|
10609
|
+
cwd,
|
|
10610
|
+
copilotHome,
|
|
10611
|
+
findZam: deps.findZam ?? (() => findExecutable("zam")),
|
|
10612
|
+
detect: deps.detect ?? (() => detectInstalledConnectHarnesses({ home, copilotHome })),
|
|
10613
|
+
connectMcp: deps.connectMcp ?? connectHarnessMcp,
|
|
10614
|
+
writeConfig: deps.writeConfig ?? ((path, content) => {
|
|
10615
|
+
mkdirSync12(dirname9(path), { recursive: true });
|
|
10616
|
+
writeFileSync10(path, content, "utf-8");
|
|
10617
|
+
}),
|
|
10618
|
+
installCopilot: deps.installCopilot ?? installCopilotExtension,
|
|
10619
|
+
installVscode: deps.installVscode ?? installVscodeExtension,
|
|
10620
|
+
resolveAntigravity: deps.resolveAntigravity ?? resolveAntigravityIdeExecutable,
|
|
10621
|
+
refreshSkills: deps.refreshSkills ?? distributeGlobalSkills
|
|
10622
|
+
};
|
|
10623
|
+
}
|
|
10624
|
+
function performAgentConnect(opts = {}, deps = {}) {
|
|
10625
|
+
const d = resolveDeps(deps);
|
|
10626
|
+
const dryRun = Boolean(opts.dryRun);
|
|
10627
|
+
const detected = opts.harness ? [opts.harness] : d.detect();
|
|
10628
|
+
const foundZam = d.findZam();
|
|
10629
|
+
const zamPath = foundZam ?? "zam";
|
|
10630
|
+
const results = [];
|
|
10631
|
+
for (const harness of detected) {
|
|
10632
|
+
const label = CONNECT_HARNESS_LABELS[harness];
|
|
10633
|
+
try {
|
|
10634
|
+
const prepared = d.connectMcp(harness, {
|
|
10635
|
+
zamPath,
|
|
10636
|
+
cwd: d.cwd,
|
|
10637
|
+
home: d.home,
|
|
10638
|
+
copilotHome: d.copilotHome
|
|
10639
|
+
});
|
|
10640
|
+
let extension = null;
|
|
10641
|
+
if (harness === "copilot") {
|
|
10642
|
+
const installed = d.installCopilot({
|
|
10643
|
+
home: d.home,
|
|
10644
|
+
zamPath,
|
|
10645
|
+
dryRun
|
|
10646
|
+
});
|
|
10647
|
+
extension = {
|
|
10648
|
+
kind: "copilot",
|
|
10649
|
+
action: installed.action,
|
|
10650
|
+
location: installed.destinationDir,
|
|
10651
|
+
detail: `${installed.launch.command} ${installed.launch.args.join(" ")}`
|
|
10652
|
+
};
|
|
10653
|
+
} else if (harness === "vscode") {
|
|
10654
|
+
const installed = d.installVscode({ home: d.home, zamPath, dryRun });
|
|
10655
|
+
extension = {
|
|
10656
|
+
kind: "vscode",
|
|
10657
|
+
action: installed.action,
|
|
10658
|
+
location: installed.vsixPath,
|
|
10659
|
+
detail: installed.launchConfigPath
|
|
10660
|
+
};
|
|
10661
|
+
} else if (harness === "antigravity") {
|
|
10662
|
+
const antigravityPath = d.resolveAntigravity();
|
|
10663
|
+
if (antigravityPath) {
|
|
10664
|
+
const installed = d.installVscode({
|
|
10665
|
+
home: d.home,
|
|
10666
|
+
zamPath,
|
|
10667
|
+
codePath: antigravityPath,
|
|
10668
|
+
dryRun
|
|
10669
|
+
});
|
|
10670
|
+
extension = {
|
|
10671
|
+
kind: "vscode",
|
|
10672
|
+
action: installed.action,
|
|
10673
|
+
location: installed.vsixPath,
|
|
10674
|
+
detail: installed.launchConfigPath
|
|
10675
|
+
};
|
|
10676
|
+
}
|
|
10677
|
+
}
|
|
10678
|
+
let wrote = false;
|
|
10679
|
+
if (!prepared.alreadyConfigured && !dryRun) {
|
|
10680
|
+
d.writeConfig(prepared.path, prepared.content);
|
|
10681
|
+
wrote = true;
|
|
10682
|
+
}
|
|
10683
|
+
results.push({
|
|
10684
|
+
harness,
|
|
10685
|
+
label,
|
|
10686
|
+
path: prepared.path,
|
|
10687
|
+
content: prepared.content,
|
|
10688
|
+
alreadyConfigured: prepared.alreadyConfigured,
|
|
10689
|
+
wrote,
|
|
10690
|
+
hint: prepared.hint,
|
|
10691
|
+
extension
|
|
10692
|
+
});
|
|
10693
|
+
} catch (error) {
|
|
10694
|
+
results.push({
|
|
10695
|
+
harness,
|
|
10696
|
+
label,
|
|
10697
|
+
path: "",
|
|
10698
|
+
content: "",
|
|
10699
|
+
alreadyConfigured: false,
|
|
10700
|
+
wrote: false,
|
|
10701
|
+
hint: "",
|
|
10702
|
+
extension: null,
|
|
10703
|
+
error: error instanceof Error ? error.message : String(error)
|
|
10704
|
+
});
|
|
10705
|
+
}
|
|
10706
|
+
}
|
|
10707
|
+
let skills = null;
|
|
10708
|
+
if (!dryRun && detected.length > 0) {
|
|
10709
|
+
const skillResults = d.refreshSkills(d.home);
|
|
10710
|
+
skills = {
|
|
10711
|
+
refreshed: skillResults.filter((result) => result.success).length,
|
|
10712
|
+
total: skillResults.length
|
|
10713
|
+
};
|
|
10714
|
+
}
|
|
10715
|
+
return {
|
|
10716
|
+
success: results.every((result) => !result.error),
|
|
10717
|
+
detected,
|
|
10718
|
+
zamPath,
|
|
10719
|
+
zamOnPath: Boolean(foundZam),
|
|
10720
|
+
results,
|
|
10721
|
+
skills
|
|
10722
|
+
};
|
|
10723
|
+
}
|
|
10724
|
+
function inspectConnectHarnesses(deps = {}) {
|
|
10725
|
+
const d = resolveDeps(deps);
|
|
10726
|
+
const foundZam = d.findZam();
|
|
10727
|
+
const zamPath = foundZam ?? "zam";
|
|
10728
|
+
const installed = new Set(d.detect());
|
|
10729
|
+
const harnesses = USER_SCOPED_CONNECT_HARNESSES.map((harness) => {
|
|
10730
|
+
const status = {
|
|
10731
|
+
harness,
|
|
10732
|
+
label: CONNECT_HARNESS_LABELS[harness],
|
|
10733
|
+
installed: installed.has(harness),
|
|
10734
|
+
configured: false,
|
|
10735
|
+
configPath: ""
|
|
10736
|
+
};
|
|
10737
|
+
try {
|
|
10738
|
+
const probe = d.connectMcp(harness, {
|
|
10739
|
+
zamPath,
|
|
10740
|
+
cwd: d.cwd,
|
|
10741
|
+
home: d.home,
|
|
10742
|
+
copilotHome: d.copilotHome
|
|
10743
|
+
});
|
|
10744
|
+
status.configured = probe.alreadyConfigured;
|
|
10745
|
+
status.configPath = probe.path;
|
|
10746
|
+
} catch (error) {
|
|
10747
|
+
status.note = error instanceof Error ? error.message : String(error);
|
|
10748
|
+
}
|
|
10749
|
+
return status;
|
|
10750
|
+
});
|
|
10751
|
+
return { zamOnPath: Boolean(foundZam), harnesses };
|
|
10752
|
+
}
|
|
9970
10753
|
|
|
9971
10754
|
// src/cli/curriculum/breadcrumb.ts
|
|
9972
10755
|
init_kernel();
|
|
@@ -12633,9 +13416,9 @@ function getCurriculumProvider(id) {
|
|
|
12633
13416
|
// src/cli/llm/vision.ts
|
|
12634
13417
|
init_kernel();
|
|
12635
13418
|
import { randomBytes } from "crypto";
|
|
12636
|
-
import { readFileSync as
|
|
13419
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
12637
13420
|
import { tmpdir as tmpdir2 } from "os";
|
|
12638
|
-
import { basename as basename4, join as
|
|
13421
|
+
import { basename as basename4, join as join19 } from "path";
|
|
12639
13422
|
var LANGUAGE_NAMES2 = {
|
|
12640
13423
|
en: "English",
|
|
12641
13424
|
de: "German",
|
|
@@ -12671,13 +13454,13 @@ async function observeUiSnapshotViaLLM(db, input) {
|
|
|
12671
13454
|
const imageUrls = [];
|
|
12672
13455
|
const isVideo = /\.(mp4|mov|m4v|avi|mkv|webm)$/i.test(input.imagePath);
|
|
12673
13456
|
if (isVideo) {
|
|
12674
|
-
const { mkdirSync:
|
|
13457
|
+
const { mkdirSync: mkdirSync15, readdirSync: readdirSync3, rmSync: rmSync4 } = await import("fs");
|
|
12675
13458
|
const { execSync: execSync5 } = await import("child_process");
|
|
12676
|
-
const tempDir =
|
|
13459
|
+
const tempDir = join19(
|
|
12677
13460
|
tmpdir2(),
|
|
12678
13461
|
`zam-frames-${randomBytes(4).toString("hex")}`
|
|
12679
13462
|
);
|
|
12680
|
-
|
|
13463
|
+
mkdirSync15(tempDir, { recursive: true });
|
|
12681
13464
|
try {
|
|
12682
13465
|
execSync5(
|
|
12683
13466
|
`ffmpeg -i "${input.imagePath}" -vf "fps=1/3,scale=1280:-1" -vsync vfr "${tempDir}/frame_%03d.png"`,
|
|
@@ -12706,7 +13489,7 @@ async function observeUiSnapshotViaLLM(db, input) {
|
|
|
12706
13489
|
}
|
|
12707
13490
|
}
|
|
12708
13491
|
for (const file of sampledFiles) {
|
|
12709
|
-
const bytes =
|
|
13492
|
+
const bytes = readFileSync14(join19(tempDir, file));
|
|
12710
13493
|
imageUrls.push(`data:image/png;base64,${bytes.toString("base64")}`);
|
|
12711
13494
|
}
|
|
12712
13495
|
} finally {
|
|
@@ -12716,7 +13499,7 @@ async function observeUiSnapshotViaLLM(db, input) {
|
|
|
12716
13499
|
}
|
|
12717
13500
|
}
|
|
12718
13501
|
} else {
|
|
12719
|
-
const imageBytes =
|
|
13502
|
+
const imageBytes = readFileSync14(input.imagePath);
|
|
12720
13503
|
const ext = input.imagePath.split(".").pop()?.toLowerCase();
|
|
12721
13504
|
const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
|
|
12722
13505
|
imageUrls.push(`data:${mime};base64,${imageBytes.toString("base64")}`);
|
|
@@ -13182,36 +13965,36 @@ async function withProviderScope(machine, action) {
|
|
|
13182
13965
|
|
|
13183
13966
|
// src/cli/provisioning/index.ts
|
|
13184
13967
|
import {
|
|
13185
|
-
existsSync as
|
|
13186
|
-
lstatSync,
|
|
13187
|
-
mkdirSync as
|
|
13188
|
-
readFileSync as
|
|
13968
|
+
existsSync as existsSync17,
|
|
13969
|
+
lstatSync as lstatSync2,
|
|
13970
|
+
mkdirSync as mkdirSync13,
|
|
13971
|
+
readFileSync as readFileSync15,
|
|
13189
13972
|
realpathSync,
|
|
13190
13973
|
rmSync as rmSync2,
|
|
13191
13974
|
symlinkSync,
|
|
13192
|
-
writeFileSync as
|
|
13975
|
+
writeFileSync as writeFileSync11
|
|
13193
13976
|
} from "fs";
|
|
13194
|
-
import { basename as basename5, dirname as
|
|
13195
|
-
import { fileURLToPath as
|
|
13196
|
-
var
|
|
13197
|
-
|
|
13198
|
-
|
|
13199
|
-
].find((candidate) =>
|
|
13977
|
+
import { basename as basename5, dirname as dirname10, join as join20 } from "path";
|
|
13978
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
13979
|
+
var packageRoot3 = [
|
|
13980
|
+
fileURLToPath5(new URL("../..", import.meta.url)),
|
|
13981
|
+
fileURLToPath5(new URL("../../..", import.meta.url))
|
|
13982
|
+
].find((candidate) => existsSync17(join20(candidate, "package.json"))) ?? fileURLToPath5(new URL("../..", import.meta.url));
|
|
13200
13983
|
var ALL_SETUP_AGENTS = ["claude", "copilot", "codex", "agent"];
|
|
13201
13984
|
var SKILL_PAIRS = [
|
|
13202
13985
|
{
|
|
13203
|
-
from:
|
|
13204
|
-
to:
|
|
13986
|
+
from: join20(packageRoot3, ".claude", "skills", "zam", "SKILL.md"),
|
|
13987
|
+
to: join20(".claude", "skills", "zam", "SKILL.md"),
|
|
13205
13988
|
agents: ["claude", "copilot"]
|
|
13206
13989
|
},
|
|
13207
13990
|
{
|
|
13208
|
-
from:
|
|
13209
|
-
to:
|
|
13991
|
+
from: join20(packageRoot3, ".agent", "skills", "zam", "SKILL.md"),
|
|
13992
|
+
to: join20(".agent", "skills", "zam", "SKILL.md"),
|
|
13210
13993
|
agents: ["agent"]
|
|
13211
13994
|
},
|
|
13212
13995
|
{
|
|
13213
|
-
from:
|
|
13214
|
-
to:
|
|
13996
|
+
from: join20(packageRoot3, ".agents", "skills", "zam", "SKILL.md"),
|
|
13997
|
+
to: join20(".agents", "skills", "zam", "SKILL.md"),
|
|
13215
13998
|
agents: ["codex"]
|
|
13216
13999
|
}
|
|
13217
14000
|
];
|
|
@@ -13241,7 +14024,7 @@ function parseSetupAgents(value) {
|
|
|
13241
14024
|
}
|
|
13242
14025
|
function pathExists(path) {
|
|
13243
14026
|
try {
|
|
13244
|
-
|
|
14027
|
+
lstatSync2(path);
|
|
13245
14028
|
return true;
|
|
13246
14029
|
} catch {
|
|
13247
14030
|
return false;
|
|
@@ -13249,7 +14032,7 @@ function pathExists(path) {
|
|
|
13249
14032
|
}
|
|
13250
14033
|
function isSymbolicLink(path) {
|
|
13251
14034
|
try {
|
|
13252
|
-
return
|
|
14035
|
+
return lstatSync2(path).isSymbolicLink();
|
|
13253
14036
|
} catch {
|
|
13254
14037
|
return false;
|
|
13255
14038
|
}
|
|
@@ -13270,16 +14053,16 @@ function linkPointsTo(sourceDir, destinationDir) {
|
|
|
13270
14053
|
}
|
|
13271
14054
|
function isZamSkillCopy(destinationDir) {
|
|
13272
14055
|
try {
|
|
13273
|
-
if (!
|
|
13274
|
-
const skillFile =
|
|
13275
|
-
if (!
|
|
13276
|
-
return /^name:\s*zam\s*$/m.test(
|
|
14056
|
+
if (!lstatSync2(destinationDir).isDirectory()) return false;
|
|
14057
|
+
const skillFile = join20(destinationDir, "SKILL.md");
|
|
14058
|
+
if (!existsSync17(skillFile)) return false;
|
|
14059
|
+
return /^name:\s*zam\s*$/m.test(readFileSync15(skillFile, "utf8"));
|
|
13277
14060
|
} catch {
|
|
13278
14061
|
return false;
|
|
13279
14062
|
}
|
|
13280
14063
|
}
|
|
13281
14064
|
function classifySkillDestination(sourceDir, destinationDir) {
|
|
13282
|
-
if (!
|
|
14065
|
+
if (!existsSync17(sourceDir)) return "source-missing";
|
|
13283
14066
|
if (!isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir)) {
|
|
13284
14067
|
return "source-directory";
|
|
13285
14068
|
}
|
|
@@ -13296,9 +14079,9 @@ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {})
|
|
|
13296
14079
|
};
|
|
13297
14080
|
for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
|
|
13298
14081
|
if (!pairAgents.some((agent) => agents.has(agent))) continue;
|
|
13299
|
-
const sourceDir =
|
|
13300
|
-
const destinationDir =
|
|
13301
|
-
const label =
|
|
14082
|
+
const sourceDir = dirname10(from);
|
|
14083
|
+
const destinationDir = dirname10(join20(cwd, to));
|
|
14084
|
+
const label = dirname10(to);
|
|
13302
14085
|
const state = classifySkillDestination(sourceDir, destinationDir);
|
|
13303
14086
|
if (state === "source-missing") {
|
|
13304
14087
|
if (!opts.quiet) {
|
|
@@ -13355,7 +14138,7 @@ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {})
|
|
|
13355
14138
|
if (destinationExists) {
|
|
13356
14139
|
rmSync2(destinationDir, { recursive: true, force: true });
|
|
13357
14140
|
}
|
|
13358
|
-
|
|
14141
|
+
mkdirSync13(dirname10(destinationDir), { recursive: true });
|
|
13359
14142
|
symlinkSync(
|
|
13360
14143
|
sourceDir,
|
|
13361
14144
|
destinationDir,
|
|
@@ -13371,8 +14154,8 @@ function inspectSkillLinks(cwd = process.cwd(), agents = parseSetupAgents()) {
|
|
|
13371
14154
|
const results = [];
|
|
13372
14155
|
for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
|
|
13373
14156
|
if (!pairAgents.some((agent) => agents.has(agent))) continue;
|
|
13374
|
-
const sourceDir =
|
|
13375
|
-
const destinationDir =
|
|
14157
|
+
const sourceDir = dirname10(from);
|
|
14158
|
+
const destinationDir = dirname10(join20(cwd, to));
|
|
13376
14159
|
results.push({
|
|
13377
14160
|
agents: pairAgents,
|
|
13378
14161
|
source: sourceDir,
|
|
@@ -13416,13 +14199,13 @@ async function resolveUser(opts, db, resolveOpts) {
|
|
|
13416
14199
|
}
|
|
13417
14200
|
|
|
13418
14201
|
// src/cli/workspaces/backup.ts
|
|
13419
|
-
import { mkdirSync as
|
|
13420
|
-
import { join as
|
|
14202
|
+
import { mkdirSync as mkdirSync14 } from "fs";
|
|
14203
|
+
import { join as join21 } from "path";
|
|
13421
14204
|
async function backupDatabaseTo(db, targetDir) {
|
|
13422
|
-
const backupDir =
|
|
13423
|
-
|
|
14205
|
+
const backupDir = join21(targetDir, "zam-backups");
|
|
14206
|
+
mkdirSync14(backupDir, { recursive: true });
|
|
13424
14207
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
13425
|
-
const dest =
|
|
14208
|
+
const dest = join21(backupDir, `zam-${stamp}.db`);
|
|
13426
14209
|
await db.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
|
|
13427
14210
|
return dest;
|
|
13428
14211
|
}
|
|
@@ -13553,20 +14336,20 @@ bridgeCommand.command("workspace-info").description("Report the workspace dir, i
|
|
|
13553
14336
|
activeWorkspace,
|
|
13554
14337
|
workspaceDir: activeWorkspace.path,
|
|
13555
14338
|
defaultWorkspaceDir: defaultWorkspaceDir(),
|
|
13556
|
-
dataDir:
|
|
14339
|
+
dataDir: join22(homedir14(), ".zam")
|
|
13557
14340
|
});
|
|
13558
14341
|
});
|
|
13559
14342
|
});
|
|
13560
14343
|
function provisionConfiguredWorkspaces() {
|
|
13561
14344
|
return getConfiguredWorkspaces().flatMap(
|
|
13562
|
-
(workspace) =>
|
|
14345
|
+
(workspace) => existsSync18(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
|
|
13563
14346
|
);
|
|
13564
14347
|
}
|
|
13565
14348
|
function buildWorkspaceLinkHealth(workspaces) {
|
|
13566
14349
|
const agents = parseSetupAgents();
|
|
13567
14350
|
const map = {};
|
|
13568
14351
|
for (const workspace of workspaces) {
|
|
13569
|
-
if (!
|
|
14352
|
+
if (!existsSync18(workspace.path)) continue;
|
|
13570
14353
|
const links = inspectSkillLinks(workspace.path, agents);
|
|
13571
14354
|
map[workspace.id] = {
|
|
13572
14355
|
health: summarizeSkillLinkHealth(links),
|
|
@@ -13596,7 +14379,7 @@ bridgeCommand.command("workspace-list").description("List configured ZAM workspa
|
|
|
13596
14379
|
activeWorkspace,
|
|
13597
14380
|
workspaceDir: activeWorkspace.path,
|
|
13598
14381
|
defaultWorkspaceDir: defaultWorkspaceDir(),
|
|
13599
|
-
dataDir:
|
|
14382
|
+
dataDir: join22(homedir14(), ".zam"),
|
|
13600
14383
|
linkHealth: buildWorkspaceLinkHealth(workspaces)
|
|
13601
14384
|
});
|
|
13602
14385
|
});
|
|
@@ -13611,7 +14394,7 @@ bridgeCommand.command("workspace-repair-links").description(
|
|
|
13611
14394
|
if (!id) jsonError("A non-empty --id is required");
|
|
13612
14395
|
const workspace = getConfiguredWorkspaces().find((item) => item.id === id);
|
|
13613
14396
|
if (!workspace) jsonError(`Workspace "${id}" is not configured`);
|
|
13614
|
-
if (!
|
|
14397
|
+
if (!existsSync18(workspace.path)) {
|
|
13615
14398
|
jsonError(`Workspace path does not exist: ${workspace.path}`);
|
|
13616
14399
|
}
|
|
13617
14400
|
const agents = parseSetupAgents(opts.agents);
|
|
@@ -13642,8 +14425,8 @@ function parseBridgeWorkspaceKind(value) {
|
|
|
13642
14425
|
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) => {
|
|
13643
14426
|
const raw = String(opts.path ?? "").trim();
|
|
13644
14427
|
if (!raw) jsonError("A non-empty --path is required");
|
|
13645
|
-
const path =
|
|
13646
|
-
if (!
|
|
14428
|
+
const path = resolve5(raw);
|
|
14429
|
+
if (!existsSync18(path)) jsonError(`Workspace path does not exist: ${path}`);
|
|
13647
14430
|
const id = opts.id ? String(opts.id).trim() : void 0;
|
|
13648
14431
|
if (opts.id && !id) jsonError("A non-empty --id is required");
|
|
13649
14432
|
const kind = parseBridgeWorkspaceKind(opts.kind);
|
|
@@ -13687,8 +14470,8 @@ bridgeCommand.command("workspace-remove").description("Unregister a ZAM workspac
|
|
|
13687
14470
|
bridgeCommand.command("set-workspace-dir").description("Set the personal workspace directory (JSON)").requiredOption("--dir <path>", "Path to the workspace directory").action(async (opts) => {
|
|
13688
14471
|
const raw = String(opts.dir ?? "").trim();
|
|
13689
14472
|
if (!raw) jsonError("A non-empty --dir is required");
|
|
13690
|
-
const dir =
|
|
13691
|
-
if (!
|
|
14473
|
+
const dir = resolve5(raw);
|
|
14474
|
+
if (!existsSync18(dir)) jsonError(`Workspace path does not exist: ${dir}`);
|
|
13692
14475
|
const skillLinks = wireSkills(dir, parseSetupAgents(), { quiet: true });
|
|
13693
14476
|
await withDb2(async (db) => {
|
|
13694
14477
|
const workspace = await activateWorkspacePath(db, dir);
|
|
@@ -13751,7 +14534,7 @@ bridgeCommand.command("agent-open").description("Launch an agent harness in the
|
|
|
13751
14534
|
jsonError(`Workspace is not configured: ${opts.workspace}`);
|
|
13752
14535
|
}
|
|
13753
14536
|
const activeWorkspace = await ensureActiveWorkspace(db);
|
|
13754
|
-
const workspace = opts.dir ?
|
|
14537
|
+
const workspace = opts.dir ? existsSync18(opts.dir) ? opts.dir : homedir14() : existingWorkspaceDirOrHome(configuredWorkspace ?? activeWorkspace);
|
|
13755
14538
|
launchHarness(harness, {
|
|
13756
14539
|
executable,
|
|
13757
14540
|
workspace,
|
|
@@ -14123,7 +14906,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
14123
14906
|
"20"
|
|
14124
14907
|
).action(async (opts) => {
|
|
14125
14908
|
try {
|
|
14126
|
-
const monitorDir =
|
|
14909
|
+
const monitorDir = join22(homedir14(), ".zam", "monitor");
|
|
14127
14910
|
let files;
|
|
14128
14911
|
try {
|
|
14129
14912
|
files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
|
|
@@ -14136,7 +14919,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
14136
14919
|
return;
|
|
14137
14920
|
}
|
|
14138
14921
|
const limit = Number(opts.limit);
|
|
14139
|
-
const sorted = files.map((f) => ({ name: f, path:
|
|
14922
|
+
const sorted = files.map((f) => ({ name: f, path: join22(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
|
|
14140
14923
|
const sessionCommands = /* @__PURE__ */ new Map();
|
|
14141
14924
|
for (const file of sorted) {
|
|
14142
14925
|
const sessionId = file.name.replace(".jsonl", "");
|
|
@@ -14248,7 +15031,7 @@ bridgeCommand.command("observe-ui-snapshot").description(
|
|
|
14248
15031
|
});
|
|
14249
15032
|
function resolveWindowsPowerShell() {
|
|
14250
15033
|
try {
|
|
14251
|
-
|
|
15034
|
+
execFileSync4("where.exe", ["pwsh.exe"], { stdio: "ignore" });
|
|
14252
15035
|
return "pwsh";
|
|
14253
15036
|
} catch {
|
|
14254
15037
|
return "powershell";
|
|
@@ -14263,7 +15046,7 @@ function captureScreenshot(outputPath, hwnd, processName) {
|
|
|
14263
15046
|
throw new Error(`Invalid process name format: ${processName}`);
|
|
14264
15047
|
}
|
|
14265
15048
|
if (platform === "win32") {
|
|
14266
|
-
const stdout =
|
|
15049
|
+
const stdout = execFileSync4(
|
|
14267
15050
|
resolveWindowsPowerShell(),
|
|
14268
15051
|
[
|
|
14269
15052
|
"-NoProfile",
|
|
@@ -14576,13 +15359,13 @@ Write-CaptureResult "fullscreen" $hwndVal $matchedBy
|
|
|
14576
15359
|
} else if (platform === "darwin") {
|
|
14577
15360
|
if (hwnd) {
|
|
14578
15361
|
const parsedHwnd = hwnd.startsWith("0x") ? parseInt(hwnd, 16) : parseInt(hwnd, 10);
|
|
14579
|
-
|
|
15362
|
+
execFileSync4("screencapture", ["-l", String(parsedHwnd), outputPath], {
|
|
14580
15363
|
stdio: "pipe"
|
|
14581
15364
|
});
|
|
14582
15365
|
return { method: "screencapture-window", target: null };
|
|
14583
15366
|
} else if (processName) {
|
|
14584
15367
|
try {
|
|
14585
|
-
const windowId =
|
|
15368
|
+
const windowId = execFileSync4(
|
|
14586
15369
|
"osascript",
|
|
14587
15370
|
[
|
|
14588
15371
|
"-e",
|
|
@@ -14591,17 +15374,17 @@ Write-CaptureResult "fullscreen" $hwndVal $matchedBy
|
|
|
14591
15374
|
{ encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }
|
|
14592
15375
|
).trim();
|
|
14593
15376
|
if (windowId && /^\d+$/.test(windowId)) {
|
|
14594
|
-
|
|
15377
|
+
execFileSync4("screencapture", ["-l", windowId, outputPath], {
|
|
14595
15378
|
stdio: "pipe"
|
|
14596
15379
|
});
|
|
14597
15380
|
return { method: "screencapture-window", target: null };
|
|
14598
15381
|
}
|
|
14599
15382
|
} catch {
|
|
14600
15383
|
}
|
|
14601
|
-
|
|
15384
|
+
execFileSync4("screencapture", ["-x", outputPath], { stdio: "pipe" });
|
|
14602
15385
|
return { method: "screencapture-full", target: null };
|
|
14603
15386
|
} else {
|
|
14604
|
-
|
|
15387
|
+
execFileSync4("screencapture", ["-x", outputPath], { stdio: "pipe" });
|
|
14605
15388
|
return { method: "screencapture-full", target: null };
|
|
14606
15389
|
}
|
|
14607
15390
|
} else {
|
|
@@ -14638,7 +15421,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
|
|
|
14638
15421
|
return;
|
|
14639
15422
|
}
|
|
14640
15423
|
}
|
|
14641
|
-
const outputPath = opts.image ?? opts.output ??
|
|
15424
|
+
const outputPath = opts.image ?? opts.output ?? join22(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
|
|
14642
15425
|
const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
|
|
14643
15426
|
if (!isProvided) {
|
|
14644
15427
|
const post = decidePostCapture(policy, {
|
|
@@ -14666,7 +15449,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
|
|
|
14666
15449
|
return;
|
|
14667
15450
|
}
|
|
14668
15451
|
}
|
|
14669
|
-
const imageBytes =
|
|
15452
|
+
const imageBytes = readFileSync16(outputPath);
|
|
14670
15453
|
const base64 = imageBytes.toString("base64");
|
|
14671
15454
|
jsonOut({
|
|
14672
15455
|
sessionId: opts.session ?? null,
|
|
@@ -14693,11 +15476,11 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
14693
15476
|
return;
|
|
14694
15477
|
}
|
|
14695
15478
|
const sessionId = opts.session;
|
|
14696
|
-
const statePath =
|
|
15479
|
+
const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
14697
15480
|
const defaultExt = platform === "win32" ? ".mkv" : ".mov";
|
|
14698
|
-
const outputPath = opts.output ??
|
|
14699
|
-
const { existsSync:
|
|
14700
|
-
if (
|
|
15481
|
+
const outputPath = opts.output ?? join22(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
|
|
15482
|
+
const { existsSync: existsSync20, writeFileSync: writeFileSync12, openSync, closeSync } = await import("fs");
|
|
15483
|
+
if (existsSync20(statePath)) {
|
|
14701
15484
|
jsonOut({
|
|
14702
15485
|
sessionId,
|
|
14703
15486
|
started: false,
|
|
@@ -14705,7 +15488,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
14705
15488
|
});
|
|
14706
15489
|
return;
|
|
14707
15490
|
}
|
|
14708
|
-
const logPath =
|
|
15491
|
+
const logPath = join22(tmpdir3(), `zam-recording-${sessionId}.log`);
|
|
14709
15492
|
let logFd;
|
|
14710
15493
|
try {
|
|
14711
15494
|
logFd = openSync(logPath, "w");
|
|
@@ -14751,7 +15534,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
14751
15534
|
}
|
|
14752
15535
|
child.unref();
|
|
14753
15536
|
if (child.pid) {
|
|
14754
|
-
|
|
15537
|
+
writeFileSync12(
|
|
14755
15538
|
statePath,
|
|
14756
15539
|
JSON.stringify({
|
|
14757
15540
|
pid: child.pid,
|
|
@@ -14787,9 +15570,9 @@ bridgeCommand.command("stop-recording").description(
|
|
|
14787
15570
|
return;
|
|
14788
15571
|
}
|
|
14789
15572
|
const sessionId = opts.session;
|
|
14790
|
-
const statePath =
|
|
14791
|
-
const { existsSync:
|
|
14792
|
-
if (!
|
|
15573
|
+
const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
15574
|
+
const { existsSync: existsSync20, readFileSync: readFileSync18, rmSync: rmSync4 } = await import("fs");
|
|
15575
|
+
if (!existsSync20(statePath)) {
|
|
14793
15576
|
jsonOut({
|
|
14794
15577
|
sessionId,
|
|
14795
15578
|
stopped: false,
|
|
@@ -14797,7 +15580,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
14797
15580
|
});
|
|
14798
15581
|
return;
|
|
14799
15582
|
}
|
|
14800
|
-
const state = JSON.parse(
|
|
15583
|
+
const state = JSON.parse(readFileSync18(statePath, "utf8"));
|
|
14801
15584
|
const { pid, outputPath } = state;
|
|
14802
15585
|
try {
|
|
14803
15586
|
process.kill(pid, "SIGINT");
|
|
@@ -14813,7 +15596,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
14813
15596
|
};
|
|
14814
15597
|
let attempts = 0;
|
|
14815
15598
|
while (isProcessRunning(pid) && attempts < 20) {
|
|
14816
|
-
await new Promise((
|
|
15599
|
+
await new Promise((resolve6) => setTimeout(resolve6, 250));
|
|
14817
15600
|
attempts++;
|
|
14818
15601
|
}
|
|
14819
15602
|
if (isProcessRunning(pid)) {
|
|
@@ -14826,7 +15609,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
14826
15609
|
rmSync4(statePath, { force: true });
|
|
14827
15610
|
} catch {
|
|
14828
15611
|
}
|
|
14829
|
-
if (!
|
|
15612
|
+
if (!existsSync20(outputPath)) {
|
|
14830
15613
|
jsonOut({
|
|
14831
15614
|
sessionId,
|
|
14832
15615
|
stopped: false,
|
|
@@ -15063,7 +15846,7 @@ bridgeCommand.command("cloud-model-hint").description("Suggest a cloud model for
|
|
|
15063
15846
|
});
|
|
15064
15847
|
bridgeCommand.command("local-llm-hints").description("Detect installed local LLM servers and suggest defaults (JSON)").action(() => {
|
|
15065
15848
|
const profile = getSystemProfile();
|
|
15066
|
-
const flmInstalled = hasCommand("flm") ||
|
|
15849
|
+
const flmInstalled = hasCommand("flm") || existsSync18("C:\\Program Files\\flm\\flm.exe");
|
|
15067
15850
|
const ollamaInstalled = isOllamaInstalled();
|
|
15068
15851
|
const runners = [
|
|
15069
15852
|
{ id: "flm", label: "FastFlowLM", installed: flmInstalled },
|
|
@@ -15203,6 +15986,93 @@ bridgeCommand.command("evaluate-answer").description(
|
|
|
15203
15986
|
}
|
|
15204
15987
|
});
|
|
15205
15988
|
});
|
|
15989
|
+
function parseDiscussionThread(raw) {
|
|
15990
|
+
if (!raw) return [];
|
|
15991
|
+
let parsed;
|
|
15992
|
+
try {
|
|
15993
|
+
parsed = JSON.parse(raw);
|
|
15994
|
+
} catch {
|
|
15995
|
+
throw new Error("Invalid --thread: not valid JSON");
|
|
15996
|
+
}
|
|
15997
|
+
if (!Array.isArray(parsed)) {
|
|
15998
|
+
throw new Error("Invalid --thread: expected a JSON array of turns");
|
|
15999
|
+
}
|
|
16000
|
+
return parsed.map((turn, index) => {
|
|
16001
|
+
const candidate = turn;
|
|
16002
|
+
if (candidate?.role !== "user" && candidate?.role !== "assistant" || typeof candidate?.content !== "string") {
|
|
16003
|
+
throw new Error(
|
|
16004
|
+
`Invalid --thread: turn ${index} must be {"role": "user"|"assistant", "content": string}`
|
|
16005
|
+
);
|
|
16006
|
+
}
|
|
16007
|
+
return { role: candidate.role, content: candidate.content };
|
|
16008
|
+
});
|
|
16009
|
+
}
|
|
16010
|
+
bridgeCommand.command("discuss-review").description(
|
|
16011
|
+
"Answer one turn of the post-reveal follow-up discussion about a card (JSON)"
|
|
16012
|
+
).requiredOption("--slug <slug>", "Token slug").requiredOption("--concept <concept>", "Target concept text").requiredOption("--domain <domain>", "Token domain").requiredOption("--bloom-level <level>", "Bloom taxonomy level").requiredOption("--question <question>", "Question prompt presented").requiredOption("--user-answer <answer>", "User's typed answer").requiredOption("--message <text>", "The learner's newest discussion turn").option("--feedback <text>", "AI feedback already shown for this answer").option(
|
|
16013
|
+
"--thread <json>",
|
|
16014
|
+
'Prior turns, oldest first, as JSON: [{"role":"user"|"assistant","content":"\u2026"},\u2026]'
|
|
16015
|
+
).option("--context <context>", "Optional token context details").option("--source-link <link>", "Optional source link").option(
|
|
16016
|
+
"--source-content <content>",
|
|
16017
|
+
"Pre-resolved source reference content (skips re-fetch when set)"
|
|
16018
|
+
).action(async (opts) => {
|
|
16019
|
+
await withDb2(async (db) => {
|
|
16020
|
+
const isEnabled = await getSetting(db, "llm.enabled") === "true";
|
|
16021
|
+
if (!isEnabled) {
|
|
16022
|
+
jsonOut({
|
|
16023
|
+
success: false,
|
|
16024
|
+
error: "LLM integration is disabled",
|
|
16025
|
+
reply: ""
|
|
16026
|
+
});
|
|
16027
|
+
return;
|
|
16028
|
+
}
|
|
16029
|
+
let thread;
|
|
16030
|
+
try {
|
|
16031
|
+
thread = parseDiscussionThread(opts.thread);
|
|
16032
|
+
} catch (err) {
|
|
16033
|
+
jsonOut({
|
|
16034
|
+
success: false,
|
|
16035
|
+
error: err.message,
|
|
16036
|
+
reply: ""
|
|
16037
|
+
});
|
|
16038
|
+
return;
|
|
16039
|
+
}
|
|
16040
|
+
let resolvedContextContent = opts.sourceContent ?? null;
|
|
16041
|
+
if (resolvedContextContent == null && opts.sourceLink) {
|
|
16042
|
+
try {
|
|
16043
|
+
const resolved = await resolveReviewContext(opts.sourceLink);
|
|
16044
|
+
resolvedContextContent = resolved?.content ?? null;
|
|
16045
|
+
} catch {
|
|
16046
|
+
}
|
|
16047
|
+
}
|
|
16048
|
+
try {
|
|
16049
|
+
const result = await discussReviewViaLLM(db, {
|
|
16050
|
+
slug: opts.slug,
|
|
16051
|
+
concept: opts.concept,
|
|
16052
|
+
domain: opts.domain,
|
|
16053
|
+
bloomLevel: Number(opts.bloomLevel),
|
|
16054
|
+
context: opts.context,
|
|
16055
|
+
question: opts.question,
|
|
16056
|
+
userAnswer: opts.userAnswer,
|
|
16057
|
+
sourceLinkContent: resolvedContextContent,
|
|
16058
|
+
feedback: opts.feedback ?? null,
|
|
16059
|
+
thread,
|
|
16060
|
+
message: opts.message
|
|
16061
|
+
});
|
|
16062
|
+
jsonOut({
|
|
16063
|
+
success: true,
|
|
16064
|
+
reply: result.text,
|
|
16065
|
+
replyModel: result.model
|
|
16066
|
+
});
|
|
16067
|
+
} catch (err) {
|
|
16068
|
+
jsonOut({
|
|
16069
|
+
success: false,
|
|
16070
|
+
error: err.message,
|
|
16071
|
+
reply: ""
|
|
16072
|
+
});
|
|
16073
|
+
}
|
|
16074
|
+
});
|
|
16075
|
+
});
|
|
15206
16076
|
bridgeCommand.command("desktop-bootstrap").description("Initialize first-run desktop state (JSON)").option("--user <id>", "Preferred user ID when none is configured").action(async (opts) => {
|
|
15207
16077
|
await withDb2(async (db) => {
|
|
15208
16078
|
const userId = await ensureDefaultUser(db, opts.user);
|
|
@@ -15231,6 +16101,54 @@ bridgeCommand.command("get-settings").description("Get active ZAM settings (JSON
|
|
|
15231
16101
|
});
|
|
15232
16102
|
});
|
|
15233
16103
|
});
|
|
16104
|
+
bridgeCommand.command("agent-harness-status").description(
|
|
16105
|
+
"Detect installed agent harnesses and their ZAM MCP configuration state (JSON)"
|
|
16106
|
+
).action(() => {
|
|
16107
|
+
const report = inspectConnectHarnesses();
|
|
16108
|
+
jsonOut({
|
|
16109
|
+
success: true,
|
|
16110
|
+
zamOnPath: report.zamOnPath,
|
|
16111
|
+
harnesses: report.harnesses
|
|
16112
|
+
});
|
|
16113
|
+
});
|
|
16114
|
+
bridgeCommand.command("agent-connect").description(
|
|
16115
|
+
"Run the idempotent agent-connect flow for one or all detected harnesses (JSON)"
|
|
16116
|
+
).option("--harness <id>", "Explicit harness id (default: all detected)").option(
|
|
16117
|
+
"--auto-once",
|
|
16118
|
+
"First-run mode: skip when the auto-connect marker is already set; set the marker after a run that detected at least one harness"
|
|
16119
|
+
).action(async (opts) => {
|
|
16120
|
+
if (opts.harness && !isConnectHarnessId(opts.harness)) {
|
|
16121
|
+
jsonOut({
|
|
16122
|
+
success: false,
|
|
16123
|
+
error: `Unsupported harness: ${opts.harness}`
|
|
16124
|
+
});
|
|
16125
|
+
return;
|
|
16126
|
+
}
|
|
16127
|
+
const harness = opts.harness;
|
|
16128
|
+
const run = () => {
|
|
16129
|
+
const report = performAgentConnect({ harness });
|
|
16130
|
+
return {
|
|
16131
|
+
success: report.success,
|
|
16132
|
+
detected: report.detected,
|
|
16133
|
+
zamOnPath: report.zamOnPath,
|
|
16134
|
+
results: report.results.map(({ content: _content, ...rest }) => rest),
|
|
16135
|
+
skills: report.skills
|
|
16136
|
+
};
|
|
16137
|
+
};
|
|
16138
|
+
if (opts.autoOnce) {
|
|
16139
|
+
if (getAgentConnectAutoDone()) {
|
|
16140
|
+
jsonOut({ success: true, skipped: true });
|
|
16141
|
+
return;
|
|
16142
|
+
}
|
|
16143
|
+
const payload = run();
|
|
16144
|
+
if (payload.detected.length > 0) {
|
|
16145
|
+
setAgentConnectAutoDone(true);
|
|
16146
|
+
}
|
|
16147
|
+
jsonOut(payload);
|
|
16148
|
+
return;
|
|
16149
|
+
}
|
|
16150
|
+
jsonOut(run());
|
|
16151
|
+
});
|
|
15234
16152
|
async function readDatabaseUserSummaries(db) {
|
|
15235
16153
|
return await db.prepare(
|
|
15236
16154
|
`SELECT user_id AS id, COUNT(*) AS cardCount
|
|
@@ -16336,12 +17254,12 @@ bridgeCommand.command("serve").description("Start the persistent JSON-RPC stdin/
|
|
|
16336
17254
|
});
|
|
16337
17255
|
|
|
16338
17256
|
// src/cli/commands/mcp.ts
|
|
16339
|
-
var __dirname =
|
|
16340
|
-
var pkgPath =
|
|
16341
|
-
if (!
|
|
16342
|
-
pkgPath =
|
|
17257
|
+
var __dirname = dirname11(fileURLToPath6(import.meta.url));
|
|
17258
|
+
var pkgPath = join23(__dirname, "..", "..", "package.json");
|
|
17259
|
+
if (!existsSync19(pkgPath)) {
|
|
17260
|
+
pkgPath = join23(__dirname, "..", "..", "..", "package.json");
|
|
16343
17261
|
}
|
|
16344
|
-
var pkg = JSON.parse(
|
|
17262
|
+
var pkg = JSON.parse(readFileSync17(pkgPath, "utf-8"));
|
|
16345
17263
|
var STUDIO_RESOURCE_URI = "ui://zam/studio";
|
|
16346
17264
|
var RECALL_RESOURCE_URI = "ui://zam/recall";
|
|
16347
17265
|
var GRAPH_RESOURCE_URI = "ui://zam/graph";
|
|
@@ -16366,13 +17284,13 @@ var STUDIO_BRIDGE_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
16366
17284
|
function loadPanelHtml(fileName, placeholderTitle) {
|
|
16367
17285
|
const candidates = [
|
|
16368
17286
|
// dist/cli/commands/mcp.js → dist/ui/
|
|
16369
|
-
|
|
17287
|
+
join23(__dirname, "..", "..", "ui", fileName),
|
|
16370
17288
|
// src/cli/commands/mcp.ts via tsx → <repo>/dist/ui/
|
|
16371
|
-
|
|
17289
|
+
join23(__dirname, "..", "..", "..", "dist", "ui", fileName)
|
|
16372
17290
|
];
|
|
16373
17291
|
for (const candidate of candidates) {
|
|
16374
|
-
if (
|
|
16375
|
-
return
|
|
17292
|
+
if (existsSync19(candidate)) {
|
|
17293
|
+
return readFileSync17(candidate, "utf-8");
|
|
16376
17294
|
}
|
|
16377
17295
|
}
|
|
16378
17296
|
return `<!doctype html>
|