zam-core 0.10.2 → 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/.agent/skills/zam/SKILL.md +31 -5
- package/.agents/skills/zam/SKILL.md +31 -5
- package/.claude/skills/zam/SKILL.md +31 -5
- package/dist/cli/app.js +960 -298
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/commands/mcp.js +1130 -114
- 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.4.vsix +0 -0
- package/dist/vscode-extension/extension.cjs +109 -0
- package/dist/vscode-extension/host.bundle.js +5331 -0
- package/dist/vscode-extension/manifest.json +5 -0
- package/package.json +3 -2
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(".");
|
|
@@ -9438,15 +9511,76 @@ async function updateCheck(params) {
|
|
|
9438
9511
|
});
|
|
9439
9512
|
}
|
|
9440
9513
|
|
|
9514
|
+
// src/cli/ui-intent.ts
|
|
9515
|
+
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
9516
|
+
import { homedir as homedir9 } from "os";
|
|
9517
|
+
import { dirname as dirname6, join as join14 } from "path";
|
|
9518
|
+
import { ulid as ulid9 } from "ulid";
|
|
9519
|
+
function getUiIntentPath(home = homedir9()) {
|
|
9520
|
+
return join14(home, ".zam", "ui-intent.json");
|
|
9521
|
+
}
|
|
9522
|
+
function getUiHostRegistrationPath(home = homedir9()) {
|
|
9523
|
+
return join14(home, ".zam", "vscode-host.json");
|
|
9524
|
+
}
|
|
9525
|
+
function compactStringInput(input) {
|
|
9526
|
+
return Object.fromEntries(
|
|
9527
|
+
Object.entries(input).filter(
|
|
9528
|
+
(entry) => typeof entry[1] === "string"
|
|
9529
|
+
)
|
|
9530
|
+
);
|
|
9531
|
+
}
|
|
9532
|
+
async function writeUiIntent(app, input = {}, opts = {}) {
|
|
9533
|
+
const path = opts.path ?? process.env.ZAM_UI_INTENT_PATH ?? getUiIntentPath();
|
|
9534
|
+
const id = opts.id ?? ulid9();
|
|
9535
|
+
const intent = {
|
|
9536
|
+
version: 1,
|
|
9537
|
+
id,
|
|
9538
|
+
app,
|
|
9539
|
+
input: compactStringInput(input),
|
|
9540
|
+
createdAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
|
|
9541
|
+
};
|
|
9542
|
+
const tempPath = join14(dirname6(path), `.ui-intent-${process.pid}-${id}.tmp`);
|
|
9543
|
+
await mkdir(dirname6(path), { recursive: true });
|
|
9544
|
+
await writeFile(tempPath, `${JSON.stringify(intent, null, 2)}
|
|
9545
|
+
`, "utf8");
|
|
9546
|
+
await rename(tempPath, path);
|
|
9547
|
+
return intent;
|
|
9548
|
+
}
|
|
9549
|
+
async function publishUiIntent(app, input = {}, opts = {}) {
|
|
9550
|
+
try {
|
|
9551
|
+
if (process.env.ZAM_DISABLE_UI_INTENT === "1") return void 0;
|
|
9552
|
+
const explicitPath = opts.path ?? process.env.ZAM_UI_INTENT_PATH;
|
|
9553
|
+
if (explicitPath) {
|
|
9554
|
+
return await writeUiIntent(app, input, { ...opts, path: explicitPath });
|
|
9555
|
+
}
|
|
9556
|
+
const now = (opts.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
9557
|
+
const registrationPath = opts.hostRegistrationPath ?? getUiHostRegistrationPath();
|
|
9558
|
+
const registration = JSON.parse(
|
|
9559
|
+
await readFile(registrationPath, "utf8")
|
|
9560
|
+
);
|
|
9561
|
+
const updatedAt = Date.parse(registration.updatedAt ?? "");
|
|
9562
|
+
if (registration.version !== 1 || typeof registration.intentPath !== "string" || !Number.isFinite(updatedAt) || now.getTime() - updatedAt > 15e3) {
|
|
9563
|
+
return void 0;
|
|
9564
|
+
}
|
|
9565
|
+
return await writeUiIntent(app, input, {
|
|
9566
|
+
...opts,
|
|
9567
|
+
path: registration.intentPath,
|
|
9568
|
+
now: () => now
|
|
9569
|
+
});
|
|
9570
|
+
} catch {
|
|
9571
|
+
return void 0;
|
|
9572
|
+
}
|
|
9573
|
+
}
|
|
9574
|
+
|
|
9441
9575
|
// src/cli/commands/bridge.ts
|
|
9442
9576
|
init_kernel();
|
|
9443
|
-
import { execFileSync as
|
|
9577
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
9444
9578
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
9445
|
-
import { existsSync as
|
|
9446
|
-
import { homedir as
|
|
9447
|
-
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";
|
|
9448
9582
|
import { Command } from "commander";
|
|
9449
|
-
import { ulid as
|
|
9583
|
+
import { ulid as ulid10 } from "ulid";
|
|
9450
9584
|
|
|
9451
9585
|
// src/cli/adapters/source-reader.ts
|
|
9452
9586
|
import dns from "dns";
|
|
@@ -9523,7 +9657,7 @@ async function readWebLink(url) {
|
|
|
9523
9657
|
redirect: "manual",
|
|
9524
9658
|
signal: controller.signal,
|
|
9525
9659
|
headers: {
|
|
9526
|
-
"User-Agent": "ZAM-Content-Studio/0.10.
|
|
9660
|
+
"User-Agent": "ZAM-Content-Studio/0.10.4"
|
|
9527
9661
|
}
|
|
9528
9662
|
});
|
|
9529
9663
|
if (res.status >= 300 && res.status < 400) {
|
|
@@ -9586,11 +9720,17 @@ async function readImageOCR(db, imagePath) {
|
|
|
9586
9720
|
return extractTextFromScanViaLLM(db, imagePath);
|
|
9587
9721
|
}
|
|
9588
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
|
+
|
|
9589
9729
|
// src/cli/agent-harness.ts
|
|
9590
9730
|
import { spawn as spawn2 } from "child_process";
|
|
9591
9731
|
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
|
|
9592
|
-
import { homedir as
|
|
9593
|
-
import { join as
|
|
9732
|
+
import { homedir as homedir10 } from "os";
|
|
9733
|
+
import { join as join16 } from "path";
|
|
9594
9734
|
|
|
9595
9735
|
// src/cli/terminal-open.ts
|
|
9596
9736
|
import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
|
|
@@ -9602,7 +9742,7 @@ import {
|
|
|
9602
9742
|
writeFileSync as writeFileSync7
|
|
9603
9743
|
} from "fs";
|
|
9604
9744
|
import { tmpdir } from "os";
|
|
9605
|
-
import { basename as basename3, delimiter, extname, isAbsolute, join as
|
|
9745
|
+
import { basename as basename3, delimiter, extname, isAbsolute, join as join15 } from "path";
|
|
9606
9746
|
function isPowerShellShell(shell) {
|
|
9607
9747
|
return shell === "pwsh" || shell === "powershell";
|
|
9608
9748
|
}
|
|
@@ -9675,7 +9815,7 @@ function findExecutable(command) {
|
|
|
9675
9815
|
const pathEntries = (process.env.PATH ?? "").split(delimiter).map((entry) => entry.trim()).filter(Boolean);
|
|
9676
9816
|
for (const entry of pathEntries) {
|
|
9677
9817
|
for (const name of windowsExecutableNames(normalized)) {
|
|
9678
|
-
const candidate =
|
|
9818
|
+
const candidate = join15(entry, name);
|
|
9679
9819
|
if (executableExists(candidate)) matches.push(candidate);
|
|
9680
9820
|
}
|
|
9681
9821
|
}
|
|
@@ -9713,7 +9853,7 @@ end tell` : `tell application "Terminal"
|
|
|
9713
9853
|
activate
|
|
9714
9854
|
do script "${escaped}"
|
|
9715
9855
|
end tell`;
|
|
9716
|
-
const tmpFile =
|
|
9856
|
+
const tmpFile = join15(tmpdir(), `zam-terminal-${label}.scpt`);
|
|
9717
9857
|
try {
|
|
9718
9858
|
writeFileSync7(tmpFile, appleScript);
|
|
9719
9859
|
execSync4(`osascript ${JSON.stringify(tmpFile)}`, { stdio: "ignore" });
|
|
@@ -9794,6 +9934,18 @@ function openTerminalWindow(opts) {
|
|
|
9794
9934
|
}
|
|
9795
9935
|
|
|
9796
9936
|
// src/cli/agent-harness.ts
|
|
9937
|
+
var ANTIGRAVITY_IDE_CANDIDATE_PATHS = {
|
|
9938
|
+
darwin: [
|
|
9939
|
+
join16(
|
|
9940
|
+
homedir10(),
|
|
9941
|
+
".antigravity-ide",
|
|
9942
|
+
"antigravity-ide",
|
|
9943
|
+
"bin",
|
|
9944
|
+
"antigravity-ide"
|
|
9945
|
+
),
|
|
9946
|
+
"/Applications/Antigravity IDE.app/Contents/Resources/app/bin/antigravity-ide"
|
|
9947
|
+
]
|
|
9948
|
+
};
|
|
9797
9949
|
var AGENT_HARNESSES = [
|
|
9798
9950
|
{ id: "claude-code", label: "Claude Code", kind: "cli", command: "claude" },
|
|
9799
9951
|
{ id: "codex", label: "Codex", kind: "cli", command: "codex" },
|
|
@@ -9805,7 +9957,7 @@ var AGENT_HARNESSES = [
|
|
|
9805
9957
|
command: "cursor",
|
|
9806
9958
|
candidatePaths: {
|
|
9807
9959
|
win32: [
|
|
9808
|
-
|
|
9960
|
+
join16(homedir10(), "AppData", "Local", "Programs", "cursor", "Cursor.exe")
|
|
9809
9961
|
],
|
|
9810
9962
|
darwin: ["/Applications/Cursor.app/Contents/MacOS/Cursor"]
|
|
9811
9963
|
}
|
|
@@ -9815,19 +9967,41 @@ var AGENT_HARNESSES = [
|
|
|
9815
9967
|
id: "antigravity",
|
|
9816
9968
|
label: "Antigravity",
|
|
9817
9969
|
kind: "app",
|
|
9818
|
-
command: "antigravity"
|
|
9970
|
+
command: "antigravity",
|
|
9971
|
+
candidatePaths: {
|
|
9972
|
+
darwin: [
|
|
9973
|
+
...ANTIGRAVITY_IDE_CANDIDATE_PATHS.darwin ?? [],
|
|
9974
|
+
"/Applications/Antigravity.app/Contents/MacOS/Antigravity"
|
|
9975
|
+
]
|
|
9976
|
+
}
|
|
9819
9977
|
},
|
|
9820
9978
|
{ id: "goose", label: "goose", kind: "cli", command: "goose" }
|
|
9821
9979
|
];
|
|
9822
9980
|
function getHarness(id) {
|
|
9823
9981
|
return AGENT_HARNESSES.find((h) => h.id === id);
|
|
9824
9982
|
}
|
|
9983
|
+
function resolveAntigravityIdeExecutable(deps = {}) {
|
|
9984
|
+
const find = deps.find ?? findExecutable;
|
|
9985
|
+
const exists = deps.exists ?? existsSync14;
|
|
9986
|
+
const platform = deps.platform ?? process.platform;
|
|
9987
|
+
const found = find("antigravity-ide");
|
|
9988
|
+
if (found) return found;
|
|
9989
|
+
return ANTIGRAVITY_IDE_CANDIDATE_PATHS[platform]?.find((path) => exists(path)) ?? null;
|
|
9990
|
+
}
|
|
9825
9991
|
function resolveHarnessExecutable(harness, overrideCommand, deps = {}) {
|
|
9826
9992
|
const find = deps.find ?? findExecutable;
|
|
9827
9993
|
const exists = deps.exists ?? existsSync14;
|
|
9828
9994
|
const platform = deps.platform ?? process.platform;
|
|
9829
9995
|
const found = find(overrideCommand || harness.command);
|
|
9830
9996
|
if (found) return found;
|
|
9997
|
+
if (!overrideCommand && harness.id === "antigravity") {
|
|
9998
|
+
const foundIde = resolveAntigravityIdeExecutable({
|
|
9999
|
+
find,
|
|
10000
|
+
exists,
|
|
10001
|
+
platform
|
|
10002
|
+
});
|
|
10003
|
+
if (foundIde) return foundIde;
|
|
10004
|
+
}
|
|
9831
10005
|
if (harness.kind === "app") {
|
|
9832
10006
|
for (const candidate of harness.candidatePaths?.[platform] ?? []) {
|
|
9833
10007
|
if (exists(candidate)) return candidate;
|
|
@@ -9872,6 +10046,710 @@ function launchHarness(harness, opts) {
|
|
|
9872
10046
|
console.log(`Launched ${harness.label} in ${opts.workspace}`);
|
|
9873
10047
|
}
|
|
9874
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
|
+
}
|
|
9875
10753
|
|
|
9876
10754
|
// src/cli/curriculum/breadcrumb.ts
|
|
9877
10755
|
init_kernel();
|
|
@@ -12538,9 +13416,9 @@ function getCurriculumProvider(id) {
|
|
|
12538
13416
|
// src/cli/llm/vision.ts
|
|
12539
13417
|
init_kernel();
|
|
12540
13418
|
import { randomBytes } from "crypto";
|
|
12541
|
-
import { readFileSync as
|
|
13419
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
12542
13420
|
import { tmpdir as tmpdir2 } from "os";
|
|
12543
|
-
import { basename as basename4, join as
|
|
13421
|
+
import { basename as basename4, join as join19 } from "path";
|
|
12544
13422
|
var LANGUAGE_NAMES2 = {
|
|
12545
13423
|
en: "English",
|
|
12546
13424
|
de: "German",
|
|
@@ -12576,13 +13454,13 @@ async function observeUiSnapshotViaLLM(db, input) {
|
|
|
12576
13454
|
const imageUrls = [];
|
|
12577
13455
|
const isVideo = /\.(mp4|mov|m4v|avi|mkv|webm)$/i.test(input.imagePath);
|
|
12578
13456
|
if (isVideo) {
|
|
12579
|
-
const { mkdirSync:
|
|
13457
|
+
const { mkdirSync: mkdirSync15, readdirSync: readdirSync3, rmSync: rmSync4 } = await import("fs");
|
|
12580
13458
|
const { execSync: execSync5 } = await import("child_process");
|
|
12581
|
-
const tempDir =
|
|
13459
|
+
const tempDir = join19(
|
|
12582
13460
|
tmpdir2(),
|
|
12583
13461
|
`zam-frames-${randomBytes(4).toString("hex")}`
|
|
12584
13462
|
);
|
|
12585
|
-
|
|
13463
|
+
mkdirSync15(tempDir, { recursive: true });
|
|
12586
13464
|
try {
|
|
12587
13465
|
execSync5(
|
|
12588
13466
|
`ffmpeg -i "${input.imagePath}" -vf "fps=1/3,scale=1280:-1" -vsync vfr "${tempDir}/frame_%03d.png"`,
|
|
@@ -12611,7 +13489,7 @@ async function observeUiSnapshotViaLLM(db, input) {
|
|
|
12611
13489
|
}
|
|
12612
13490
|
}
|
|
12613
13491
|
for (const file of sampledFiles) {
|
|
12614
|
-
const bytes =
|
|
13492
|
+
const bytes = readFileSync14(join19(tempDir, file));
|
|
12615
13493
|
imageUrls.push(`data:image/png;base64,${bytes.toString("base64")}`);
|
|
12616
13494
|
}
|
|
12617
13495
|
} finally {
|
|
@@ -12621,7 +13499,7 @@ async function observeUiSnapshotViaLLM(db, input) {
|
|
|
12621
13499
|
}
|
|
12622
13500
|
}
|
|
12623
13501
|
} else {
|
|
12624
|
-
const imageBytes =
|
|
13502
|
+
const imageBytes = readFileSync14(input.imagePath);
|
|
12625
13503
|
const ext = input.imagePath.split(".").pop()?.toLowerCase();
|
|
12626
13504
|
const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
|
|
12627
13505
|
imageUrls.push(`data:${mime};base64,${imageBytes.toString("base64")}`);
|
|
@@ -13087,36 +13965,36 @@ async function withProviderScope(machine, action) {
|
|
|
13087
13965
|
|
|
13088
13966
|
// src/cli/provisioning/index.ts
|
|
13089
13967
|
import {
|
|
13090
|
-
existsSync as
|
|
13091
|
-
lstatSync,
|
|
13092
|
-
mkdirSync as
|
|
13093
|
-
readFileSync as
|
|
13968
|
+
existsSync as existsSync17,
|
|
13969
|
+
lstatSync as lstatSync2,
|
|
13970
|
+
mkdirSync as mkdirSync13,
|
|
13971
|
+
readFileSync as readFileSync15,
|
|
13094
13972
|
realpathSync,
|
|
13095
13973
|
rmSync as rmSync2,
|
|
13096
13974
|
symlinkSync,
|
|
13097
|
-
writeFileSync as
|
|
13975
|
+
writeFileSync as writeFileSync11
|
|
13098
13976
|
} from "fs";
|
|
13099
|
-
import { basename as basename5, dirname as
|
|
13100
|
-
import { fileURLToPath as
|
|
13101
|
-
var
|
|
13102
|
-
|
|
13103
|
-
|
|
13104
|
-
].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));
|
|
13105
13983
|
var ALL_SETUP_AGENTS = ["claude", "copilot", "codex", "agent"];
|
|
13106
13984
|
var SKILL_PAIRS = [
|
|
13107
13985
|
{
|
|
13108
|
-
from:
|
|
13109
|
-
to:
|
|
13986
|
+
from: join20(packageRoot3, ".claude", "skills", "zam", "SKILL.md"),
|
|
13987
|
+
to: join20(".claude", "skills", "zam", "SKILL.md"),
|
|
13110
13988
|
agents: ["claude", "copilot"]
|
|
13111
13989
|
},
|
|
13112
13990
|
{
|
|
13113
|
-
from:
|
|
13114
|
-
to:
|
|
13991
|
+
from: join20(packageRoot3, ".agent", "skills", "zam", "SKILL.md"),
|
|
13992
|
+
to: join20(".agent", "skills", "zam", "SKILL.md"),
|
|
13115
13993
|
agents: ["agent"]
|
|
13116
13994
|
},
|
|
13117
13995
|
{
|
|
13118
|
-
from:
|
|
13119
|
-
to:
|
|
13996
|
+
from: join20(packageRoot3, ".agents", "skills", "zam", "SKILL.md"),
|
|
13997
|
+
to: join20(".agents", "skills", "zam", "SKILL.md"),
|
|
13120
13998
|
agents: ["codex"]
|
|
13121
13999
|
}
|
|
13122
14000
|
];
|
|
@@ -13146,7 +14024,7 @@ function parseSetupAgents(value) {
|
|
|
13146
14024
|
}
|
|
13147
14025
|
function pathExists(path) {
|
|
13148
14026
|
try {
|
|
13149
|
-
|
|
14027
|
+
lstatSync2(path);
|
|
13150
14028
|
return true;
|
|
13151
14029
|
} catch {
|
|
13152
14030
|
return false;
|
|
@@ -13154,7 +14032,7 @@ function pathExists(path) {
|
|
|
13154
14032
|
}
|
|
13155
14033
|
function isSymbolicLink(path) {
|
|
13156
14034
|
try {
|
|
13157
|
-
return
|
|
14035
|
+
return lstatSync2(path).isSymbolicLink();
|
|
13158
14036
|
} catch {
|
|
13159
14037
|
return false;
|
|
13160
14038
|
}
|
|
@@ -13175,16 +14053,16 @@ function linkPointsTo(sourceDir, destinationDir) {
|
|
|
13175
14053
|
}
|
|
13176
14054
|
function isZamSkillCopy(destinationDir) {
|
|
13177
14055
|
try {
|
|
13178
|
-
if (!
|
|
13179
|
-
const skillFile =
|
|
13180
|
-
if (!
|
|
13181
|
-
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"));
|
|
13182
14060
|
} catch {
|
|
13183
14061
|
return false;
|
|
13184
14062
|
}
|
|
13185
14063
|
}
|
|
13186
14064
|
function classifySkillDestination(sourceDir, destinationDir) {
|
|
13187
|
-
if (!
|
|
14065
|
+
if (!existsSync17(sourceDir)) return "source-missing";
|
|
13188
14066
|
if (!isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir)) {
|
|
13189
14067
|
return "source-directory";
|
|
13190
14068
|
}
|
|
@@ -13201,9 +14079,9 @@ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {})
|
|
|
13201
14079
|
};
|
|
13202
14080
|
for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
|
|
13203
14081
|
if (!pairAgents.some((agent) => agents.has(agent))) continue;
|
|
13204
|
-
const sourceDir =
|
|
13205
|
-
const destinationDir =
|
|
13206
|
-
const label =
|
|
14082
|
+
const sourceDir = dirname10(from);
|
|
14083
|
+
const destinationDir = dirname10(join20(cwd, to));
|
|
14084
|
+
const label = dirname10(to);
|
|
13207
14085
|
const state = classifySkillDestination(sourceDir, destinationDir);
|
|
13208
14086
|
if (state === "source-missing") {
|
|
13209
14087
|
if (!opts.quiet) {
|
|
@@ -13260,7 +14138,7 @@ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {})
|
|
|
13260
14138
|
if (destinationExists) {
|
|
13261
14139
|
rmSync2(destinationDir, { recursive: true, force: true });
|
|
13262
14140
|
}
|
|
13263
|
-
|
|
14141
|
+
mkdirSync13(dirname10(destinationDir), { recursive: true });
|
|
13264
14142
|
symlinkSync(
|
|
13265
14143
|
sourceDir,
|
|
13266
14144
|
destinationDir,
|
|
@@ -13276,8 +14154,8 @@ function inspectSkillLinks(cwd = process.cwd(), agents = parseSetupAgents()) {
|
|
|
13276
14154
|
const results = [];
|
|
13277
14155
|
for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
|
|
13278
14156
|
if (!pairAgents.some((agent) => agents.has(agent))) continue;
|
|
13279
|
-
const sourceDir =
|
|
13280
|
-
const destinationDir =
|
|
14157
|
+
const sourceDir = dirname10(from);
|
|
14158
|
+
const destinationDir = dirname10(join20(cwd, to));
|
|
13281
14159
|
results.push({
|
|
13282
14160
|
agents: pairAgents,
|
|
13283
14161
|
source: sourceDir,
|
|
@@ -13321,13 +14199,13 @@ async function resolveUser(opts, db, resolveOpts) {
|
|
|
13321
14199
|
}
|
|
13322
14200
|
|
|
13323
14201
|
// src/cli/workspaces/backup.ts
|
|
13324
|
-
import { mkdirSync as
|
|
13325
|
-
import { join as
|
|
14202
|
+
import { mkdirSync as mkdirSync14 } from "fs";
|
|
14203
|
+
import { join as join21 } from "path";
|
|
13326
14204
|
async function backupDatabaseTo(db, targetDir) {
|
|
13327
|
-
const backupDir =
|
|
13328
|
-
|
|
14205
|
+
const backupDir = join21(targetDir, "zam-backups");
|
|
14206
|
+
mkdirSync14(backupDir, { recursive: true });
|
|
13329
14207
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
13330
|
-
const dest =
|
|
14208
|
+
const dest = join21(backupDir, `zam-${stamp}.db`);
|
|
13331
14209
|
await db.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
|
|
13332
14210
|
return dest;
|
|
13333
14211
|
}
|
|
@@ -13458,20 +14336,20 @@ bridgeCommand.command("workspace-info").description("Report the workspace dir, i
|
|
|
13458
14336
|
activeWorkspace,
|
|
13459
14337
|
workspaceDir: activeWorkspace.path,
|
|
13460
14338
|
defaultWorkspaceDir: defaultWorkspaceDir(),
|
|
13461
|
-
dataDir:
|
|
14339
|
+
dataDir: join22(homedir14(), ".zam")
|
|
13462
14340
|
});
|
|
13463
14341
|
});
|
|
13464
14342
|
});
|
|
13465
14343
|
function provisionConfiguredWorkspaces() {
|
|
13466
14344
|
return getConfiguredWorkspaces().flatMap(
|
|
13467
|
-
(workspace) =>
|
|
14345
|
+
(workspace) => existsSync18(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
|
|
13468
14346
|
);
|
|
13469
14347
|
}
|
|
13470
14348
|
function buildWorkspaceLinkHealth(workspaces) {
|
|
13471
14349
|
const agents = parseSetupAgents();
|
|
13472
14350
|
const map = {};
|
|
13473
14351
|
for (const workspace of workspaces) {
|
|
13474
|
-
if (!
|
|
14352
|
+
if (!existsSync18(workspace.path)) continue;
|
|
13475
14353
|
const links = inspectSkillLinks(workspace.path, agents);
|
|
13476
14354
|
map[workspace.id] = {
|
|
13477
14355
|
health: summarizeSkillLinkHealth(links),
|
|
@@ -13501,7 +14379,7 @@ bridgeCommand.command("workspace-list").description("List configured ZAM workspa
|
|
|
13501
14379
|
activeWorkspace,
|
|
13502
14380
|
workspaceDir: activeWorkspace.path,
|
|
13503
14381
|
defaultWorkspaceDir: defaultWorkspaceDir(),
|
|
13504
|
-
dataDir:
|
|
14382
|
+
dataDir: join22(homedir14(), ".zam"),
|
|
13505
14383
|
linkHealth: buildWorkspaceLinkHealth(workspaces)
|
|
13506
14384
|
});
|
|
13507
14385
|
});
|
|
@@ -13516,7 +14394,7 @@ bridgeCommand.command("workspace-repair-links").description(
|
|
|
13516
14394
|
if (!id) jsonError("A non-empty --id is required");
|
|
13517
14395
|
const workspace = getConfiguredWorkspaces().find((item) => item.id === id);
|
|
13518
14396
|
if (!workspace) jsonError(`Workspace "${id}" is not configured`);
|
|
13519
|
-
if (!
|
|
14397
|
+
if (!existsSync18(workspace.path)) {
|
|
13520
14398
|
jsonError(`Workspace path does not exist: ${workspace.path}`);
|
|
13521
14399
|
}
|
|
13522
14400
|
const agents = parseSetupAgents(opts.agents);
|
|
@@ -13547,8 +14425,8 @@ function parseBridgeWorkspaceKind(value) {
|
|
|
13547
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) => {
|
|
13548
14426
|
const raw = String(opts.path ?? "").trim();
|
|
13549
14427
|
if (!raw) jsonError("A non-empty --path is required");
|
|
13550
|
-
const path =
|
|
13551
|
-
if (!
|
|
14428
|
+
const path = resolve5(raw);
|
|
14429
|
+
if (!existsSync18(path)) jsonError(`Workspace path does not exist: ${path}`);
|
|
13552
14430
|
const id = opts.id ? String(opts.id).trim() : void 0;
|
|
13553
14431
|
if (opts.id && !id) jsonError("A non-empty --id is required");
|
|
13554
14432
|
const kind = parseBridgeWorkspaceKind(opts.kind);
|
|
@@ -13592,8 +14470,8 @@ bridgeCommand.command("workspace-remove").description("Unregister a ZAM workspac
|
|
|
13592
14470
|
bridgeCommand.command("set-workspace-dir").description("Set the personal workspace directory (JSON)").requiredOption("--dir <path>", "Path to the workspace directory").action(async (opts) => {
|
|
13593
14471
|
const raw = String(opts.dir ?? "").trim();
|
|
13594
14472
|
if (!raw) jsonError("A non-empty --dir is required");
|
|
13595
|
-
const dir =
|
|
13596
|
-
if (!
|
|
14473
|
+
const dir = resolve5(raw);
|
|
14474
|
+
if (!existsSync18(dir)) jsonError(`Workspace path does not exist: ${dir}`);
|
|
13597
14475
|
const skillLinks = wireSkills(dir, parseSetupAgents(), { quiet: true });
|
|
13598
14476
|
await withDb2(async (db) => {
|
|
13599
14477
|
const workspace = await activateWorkspacePath(db, dir);
|
|
@@ -13656,7 +14534,7 @@ bridgeCommand.command("agent-open").description("Launch an agent harness in the
|
|
|
13656
14534
|
jsonError(`Workspace is not configured: ${opts.workspace}`);
|
|
13657
14535
|
}
|
|
13658
14536
|
const activeWorkspace = await ensureActiveWorkspace(db);
|
|
13659
|
-
const workspace = opts.dir ?
|
|
14537
|
+
const workspace = opts.dir ? existsSync18(opts.dir) ? opts.dir : homedir14() : existingWorkspaceDirOrHome(configuredWorkspace ?? activeWorkspace);
|
|
13660
14538
|
launchHarness(harness, {
|
|
13661
14539
|
executable,
|
|
13662
14540
|
workspace,
|
|
@@ -14028,7 +14906,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
14028
14906
|
"20"
|
|
14029
14907
|
).action(async (opts) => {
|
|
14030
14908
|
try {
|
|
14031
|
-
const monitorDir =
|
|
14909
|
+
const monitorDir = join22(homedir14(), ".zam", "monitor");
|
|
14032
14910
|
let files;
|
|
14033
14911
|
try {
|
|
14034
14912
|
files = readdirSync2(monitorDir).filter((f) => f.endsWith(".jsonl"));
|
|
@@ -14041,7 +14919,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
14041
14919
|
return;
|
|
14042
14920
|
}
|
|
14043
14921
|
const limit = Number(opts.limit);
|
|
14044
|
-
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);
|
|
14045
14923
|
const sessionCommands = /* @__PURE__ */ new Map();
|
|
14046
14924
|
for (const file of sorted) {
|
|
14047
14925
|
const sessionId = file.name.replace(".jsonl", "");
|
|
@@ -14153,7 +15031,7 @@ bridgeCommand.command("observe-ui-snapshot").description(
|
|
|
14153
15031
|
});
|
|
14154
15032
|
function resolveWindowsPowerShell() {
|
|
14155
15033
|
try {
|
|
14156
|
-
|
|
15034
|
+
execFileSync4("where.exe", ["pwsh.exe"], { stdio: "ignore" });
|
|
14157
15035
|
return "pwsh";
|
|
14158
15036
|
} catch {
|
|
14159
15037
|
return "powershell";
|
|
@@ -14168,7 +15046,7 @@ function captureScreenshot(outputPath, hwnd, processName) {
|
|
|
14168
15046
|
throw new Error(`Invalid process name format: ${processName}`);
|
|
14169
15047
|
}
|
|
14170
15048
|
if (platform === "win32") {
|
|
14171
|
-
const stdout =
|
|
15049
|
+
const stdout = execFileSync4(
|
|
14172
15050
|
resolveWindowsPowerShell(),
|
|
14173
15051
|
[
|
|
14174
15052
|
"-NoProfile",
|
|
@@ -14481,13 +15359,13 @@ Write-CaptureResult "fullscreen" $hwndVal $matchedBy
|
|
|
14481
15359
|
} else if (platform === "darwin") {
|
|
14482
15360
|
if (hwnd) {
|
|
14483
15361
|
const parsedHwnd = hwnd.startsWith("0x") ? parseInt(hwnd, 16) : parseInt(hwnd, 10);
|
|
14484
|
-
|
|
15362
|
+
execFileSync4("screencapture", ["-l", String(parsedHwnd), outputPath], {
|
|
14485
15363
|
stdio: "pipe"
|
|
14486
15364
|
});
|
|
14487
15365
|
return { method: "screencapture-window", target: null };
|
|
14488
15366
|
} else if (processName) {
|
|
14489
15367
|
try {
|
|
14490
|
-
const windowId =
|
|
15368
|
+
const windowId = execFileSync4(
|
|
14491
15369
|
"osascript",
|
|
14492
15370
|
[
|
|
14493
15371
|
"-e",
|
|
@@ -14496,17 +15374,17 @@ Write-CaptureResult "fullscreen" $hwndVal $matchedBy
|
|
|
14496
15374
|
{ encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] }
|
|
14497
15375
|
).trim();
|
|
14498
15376
|
if (windowId && /^\d+$/.test(windowId)) {
|
|
14499
|
-
|
|
15377
|
+
execFileSync4("screencapture", ["-l", windowId, outputPath], {
|
|
14500
15378
|
stdio: "pipe"
|
|
14501
15379
|
});
|
|
14502
15380
|
return { method: "screencapture-window", target: null };
|
|
14503
15381
|
}
|
|
14504
15382
|
} catch {
|
|
14505
15383
|
}
|
|
14506
|
-
|
|
15384
|
+
execFileSync4("screencapture", ["-x", outputPath], { stdio: "pipe" });
|
|
14507
15385
|
return { method: "screencapture-full", target: null };
|
|
14508
15386
|
} else {
|
|
14509
|
-
|
|
15387
|
+
execFileSync4("screencapture", ["-x", outputPath], { stdio: "pipe" });
|
|
14510
15388
|
return { method: "screencapture-full", target: null };
|
|
14511
15389
|
}
|
|
14512
15390
|
} else {
|
|
@@ -14543,7 +15421,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
|
|
|
14543
15421
|
return;
|
|
14544
15422
|
}
|
|
14545
15423
|
}
|
|
14546
|
-
const outputPath = opts.image ?? opts.output ??
|
|
15424
|
+
const outputPath = opts.image ?? opts.output ?? join22(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
|
|
14547
15425
|
const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
|
|
14548
15426
|
if (!isProvided) {
|
|
14549
15427
|
const post = decidePostCapture(policy, {
|
|
@@ -14571,7 +15449,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
|
|
|
14571
15449
|
return;
|
|
14572
15450
|
}
|
|
14573
15451
|
}
|
|
14574
|
-
const imageBytes =
|
|
15452
|
+
const imageBytes = readFileSync16(outputPath);
|
|
14575
15453
|
const base64 = imageBytes.toString("base64");
|
|
14576
15454
|
jsonOut({
|
|
14577
15455
|
sessionId: opts.session ?? null,
|
|
@@ -14598,11 +15476,11 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
14598
15476
|
return;
|
|
14599
15477
|
}
|
|
14600
15478
|
const sessionId = opts.session;
|
|
14601
|
-
const statePath =
|
|
15479
|
+
const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
14602
15480
|
const defaultExt = platform === "win32" ? ".mkv" : ".mov";
|
|
14603
|
-
const outputPath = opts.output ??
|
|
14604
|
-
const { existsSync:
|
|
14605
|
-
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)) {
|
|
14606
15484
|
jsonOut({
|
|
14607
15485
|
sessionId,
|
|
14608
15486
|
started: false,
|
|
@@ -14610,7 +15488,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
14610
15488
|
});
|
|
14611
15489
|
return;
|
|
14612
15490
|
}
|
|
14613
|
-
const logPath =
|
|
15491
|
+
const logPath = join22(tmpdir3(), `zam-recording-${sessionId}.log`);
|
|
14614
15492
|
let logFd;
|
|
14615
15493
|
try {
|
|
14616
15494
|
logFd = openSync(logPath, "w");
|
|
@@ -14656,7 +15534,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
14656
15534
|
}
|
|
14657
15535
|
child.unref();
|
|
14658
15536
|
if (child.pid) {
|
|
14659
|
-
|
|
15537
|
+
writeFileSync12(
|
|
14660
15538
|
statePath,
|
|
14661
15539
|
JSON.stringify({
|
|
14662
15540
|
pid: child.pid,
|
|
@@ -14692,9 +15570,9 @@ bridgeCommand.command("stop-recording").description(
|
|
|
14692
15570
|
return;
|
|
14693
15571
|
}
|
|
14694
15572
|
const sessionId = opts.session;
|
|
14695
|
-
const statePath =
|
|
14696
|
-
const { existsSync:
|
|
14697
|
-
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)) {
|
|
14698
15576
|
jsonOut({
|
|
14699
15577
|
sessionId,
|
|
14700
15578
|
stopped: false,
|
|
@@ -14702,7 +15580,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
14702
15580
|
});
|
|
14703
15581
|
return;
|
|
14704
15582
|
}
|
|
14705
|
-
const state = JSON.parse(
|
|
15583
|
+
const state = JSON.parse(readFileSync18(statePath, "utf8"));
|
|
14706
15584
|
const { pid, outputPath } = state;
|
|
14707
15585
|
try {
|
|
14708
15586
|
process.kill(pid, "SIGINT");
|
|
@@ -14718,7 +15596,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
14718
15596
|
};
|
|
14719
15597
|
let attempts = 0;
|
|
14720
15598
|
while (isProcessRunning(pid) && attempts < 20) {
|
|
14721
|
-
await new Promise((
|
|
15599
|
+
await new Promise((resolve6) => setTimeout(resolve6, 250));
|
|
14722
15600
|
attempts++;
|
|
14723
15601
|
}
|
|
14724
15602
|
if (isProcessRunning(pid)) {
|
|
@@ -14731,7 +15609,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
14731
15609
|
rmSync4(statePath, { force: true });
|
|
14732
15610
|
} catch {
|
|
14733
15611
|
}
|
|
14734
|
-
if (!
|
|
15612
|
+
if (!existsSync20(outputPath)) {
|
|
14735
15613
|
jsonOut({
|
|
14736
15614
|
sessionId,
|
|
14737
15615
|
stopped: false,
|
|
@@ -14968,7 +15846,7 @@ bridgeCommand.command("cloud-model-hint").description("Suggest a cloud model for
|
|
|
14968
15846
|
});
|
|
14969
15847
|
bridgeCommand.command("local-llm-hints").description("Detect installed local LLM servers and suggest defaults (JSON)").action(() => {
|
|
14970
15848
|
const profile = getSystemProfile();
|
|
14971
|
-
const flmInstalled = hasCommand("flm") ||
|
|
15849
|
+
const flmInstalled = hasCommand("flm") || existsSync18("C:\\Program Files\\flm\\flm.exe");
|
|
14972
15850
|
const ollamaInstalled = isOllamaInstalled();
|
|
14973
15851
|
const runners = [
|
|
14974
15852
|
{ id: "flm", label: "FastFlowLM", installed: flmInstalled },
|
|
@@ -15108,6 +15986,93 @@ bridgeCommand.command("evaluate-answer").description(
|
|
|
15108
15986
|
}
|
|
15109
15987
|
});
|
|
15110
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
|
+
});
|
|
15111
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) => {
|
|
15112
16077
|
await withDb2(async (db) => {
|
|
15113
16078
|
const userId = await ensureDefaultUser(db, opts.user);
|
|
@@ -15136,6 +16101,54 @@ bridgeCommand.command("get-settings").description("Get active ZAM settings (JSON
|
|
|
15136
16101
|
});
|
|
15137
16102
|
});
|
|
15138
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
|
+
});
|
|
15139
16152
|
async function readDatabaseUserSummaries(db) {
|
|
15140
16153
|
return await db.prepare(
|
|
15141
16154
|
`SELECT user_id AS id, COUNT(*) AS cardCount
|
|
@@ -15757,7 +16770,7 @@ bridgeCommand.command("personal-source-import").description(
|
|
|
15757
16770
|
} else {
|
|
15758
16771
|
throw new Error(`Invalid source type: ${opts.type}`);
|
|
15759
16772
|
}
|
|
15760
|
-
const sourceId =
|
|
16773
|
+
const sourceId = ulid10();
|
|
15761
16774
|
await db.prepare(
|
|
15762
16775
|
`INSERT INTO sources (id, type, uri, content)
|
|
15763
16776
|
VALUES (?, ?, ?, ?)
|
|
@@ -15976,7 +16989,7 @@ bridgeCommand.command("curriculum-extract-topics").description(
|
|
|
15976
16989
|
}
|
|
15977
16990
|
}
|
|
15978
16991
|
const pageCleanedText = cleanHtml(rawHtml);
|
|
15979
|
-
const sourceId =
|
|
16992
|
+
const sourceId = ulid10();
|
|
15980
16993
|
await db.prepare(
|
|
15981
16994
|
`INSERT INTO sources (id, type, uri, content)
|
|
15982
16995
|
VALUES (?, 'web', ?, ?)
|
|
@@ -16241,12 +17254,12 @@ bridgeCommand.command("serve").description("Start the persistent JSON-RPC stdin/
|
|
|
16241
17254
|
});
|
|
16242
17255
|
|
|
16243
17256
|
// src/cli/commands/mcp.ts
|
|
16244
|
-
var __dirname =
|
|
16245
|
-
var pkgPath =
|
|
16246
|
-
if (!
|
|
16247
|
-
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");
|
|
16248
17261
|
}
|
|
16249
|
-
var pkg = JSON.parse(
|
|
17262
|
+
var pkg = JSON.parse(readFileSync17(pkgPath, "utf-8"));
|
|
16250
17263
|
var STUDIO_RESOURCE_URI = "ui://zam/studio";
|
|
16251
17264
|
var RECALL_RESOURCE_URI = "ui://zam/recall";
|
|
16252
17265
|
var GRAPH_RESOURCE_URI = "ui://zam/graph";
|
|
@@ -16271,13 +17284,13 @@ var STUDIO_BRIDGE_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
16271
17284
|
function loadPanelHtml(fileName, placeholderTitle) {
|
|
16272
17285
|
const candidates = [
|
|
16273
17286
|
// dist/cli/commands/mcp.js → dist/ui/
|
|
16274
|
-
|
|
17287
|
+
join23(__dirname, "..", "..", "ui", fileName),
|
|
16275
17288
|
// src/cli/commands/mcp.ts via tsx → <repo>/dist/ui/
|
|
16276
|
-
|
|
17289
|
+
join23(__dirname, "..", "..", "..", "dist", "ui", fileName)
|
|
16277
17290
|
];
|
|
16278
17291
|
for (const candidate of candidates) {
|
|
16279
|
-
if (
|
|
16280
|
-
return
|
|
17292
|
+
if (existsSync19(candidate)) {
|
|
17293
|
+
return readFileSync17(candidate, "utf-8");
|
|
16281
17294
|
}
|
|
16282
17295
|
}
|
|
16283
17296
|
return `<!doctype html>
|
|
@@ -16672,7 +17685,7 @@ function createMcpServer(db) {
|
|
|
16672
17685
|
"zam_open_studio",
|
|
16673
17686
|
{
|
|
16674
17687
|
title: "ZAM Studio",
|
|
16675
|
-
description: "Open the ZAM Studio panel
|
|
17688
|
+
description: "Open the legacy all-in-one ZAM Studio panel for standalone onboarding and content curation. Agent harness workflows should use the focused Recall, Graph, and Settings apps instead.",
|
|
16676
17689
|
inputSchema: {},
|
|
16677
17690
|
annotations: {
|
|
16678
17691
|
...commonAnnotations,
|
|
@@ -16727,6 +17740,7 @@ function createMcpServer(db) {
|
|
|
16727
17740
|
wrapHandler(
|
|
16728
17741
|
async ({ user, domain }) => {
|
|
16729
17742
|
const userId = await getUserId(user).catch(() => null);
|
|
17743
|
+
await publishUiIntent("recall", { user, domain });
|
|
16730
17744
|
return {
|
|
16731
17745
|
recall: "zam",
|
|
16732
17746
|
version: pkg.version,
|
|
@@ -16771,6 +17785,7 @@ function createMcpServer(db) {
|
|
|
16771
17785
|
},
|
|
16772
17786
|
wrapHandler(async ({ focus, user }) => {
|
|
16773
17787
|
const userId = await getUserId(user).catch(() => null);
|
|
17788
|
+
await publishUiIntent("graph", { user, focus });
|
|
16774
17789
|
return {
|
|
16775
17790
|
graph: "zam",
|
|
16776
17791
|
focus: focus ?? null,
|
|
@@ -16812,6 +17827,7 @@ function createMcpServer(db) {
|
|
|
16812
17827
|
},
|
|
16813
17828
|
wrapHandler(async ({ user }) => {
|
|
16814
17829
|
const userId = await getUserId(user).catch(() => null);
|
|
17830
|
+
await publishUiIntent("settings", { user });
|
|
16815
17831
|
return {
|
|
16816
17832
|
settings: "zam",
|
|
16817
17833
|
version: pkg.version,
|