zam-core 0.13.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 +565 -224
- 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 +121 -21
- 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 +1 -1
- package/dist/vscode-extension/ZAM_Companion_0.13.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,
|
|
@@ -9168,18 +9234,31 @@ __export(io_exports, {
|
|
|
9168
9234
|
findRepoRoot: () => findRepoRoot,
|
|
9169
9235
|
loadBundle: () => loadBundle,
|
|
9170
9236
|
resolveArticlePath: () => resolveArticlePath,
|
|
9237
|
+
resolveBundleDirFromRoots: () => resolveBundleDirFromRoots,
|
|
9171
9238
|
resolveCitationPath: () => resolveCitationPath,
|
|
9172
9239
|
upsertArticle: () => upsertArticle
|
|
9173
9240
|
});
|
|
9174
9241
|
import {
|
|
9175
|
-
existsSync as
|
|
9176
|
-
mkdirSync as
|
|
9177
|
-
readdirSync as
|
|
9178
|
-
readFileSync as
|
|
9179
|
-
realpathSync
|
|
9180
|
-
writeFileSync as
|
|
9242
|
+
existsSync as existsSync13,
|
|
9243
|
+
mkdirSync as mkdirSync9,
|
|
9244
|
+
readdirSync as readdirSync2,
|
|
9245
|
+
readFileSync as readFileSync11,
|
|
9246
|
+
realpathSync,
|
|
9247
|
+
writeFileSync as writeFileSync6
|
|
9181
9248
|
} from "fs";
|
|
9182
|
-
import { dirname as
|
|
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
|
+
}
|
|
9183
9262
|
function resolveArticlePath(dir, file) {
|
|
9184
9263
|
if (file.includes("/") || file.includes("\\") || file.includes("..")) {
|
|
9185
9264
|
throw new Error(`invalid article file name: ${file}`);
|
|
@@ -9187,19 +9266,19 @@ function resolveArticlePath(dir, file) {
|
|
|
9187
9266
|
if (isReservedFile(file)) {
|
|
9188
9267
|
throw new Error(`refusing to address reserved file: ${file}`);
|
|
9189
9268
|
}
|
|
9190
|
-
return
|
|
9269
|
+
return join13(resolve4(dir), file);
|
|
9191
9270
|
}
|
|
9192
9271
|
function findRepoRoot(startDir) {
|
|
9193
|
-
let dir =
|
|
9272
|
+
let dir = resolve4(startDir);
|
|
9194
9273
|
for (; ; ) {
|
|
9195
|
-
if (
|
|
9196
|
-
const parent =
|
|
9197
|
-
if (parent === dir) return
|
|
9274
|
+
if (existsSync13(join13(dir, ".git"))) return dir;
|
|
9275
|
+
const parent = dirname6(dir);
|
|
9276
|
+
if (parent === dir) return resolve4(startDir, "..");
|
|
9198
9277
|
dir = parent;
|
|
9199
9278
|
}
|
|
9200
9279
|
}
|
|
9201
9280
|
function resolveCitationPath(bundleDir, target) {
|
|
9202
|
-
if (
|
|
9281
|
+
if (isAbsolute(target)) {
|
|
9203
9282
|
throw new Error(
|
|
9204
9283
|
`invalid citation target: absolute paths are not allowed (${target})`
|
|
9205
9284
|
);
|
|
@@ -9210,26 +9289,26 @@ function resolveCitationPath(bundleDir, target) {
|
|
|
9210
9289
|
);
|
|
9211
9290
|
}
|
|
9212
9291
|
const root = findRepoRoot(bundleDir);
|
|
9213
|
-
const resolved =
|
|
9292
|
+
const resolved = resolve4(bundleDir, target);
|
|
9214
9293
|
assertContained(root, resolved, target);
|
|
9215
|
-
if (
|
|
9216
|
-
assertContained(
|
|
9294
|
+
if (existsSync13(resolved)) {
|
|
9295
|
+
assertContained(realpathSync(root), realpathSync(resolved), target);
|
|
9217
9296
|
}
|
|
9218
9297
|
return resolved;
|
|
9219
9298
|
}
|
|
9220
9299
|
function assertContained(root, candidate, target) {
|
|
9221
9300
|
const rel = relative(root, candidate);
|
|
9222
|
-
if (rel === ".." || rel.startsWith(`..${sep}`) ||
|
|
9301
|
+
if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
9223
9302
|
throw new Error(
|
|
9224
9303
|
`invalid citation target: resolves outside the repository root (${target})`
|
|
9225
9304
|
);
|
|
9226
9305
|
}
|
|
9227
9306
|
}
|
|
9228
9307
|
function loadBundle(dir) {
|
|
9229
|
-
const root =
|
|
9308
|
+
const root = resolve4(dir);
|
|
9230
9309
|
let entries;
|
|
9231
9310
|
try {
|
|
9232
|
-
entries =
|
|
9311
|
+
entries = readdirSync2(root).filter(
|
|
9233
9312
|
(name) => name.endsWith(".md") && !isReservedFile(name)
|
|
9234
9313
|
);
|
|
9235
9314
|
} catch {
|
|
@@ -9237,7 +9316,7 @@ function loadBundle(dir) {
|
|
|
9237
9316
|
}
|
|
9238
9317
|
const articles = entries.sort().map((file) => ({
|
|
9239
9318
|
file,
|
|
9240
|
-
markdown:
|
|
9319
|
+
markdown: readFileSync11(join13(root, file), "utf8")
|
|
9241
9320
|
}));
|
|
9242
9321
|
const problems = articles.flatMap(
|
|
9243
9322
|
({ file, markdown }) => validateArticle(file, markdown).problems
|
|
@@ -9259,25 +9338,25 @@ function upsertArticle(dir, file, markdown, today = (/* @__PURE__ */ new Date())
|
|
|
9259
9338
|
const target = resolveArticlePath(dir, file);
|
|
9260
9339
|
const validation = validateArticle(file, markdown);
|
|
9261
9340
|
if (!validation.ok) return { validation };
|
|
9262
|
-
const root =
|
|
9263
|
-
|
|
9341
|
+
const root = resolve4(dir);
|
|
9342
|
+
mkdirSync9(root, { recursive: true });
|
|
9264
9343
|
let created = true;
|
|
9265
9344
|
try {
|
|
9266
|
-
|
|
9345
|
+
readFileSync11(target, "utf8");
|
|
9267
9346
|
created = false;
|
|
9268
9347
|
} catch {
|
|
9269
9348
|
}
|
|
9270
|
-
|
|
9349
|
+
writeFileSync6(target, markdown, "utf8");
|
|
9271
9350
|
const bundle = loadBundle(root);
|
|
9272
|
-
|
|
9351
|
+
writeFileSync6(join13(root, "index.md"), renderIndex(bundle.catalog), "utf8");
|
|
9273
9352
|
let log = "";
|
|
9274
9353
|
try {
|
|
9275
|
-
log =
|
|
9354
|
+
log = readFileSync11(join13(root, "log.md"), "utf8");
|
|
9276
9355
|
} catch {
|
|
9277
9356
|
}
|
|
9278
9357
|
const entry = bundle.catalog.find((e) => e.file === file);
|
|
9279
|
-
|
|
9280
|
-
|
|
9358
|
+
writeFileSync6(
|
|
9359
|
+
join13(root, "log.md"),
|
|
9281
9360
|
appendLog(
|
|
9282
9361
|
log,
|
|
9283
9362
|
today,
|
|
@@ -9300,7 +9379,7 @@ var init_io = __esm({
|
|
|
9300
9379
|
init_kernel();
|
|
9301
9380
|
import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
|
|
9302
9381
|
import { dirname as dirname12, join as join25 } from "path";
|
|
9303
|
-
import { fileURLToPath as
|
|
9382
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
9304
9383
|
import {
|
|
9305
9384
|
RESOURCE_MIME_TYPE,
|
|
9306
9385
|
registerAppResource,
|
|
@@ -9513,8 +9592,8 @@ function buildCompanionContext(input) {
|
|
|
9513
9592
|
|
|
9514
9593
|
// src/cli/bridge-handlers.ts
|
|
9515
9594
|
init_kernel();
|
|
9516
|
-
import { mkdirSync as
|
|
9517
|
-
import { join as
|
|
9595
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
9596
|
+
import { join as join14 } from "path";
|
|
9518
9597
|
|
|
9519
9598
|
// src/cli/knowledge-contexts.ts
|
|
9520
9599
|
init_kernel();
|
|
@@ -10397,6 +10476,161 @@ async function addToken(db, params) {
|
|
|
10397
10476
|
possible_duplicates: possibleDuplicates
|
|
10398
10477
|
};
|
|
10399
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
|
+
}
|
|
10400
10634
|
async function findTokens2(db, params) {
|
|
10401
10635
|
const userId = await resolveHandlerUser(db, params.user);
|
|
10402
10636
|
if (!params.context.trim()) {
|
|
@@ -10721,11 +10955,11 @@ async function backupCreate(db, params) {
|
|
|
10721
10955
|
const targetDir = params.dir || (await ensureActiveWorkspace(db)).path;
|
|
10722
10956
|
const snapshot = await exportSnapshot(db);
|
|
10723
10957
|
const manifest = verifySnapshot(snapshot);
|
|
10724
|
-
const backupDir =
|
|
10725
|
-
|
|
10958
|
+
const backupDir = join14(targetDir, "zam-backups");
|
|
10959
|
+
mkdirSync10(backupDir, { recursive: true });
|
|
10726
10960
|
const stamp = manifest.createdAt.replace(/[:.]/g, "-");
|
|
10727
|
-
const path =
|
|
10728
|
-
|
|
10961
|
+
const path = join14(backupDir, `zam-snapshot-${stamp}.sql`);
|
|
10962
|
+
writeFileSync7(path, snapshot, "utf-8");
|
|
10729
10963
|
return {
|
|
10730
10964
|
ok: true,
|
|
10731
10965
|
path,
|
|
@@ -10750,27 +10984,27 @@ init_kernel();
|
|
|
10750
10984
|
|
|
10751
10985
|
// src/cli/agent-connect.ts
|
|
10752
10986
|
init_kernel();
|
|
10753
|
-
import { mkdirSync as
|
|
10987
|
+
import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync11 } from "fs";
|
|
10754
10988
|
import { homedir as homedir12 } from "os";
|
|
10755
|
-
import { dirname as
|
|
10989
|
+
import { dirname as dirname9 } from "path";
|
|
10756
10990
|
|
|
10757
10991
|
// src/cli/agent-harness.ts
|
|
10758
10992
|
import { spawn as spawn2 } from "child_process";
|
|
10759
|
-
import { existsSync as
|
|
10993
|
+
import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
|
|
10760
10994
|
import { homedir as homedir9 } from "os";
|
|
10761
|
-
import { join as
|
|
10995
|
+
import { join as join16 } from "path";
|
|
10762
10996
|
|
|
10763
10997
|
// src/cli/terminal-open.ts
|
|
10764
10998
|
import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
|
|
10765
10999
|
import {
|
|
10766
11000
|
accessSync,
|
|
10767
11001
|
constants,
|
|
10768
|
-
existsSync as
|
|
11002
|
+
existsSync as existsSync14,
|
|
10769
11003
|
unlinkSync as unlinkSync2,
|
|
10770
|
-
writeFileSync as
|
|
11004
|
+
writeFileSync as writeFileSync8
|
|
10771
11005
|
} from "fs";
|
|
10772
11006
|
import { tmpdir } from "os";
|
|
10773
|
-
import { basename as basename3, delimiter, extname, isAbsolute, join as
|
|
11007
|
+
import { basename as basename3, delimiter, extname, isAbsolute as isAbsolute2, join as join15 } from "path";
|
|
10774
11008
|
function isPowerShellShell(shell) {
|
|
10775
11009
|
return shell === "pwsh" || shell === "powershell";
|
|
10776
11010
|
}
|
|
@@ -10812,7 +11046,7 @@ function stripSurroundingQuotes(command) {
|
|
|
10812
11046
|
return trimmed;
|
|
10813
11047
|
}
|
|
10814
11048
|
function executableExists(path) {
|
|
10815
|
-
if (!
|
|
11049
|
+
if (!existsSync14(path)) return false;
|
|
10816
11050
|
if (process.platform === "win32") return true;
|
|
10817
11051
|
try {
|
|
10818
11052
|
accessSync(path, constants.X_OK);
|
|
@@ -10828,7 +11062,7 @@ function windowsExecutableNames(command) {
|
|
|
10828
11062
|
return extensions.map((ext) => `${command}${ext}`);
|
|
10829
11063
|
}
|
|
10830
11064
|
function hasDirectoryPart(command) {
|
|
10831
|
-
return
|
|
11065
|
+
return isAbsolute2(command) || command.includes("/") || command.includes("\\");
|
|
10832
11066
|
}
|
|
10833
11067
|
function findExecutable(command) {
|
|
10834
11068
|
const normalized = stripSurroundingQuotes(command);
|
|
@@ -10843,7 +11077,7 @@ function findExecutable(command) {
|
|
|
10843
11077
|
const pathEntries = (process.env.PATH ?? "").split(delimiter).map((entry) => entry.trim()).filter(Boolean);
|
|
10844
11078
|
for (const entry of pathEntries) {
|
|
10845
11079
|
for (const name of windowsExecutableNames(normalized)) {
|
|
10846
|
-
const candidate =
|
|
11080
|
+
const candidate = join15(entry, name);
|
|
10847
11081
|
if (executableExists(candidate)) matches.push(candidate);
|
|
10848
11082
|
}
|
|
10849
11083
|
}
|
|
@@ -10881,9 +11115,9 @@ end tell` : `tell application "Terminal"
|
|
|
10881
11115
|
activate
|
|
10882
11116
|
do script "${escaped}"
|
|
10883
11117
|
end tell`;
|
|
10884
|
-
const tmpFile =
|
|
11118
|
+
const tmpFile = join15(tmpdir(), `zam-terminal-${label}.scpt`);
|
|
10885
11119
|
try {
|
|
10886
|
-
|
|
11120
|
+
writeFileSync8(tmpFile, appleScript);
|
|
10887
11121
|
execSync4(`osascript ${JSON.stringify(tmpFile)}`, { stdio: "ignore" });
|
|
10888
11122
|
if (!silent) {
|
|
10889
11123
|
console.log(
|
|
@@ -10964,7 +11198,7 @@ function openTerminalWindow(opts) {
|
|
|
10964
11198
|
// src/cli/agent-harness.ts
|
|
10965
11199
|
var ANTIGRAVITY_IDE_CANDIDATE_PATHS = {
|
|
10966
11200
|
darwin: [
|
|
10967
|
-
|
|
11201
|
+
join16(
|
|
10968
11202
|
homedir9(),
|
|
10969
11203
|
".antigravity-ide",
|
|
10970
11204
|
"antigravity-ide",
|
|
@@ -10985,7 +11219,7 @@ var AGENT_HARNESSES = [
|
|
|
10985
11219
|
command: "cursor",
|
|
10986
11220
|
candidatePaths: {
|
|
10987
11221
|
win32: [
|
|
10988
|
-
|
|
11222
|
+
join16(homedir9(), "AppData", "Local", "Programs", "cursor", "Cursor.exe")
|
|
10989
11223
|
],
|
|
10990
11224
|
darwin: ["/Applications/Cursor.app/Contents/MacOS/Cursor"]
|
|
10991
11225
|
}
|
|
@@ -11010,7 +11244,7 @@ function getHarness(id) {
|
|
|
11010
11244
|
}
|
|
11011
11245
|
function resolveAntigravityIdeExecutable(deps = {}) {
|
|
11012
11246
|
const find = deps.find ?? findExecutable;
|
|
11013
|
-
const exists = deps.exists ??
|
|
11247
|
+
const exists = deps.exists ?? existsSync15;
|
|
11014
11248
|
const platform = deps.platform ?? process.platform;
|
|
11015
11249
|
const found = find("antigravity-ide");
|
|
11016
11250
|
if (found) return found;
|
|
@@ -11018,7 +11252,7 @@ function resolveAntigravityIdeExecutable(deps = {}) {
|
|
|
11018
11252
|
}
|
|
11019
11253
|
function resolveHarnessExecutable(harness, overrideCommand, deps = {}) {
|
|
11020
11254
|
const find = deps.find ?? findExecutable;
|
|
11021
|
-
const exists = deps.exists ??
|
|
11255
|
+
const exists = deps.exists ?? existsSync15;
|
|
11022
11256
|
const platform = deps.platform ?? process.platform;
|
|
11023
11257
|
const found = find(overrideCommand || harness.command);
|
|
11024
11258
|
if (found) return found;
|
|
@@ -11078,18 +11312,18 @@ function detectInstalledConnectHarnesses(options = {}) {
|
|
|
11078
11312
|
const home = options.home ?? homedir9();
|
|
11079
11313
|
const platform = options.platform ?? process.platform;
|
|
11080
11314
|
const find = options.find ?? findExecutable;
|
|
11081
|
-
const exists = options.exists ??
|
|
11315
|
+
const exists = options.exists ?? existsSync15;
|
|
11082
11316
|
const detected = [];
|
|
11083
11317
|
const hasCommandOrPath = (command, paths = []) => Boolean(find(command)) || paths.some((path) => exists(path));
|
|
11084
11318
|
if (hasCommandOrPath("codex", [
|
|
11085
|
-
|
|
11086
|
-
...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")] : []
|
|
11087
11321
|
])) {
|
|
11088
11322
|
detected.push("codex");
|
|
11089
11323
|
}
|
|
11090
11324
|
const vscodePaths = platform === "darwin" ? [
|
|
11091
11325
|
"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
|
|
11092
|
-
|
|
11326
|
+
join16(
|
|
11093
11327
|
home,
|
|
11094
11328
|
"Applications",
|
|
11095
11329
|
"Visual Studio Code.app",
|
|
@@ -11100,7 +11334,7 @@ function detectInstalledConnectHarnesses(options = {}) {
|
|
|
11100
11334
|
"code"
|
|
11101
11335
|
)
|
|
11102
11336
|
] : platform === "win32" ? [
|
|
11103
|
-
|
|
11337
|
+
join16(
|
|
11104
11338
|
home,
|
|
11105
11339
|
"AppData",
|
|
11106
11340
|
"Local",
|
|
@@ -11111,23 +11345,23 @@ function detectInstalledConnectHarnesses(options = {}) {
|
|
|
11111
11345
|
)
|
|
11112
11346
|
] : ["/usr/bin/code", "/usr/local/bin/code", "/snap/bin/code"];
|
|
11113
11347
|
if (hasCommandOrPath("code", vscodePaths)) detected.push("vscode");
|
|
11114
|
-
const copilotHome = options.copilotHome ??
|
|
11348
|
+
const copilotHome = options.copilotHome ?? join16(home, ".copilot");
|
|
11115
11349
|
if (hasCommandOrPath("copilot", [
|
|
11116
11350
|
copilotHome,
|
|
11117
11351
|
...platform === "darwin" ? ["/Applications/GitHub Copilot.app"] : []
|
|
11118
11352
|
])) {
|
|
11119
11353
|
detected.push("copilot");
|
|
11120
11354
|
}
|
|
11121
|
-
if (hasCommandOrPath("opencode", [
|
|
11355
|
+
if (hasCommandOrPath("opencode", [join16(home, ".config", "opencode")])) {
|
|
11122
11356
|
detected.push("opencode");
|
|
11123
11357
|
}
|
|
11124
|
-
if (hasCommandOrPath("goose", [
|
|
11358
|
+
if (hasCommandOrPath("goose", [join16(home, ".config", "goose")])) {
|
|
11125
11359
|
detected.push("goose");
|
|
11126
11360
|
}
|
|
11127
11361
|
if (hasCommandOrPath("antigravity", [
|
|
11128
|
-
|
|
11362
|
+
join16(home, ".gemini", "config"),
|
|
11129
11363
|
...platform === "darwin" ? [
|
|
11130
|
-
|
|
11364
|
+
join16(
|
|
11131
11365
|
home,
|
|
11132
11366
|
".antigravity-ide",
|
|
11133
11367
|
"antigravity-ide",
|
|
@@ -11140,7 +11374,7 @@ function detectInstalledConnectHarnesses(options = {}) {
|
|
|
11140
11374
|
]) || find("antigravity-ide")) {
|
|
11141
11375
|
detected.push("antigravity");
|
|
11142
11376
|
}
|
|
11143
|
-
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");
|
|
11144
11378
|
if (exists(claudeDesktopPath)) detected.push("claude-desktop");
|
|
11145
11379
|
return detected;
|
|
11146
11380
|
}
|
|
@@ -11173,11 +11407,11 @@ function connectHarnessMcp(harnessId, opts) {
|
|
|
11173
11407
|
return false;
|
|
11174
11408
|
}
|
|
11175
11409
|
}
|
|
11176
|
-
return
|
|
11410
|
+
return existsSync15(p);
|
|
11177
11411
|
};
|
|
11178
11412
|
const read = (p) => {
|
|
11179
11413
|
if (opts.readFile) return opts.readFile(p);
|
|
11180
|
-
return
|
|
11414
|
+
return readFileSync12(p, "utf-8");
|
|
11181
11415
|
};
|
|
11182
11416
|
let targetPath = "";
|
|
11183
11417
|
let content = "";
|
|
@@ -11209,32 +11443,32 @@ function connectHarnessMcp(harnessId, opts) {
|
|
|
11209
11443
|
return JSON.stringify(existing, null, 2);
|
|
11210
11444
|
};
|
|
11211
11445
|
if (harnessId === "claude-code") {
|
|
11212
|
-
targetPath =
|
|
11446
|
+
targetPath = join16(opts.cwd, ".mcp.json");
|
|
11213
11447
|
hint = "Claude Code will prompt you to approve the 'zam' MCP server on next launch.";
|
|
11214
11448
|
content = mergeMcpServersJson(targetPath);
|
|
11215
11449
|
} else if (harnessId === "claude-desktop") {
|
|
11216
11450
|
const platform = opts.platform ?? process.platform;
|
|
11217
|
-
targetPath = platform === "win32" ?
|
|
11451
|
+
targetPath = platform === "win32" ? join16(
|
|
11218
11452
|
opts.home,
|
|
11219
11453
|
"AppData",
|
|
11220
11454
|
"Roaming",
|
|
11221
11455
|
"Claude",
|
|
11222
11456
|
"claude_desktop_config.json"
|
|
11223
|
-
) : platform === "darwin" ?
|
|
11457
|
+
) : platform === "darwin" ? join16(
|
|
11224
11458
|
opts.home,
|
|
11225
11459
|
"Library",
|
|
11226
11460
|
"Application Support",
|
|
11227
11461
|
"Claude",
|
|
11228
11462
|
"claude_desktop_config.json"
|
|
11229
|
-
) :
|
|
11463
|
+
) : join16(opts.home, ".config", "Claude", "claude_desktop_config.json");
|
|
11230
11464
|
hint = "Restart Claude Desktop to load the 'zam' MCP server; MCP Apps panels render inline in the chat.";
|
|
11231
11465
|
content = mergeMcpServersJson(targetPath);
|
|
11232
11466
|
} else if (harnessId === "antigravity") {
|
|
11233
|
-
targetPath =
|
|
11467
|
+
targetPath = join16(opts.home, ".gemini", "config", "mcp_config.json");
|
|
11234
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.";
|
|
11235
11469
|
content = mergeMcpServersJson(targetPath);
|
|
11236
11470
|
} else if (harnessId === "opencode") {
|
|
11237
|
-
targetPath =
|
|
11471
|
+
targetPath = join16(opts.home, ".config", "opencode", "opencode.json");
|
|
11238
11472
|
hint = "OpenCode will load the enabled 'zam' MCP server on next launch.";
|
|
11239
11473
|
let existing = {};
|
|
11240
11474
|
if (exists(targetPath)) {
|
|
@@ -11262,7 +11496,7 @@ function connectHarnessMcp(harnessId, opts) {
|
|
|
11262
11496
|
existing.mcp = servers;
|
|
11263
11497
|
content = JSON.stringify(existing, null, 2);
|
|
11264
11498
|
} else if (harnessId === "codex") {
|
|
11265
|
-
targetPath =
|
|
11499
|
+
targetPath = join16(opts.home, ".codex", "config.toml");
|
|
11266
11500
|
hint = "Codex will prompt for tool execution approvals or respect the TOML approval modes.";
|
|
11267
11501
|
let existingStr = "";
|
|
11268
11502
|
if (exists(targetPath)) {
|
|
@@ -11289,14 +11523,14 @@ ${block}` : block;
|
|
|
11289
11523
|
}
|
|
11290
11524
|
} else if (harnessId === "vscode") {
|
|
11291
11525
|
const platform = opts.platform ?? process.platform;
|
|
11292
|
-
targetPath = platform === "win32" ?
|
|
11526
|
+
targetPath = platform === "win32" ? join16(opts.home, "AppData", "Roaming", "Code", "User", "mcp.json") : platform === "darwin" ? join16(
|
|
11293
11527
|
opts.home,
|
|
11294
11528
|
"Library",
|
|
11295
11529
|
"Application Support",
|
|
11296
11530
|
"Code",
|
|
11297
11531
|
"User",
|
|
11298
11532
|
"mcp.json"
|
|
11299
|
-
) :
|
|
11533
|
+
) : join16(opts.home, ".config", "Code", "User", "mcp.json");
|
|
11300
11534
|
hint = "Reload VS Code after setup. ZAM Companion stays separate from the Codex chat and can be moved to any panel or sidebar.";
|
|
11301
11535
|
let existing = {};
|
|
11302
11536
|
if (exists(targetPath)) {
|
|
@@ -11318,7 +11552,7 @@ ${block}` : block;
|
|
|
11318
11552
|
if (!existing.inputs) existing.inputs = [];
|
|
11319
11553
|
content = JSON.stringify(existing, null, 2);
|
|
11320
11554
|
} else if (harnessId === "goose") {
|
|
11321
|
-
targetPath =
|
|
11555
|
+
targetPath = join16(opts.home, ".config", "goose", "config.yaml");
|
|
11322
11556
|
hint = "goose will load the 'zam' extension on next session start. Run 'goose configure' to manage extensions.";
|
|
11323
11557
|
const isJs = opts.zamPath.endsWith(".js");
|
|
11324
11558
|
const cmdStr = isJs ? process.execPath : opts.zamPath;
|
|
@@ -11357,8 +11591,8 @@ ${zamExtension}
|
|
|
11357
11591
|
`;
|
|
11358
11592
|
}
|
|
11359
11593
|
} else if (harnessId === "copilot") {
|
|
11360
|
-
targetPath =
|
|
11361
|
-
opts.copilotHome ??
|
|
11594
|
+
targetPath = join16(
|
|
11595
|
+
opts.copilotHome ?? join16(opts.home, ".copilot"),
|
|
11362
11596
|
"mcp-config.json"
|
|
11363
11597
|
);
|
|
11364
11598
|
hint = "Restart GitHub Copilot or start a new session to load the 'zam' MCP server and its focused Recall, Graph, and Settings canvases.";
|
|
@@ -11400,15 +11634,15 @@ ${zamExtension}
|
|
|
11400
11634
|
|
|
11401
11635
|
// src/cli/copilot-extension.ts
|
|
11402
11636
|
import {
|
|
11403
|
-
existsSync as
|
|
11637
|
+
existsSync as existsSync16,
|
|
11404
11638
|
lstatSync,
|
|
11405
|
-
mkdirSync as
|
|
11406
|
-
readFileSync as
|
|
11407
|
-
writeFileSync as
|
|
11639
|
+
mkdirSync as mkdirSync11,
|
|
11640
|
+
readFileSync as readFileSync13,
|
|
11641
|
+
writeFileSync as writeFileSync9
|
|
11408
11642
|
} from "fs";
|
|
11409
11643
|
import { homedir as homedir10 } from "os";
|
|
11410
|
-
import { dirname as
|
|
11411
|
-
import { fileURLToPath as
|
|
11644
|
+
import { dirname as dirname7, join as join17, resolve as resolve5 } from "path";
|
|
11645
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
11412
11646
|
var EXTENSION_NAME = "zam-mcp-apps";
|
|
11413
11647
|
var EXTENSION_FILES = [
|
|
11414
11648
|
"extension.mjs",
|
|
@@ -11417,18 +11651,18 @@ var EXTENSION_FILES = [
|
|
|
11417
11651
|
"manifest.json"
|
|
11418
11652
|
];
|
|
11419
11653
|
var packageRoot = [
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
].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));
|
|
11423
11657
|
function defaultCliEntry() {
|
|
11424
|
-
return
|
|
11658
|
+
return join17(dirname7(fileURLToPath4(import.meta.url)), "index.js");
|
|
11425
11659
|
}
|
|
11426
11660
|
function resolveCopilotHome(home = homedir10(), configuredHome = process.env.COPILOT_HOME) {
|
|
11427
|
-
return configuredHome?.trim() ?
|
|
11661
|
+
return configuredHome?.trim() ? resolve5(configuredHome) : join17(home, ".copilot");
|
|
11428
11662
|
}
|
|
11429
11663
|
function resolveCopilotZamLaunch(zamPath, options = {}) {
|
|
11430
11664
|
const cliEntry = options.cliEntry ?? defaultCliEntry();
|
|
11431
|
-
if (
|
|
11665
|
+
if (existsSync16(cliEntry)) {
|
|
11432
11666
|
return {
|
|
11433
11667
|
command: options.nodePath ?? process.execPath,
|
|
11434
11668
|
args: [cliEntry, "mcp"]
|
|
@@ -11440,20 +11674,20 @@ function resolveCopilotZamLaunch(zamPath, options = {}) {
|
|
|
11440
11674
|
};
|
|
11441
11675
|
}
|
|
11442
11676
|
function planCopilotExtensionInstall(options) {
|
|
11443
|
-
const sourceDir = options.assetsDir ??
|
|
11677
|
+
const sourceDir = options.assetsDir ?? join17(packageRoot, "dist", "copilot-extension");
|
|
11444
11678
|
for (const file of EXTENSION_FILES) {
|
|
11445
|
-
if (!
|
|
11679
|
+
if (!existsSync16(join17(sourceDir, file))) {
|
|
11446
11680
|
throw new Error(
|
|
11447
|
-
`Copilot MCP Apps asset is missing: ${
|
|
11681
|
+
`Copilot MCP Apps asset is missing: ${join17(sourceDir, file)}. Run \`npm run build\` and retry.`
|
|
11448
11682
|
);
|
|
11449
11683
|
}
|
|
11450
11684
|
}
|
|
11451
11685
|
const manifest = JSON.parse(
|
|
11452
|
-
|
|
11686
|
+
readFileSync13(join17(sourceDir, "manifest.json"), "utf8")
|
|
11453
11687
|
);
|
|
11454
11688
|
if (manifest.name !== EXTENSION_NAME || typeof manifest.version !== "string" || !manifest.version) {
|
|
11455
11689
|
throw new Error(
|
|
11456
|
-
`Invalid Copilot MCP Apps manifest: ${
|
|
11690
|
+
`Invalid Copilot MCP Apps manifest: ${join17(sourceDir, "manifest.json")}`
|
|
11457
11691
|
);
|
|
11458
11692
|
}
|
|
11459
11693
|
const copilotHome = resolveCopilotHome(
|
|
@@ -11462,15 +11696,15 @@ function planCopilotExtensionInstall(options) {
|
|
|
11462
11696
|
);
|
|
11463
11697
|
return {
|
|
11464
11698
|
sourceDir,
|
|
11465
|
-
destinationDir:
|
|
11699
|
+
destinationDir: join17(copilotHome, "extensions", EXTENSION_NAME),
|
|
11466
11700
|
launch: resolveCopilotZamLaunch(options.zamPath, options),
|
|
11467
11701
|
files: EXTENSION_FILES
|
|
11468
11702
|
};
|
|
11469
11703
|
}
|
|
11470
11704
|
function writeIfChanged(path, content) {
|
|
11471
11705
|
const next = Buffer.isBuffer(content) ? content : Buffer.from(content);
|
|
11472
|
-
if (
|
|
11473
|
-
|
|
11706
|
+
if (existsSync16(path) && readFileSync13(path).equals(next)) return false;
|
|
11707
|
+
writeFileSync9(path, next);
|
|
11474
11708
|
return true;
|
|
11475
11709
|
}
|
|
11476
11710
|
function installCopilotExtension(options) {
|
|
@@ -11478,18 +11712,18 @@ function installCopilotExtension(options) {
|
|
|
11478
11712
|
if (options.dryRun) {
|
|
11479
11713
|
return { ...plan, action: "planned", changedFiles: [] };
|
|
11480
11714
|
}
|
|
11481
|
-
const destinationExisted =
|
|
11715
|
+
const destinationExisted = existsSync16(plan.destinationDir);
|
|
11482
11716
|
if (destinationExisted && lstatSync(plan.destinationDir).isSymbolicLink()) {
|
|
11483
11717
|
throw new Error(
|
|
11484
11718
|
`Refusing to replace symlinked Copilot extension directory: ${plan.destinationDir}`
|
|
11485
11719
|
);
|
|
11486
11720
|
}
|
|
11487
|
-
|
|
11721
|
+
mkdirSync11(plan.destinationDir, { recursive: true });
|
|
11488
11722
|
const changedFiles = [];
|
|
11489
11723
|
for (const file of plan.files) {
|
|
11490
|
-
const source =
|
|
11491
|
-
const destination =
|
|
11492
|
-
if (writeIfChanged(destination,
|
|
11724
|
+
const source = join17(plan.sourceDir, file);
|
|
11725
|
+
const destination = join17(plan.destinationDir, file);
|
|
11726
|
+
if (writeIfChanged(destination, readFileSync13(source))) {
|
|
11493
11727
|
changedFiles.push(file);
|
|
11494
11728
|
}
|
|
11495
11729
|
}
|
|
@@ -11503,7 +11737,7 @@ function installCopilotExtension(options) {
|
|
|
11503
11737
|
2
|
|
11504
11738
|
)}
|
|
11505
11739
|
`;
|
|
11506
|
-
if (writeIfChanged(
|
|
11740
|
+
if (writeIfChanged(join17(plan.destinationDir, "launch.json"), launchContent)) {
|
|
11507
11741
|
changedFiles.push("launch.json");
|
|
11508
11742
|
}
|
|
11509
11743
|
return {
|
|
@@ -11515,24 +11749,24 @@ function installCopilotExtension(options) {
|
|
|
11515
11749
|
|
|
11516
11750
|
// src/cli/vscode-extension.ts
|
|
11517
11751
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
11518
|
-
import { existsSync as
|
|
11752
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync12, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "fs";
|
|
11519
11753
|
import { homedir as homedir11 } from "os";
|
|
11520
|
-
import { dirname as
|
|
11521
|
-
import { fileURLToPath as
|
|
11754
|
+
import { dirname as dirname8, join as join18 } from "path";
|
|
11755
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
11522
11756
|
var packageRoot2 = [
|
|
11523
|
-
|
|
11524
|
-
|
|
11525
|
-
].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));
|
|
11526
11760
|
function resolveVscodeExecutable(options = {}) {
|
|
11527
11761
|
const home = options.home ?? homedir11();
|
|
11528
11762
|
const platform = options.platform ?? process.platform;
|
|
11529
11763
|
const find = options.find ?? findExecutable;
|
|
11530
|
-
const exists = options.exists ??
|
|
11764
|
+
const exists = options.exists ?? existsSync17;
|
|
11531
11765
|
const found = find("code");
|
|
11532
11766
|
if (found) return found;
|
|
11533
11767
|
const candidates = platform === "darwin" ? [
|
|
11534
11768
|
"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code",
|
|
11535
|
-
|
|
11769
|
+
join18(
|
|
11536
11770
|
home,
|
|
11537
11771
|
"Applications",
|
|
11538
11772
|
"Visual Studio Code.app",
|
|
@@ -11543,7 +11777,7 @@ function resolveVscodeExecutable(options = {}) {
|
|
|
11543
11777
|
"code"
|
|
11544
11778
|
)
|
|
11545
11779
|
] : platform === "win32" ? [
|
|
11546
|
-
|
|
11780
|
+
join18(
|
|
11547
11781
|
home,
|
|
11548
11782
|
"AppData",
|
|
11549
11783
|
"Local",
|
|
@@ -11563,7 +11797,7 @@ function buildVscodeCliInvocation(command, args, platform = process.platform) {
|
|
|
11563
11797
|
}
|
|
11564
11798
|
function packageVersion() {
|
|
11565
11799
|
const parsed = JSON.parse(
|
|
11566
|
-
|
|
11800
|
+
readFileSync14(join18(packageRoot2, "package.json"), "utf8")
|
|
11567
11801
|
);
|
|
11568
11802
|
if (typeof parsed.version !== "string" || !parsed.version) {
|
|
11569
11803
|
throw new Error("Cannot determine the ZAM package version");
|
|
@@ -11573,9 +11807,9 @@ function packageVersion() {
|
|
|
11573
11807
|
function planVscodeExtensionInstall(options) {
|
|
11574
11808
|
const home = options.home ?? homedir11();
|
|
11575
11809
|
const version = options.version ?? packageVersion();
|
|
11576
|
-
const assetsDir = options.assetsDir ??
|
|
11577
|
-
const vsixPath =
|
|
11578
|
-
if (!
|
|
11810
|
+
const assetsDir = options.assetsDir ?? join18(packageRoot2, "dist", "vscode-extension");
|
|
11811
|
+
const vsixPath = join18(assetsDir, `ZAM_Companion_${version}.vsix`);
|
|
11812
|
+
if (!existsSync17(vsixPath)) {
|
|
11579
11813
|
throw new Error(
|
|
11580
11814
|
`ZAM Companion VSIX asset is missing: ${vsixPath}. Run \`npm run build\` and retry.`
|
|
11581
11815
|
);
|
|
@@ -11595,7 +11829,7 @@ function planVscodeExtensionInstall(options) {
|
|
|
11595
11829
|
version,
|
|
11596
11830
|
vsixPath,
|
|
11597
11831
|
codePath,
|
|
11598
|
-
launchConfigPath:
|
|
11832
|
+
launchConfigPath: join18(home, ".zam", "vscode-launch.json"),
|
|
11599
11833
|
launch: { command: options.zamPath, args: ["mcp"] }
|
|
11600
11834
|
};
|
|
11601
11835
|
}
|
|
@@ -11631,8 +11865,8 @@ function installVscodeExtension(options) {
|
|
|
11631
11865
|
2
|
|
11632
11866
|
)}
|
|
11633
11867
|
`;
|
|
11634
|
-
const existed =
|
|
11635
|
-
const changed = !existed ||
|
|
11868
|
+
const existed = existsSync17(plan.launchConfigPath);
|
|
11869
|
+
const changed = !existed || readFileSync14(plan.launchConfigPath, "utf8") !== launchContent;
|
|
11636
11870
|
const run = options.run ?? ((command, args) => {
|
|
11637
11871
|
const invocation = buildVscodeCliInvocation(command, args);
|
|
11638
11872
|
execFileSync3(invocation.command, invocation.args, {
|
|
@@ -11654,8 +11888,8 @@ function installVscodeExtension(options) {
|
|
|
11654
11888
|
}
|
|
11655
11889
|
run(plan.codePath, ["--install-extension", plan.vsixPath, "--force"]);
|
|
11656
11890
|
if (changed) {
|
|
11657
|
-
|
|
11658
|
-
|
|
11891
|
+
mkdirSync12(dirname8(plan.launchConfigPath), { recursive: true });
|
|
11892
|
+
writeFileSync10(plan.launchConfigPath, launchContent, "utf8");
|
|
11659
11893
|
}
|
|
11660
11894
|
return {
|
|
11661
11895
|
...plan,
|
|
@@ -11715,8 +11949,8 @@ function resolveDeps(deps) {
|
|
|
11715
11949
|
detect: deps.detect ?? (() => detectInstalledConnectHarnesses({ home, copilotHome })),
|
|
11716
11950
|
connectMcp: deps.connectMcp ?? connectHarnessMcp,
|
|
11717
11951
|
writeConfig: deps.writeConfig ?? ((path, content) => {
|
|
11718
|
-
|
|
11719
|
-
|
|
11952
|
+
mkdirSync13(dirname9(path), { recursive: true });
|
|
11953
|
+
writeFileSync11(path, content, "utf-8");
|
|
11720
11954
|
}),
|
|
11721
11955
|
installCopilot: deps.installCopilot ?? installCopilotExtension,
|
|
11722
11956
|
installVscode: deps.installVscode ?? installVscodeExtension,
|
|
@@ -11862,9 +12096,9 @@ function inspectConnectHarnesses(deps = {}) {
|
|
|
11862
12096
|
init_kernel();
|
|
11863
12097
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
11864
12098
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
11865
|
-
import { existsSync as
|
|
12099
|
+
import { existsSync as existsSync21, readdirSync as readdirSync3, readFileSync as readFileSync18, rmSync as rmSync3 } from "fs";
|
|
11866
12100
|
import { homedir as homedir14, tmpdir as tmpdir3 } from "os";
|
|
11867
|
-
import { join as
|
|
12101
|
+
import { join as join23, resolve as resolve7 } from "path";
|
|
11868
12102
|
import { Command } from "commander";
|
|
11869
12103
|
import { ulid as ulid10 } from "ulid";
|
|
11870
12104
|
|
|
@@ -11944,7 +12178,7 @@ async function readWebLink(url) {
|
|
|
11944
12178
|
redirect: "manual",
|
|
11945
12179
|
signal: controller.signal,
|
|
11946
12180
|
headers: {
|
|
11947
|
-
"User-Agent": "ZAM-Content-Studio/0.
|
|
12181
|
+
"User-Agent": "ZAM-Content-Studio/0.14.0"
|
|
11948
12182
|
}
|
|
11949
12183
|
});
|
|
11950
12184
|
if (res.status >= 300 && res.status < 400) {
|
|
@@ -12011,16 +12245,16 @@ async function readImageOCR(db, imagePath) {
|
|
|
12011
12245
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
12012
12246
|
import {
|
|
12013
12247
|
chmodSync as chmodSync2,
|
|
12014
|
-
existsSync as
|
|
12015
|
-
mkdirSync as
|
|
12016
|
-
readFileSync as
|
|
12017
|
-
writeFileSync as
|
|
12248
|
+
existsSync as existsSync18,
|
|
12249
|
+
mkdirSync as mkdirSync14,
|
|
12250
|
+
readFileSync as readFileSync15,
|
|
12251
|
+
writeFileSync as writeFileSync12
|
|
12018
12252
|
} from "fs";
|
|
12019
12253
|
import { homedir as homedir13 } from "os";
|
|
12020
|
-
import { delimiter as delimiter2, join as
|
|
12254
|
+
import { delimiter as delimiter2, join as join19, resolve as resolve6 } from "path";
|
|
12021
12255
|
var BUILT_CLI_PATTERN = /[\\/]dist[\\/]cli[\\/]index\.js$/;
|
|
12022
12256
|
function normalizeForCompare(path, platform) {
|
|
12023
|
-
const resolved =
|
|
12257
|
+
const resolved = resolve6(path);
|
|
12024
12258
|
return platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
12025
12259
|
}
|
|
12026
12260
|
function windowsShimContent(nodePath, cliPath) {
|
|
@@ -12060,14 +12294,14 @@ function ensureWindowsUserPath(binDir) {
|
|
|
12060
12294
|
return output.endsWith("updated");
|
|
12061
12295
|
}
|
|
12062
12296
|
function ensureUnixUserPath(home, platform) {
|
|
12063
|
-
const profile =
|
|
12064
|
-
const existing =
|
|
12297
|
+
const profile = join19(home, platform === "darwin" ? ".zprofile" : ".profile");
|
|
12298
|
+
const existing = existsSync18(profile) ? readFileSync15(profile, "utf8") : "";
|
|
12065
12299
|
if (existing.includes(".zam/bin")) return false;
|
|
12066
12300
|
const block = `
|
|
12067
12301
|
# Added by ZAM: keep the zam CLI on PATH
|
|
12068
12302
|
export PATH="$HOME/.zam/bin:$PATH"
|
|
12069
12303
|
`;
|
|
12070
|
-
|
|
12304
|
+
writeFileSync12(profile, existing + block, "utf8");
|
|
12071
12305
|
return true;
|
|
12072
12306
|
}
|
|
12073
12307
|
function installCliShim(options = {}) {
|
|
@@ -12075,10 +12309,10 @@ function installCliShim(options = {}) {
|
|
|
12075
12309
|
const platform = options.platform ?? process.platform;
|
|
12076
12310
|
const env = options.env ?? process.env;
|
|
12077
12311
|
const find = options.find ?? findExecutable;
|
|
12078
|
-
const nodePath =
|
|
12079
|
-
const cliPath =
|
|
12080
|
-
const binDir =
|
|
12081
|
-
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");
|
|
12082
12316
|
const report = {
|
|
12083
12317
|
status: "ok",
|
|
12084
12318
|
binDir,
|
|
@@ -12089,7 +12323,7 @@ function installCliShim(options = {}) {
|
|
|
12089
12323
|
pathUpdated: false,
|
|
12090
12324
|
needsNewTerminal: false
|
|
12091
12325
|
};
|
|
12092
|
-
if (!BUILT_CLI_PATTERN.test(cliPath) || !
|
|
12326
|
+
if (!BUILT_CLI_PATTERN.test(cliPath) || !existsSync18(cliPath)) {
|
|
12093
12327
|
report.status = "skipped";
|
|
12094
12328
|
report.detail = `Not running from a built CLI entry (${cliPath}); nothing to link.`;
|
|
12095
12329
|
return report;
|
|
@@ -12103,11 +12337,11 @@ function installCliShim(options = {}) {
|
|
|
12103
12337
|
return report;
|
|
12104
12338
|
}
|
|
12105
12339
|
const content = platform === "win32" ? windowsShimContent(nodePath, cliPath) : unixShimContent(nodePath, cliPath);
|
|
12106
|
-
const existed =
|
|
12107
|
-
const changed = !existed ||
|
|
12340
|
+
const existed = existsSync18(shimPath);
|
|
12341
|
+
const changed = !existed || readFileSync15(shimPath, "utf8") !== content;
|
|
12108
12342
|
if (changed) {
|
|
12109
|
-
|
|
12110
|
-
|
|
12343
|
+
mkdirSync14(binDir, { recursive: true });
|
|
12344
|
+
writeFileSync12(shimPath, content, "utf8");
|
|
12111
12345
|
if (platform !== "win32") chmodSync2(shimPath, 493);
|
|
12112
12346
|
}
|
|
12113
12347
|
report.status = !existed ? "installed" : changed ? "refreshed" : "ok";
|
|
@@ -28816,40 +29050,40 @@ function getCurriculumProvider(id) {
|
|
|
28816
29050
|
|
|
28817
29051
|
// src/cli/install-repair.ts
|
|
28818
29052
|
init_kernel();
|
|
28819
|
-
import { existsSync as
|
|
29053
|
+
import { existsSync as existsSync20 } from "fs";
|
|
28820
29054
|
|
|
28821
29055
|
// src/cli/provisioning/index.ts
|
|
28822
29056
|
import {
|
|
28823
|
-
existsSync as
|
|
29057
|
+
existsSync as existsSync19,
|
|
28824
29058
|
lstatSync as lstatSync2,
|
|
28825
|
-
mkdirSync as
|
|
28826
|
-
readFileSync as
|
|
28827
|
-
realpathSync,
|
|
29059
|
+
mkdirSync as mkdirSync15,
|
|
29060
|
+
readFileSync as readFileSync16,
|
|
29061
|
+
realpathSync as realpathSync2,
|
|
28828
29062
|
rmSync as rmSync2,
|
|
28829
29063
|
symlinkSync,
|
|
28830
|
-
writeFileSync as
|
|
29064
|
+
writeFileSync as writeFileSync13
|
|
28831
29065
|
} from "fs";
|
|
28832
|
-
import { basename as basename4, dirname as
|
|
28833
|
-
import { fileURLToPath as
|
|
29066
|
+
import { basename as basename4, dirname as dirname10, join as join20 } from "path";
|
|
29067
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
28834
29068
|
var packageRoot3 = [
|
|
28835
|
-
|
|
28836
|
-
|
|
28837
|
-
].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));
|
|
28838
29072
|
var ALL_SETUP_AGENTS = ["claude", "copilot", "codex", "agent"];
|
|
28839
29073
|
var SKILL_PAIRS = [
|
|
28840
29074
|
{
|
|
28841
|
-
from:
|
|
28842
|
-
to:
|
|
29075
|
+
from: join20(packageRoot3, ".claude", "skills", "zam", "SKILL.md"),
|
|
29076
|
+
to: join20(".claude", "skills", "zam", "SKILL.md"),
|
|
28843
29077
|
agents: ["claude", "copilot"]
|
|
28844
29078
|
},
|
|
28845
29079
|
{
|
|
28846
|
-
from:
|
|
28847
|
-
to:
|
|
29080
|
+
from: join20(packageRoot3, ".agent", "skills", "zam", "SKILL.md"),
|
|
29081
|
+
to: join20(".agent", "skills", "zam", "SKILL.md"),
|
|
28848
29082
|
agents: ["agent"]
|
|
28849
29083
|
},
|
|
28850
29084
|
{
|
|
28851
|
-
from:
|
|
28852
|
-
to:
|
|
29085
|
+
from: join20(packageRoot3, ".agents", "skills", "zam", "SKILL.md"),
|
|
29086
|
+
to: join20(".agents", "skills", "zam", "SKILL.md"),
|
|
28853
29087
|
agents: ["codex"]
|
|
28854
29088
|
}
|
|
28855
29089
|
];
|
|
@@ -28893,7 +29127,7 @@ function isSymbolicLink(path) {
|
|
|
28893
29127
|
}
|
|
28894
29128
|
}
|
|
28895
29129
|
function comparableRealPath(path) {
|
|
28896
|
-
const real =
|
|
29130
|
+
const real = realpathSync2(path);
|
|
28897
29131
|
return process.platform === "win32" ? real.toLowerCase() : real;
|
|
28898
29132
|
}
|
|
28899
29133
|
function pathsResolveToSameDirectory(sourceDir, destinationDir) {
|
|
@@ -28909,15 +29143,15 @@ function linkPointsTo(sourceDir, destinationDir) {
|
|
|
28909
29143
|
function isZamSkillCopy(destinationDir) {
|
|
28910
29144
|
try {
|
|
28911
29145
|
if (!lstatSync2(destinationDir).isDirectory()) return false;
|
|
28912
|
-
const skillFile =
|
|
28913
|
-
if (!
|
|
28914
|
-
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"));
|
|
28915
29149
|
} catch {
|
|
28916
29150
|
return false;
|
|
28917
29151
|
}
|
|
28918
29152
|
}
|
|
28919
29153
|
function classifySkillDestination(sourceDir, destinationDir) {
|
|
28920
|
-
if (!
|
|
29154
|
+
if (!existsSync19(sourceDir)) return "source-missing";
|
|
28921
29155
|
if (!isSymbolicLink(destinationDir) && pathsResolveToSameDirectory(sourceDir, destinationDir)) {
|
|
28922
29156
|
return "source-directory";
|
|
28923
29157
|
}
|
|
@@ -28934,9 +29168,9 @@ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {})
|
|
|
28934
29168
|
};
|
|
28935
29169
|
for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
|
|
28936
29170
|
if (!pairAgents.some((agent) => agents.has(agent))) continue;
|
|
28937
|
-
const sourceDir =
|
|
28938
|
-
const destinationDir =
|
|
28939
|
-
const label =
|
|
29171
|
+
const sourceDir = dirname10(from);
|
|
29172
|
+
const destinationDir = dirname10(join20(cwd, to));
|
|
29173
|
+
const label = dirname10(to);
|
|
28940
29174
|
const state = classifySkillDestination(sourceDir, destinationDir);
|
|
28941
29175
|
if (state === "source-missing") {
|
|
28942
29176
|
if (!opts.quiet) {
|
|
@@ -28993,7 +29227,7 @@ function wireSkills(cwd = process.cwd(), agents = parseSetupAgents(), opts = {})
|
|
|
28993
29227
|
if (destinationExists) {
|
|
28994
29228
|
rmSync2(destinationDir, { recursive: true, force: true });
|
|
28995
29229
|
}
|
|
28996
|
-
|
|
29230
|
+
mkdirSync15(dirname10(destinationDir), { recursive: true });
|
|
28997
29231
|
symlinkSync(
|
|
28998
29232
|
sourceDir,
|
|
28999
29233
|
destinationDir,
|
|
@@ -29009,8 +29243,8 @@ function inspectSkillLinks(cwd = process.cwd(), agents = parseSetupAgents()) {
|
|
|
29009
29243
|
const results = [];
|
|
29010
29244
|
for (const { from, to, agents: pairAgents } of SKILL_PAIRS) {
|
|
29011
29245
|
if (!pairAgents.some((agent) => agents.has(agent))) continue;
|
|
29012
|
-
const sourceDir =
|
|
29013
|
-
const destinationDir =
|
|
29246
|
+
const sourceDir = dirname10(from);
|
|
29247
|
+
const destinationDir = dirname10(join20(cwd, to));
|
|
29014
29248
|
results.push({
|
|
29015
29249
|
agents: pairAgents,
|
|
29016
29250
|
source: sourceDir,
|
|
@@ -29041,7 +29275,7 @@ function defaultRepairWorkspaces() {
|
|
|
29041
29275
|
try {
|
|
29042
29276
|
const agents = parseSetupAgents();
|
|
29043
29277
|
for (const workspace of getConfiguredWorkspaces()) {
|
|
29044
|
-
if (!
|
|
29278
|
+
if (!existsSync20(workspace.path)) {
|
|
29045
29279
|
summary.missing += 1;
|
|
29046
29280
|
continue;
|
|
29047
29281
|
}
|
|
@@ -29234,9 +29468,9 @@ init_client();
|
|
|
29234
29468
|
init_kernel();
|
|
29235
29469
|
init_client();
|
|
29236
29470
|
import { randomBytes } from "crypto";
|
|
29237
|
-
import { readFileSync as
|
|
29471
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
29238
29472
|
import { tmpdir as tmpdir2 } from "os";
|
|
29239
|
-
import { basename as basename5, join as
|
|
29473
|
+
import { basename as basename5, join as join21 } from "path";
|
|
29240
29474
|
var LANGUAGE_NAMES2 = {
|
|
29241
29475
|
en: "English",
|
|
29242
29476
|
de: "German",
|
|
@@ -29274,7 +29508,7 @@ async function observeUiSnapshotViaLLM(db, input) {
|
|
|
29274
29508
|
if (isVideo) {
|
|
29275
29509
|
const { mkdirSync: mkdirSync17, readdirSync: readdirSync4, rmSync: rmSync4 } = await import("fs");
|
|
29276
29510
|
const { execSync: execSync5 } = await import("child_process");
|
|
29277
|
-
const tempDir =
|
|
29511
|
+
const tempDir = join21(
|
|
29278
29512
|
tmpdir2(),
|
|
29279
29513
|
`zam-frames-${randomBytes(4).toString("hex")}`
|
|
29280
29514
|
);
|
|
@@ -29307,7 +29541,7 @@ async function observeUiSnapshotViaLLM(db, input) {
|
|
|
29307
29541
|
}
|
|
29308
29542
|
}
|
|
29309
29543
|
for (const file of sampledFiles) {
|
|
29310
|
-
const bytes =
|
|
29544
|
+
const bytes = readFileSync17(join21(tempDir, file));
|
|
29311
29545
|
imageUrls.push(`data:image/png;base64,${bytes.toString("base64")}`);
|
|
29312
29546
|
}
|
|
29313
29547
|
} finally {
|
|
@@ -29317,7 +29551,7 @@ async function observeUiSnapshotViaLLM(db, input) {
|
|
|
29317
29551
|
}
|
|
29318
29552
|
}
|
|
29319
29553
|
} else {
|
|
29320
|
-
const imageBytes =
|
|
29554
|
+
const imageBytes = readFileSync17(input.imagePath);
|
|
29321
29555
|
const ext = input.imagePath.split(".").pop()?.toLowerCase();
|
|
29322
29556
|
const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
|
|
29323
29557
|
imageUrls.push(`data:${mime};base64,${imageBytes.toString("base64")}`);
|
|
@@ -29826,13 +30060,13 @@ async function resolveUser(opts, db, resolveOpts) {
|
|
|
29826
30060
|
}
|
|
29827
30061
|
|
|
29828
30062
|
// src/cli/workspaces/backup.ts
|
|
29829
|
-
import { mkdirSync as
|
|
29830
|
-
import { join as
|
|
30063
|
+
import { mkdirSync as mkdirSync16 } from "fs";
|
|
30064
|
+
import { join as join22 } from "path";
|
|
29831
30065
|
async function backupDatabaseTo(db, targetDir) {
|
|
29832
|
-
const backupDir =
|
|
29833
|
-
|
|
30066
|
+
const backupDir = join22(targetDir, "zam-backups");
|
|
30067
|
+
mkdirSync16(backupDir, { recursive: true });
|
|
29834
30068
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
29835
|
-
const dest =
|
|
30069
|
+
const dest = join22(backupDir, `zam-${stamp}.db`);
|
|
29836
30070
|
await db.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
|
|
29837
30071
|
return dest;
|
|
29838
30072
|
}
|
|
@@ -29966,20 +30200,20 @@ bridgeCommand.command("workspace-info").description("Report the workspace dir, i
|
|
|
29966
30200
|
activeWorkspace,
|
|
29967
30201
|
workspaceDir: activeWorkspace.path,
|
|
29968
30202
|
defaultWorkspaceDir: defaultWorkspaceDir(),
|
|
29969
|
-
dataDir:
|
|
30203
|
+
dataDir: join23(homedir14(), ".zam")
|
|
29970
30204
|
});
|
|
29971
30205
|
});
|
|
29972
30206
|
});
|
|
29973
30207
|
function provisionConfiguredWorkspaces() {
|
|
29974
30208
|
return getConfiguredWorkspaces().flatMap(
|
|
29975
|
-
(workspace) =>
|
|
30209
|
+
(workspace) => existsSync21(workspace.path) ? wireSkills(workspace.path, parseSetupAgents(), { quiet: true }) : []
|
|
29976
30210
|
);
|
|
29977
30211
|
}
|
|
29978
30212
|
function buildWorkspaceLinkHealth(workspaces) {
|
|
29979
30213
|
const agents = parseSetupAgents();
|
|
29980
30214
|
const map = {};
|
|
29981
30215
|
for (const workspace of workspaces) {
|
|
29982
|
-
if (!
|
|
30216
|
+
if (!existsSync21(workspace.path)) continue;
|
|
29983
30217
|
const links = inspectSkillLinks(workspace.path, agents);
|
|
29984
30218
|
map[workspace.id] = {
|
|
29985
30219
|
health: summarizeSkillLinkHealth(links),
|
|
@@ -30009,7 +30243,7 @@ bridgeCommand.command("workspace-list").description("List configured ZAM workspa
|
|
|
30009
30243
|
activeWorkspace,
|
|
30010
30244
|
workspaceDir: activeWorkspace.path,
|
|
30011
30245
|
defaultWorkspaceDir: defaultWorkspaceDir(),
|
|
30012
|
-
dataDir:
|
|
30246
|
+
dataDir: join23(homedir14(), ".zam"),
|
|
30013
30247
|
linkHealth: buildWorkspaceLinkHealth(workspaces)
|
|
30014
30248
|
});
|
|
30015
30249
|
});
|
|
@@ -30024,7 +30258,7 @@ bridgeCommand.command("workspace-repair-links").description(
|
|
|
30024
30258
|
if (!id) jsonError("A non-empty --id is required");
|
|
30025
30259
|
const workspace = getConfiguredWorkspaces().find((item) => item.id === id);
|
|
30026
30260
|
if (!workspace) jsonError(`Workspace "${id}" is not configured`);
|
|
30027
|
-
if (!
|
|
30261
|
+
if (!existsSync21(workspace.path)) {
|
|
30028
30262
|
jsonError(`Workspace path does not exist: ${workspace.path}`);
|
|
30029
30263
|
}
|
|
30030
30264
|
const agents = parseSetupAgents(opts.agents);
|
|
@@ -30055,8 +30289,8 @@ function parseBridgeWorkspaceKind(value) {
|
|
|
30055
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) => {
|
|
30056
30290
|
const raw = String(opts.path ?? "").trim();
|
|
30057
30291
|
if (!raw) jsonError("A non-empty --path is required");
|
|
30058
|
-
const path =
|
|
30059
|
-
if (!
|
|
30292
|
+
const path = resolve7(raw);
|
|
30293
|
+
if (!existsSync21(path)) jsonError(`Workspace path does not exist: ${path}`);
|
|
30060
30294
|
const id = opts.id ? String(opts.id).trim() : void 0;
|
|
30061
30295
|
if (opts.id && !id) jsonError("A non-empty --id is required");
|
|
30062
30296
|
const kind = parseBridgeWorkspaceKind(opts.kind);
|
|
@@ -30100,8 +30334,8 @@ bridgeCommand.command("workspace-remove").description("Unregister a ZAM workspac
|
|
|
30100
30334
|
bridgeCommand.command("set-workspace-dir").description("Set the personal workspace directory (JSON)").requiredOption("--dir <path>", "Path to the workspace directory").action(async (opts) => {
|
|
30101
30335
|
const raw = String(opts.dir ?? "").trim();
|
|
30102
30336
|
if (!raw) jsonError("A non-empty --dir is required");
|
|
30103
|
-
const dir =
|
|
30104
|
-
if (!
|
|
30337
|
+
const dir = resolve7(raw);
|
|
30338
|
+
if (!existsSync21(dir)) jsonError(`Workspace path does not exist: ${dir}`);
|
|
30105
30339
|
const skillLinks = wireSkills(dir, parseSetupAgents(), { quiet: true });
|
|
30106
30340
|
await withOptionalDb2(async (db) => {
|
|
30107
30341
|
const workspace = await activateWorkspacePath(db, dir);
|
|
@@ -30164,7 +30398,7 @@ bridgeCommand.command("agent-open").description("Launch an agent harness in the
|
|
|
30164
30398
|
jsonError(`Workspace is not configured: ${opts.workspace}`);
|
|
30165
30399
|
}
|
|
30166
30400
|
const activeWorkspace = await ensureActiveWorkspace(db);
|
|
30167
|
-
const workspace = opts.dir ?
|
|
30401
|
+
const workspace = opts.dir ? existsSync21(opts.dir) ? opts.dir : homedir14() : existingWorkspaceDirOrHome(configuredWorkspace ?? activeWorkspace);
|
|
30168
30402
|
launchHarness(harness, {
|
|
30169
30403
|
executable,
|
|
30170
30404
|
workspace,
|
|
@@ -30440,6 +30674,52 @@ bridgeCommand.command("add-token").description("Create a token + card from JSON
|
|
|
30440
30674
|
jsonError(err.message);
|
|
30441
30675
|
}
|
|
30442
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
|
+
});
|
|
30443
30723
|
bridgeCommand.command("relevant-tokens").description("Find tokens relevant to a given context").option("--user <id>", "User ID (default: whoami)").action(async (opts) => {
|
|
30444
30724
|
try {
|
|
30445
30725
|
let raw;
|
|
@@ -30536,10 +30816,10 @@ bridgeCommand.command("discover-skills").description(
|
|
|
30536
30816
|
"20"
|
|
30537
30817
|
).action(async (opts) => {
|
|
30538
30818
|
try {
|
|
30539
|
-
const monitorDir =
|
|
30819
|
+
const monitorDir = join23(homedir14(), ".zam", "monitor");
|
|
30540
30820
|
let files;
|
|
30541
30821
|
try {
|
|
30542
|
-
files =
|
|
30822
|
+
files = readdirSync3(monitorDir).filter((f) => f.endsWith(".jsonl"));
|
|
30543
30823
|
} catch {
|
|
30544
30824
|
jsonOut({ proposals: [], message: "No monitor logs found." });
|
|
30545
30825
|
return;
|
|
@@ -30549,7 +30829,7 @@ bridgeCommand.command("discover-skills").description(
|
|
|
30549
30829
|
return;
|
|
30550
30830
|
}
|
|
30551
30831
|
const limit = Number(opts.limit);
|
|
30552
|
-
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);
|
|
30553
30833
|
const sessionCommands = /* @__PURE__ */ new Map();
|
|
30554
30834
|
for (const file of sorted) {
|
|
30555
30835
|
const sessionId = file.name.replace(".jsonl", "");
|
|
@@ -31051,7 +31331,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
|
|
|
31051
31331
|
return;
|
|
31052
31332
|
}
|
|
31053
31333
|
}
|
|
31054
|
-
const outputPath = opts.image ?? opts.output ??
|
|
31334
|
+
const outputPath = opts.image ?? opts.output ?? join23(tmpdir3(), `zam-capture-${randomBytes2(4).toString("hex")}.png`);
|
|
31055
31335
|
const captureResult = isProvided ? { method: "provided", target: null } : captureScreenshot(outputPath, opts.hwnd, opts.processName);
|
|
31056
31336
|
if (!isProvided) {
|
|
31057
31337
|
const post = decidePostCapture(policy, {
|
|
@@ -31079,7 +31359,7 @@ bridgeCommand.command("capture-ui").description("Capture a screenshot for agent-
|
|
|
31079
31359
|
return;
|
|
31080
31360
|
}
|
|
31081
31361
|
}
|
|
31082
|
-
const imageBytes =
|
|
31362
|
+
const imageBytes = readFileSync18(outputPath);
|
|
31083
31363
|
const base64 = imageBytes.toString("base64");
|
|
31084
31364
|
jsonOut({
|
|
31085
31365
|
sessionId: opts.session ?? null,
|
|
@@ -31106,9 +31386,9 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
31106
31386
|
return;
|
|
31107
31387
|
}
|
|
31108
31388
|
const sessionId = opts.session;
|
|
31109
|
-
const statePath =
|
|
31389
|
+
const statePath = join23(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
31110
31390
|
const defaultExt = platform === "win32" ? ".mkv" : ".mov";
|
|
31111
|
-
const outputPath = opts.output ??
|
|
31391
|
+
const outputPath = opts.output ?? join23(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
|
|
31112
31392
|
const { existsSync: existsSync23, writeFileSync: writeFileSync14, openSync, closeSync } = await import("fs");
|
|
31113
31393
|
if (existsSync23(statePath)) {
|
|
31114
31394
|
jsonOut({
|
|
@@ -31118,7 +31398,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
|
|
|
31118
31398
|
});
|
|
31119
31399
|
return;
|
|
31120
31400
|
}
|
|
31121
|
-
const logPath =
|
|
31401
|
+
const logPath = join23(tmpdir3(), `zam-recording-${sessionId}.log`);
|
|
31122
31402
|
let logFd;
|
|
31123
31403
|
try {
|
|
31124
31404
|
logFd = openSync(logPath, "w");
|
|
@@ -31200,7 +31480,7 @@ bridgeCommand.command("stop-recording").description(
|
|
|
31200
31480
|
return;
|
|
31201
31481
|
}
|
|
31202
31482
|
const sessionId = opts.session;
|
|
31203
|
-
const statePath =
|
|
31483
|
+
const statePath = join23(tmpdir3(), `zam-recording-${sessionId}.json`);
|
|
31204
31484
|
const { existsSync: existsSync23, readFileSync: readFileSync20, rmSync: rmSync4 } = await import("fs");
|
|
31205
31485
|
if (!existsSync23(statePath)) {
|
|
31206
31486
|
jsonOut({
|
|
@@ -31669,7 +31949,7 @@ bridgeCommand.command("cloud-model-hint").description("Suggest a cloud model for
|
|
|
31669
31949
|
});
|
|
31670
31950
|
bridgeCommand.command("local-llm-hints").description("Detect installed local LLM servers and suggest defaults (JSON)").action(() => {
|
|
31671
31951
|
const profile = getSystemProfile();
|
|
31672
|
-
const flmInstalled = hasCommand("flm") ||
|
|
31952
|
+
const flmInstalled = hasCommand("flm") || existsSync21("C:\\Program Files\\flm\\flm.exe");
|
|
31673
31953
|
const ollamaInstalled = isOllamaInstalled();
|
|
31674
31954
|
const runners = [
|
|
31675
31955
|
{ id: "flm", label: "FastFlowLM", installed: flmInstalled },
|
|
@@ -33804,13 +34084,13 @@ async function writeCompanionContext(db, request, options = {}) {
|
|
|
33804
34084
|
// src/cli/ui-intent.ts
|
|
33805
34085
|
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
33806
34086
|
import { homedir as homedir15 } from "os";
|
|
33807
|
-
import { dirname as
|
|
34087
|
+
import { dirname as dirname11, join as join24 } from "path";
|
|
33808
34088
|
import { ulid as ulid11 } from "ulid";
|
|
33809
34089
|
function getUiIntentPath(home = homedir15()) {
|
|
33810
|
-
return
|
|
34090
|
+
return join24(home, ".zam", "ui-intent.json");
|
|
33811
34091
|
}
|
|
33812
34092
|
function getUiHostRegistrationPath(home = homedir15()) {
|
|
33813
|
-
return
|
|
34093
|
+
return join24(home, ".zam", "vscode-host.json");
|
|
33814
34094
|
}
|
|
33815
34095
|
function compactStringInput(input) {
|
|
33816
34096
|
return Object.fromEntries(
|
|
@@ -33829,8 +34109,8 @@ async function writeUiIntent(app, input = {}, opts = {}) {
|
|
|
33829
34109
|
input: compactStringInput(input),
|
|
33830
34110
|
createdAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
|
|
33831
34111
|
};
|
|
33832
|
-
const tempPath =
|
|
33833
|
-
await mkdir(
|
|
34112
|
+
const tempPath = join24(dirname11(path), `.ui-intent-${process.pid}-${id}.tmp`);
|
|
34113
|
+
await mkdir(dirname11(path), { recursive: true });
|
|
33834
34114
|
await writeFile(tempPath, `${JSON.stringify(intent, null, 2)}
|
|
33835
34115
|
`, "utf8");
|
|
33836
34116
|
await rename(tempPath, path);
|
|
@@ -33863,7 +34143,7 @@ async function publishUiIntent(app, input = {}, opts = {}) {
|
|
|
33863
34143
|
}
|
|
33864
34144
|
|
|
33865
34145
|
// src/cli/commands/mcp.ts
|
|
33866
|
-
var __dirname = dirname12(
|
|
34146
|
+
var __dirname = dirname12(fileURLToPath7(import.meta.url));
|
|
33867
34147
|
var pkgPath = join25(__dirname, "..", "..", "package.json");
|
|
33868
34148
|
if (!existsSync22(pkgPath)) {
|
|
33869
34149
|
pkgPath = join25(__dirname, "..", "..", "..", "package.json");
|
|
@@ -34667,7 +34947,24 @@ function createMcpServer(db) {
|
|
|
34667
34947
|
}
|
|
34668
34948
|
)
|
|
34669
34949
|
);
|
|
34670
|
-
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
|
+
}
|
|
34671
34968
|
server.registerTool(
|
|
34672
34969
|
"zam_okf_catalog",
|
|
34673
34970
|
{
|
|
@@ -34685,8 +34982,8 @@ function createMcpServer(db) {
|
|
|
34685
34982
|
},
|
|
34686
34983
|
wrapHandler(
|
|
34687
34984
|
async (params) => {
|
|
34688
|
-
const {
|
|
34689
|
-
const bundle = loadBundle2(params.bundle_dir
|
|
34985
|
+
const { loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
34986
|
+
const bundle = loadBundle2(await resolveOkfBundleDir(params.bundle_dir));
|
|
34690
34987
|
let log;
|
|
34691
34988
|
if (params.include_log) {
|
|
34692
34989
|
const { readFileSync: readFileSync20 } = await import("fs");
|
|
@@ -34720,10 +35017,10 @@ function createMcpServer(db) {
|
|
|
34720
35017
|
},
|
|
34721
35018
|
wrapHandler(async (params) => {
|
|
34722
35019
|
const { readFileSync: readFileSync20 } = await import("fs");
|
|
34723
|
-
const {
|
|
35020
|
+
const { resolveArticlePath: resolveArticlePath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
34724
35021
|
const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
|
|
34725
35022
|
const path = resolveArticlePath2(
|
|
34726
|
-
params.bundle_dir
|
|
35023
|
+
await resolveOkfBundleDir(params.bundle_dir),
|
|
34727
35024
|
params.file
|
|
34728
35025
|
);
|
|
34729
35026
|
const markdown = readFileSync20(path, "utf8");
|
|
@@ -34750,9 +35047,9 @@ function createMcpServer(db) {
|
|
|
34750
35047
|
},
|
|
34751
35048
|
wrapHandler(
|
|
34752
35049
|
async (params) => {
|
|
34753
|
-
const {
|
|
35050
|
+
const { upsertArticle: upsertArticle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
34754
35051
|
const result = upsertArticle2(
|
|
34755
|
-
params.bundle_dir
|
|
35052
|
+
await resolveOkfBundleDir(params.bundle_dir),
|
|
34756
35053
|
params.file,
|
|
34757
35054
|
params.markdown
|
|
34758
35055
|
);
|
|
@@ -34763,6 +35060,50 @@ function createMcpServer(db) {
|
|
|
34763
35060
|
}
|
|
34764
35061
|
)
|
|
34765
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
|
+
);
|
|
34766
35107
|
server.registerTool(
|
|
34767
35108
|
"zam_okf_read_citation",
|
|
34768
35109
|
{
|
|
@@ -34781,8 +35122,8 @@ function createMcpServer(db) {
|
|
|
34781
35122
|
wrapHandler(async (params) => {
|
|
34782
35123
|
const { readFileSync: readFileSync20 } = await import("fs");
|
|
34783
35124
|
const { relative: relative2, sep: sep2 } = await import("path");
|
|
34784
|
-
const {
|
|
34785
|
-
const bundleDir = params.bundle_dir
|
|
35125
|
+
const { findRepoRoot: findRepoRoot2, resolveCitationPath: resolveCitationPath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
35126
|
+
const bundleDir = await resolveOkfBundleDir(params.bundle_dir);
|
|
34786
35127
|
const path = resolveCitationPath2(bundleDir, params.target);
|
|
34787
35128
|
const content = readFileSync20(path, "utf8");
|
|
34788
35129
|
const root = findRepoRoot2(bundleDir);
|
|
@@ -34815,11 +35156,11 @@ function createMcpServer(db) {
|
|
|
34815
35156
|
getNativeClientInfo(),
|
|
34816
35157
|
{ clientSamplingCapable: getClientSamplingCapable() }
|
|
34817
35158
|
);
|
|
34818
|
-
await
|
|
34819
|
-
const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
35159
|
+
const { loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
|
|
34820
35160
|
const { resolve: resolve8 } = await import("path");
|
|
34821
|
-
const requestedDir = bundle_dir
|
|
35161
|
+
const requestedDir = await resolveOkfBundleDir(bundle_dir);
|
|
34822
35162
|
let resolvedBundleDir = resolve8(requestedDir);
|
|
35163
|
+
await publishUiIntent("okf", { bundle_dir: resolvedBundleDir });
|
|
34823
35164
|
let catalog = [];
|
|
34824
35165
|
let problems = [];
|
|
34825
35166
|
let log = "";
|