zam-core 0.11.1 → 0.12.0
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 +1 -1
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/commands/mcp.js +365 -22
- 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/vscode-extension/{ZAM_Companion_0.11.1.vsix → ZAM_Companion_0.12.0.vsix} +0 -0
- package/dist/vscode-extension/manifest.json +2 -2
- package/package.json +1 -1
package/dist/cli/commands/mcp.js
CHANGED
|
@@ -8352,7 +8352,7 @@ async function prepareRecallChain(db, opts) {
|
|
|
8352
8352
|
} else {
|
|
8353
8353
|
spawnLocalRunner(endpoint.url, endpoint.model, endpoint.runner);
|
|
8354
8354
|
while (Date.now() < deadline) {
|
|
8355
|
-
await new Promise((
|
|
8355
|
+
await new Promise((resolve8) => setTimeout(resolve8, 1e3));
|
|
8356
8356
|
if (await isLlmOnline(endpoint.url)) {
|
|
8357
8357
|
online = true;
|
|
8358
8358
|
break;
|
|
@@ -8645,7 +8645,7 @@ async function startLocalRunner(url, model, locale, hint) {
|
|
|
8645
8645
|
let attempts = 0;
|
|
8646
8646
|
const dotsPerLine = 30;
|
|
8647
8647
|
while (true) {
|
|
8648
|
-
await new Promise((
|
|
8648
|
+
await new Promise((resolve8) => setTimeout(resolve8, 500));
|
|
8649
8649
|
if (await isLlmOnline(url)) {
|
|
8650
8650
|
if (attempts > 0) process.stdout.write("\n");
|
|
8651
8651
|
return true;
|
|
@@ -8734,8 +8734,8 @@ async function fetchWithInteractiveTimeout(url, options = {}) {
|
|
|
8734
8734
|
const dotsPerLine = 30;
|
|
8735
8735
|
while (true) {
|
|
8736
8736
|
let timeoutId;
|
|
8737
|
-
const timeoutPromise = new Promise((
|
|
8738
|
-
timeoutId = setTimeout(() =>
|
|
8737
|
+
const timeoutPromise = new Promise((resolve8) => {
|
|
8738
|
+
timeoutId = setTimeout(() => resolve8("timeout"), timeoutMs);
|
|
8739
8739
|
});
|
|
8740
8740
|
const dotsInterval = setInterval(() => {
|
|
8741
8741
|
process.stdout.write(".");
|
|
@@ -8989,10 +8989,272 @@ var init_client = __esm({
|
|
|
8989
8989
|
}
|
|
8990
8990
|
});
|
|
8991
8991
|
|
|
8992
|
+
// src/cli/okf/bundle.ts
|
|
8993
|
+
var bundle_exports = {};
|
|
8994
|
+
__export(bundle_exports, {
|
|
8995
|
+
OKF_VERSION: () => OKF_VERSION,
|
|
8996
|
+
RESERVED_FILES: () => RESERVED_FILES,
|
|
8997
|
+
appendLog: () => appendLog,
|
|
8998
|
+
buildCatalog: () => buildCatalog,
|
|
8999
|
+
isReservedFile: () => isReservedFile,
|
|
9000
|
+
parseFrontmatter: () => parseFrontmatter,
|
|
9001
|
+
renderIndex: () => renderIndex,
|
|
9002
|
+
toCatalogEntry: () => toCatalogEntry,
|
|
9003
|
+
validateArticle: () => validateArticle
|
|
9004
|
+
});
|
|
9005
|
+
function isReservedFile(file) {
|
|
9006
|
+
return RESERVED_FILES.includes(file);
|
|
9007
|
+
}
|
|
9008
|
+
function unquote(raw) {
|
|
9009
|
+
const v = raw.trim();
|
|
9010
|
+
if (v.length >= 2 && (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'"))) {
|
|
9011
|
+
return v.slice(1, -1);
|
|
9012
|
+
}
|
|
9013
|
+
return v;
|
|
9014
|
+
}
|
|
9015
|
+
function parseFrontmatter(markdown) {
|
|
9016
|
+
const lines = markdown.split("\n");
|
|
9017
|
+
if (lines[0]?.trim() !== "---") {
|
|
9018
|
+
throw new Error("frontmatter: file must start with a --- fence");
|
|
9019
|
+
}
|
|
9020
|
+
const fields = {};
|
|
9021
|
+
let listKey = null;
|
|
9022
|
+
let i = 1;
|
|
9023
|
+
for (; i < lines.length; i++) {
|
|
9024
|
+
const line = lines[i];
|
|
9025
|
+
if (line.trim() === "---") break;
|
|
9026
|
+
if (line.trim() === "") continue;
|
|
9027
|
+
const listItem = /^\s+-\s+(.+)$/.exec(line);
|
|
9028
|
+
if (listItem) {
|
|
9029
|
+
if (!listKey) {
|
|
9030
|
+
throw new Error(`frontmatter line ${i + 1}: list item without a key`);
|
|
9031
|
+
}
|
|
9032
|
+
fields[listKey].push(unquote(listItem[1]));
|
|
9033
|
+
continue;
|
|
9034
|
+
}
|
|
9035
|
+
const pair = /^([A-Za-z0-9_-]+):(.*)$/.exec(line);
|
|
9036
|
+
if (!pair) {
|
|
9037
|
+
throw new Error(
|
|
9038
|
+
`frontmatter line ${i + 1}: expected "key: value" or "- item"`
|
|
9039
|
+
);
|
|
9040
|
+
}
|
|
9041
|
+
const key = pair[1];
|
|
9042
|
+
const rest = pair[2].trim();
|
|
9043
|
+
if (rest === "") {
|
|
9044
|
+
fields[key] = [];
|
|
9045
|
+
listKey = key;
|
|
9046
|
+
} else {
|
|
9047
|
+
fields[key] = unquote(rest);
|
|
9048
|
+
listKey = null;
|
|
9049
|
+
}
|
|
9050
|
+
}
|
|
9051
|
+
if (i >= lines.length) {
|
|
9052
|
+
throw new Error("frontmatter: missing closing --- fence");
|
|
9053
|
+
}
|
|
9054
|
+
return { fields, body: lines.slice(i + 1).join("\n") };
|
|
9055
|
+
}
|
|
9056
|
+
function scalar(fields, key) {
|
|
9057
|
+
const v = fields[key];
|
|
9058
|
+
return typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
|
|
9059
|
+
}
|
|
9060
|
+
function validateArticle(file, markdown) {
|
|
9061
|
+
const problems = [];
|
|
9062
|
+
if (isReservedFile(file)) {
|
|
9063
|
+
return { ok: false, problems: [`${file}: reserved OKF file name`] };
|
|
9064
|
+
}
|
|
9065
|
+
if (!FILE_NAME_RE.test(file)) {
|
|
9066
|
+
problems.push(`${file}: file name must be kebab-case and end in .md`);
|
|
9067
|
+
}
|
|
9068
|
+
let parsed = null;
|
|
9069
|
+
try {
|
|
9070
|
+
parsed = parseFrontmatter(markdown);
|
|
9071
|
+
} catch (err) {
|
|
9072
|
+
problems.push(
|
|
9073
|
+
`${file}: ${err instanceof Error ? err.message : String(err)}`
|
|
9074
|
+
);
|
|
9075
|
+
}
|
|
9076
|
+
if (parsed) {
|
|
9077
|
+
if (!scalar(parsed.fields, "type")) {
|
|
9078
|
+
problems.push(`${file}: frontmatter field "type" is required`);
|
|
9079
|
+
}
|
|
9080
|
+
if (!scalar(parsed.fields, "description")) {
|
|
9081
|
+
problems.push(`${file}: frontmatter field "description" is required`);
|
|
9082
|
+
}
|
|
9083
|
+
if (parsed.body.trim() === "") {
|
|
9084
|
+
problems.push(`${file}: article body is empty`);
|
|
9085
|
+
}
|
|
9086
|
+
}
|
|
9087
|
+
return { ok: problems.length === 0, problems };
|
|
9088
|
+
}
|
|
9089
|
+
function toCatalogEntry(file, markdown) {
|
|
9090
|
+
const { fields } = parseFrontmatter(markdown);
|
|
9091
|
+
const tags = Array.isArray(fields.tags) ? fields.tags : [];
|
|
9092
|
+
return {
|
|
9093
|
+
file,
|
|
9094
|
+
type: scalar(fields, "type") ?? "",
|
|
9095
|
+
title: scalar(fields, "title") ?? file.replace(/\.md$/, ""),
|
|
9096
|
+
description: scalar(fields, "description") ?? "",
|
|
9097
|
+
tags,
|
|
9098
|
+
resource: scalar(fields, "resource"),
|
|
9099
|
+
timestamp: scalar(fields, "timestamp")
|
|
9100
|
+
};
|
|
9101
|
+
}
|
|
9102
|
+
function buildCatalog(articles) {
|
|
9103
|
+
return articles.map(({ file, markdown }) => toCatalogEntry(file, markdown)).sort((a, b) => a.file.localeCompare(b.file));
|
|
9104
|
+
}
|
|
9105
|
+
function renderIndex(catalog, okfVersion = OKF_VERSION) {
|
|
9106
|
+
const types = [...new Set(catalog.map((e) => e.type))].sort();
|
|
9107
|
+
const sections = types.map((type) => {
|
|
9108
|
+
const rows = catalog.filter((e) => e.type === type).map((e) => `- [${e.title}](${e.file}) \u2014 ${e.description}`).join("\n");
|
|
9109
|
+
return `## ${type}
|
|
9110
|
+
|
|
9111
|
+
${rows}`;
|
|
9112
|
+
});
|
|
9113
|
+
return [
|
|
9114
|
+
"---",
|
|
9115
|
+
`okf_version: "${okfVersion}"`,
|
|
9116
|
+
"---",
|
|
9117
|
+
"",
|
|
9118
|
+
"# ZAM Knowledge Base",
|
|
9119
|
+
"",
|
|
9120
|
+
"Living reference knowledge for this repository in",
|
|
9121
|
+
"[Open Knowledge Format](https://github.com/GoogleCloudPlatform/knowledge-catalog).",
|
|
9122
|
+
"Current truth only \u2014 the *why* behind it lives in [../adr/](../adr/)",
|
|
9123
|
+
"(ADR 2026-07-17). Do not edit by hand: write through the",
|
|
9124
|
+
"`zam_okf_upsert` MCP tool.",
|
|
9125
|
+
"",
|
|
9126
|
+
sections.join("\n\n"),
|
|
9127
|
+
""
|
|
9128
|
+
].join("\n");
|
|
9129
|
+
}
|
|
9130
|
+
function appendLog(existing, date, line) {
|
|
9131
|
+
const header = `## ${date}`;
|
|
9132
|
+
const entry = `- ${line}`;
|
|
9133
|
+
const trimmed = existing.trim();
|
|
9134
|
+
if (trimmed === "") {
|
|
9135
|
+
return `# Log
|
|
9136
|
+
|
|
9137
|
+
${header}
|
|
9138
|
+
|
|
9139
|
+
${entry}
|
|
9140
|
+
`;
|
|
9141
|
+
}
|
|
9142
|
+
const lines = trimmed.split("\n");
|
|
9143
|
+
const firstHeaderIdx = lines.findIndex((l) => l.startsWith("## "));
|
|
9144
|
+
if (firstHeaderIdx !== -1 && lines[firstHeaderIdx].trim() === header) {
|
|
9145
|
+
lines.splice(firstHeaderIdx + 2, 0, entry);
|
|
9146
|
+
return `${lines.join("\n")}
|
|
9147
|
+
`;
|
|
9148
|
+
}
|
|
9149
|
+
const insertAt = firstHeaderIdx === -1 ? lines.length : firstHeaderIdx;
|
|
9150
|
+
lines.splice(insertAt, 0, header, "", entry, "");
|
|
9151
|
+
return `${lines.join("\n")}
|
|
9152
|
+
`;
|
|
9153
|
+
}
|
|
9154
|
+
var OKF_VERSION, RESERVED_FILES, FILE_NAME_RE;
|
|
9155
|
+
var init_bundle = __esm({
|
|
9156
|
+
"src/cli/okf/bundle.ts"() {
|
|
9157
|
+
"use strict";
|
|
9158
|
+
OKF_VERSION = "0.1";
|
|
9159
|
+
RESERVED_FILES = ["index.md", "log.md"];
|
|
9160
|
+
FILE_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*\.md$/;
|
|
9161
|
+
}
|
|
9162
|
+
});
|
|
9163
|
+
|
|
9164
|
+
// src/cli/okf/io.ts
|
|
9165
|
+
var io_exports = {};
|
|
9166
|
+
__export(io_exports, {
|
|
9167
|
+
DEFAULT_BUNDLE_DIR: () => DEFAULT_BUNDLE_DIR,
|
|
9168
|
+
loadBundle: () => loadBundle,
|
|
9169
|
+
resolveArticlePath: () => resolveArticlePath,
|
|
9170
|
+
upsertArticle: () => upsertArticle
|
|
9171
|
+
});
|
|
9172
|
+
import { mkdirSync as mkdirSync16, readdirSync as readdirSync3, readFileSync as readFileSync18, writeFileSync as writeFileSync13 } from "fs";
|
|
9173
|
+
import { join as join24, resolve as resolve7 } from "path";
|
|
9174
|
+
function resolveArticlePath(dir, file) {
|
|
9175
|
+
if (file.includes("/") || file.includes("\\") || file.includes("..")) {
|
|
9176
|
+
throw new Error(`invalid article file name: ${file}`);
|
|
9177
|
+
}
|
|
9178
|
+
if (isReservedFile(file)) {
|
|
9179
|
+
throw new Error(`refusing to address reserved file: ${file}`);
|
|
9180
|
+
}
|
|
9181
|
+
return join24(resolve7(dir), file);
|
|
9182
|
+
}
|
|
9183
|
+
function loadBundle(dir) {
|
|
9184
|
+
const root = resolve7(dir);
|
|
9185
|
+
let entries;
|
|
9186
|
+
try {
|
|
9187
|
+
entries = readdirSync3(root).filter(
|
|
9188
|
+
(name) => name.endsWith(".md") && !isReservedFile(name)
|
|
9189
|
+
);
|
|
9190
|
+
} catch {
|
|
9191
|
+
throw new Error(`OKF bundle directory not found: ${root}`);
|
|
9192
|
+
}
|
|
9193
|
+
const articles = entries.sort().map((file) => ({
|
|
9194
|
+
file,
|
|
9195
|
+
markdown: readFileSync18(join24(root, file), "utf8")
|
|
9196
|
+
}));
|
|
9197
|
+
const problems = articles.flatMap(
|
|
9198
|
+
({ file, markdown }) => validateArticle(file, markdown).problems
|
|
9199
|
+
);
|
|
9200
|
+
const catalog = problems.length === 0 ? buildCatalog(articles) : safeCatalog(articles);
|
|
9201
|
+
return { dir: root, articles, catalog, problems };
|
|
9202
|
+
}
|
|
9203
|
+
function safeCatalog(articles) {
|
|
9204
|
+
const entries = [];
|
|
9205
|
+
for (const { file, markdown } of articles) {
|
|
9206
|
+
try {
|
|
9207
|
+
entries.push(toCatalogEntry(file, markdown));
|
|
9208
|
+
} catch {
|
|
9209
|
+
}
|
|
9210
|
+
}
|
|
9211
|
+
return entries.sort((a, b) => a.file.localeCompare(b.file));
|
|
9212
|
+
}
|
|
9213
|
+
function upsertArticle(dir, file, markdown, today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)) {
|
|
9214
|
+
const target = resolveArticlePath(dir, file);
|
|
9215
|
+
const validation = validateArticle(file, markdown);
|
|
9216
|
+
if (!validation.ok) return { validation };
|
|
9217
|
+
const root = resolve7(dir);
|
|
9218
|
+
mkdirSync16(root, { recursive: true });
|
|
9219
|
+
let created = true;
|
|
9220
|
+
try {
|
|
9221
|
+
readFileSync18(target, "utf8");
|
|
9222
|
+
created = false;
|
|
9223
|
+
} catch {
|
|
9224
|
+
}
|
|
9225
|
+
writeFileSync13(target, markdown, "utf8");
|
|
9226
|
+
const bundle = loadBundle(root);
|
|
9227
|
+
writeFileSync13(join24(root, "index.md"), renderIndex(bundle.catalog), "utf8");
|
|
9228
|
+
let log = "";
|
|
9229
|
+
try {
|
|
9230
|
+
log = readFileSync18(join24(root, "log.md"), "utf8");
|
|
9231
|
+
} catch {
|
|
9232
|
+
}
|
|
9233
|
+
const entry = bundle.catalog.find((e) => e.file === file);
|
|
9234
|
+
writeFileSync13(
|
|
9235
|
+
join24(root, "log.md"),
|
|
9236
|
+
appendLog(
|
|
9237
|
+
log,
|
|
9238
|
+
today,
|
|
9239
|
+
`**${created ? "Creation" : "Update"}** \u2014 [${entry?.title ?? file}](${file})`
|
|
9240
|
+
),
|
|
9241
|
+
"utf8"
|
|
9242
|
+
);
|
|
9243
|
+
return { validation, entry, created };
|
|
9244
|
+
}
|
|
9245
|
+
var DEFAULT_BUNDLE_DIR;
|
|
9246
|
+
var init_io = __esm({
|
|
9247
|
+
"src/cli/okf/io.ts"() {
|
|
9248
|
+
"use strict";
|
|
9249
|
+
init_bundle();
|
|
9250
|
+
DEFAULT_BUNDLE_DIR = "docs/okf";
|
|
9251
|
+
}
|
|
9252
|
+
});
|
|
9253
|
+
|
|
8992
9254
|
// src/cli/commands/mcp.ts
|
|
8993
9255
|
init_kernel();
|
|
8994
|
-
import { existsSync as existsSync21, readFileSync as
|
|
8995
|
-
import { dirname as dirname11, join as
|
|
9256
|
+
import { existsSync as existsSync21, readFileSync as readFileSync19 } from "fs";
|
|
9257
|
+
import { dirname as dirname11, join as join25 } from "path";
|
|
8996
9258
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
8997
9259
|
import {
|
|
8998
9260
|
RESOURCE_MIME_TYPE,
|
|
@@ -11636,7 +11898,7 @@ async function readWebLink(url) {
|
|
|
11636
11898
|
redirect: "manual",
|
|
11637
11899
|
signal: controller.signal,
|
|
11638
11900
|
headers: {
|
|
11639
|
-
"User-Agent": "ZAM-Content-Studio/0.
|
|
11901
|
+
"User-Agent": "ZAM-Content-Studio/0.12.0"
|
|
11640
11902
|
}
|
|
11641
11903
|
});
|
|
11642
11904
|
if (res.status >= 300 && res.status < 400) {
|
|
@@ -28964,25 +29226,25 @@ async function observeUiSnapshotViaLLM(db, input) {
|
|
|
28964
29226
|
const imageUrls = [];
|
|
28965
29227
|
const isVideo = /\.(mp4|mov|m4v|avi|mkv|webm)$/i.test(input.imagePath);
|
|
28966
29228
|
if (isVideo) {
|
|
28967
|
-
const { mkdirSync:
|
|
29229
|
+
const { mkdirSync: mkdirSync17, readdirSync: readdirSync4, rmSync: rmSync4 } = await import("fs");
|
|
28968
29230
|
const { execSync: execSync5 } = await import("child_process");
|
|
28969
29231
|
const tempDir = join20(
|
|
28970
29232
|
tmpdir2(),
|
|
28971
29233
|
`zam-frames-${randomBytes(4).toString("hex")}`
|
|
28972
29234
|
);
|
|
28973
|
-
|
|
29235
|
+
mkdirSync17(tempDir, { recursive: true });
|
|
28974
29236
|
try {
|
|
28975
29237
|
execSync5(
|
|
28976
29238
|
`ffmpeg -i "${input.imagePath}" -vf "fps=1/3,scale=1280:-1" -vsync vfr "${tempDir}/frame_%03d.png"`,
|
|
28977
29239
|
{ stdio: "ignore" }
|
|
28978
29240
|
);
|
|
28979
|
-
let files =
|
|
29241
|
+
let files = readdirSync4(tempDir).filter((f) => f.endsWith(".png")).sort();
|
|
28980
29242
|
if (files.length === 0) {
|
|
28981
29243
|
execSync5(
|
|
28982
29244
|
`ffmpeg -i "${input.imagePath}" -vframes 1 "${tempDir}/frame_001.png"`,
|
|
28983
29245
|
{ stdio: "ignore" }
|
|
28984
29246
|
);
|
|
28985
|
-
files =
|
|
29247
|
+
files = readdirSync4(tempDir).filter((f) => f.endsWith(".png")).sort();
|
|
28986
29248
|
}
|
|
28987
29249
|
const maxFrames = cfg.maxFrames ?? 100;
|
|
28988
29250
|
let sampledFiles = files;
|
|
@@ -30801,7 +31063,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
30801
31063
|
const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
30802
31064
|
const defaultExt = platform === "win32" ? ".mkv" : ".mov";
|
|
30803
31065
|
const outputPath = opts.output ?? join22(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
|
|
30804
|
-
const { existsSync: existsSync22, writeFileSync:
|
|
31066
|
+
const { existsSync: existsSync22, writeFileSync: writeFileSync14, openSync, closeSync } = await import("fs");
|
|
30805
31067
|
if (existsSync22(statePath)) {
|
|
30806
31068
|
jsonOut({
|
|
30807
31069
|
sessionId,
|
|
@@ -30856,7 +31118,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
30856
31118
|
}
|
|
30857
31119
|
child.unref();
|
|
30858
31120
|
if (child.pid) {
|
|
30859
|
-
|
|
31121
|
+
writeFileSync14(
|
|
30860
31122
|
statePath,
|
|
30861
31123
|
JSON.stringify({
|
|
30862
31124
|
pid: child.pid,
|
|
@@ -30893,7 +31155,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
30893
31155
|
}
|
|
30894
31156
|
const sessionId = opts.session;
|
|
30895
31157
|
const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
30896
|
-
const { existsSync: existsSync22, readFileSync:
|
|
31158
|
+
const { existsSync: existsSync22, readFileSync: readFileSync20, rmSync: rmSync4 } = await import("fs");
|
|
30897
31159
|
if (!existsSync22(statePath)) {
|
|
30898
31160
|
jsonOut({
|
|
30899
31161
|
sessionId,
|
|
@@ -30902,7 +31164,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
30902
31164
|
});
|
|
30903
31165
|
return;
|
|
30904
31166
|
}
|
|
30905
|
-
const state = JSON.parse(
|
|
31167
|
+
const state = JSON.parse(readFileSync20(statePath, "utf8"));
|
|
30906
31168
|
const { pid, outputPath } = state;
|
|
30907
31169
|
try {
|
|
30908
31170
|
process.kill(pid, "SIGINT");
|
|
@@ -30918,7 +31180,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
30918
31180
|
};
|
|
30919
31181
|
let attempts = 0;
|
|
30920
31182
|
while (isProcessRunning(pid) && attempts < 20) {
|
|
30921
|
-
await new Promise((
|
|
31183
|
+
await new Promise((resolve8) => setTimeout(resolve8, 250));
|
|
30922
31184
|
attempts++;
|
|
30923
31185
|
}
|
|
30924
31186
|
if (isProcessRunning(pid)) {
|
|
@@ -33556,11 +33818,11 @@ async function publishUiIntent(app, input = {}, opts = {}) {
|
|
|
33556
33818
|
|
|
33557
33819
|
// src/cli/commands/mcp.ts
|
|
33558
33820
|
var __dirname = dirname11(fileURLToPath6(import.meta.url));
|
|
33559
|
-
var pkgPath =
|
|
33821
|
+
var pkgPath = join25(__dirname, "..", "..", "package.json");
|
|
33560
33822
|
if (!existsSync21(pkgPath)) {
|
|
33561
|
-
pkgPath =
|
|
33823
|
+
pkgPath = join25(__dirname, "..", "..", "..", "package.json");
|
|
33562
33824
|
}
|
|
33563
|
-
var pkg = JSON.parse(
|
|
33825
|
+
var pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
|
|
33564
33826
|
var STUDIO_RESOURCE_URI = "ui://zam/studio";
|
|
33565
33827
|
var RECALL_RESOURCE_URI = "ui://zam/recall";
|
|
33566
33828
|
var GRAPH_RESOURCE_URI = "ui://zam/graph";
|
|
@@ -33587,13 +33849,13 @@ var STUDIO_BRIDGE_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
33587
33849
|
function loadPanelHtml(fileName, placeholderTitle) {
|
|
33588
33850
|
const candidates = [
|
|
33589
33851
|
// dist/cli/commands/mcp.js → dist/ui/
|
|
33590
|
-
|
|
33852
|
+
join25(__dirname, "..", "..", "ui", fileName),
|
|
33591
33853
|
// src/cli/commands/mcp.ts via tsx → <repo>/dist/ui/
|
|
33592
|
-
|
|
33854
|
+
join25(__dirname, "..", "..", "..", "dist", "ui", fileName)
|
|
33593
33855
|
];
|
|
33594
33856
|
for (const candidate of candidates) {
|
|
33595
33857
|
if (existsSync21(candidate)) {
|
|
33596
|
-
return
|
|
33858
|
+
return readFileSync19(candidate, "utf-8");
|
|
33597
33859
|
}
|
|
33598
33860
|
}
|
|
33599
33861
|
return `<!doctype html>
|
|
@@ -34358,6 +34620,87 @@ function createMcpServer(db) {
|
|
|
34358
34620
|
}
|
|
34359
34621
|
)
|
|
34360
34622
|
);
|
|
34623
|
+
const okfBundleDirSchema = z.string().optional().describe("Bundle directory (default docs/okf under the server cwd)");
|
|
34624
|
+
server.registerTool(
|
|
34625
|
+
"zam_okf_catalog",
|
|
34626
|
+
{
|
|
34627
|
+
description: "List the OKF knowledge-base articles (type, title, description, tags, resource URL) plus conformance problems, if any",
|
|
34628
|
+
inputSchema: {
|
|
34629
|
+
bundle_dir: okfBundleDirSchema
|
|
34630
|
+
},
|
|
34631
|
+
annotations: {
|
|
34632
|
+
...commonAnnotations,
|
|
34633
|
+
readOnlyHint: true
|
|
34634
|
+
}
|
|
34635
|
+
},
|
|
34636
|
+
wrapHandler(async (params) => {
|
|
34637
|
+
const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
34638
|
+
const bundle = loadBundle2(params.bundle_dir ?? DEFAULT_BUNDLE_DIR2);
|
|
34639
|
+
return {
|
|
34640
|
+
dir: bundle.dir,
|
|
34641
|
+
articles: bundle.catalog,
|
|
34642
|
+
problems: bundle.problems
|
|
34643
|
+
};
|
|
34644
|
+
})
|
|
34645
|
+
);
|
|
34646
|
+
server.registerTool(
|
|
34647
|
+
"zam_okf_read",
|
|
34648
|
+
{
|
|
34649
|
+
description: "Read one OKF knowledge-base article: raw markdown plus parsed frontmatter",
|
|
34650
|
+
inputSchema: {
|
|
34651
|
+
bundle_dir: okfBundleDirSchema,
|
|
34652
|
+
file: z.string().describe("Article file name, e.g. fsrs-scheduling.md")
|
|
34653
|
+
},
|
|
34654
|
+
annotations: {
|
|
34655
|
+
...commonAnnotations,
|
|
34656
|
+
readOnlyHint: true
|
|
34657
|
+
}
|
|
34658
|
+
},
|
|
34659
|
+
wrapHandler(async (params) => {
|
|
34660
|
+
const { readFileSync: readFileSync20 } = await import("fs");
|
|
34661
|
+
const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, resolveArticlePath: resolveArticlePath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
34662
|
+
const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
|
|
34663
|
+
const path = resolveArticlePath2(
|
|
34664
|
+
params.bundle_dir ?? DEFAULT_BUNDLE_DIR2,
|
|
34665
|
+
params.file
|
|
34666
|
+
);
|
|
34667
|
+
const markdown = readFileSync20(path, "utf8");
|
|
34668
|
+
const { fields } = parseFrontmatter2(markdown);
|
|
34669
|
+
return { file: params.file, frontmatter: fields, markdown };
|
|
34670
|
+
})
|
|
34671
|
+
);
|
|
34672
|
+
server.registerTool(
|
|
34673
|
+
"zam_okf_upsert",
|
|
34674
|
+
{
|
|
34675
|
+
description: "Create or update an OKF knowledge-base article through the validated write path: checks the frontmatter contract, regenerates index.md, and appends the log.md entry. Never edit bundle files directly.",
|
|
34676
|
+
inputSchema: {
|
|
34677
|
+
bundle_dir: okfBundleDirSchema,
|
|
34678
|
+
file: z.string().describe(
|
|
34679
|
+
"Kebab-case article file name ending in .md (permanent ID)"
|
|
34680
|
+
),
|
|
34681
|
+
markdown: z.string().describe(
|
|
34682
|
+
"Full article: --- frontmatter (type, title, description, tags, resource, timestamp) then the body"
|
|
34683
|
+
)
|
|
34684
|
+
},
|
|
34685
|
+
annotations: {
|
|
34686
|
+
...commonAnnotations
|
|
34687
|
+
}
|
|
34688
|
+
},
|
|
34689
|
+
wrapHandler(
|
|
34690
|
+
async (params) => {
|
|
34691
|
+
const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, upsertArticle: upsertArticle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
34692
|
+
const result = upsertArticle2(
|
|
34693
|
+
params.bundle_dir ?? DEFAULT_BUNDLE_DIR2,
|
|
34694
|
+
params.file,
|
|
34695
|
+
params.markdown
|
|
34696
|
+
);
|
|
34697
|
+
if (!result.validation.ok) {
|
|
34698
|
+
return { ok: false, problems: result.validation.problems };
|
|
34699
|
+
}
|
|
34700
|
+
return { ok: true, created: result.created, entry: result.entry };
|
|
34701
|
+
}
|
|
34702
|
+
)
|
|
34703
|
+
);
|
|
34361
34704
|
return server;
|
|
34362
34705
|
}
|
|
34363
34706
|
async function runMcpServer() {
|