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 CHANGED
@@ -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,
@@ -6815,11 +6829,16 @@ import { Command as Command27 } from "commander";
6815
6829
 
6816
6830
  // src/cli/commands/agent.ts
6817
6831
  init_kernel();
6818
- import { existsSync as existsSync15, mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
6819
- import { homedir as homedir11 } from "os";
6820
- import { dirname as dirname7, join as join15 } from "path";
6832
+ import { existsSync as existsSync15 } from "fs";
6833
+ import { join as join15 } from "path";
6821
6834
  import { Command } from "commander";
6822
6835
 
6836
+ // src/cli/agent-connect.ts
6837
+ init_kernel();
6838
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync9 } from "fs";
6839
+ import { homedir as homedir11 } from "os";
6840
+ import { dirname as dirname7 } from "path";
6841
+
6823
6842
  // src/cli/agent-harness.ts
6824
6843
  import { spawn } from "child_process";
6825
6844
  import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
@@ -7673,16 +7692,7 @@ function installVscodeExtension(options) {
7673
7692
  };
7674
7693
  }
7675
7694
 
7676
- // src/cli/commands/agent.ts
7677
- var C = {
7678
- reset: "\x1B[0m",
7679
- bold: "\x1B[1m",
7680
- cyan: "\x1B[36m",
7681
- dim: "\x1B[2m",
7682
- green: "\x1B[32m",
7683
- yellow: "\x1B[33m"
7684
- };
7685
- var SUPPORTED_AGENTS = ["opencode"];
7695
+ // src/cli/agent-connect.ts
7686
7696
  var CONNECT_HARNESSES = [
7687
7697
  "claude-code",
7688
7698
  "claude-desktop",
@@ -7693,9 +7703,189 @@ var CONNECT_HARNESSES = [
7693
7703
  "goose",
7694
7704
  "copilot"
7695
7705
  ];
7706
+ var USER_SCOPED_CONNECT_HARNESSES = [
7707
+ "claude-desktop",
7708
+ "antigravity",
7709
+ "codex",
7710
+ "vscode",
7711
+ "opencode",
7712
+ "goose",
7713
+ "copilot"
7714
+ ];
7715
+ var CONNECT_HARNESS_LABELS = {
7716
+ "claude-code": "Claude Code",
7717
+ "claude-desktop": "Claude Desktop",
7718
+ antigravity: "Antigravity",
7719
+ codex: "Codex",
7720
+ vscode: "VS Code",
7721
+ opencode: "OpenCode",
7722
+ goose: "Goose",
7723
+ copilot: "GitHub Copilot"
7724
+ };
7696
7725
  function isConnectHarnessId(value) {
7697
7726
  return CONNECT_HARNESSES.includes(value);
7698
7727
  }
7728
+ function resolveDeps(deps) {
7729
+ const home = deps.home ?? homedir11();
7730
+ const cwd = deps.cwd ?? process.cwd();
7731
+ const copilotHome = deps.copilotHome ?? process.env.COPILOT_HOME;
7732
+ return {
7733
+ home,
7734
+ cwd,
7735
+ copilotHome,
7736
+ findZam: deps.findZam ?? (() => findExecutable("zam")),
7737
+ detect: deps.detect ?? (() => detectInstalledConnectHarnesses({ home, copilotHome })),
7738
+ connectMcp: deps.connectMcp ?? connectHarnessMcp,
7739
+ writeConfig: deps.writeConfig ?? ((path, content) => {
7740
+ mkdirSync10(dirname7(path), { recursive: true });
7741
+ writeFileSync9(path, content, "utf-8");
7742
+ }),
7743
+ installCopilot: deps.installCopilot ?? installCopilotExtension,
7744
+ installVscode: deps.installVscode ?? installVscodeExtension,
7745
+ resolveAntigravity: deps.resolveAntigravity ?? resolveAntigravityIdeExecutable,
7746
+ refreshSkills: deps.refreshSkills ?? distributeGlobalSkills
7747
+ };
7748
+ }
7749
+ function performAgentConnect(opts = {}, deps = {}) {
7750
+ const d = resolveDeps(deps);
7751
+ const dryRun = Boolean(opts.dryRun);
7752
+ const detected = opts.harness ? [opts.harness] : d.detect();
7753
+ const foundZam = d.findZam();
7754
+ const zamPath = foundZam ?? "zam";
7755
+ const results = [];
7756
+ for (const harness of detected) {
7757
+ const label = CONNECT_HARNESS_LABELS[harness];
7758
+ try {
7759
+ const prepared = d.connectMcp(harness, {
7760
+ zamPath,
7761
+ cwd: d.cwd,
7762
+ home: d.home,
7763
+ copilotHome: d.copilotHome
7764
+ });
7765
+ let extension = null;
7766
+ if (harness === "copilot") {
7767
+ const installed = d.installCopilot({
7768
+ home: d.home,
7769
+ zamPath,
7770
+ dryRun
7771
+ });
7772
+ extension = {
7773
+ kind: "copilot",
7774
+ action: installed.action,
7775
+ location: installed.destinationDir,
7776
+ detail: `${installed.launch.command} ${installed.launch.args.join(" ")}`
7777
+ };
7778
+ } else if (harness === "vscode") {
7779
+ const installed = d.installVscode({ home: d.home, zamPath, dryRun });
7780
+ extension = {
7781
+ kind: "vscode",
7782
+ action: installed.action,
7783
+ location: installed.vsixPath,
7784
+ detail: installed.launchConfigPath
7785
+ };
7786
+ } else if (harness === "antigravity") {
7787
+ const antigravityPath = d.resolveAntigravity();
7788
+ if (antigravityPath) {
7789
+ const installed = d.installVscode({
7790
+ home: d.home,
7791
+ zamPath,
7792
+ codePath: antigravityPath,
7793
+ dryRun
7794
+ });
7795
+ extension = {
7796
+ kind: "vscode",
7797
+ action: installed.action,
7798
+ location: installed.vsixPath,
7799
+ detail: installed.launchConfigPath
7800
+ };
7801
+ }
7802
+ }
7803
+ let wrote = false;
7804
+ if (!prepared.alreadyConfigured && !dryRun) {
7805
+ d.writeConfig(prepared.path, prepared.content);
7806
+ wrote = true;
7807
+ }
7808
+ results.push({
7809
+ harness,
7810
+ label,
7811
+ path: prepared.path,
7812
+ content: prepared.content,
7813
+ alreadyConfigured: prepared.alreadyConfigured,
7814
+ wrote,
7815
+ hint: prepared.hint,
7816
+ extension
7817
+ });
7818
+ } catch (error) {
7819
+ results.push({
7820
+ harness,
7821
+ label,
7822
+ path: "",
7823
+ content: "",
7824
+ alreadyConfigured: false,
7825
+ wrote: false,
7826
+ hint: "",
7827
+ extension: null,
7828
+ error: error instanceof Error ? error.message : String(error)
7829
+ });
7830
+ }
7831
+ }
7832
+ let skills = null;
7833
+ if (!dryRun && detected.length > 0) {
7834
+ const skillResults = d.refreshSkills(d.home);
7835
+ skills = {
7836
+ refreshed: skillResults.filter((result) => result.success).length,
7837
+ total: skillResults.length
7838
+ };
7839
+ }
7840
+ return {
7841
+ success: results.every((result) => !result.error),
7842
+ detected,
7843
+ zamPath,
7844
+ zamOnPath: Boolean(foundZam),
7845
+ results,
7846
+ skills
7847
+ };
7848
+ }
7849
+ function inspectConnectHarnesses(deps = {}) {
7850
+ const d = resolveDeps(deps);
7851
+ const foundZam = d.findZam();
7852
+ const zamPath = foundZam ?? "zam";
7853
+ const installed = new Set(d.detect());
7854
+ const harnesses = USER_SCOPED_CONNECT_HARNESSES.map((harness) => {
7855
+ const status = {
7856
+ harness,
7857
+ label: CONNECT_HARNESS_LABELS[harness],
7858
+ installed: installed.has(harness),
7859
+ configured: false,
7860
+ configPath: ""
7861
+ };
7862
+ try {
7863
+ const probe = d.connectMcp(harness, {
7864
+ zamPath,
7865
+ cwd: d.cwd,
7866
+ home: d.home,
7867
+ copilotHome: d.copilotHome
7868
+ });
7869
+ status.configured = probe.alreadyConfigured;
7870
+ status.configPath = probe.path;
7871
+ } catch (error) {
7872
+ status.note = error instanceof Error ? error.message : String(error);
7873
+ }
7874
+ return status;
7875
+ });
7876
+ return { zamOnPath: Boolean(foundZam), harnesses };
7877
+ }
7878
+
7879
+ // src/cli/commands/agent.ts
7880
+ var C = {
7881
+ reset: "\x1B[0m",
7882
+ bold: "\x1B[1m",
7883
+ cyan: "\x1B[36m",
7884
+ dim: "\x1B[2m",
7885
+ green: "\x1B[32m",
7886
+ yellow: "\x1B[33m"
7887
+ };
7888
+ var SUPPORTED_AGENTS = ["opencode"];
7699
7889
  function agentsMdPresent(cwd = process.cwd()) {
7700
7890
  return existsSync15(join15(cwd, "AGENTS.md"));
7701
7891
  }
@@ -7823,125 +8013,69 @@ var connectCmd = new Command("connect").description(
7823
8013
  explicitHarness = harnessArg;
7824
8014
  }
7825
8015
  }
