zam-core 0.12.0 → 0.14.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 +804 -217
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/commands/mcp.js +738 -223
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/copilot-extension/extension.mjs +34 -0
- 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 +32 -1
- package/dist/index.js +70 -4
- package/dist/index.js.map +1 -1
- package/dist/ui/okf-panel.html +887 -0
- package/dist/vscode-extension/ZAM_Companion_0.14.0.vsix +0 -0
- package/dist/vscode-extension/extension.cjs +15 -15
- package/dist/vscode-extension/manifest.json +2 -2
- package/package.json +2 -2
- package/dist/vscode-extension/ZAM_Companion_0.12.0.vsix +0 -0
package/dist/cli/commands/mcp.js
CHANGED
|
@@ -590,7 +590,13 @@ CREATE TABLE IF NOT EXISTS tokens (
|
|
|
590
590
|
-- validated in code, not via CHECK. Column default is 'llm' so unlabeled
|
|
591
591
|
-- writes (pre-M013 rows, old snapshot restores) count as LLM-era content;
|
|
592
592
|
-- createToken() defaults to 'manual' for API callers instead.
|
|
593
|
-
question_source TEXT NOT NULL DEFAULT 'llm'
|
|
593
|
+
question_source TEXT NOT NULL DEFAULT 'llm',
|
|
594
|
+
-- Maintenance state (ADR 2026-07-18): when set, the token's binding to
|
|
595
|
+
-- its source is unclear (e.g. stale source_link after an article split,
|
|
596
|
+
-- or an ambiguous re-import). Cards of a token in maintenance are
|
|
597
|
+
-- excluded from scheduling until repaired; learning state is preserved.
|
|
598
|
+
maintenance_at TEXT,
|
|
599
|
+
maintenance_reason TEXT
|
|
594
600
|
);
|
|
595
601
|
|
|
596
602
|
-- Prerequisite dependency graph: "to learn A, first know B"
|
|
@@ -1140,6 +1146,10 @@ async function runMigrations(db) {
|
|
|
1140
1146
|
`ALTER TABLE tokens ADD COLUMN question_source TEXT NOT NULL DEFAULT 'llm'`
|
|
1141
1147
|
);
|
|
1142
1148
|
}
|
|
1149
|
+
if (tokenCols.length > 0 && !tokenCols.some((c) => c.name === "maintenance_at")) {
|
|
1150
|
+
await db.exec(`ALTER TABLE tokens ADD COLUMN maintenance_at TEXT`);
|
|
1151
|
+
await db.exec(`ALTER TABLE tokens ADD COLUMN maintenance_reason TEXT`);
|
|
1152
|
+
}
|
|
1143
1153
|
}
|
|
1144
1154
|
var DEFAULT_DB_DIR, DEFAULT_DB_PATH, nodeRequire, TRANSIENT_REMOTE_ERROR_PATTERNS;
|
|
1145
1155
|
var init_connection = __esm({
|
|
@@ -1630,6 +1640,23 @@ async function updateCard(db, cardId, updates) {
|
|
|
1630
1640
|
}
|
|
1631
1641
|
return await db.prepare("SELECT * FROM cards WHERE id = ?").get(cardId);
|
|
1632
1642
|
}
|
|
1643
|
+
async function resetCardsForToken(db, tokenId, now) {
|
|
1644
|
+
const ts = now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1645
|
+
const result = await db.prepare(
|
|
1646
|
+
`UPDATE cards SET
|
|
1647
|
+
stability = 0.0,
|
|
1648
|
+
difficulty = 0.5,
|
|
1649
|
+
elapsed_days = 0.0,
|
|
1650
|
+
scheduled_days = 0.0,
|
|
1651
|
+
reps = 0,
|
|
1652
|
+
lapses = 0,
|
|
1653
|
+
state = 'new',
|
|
1654
|
+
due_at = ?,
|
|
1655
|
+
last_review_at = NULL
|
|
1656
|
+
WHERE token_id = ?`
|
|
1657
|
+
).run(ts, tokenId);
|
|
1658
|
+
return result.changes;
|
|
1659
|
+
}
|
|
1633
1660
|
async function getCardDeletionImpact(db, tokenId, userId) {
|
|
1634
1661
|
const card = await getCard(db, tokenId, userId);
|
|
1635
1662
|
if (!card) {
|
|
@@ -1652,7 +1679,8 @@ async function getDueCards(db, userId, now, domain, knowledgeContext) {
|
|
|
1652
1679
|
let sql = `SELECT c.*, t.slug, t.concept, t.domain, t.bloom_level
|
|
1653
1680
|
FROM cards c
|
|
1654
1681
|
JOIN tokens t ON t.id = c.token_id
|
|
1655
|
-
WHERE c.user_id = ? AND c.blocked = 0 AND c.due_at <=
|
|
1682
|
+
WHERE c.user_id = ? AND c.blocked = 0 AND c.due_at <= ?
|
|
1683
|
+
AND t.maintenance_at IS NULL`;
|
|
1656
1684
|
const params = [userId, cutoff];
|
|
1657
1685
|
if (domain) {
|
|
1658
1686
|
sql += " AND t.domain = ?";
|
|
@@ -1953,6 +1981,38 @@ async function deprecateToken(db, slug) {
|
|
|
1953
1981
|
).run(now, now, slug);
|
|
1954
1982
|
return await getTokenBySlug(db, slug);
|
|
1955
1983
|
}
|
|
1984
|
+
async function getTokensBySourceLinkBase(db, base) {
|
|
1985
|
+
return await db.prepare(
|
|
1986
|
+
`SELECT * FROM tokens
|
|
1987
|
+
WHERE (source_link = ? OR source_link LIKE ? || '#%' ESCAPE '\\')
|
|
1988
|
+
AND deprecated_at IS NULL`
|
|
1989
|
+
).all(base, escapeLike(base));
|
|
1990
|
+
}
|
|
1991
|
+
function escapeLike(value) {
|
|
1992
|
+
return value.replace(/[\\%_]/g, (ch) => `\\${ch}`);
|
|
1993
|
+
}
|
|
1994
|
+
async function setTokenMaintenance(db, slug, reason) {
|
|
1995
|
+
const token = await getTokenBySlug(db, slug);
|
|
1996
|
+
if (!token) {
|
|
1997
|
+
throw new Error(`Token not found: ${slug}`);
|
|
1998
|
+
}
|
|
1999
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2000
|
+
await db.prepare(
|
|
2001
|
+
"UPDATE tokens SET maintenance_at = ?, maintenance_reason = ?, updated_at = ? WHERE slug = ?"
|
|
2002
|
+
).run(now, reason, now, slug);
|
|
2003
|
+
return await getTokenBySlug(db, slug);
|
|
2004
|
+
}
|
|
2005
|
+
async function clearTokenMaintenance(db, slug) {
|
|
2006
|
+
const token = await getTokenBySlug(db, slug);
|
|
2007
|
+
if (!token) {
|
|
2008
|
+
throw new Error(`Token not found: ${slug}`);
|
|
2009
|
+
}
|
|
2010
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2011
|
+
await db.prepare(
|
|
2012
|
+
"UPDATE tokens SET maintenance_at = NULL, maintenance_reason = NULL, updated_at = ? WHERE slug = ?"
|
|
2013
|
+
).run(now, slug);
|
|
2014
|
+
return await getTokenBySlug(db, slug);
|
|
2015
|
+
}
|
|
1956
2016
|
async function getTokenDeleteImpact(db, slug) {
|
|
1957
2017
|
const token = await getTokenBySlug(db, slug);
|
|
1958
2018
|
if (!token) {
|
|
@@ -5268,7 +5328,8 @@ async function buildReviewQueue(db, options) {
|
|
|
5268
5328
|
AND c.blocked = 0
|
|
5269
5329
|
AND c.due_at <= ?
|
|
5270
5330
|
AND c.state IN ('review', 'relearning', 'learning')
|
|
5271
|
-
AND t.deprecated_at IS NULL
|
|
5331
|
+
AND t.deprecated_at IS NULL
|
|
5332
|
+
AND t.maintenance_at IS NULL`;
|
|
5272
5333
|
const dueParams = [options.userId, nowISO];
|
|
5273
5334
|
if (options.knowledgeContext) {
|
|
5274
5335
|
dueSql += ` AND EXISTS (
|
|
@@ -5298,7 +5359,8 @@ async function buildReviewQueue(db, options) {
|
|
|
5298
5359
|
WHERE c.user_id = ?
|
|
5299
5360
|
AND c.blocked = 0
|
|
5300
5361
|
AND c.state = 'new'
|
|
5301
|
-
AND t.deprecated_at IS NULL
|
|
5362
|
+
AND t.deprecated_at IS NULL
|
|
5363
|
+
AND t.maintenance_at IS NULL`;
|
|
5302
5364
|
const newParams = [options.userId];
|
|
5303
5365
|
if (options.knowledgeContext) {
|
|
5304
5366
|
newSql += ` AND EXISTS (
|
|
@@ -6992,6 +7054,7 @@ __export(kernel_exports, {
|
|
|
6992
7054
|
clearADOCredentials: () => clearADOCredentials,
|
|
6993
7055
|
clearProviderApiKey: () => clearProviderApiKey,
|
|
6994
7056
|
clearReviewContextCache: () => clearReviewContextCache,
|
|
7057
|
+
clearTokenMaintenance: () => clearTokenMaintenance,
|
|
6995
7058
|
clearTursoCredentials: () => clearTursoCredentials,
|
|
6996
7059
|
compareVersions: () => compareVersions,
|
|
6997
7060
|
computeContentHash: () => computeContentHash,
|
|
@@ -7100,6 +7163,7 @@ __export(kernel_exports, {
|
|
|
7100
7163
|
getTokenDeleteImpact: () => getTokenDeleteImpact,
|
|
7101
7164
|
getTokenEmbedding: () => getTokenEmbedding,
|
|
7102
7165
|
getTokenNeighborhood: () => getTokenNeighborhood,
|
|
7166
|
+
getTokensBySourceLinkBase: () => getTokensBySourceLinkBase,
|
|
7103
7167
|
getTursoCredentials: () => getTursoCredentials,
|
|
7104
7168
|
getUiObservationPath: () => getUiObservationPath,
|
|
7105
7169
|
getUiObserverDir: () => getUiObserverDir,
|
|
@@ -7154,6 +7218,7 @@ __export(kernel_exports, {
|
|
|
7154
7218
|
readMonitorLog: () => readMonitorLog,
|
|
7155
7219
|
readUiObservationLog: () => readUiObservationLog,
|
|
7156
7220
|
removeConfiguredWorkspace: () => removeConfiguredWorkspace,
|
|
7221
|
+
resetCardsForToken: () => resetCardsForToken,
|
|
7157
7222
|
resolveAllBeliefPaths: () => resolveAllBeliefPaths,
|
|
7158
7223
|
resolveAllGoalPaths: () => resolveAllGoalPaths,
|
|
7159
7224
|
resolveObserverPolicy: () => resolveObserverPolicy,
|
|
@@ -7185,6 +7250,7 @@ __export(kernel_exports, {
|
|
|
7185
7250
|
setLastRepairedVersion: () => setLastRepairedVersion,
|
|
7186
7251
|
setProviderApiKey: () => setProviderApiKey,
|
|
7187
7252
|
setSetting: () => setSetting,
|
|
7253
|
+
setTokenMaintenance: () => setTokenMaintenance,
|
|
7188
7254
|
setTursoCredentials: () => setTursoCredentials,
|
|
7189
7255
|
slugify: () => slugify,
|
|
7190
7256
|
startSession: () => startSession,
|
|
@@ -9165,12 +9231,34 @@ var init_bundle = __esm({
|
|
|
9165
9231
|
var io_exports = {};
|
|
9166
9232
|
__export(io_exports, {
|
|
9167
9233
|
DEFAULT_BUNDLE_DIR: () => DEFAULT_BUNDLE_DIR,
|
|
9234
|
+
findRepoRoot: () => findRepoRoot,
|
|
9168
9235
|
loadBundle: () => loadBundle,
|
|
9169
9236
|
resolveArticlePath: () => resolveArticlePath,
|
|
9237
|
+
resolveBundleDirFromRoots: () => resolveBundleDirFromRoots,
|
|
9238
|
+
resolveCitationPath: () => resolveCitationPath,
|
|
9170
9239
|
upsertArticle: () => upsertArticle
|
|
9171
9240
|
});
|
|
9172
|
-
import {
|
|
9173
|
-
|
|
9241
|
+
import {
|
|
9242
|
+
existsSync as existsSync13,
|
|
9243
|
+
mkdirSync as mkdirSync9,
|
|
9244
|
+
readdirSync as readdirSync2,
|
|
9245
|
+
readFileSync as readFileSync11,
|
|
9246
|
+
realpathSync,
|
|
9247
|
+
writeFileSync as writeFileSync6
|
|
9248
|
+
} from "fs";
|
|
9249
|
+
import { dirname as dirname6, isAbsolute, join as join13, relative, resolve as resolve4, sep } from "path";
|
|
9250
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
9251
|
+
function resolveBundleDirFromRoots(rootUris, fallback) {
|
|
9252
|
+
const dirs = [];
|
|
9253
|
+
for (const uri of rootUris) {
|
|
9254
|
+
if (!uri?.startsWith("file:")) continue;
|
|
9255
|
+
try {
|
|
9256
|
+
dirs.push(join13(fileURLToPath3(uri), DEFAULT_BUNDLE_DIR));
|
|
9257
|
+
} catch {
|
|
9258
|
+
}
|
|
9259
|
+
}
|
|
9260
|
+
return dirs.find((dir) => existsSync13(dir)) ?? dirs[0] ?? fallback;
|
|
9261
|
+
}
|
|
9174
9262
|
function resolveArticlePath(dir, file) {
|
|
9175
9263
|
if (file.includes("/") || file.includes("\\") || file.includes("..")) {
|
|
9176
9264
|
throw new Error(`invalid article file name: ${file}`);
|
|
@@ -9178,13 +9266,49 @@ function resolveArticlePath(dir, file) {
|
|
|
9178
9266
|
if (isReservedFile(file)) {
|
|
9179
9267
|
throw new Error(`refusing to address reserved file: ${file}`);
|
|
9180
9268
|
}
|
|
9181
|
-
return
|
|
9269
|
+
return join13(resolve4(dir), file);
|
|
9270
|
+
}
|
|
9271
|
+
function findRepoRoot(startDir) {
|
|
9272
|
+
let dir = resolve4(startDir);
|
|
9273
|
+
for (; ; ) {
|
|
9274
|
+
if (existsSync13(join13(dir, ".git"))) return dir;
|
|
9275
|
+
const parent = dirname6(dir);
|
|
9276
|
+
if (parent === dir) return resolve4(startDir, "..");
|
|
9277
|
+
dir = parent;
|
|
9278
|
+
}
|
|
9279
|
+
}
|
|
9280
|
+
function resolveCitationPath(bundleDir, target) {
|
|
9281
|
+
if (isAbsolute(target)) {
|
|
9282
|
+
throw new Error(
|
|
9283
|
+
`invalid citation target: absolute paths are not allowed (${target})`
|
|
9284
|
+
);
|
|
9285
|
+
}
|
|
9286
|
+
if (!target.endsWith(".md")) {
|
|
9287
|
+
throw new Error(
|
|
9288
|
+
`invalid citation target: only .md files are readable (${target})`
|
|
9289
|
+
);
|
|
9290
|
+
}
|
|
9291
|
+
const root = findRepoRoot(bundleDir);
|
|
9292
|
+
const resolved = resolve4(bundleDir, target);
|
|
9293
|
+
assertContained(root, resolved, target);
|
|
9294
|
+
if (existsSync13(resolved)) {
|
|
9295
|
+
assertContained(realpathSync(root), realpathSync(resolved), target);
|
|
9296
|
+
}
|
|
9297
|
+
return resolved;
|
|
9298
|
+
}
|
|
9299
|
+
function assertContained(root, candidate, target) {
|
|
9300
|
+
const rel = relative(root, candidate);
|
|
9301
|
+
if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
9302
|
+
throw new Error(
|
|
9303
|
+
`invalid citation target: resolves outside the repository root (${target})`
|
|
9304
|
+
);
|
|
9305
|
+
}
|
|
9182
9306
|
}
|
|
9183
9307
|
function loadBundle(dir) {
|
|
9184
|
-
const root =
|
|
9308
|
+
const root = resolve4(dir);
|
|
9185
9309
|
let entries;
|
|
9186
9310
|
try {
|
|
9187
|
-
entries =
|
|
9311
|
+
entries = readdirSync2(root).filter(
|
|
9188
9312
|
(name) => name.endsWith(".md") && !isReservedFile(name)
|
|
9189
9313
|
);
|
|
9190
9314
|
} catch {
|
|
@@ -9192,7 +9316,7 @@ function loadBundle(dir) {
|
|
|
9192
9316
|
}
|
|
9193
9317
|
const articles = entries.sort().map((file) => ({
|
|
9194
9318
|
file,
|
|
9195
|
-
markdown:
|
|
9319
|
+
markdown: readFileSync11(join13(root, file), "utf8")
|
|
9196
9320
|
}));
|
|
9197
9321
|
const problems = articles.flatMap(
|
|
9198
9322
|
({ file, markdown }) => validateArticle(file, markdown).problems
|
|
@@ -9214,25 +9338,25 @@ function upsertArticle(dir, file, markdown, today = (/* @__PURE__ */ new Date())
|
|
|
9214
9338
|
const target = resolveArticlePath(dir, file);
|
|
9215
9339
|
const validation = validateArticle(file, markdown);
|
|
9216
9340
|
if (!validation.ok) return { validation };
|
|
9217
|
-
const root =
|
|
9218
|
-
|
|
9341
|
+
const root = resolve4(dir);
|
|
9342
|
+
mkdirSync9(root, { recursive: true });
|
|
9219
9343
|
let created = true;
|
|
9220
9344
|
try {
|
|
9221
|
-
|
|
9345
|
+
readFileSync11(target, "utf8");
|
|
9222
9346
|
created = false;
|
|
9223
9347
|
} catch {
|
|
9224
9348
|
}
|
|
9225
|
-
|
|
9349
|
+
writeFileSync6(target, markdown, "utf8");
|
|
9226
9350
|
const bundle = loadBundle(root);
|
|
9227
|
-
|
|
9351
|
+
writeFileSync6(join13(root, "index.md"), renderIndex(bundle.catalog), "utf8");
|
|
9228
9352
|
let log = "";
|
|
9229
9353
|
try {
|
|
9230
|
-
log =
|
|
9354
|
+
log = readFileSync11(join13(root, "log.md"), "utf8");
|
|
9231
9355
|
} catch {
|
|
9232
9356
|
}
|
|
9233
9357
|
const entry = bundle.catalog.find((e) => e.file === file);
|
|
9234
|
-
|
|
9235
|
-
|
|
9358
|
+
writeFileSync6(
|
|
9359
|
+
join13(root, "log.md"),
|
|
9236
9360
|
appendLog(
|
|
9237
9361
|
log,
|
|
9238
9362
|
today,
|
|
@@ -9253,9 +9377,9 @@ var init_io = __esm({
|
|
|
9253
9377
|
|
|
9254
9378
|
// src/cli/commands/mcp.ts
|
|
9255
9379
|
init_kernel();
|
|
9256
|
-
import { existsSync as
|
|
9257
|
-
import { dirname as
|
|
9258
|
-
import { fileURLToPath as
|
|
9380
|
+
import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
|
|
9381
|
+
import { dirname as dirname12, join as join25 } from "path";
|
|
9382
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
9259
9383
|
import {
|
|
9260
9384
|
RESOURCE_MIME_TYPE,
|
|
9261
9385
|
registerAppResource,
|
|
@@ -9347,7 +9471,8 @@ var COMPANION_SURFACES = [
|
|
|
9347
9471
|
"recall",
|
|
9348
9472
|
"graph",
|
|
9349
9473
|
"settings",
|
|
9350
|
-
"studio"
|
|
9474
|
+
"studio",
|
|
9475
|
+
"okf"
|
|
9351
9476
|
];
|
|
9352
9477
|
function isCompanionSurface(value) {
|
|
9353
9478
|
return typeof value === "string" && COMPANION_SURFACES.includes(value);
|
|
@@ -9467,8 +9592,8 @@ function buildCompanionContext(input) {
|
|
|
9467
9592
|
|
|
9468
9593
|
// src/cli/bridge-handlers.ts
|
|
9469
9594
|
init_kernel();
|
|
9470
|
-
import { mkdirSync as
|
|
9471
|
-
import { join as
|
|
9595
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
9596
|
+
import { join as join14 } from "path";
|
|
9472
9597
|
|
|
9473
9598
|
// src/cli/knowledge-contexts.ts
|
|
9474
9599
|
init_kernel();
|
|
@@ -10351,6 +10476,161 @@ async function addToken(db, params) {
|
|
|
10351
10476
|
possible_duplicates: possibleDuplicates
|
|
10352
10477
|
};
|
|
10353
10478
|
}
|
|
10479
|
+
async function importOkfTokens(db, params) {
|
|
10480
|
+
const userId = await resolveHandlerUser(db, params.user);
|
|
10481
|
+
if (!params.file?.trim()) throw new Error("file must be non-empty");
|
|
10482
|
+
if (!Array.isArray(params.tokens) || params.tokens.length === 0) {
|
|
10483
|
+
throw new Error("tokens must be a non-empty array");
|
|
10484
|
+
}
|
|
10485
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10486
|
+
for (const input of params.tokens) {
|
|
10487
|
+
const slug = input.slug?.trim();
|
|
10488
|
+
if (!slug || !input.concept?.trim()) {
|
|
10489
|
+
throw new Error("every token needs a non-empty slug and concept");
|
|
10490
|
+
}
|
|
10491
|
+
if (seen.has(slug)) throw new Error(`duplicate token slug: ${slug}`);
|
|
10492
|
+
seen.add(slug);
|
|
10493
|
+
if (input.bloomLevel !== void 0 && (!Number.isInteger(input.bloomLevel) || input.bloomLevel < 1 || input.bloomLevel > 5)) {
|
|
10494
|
+
throw new Error(`bloomLevel must be 1-5 (token: ${slug})`);
|
|
10495
|
+
}
|
|
10496
|
+
}
|
|
10497
|
+
const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, resolveArticlePath: resolveArticlePath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
10498
|
+
const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
|
|
10499
|
+
const { readFileSync: readFileSync20 } = await import("fs");
|
|
10500
|
+
const bundleDir = params.bundleDir ?? DEFAULT_BUNDLE_DIR2;
|
|
10501
|
+
const articlePath = resolveArticlePath2(bundleDir, params.file);
|
|
10502
|
+
let markdown;
|
|
10503
|
+
try {
|
|
10504
|
+
markdown = readFileSync20(articlePath, "utf8");
|
|
10505
|
+
} catch {
|
|
10506
|
+
throw new Error(`Article not found: ${articlePath}`);
|
|
10507
|
+
}
|
|
10508
|
+
let resource;
|
|
10509
|
+
try {
|
|
10510
|
+
const { fields } = parseFrontmatter2(markdown);
|
|
10511
|
+
resource = typeof fields.resource === "string" ? fields.resource : void 0;
|
|
10512
|
+
} catch {
|
|
10513
|
+
resource = void 0;
|
|
10514
|
+
}
|
|
10515
|
+
const sourceBase = resource ?? articlePath;
|
|
10516
|
+
const sourceLinkFor = (anchor) => anchor?.trim() ? `${sourceBase}#${anchor.trim()}` : sourceBase;
|
|
10517
|
+
const created = [];
|
|
10518
|
+
const updated = [];
|
|
10519
|
+
const replaced = [];
|
|
10520
|
+
const maintenance = [];
|
|
10521
|
+
let cardsEnsured = 0;
|
|
10522
|
+
await db.transaction(async (tx) => {
|
|
10523
|
+
const inImport = /* @__PURE__ */ new Map();
|
|
10524
|
+
for (const input of params.tokens) {
|
|
10525
|
+
const mode = input.mode ?? "new";
|
|
10526
|
+
const existing = await getTokenBySlug(tx, input.slug);
|
|
10527
|
+
if (mode === "new") {
|
|
10528
|
+
if (existing) {
|
|
10529
|
+
throw new Error(
|
|
10530
|
+
`Token '${input.slug}' already exists. Use mode "update" to refresh it (keeps learning state), "replace" if the concept changed (resets learning state), or choose a different slug.`
|
|
10531
|
+
);
|
|
10532
|
+
}
|
|
10533
|
+
const token2 = await createToken(tx, {
|
|
10534
|
+
slug: input.slug,
|
|
10535
|
+
title: input.title,
|
|
10536
|
+
concept: input.concept,
|
|
10537
|
+
domain: input.domain,
|
|
10538
|
+
bloom_level: input.bloomLevel ?? 1,
|
|
10539
|
+
source_link: sourceLinkFor(input.anchor),
|
|
10540
|
+
question: input.question ?? null,
|
|
10541
|
+
question_source: input.question ? "llm" : void 0
|
|
10542
|
+
});
|
|
10543
|
+
inImport.set(input.slug, token2);
|
|
10544
|
+
created.push(input.slug);
|
|
10545
|
+
} else {
|
|
10546
|
+
if (!existing) {
|
|
10547
|
+
throw new Error(
|
|
10548
|
+
`Token '${input.slug}' does not exist \u2014 mode "${mode}" requires an existing token.`
|
|
10549
|
+
);
|
|
10550
|
+
}
|
|
10551
|
+
const updates = {
|
|
10552
|
+
concept: input.concept,
|
|
10553
|
+
source_link: sourceLinkFor(input.anchor)
|
|
10554
|
+
};
|
|
10555
|
+
if (input.title !== void 0) updates.title = input.title;
|
|
10556
|
+
if (input.domain !== void 0) updates.domain = input.domain;
|
|
10557
|
+
if (input.bloomLevel !== void 0) {
|
|
10558
|
+
updates.bloom_level = input.bloomLevel;
|
|
10559
|
+
}
|
|
10560
|
+
if (input.question !== void 0) {
|
|
10561
|
+
updates.question = input.question;
|
|
10562
|
+
updates.question_source = input.question ? "llm" : void 0;
|
|
10563
|
+
}
|
|
10564
|
+
const token2 = await updateToken(tx, input.slug, updates);
|
|
10565
|
+
if (existing.maintenance_at) {
|
|
10566
|
+
await clearTokenMaintenance(tx, input.slug);
|
|
10567
|
+
}
|
|
10568
|
+
if (mode === "replace") {
|
|
10569
|
+
await resetCardsForToken(tx, token2.id);
|
|
10570
|
+
replaced.push(input.slug);
|
|
10571
|
+
} else {
|
|
10572
|
+
updated.push(input.slug);
|
|
10573
|
+
}
|
|
10574
|
+
inImport.set(input.slug, token2);
|
|
10575
|
+
}
|
|
10576
|
+
const ctxNames = parseKnowledgeContextNames(input.knowledgeContexts);
|
|
10577
|
+
const assigned = await resolveKnowledgeContexts(tx, ctxNames);
|
|
10578
|
+
const token = inImport.get(input.slug);
|
|
10579
|
+
if (token) {
|
|
10580
|
+
for (const context of assigned) {
|
|
10581
|
+
await assignTokenToContext(tx, token.id, context.id);
|
|
10582
|
+
}
|
|
10583
|
+
}
|
|
10584
|
+
}
|
|
10585
|
+
for (const input of params.tokens) {
|
|
10586
|
+
const token = inImport.get(input.slug);
|
|
10587
|
+
if (!token) continue;
|
|
10588
|
+
const prereqSlugs = [
|
|
10589
|
+
...new Set(
|
|
10590
|
+
(input.prerequisites ?? []).map((s) => s.trim()).filter(Boolean)
|
|
10591
|
+
)
|
|
10592
|
+
];
|
|
10593
|
+
for (const prereqSlug of prereqSlugs) {
|
|
10594
|
+
const target = inImport.get(prereqSlug) ?? await getTokenBySlug(tx, prereqSlug);
|
|
10595
|
+
if (!target) {
|
|
10596
|
+
throw new Error(
|
|
10597
|
+
`Prerequisite token not found: ${prereqSlug} (for '${input.slug}')`
|
|
10598
|
+
);
|
|
10599
|
+
}
|
|
10600
|
+
await addPrerequisite(tx, token.id, target.id);
|
|
10601
|
+
}
|
|
10602
|
+
}
|
|
10603
|
+
for (const token of inImport.values()) {
|
|
10604
|
+
await ensureCard(tx, token.id, userId);
|
|
10605
|
+
cardsEnsured++;
|
|
10606
|
+
}
|
|
10607
|
+
const prior = await getTokensBySourceLinkBase(tx, sourceBase);
|
|
10608
|
+
for (const token of prior) {
|
|
10609
|
+
if (!inImport.has(token.slug)) {
|
|
10610
|
+
await setTokenMaintenance(
|
|
10611
|
+
tx,
|
|
10612
|
+
token.slug,
|
|
10613
|
+
`absent from re-import of ${params.file}`
|
|
10614
|
+
);
|
|
10615
|
+
maintenance.push(token.slug);
|
|
10616
|
+
}
|
|
10617
|
+
}
|
|
10618
|
+
});
|
|
10619
|
+
try {
|
|
10620
|
+
await ensureTokenEmbeddings(db, { limit: 16 });
|
|
10621
|
+
} catch {
|
|
10622
|
+
}
|
|
10623
|
+
return {
|
|
10624
|
+
success: true,
|
|
10625
|
+
user: userId,
|
|
10626
|
+
article: { file: params.file, source_link: sourceBase },
|
|
10627
|
+
created,
|
|
10628
|
+
updated,
|
|
10629
|
+
replaced,
|
|
10630
|
+
maintenance,
|
|
10631
|
+
cards: cardsEnsured
|
|
10632
|
+
};
|
|
10633
|
+
}
|
|
10354
10634
|
async function findTokens2(db, params) {
|
|
10355
10635
|
const userId = await resolveHandlerUser(db, params.user);
|
|
10356
10636
|
if (!params.context.trim()) {
|
|
@@ -10675,11 +10955,11 @@ async function backupCreate(db, params) {
|
|
|
10675
10955
|
const targetDir = params.dir || (await ensureActiveWorkspace(db)).path;
|
|
10676
10956
|
const snapshot = await exportSnapshot(db);
|
|
10677
10957
|
const manifest = verifySnapshot(snapshot);
|
|
10678
|
-
const backupDir =
|
|
10679
|
-
|
|
10958
|
+
const backupDir = join14(targetDir, "zam-backups");
|
|
10959
|
+
mkdirSync10(backupDir, { recursive: true });
|
|
10680
10960
|
const stamp = manifest.createdAt.replace(/[:.]/g, "-");
|
|
10681
|
-
const path =
|
|
10682
|
-
|
|
10961
|
+
const path = join14(backupDir, `zam-snapshot-${stamp}.sql`);
|
|
10962
|
+
writeFileSync7(path, snapshot, "utf-8");
|
|
10683
10963
|
return {
|
|
10684
10964
|
ok: true,
|
|
10685
10965
|
path,
|
|
@@ -10704,27 +10984,27 @@ init_kernel();
|
|
|
10704
10984
|
|
|
10705
10985
|
// src/cli/agent-connect.ts
|
|
10706
10986
|
init_kernel();
|
|
10707
|
-
import { mkdirSync as
|
|
10987
|
+
import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
|
|
10708
10988
|
import { homedir as homedir12 } from "os";
|
|
10709
|
-
import { dirname as
|
|
10989
|
+
import { dirname as dirname9 } from "path";
|
|
10710
10990
|
|
|
10711
10991
|
// src/cli/agent-harness.ts
|
|
10712
10992
|
import { spawn as spawn2 } from "child_process";
|
|
10713
|
-
import { existsSync as
|
|
10993
|
+
import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
|
|
10714
10994
|
import { homedir as homedir9 } from "os";
|
|
10715
|
-
import { join as
|
|
10995
|
+
import { join as join16 } from "path";
|
|
10716
10996
|
|
|
10717
10997
|
// src/cli/terminal-open.ts
|
|
10718
10998
|
import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
|
|
10719
10999
|
import {
|
|
10720
11000
|
accessSync,
|
|
10721
11001
|
constants,
|
|
10722
|
-
existsSync as
|
|
11002
|
+
existsSync as existsSync14,
|
|
10723
11003
|
unlinkSync as unlinkSync2,
|
|
10724
|
-
writeFileSync as
|
|
11004
|
+
writeFileSync as writeFileSync8
|
|
10725
11005
|
} from "fs";
|
|
10726
11006
|
import { tmpdir } from "os";
|
|
10727
|
-
import { basename as basename3, delimiter, extname, isAbsolute, join as
|
|
11007
|
+
import { basename as basename3, delimiter, extname, isAbsolute as isAbsolute2, join as join15 } from "path";
|
|
10728
11008
|
function isPowerShellShell(shell) {
|
|
10729
11009
|
return shell === "pwsh" || shell === "powershell";
|
|
10730
11010
|
}
|
|
@@ -10766,7 +11046,7 @@ function stripSurroundingQuotes(command) {
|
|
|
10766
11046
|
return trimmed;
|
|
10767
11047
|
}
|
|
10768
11048
|
function executableExists(path) {
|
|
10769
|
-
if (!
|
|
11049
|
+
if (!existsSync14(path)) return false;
|
|
10770
11050
|
if (process.platform === "win32") return true;
|
|
10771
11051
|
try {
|
|
10772
11052
|
accessSync(path, constants.X_OK);
|
|
@@ -10782,7 +11062,7 @@ function windowsExecutableNames(command) {
|
|
|
10782
11062
|
return extensions.map((ext) => `${command}${ext}`);
|
|
10783
11063
|
}
|
|
10784
11064
|
function hasDirectoryPart(command) {
|
|
10785
|
-
return
|
|
11065
|
+
return isAbsolute2(command) || command.includes("/") || command.includes("\\");
|
|
10786
11066
|
}
|
|
10787
11067
|
function findExecutable(command) {
|
|
10788
11068
|
const normalized = stripSurroundingQuotes(command);
|
|
@@ -10797,7 +11077,7 @@ function findExecutable(command) {
|
|
|
10797
11077
|
const pathEntries = (process.env.PATH ?? "").split(delimiter).map((entry) => entry.trim()).filter(Boolean);
|
|
10798
11078
|
for (const entry of pathEntries) {
|
|
10799
11079
|
for (const name of windowsExecutableNames(normalized)) {
|
|
10800
|
-
const candidate =
|
|
11080
|
+
const candidate = join15(entry, name);
|
|
10801
11081
|
if (executableExists(candidate)) matches.push(candidate);
|
|
10802
11082
|
}
|
|
10803
11083
|
}
|
|
@@ -10835,9 +11115,9 @@ end tell` : `tell application "Terminal"
|
|
|
10835
11115
|
activate
|
|
10836
11116
|
do script "${escaped}"
|
|
10837
11117
|
end tell`;
|
|
10838
|
-
const tmpFile =
|
|
11118
|
+
const tmpFile = join15(tmpdir(), `zam-terminal-${label}.scpt`);
|
|
10839
11119
|
try {
|
|
10840
|
-
|
|
11120
|
+
writeFileSync8(tmpFile, appleScript);
|
|
10841
11121
|
execSync4(`osascript ${JSON.stringify(tmpFile)}`, { stdio: "ignore" });
|
|
10842
11122
|
if (!silent) {
|
|
10843
11123
|
console.log(
|
|
@@ -10918,7 +11198,7 @@ function openTerminalWindow(opts) {
|
|
|
10918
11198
|
// src/cli/agent-harness.ts
|
|
10919
11199
|
var ANTIGRAVITY_IDE_CANDIDATE_PATHS = {
|
|
10920
11200
|
darwin: [
|
|
10921
|
-
|
|
11201
|
+
join16(
|
|
10922
11202
|
homedir9(),
|
|
10923
11203
|
".antigravity-ide",
|
|
10924
11204
|
"antigravity-ide",
|
|
@@ -10939,7 +11219,7 @@ var AGENT_HARNESSES = [
|
|
|
10939
11219
|
command: "cursor",
|
|
10940
11220
|
candidatePaths: {
|
|
10941
11221
|
win32: [
|
|
10942
|
-
|
|
11222
|
+
join16(homedir9(), "AppData", "Local", "Programs", "cursor", "Cursor.exe")
|
|
10943
11223
|
],
|
|
10944
11224
|
darwin: ["/Applications/Cursor.app/Contents/MacOS/Cursor"]
|
|
10945
11225
|
}
|
|
@@ -10964,7 +11244,7 @@ function getHarness(id) {
|
|
|
10964
11244
|
}
|
|
10965
11245
|
function resolveAntigravityIdeExecutable(deps = {}) {
|
|
10966
11246
|
const find = deps.find ?? findExecutable;
|
|
10967
|
-
const exists = deps.exists ??
|
|
11247
|
+
const exists = deps.exists ?? existsSync15;
|
|
10968
11248
|
const platform = deps.platform ?? process.platform;
|
|
10969
11249
|
const found = find("antigravity-ide");
|
|
10970
11250
|
if (found) return found;
|
|
@@ -10972,7 +11252,7 @@ function resolveAntigravityIdeExecutable(deps = {}) {
|
|
|
10972
11252
|
}
|
|
10973
11253
|
function resolveHarnessExecutable(harness, overrideCommand, deps = {}) {
|
|
10974
11254
|
const find = deps.find ?? findExecutable;
|
|
10975
|
-
const exists = deps.exists ??
|
|
11255
|
+
const exists = deps.exists ?? existsSync15;
|
|
10976
11256
|
const platform = deps.platform ?? process.platform;
|
|
10977
11257
|
const found = find(overrideCommand || harness.command);
|
|
10978
11258
|
if (found) return found;
|
|
@@ -11032,18 +11312,18 @@ function detectInstalledConnectHarnesses(options = {}) {
|
|
|
11032
11312
|
const home = options.home ?? homedir9();
|
|
11033
11313
|
const platform = options.platform ?? process.platform;
|
|
11034
11314
|
const find = options.find ?? findExecutable;
|
|
11035
|
-
const exists = options.exists ??
|
|
11315
|
+
const exists = options.exists ?? existsSync15;
|
|
11036
11316
|
const detected = [];
|
|
11037
11317
|
const hasCommandOrPath = (command, paths = []) => Boolean(find(command)) || paths.some((path) => exists(path));
|
|
11038
11318
|
if (hasCommandOrPath("codex", [
|
|
11039
|
-
|
|
11040
|
-
...platform === "darwin" ? ["/Applications/Codex.app"] : platform === "win32" ? [
|
|
11319
|
+
join16(home, ".codex"),
|
|
11320
|
+
...platform === "darwin" ? ["/Applications/Codex.app"] : platform === "win32" ? [join16(home, "AppData", "Local", "Programs", "Codex")] : []
|
|
11041
11321
|
])) {
|
|
11042
11322
|
detected.push("codex");
|
|
11043
11323
|
}
|
|
11044
11324
|
const vscodePaths = platform === "darwin" ? [
|
|
11045
11325
|
"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
|
|
11046
|
-
|
|
11326
|
+
join16(
|
|
11047
11327
|
home,
|
|
11048
11328
|
"Applications",
|
|
11049
11329
|
"Visual Studio Code.app",
|
|
@@ -11054,7 +11334,7 @@ function detectInstalledConnectHarnesses(options = {}) {
|
|
|
11054
11334
|
"code"
|
|
11055
11335
|
)
|
|
11056
11336
|
] : platform === "win32" ? [
|
|
11057
|
-
|
|
11337
|
+
join16(
|
|
11058
11338
|
home,
|
|
11059
11339
|
"AppData",
|
|
11060
11340
|
"Local",
|
|
@@ -11065,23 +11345,23 @@ function detectInstalledConnectHarnesses(options = {}) {
|
|
|
11065
11345
|
)
|
|
11066
11346
|
] : ["/usr/bin/code", "/usr/local/bin/code", "/snap/bin/code"];
|
|
11067
11347
|
if (hasCommandOrPath("code", vscodePaths)) detected.push("vscode");
|
|
11068
|
-
const copilotHome = options.copilotHome ??
|
|
11348
|
+
const copilotHome = options.copilotHome ?? join16(home, ".copilot");
|
|
11069
11349
|
if (hasCommandOrPath("copilot", [
|
|
11070
11350
|
copilotHome,
|
|
11071
11351
|
...platform === "darwin" ? ["/Applications/GitHub Copilot.app"] : []
|
|
11072
11352
|
])) {
|
|
11073
11353
|
detected.push("copilot");
|
|
11074
11354
|
}
|
|
11075
|
-
if (hasCommandOrPath("opencode", [
|
|
11355
|
+
if (hasCommandOrPath("opencode", [join16(home, ".config", "opencode")])) {
|
|
11076
11356
|
detected.push("opencode");
|
|
11077
11357
|
}
|
|
11078
|
-
if (hasCommandOrPath("goose", [
|
|
11358
|
+
if (hasCommandOrPath("goose", [join16(home, ".config", "goose")])) {
|
|
11079
11359
|
detected.push("goose");
|
|
11080
11360
|
}
|
|
11081
11361
|
if (hasCommandOrPath("antigravity", [
|
|
11082
|
-
|
|
11362
|
+
join16(home, ".gemini", "config"),
|
|
11083
11363
|
...platform === "darwin" ? [
|
|
11084
|
-
|
|
11364
|
+
join16(
|
|
11085
11365
|
home,
|
|
11086
11366
|
".antigravity-ide",
|
|
11087
11367
|
"antigravity-ide",
|
|
@@ -11094,7 +11374,7 @@ function detectInstalledConnectHarnesses(options = {}) {
|
|
|
11094
11374
|
]) || find("antigravity-ide")) {
|
|
11095
11375
|
detected.push("antigravity");
|
|
11096
11376
|
}
|
|
11097
|
-
const claudeDesktopPath = platform === "darwin" ? "/Applications/Claude.app" : platform === "win32" ? exists(
|
|
11377
|
+
const claudeDesktopPath = platform === "darwin" ? "/Applications/Claude.app" : platform === "win32" ? exists(join16(home, "AppData", "Local", "AnthropicClaude")) ? join16(home, "AppData", "Local", "AnthropicClaude") : exists(join16(home, "AppData", "Local", "Claude")) ? join16(home, "AppData", "Local", "Claude") : join16(home, "AppData", "Roaming", "Claude") : join16(home, ".config", "Claude");
|
|
11098
11378
|
if (exists(claudeDesktopPath)) detected.push("claude-desktop");
|
|
11099
11379
|
return detected;
|
|
11100
11380
|
}
|
|
@@ -11127,11 +11407,11 @@ function connectHarnessMcp(harnessId, opts) {
|
|
|
11127
11407
|
return false;
|
|
11128
11408
|
}
|
|
11129
11409
|
}
|
|
11130
|
-
return
|
|
11410
|
+
return existsSync15(p);
|
|
11131
11411
|
};
|
|
11132
11412
|
const read = (p) => {
|
|
11133
11413
|
if (opts.readFile) return opts.readFile(p);
|
|
11134
|
-
return
|
|
11414
|
+
return readFileSync12(p, "utf-8");
|
|
11135
11415
|
};
|
|
11136
11416
|
let targetPath = "";
|
|
11137
11417
|
let content = "";
|
|
@@ -11163,32 +11443,32 @@ function connectHarnessMcp(harnessId, opts) {
|
|
|
11163
11443
|
return JSON.stringify(existing, null, 2);
|
|
11164
11444
|
};
|
|
11165
11445
|
if (harnessId === "claude-code") {
|
|
11166
|
-
targetPath =
|
|
11446
|
+
targetPath = join16(opts.cwd, ".mcp.json");
|
|
11167
11447
|
hint = "Claude Code will prompt you to approve the 'zam' MCP server on next launch.";
|
|
11168
11448
|
content = mergeMcpServersJson(targetPath);
|
|
11169
11449
|
} else if (harnessId === "claude-desktop") {
|
|
11170
11450
|
const platform = opts.platform ?? process.platform;
|
|
11171
|
-
targetPath = platform === "win32" ?
|
|
11451
|
+
targetPath = platform === "win32" ? join16(
|
|
11172
11452
|
opts.home,
|
|
11173
11453
|
"AppData",
|
|
11174
11454
|
"Roaming",
|
|
11175
11455
|
"Claude",
|
|
11176
11456
|
"claude_desktop_config.json"
|
|
11177
|
-
) : platform === "darwin" ?
|
|
11457
|
+
) : platform === "darwin" ? join16(
|
|
11178
11458
|
opts.home,
|
|
11179
11459
|
"Library",
|
|
11180
11460
|
"Application Support",
|
|
11181
11461
|
"Claude",
|
|
11182
11462
|
"claude_desktop_config.json"
|
|
11183
|
-
) :
|
|
11463
|
+
) : join16(opts.home, ".config", "Claude", "claude_desktop_config.json");
|
|
11184
11464
|
hint = "Restart Claude Desktop to load the 'zam' MCP server; MCP Apps panels render inline in the chat.";
|
|
11185
11465
|
content = mergeMcpServersJson(targetPath);
|
|
11186
11466
|
} else if (harnessId === "antigravity") {
|
|
11187
|
-
targetPath =
|
|
11467
|
+
targetPath = join16(opts.home, ".gemini", "config", "mcp_config.json");
|
|
11188
11468
|
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.";
|
|
11189
11469
|
content = mergeMcpServersJson(targetPath);
|
|
11190
11470
|
} else if (harnessId === "opencode") {
|
|
11191
|
-
targetPath =
|
|
11471
|
+
targetPath = join16(opts.home, ".config", "opencode", "opencode.json");
|
|
11192
11472
|
hint = "OpenCode will load the enabled 'zam' MCP server on next launch.";
|
|
11193
11473
|
let existing = {};
|
|
11194
11474
|
if (exists(targetPath)) {
|
|
@@ -11216,7 +11496,7 @@ function connectHarnessMcp(harnessId, opts) {
|
|
|
11216
11496
|
existing.mcp = servers;
|
|
11217
11497
|
content = JSON.stringify(existing, null, 2);
|
|
11218
11498
|
} else if (harnessId === "codex") {
|
|
11219
|
-
targetPath =
|
|
11499
|
+
targetPath = join16(opts.home, ".codex", "config.toml");
|
|
11220
11500
|
hint = "Codex will prompt for tool execution approvals or respect the TOML approval modes.";
|
|
11221
11501
|
let existingStr = "";
|
|
11222
11502
|
if (exists(targetPath)) {
|
|
@@ -11243,14 +11523,14 @@ ${block}` : block;
|
|
|
11243
11523
|
}
|
|
11244
11524
|
} else if (harnessId === "vscode") {
|
|
11245
11525
|
const platform = opts.platform ?? process.platform;
|
|
11246
|
-
targetPath = platform === "win32" ?
|
|
11526
|
+
targetPath = platform === "win32" ? join16(opts.home, "AppData", "Roaming", "Code", "User", "mcp.json") : platform === "darwin" ? join16(
|
|
11247
11527
|
opts.home,
|
|
11248
11528
|
"Library",
|
|
11249
11529
|
"Application Support",
|
|
11250
11530
|
"Code",
|
|
11251
11531
|
"User",
|
|
11252
11532
|
"mcp.json"
|
|
11253
|
-
) :
|
|
11533
|
+
) : join16(opts.home, ".config", "Code", "User", "mcp.json");
|
|
11254
11534
|
hint = "Reload VS Code after setup. ZAM Companion stays separate from the Codex chat and can be moved to any panel or sidebar.";
|
|
11255
11535
|
let existing = {};
|
|
11256
11536
|
if (exists(targetPath)) {
|
|
@@ -11272,7 +11552,7 @@ ${block}` : block;
|
|
|
11272
11552
|
if (!existing.inputs) existing.inputs = [];
|
|
11273
11553
|
content = JSON.stringify(existing, null, 2);
|
|
11274
11554
|
} else if (harnessId === "goose") {
|
|
11275
|
-
targetPath =
|
|
11555
|
+
targetPath = join16(opts.home, ".config", "goose", "config.yaml");
|
|
11276
11556
|
hint = "goose will load the 'zam' extension on next session start. Run 'goose configure' to manage extensions.";
|
|
11277
11557
|
const isJs = opts.zamPath.endsWith(".js");
|
|
11278
11558
|
const cmdStr = isJs ? process.execPath : opts.zamPath;
|
|
@@ -11311,8 +11591,8 @@ ${zamExtension}
|
|
|
11311
11591
|
`;
|
|
11312
11592
|
}
|
|
11313
11593
|
} else if (harnessId === "copilot") {
|
|
11314
|
-
targetPath =
|
|
11315
|
-
opts.copilotHome ??
|
|
11594
|
+
targetPath = join16(
|
|
11595
|
+
opts.copilotHome ?? join16(opts.home, ".copilot"),
|
|
11316
11596
|
"mcp-config.json"
|
|
11317
11597
|
);
|
|
11318
11598
|
hint = "Restart GitHub Copilot or start a new session to load the 'zam' MCP server and its focused Recall, Graph, and Settings canvases.";
|
|
@@ -11354,15 +11634,15 @@ ${zamExtension}
|
|
|
11354
11634
|
|
|
11355
11635
|
// src/cli/copilot-extension.ts
|
|
11356
11636
|
import {
|
|
11357
|
-
existsSync as
|
|
11637
|
+
existsSync as existsSync16,
|
|
11358
11638
|
lstatSync,
|
|
11359
|
-
mkdirSync as
|
|
11360
|
-
readFileSync as
|
|
11361
|
-
writeFileSync as
|
|
11639
|
+
mkdirSync as mkdirSync11,
|
|
11640
|
+
readFileSync as readFileSync13,
|
|
11641
|
+
writeFileSync as writeFileSync9
|
|
11362
11642
|
} from "fs";
|
|
11363
11643
|
import { homedir as homedir10 } from "os";
|
|
11364
|
-
import { dirname as
|
|
11365
|
-
import { fileURLToPath as
|
|
11644
|
+
import { dirname as dirname7, join as join17, resolve as resolve5 } from "path";
|
|
11645
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
11366
11646
|
var EXTENSION_NAME = "zam-mcp-apps";
|
|
11367
11647
|
var EXTENSION_FILES = [
|
|
11368
11648
|
"extension.mjs",
|
|
@@ -11371,18 +11651,18 @@ var EXTENSION_FILES = [
|
|
|
11371
11651
|
"manifest.json"
|
|
11372
11652
|
];
|
|
11373
11653
|
var packageRoot = [
|
|
11374
|
-
|
|
11375
|
-
|
|
11376
|
-
].find((candidate) =>
|
|
11654
|
+
fileURLToPath4(new URL("../..", import.meta.url)),
|
|
11655
|
+
fileURLToPath4(new URL("../../..", import.meta.url))
|
|
11656
|
+
].find((candidate) => existsSync16(join17(candidate, "package.json"))) ?? fileURLToPath4(new URL("../..", import.meta.url));
|
|
11377
11657
|
function defaultCliEntry() {
|
|
11378
|
-
return
|
|
11658
|
+
return join17(dirname7(fileURLToPath4(import.meta.url)), "index.js");
|
|
11379
11659
|
}
|
|
11380
11660
|
function resolveCopilotHome(home = homedir10(), configuredHome = process.env.COPILOT_HOME) {
|
|
11381
|
-
return configuredHome?.trim() ?
|
|
11661
|
+
return configuredHome?.trim() ? resolve5(configuredHome) : join17(home, ".copilot");
|
|
11382
11662
|
}
|
|
11383
11663
|
function resolveCopilotZamLaunch(zamPath, options = {}) {
|
|
11384
11664
|
const cliEntry = options.cliEntry ?? defaultCliEntry();
|
|
11385
|
-
if (
|
|
11665
|
+
if (existsSync16(cliEntry)) {
|
|
11386
11666
|
return {
|
|
11387
11667
|
command: options.nodePath ?? process.execPath,
|
|
11388
11668
|
args: [cliEntry, "mcp"]
|
|
@@ -11394,20 +11674,20 @@ function resolveCopilotZamLaunch(zamPath, options = {}) {
|
|
|
11394
11674
|
};
|
|
11395
11675
|
}
|
|
11396
11676
|
function planCopilotExtensionInstall(options) {
|
|
11397
|
-
const sourceDir = options.assetsDir ??
|
|
11677
|
+
const sourceDir = options.assetsDir ?? join17(packageRoot, "dist", "copilot-extension");
|
|
11398
11678
|
for (const file of EXTENSION_FILES) {
|
|
11399
|
-
if (!
|
|
11679
|
+
if (!existsSync16(join17(sourceDir, file))) {
|
|
11400
11680
|
throw new Error(
|
|
11401
|
-
`Copilot MCP Apps asset is missing: ${
|
|
11681
|
+
`Copilot MCP Apps asset is missing: ${join17(sourceDir, file)}. Run \`npm run build\` and retry.`
|
|
11402
11682
|
);
|
|
11403
11683
|
}
|
|
11404
11684
|
}
|
|
11405
11685
|
const manifest = JSON.parse(
|
|
11406
|
-
|
|
11686
|
+
readFileSync13(join17(sourceDir, "manifest.json"), "utf8")
|
|
11407
11687
|
);
|
|
11408
11688
|
if (manifest.name !== EXTENSION_NAME || typeof manifest.version !== "string" || !manifest.version) {
|
|
11409
11689
|
throw new Error(
|
|
11410
|
-
`Invalid Copilot MCP Apps manifest: ${
|
|
11690
|
+
`Invalid Copilot MCP Apps manifest: ${join17(sourceDir, "manifest.json")}`
|
|
11411
11691
|
);
|
|
11412
11692
|
}
|
|
11413
11693
|
const copilotHome = resolveCopilotHome(
|
|
@@ -11416,15 +11696,15 @@ function planCopilotExtensionInstall(options) {
|
|
|
11416
11696
|
);
|
|
11417
11697
|
return {
|
|
11418
11698
|
sourceDir,
|
|
11419
|
-
destinationDir:
|
|
11699
|
+
destinationDir: join17(copilotHome, "extensions", EXTENSION_NAME),
|
|
11420
11700
|
launch: resolveCopilotZamLaunch(options.zamPath, options),
|
|
11421
11701
|
files: EXTENSION_FILES
|
|
11422
11702
|
};
|
|
11423
11703
|
}
|
|
11424
11704
|
function writeIfChanged(path, content) {
|
|
11425
11705
|
const next = Buffer.isBuffer(content) ? content : Buffer.from(content);
|
|
11426
|
-
if (
|
|
11427
|
-
|
|
11706
|
+
if (existsSync16(path) && readFileSync13(path).equals(next)) return false;
|
|
11707
|
+
writeFileSync9(path, next);
|
|
11428
11708
|
return true;
|
|
11429
11709
|
}
|
|
11430
11710
|
function installCopilotExtension(options) {
|
|
@@ -11432,18 +11712,18 @@ function installCopilotExtension(options) {
|
|
|
11432
11712
|
if (options.dryRun) {
|
|
11433
11713
|
return { ...plan, action: "planned", changedFiles: [] };
|
|
11434
11714
|
}
|
|
11435
|
-
const destinationExisted =
|
|
11715
|
+
const destinationExisted = existsSync16(plan.destinationDir);
|
|
11436
11716
|
if (destinationExisted && lstatSync(plan.destinationDir).isSymbolicLink()) {
|
|
11437
11717
|
throw new Error(
|
|
11438
11718
|
`Refusing to replace symlinked Copilot extension directory: ${plan.destinationDir}`
|
|
11439
11719
|
);
|
|
11440
11720
|
}
|
|
11441
|
-
|
|
11721
|
+
mkdirSync11(plan.destinationDir, { recursive: true });
|
|
11442
11722
|
const changedFiles = [];
|
|
11443
11723
|
for (const file of plan.files) {
|
|
11444
|
-
const source =
|
|
11445
|
-
const destination =
|
|
11446
|
-
if (writeIfChanged(destination,
|
|
11724
|
+
const source = join17(plan.sourceDir, file);
|
|
11725
|
+
const destination = join17(plan.destinationDir, file);
|
|
11726
|
+
if (writeIfChanged(destination, readFileSync13(source))) {
|
|
11447
11727
|
changedFiles.push(file);
|
|
11448
11728
|
}
|
|
11449
11729
|
}
|
|
@@ -11457,7 +11737,7 @@ function installCopilotExtension(options) {
|
|
|
11457
11737
|
2
|
|
11458
11738
|
)}
|
|
11459
11739
|
`;
|
|
11460
|
-
if (writeIfChanged(
|
|
11740
|
+
if (writeIfChanged(join17(plan.destinationDir, "launch.json"), launchContent)) {
|
|
11461
11741
|
changedFiles.push("launch.json");
|
|
11462
11742
|
}
|
|
11463
11743
|
return {
|
|
@@ -11469,24 +11749,24 @@ function installCopilotExtension(options) {
|
|
|
11469
11749
|
|
|
11470
11750
|
// src/cli/vscode-extension.ts
|
|
11471
11751
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
11472
|
-
import { existsSync as
|
|
11752
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync12, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "fs";
|
|
11473
11753
|
import { homedir as homedir11 } from "os";
|
|
11474
|
-
import { dirname as
|
|
11475
|
-
import { fileURLToPath as
|
|
11754
|
+
import { dirname as dirname8, join as join18 } from "path";
|
|
11755
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
11476
11756
|
var packageRoot2 = [
|
|
11477
|
-
|
|
11478
|
-
|
|
11479
|
-
].find((candidate) =>
|
|
11757
|
+
fileURLToPath5(new URL("../..", import.meta.url)),
|
|
11758
|
+
fileURLToPath5(new URL("../../..", import.meta.url))
|
|
11759
|
+
].find((candidate) => existsSync17(join18(candidate, "package.json"))) ?? fileURLToPath5(new URL("../..", import.meta.url));
|
|
11480
11760
|
function resolveVscodeExecutable(options = {}) {
|
|
11481
11761
|
const home = options.home ?? homedir11();
|
|
11482
11762
|
const platform = options.platform ?? process.platform;
|
|
11483
11763
|
const find = options.find ?? findExecutable;
|
|
11484
|
-
const exists = options.exists ??
|
|
11764
|
+
const exists = options.exists ?? existsSync17;
|
|
11485
11765
|
const found = find("code");
|
|
11486
11766
|
if (found) return found;
|
|
11487
11767
|
const candidates = platform === "darwin" ? [
|
|
11488
11768
|
"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
|
|
11489
|
-
|
|
11769
|
+
join18(
|
|
11490
11770
|
home,
|
|
11491
11771
|
"Applications",
|
|
11492
11772
|
"Visual Studio Code.app",
|
|
@@ -11497,7 +11777,7 @@ function resolveVscodeExecutable(options = {}) {
|
|
|
11497
11777
|
"code"
|
|
11498
11778
|
)
|
|
11499
11779
|
] : platform === "win32" ? [
|
|
11500
|
-
|
|
11780
|
+
join18(
|
|
11501
11781
|
home,
|
|
11502
11782
|
"AppData",
|
|
11503
11783
|
"Local",
|
|
@@ -11517,7 +11797,7 @@ function buildVscodeCliInvocation(command, args, platform = process.platform) {
|
|
|
11517
11797
|
}
|
|
11518
11798
|
function packageVersion() {
|
|
11519
11799
|
const parsed = JSON.parse(
|
|
11520
|
-
|
|
11800
|
+
readFileSync14(join18(packageRoot2, "package.json"), "utf8")
|
|
11521
11801
|
);
|
|
11522
11802
|
if (typeof parsed.version !== "string" || !parsed.version) {
|
|
11523
11803
|
throw new Error("Cannot determine the ZAM package version");
|
|
@@ -11527,9 +11807,9 @@ function packageVersion() {
|
|
|
11527
11807
|
function planVscodeExtensionInstall(options) {
|
|
11528
11808
|
const home = options.home ?? homedir11();
|
|
11529
11809
|
const version = options.version ?? packageVersion();
|
|
11530
|
-
const assetsDir = options.assetsDir ??
|
|
11531
|
-
const vsixPath =
|
|
11532
|
-
if (!
|
|
11810
|
+
const assetsDir = options.assetsDir ?? join18(packageRoot2, "dist", "vscode-extension");
|
|
11811
|
+
const vsixPath = join18(assetsDir, `ZAM_Companion_${version}.vsix`);
|
|
11812
|
+
if (!existsSync17(vsixPath)) {
|
|
11533
11813
|
throw new Error(
|
|
11534
11814
|
`ZAM Companion VSIX asset is missing: ${vsixPath}. Run \`npm run build\` and retry.`
|
|
11535
11815
|
);
|
|
@@ -11549,7 +11829,7 @@ function planVscodeExtensionInstall(options) {
|
|
|
11549
11829
|
version,
|
|
11550
11830
|
vsixPath,
|
|
11551
11831
|
codePath,
|
|
11552
|
-
launchConfigPath:
|
|
11832
|
+
launchConfigPath: join18(home, ".zam", "vscode-launch.json"),
|
|
11553
11833
|
launch: { command: options.zamPath, args: ["mcp"] }
|
|
11554
11834
|
};
|
|
11555
11835
|
}
|
|
@@ -11585,8 +11865,8 @@ function installVscodeExtension(options) {
|
|
|
11585
11865
|
2
|
|
11586
11866
|
)}
|
|
11587
11867
|
`;
|
|
11588
|
-
const existed =
|
|
11589
|
-
const changed = !existed ||
|
|
11868
|
+
const existed = existsSync17(plan.launchConfigPath);
|
|
11869
|
+
const changed = !existed || readFileSync14(plan.launchConfigPath, "utf8") !== launchContent;
|
|
11590
11870
|
const run = options.run ?? ((command, args) => {
|
|
11591
11871
|
const invocation = buildVscodeCliInvocation(command, args);
|
|
11592
11872
|
execFileSync3(invocation.command, invocation.args, {
|
|
@@ -11608,8 +11888,8 @@ function installVscodeExtension(options) {
|
|
|
11608
11888
|
}
|
|
11609
11889
|
run(plan.codePath, ["--install-extension", plan.vsixPath, "--force"]);
|
|
11610
11890
|
if (changed) {
|
|
11611
|
-
|
|
11612
|
-
|
|
11891
|
+
mkdirSync12(dirname8(plan.launchConfigPath), { recursive: true });
|
|
11892
|
+
writeFileSync10(plan.launchConfigPath, launchContent, "utf8");
|
|
11613
11893
|
}
|
|
11614
11894
|
return {
|
|
11615
11895
|
...plan,
|
|
@@ -11669,8 +11949,8 @@ function resolveDeps(deps) {
|
|
|
11669
11949
|
detect: deps.detect ?? (() => detectInstalledConnectHarnesses({ home, copilotHome })),
|
|
11670
11950
|
connectMcp: deps.connectMcp ?? connectHarnessMcp,
|
|
11671
11951
|
writeConfig: deps.writeConfig ?? ((path, content) => {
|
|
11672
|
-
|
|
11673
|
-
|
|
11952
|
+
mkdirSync13(dirname9(path), { recursive: true });
|
|
11953
|
+
writeFileSync11(path, content, "utf-8");
|
|
11674
11954
|
}),
|
|
11675
11955
|
installCopilot: deps.installCopilot ?? installCopilotExtension,
|
|
11676
11956
|
installVscode: deps.installVscode ?? installVscodeExtension,
|
|
@@ -11816,9 +12096,9 @@ function inspectConnectHarnesses(deps = {}) {
|
|
|
11816
12096
|
init_kernel();
|
|
11817
12097
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
11818
12098
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
11819
|
-
import { existsSync as
|
|
12099
|
+
import { existsSync as existsSync21, readdirSync as readdirSync3, readFileSync as readFileSync18, rmSync as rmSync3 } from "fs";
|
|
11820
12100
|
import { homedir as homedir14, tmpdir as tmpdir3 } from "os";
|
|
11821
|
-
import { join as
|
|
12101
|
+
import { join as join23, resolve as resolve7 } from "path";
|
|
11822
12102
|
import { Command } from "commander";
|
|
11823
12103
|
import { ulid as ulid10 } from "ulid";
|
|
11824
12104
|
|
|
@@ -11898,7 +12178,7 @@ async function readWebLink(url) {
|
|
|
11898
12178
|
redirect: "manual",
|
|
11899
12179
|
signal: controller.signal,
|
|
11900
12180
|
headers: {
|
|
11901
|
-
"User-Agent": "ZAM-Content-Studio/0.
|
|
12181
|
+
"User-Agent": "ZAM-Content-Studio/0.14.0"
|
|
11902
12182
|
}
|
|
11903
12183
|
});
|
|
11904
12184
|
if (res.status >= 300 && res.status < 400) {
|
|
@@ -11965,16 +12245,16 @@ async function readImageOCR(db, imagePath) {
|
|
|
11965
12245
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
11966
12246
|
import {
|
|
11967
12247
|
chmodSync as chmodSync2,
|
|
11968
|
-
existsSync as
|
|
11969
|
-
mkdirSync as
|
|
11970
|
-
readFileSync as
|
|
11971
|
-
writeFileSync as
|
|
12248
|
+
existsSync as existsSync18,
|
|
12249
|
+
mkdirSync as mkdirSync14,
|
|
12250
|
+
readFileSync as readFileSync15,
|
|
12251
|
+
writeFileSync as writeFileSync12
|
|
11972
12252
|
} from "fs";
|
|
11973
12253
|
import { homedir as homedir13 } from "os";
|
|
11974
|
-
import { delimiter as delimiter2, join as
|
|
12254
|
+
import { delimiter as delimiter2, join as join19, resolve as resolve6 } from "path";
|
|
11975
12255
|
var BUILT_CLI_PATTERN = /[\\/]dist[\\/]cli[\\/]index\.js$/;
|
|
11976
12256
|
function normalizeForCompare(path, platform) {
|
|
11977
|
-
const resolved =
|
|
12257
|
+
const resolved = resolve6(path);
|
|
11978
12258
|
return platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
11979
12259
|
}
|
|
11980
12260
|
function windowsShimContent(nodePath, cliPath) {
|
|
@@ -12014,14 +12294,14 @@ function ensureWindowsUserPath(binDir) {
|
|
|
12014
12294
|
return output.endsWith("updated");
|
|
12015
12295
|
}
|
|
12016
12296
|
function ensureUnixUserPath(home, platform) {
|
|
12017
|
-
const profile =
|
|
12018
|
-
const existing =
|
|
12297
|
+
const profile = join19(home, platform === "darwin" ? ".zprofile" : ".profile");
|
|
12298
|
+
const existing = existsSync18(profile) ? readFileSync15(profile, "utf8") : "";
|
|
12019
12299
|
if (existing.includes(".zam/bin")) return false;
|
|
12020
12300
|
const block = `
|
|
12021
12301
|
# Added by ZAM: keep the zam CLI on PATH
|
|
12022
12302
|
export PATH="$HOME/.zam/bin:$PATH"
|
|
12023
12303
|
`;
|
|
12024
|
-
|
|
12304
|
+
writeFileSync12(profile, existing + block, "utf8");
|
|
12025
12305
|
return true;
|
|
12026
12306
|
}
|
|
12027
12307
|
function installCliShim(options = {}) {
|
|
@@ -12029,10 +12309,10 @@ function installCliShim(options = {}) {
|
|
|
12029
12309
|
const platform = options.platform ?? process.platform;
|
|
12030
12310
|
const env = options.env ?? process.env;
|
|
12031
12311
|
const find = options.find ?? findExecutable;
|
|
12032
|
-
const nodePath =
|
|
12033
|
-
const cliPath =
|
|
12034
|
-
const binDir =
|
|
12035
|
-
const shimPath =
|
|
12312
|
+
const nodePath = resolve6(options.nodePath ?? process.execPath);
|
|
12313
|
+
const cliPath = resolve6(options.cliPath ?? process.argv[1] ?? "");
|
|
12314
|
+
const binDir = join19(home, ".zam", "bin");
|
|
12315
|
+
const shimPath = join19(binDir, platform === "win32" ? "zam.cmd" : "zam");
|
|
12036
12316
|
const report = {
|
|
12037
12317
|
status: "ok",
|
|
12038
12318
|
binDir,
|
|
@@ -12043,7 +12323,7 @@ function installCliShim(options = {}) {
|
|
|
12043
12323
|
pathUpdated: false,
|
|
12044
12324
|
needsNewTerminal: false
|
|
12045
12325
|
};
|
|
12046
|
-
if (!BUILT_CLI_PATTERN.test(cliPath) || !
|
|
12326
|
+
if (!BUILT_CLI_PATTERN.test(cliPath) || !existsSync18(cliPath)) {
|
|
12047
12327
|
report.status = "skipped";
|
|
12048
12328
|
report.detail = `Not running from a built CLI entry (${cliPath}); nothing to link.`;
|
|
12049
12329
|
return report;
|
|
@@ -12057,11 +12337,11 @@ function installCliShim(options = {}) {
|
|
|
12057
12337
|
return report;
|
|
12058
12338
|
}
|
|
12059
12339
|
const content = platform === "win32" ? windowsShimContent(nodePath, cliPath) : unixShimContent(nodePath, cliPath);
|
|
12060
|
-
const existed =
|
|
12061
|
-
const changed = !existed ||
|
|
12340
|
+
const existed = existsSync18(shimPath);
|
|
12341
|
+
const changed = !existed || readFileSync15(shimPath, "utf8") !== content;
|
|
12062
12342
|
if (changed) {
|
|
12063
|
-
|
|
12064
|
-
|
|
12343
|
+
mkdirSync14(binDir, { recursive: true });
|
|
12344
|
+
writeFileSync12(shimPath, content, "utf8");
|
|
12065
12345
|
if (platform !== "win32") chmodSync2(shimPath, 493);
|
|
12066
12346
|
}
|
|
12067
12347
|
report.status = !existed ? "installed" : changed ? "refreshed" : "ok";
|
|
@@ -28770,40 +29050,40 @@ function getCurriculumProvider(id) {
|
|
|
28770
29050
|
|
|
28771
29051
|
// src/cli/install-repair.ts
|
|
28772
29052
|
init_kernel();
|
|
28773
|
-
import { existsSync as
|
|
29053
|
+
import { existsSync as existsSync20 } from "fs";
|
|
28774
29054
|
|
|
28775
29055
|
// src/cli/provisioning/index.ts
|
|
28776
29056
|
import {
|
|
28777
|
-
existsSync as
|
|
29057
|
+
existsSync as existsSync19,
|
|
28778
29058
|
lstatSync as lstatSync2,
|
|
28779
|
-
mkdirSync as
|
|
28780
|
-
readFileSync as
|
|
28781
|
-
realpathSync,
|
|
29059
|
+
mkdirSync as mkdirSync15,
|
|
29060
|
+
readFileSync as readFileSync16,
|
|
29061
|
+
realpathSync as realpathSync2,
|
|
28782
29062
|
rmSync as rmSync2,
|
|
28783
29063
|
symlinkSync,
|
|
28784
|
-
writeFileSync as
|
|
29064
|
+
writeFileSync as writeFileSync13
|
|
28785
29065
|
} from "fs";
|
|
28786
|
-
import { basename as basename4, dirname as
|
|
28787
|
-
import { fileURLToPath as
|
|
29066
|
+
import { basename as basename4, dirname as dirname10, join as join20 } from "path";
|
|
29067
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
28788
29068
|
var packageRoot3 = [
|
|
28789
|
-
|
|
28790
|
-
|
|
28791
|
-
].find((candidate) =>
|
|
29069
|
+
fileURLToPath6(new URL("../..", import.meta.url)),
|
|
29070
|
+
fileURLToPath6(new URL("../../..", import.meta.url))
|
|
29071
|
+
].find((candidate) => existsSync19(join20(candidate, "package.json"))) ?? fileURLToPath6(new URL("../..", import.meta.url));
|
|
28792
29072
|
var ALL_SETUP_AGENTS = ["claude", "copilot", "codex", "agent"];
|
|
28793
29073
|
var SKILL_PAIRS = [
|
|
28794
29074
|
{
|
|
28795
|
-
from:
|
|
28796
|
-
to:
|
|
29075
|
+
from: join20(packageRoot3, ".claude", "skills", "zam", "SKILL.md"),
|
|
29076
|
+
to: join20(".claude", "skills", "zam", "SKILL.md"),
|
|
28797
29077
|
agents: ["claude", "copilot"]
|
|
28798
29078
|
},
|
|
28799
29079
|
{
|
|
28800
|
-
from:
|
|
28801
|
-
to:
|
|
29080
|
+
from: join20(packageRoot3, ".agent", "skills", "zam", "SKILL.md"),
|
|
29081
|
+
to: join20(".agent", "skills", "zam", "SKILL.md"),
|
|
28802
29082
|
agents: ["agent"]
|
|
28803
29083
|
},
|
|
28804
29084
|
{
|
|
28805
|
-
from:
|
|
28806
|
-
to:
|
|
29085
|
+
from: join20(packageRoot3, ".agents", "skills", "zam", "SKILL.md"),
|
|
29086
|
+
to: join20(".agents", "skills", "zam", "SKILL.md"),
|
|
28807
29087
|
agents: ["codex"]
|
|
28808
29088
|
}
|
|
28809
29089
|
];
|
|
@@ -28847,7 +29127,7 @@ function isSymbolicLink(path) {
|
|
|
28847
29127
|
}
|
|
28848
29128
|
}
|
|
28849
29129
|
function comparableRealPath(path) {
|
|
28850
|
-
const real =
|
|
29130
|
+
const real = realpathSync2(path);
|
|
28851
29131
|
return process.platform === "win32" ? real.toLowerCase() : real;
|
|
28852
29132
|
}
|
|
28853
29133
|
function pathsResolveToSameDirectory(sourceDir, destinationDir) {
|
|
@@ -28863,15 +29143,15 @@ function linkPointsTo(sourceDir, destinationDir) {
|
|
|
28863
29143
|
function isZamSkillCopy(destinationDir) {
|
|
28864
29144
|
try {
|
|
28865
29145
|
if (!lstatSync2(destinationDir).isDirectory()) return false;
|
|
28866
|
-
const skillFile =
|
|
28867
|
-
if (!
|
|
28868
|
-
return /^name:\s*zam\s*$/m.test(
|
|
29146
|
+
const skillFile = join20(destinationDir, "SKILL.md");
|
|
29147
|
+
if (!existsSync19(skillFile)) return false;
|
|
29148
|
+
return /^name:\s*zam\s*$/m.test(readFileSync16(skillFile, "utf8"));
|
|
28869
29149
|
} catch {
|
|
28870
29150
|
return false;
|
|
28871
29151
|
}
|
|
28872
29152
|
}
|
|
28873
29153
|
function classifySkillDestination(sourceDir, destinationDir) {
|
|
28874
|
-
if (!
|
|
29154
|
+
if (!existsSync19(sourceDir)) return "source-missing";
|
|
28875
29155
|
if (!isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir)) {
|
|
28876
29156
|
return "source-directory";
|
|
28877
29157
|
}
|
|
@@ -28888,9 +29168,9 @@ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {})
|
|
|
28888
29168
|
};
|
|
28889
29169
|
for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
|
|
28890
29170
|
if (!pairAgents.some((agent) => agents.has(agent))) continue;
|
|
28891
|
-
const sourceDir =
|
|
28892
|
-
const destinationDir =
|
|
28893
|
-
const label =
|
|
29171
|
+
const sourceDir = dirname10(from);
|
|
29172
|
+
const destinationDir = dirname10(join20(cwd, to));
|
|
29173
|
+
const label = dirname10(to);
|
|
28894
29174
|
const state = classifySkillDestination(sourceDir, destinationDir);
|
|
28895
29175
|
if (state === "source-missing") {
|
|
28896
29176
|
if (!opts.quiet) {
|
|
@@ -28947,7 +29227,7 @@ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {})
|
|
|
28947
29227
|
if (destinationExists) {
|
|
28948
29228
|
rmSync2(destinationDir, { recursive: true, force: true });
|
|
28949
29229
|
}
|
|
28950
|
-
|
|
29230
|
+
mkdirSync15(dirname10(destinationDir), { recursive: true });
|
|
28951
29231
|
symlinkSync(
|
|
28952
29232
|
sourceDir,
|
|
28953
29233
|
destinationDir,
|
|
@@ -28963,8 +29243,8 @@ function inspectSkillLinks(cwd = process.cwd(), agents = parseSetupAgents()) {
|
|
|
28963
29243
|
const results = [];
|
|
28964
29244
|
for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
|
|
28965
29245
|
if (!pairAgents.some((agent) => agents.has(agent))) continue;
|
|
28966
|
-
const sourceDir =
|
|
28967
|
-
const destinationDir =
|
|
29246
|
+
const sourceDir = dirname10(from);
|
|
29247
|
+
const destinationDir = dirname10(join20(cwd, to));
|
|
28968
29248
|
results.push({
|
|
28969
29249
|
agents: pairAgents,
|
|
28970
29250
|
source: sourceDir,
|
|
@@ -28995,7 +29275,7 @@ function defaultRepairWorkspaces() {
|
|
|
28995
29275
|
try {
|
|
28996
29276
|
const agents = parseSetupAgents();
|
|
28997
29277
|
for (const workspace of getConfiguredWorkspaces()) {
|
|
28998
|
-
if (!
|
|
29278
|
+
if (!existsSync20(workspace.path)) {
|
|
28999
29279
|
summary.missing += 1;
|
|
29000
29280
|
continue;
|
|
29001
29281
|
}
|
|
@@ -29188,9 +29468,9 @@ init_client();
|
|
|
29188
29468
|
init_kernel();
|
|
29189
29469
|
init_client();
|
|
29190
29470
|
import { randomBytes } from "crypto";
|
|
29191
|
-
import { readFileSync as
|
|
29471
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
29192
29472
|
import { tmpdir as tmpdir2 } from "os";
|
|
29193
|
-
import { basename as basename5, join as
|
|
29473
|
+
import { basename as basename5, join as join21 } from "path";
|
|
29194
29474
|
var LANGUAGE_NAMES2 = {
|
|
29195
29475
|
en: "English",
|
|
29196
29476
|
de: "German",
|
|
@@ -29228,7 +29508,7 @@ async function observeUiSnapshotViaLLM(db, input) {
|
|
|
29228
29508
|
if (isVideo) {
|
|
29229
29509
|
const { mkdirSync: mkdirSync17, readdirSync: readdirSync4, rmSync: rmSync4 } = await import("fs");
|
|
29230
29510
|
const { execSync: execSync5 } = await import("child_process");
|
|
29231
|
-
const tempDir =
|
|
29511
|
+
const tempDir = join21(
|
|
29232
29512
|
tmpdir2(),
|
|
29233
29513
|
`zam-frames-${randomBytes(4).toString("hex")}`
|
|
29234
29514
|
);
|
|
@@ -29261,7 +29541,7 @@ async function observeUiSnapshotViaLLM(db, input) {
|
|
|
29261
29541
|
}
|
|
29262
29542
|
}
|
|
29263
29543
|
for (const file of sampledFiles) {
|
|
29264
|
-
const bytes =
|
|
29544
|
+
const bytes = readFileSync17(join21(tempDir, file));
|
|
29265
29545
|
imageUrls.push(`data:image/png;base64,${bytes.toString("base64")}`);
|
|
29266
29546
|
}
|
|
29267
29547
|
} finally {
|
|
@@ -29271,7 +29551,7 @@ async function observeUiSnapshotViaLLM(db, input) {
|
|
|
29271
29551
|
}
|
|
29272
29552
|
}
|
|
29273
29553
|
} else {
|
|
29274
|
-
const imageBytes =
|
|
29554
|
+
const imageBytes = readFileSync17(input.imagePath);
|
|
29275
29555
|
const ext = input.imagePath.split(".").pop()?.toLowerCase();
|
|
29276
29556
|
const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
|
|
29277
29557
|
imageUrls.push(`data:${mime};base64,${imageBytes.toString("base64")}`);
|
|
@@ -29780,13 +30060,13 @@ async function resolveUser(opts, db, resolveOpts) {
|
|
|
29780
30060
|
}
|
|
29781
30061
|
|
|
29782
30062
|
// src/cli/workspaces/backup.ts
|
|
29783
|
-
import { mkdirSync as
|
|
29784
|
-
import { join as
|
|
30063
|
+
import { mkdirSync as mkdirSync16 } from "fs";
|
|
30064
|
+
import { join as join22 } from "path";
|
|
29785
30065
|
async function backupDatabaseTo(db, targetDir) {
|
|
29786
|
-
const backupDir =
|
|
29787
|
-
|
|
30066
|
+
const backupDir = join22(targetDir, "zam-backups");
|
|
30067
|
+
mkdirSync16(backupDir, { recursive: true });
|
|
29788
30068
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
29789
|
-
const dest =
|
|
30069
|
+
const dest = join22(backupDir, `zam-${stamp}.db`);
|
|
29790
30070
|
await db.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
|
|
29791
30071
|
return dest;
|
|
29792
30072
|
}
|
|
@@ -29920,20 +30200,20 @@ bridgeCommand.command("workspace-info").description("Report the workspace dir, i
|
|
|
29920
30200
|
activeWorkspace,
|
|
29921
30201
|
workspaceDir: activeWorkspace.path,
|
|
29922
30202
|
defaultWorkspaceDir: defaultWorkspaceDir(),
|
|
29923
|
-
dataDir:
|
|
30203
|
+
dataDir: join23(homedir14(), ".zam")
|
|
29924
30204
|
});
|
|
29925
30205
|
});
|
|
29926
30206
|
});
|
|
29927
30207
|
function provisionConfiguredWorkspaces() {
|
|
29928
30208
|
return getConfiguredWorkspaces().flatMap(
|
|
29929
|
-
(workspace) =>
|
|
30209
|
+
(workspace) => existsSync21(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
|
|
29930
30210
|
);
|
|
29931
30211
|
}
|
|
29932
30212
|
function buildWorkspaceLinkHealth(workspaces) {
|
|
29933
30213
|
const agents = parseSetupAgents();
|
|
29934
30214
|
const map = {};
|
|
29935
30215
|
for (const workspace of workspaces) {
|
|
29936
|
-
if (!
|
|
30216
|
+
if (!existsSync21(workspace.path)) continue;
|
|
29937
30217
|
const links = inspectSkillLinks(workspace.path, agents);
|
|
29938
30218
|
map[workspace.id] = {
|
|
29939
30219
|
health: summarizeSkillLinkHealth(links),
|
|
@@ -29963,7 +30243,7 @@ bridgeCommand.command("workspace-list").description("List configured ZAM workspa
|
|
|
29963
30243
|
activeWorkspace,
|
|
29964
30244
|
workspaceDir: activeWorkspace.path,
|
|
29965
30245
|
defaultWorkspaceDir: defaultWorkspaceDir(),
|
|
29966
|
-
dataDir:
|
|
30246
|
+
dataDir: join23(homedir14(), ".zam"),
|
|
29967
30247
|
linkHealth: buildWorkspaceLinkHealth(workspaces)
|
|
29968
30248
|
});
|
|
29969
30249
|
});
|
|
@@ -29978,7 +30258,7 @@ bridgeCommand.command("workspace-repair-links").description(
|
|
|
29978
30258
|
if (!id) jsonError("A non-empty --id is required");
|
|
29979
30259
|
const workspace = getConfiguredWorkspaces().find((item) => item.id === id);
|
|
29980
30260
|
if (!workspace) jsonError(`Workspace "${id}" is not configured`);
|
|
29981
|
-
if (!
|
|
30261
|
+
if (!existsSync21(workspace.path)) {
|
|
29982
30262
|
jsonError(`Workspace path does not exist: ${workspace.path}`);
|
|
29983
30263
|
}
|
|
29984
30264
|
const agents = parseSetupAgents(opts.agents);
|
|
@@ -30009,8 +30289,8 @@ function parseBridgeWorkspaceKind(value) {
|
|
|
30009
30289
|
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) => {
|
|
30010
30290
|
const raw = String(opts.path ?? "").trim();
|
|
30011
30291
|
if (!raw) jsonError("A non-empty --path is required");
|
|
30012
|
-
const path =
|
|
30013
|
-
if (!
|
|
30292
|
+
const path = resolve7(raw);
|
|
30293
|
+
if (!existsSync21(path)) jsonError(`Workspace path does not exist: ${path}`);
|
|
30014
30294
|
const id = opts.id ? String(opts.id).trim() : void 0;
|
|
30015
30295
|
if (opts.id && !id) jsonError("A non-empty --id is required");
|
|
30016
30296
|
const kind = parseBridgeWorkspaceKind(opts.kind);
|
|
@@ -30054,8 +30334,8 @@ bridgeCommand.command("workspace-remove").description("Unregister a ZAM workspac
|
|
|
30054
30334
|
bridgeCommand.command("set-workspace-dir").description("Set the personal workspace directory (JSON)").requiredOption("--dir <path>", "Path to the workspace directory").action(async (opts) => {
|
|
30055
30335
|
const raw = String(opts.dir ?? "").trim();
|
|
30056
30336
|
if (!raw) jsonError("A non-empty --dir is required");
|
|
30057
|
-
const dir =
|
|
30058
|
-
if (!
|
|
30337
|
+
const dir = resolve7(raw);
|
|
30338
|
+
if (!existsSync21(dir)) jsonError(`Workspace path does not exist: ${dir}`);
|
|
30059
30339
|
const skillLinks = wireSkills(dir, parseSetupAgents(), { quiet: true });
|
|
30060
30340
|
await withOptionalDb2(async (db) => {
|
|
30061
30341
|
const workspace = await activateWorkspacePath(db, dir);
|
|
@@ -30118,7 +30398,7 @@ bridgeCommand.command("agent-open").description("Launch an agent harness in the
|
|
|
30118
30398
|
jsonError(`Workspace is not configured: ${opts.workspace}`);
|
|
30119
30399
|
}
|
|
30120
30400
|
const activeWorkspace = await ensureActiveWorkspace(db);
|
|
30121
|
-
const workspace = opts.dir ?
|
|
30401
|
+
const workspace = opts.dir ? existsSync21(opts.dir) ? opts.dir : homedir14() : existingWorkspaceDirOrHome(configuredWorkspace ?? activeWorkspace);
|
|
30122
30402
|
launchHarness(harness, {
|
|
30123
30403
|
executable,
|
|
30124
30404
|
workspace,
|
|
@@ -30394,6 +30674,52 @@ bridgeCommand.command("add-token").description("Create a token + card from JSON
|
|
|
30394
30674
|
jsonError(err.message);
|
|
30395
30675
|
}
|
|
30396
30676
|
});
|
|
30677
|
+
bridgeCommand.command("okf-import").description(
|
|
30678
|
+
"Record an agent's decomposition of an OKF article as learning tokens (JSON stdin, ADR 2026-07-18)"
|
|
30679
|
+
).option("--user <id>", "User ID (default: whoami)").action(async (opts) => {
|
|
30680
|
+
try {
|
|
30681
|
+
let raw;
|
|
30682
|
+
if (isServeMode) {
|
|
30683
|
+
raw = serveStdinPayload ?? "";
|
|
30684
|
+
} else {
|
|
30685
|
+
const chunks = [];
|
|
30686
|
+
for await (const chunk of process.stdin) {
|
|
30687
|
+
chunks.push(chunk);
|
|
30688
|
+
}
|
|
30689
|
+
raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
30690
|
+
}
|
|
30691
|
+
if (!raw) {
|
|
30692
|
+
jsonError(
|
|
30693
|
+
"No input received on stdin. Pipe JSON: { file, bundle_dir?, tokens: [...] }"
|
|
30694
|
+
);
|
|
30695
|
+
}
|
|
30696
|
+
let data;
|
|
30697
|
+
try {
|
|
30698
|
+
data = JSON.parse(raw);
|
|
30699
|
+
} catch {
|
|
30700
|
+
jsonError("Invalid JSON input");
|
|
30701
|
+
}
|
|
30702
|
+
if (!data?.file || !Array.isArray(data?.tokens)) {
|
|
30703
|
+
jsonError("JSON must include 'file' and a 'tokens' array");
|
|
30704
|
+
}
|
|
30705
|
+
await withDb2(async (db) => {
|
|
30706
|
+
try {
|
|
30707
|
+
const userId = await resolveUser(opts, db, { json: true });
|
|
30708
|
+
const result = await importOkfTokens(db, {
|
|
30709
|
+
user: userId,
|
|
30710
|
+
bundleDir: data.bundle_dir,
|
|
30711
|
+
file: data.file,
|
|
30712
|
+
tokens: data.tokens
|
|
30713
|
+
});
|
|
30714
|
+
jsonOut(result);
|
|
30715
|
+
} catch (err) {
|
|
30716
|
+
jsonError(err.message);
|
|
30717
|
+
}
|
|
30718
|
+
});
|
|
30719
|
+
} catch (err) {
|
|
30720
|
+
jsonError(err.message);
|
|
30721
|
+
}
|
|
30722
|
+
});
|
|
30397
30723
|
bridgeCommand.command("relevant-tokens").description("Find tokens relevant to a given context").option("--user <id>", "User ID (default: whoami)").action(async (opts) => {
|
|
30398
30724
|
try {
|
|
30399
30725
|
let raw;
|
|
@@ -30490,10 +30816,10 @@ bridgeCommand.command("discover-skills").description(
|
|
|
30490
30816
|
"20"
|
|
30491
30817
|
).action(async (opts) => {
|
|
30492
30818
|
try {
|
|
30493
|
-
const monitorDir =
|
|
30819
|
+
const monitorDir = join23(homedir14(), ".zam", "monitor");
|
|
30494
30820
|
let files;
|
|
30495
30821
|
try {
|
|
30496
|
-
files =
|
|
30822
|
+
files = readdirSync3(monitorDir).filter((f) => f.endsWith(".jsonl"));
|
|
30497
30823
|
} catch {
|
|
30498
30824
|
jsonOut({ proposals: [], message: "No monitor logs found." });
|
|
30499
30825
|
return;
|
|
@@ -30503,7 +30829,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
30503
30829
|
return;
|
|
30504
30830
|
}
|
|
30505
30831
|
const limit = Number(opts.limit);
|
|
30506
|
-
const sorted = files.map((f) => ({ name: f, path:
|
|
30832
|
+
const sorted = files.map((f) => ({ name: f, path: join23(monitorDir, f) })).sort((a, b) => b.name.localeCompare(a.name)).slice(0, limit);
|
|
30507
30833
|
const sessionCommands = /* @__PURE__ */ new Map();
|
|
30508
30834
|
for (const file of sorted) {
|
|
30509
30835
|
const sessionId = file.name.replace(".jsonl", "");
|
|
@@ -31005,7 +31331,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
|
|
|
31005
31331
|
return;
|
|
31006
31332
|
}
|
|
31007
31333
|
}
|
|
31008
|
-
const outputPath = opts.image ?? opts.output ??
|
|
31334
|
+
const outputPath = opts.image ?? opts.output ?? join23(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
|
|
31009
31335
|
const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
|
|
31010
31336
|
if (!isProvided) {
|
|
31011
31337
|
const post = decidePostCapture(policy, {
|
|
@@ -31033,7 +31359,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
|
|
|
31033
31359
|
return;
|
|
31034
31360
|
}
|
|
31035
31361
|
}
|
|
31036
|
-
const imageBytes =
|
|
31362
|
+
const imageBytes = readFileSync18(outputPath);
|
|
31037
31363
|
const base64 = imageBytes.toString("base64");
|
|
31038
31364
|
jsonOut({
|
|
31039
31365
|
sessionId: opts.session ?? null,
|
|
@@ -31060,11 +31386,11 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
31060
31386
|
return;
|
|
31061
31387
|
}
|
|
31062
31388
|
const sessionId = opts.session;
|
|
31063
|
-
const statePath =
|
|
31389
|
+
const statePath = join23(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
31064
31390
|
const defaultExt = platform === "win32" ? ".mkv" : ".mov";
|
|
31065
|
-
const outputPath = opts.output ??
|
|
31066
|
-
const { existsSync:
|
|
31067
|
-
if (
|
|
31391
|
+
const outputPath = opts.output ?? join23(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
|
|
31392
|
+
const { existsSync: existsSync23, writeFileSync: writeFileSync14, openSync, closeSync } = await import("fs");
|
|
31393
|
+
if (existsSync23(statePath)) {
|
|
31068
31394
|
jsonOut({
|
|
31069
31395
|
sessionId,
|
|
31070
31396
|
started: false,
|
|
@@ -31072,7 +31398,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
31072
31398
|
});
|
|
31073
31399
|
return;
|
|
31074
31400
|
}
|
|
31075
|
-
const logPath =
|
|
31401
|
+
const logPath = join23(tmpdir3(), `zam-recording-${sessionId}.log`);
|
|
31076
31402
|
let logFd;
|
|
31077
31403
|
try {
|
|
31078
31404
|
logFd = openSync(logPath, "w");
|
|
@@ -31154,9 +31480,9 @@ bridgeCommand.command("stop-recording").description(
|
|
|
31154
31480
|
return;
|
|
31155
31481
|
}
|
|
31156
31482
|
const sessionId = opts.session;
|
|
31157
|
-
const statePath =
|
|
31158
|
-
const { existsSync:
|
|
31159
|
-
if (!
|
|
31483
|
+
const statePath = join23(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
31484
|
+
const { existsSync: existsSync23, readFileSync: readFileSync20, rmSync: rmSync4 } = await import("fs");
|
|
31485
|
+
if (!existsSync23(statePath)) {
|
|
31160
31486
|
jsonOut({
|
|
31161
31487
|
sessionId,
|
|
31162
31488
|
stopped: false,
|
|
@@ -31193,7 +31519,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
31193
31519
|
rmSync4(statePath, { force: true });
|
|
31194
31520
|
} catch {
|
|
31195
31521
|
}
|
|
31196
|
-
if (!
|
|
31522
|
+
if (!existsSync23(outputPath)) {
|
|
31197
31523
|
jsonOut({
|
|
31198
31524
|
sessionId,
|
|
31199
31525
|
stopped: false,
|
|
@@ -31623,7 +31949,7 @@ bridgeCommand.command("cloud-model-hint").description("Suggest a cloud model for
|
|
|
31623
31949
|
});
|
|
31624
31950
|
bridgeCommand.command("local-llm-hints").description("Detect installed local LLM servers and suggest defaults (JSON)").action(() => {
|
|
31625
31951
|
const profile = getSystemProfile();
|
|
31626
|
-
const flmInstalled = hasCommand("flm") ||
|
|
31952
|
+
const flmInstalled = hasCommand("flm") || existsSync21("C:\\Program Files\\flm\\flm.exe");
|
|
31627
31953
|
const ollamaInstalled = isOllamaInstalled();
|
|
31628
31954
|
const runners = [
|
|
31629
31955
|
{ id: "flm", label: "FastFlowLM", installed: flmInstalled },
|
|
@@ -33758,13 +34084,13 @@ async function writeCompanionContext(db, request, options = {}) {
|
|
|
33758
34084
|
// src/cli/ui-intent.ts
|
|
33759
34085
|
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
33760
34086
|
import { homedir as homedir15 } from "os";
|
|
33761
|
-
import { dirname as
|
|
34087
|
+
import { dirname as dirname11, join as join24 } from "path";
|
|
33762
34088
|
import { ulid as ulid11 } from "ulid";
|
|
33763
34089
|
function getUiIntentPath(home = homedir15()) {
|
|
33764
|
-
return
|
|
34090
|
+
return join24(home, ".zam", "ui-intent.json");
|
|
33765
34091
|
}
|
|
33766
34092
|
function getUiHostRegistrationPath(home = homedir15()) {
|
|
33767
|
-
return
|
|
34093
|
+
return join24(home, ".zam", "vscode-host.json");
|
|
33768
34094
|
}
|
|
33769
34095
|
function compactStringInput(input) {
|
|
33770
34096
|
return Object.fromEntries(
|
|
@@ -33783,8 +34109,8 @@ async function writeUiIntent(app, input = {}, opts = {}) {
|
|
|
33783
34109
|
input: compactStringInput(input),
|
|
33784
34110
|
createdAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
|
|
33785
34111
|
};
|
|
33786
|
-
const tempPath =
|
|
33787
|
-
await mkdir(
|
|
34112
|
+
const tempPath = join24(dirname11(path), `.ui-intent-${process.pid}-${id}.tmp`);
|
|
34113
|
+
await mkdir(dirname11(path), { recursive: true });
|
|
33788
34114
|
await writeFile(tempPath, `${JSON.stringify(intent, null, 2)}
|
|
33789
34115
|
`, "utf8");
|
|
33790
34116
|
await rename(tempPath, path);
|
|
@@ -33817,9 +34143,9 @@ async function publishUiIntent(app, input = {}, opts = {}) {
|
|
|
33817
34143
|
}
|
|
33818
34144
|
|
|
33819
34145
|
// src/cli/commands/mcp.ts
|
|
33820
|
-
var __dirname =
|
|
34146
|
+
var __dirname = dirname12(fileURLToPath7(import.meta.url));
|
|
33821
34147
|
var pkgPath = join25(__dirname, "..", "..", "package.json");
|
|
33822
|
-
if (!
|
|
34148
|
+
if (!existsSync22(pkgPath)) {
|
|
33823
34149
|
pkgPath = join25(__dirname, "..", "..", "..", "package.json");
|
|
33824
34150
|
}
|
|
33825
34151
|
var pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
|
|
@@ -33827,6 +34153,7 @@ var STUDIO_RESOURCE_URI = "ui://zam/studio";
|
|
|
33827
34153
|
var RECALL_RESOURCE_URI = "ui://zam/recall";
|
|
33828
34154
|
var GRAPH_RESOURCE_URI = "ui://zam/graph";
|
|
33829
34155
|
var SETTINGS_RESOURCE_URI = "ui://zam/settings";
|
|
34156
|
+
var OKF_RESOURCE_URI = "ui://zam/okf";
|
|
33830
34157
|
var STUDIO_BRIDGE_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
|
|
33831
34158
|
"list-tokens",
|
|
33832
34159
|
"personal-card-list",
|
|
@@ -33854,7 +34181,7 @@ function loadPanelHtml(fileName, placeholderTitle) {
|
|
|
33854
34181
|
join25(__dirname, "..", "..", "..", "dist", "ui", fileName)
|
|
33855
34182
|
];
|
|
33856
34183
|
for (const candidate of candidates) {
|
|
33857
|
-
if (
|
|
34184
|
+
if (existsSync22(candidate)) {
|
|
33858
34185
|
return readFileSync19(candidate, "utf-8");
|
|
33859
34186
|
}
|
|
33860
34187
|
}
|
|
@@ -34620,28 +34947,60 @@ function createMcpServer(db) {
|
|
|
34620
34947
|
}
|
|
34621
34948
|
)
|
|
34622
34949
|
);
|
|
34623
|
-
const okfBundleDirSchema = z.string().optional().describe(
|
|
34950
|
+
const okfBundleDirSchema = z.string().optional().describe(
|
|
34951
|
+
"Bundle directory (default: docs/okf under the client's workspace root, falling back to the server cwd)"
|
|
34952
|
+
);
|
|
34953
|
+
async function resolveOkfBundleDir(explicit) {
|
|
34954
|
+
const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, resolveBundleDirFromRoots: resolveBundleDirFromRoots2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
34955
|
+
if (explicit) return explicit;
|
|
34956
|
+
try {
|
|
34957
|
+
if (server.server.getClientCapabilities()?.roots) {
|
|
34958
|
+
const { roots } = await server.server.listRoots();
|
|
34959
|
+
return resolveBundleDirFromRoots2(
|
|
34960
|
+
(roots ?? []).map((root) => root.uri),
|
|
34961
|
+
DEFAULT_BUNDLE_DIR2
|
|
34962
|
+
);
|
|
34963
|
+
}
|
|
34964
|
+
} catch {
|
|
34965
|
+
}
|
|
34966
|
+
return DEFAULT_BUNDLE_DIR2;
|
|
34967
|
+
}
|
|
34624
34968
|
server.registerTool(
|
|
34625
34969
|
"zam_okf_catalog",
|
|
34626
34970
|
{
|
|
34627
34971
|
description: "List the OKF knowledge-base articles (type, title, description, tags, resource URL) plus conformance problems, if any",
|
|
34628
34972
|
inputSchema: {
|
|
34629
|
-
bundle_dir: okfBundleDirSchema
|
|
34973
|
+
bundle_dir: okfBundleDirSchema,
|
|
34974
|
+
include_log: z.boolean().optional().describe(
|
|
34975
|
+
"Also return the raw log.md text (empty string if missing)"
|
|
34976
|
+
)
|
|
34630
34977
|
},
|
|
34631
34978
|
annotations: {
|
|
34632
34979
|
...commonAnnotations,
|
|
34633
34980
|
readOnlyHint: true
|
|
34634
34981
|
}
|
|
34635
34982
|
},
|
|
34636
|
-
wrapHandler(
|
|
34637
|
-
|
|
34638
|
-
|
|
34639
|
-
|
|
34640
|
-
|
|
34641
|
-
|
|
34642
|
-
|
|
34643
|
-
|
|
34644
|
-
|
|
34983
|
+
wrapHandler(
|
|
34984
|
+
async (params) => {
|
|
34985
|
+
const { loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
34986
|
+
const bundle = loadBundle2(await resolveOkfBundleDir(params.bundle_dir));
|
|
34987
|
+
let log;
|
|
34988
|
+
if (params.include_log) {
|
|
34989
|
+
const { readFileSync: readFileSync20 } = await import("fs");
|
|
34990
|
+
try {
|
|
34991
|
+
log = readFileSync20(join25(bundle.dir, "log.md"), "utf8");
|
|
34992
|
+
} catch {
|
|
34993
|
+
log = "";
|
|
34994
|
+
}
|
|
34995
|
+
}
|
|
34996
|
+
return {
|
|
34997
|
+
dir: bundle.dir,
|
|
34998
|
+
articles: bundle.catalog,
|
|
34999
|
+
problems: bundle.problems,
|
|
35000
|
+
...params.include_log ? { log } : {}
|
|
35001
|
+
};
|
|
35002
|
+
}
|
|
35003
|
+
)
|
|
34645
35004
|
);
|
|
34646
35005
|
server.registerTool(
|
|
34647
35006
|
"zam_okf_read",
|
|
@@ -34658,10 +35017,10 @@ function createMcpServer(db) {
|
|
|
34658
35017
|
},
|
|
34659
35018
|
wrapHandler(async (params) => {
|
|
34660
35019
|
const { readFileSync: readFileSync20 } = await import("fs");
|
|
34661
|
-
const {
|
|
35020
|
+
const { resolveArticlePath: resolveArticlePath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
34662
35021
|
const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
|
|
34663
35022
|
const path = resolveArticlePath2(
|
|
34664
|
-
params.bundle_dir
|
|
35023
|
+
await resolveOkfBundleDir(params.bundle_dir),
|
|
34665
35024
|
params.file
|
|
34666
35025
|
);
|
|
34667
35026
|
const markdown = readFileSync20(path, "utf8");
|
|
@@ -34688,9 +35047,9 @@ function createMcpServer(db) {
|
|
|
34688
35047
|
},
|
|
34689
35048
|
wrapHandler(
|
|
34690
35049
|
async (params) => {
|
|
34691
|
-
const {
|
|
35050
|
+
const { upsertArticle: upsertArticle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
34692
35051
|
const result = upsertArticle2(
|
|
34693
|
-
params.bundle_dir
|
|
35052
|
+
await resolveOkfBundleDir(params.bundle_dir),
|
|
34694
35053
|
params.file,
|
|
34695
35054
|
params.markdown
|
|
34696
35055
|
);
|
|
@@ -34701,6 +35060,162 @@ function createMcpServer(db) {
|
|
|
34701
35060
|
}
|
|
34702
35061
|
)
|
|
34703
35062
|
);
|
|
35063
|
+
server.registerTool(
|
|
35064
|
+
"zam_okf_import",
|
|
35065
|
+
{
|
|
35066
|
+
description: "Record YOUR finished decomposition of one OKF article as learning tokens + cards (ADR 2026-07-18). Decomposition is your job, not this tool's \u2014 before calling: (1) read the FULL article (zam_okf_read); (2) extract the concepts a practitioner must produce FROM MEMORY \u2014 recall-speed knowledge only; facts one would look up stay in the article; (3) one atomic concept per token, with a JUDGED bloom level and a JUDGED domain per token (reuse existing domains where they fit); (4) arrange a prerequisite DAG from foundational to dependent (in-import slugs and existing tokens both allowed); (5) check for existing tokens first (zam_find_tokens) and link them as prerequisites instead of duplicating. Multiple tokens are the expected outcome for real articles. On RE-import classify each token via mode: 'new' adds, 'update' refreshes content and KEEPS learning state, 'replace' means the concept changed \u2014 content refreshed and learning state RESET to the beginning. Previously imported tokens you do not confirm are moved to maintenance (kept, unscheduled) \u2014 never deleted. The write is atomic; every token gets a card for the importing user.",
|
|
35067
|
+
inputSchema: {
|
|
35068
|
+
user: z.string().optional().describe("User ID to create cards for"),
|
|
35069
|
+
bundle_dir: okfBundleDirSchema,
|
|
35070
|
+
file: z.string().describe("Article file name inside the bundle (kebab-case .md)"),
|
|
35071
|
+
tokens: z.array(
|
|
35072
|
+
z.object({
|
|
35073
|
+
slug: z.string().describe("Unique kebab-case token slug"),
|
|
35074
|
+
title: z.string().optional().describe("Display title"),
|
|
35075
|
+
concept: z.string().describe("The memorable concept, stated precisely"),
|
|
35076
|
+
bloomLevel: z.number().int().min(1).max(5).optional().describe("Judged Bloom level (1-5)"),
|
|
35077
|
+
domain: z.string().optional().describe("Judged domain (reuse existing domains)"),
|
|
35078
|
+
anchor: z.string().optional().describe(
|
|
35079
|
+
"Heading anchor in the article; appended to the source link"
|
|
35080
|
+
),
|
|
35081
|
+
prerequisites: z.array(z.string()).optional().describe(
|
|
35082
|
+
"Slugs this token requires \u2014 in-import or existing tokens"
|
|
35083
|
+
),
|
|
35084
|
+
knowledgeContexts: z.array(z.string()).optional().describe("Knowledge contexts to assign"),
|
|
35085
|
+
question: z.string().nullable().optional().describe("Optional pre-defined review question"),
|
|
35086
|
+
mode: z.enum(["new", "update", "replace"]).optional().describe(
|
|
35087
|
+
"Re-import classification: new (default) | update (keep learning state) | replace (concept changed, reset learning state)"
|
|
35088
|
+
)
|
|
35089
|
+
})
|
|
35090
|
+
).min(1).describe("Your finished decomposition")
|
|
35091
|
+
},
|
|
35092
|
+
annotations: {
|
|
35093
|
+
...commonAnnotations
|
|
35094
|
+
}
|
|
35095
|
+
},
|
|
35096
|
+
wrapHandler(
|
|
35097
|
+
async (params) => {
|
|
35098
|
+
return await importOkfTokens(db, {
|
|
35099
|
+
user: await getUserId(params.user),
|
|
35100
|
+
bundleDir: await resolveOkfBundleDir(params.bundle_dir),
|
|
35101
|
+
file: params.file,
|
|
35102
|
+
tokens: params.tokens
|
|
35103
|
+
});
|
|
35104
|
+
}
|
|
35105
|
+
)
|
|
35106
|
+
);
|
|
35107
|
+
server.registerTool(
|
|
35108
|
+
"zam_okf_read_citation",
|
|
35109
|
+
{
|
|
35110
|
+
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",
|
|
35111
|
+
inputSchema: {
|
|
35112
|
+
bundle_dir: okfBundleDirSchema,
|
|
35113
|
+
target: z.string().describe(
|
|
35114
|
+
"Path to the citation target relative to bundle_dir, e.g. ../adr/2026-07-17-x.md"
|
|
35115
|
+
)
|
|
35116
|
+
},
|
|
35117
|
+
annotations: {
|
|
35118
|
+
...commonAnnotations,
|
|
35119
|
+
readOnlyHint: true
|
|
35120
|
+
}
|
|
35121
|
+
},
|
|
35122
|
+
wrapHandler(async (params) => {
|
|
35123
|
+
const { readFileSync: readFileSync20 } = await import("fs");
|
|
35124
|
+
const { relative: relative2, sep: sep2 } = await import("path");
|
|
35125
|
+
const { findRepoRoot: findRepoRoot2, resolveCitationPath: resolveCitationPath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
35126
|
+
const bundleDir = await resolveOkfBundleDir(params.bundle_dir);
|
|
35127
|
+
const path = resolveCitationPath2(bundleDir, params.target);
|
|
35128
|
+
const content = readFileSync20(path, "utf8");
|
|
35129
|
+
const root = findRepoRoot2(bundleDir);
|
|
35130
|
+
const repoRelativePath = relative2(root, path).split(sep2).join("/");
|
|
35131
|
+
return { target: params.target, path: repoRelativePath, content };
|
|
35132
|
+
})
|
|
35133
|
+
);
|
|
35134
|
+
registerAppTool(
|
|
35135
|
+
server,
|
|
35136
|
+
"zam_okf_visualize",
|
|
35137
|
+
{
|
|
35138
|
+
title: "Open ZAM OKF visualizer",
|
|
35139
|
+
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).",
|
|
35140
|
+
inputSchema: {
|
|
35141
|
+
bundle_dir: okfBundleDirSchema
|
|
35142
|
+
},
|
|
35143
|
+
annotations: {
|
|
35144
|
+
...commonAnnotations,
|
|
35145
|
+
readOnlyHint: true
|
|
35146
|
+
},
|
|
35147
|
+
_meta: {
|
|
35148
|
+
ui: { resourceUri: OKF_RESOURCE_URI }
|
|
35149
|
+
}
|
|
35150
|
+
},
|
|
35151
|
+
wrapHandler(async ({ bundle_dir }) => {
|
|
35152
|
+
const opening = await resolveOpeningCompanionContextSafely(
|
|
35153
|
+
db,
|
|
35154
|
+
"okf",
|
|
35155
|
+
void 0,
|
|
35156
|
+
getNativeClientInfo(),
|
|
35157
|
+
{ clientSamplingCapable: getClientSamplingCapable() }
|
|
35158
|
+
);
|
|
35159
|
+
const { loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
35160
|
+
const { resolve: resolve8 } = await import("path");
|
|
35161
|
+
const requestedDir = await resolveOkfBundleDir(bundle_dir);
|
|
35162
|
+
let resolvedBundleDir = resolve8(requestedDir);
|
|
35163
|
+
await publishUiIntent("okf", { bundle_dir: resolvedBundleDir });
|
|
35164
|
+
let catalog = [];
|
|
35165
|
+
let problems = [];
|
|
35166
|
+
let log = "";
|
|
35167
|
+
let okfVersion = null;
|
|
35168
|
+
try {
|
|
35169
|
+
const bundle = loadBundle2(requestedDir);
|
|
35170
|
+
resolvedBundleDir = bundle.dir;
|
|
35171
|
+
catalog = bundle.catalog;
|
|
35172
|
+
problems = bundle.problems;
|
|
35173
|
+
const { readFileSync: readFileSync20 } = await import("fs");
|
|
35174
|
+
try {
|
|
35175
|
+
log = readFileSync20(join25(bundle.dir, "log.md"), "utf8");
|
|
35176
|
+
} catch {
|
|
35177
|
+
log = "";
|
|
35178
|
+
}
|
|
35179
|
+
try {
|
|
35180
|
+
const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
|
|
35181
|
+
const indexRaw = readFileSync20(join25(bundle.dir, "index.md"), "utf8");
|
|
35182
|
+
const { fields } = parseFrontmatter2(indexRaw);
|
|
35183
|
+
okfVersion = typeof fields.okf_version === "string" ? fields.okf_version : null;
|
|
35184
|
+
} catch {
|
|
35185
|
+
okfVersion = null;
|
|
35186
|
+
}
|
|
35187
|
+
} catch (error) {
|
|
35188
|
+
problems = [error instanceof Error ? error.message : String(error)];
|
|
35189
|
+
}
|
|
35190
|
+
return {
|
|
35191
|
+
okf: "zam",
|
|
35192
|
+
version: pkg.version,
|
|
35193
|
+
user: opening.context.user.currentId ?? null,
|
|
35194
|
+
bundleDir: resolvedBundleDir,
|
|
35195
|
+
okfVersion,
|
|
35196
|
+
catalog,
|
|
35197
|
+
problems,
|
|
35198
|
+
log,
|
|
35199
|
+
companionContext: opening.context,
|
|
35200
|
+
...opening.degraded ? { companionContextDegraded: true } : {}
|
|
35201
|
+
};
|
|
35202
|
+
})
|
|
35203
|
+
);
|
|
35204
|
+
registerAppResource(
|
|
35205
|
+
server,
|
|
35206
|
+
"zam-okf",
|
|
35207
|
+
OKF_RESOURCE_URI,
|
|
35208
|
+
{ mimeType: RESOURCE_MIME_TYPE },
|
|
35209
|
+
async () => ({
|
|
35210
|
+
contents: [
|
|
35211
|
+
{
|
|
35212
|
+
uri: OKF_RESOURCE_URI,
|
|
35213
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
35214
|
+
text: loadPanelHtml("okf-panel.html", "ZAM OKF")
|
|
35215
|
+
}
|
|
35216
|
+
]
|
|
35217
|
+
})
|
|
35218
|
+
);
|
|
34704
35219
|
return server;
|
|
34705
35220
|
}
|
|
34706
35221
|
async function runMcpServer() {
|