zam-core 0.11.1 → 0.13.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 +546 -29
- 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/ui/okf-panel.html +787 -0
- package/dist/vscode-extension/{ZAM_Companion_0.11.1.vsix → ZAM_Companion_0.13.0.vsix} +0 -0
- package/dist/vscode-extension/manifest.json +2 -2
- package/package.json +2 -2
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,317 @@ 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
|
+
findRepoRoot: () => findRepoRoot,
|
|
9169
|
+
loadBundle: () => loadBundle,
|
|
9170
|
+
resolveArticlePath: () => resolveArticlePath,
|
|
9171
|
+
resolveCitationPath: () => resolveCitationPath,
|
|
9172
|
+
upsertArticle: () => upsertArticle
|
|
9173
|
+
});
|
|
9174
|
+
import {
|
|
9175
|
+
existsSync as existsSync21,
|
|
9176
|
+
mkdirSync as mkdirSync16,
|
|
9177
|
+
readdirSync as readdirSync3,
|
|
9178
|
+
readFileSync as readFileSync18,
|
|
9179
|
+
realpathSync as realpathSync2,
|
|
9180
|
+
writeFileSync as writeFileSync13
|
|
9181
|
+
} from "fs";
|
|
9182
|
+
import { dirname as dirname11, isAbsolute as isAbsolute2, join as join24, relative, resolve as resolve7, sep } from "path";
|
|
9183
|
+
function resolveArticlePath(dir, file) {
|
|
9184
|
+
if (file.includes("/") || file.includes("\\") || file.includes("..")) {
|
|
9185
|
+
throw new Error(`invalid article file name: ${file}`);
|
|
9186
|
+
}
|
|
9187
|
+
if (isReservedFile(file)) {
|
|
9188
|
+
throw new Error(`refusing to address reserved file: ${file}`);
|
|
9189
|
+
}
|
|
9190
|
+
return join24(resolve7(dir), file);
|
|
9191
|
+
}
|
|
9192
|
+
function findRepoRoot(startDir) {
|
|
9193
|
+
let dir = resolve7(startDir);
|
|
9194
|
+
for (; ; ) {
|
|
9195
|
+
if (existsSync21(join24(dir, ".git"))) return dir;
|
|
9196
|
+
const parent = dirname11(dir);
|
|
9197
|
+
if (parent === dir) return resolve7(startDir, "..");
|
|
9198
|
+
dir = parent;
|
|
9199
|
+
}
|
|
9200
|
+
}
|
|
9201
|
+
function resolveCitationPath(bundleDir, target) {
|
|
9202
|
+
if (isAbsolute2(target)) {
|
|
9203
|
+
throw new Error(
|
|
9204
|
+
`invalid citation target: absolute paths are not allowed (${target})`
|
|
9205
|
+
);
|
|
9206
|
+
}
|
|
9207
|
+
if (!target.endsWith(".md")) {
|
|
9208
|
+
throw new Error(
|
|
9209
|
+
`invalid citation target: only .md files are readable (${target})`
|
|
9210
|
+
);
|
|
9211
|
+
}
|
|
9212
|
+
const root = findRepoRoot(bundleDir);
|
|
9213
|
+
const resolved = resolve7(bundleDir, target);
|
|
9214
|
+
assertContained(root, resolved, target);
|
|
9215
|
+
if (existsSync21(resolved)) {
|
|
9216
|
+
assertContained(realpathSync2(root), realpathSync2(resolved), target);
|
|
9217
|
+
}
|
|
9218
|
+
return resolved;
|
|
9219
|
+
}
|
|
9220
|
+
function assertContained(root, candidate, target) {
|
|
9221
|
+
const rel = relative(root, candidate);
|
|
9222
|
+
if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute2(rel)) {
|
|
9223
|
+
throw new Error(
|
|
9224
|
+
`invalid citation target: resolves outside the repository root (${target})`
|
|
9225
|
+
);
|
|
9226
|
+
}
|
|
9227
|
+
}
|
|
9228
|
+
function loadBundle(dir) {
|
|
9229
|
+
const root = resolve7(dir);
|
|
9230
|
+
let entries;
|
|
9231
|
+
try {
|
|
9232
|
+
entries = readdirSync3(root).filter(
|
|
9233
|
+
(name) => name.endsWith(".md") && !isReservedFile(name)
|
|
9234
|
+
);
|
|
9235
|
+
} catch {
|
|
9236
|
+
throw new Error(`OKF bundle directory not found: ${root}`);
|
|
9237
|
+
}
|
|
9238
|
+
const articles = entries.sort().map((file) => ({
|
|
9239
|
+
file,
|
|
9240
|
+
markdown: readFileSync18(join24(root, file), "utf8")
|
|
9241
|
+
}));
|
|
9242
|
+
const problems = articles.flatMap(
|
|
9243
|
+
({ file, markdown }) => validateArticle(file, markdown).problems
|
|
9244
|
+
);
|
|
9245
|
+
const catalog = problems.length === 0 ? buildCatalog(articles) : safeCatalog(articles);
|
|
9246
|
+
return { dir: root, articles, catalog, problems };
|
|
9247
|
+
}
|
|
9248
|
+
function safeCatalog(articles) {
|
|
9249
|
+
const entries = [];
|
|
9250
|
+
for (const { file, markdown } of articles) {
|
|
9251
|
+
try {
|
|
9252
|
+
entries.push(toCatalogEntry(file, markdown));
|
|
9253
|
+
} catch {
|
|
9254
|
+
}
|
|
9255
|
+
}
|
|
9256
|
+
return entries.sort((a, b) => a.file.localeCompare(b.file));
|
|
9257
|
+
}
|
|
9258
|
+
function upsertArticle(dir, file, markdown, today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)) {
|
|
9259
|
+
const target = resolveArticlePath(dir, file);
|
|
9260
|
+
const validation = validateArticle(file, markdown);
|
|
9261
|
+
if (!validation.ok) return { validation };
|
|
9262
|
+
const root = resolve7(dir);
|
|
9263
|
+
mkdirSync16(root, { recursive: true });
|
|
9264
|
+
let created = true;
|
|
9265
|
+
try {
|
|
9266
|
+
readFileSync18(target, "utf8");
|
|
9267
|
+
created = false;
|
|
9268
|
+
} catch {
|
|
9269
|
+
}
|
|
9270
|
+
writeFileSync13(target, markdown, "utf8");
|
|
9271
|
+
const bundle = loadBundle(root);
|
|
9272
|
+
writeFileSync13(join24(root, "index.md"), renderIndex(bundle.catalog), "utf8");
|
|
9273
|
+
let log = "";
|
|
9274
|
+
try {
|
|
9275
|
+
log = readFileSync18(join24(root, "log.md"), "utf8");
|
|
9276
|
+
} catch {
|
|
9277
|
+
}
|
|
9278
|
+
const entry = bundle.catalog.find((e) => e.file === file);
|
|
9279
|
+
writeFileSync13(
|
|
9280
|
+
join24(root, "log.md"),
|
|
9281
|
+
appendLog(
|
|
9282
|
+
log,
|
|
9283
|
+
today,
|
|
9284
|
+
`**${created ? "Creation" : "Update"}** \u2014 [${entry?.title ?? file}](${file})`
|
|
9285
|
+
),
|
|
9286
|
+
"utf8"
|
|
9287
|
+
);
|
|
9288
|
+
return { validation, entry, created };
|
|
9289
|
+
}
|
|
9290
|
+
var DEFAULT_BUNDLE_DIR;
|
|
9291
|
+
var init_io = __esm({
|
|
9292
|
+
"src/cli/okf/io.ts"() {
|
|
9293
|
+
"use strict";
|
|
9294
|
+
init_bundle();
|
|
9295
|
+
DEFAULT_BUNDLE_DIR = "docs/okf";
|
|
9296
|
+
}
|
|
9297
|
+
});
|
|
9298
|
+
|
|
8992
9299
|
// src/cli/commands/mcp.ts
|
|
8993
9300
|
init_kernel();
|
|
8994
|
-
import { existsSync as
|
|
8995
|
-
import { dirname as
|
|
9301
|
+
import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
|
|
9302
|
+
import { dirname as dirname12, join as join25 } from "path";
|
|
8996
9303
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
8997
9304
|
import {
|
|
8998
9305
|
RESOURCE_MIME_TYPE,
|
|
@@ -9085,7 +9392,8 @@ var COMPANION_SURFACES = [
|
|
|
9085
9392
|
"recall",
|
|
9086
9393
|
"graph",
|
|
9087
9394
|
"settings",
|
|
9088
|
-
"studio"
|
|
9395
|
+
"studio",
|
|
9396
|
+
"okf"
|
|
9089
9397
|
];
|
|
9090
9398
|
function isCompanionSurface(value) {
|
|
9091
9399
|
return typeof value === "string" && COMPANION_SURFACES.includes(value);
|
|
@@ -11636,7 +11944,7 @@ async function readWebLink(url) {
|
|
|
11636
11944
|
redirect: "manual",
|
|
11637
11945
|
signal: controller.signal,
|
|
11638
11946
|
headers: {
|
|
11639
|
-
"User-Agent": "ZAM-Content-Studio/0.
|
|
11947
|
+
"User-Agent": "ZAM-Content-Studio/0.13.0"
|
|
11640
11948
|
}
|
|
11641
11949
|
});
|
|
11642
11950
|
if (res.status >= 300 && res.status < 400) {
|
|
@@ -28964,25 +29272,25 @@ async function observeUiSnapshotViaLLM(db, input) {
|
|
|
28964
29272
|
const imageUrls = [];
|
|
28965
29273
|
const isVideo = /\.(mp4|mov|m4v|avi|mkv|webm)$/i.test(input.imagePath);
|
|
28966
29274
|
if (isVideo) {
|
|
28967
|
-
const { mkdirSync:
|
|
29275
|
+
const { mkdirSync: mkdirSync17, readdirSync: readdirSync4, rmSync: rmSync4 } = await import("fs");
|
|
28968
29276
|
const { execSync: execSync5 } = await import("child_process");
|
|
28969
29277
|
const tempDir = join20(
|
|
28970
29278
|
tmpdir2(),
|
|
28971
29279
|
`zam-frames-${randomBytes(4).toString("hex")}`
|
|
28972
29280
|
);
|
|
28973
|
-
|
|
29281
|
+
mkdirSync17(tempDir, { recursive: true });
|
|
28974
29282
|
try {
|
|
28975
29283
|
execSync5(
|
|
28976
29284
|
`ffmpeg -i "${input.imagePath}" -vf "fps=1/3,scale=1280:-1" -vsync vfr "${tempDir}/frame_%03d.png"`,
|
|
28977
29285
|
{ stdio: "ignore" }
|
|
28978
29286
|
);
|
|
28979
|
-
let files =
|
|
29287
|
+
let files = readdirSync4(tempDir).filter((f) => f.endsWith(".png")).sort();
|
|
28980
29288
|
if (files.length === 0) {
|
|
28981
29289
|
execSync5(
|
|
28982
29290
|
`ffmpeg -i "${input.imagePath}" -vframes 1 "${tempDir}/frame_001.png"`,
|
|
28983
29291
|
{ stdio: "ignore" }
|
|
28984
29292
|
);
|
|
28985
|
-
files =
|
|
29293
|
+
files = readdirSync4(tempDir).filter((f) => f.endsWith(".png")).sort();
|
|
28986
29294
|
}
|
|
28987
29295
|
const maxFrames = cfg.maxFrames ?? 100;
|
|
28988
29296
|
let sampledFiles = files;
|
|
@@ -30801,8 +31109,8 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
30801
31109
|
const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
30802
31110
|
const defaultExt = platform === "win32" ? ".mkv" : ".mov";
|
|
30803
31111
|
const outputPath = opts.output ?? join22(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
|
|
30804
|
-
const { existsSync:
|
|
30805
|
-
if (
|
|
31112
|
+
const { existsSync: existsSync23, writeFileSync: writeFileSync14, openSync, closeSync } = await import("fs");
|
|
31113
|
+
if (existsSync23(statePath)) {
|
|
30806
31114
|
jsonOut({
|
|
30807
31115
|
sessionId,
|
|
30808
31116
|
started: false,
|
|
@@ -30856,7 +31164,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
30856
31164
|
}
|
|
30857
31165
|
child.unref();
|
|
30858
31166
|
if (child.pid) {
|
|
30859
|
-
|
|
31167
|
+
writeFileSync14(
|
|
30860
31168
|
statePath,
|
|
30861
31169
|
JSON.stringify({
|
|
30862
31170
|
pid: child.pid,
|
|
@@ -30893,8 +31201,8 @@ bridgeCommand.command("stop-recording").description(
|
|
|
30893
31201
|
}
|
|
30894
31202
|
const sessionId = opts.session;
|
|
30895
31203
|
const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
30896
|
-
const { existsSync:
|
|
30897
|
-
if (!
|
|
31204
|
+
const { existsSync: existsSync23, readFileSync: readFileSync20, rmSync: rmSync4 } = await import("fs");
|
|
31205
|
+
if (!existsSync23(statePath)) {
|
|
30898
31206
|
jsonOut({
|
|
30899
31207
|
sessionId,
|
|
30900
31208
|
stopped: false,
|
|
@@ -30902,7 +31210,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
30902
31210
|
});
|
|
30903
31211
|
return;
|
|
30904
31212
|
}
|
|
30905
|
-
const state = JSON.parse(
|
|
31213
|
+
const state = JSON.parse(readFileSync20(statePath, "utf8"));
|
|
30906
31214
|
const { pid, outputPath } = state;
|
|
30907
31215
|
try {
|
|
30908
31216
|
process.kill(pid, "SIGINT");
|
|
@@ -30918,7 +31226,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
30918
31226
|
};
|
|
30919
31227
|
let attempts = 0;
|
|
30920
31228
|
while (isProcessRunning(pid) && attempts < 20) {
|
|
30921
|
-
await new Promise((
|
|
31229
|
+
await new Promise((resolve8) => setTimeout(resolve8, 250));
|
|
30922
31230
|
attempts++;
|
|
30923
31231
|
}
|
|
30924
31232
|
if (isProcessRunning(pid)) {
|
|
@@ -30931,7 +31239,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
30931
31239
|
rmSync4(statePath, { force: true });
|
|
30932
31240
|
} catch {
|
|
30933
31241
|
}
|
|
30934
|
-
if (!
|
|
31242
|
+
if (!existsSync23(outputPath)) {
|
|
30935
31243
|
jsonOut({
|
|
30936
31244
|
sessionId,
|
|
30937
31245
|
stopped: false,
|
|
@@ -33555,16 +33863,17 @@ async function publishUiIntent(app, input = {}, opts = {}) {
|
|
|
33555
33863
|
}
|
|
33556
33864
|
|
|
33557
33865
|
// src/cli/commands/mcp.ts
|
|
33558
|
-
var __dirname =
|
|
33559
|
-
var pkgPath =
|
|
33560
|
-
if (!
|
|
33561
|
-
pkgPath =
|
|
33866
|
+
var __dirname = dirname12(fileURLToPath6(import.meta.url));
|
|
33867
|
+
var pkgPath = join25(__dirname, "..", "..", "package.json");
|
|
33868
|
+
if (!existsSync22(pkgPath)) {
|
|
33869
|
+
pkgPath = join25(__dirname, "..", "..", "..", "package.json");
|
|
33562
33870
|
}
|
|
33563
|
-
var pkg = JSON.parse(
|
|
33871
|
+
var pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
|
|
33564
33872
|
var STUDIO_RESOURCE_URI = "ui://zam/studio";
|
|
33565
33873
|
var RECALL_RESOURCE_URI = "ui://zam/recall";
|
|
33566
33874
|
var GRAPH_RESOURCE_URI = "ui://zam/graph";
|
|
33567
33875
|
var SETTINGS_RESOURCE_URI = "ui://zam/settings";
|
|
33876
|
+
var OKF_RESOURCE_URI = "ui://zam/okf";
|
|
33568
33877
|
var STUDIO_BRIDGE_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
|
|
33569
33878
|
"list-tokens",
|
|
33570
33879
|
"personal-card-list",
|
|
@@ -33587,13 +33896,13 @@ var STUDIO_BRIDGE_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
33587
33896
|
function loadPanelHtml(fileName, placeholderTitle) {
|
|
33588
33897
|
const candidates = [
|
|
33589
33898
|
// dist/cli/commands/mcp.js → dist/ui/
|
|
33590
|
-
|
|
33899
|
+
join25(__dirname, "..", "..", "ui", fileName),
|
|
33591
33900
|
// src/cli/commands/mcp.ts via tsx → <repo>/dist/ui/
|
|
33592
|
-
|
|
33901
|
+
join25(__dirname, "..", "..", "..", "dist", "ui", fileName)
|
|
33593
33902
|
];
|
|
33594
33903
|
for (const candidate of candidates) {
|
|
33595
|
-
if (
|
|
33596
|
-
return
|
|
33904
|
+
if (existsSync22(candidate)) {
|
|
33905
|
+
return readFileSync19(candidate, "utf-8");
|
|
33597
33906
|
}
|
|
33598
33907
|
}
|
|
33599
33908
|
return `<!doctype html>
|
|
@@ -34358,6 +34667,214 @@ function createMcpServer(db) {
|
|
|
34358
34667
|
}
|
|
34359
34668
|
)
|
|
34360
34669
|
);
|
|
34670
|
+
const okfBundleDirSchema = z.string().optional().describe("Bundle directory (default docs/okf under the server cwd)");
|
|
34671
|
+
server.registerTool(
|
|
34672
|
+
"zam_okf_catalog",
|
|
34673
|
+
{
|
|
34674
|
+
description: "List the OKF knowledge-base articles (type, title, description, tags, resource URL) plus conformance problems, if any",
|
|
34675
|
+
inputSchema: {
|
|
34676
|
+
bundle_dir: okfBundleDirSchema,
|
|
34677
|
+
include_log: z.boolean().optional().describe(
|
|
34678
|
+
"Also return the raw log.md text (empty string if missing)"
|
|
34679
|
+
)
|
|
34680
|
+
},
|
|
34681
|
+
annotations: {
|
|
34682
|
+
...commonAnnotations,
|
|
34683
|
+
readOnlyHint: true
|
|
34684
|
+
}
|
|
34685
|
+
},
|
|
34686
|
+
wrapHandler(
|
|
34687
|
+
async (params) => {
|
|
34688
|
+
const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
34689
|
+
const bundle = loadBundle2(params.bundle_dir ?? DEFAULT_BUNDLE_DIR2);
|
|
34690
|
+
let log;
|
|
34691
|
+
if (params.include_log) {
|
|
34692
|
+
const { readFileSync: readFileSync20 } = await import("fs");
|
|
34693
|
+
try {
|
|
34694
|
+
log = readFileSync20(join25(bundle.dir, "log.md"), "utf8");
|
|
34695
|
+
} catch {
|
|
34696
|
+
log = "";
|
|
34697
|
+
}
|
|
34698
|
+
}
|
|
34699
|
+
return {
|
|
34700
|
+
dir: bundle.dir,
|
|
34701
|
+
articles: bundle.catalog,
|
|
34702
|
+
problems: bundle.problems,
|
|
34703
|
+
...params.include_log ? { log } : {}
|
|
34704
|
+
};
|
|
34705
|
+
}
|
|
34706
|
+
)
|
|
34707
|
+
);
|
|
34708
|
+
server.registerTool(
|
|
34709
|
+
"zam_okf_read",
|
|
34710
|
+
{
|
|
34711
|
+
description: "Read one OKF knowledge-base article: raw markdown plus parsed frontmatter",
|
|
34712
|
+
inputSchema: {
|
|
34713
|
+
bundle_dir: okfBundleDirSchema,
|
|
34714
|
+
file: z.string().describe("Article file name, e.g. fsrs-scheduling.md")
|
|
34715
|
+
},
|
|
34716
|
+
annotations: {
|
|
34717
|
+
...commonAnnotations,
|
|
34718
|
+
readOnlyHint: true
|
|
34719
|
+
}
|
|
34720
|
+
},
|
|
34721
|
+
wrapHandler(async (params) => {
|
|
34722
|
+
const { readFileSync: readFileSync20 } = await import("fs");
|
|
34723
|
+
const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, resolveArticlePath: resolveArticlePath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
34724
|
+
const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
|
|
34725
|
+
const path = resolveArticlePath2(
|
|
34726
|
+
params.bundle_dir ?? DEFAULT_BUNDLE_DIR2,
|
|
34727
|
+
params.file
|
|
34728
|
+
);
|
|
34729
|
+
const markdown = readFileSync20(path, "utf8");
|
|
34730
|
+
const { fields } = parseFrontmatter2(markdown);
|
|
34731
|
+
return { file: params.file, frontmatter: fields, markdown };
|
|
34732
|
+
})
|
|
34733
|
+
);
|
|
34734
|
+
server.registerTool(
|
|
34735
|
+
"zam_okf_upsert",
|
|
34736
|
+
{
|
|
34737
|
+
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.",
|
|
34738
|
+
inputSchema: {
|
|
34739
|
+
bundle_dir: okfBundleDirSchema,
|
|
34740
|
+
file: z.string().describe(
|
|
34741
|
+
"Kebab-case article file name ending in .md (permanent ID)"
|
|
34742
|
+
),
|
|
34743
|
+
markdown: z.string().describe(
|
|
34744
|
+
"Full article: --- frontmatter (type, title, description, tags, resource, timestamp) then the body"
|
|
34745
|
+
)
|
|
34746
|
+
},
|
|
34747
|
+
annotations: {
|
|
34748
|
+
...commonAnnotations
|
|
34749
|
+
}
|
|
34750
|
+
},
|
|
34751
|
+
wrapHandler(
|
|
34752
|
+
async (params) => {
|
|
34753
|
+
const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, upsertArticle: upsertArticle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
34754
|
+
const result = upsertArticle2(
|
|
34755
|
+
params.bundle_dir ?? DEFAULT_BUNDLE_DIR2,
|
|
34756
|
+
params.file,
|
|
34757
|
+
params.markdown
|
|
34758
|
+
);
|
|
34759
|
+
if (!result.validation.ok) {
|
|
34760
|
+
return { ok: false, problems: result.validation.problems };
|
|
34761
|
+
}
|
|
34762
|
+
return { ok: true, created: result.created, entry: result.entry };
|
|
34763
|
+
}
|
|
34764
|
+
)
|
|
34765
|
+
);
|
|
34766
|
+
server.registerTool(
|
|
34767
|
+
"zam_okf_read_citation",
|
|
34768
|
+
{
|
|
34769
|
+
description: "Read a citation target referenced by an OKF article (e.g. an ADR): read-only, restricted to .md files that resolve inside the repository root \u2014 the target may be outside the bundle but never outside the repo",
|
|
34770
|
+
inputSchema: {
|
|
34771
|
+
bundle_dir: okfBundleDirSchema,
|
|
34772
|
+
target: z.string().describe(
|
|
34773
|
+
"Path to the citation target relative to bundle_dir, e.g. ../adr/2026-07-17-x.md"
|
|
34774
|
+
)
|
|
34775
|
+
},
|
|
34776
|
+
annotations: {
|
|
34777
|
+
...commonAnnotations,
|
|
34778
|
+
readOnlyHint: true
|
|
34779
|
+
}
|
|
34780
|
+
},
|
|
34781
|
+
wrapHandler(async (params) => {
|
|
34782
|
+
const { readFileSync: readFileSync20 } = await import("fs");
|
|
34783
|
+
const { relative: relative2, sep: sep2 } = await import("path");
|
|
34784
|
+
const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, findRepoRoot: findRepoRoot2, resolveCitationPath: resolveCitationPath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
34785
|
+
const bundleDir = params.bundle_dir ?? DEFAULT_BUNDLE_DIR2;
|
|
34786
|
+
const path = resolveCitationPath2(bundleDir, params.target);
|
|
34787
|
+
const content = readFileSync20(path, "utf8");
|
|
34788
|
+
const root = findRepoRoot2(bundleDir);
|
|
34789
|
+
const repoRelativePath = relative2(root, path).split(sep2).join("/");
|
|
34790
|
+
return { target: params.target, path: repoRelativePath, content };
|
|
34791
|
+
})
|
|
34792
|
+
);
|
|
34793
|
+
registerAppTool(
|
|
34794
|
+
server,
|
|
34795
|
+
"zam_okf_visualize",
|
|
34796
|
+
{
|
|
34797
|
+
title: "Open ZAM OKF visualizer",
|
|
34798
|
+
description: "Open the ZAM OKF knowledge-base visualizer: articles by type with search, a markdown reader with inline-expandable cited ADRs, a link graph, and the log. Pass `bundle_dir` to browse a bundle other than the default (docs/okf under the server cwd).",
|
|
34799
|
+
inputSchema: {
|
|
34800
|
+
bundle_dir: okfBundleDirSchema
|
|
34801
|
+
},
|
|
34802
|
+
annotations: {
|
|
34803
|
+
...commonAnnotations,
|
|
34804
|
+
readOnlyHint: true
|
|
34805
|
+
},
|
|
34806
|
+
_meta: {
|
|
34807
|
+
ui: { resourceUri: OKF_RESOURCE_URI }
|
|
34808
|
+
}
|
|
34809
|
+
},
|
|
34810
|
+
wrapHandler(async ({ bundle_dir }) => {
|
|
34811
|
+
const opening = await resolveOpeningCompanionContextSafely(
|
|
34812
|
+
db,
|
|
34813
|
+
"okf",
|
|
34814
|
+
void 0,
|
|
34815
|
+
getNativeClientInfo(),
|
|
34816
|
+
{ clientSamplingCapable: getClientSamplingCapable() }
|
|
34817
|
+
);
|
|
34818
|
+
await publishUiIntent("okf", { bundle_dir });
|
|
34819
|
+
const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
34820
|
+
const { resolve: resolve8 } = await import("path");
|
|
34821
|
+
const requestedDir = bundle_dir ?? DEFAULT_BUNDLE_DIR2;
|
|
34822
|
+
let resolvedBundleDir = resolve8(requestedDir);
|
|
34823
|
+
let catalog = [];
|
|
34824
|
+
let problems = [];
|
|
34825
|
+
let log = "";
|
|
34826
|
+
let okfVersion = null;
|
|
34827
|
+
try {
|
|
34828
|
+
const bundle = loadBundle2(requestedDir);
|
|
34829
|
+
resolvedBundleDir = bundle.dir;
|
|
34830
|
+
catalog = bundle.catalog;
|
|
34831
|
+
problems = bundle.problems;
|
|
34832
|
+
const { readFileSync: readFileSync20 } = await import("fs");
|
|
34833
|
+
try {
|
|
34834
|
+
log = readFileSync20(join25(bundle.dir, "log.md"), "utf8");
|
|
34835
|
+
} catch {
|
|
34836
|
+
log = "";
|
|
34837
|
+
}
|
|
34838
|
+
try {
|
|
34839
|
+
const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
|
|
34840
|
+
const indexRaw = readFileSync20(join25(bundle.dir, "index.md"), "utf8");
|
|
34841
|
+
const { fields } = parseFrontmatter2(indexRaw);
|
|
34842
|
+
okfVersion = typeof fields.okf_version === "string" ? fields.okf_version : null;
|
|
34843
|
+
} catch {
|
|
34844
|
+
okfVersion = null;
|
|
34845
|
+
}
|
|
34846
|
+
} catch (error) {
|
|
34847
|
+
problems = [error instanceof Error ? error.message : String(error)];
|
|
34848
|
+
}
|
|
34849
|
+
return {
|
|
34850
|
+
okf: "zam",
|
|
34851
|
+
version: pkg.version,
|
|
34852
|
+
user: opening.context.user.currentId ?? null,
|
|
34853
|
+
bundleDir: resolvedBundleDir,
|
|
34854
|
+
okfVersion,
|
|
34855
|
+
catalog,
|
|
34856
|
+
problems,
|
|
34857
|
+
log,
|
|
34858
|
+
companionContext: opening.context,
|
|
34859
|
+
...opening.degraded ? { companionContextDegraded: true } : {}
|
|
34860
|
+
};
|
|
34861
|
+
})
|
|
34862
|
+
);
|
|
34863
|
+
registerAppResource(
|
|
34864
|
+
server,
|
|
34865
|
+
"zam-okf",
|
|
34866
|
+
OKF_RESOURCE_URI,
|
|
34867
|
+
{ mimeType: RESOURCE_MIME_TYPE },
|
|
34868
|
+
async () => ({
|
|
34869
|
+
contents: [
|
|
34870
|
+
{
|
|
34871
|
+
uri: OKF_RESOURCE_URI,
|
|
34872
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
34873
|
+
text: loadPanelHtml("okf-panel.html", "ZAM OKF")
|
|
34874
|
+
}
|
|
34875
|
+
]
|
|
34876
|
+
})
|
|
34877
|
+
);
|
|
34361
34878
|
return server;
|
|
34362
34879
|
}
|
|
34363
34880
|
async function runMcpServer() {
|