7826
- const home = homedir11();
7827
- const harnesses = explicitHarness ? [explicitHarness] : detectInstalledConnectHarnesses({
7828
- home,
7829
- copilotHome: process.env.COPILOT_HOME
8016
+ const report = performAgentConnect({
8017
+ harness: explicitHarness,
8018
+ dryRun: Boolean(opts.print)
7830
8019
  });
7831
- if (harnesses.length === 0) {
8020
+ if (report.detected.length === 0) {
7832
8021
  console.error(
7833
8022
  "No supported user-scoped agent harness was detected. Install Codex, VS Code, or another supported host, or pass a harness explicitly."
7834
8023
  );
7835
8024
  process.exit(1);
7836
8025
  }
7837
- let zamPath = findExecutable("zam");
7838
- if (!zamPath) {
8026
+ if (!report.zamOnPath) {
7839
8027
  console.warn(
7840
8028
  `${C.yellow}Warning: 'zam' executable was not found on your PATH. Falling back to literal 'zam'.${C.reset}`
7841
8029
  );
7842
- zamPath = "zam";
7843
8030
  }
7844
- for (const harness of harnesses) {
7845
- let result;
7846
- try {
7847
- result = connectHarnessMcp(harness, {
7848
- zamPath,
7849
- cwd: process.cwd(),
7850
- home,
7851
- copilotHome: process.env.COPILOT_HOME
7852
- });
7853
- } catch (error) {
7854
- console.error(
7855
- `Error preparing ${harness} MCP configuration: ${error instanceof Error ? error.message : String(error)}`
7856
- );
7857
- process.exit(1);
7858
- }
7859
- let copilotExtension;
7860
- let vscodeExtension;
7861
- try {
7862
- if (harness === "copilot") {
7863
- copilotExtension = installCopilotExtension({
7864
- home,
7865
- zamPath,
7866
- dryRun: Boolean(opts.print)
7867
- });
7868
- } else if (harness === "vscode") {
7869
- vscodeExtension = installVscodeExtension({
7870
- home,
7871
- zamPath,
7872
- dryRun: Boolean(opts.print)
7873
- });
7874
- } else if (harness === "antigravity") {
7875
- const antigravityPath = resolveAntigravityIdeExecutable();
7876
- if (antigravityPath) {
7877
- vscodeExtension = installVscodeExtension({
7878
- home,
7879
- zamPath,
7880
- codePath: antigravityPath,
7881
- dryRun: Boolean(opts.print)
7882
- });
7883
- }
7884
- }
7885
- } catch (error) {
7886
- console.error(
7887
- `Error preparing ${harness} companion extension: ${error instanceof Error ? error.message : String(error)}`
7888
- );
7889
- process.exit(1);
8031
+ for (const result of report.results) {
8032
+ if (result.error) {
8033
+ console.error(`Error connecting ${result.harness}: ${result.error}`);
8034
+ continue;
7890
8035
  }
7891
8036
  if (opts.print) {
7892
- if (harnesses.length > 1) console.log(`${C.bold}${harness}${C.reset}`);
8037
+ if (report.results.length > 1)
8038
+ console.log(`${C.bold}${result.harness}${C.reset}`);
7893
8039
  console.log(`Path: ${result.path}`);
7894
8040
  console.log(`Content:
7895
8041
  ${result.content}`);
7896
- if (copilotExtension) {
7897
- console.log(`Extension: ${copilotExtension.destinationDir}`);
7898
- console.log(
7899
- `Launch: ${copilotExtension.launch.command} ${copilotExtension.launch.args.join(" ")}`
7900
- );
7901
- }
7902
- if (vscodeExtension) {
7903
- console.log(`Extension: ${vscodeExtension.vsixPath}`);
7904
- console.log(`Launch config: ${vscodeExtension.launchConfigPath}`);
8042
+ if (result.extension?.kind === "copilot") {
8043
+ console.log(`Extension: ${result.extension.location}`);
8044
+ console.log(`Launch: ${result.extension.detail}`);
8045
+ } else if (result.extension) {
8046
+ console.log(`Extension: ${result.extension.location}`);
8047
+ console.log(`Launch config: ${result.extension.detail}`);
7905
8048
  }
7906
8049
  continue;
7907
8050
  }
7908
8051
  if (result.alreadyConfigured) {
7909
8052
  console.log(
7910
- `${C.green}\u2713${C.reset} ${harness}: MCP server 'zam' already configured in ${result.path}`
8053
+ `${C.green}\u2713${C.reset} ${result.harness}: MCP server 'zam' already configured in ${result.path}`
7911
8054
  );
7912
8055
  } else {
7913
- try {
7914
- mkdirSync10(dirname7(result.path), { recursive: true });
7915
- writeFileSync9(result.path, result.content, "utf-8");
7916
- console.log(
7917
- `${C.green}\u2713${C.reset} ${harness}: wrote MCP configuration to ${result.path}`
7918
- );
7919
- } catch (error) {
7920
- console.error(
7921
- `Error writing ${harness} MCP configuration: ${error instanceof Error ? error.message : String(error)}`
7922
- );
7923
- process.exit(1);
7924
- }
7925
- }
7926
- if (copilotExtension) {
7927
8056
  console.log(
7928
- `${C.green}\u2713${C.reset} Copilot MCP Apps extension ${copilotExtension.action} at ${copilotExtension.destinationDir}`
8057
+ `${C.green}\u2713${C.reset} ${result.harness}: wrote MCP configuration to ${result.path}`
7929
8058
  );
7930
8059
  }
7931
- if (vscodeExtension) {
8060
+ if (result.extension?.kind === "copilot") {
8061
+ console.log(
8062
+ `${C.green}\u2713${C.reset} Copilot MCP Apps extension ${result.extension.action} at ${result.extension.location}`
8063
+ );
8064
+ } else if (result.extension) {
7932
8065
  console.log(
7933
- `${C.green}\u2713${C.reset} ZAM Companion ${vscodeExtension.action} from ${vscodeExtension.vsixPath}`
8066
+ `${C.green}\u2713${C.reset} ZAM Companion ${result.extension.action} from ${result.extension.location}`
7934
8067
  );
7935
8068
  }
7936
8069
  console.log(` ${C.dim}${result.hint}${C.reset}`);
7937
8070
  }
7938
- if (!opts.print) {
7939
- const skills = distributeGlobalSkills(home);
7940
- const installed = skills.filter((result) => result.success).length;
8071
+ if (report.skills) {
7941
8072
  console.log(
7942
- `${C.green}\u2713${C.reset} Refreshed ${installed}/${skills.length} global ZAM skill installations`
8073
+ `${C.green}\u2713${C.reset} Refreshed ${report.skills.refreshed}/${report.skills.total} global ZAM skill installations`
7943
8074
  );
7944
8075
  }
8076
+ if (!report.success) {
8077
+ process.exit(1);
8078
+ }
7945
8079
  });
7946
8080
  var agentCommand = new Command("agent").description("Provision and inspect the agent that drives ZAM sessions").addCommand(installCmd).addCommand(statusCmd).addCommand(openCmd).addCommand(listCmd).addCommand(connectCmd).action(printStatus);
7947
8081
 
@@ -7967,6 +8101,7 @@ var DEFAULT_LLM_URL = "http://localhost:8000/v1";
7967
8101
  var DEFAULT_LLM_MAX_TOKENS = 1e4;
7968
8102
  var RECALL_QUESTION_MAX_OUTPUT_TOKENS = 400;
7969
8103
  var RECALL_EVALUATION_MAX_OUTPUT_TOKENS = 1200;
8104
+ var RECALL_DISCUSSION_MAX_OUTPUT_TOKENS = 1200;
7970
8105
  var RECALL_ENDPOINT_CACHE_MS = 6e4;
7971
8106
  var cachedRecallEndpoint = null;
7972
8107
  function recallEndpointSignature(cfg) {
@@ -8329,6 +8464,64 @@ Evaluation:`;
8329
8464
  providerName: endpoint.providerName
8330
8465
  };
8331
8466
  }
8467
+ async function discussReviewViaLLM(db, input8) {
8468
+ const cfg = await getProviderForRole(db, "recall");
8469
+ const endpoint = await resolveUsableRecallEndpoint(db);
8470
+ const langName = LANGUAGE_NAMES[cfg.locale] || "English";
8471
+ const systemPrompt = `You are ZAM, a warm, precise, and encouraging skills trainer in a follow-up discussion about one flashcard.
8472
+ 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.
8473
+
8474
+ Guidelines:
8475
+ 1. Answer the learner's follow-up directly and concretely in ${langName}, grounded in the card's target concept, context, and source reference.
8476
+ 2. Stay scoped to this card and its concept. If the learner drifts to unrelated territory, answer briefly and steer back to the concept.
8477
+ 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.
8478
+ 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.`;
8479
+ const cardFrame = `The card under discussion:
8480
+ Domain: ${input8.domain}
8481
+ Slug: ${input8.slug}
8482
+ Recall Question: ${input8.question}
8483
+ Learner's Answer: ${input8.userAnswer}
8484
+
8485
+ Target Concept (Correct Answer): ${input8.concept}
8486
+ Target Context: ${input8.context || "(none)"}
8487
+ ${input8.sourceLinkContent ? `Source Code Reference:
8488
+ ${input8.sourceLinkContent}` : ""}`;
8489
+ const messages = [
8490
+ { role: "system", content: systemPrompt },
8491
+ { role: "user", content: cardFrame }
8492
+ ];
8493
+ const feedback = input8.feedback?.trim();
8494
+ if (feedback) {
8495
+ messages.push({ role: "assistant", content: feedback });
8496
+ }
8497
+ for (const turn of input8.thread) {
8498
+ messages.push({ role: turn.role, content: turn.content });
8499
+ }
8500
+ messages.push({ role: "user", content: input8.message });
8501
+ const res = await fetchWithInteractiveTimeout(
8502
+ `${endpoint.url}/chat/completions`,
8503
+ {
8504
+ method: "POST",
8505
+ headers: {
8506
+ "Content-Type": "application/json",
8507
+ Authorization: `Bearer ${endpoint.apiKey}`
8508
+ },
8509
+ body: JSON.stringify({
8510
+ model: endpoint.model,
8511
+ messages,
8512
+ temperature: 0.3,
8513
+ max_tokens: RECALL_DISCUSSION_MAX_OUTPUT_TOKENS
8514
+ }),
8515
+ locale: cfg.locale
8516
+ }
8517
+ );
8518
+ const text = await readChatContent(res, "LLM discussion");
8519
+ return {
8520
+ text,
8521
+ model: endpoint.model,
8522
+ providerName: endpoint.providerName
8523
+ };
8524
+ }
8332
8525
  var MAX_IMPORT_TEXT_CHARS = 2e5;
8333
8526
  var VALID_GENERATED_MODES = /* @__PURE__ */ new Set(["shadowing", "copilot", "autonomy"]);
8334
8527
  function parseGeneratedCardArray(responseText, label, limits) {
@@ -9566,7 +9759,7 @@ async function readWebLink(url) {
9566
9759
  redirect: "manual",
9567
9760
  signal: controller.signal,
9568
9761
  headers: {
9569
- "User-Agent": "ZAM-Content-Studio/0.10.0"
9762
+ "User-Agent": "ZAM-Content-Studio/0.10.4"
9570
9763
  }
9571
9764
  });
9572
9765
  if (res.status >= 300 && res.status < 400) {
@@ -16224,6 +16417,93 @@ bridgeCommand.command("evaluate-answer").description(
16224
16417
  }
16225
16418
  });
16226
16419
  });
16420
+ function parseDiscussionThread(raw) {
16421
+ if (!raw) return [];
16422
+ let parsed;
16423
+ try {
16424
+ parsed = JSON.parse(raw);
16425
+ } catch {
16426
+ throw new Error("Invalid --thread: not valid JSON");
16427
+ }
16428
+ if (!Array.isArray(parsed)) {
16429
+ throw new Error("Invalid --thread: expected a JSON array of turns");
16430
+ }
16431
+ return parsed.map((turn, index) => {
16432
+ const candidate = turn;
16433
+ if (candidate?.role !== "user" && candidate?.role !== "assistant" || typeof candidate?.content !== "string") {
16434
+ throw new Error(
16435
+ `Invalid --thread: turn ${index} must be {"role": "user"|"assistant", "content": string}`
16436
+ );
16437
+ }
16438
+ return { role: candidate.role, content: candidate.content };
16439
+ });
16440
+ }
16441
+ bridgeCommand.command("discuss-review").description(
16442
+ "Answer one turn of the post-reveal follow-up discussion about a card (JSON)"
16443
+ ).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(
16444
+ "--thread <json>",
16445
+ 'Prior turns, oldest first, as JSON: [{"role":"user"|"assistant","content":"\u2026"},\u2026]'
16446
+ ).option("--context <context>", "Optional token context details").option("--source-link <link>", "Optional source link").option(
16447
+ "--source-content <content>",
16448
+ "Pre-resolved source reference content (skips re-fetch when set)"
16449
+ ).action(async (opts) => {
16450
+ await withDb2(async (db) => {
16451
+ const isEnabled = await getSetting(db, "llm.enabled") === "true";
16452
+ if (!isEnabled) {
16453
+ jsonOut2({
16454
+ success: false,
16455
+ error: "LLM integration is disabled",
16456
+ reply: ""
16457
+ });
16458
+ return;
16459
+ }
16460
+ let thread;
16461
+ try {
16462
+ thread = parseDiscussionThread(opts.thread);
16463
+ } catch (err) {
16464
+ jsonOut2({
16465
+ success: false,
16466
+ error: err.message,
16467
+ reply: ""
16468
+ });
16469
+ return;
16470
+ }
16471
+ let resolvedContextContent = opts.sourceContent ?? null;
16472
+ if (resolvedContextContent == null && opts.sourceLink) {
16473
+ try {
16474
+ const resolved = await resolveReviewContext(opts.sourceLink);
16475
+ resolvedContextContent = resolved?.content ?? null;
16476
+ } catch {
16477
+ }
16478
+ }
16479
+ try {
16480
+ const result = await discussReviewViaLLM(db, {
16481
+ slug: opts.slug,
16482
+ concept: opts.concept,
16483
+ domain: opts.domain,
16484
+ bloomLevel: Number(opts.bloomLevel),
16485
+ context: opts.context,
16486
+ question: opts.question,
16487
+ userAnswer: opts.userAnswer,
16488
+ sourceLinkContent: resolvedContextContent,
16489
+ feedback: opts.feedback ?? null,
16490
+ thread,
16491
+ message: opts.message
16492
+ });
16493
+ jsonOut2({
16494
+ success: true,
16495
+ reply: result.text,
16496
+ replyModel: result.model
16497
+ });
16498
+ } catch (err) {
16499
+ jsonOut2({
16500
+ success: false,
16501
+ error: err.message,
16502
+ reply: ""
16503
+ });
16504
+ }
16505
+ });
16506
+ });
16227
16507
  bridgeCommand.command("desktop-bootstrap").description("Initialize first-run desktop state (JSON)").option("--user <id>", "Preferred user ID when none is configured").action(async (opts) => {
16228
16508
  await withDb2(async (db) => {
16229
16509
  const userId = await ensureDefaultUser(db, opts.user);
@@ -16252,6 +16532,54 @@ bridgeCommand.command("get-settings").description("Get active ZAM settings (JSON
16252
16532
  });
16253
16533
  });
16254
16534
  });
16535
+ bridgeCommand.command("agent-harness-status").description(
16536
+ "Detect installed agent harnesses and their ZAM MCP configuration state (JSON)"
16537
+ ).action(() => {
16538
+ const report = inspectConnectHarnesses();
16539
+ jsonOut2({
16540
+ success: true,
16541
+ zamOnPath: report.zamOnPath,
16542
+ harnesses: report.harnesses
16543
+ });
16544
+ });
16545
+ bridgeCommand.command("agent-connect").description(
16546
+ "Run the idempotent agent-connect flow for one or all detected harnesses (JSON)"
16547
+ ).option("--harness <id>", "Explicit harness id (default: all detected)").option(
16548
+ "--auto-once",
16549
+ "First-run mode: skip when the auto-connect marker is already set; set the marker after a run that detected at least one harness"
16550
+ ).action(async (opts) => {
16551
+ if (opts.harness && !isConnectHarnessId(opts.harness)) {
16552
+ jsonOut2({
16553
+ success: false,
16554
+ error: `Unsupported harness: ${opts.harness}`
16555
+ });
16556
+ return;
16557
+ }
16558
+ const harness = opts.harness;
16559
+ const run = () => {
16560
+ const report = performAgentConnect({ harness });
16561
+ return {
16562
+ success: report.success,
16563
+ detected: report.detected,
16564
+ zamOnPath: report.zamOnPath,
16565
+ results: report.results.map(({ content: _content, ...rest }) => rest),
16566
+ skills: report.skills
16567
+ };
16568
+ };
16569
+ if (opts.autoOnce) {
16570
+ if (getAgentConnectAutoDone()) {
16571
+ jsonOut2({ success: true, skipped: true });
16572
+ return;
16573
+ }
16574
+ const payload = run();
16575
+ if (payload.detected.length > 0) {
16576
+ setAgentConnectAutoDone(true);
16577
+ }
16578
+ jsonOut2(payload);
16579
+ return;
16580
+ }
16581
+ jsonOut2(run());
16582
+ });
16255
16583
  async function readDatabaseUserSummaries(db) {
16256
16584
  return await db.prepare(
16257
16585
  `SELECT user_id AS id, COUNT(*) AS cardCount
@@ -21227,9 +21555,49 @@ async function activateMachineProviderConfig(db) {
21227
21555
  ` activate ${providerCount} machine-local provider(s) from ~/.zam/config.json`
21228
21556
  );
21229
21557
  }
21558
+ function connectDetectedAgentHarnesses(skip, dryRun) {
21559
+ if (skip) return;
21560
+ try {
21561
+ const report = performAgentConnect({ dryRun });
21562
+ if (report.detected.length === 0) {
21563
+ console.log(
21564
+ " skip no supported agent harness detected (zam agent connect <harness> configures one explicitly)"
21565
+ );
21566
+ return;
21567
+ }
21568
+ for (const result of report.results) {
21569
+ if (result.error) {
21570
+ console.warn(` warn ${result.harness}: ${result.error}`);
21571
+ } else if (dryRun) {
21572
+ console.log(
21573
+ ` plan ${result.harness}: would ensure MCP config at ${result.path}`
21574
+ );
21575
+ } else if (result.alreadyConfigured) {
21576
+ console.log(
21577
+ ` skip ${result.harness}: MCP already configured (${result.path})`
21578
+ );
21579
+ } else {
21580
+ console.log(
21581
+ ` wire ${result.harness}: MCP configured (${result.path})`
21582
+ );
21583
+ }
21584
+ }
21585
+ if (report.skills) {
21586
+ console.log(
21587
+ ` wire global ZAM skill: ${report.skills.refreshed}/${report.skills.total} locations`
21588
+ );
21589
+ }
21590
+ } catch (err) {
21591
+ console.warn(` warn agent connect: ${err.message}`);
21592
+ }
21593
+ }
21230
21594
  var setupCommand = new Command18("setup").description(
21231
21595
  "Link ZAM skill directories into this workspace and initialize the database"
21232
- ).option("--force", "replace an unmanaged existing ZAM skill directory", false).option("--skip-init", "skip database initialization", false).option("--skip-claude-md", "skip CLAUDE.md generation", false).option("--skip-agents-md", "skip AGENTS.md generation", false).option("--target <path>", "repository/workspace directory to set up").option(
21596
+ ).option("--force", "replace an unmanaged existing ZAM skill directory", false).option("--skip-init", "skip database initialization", false).option("--skip-claude-md", "skip CLAUDE.md generation", false).option("--skip-agents-md", "skip AGENTS.md generation", false).option(
21597
+ "--skip-agent-connect",
21598
+ "skip wiring detected agent harnesses via MCP",
21599
+ false
21600
+ ).option("--target <path>", "repository/workspace directory to set up").option(
21233
21601
  "--agents <list>",
21234
21602
  "comma-separated agents to wire: all, claude, copilot, codex, agent"
21235
21603
  ).option(
@@ -21256,6 +21624,7 @@ var setupCommand = new Command18("setup").description(
21256
21624
  dryRun: opts.dryRun
21257
21625
  });
21258
21626
  await initDatabase(opts.skipInit || opts.dryRun);
21627
+ connectDetectedAgentHarnesses(opts.skipAgentConnect, opts.dryRun);
21259
21628
  if (agents.has("claude")) {
21260
21629
  writeClaudeMd(opts.skipClaudeMd, target, {
21261
21630
  dryRun: opts.dryRun,