zam-core 0.7.1 → 0.7.2
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/index.js +912 -113
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +31 -2
- package/dist/index.js +80 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { readFileSync as readFileSync16 } from "fs";
|
|
5
5
|
import { dirname as dirname10, join as join25 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
7
|
-
import { Command as
|
|
7
|
+
import { Command as Command26 } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/cli/commands/agent.ts
|
|
10
10
|
import { existsSync as existsSync13 } from "fs";
|
|
@@ -543,6 +543,7 @@ var SCHEMA = `
|
|
|
543
543
|
CREATE TABLE IF NOT EXISTS tokens (
|
|
544
544
|
id TEXT PRIMARY KEY,
|
|
545
545
|
slug TEXT UNIQUE NOT NULL,
|
|
546
|
+
title TEXT NOT NULL DEFAULT '',
|
|
546
547
|
concept TEXT NOT NULL,
|
|
547
548
|
domain TEXT NOT NULL DEFAULT '',
|
|
548
549
|
bloom_level INTEGER NOT NULL DEFAULT 1 CHECK (bloom_level BETWEEN 1 AND 5),
|
|
@@ -691,6 +692,8 @@ CREATE INDEX IF NOT EXISTS idx_cards_token_user ON cards(token_id, user_id);
|
|
|
691
692
|
CREATE INDEX IF NOT EXISTS idx_review_logs_card ON review_logs(card_id);
|
|
692
693
|
CREATE INDEX IF NOT EXISTS idx_review_logs_user ON review_logs(user_id, reviewed_at);
|
|
693
694
|
CREATE INDEX IF NOT EXISTS idx_session_steps_session ON session_steps(session_id);
|
|
695
|
+
CREATE INDEX IF NOT EXISTS idx_tokens_title ON tokens(title);
|
|
696
|
+
CREATE INDEX IF NOT EXISTS idx_tokens_domain ON tokens(domain);
|
|
694
697
|
`;
|
|
695
698
|
|
|
696
699
|
// src/kernel/db/sync-adapter.ts
|
|
@@ -1024,6 +1027,15 @@ async function runMigrations(db) {
|
|
|
1024
1027
|
embedded_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1025
1028
|
)
|
|
1026
1029
|
`);
|
|
1030
|
+
if (!tokenCols.some((c) => c.name === "title")) {
|
|
1031
|
+
await db.exec(
|
|
1032
|
+
`ALTER TABLE tokens ADD COLUMN title TEXT NOT NULL DEFAULT ''`
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
await db.exec(`CREATE INDEX IF NOT EXISTS idx_tokens_title ON tokens(title)`);
|
|
1036
|
+
await db.exec(
|
|
1037
|
+
`CREATE INDEX IF NOT EXISTS idx_tokens_domain ON tokens(domain)`
|
|
1038
|
+
);
|
|
1027
1039
|
}
|
|
1028
1040
|
|
|
1029
1041
|
// src/kernel/db/snapshot.ts
|
|
@@ -1516,12 +1528,14 @@ async function createToken(db, input8) {
|
|
|
1516
1528
|
if (bloom < 1 || bloom > 5) {
|
|
1517
1529
|
throw new Error(`bloom_level must be between 1 and 5, got ${bloom}`);
|
|
1518
1530
|
}
|
|
1531
|
+
const title = input8.title ?? "";
|
|
1519
1532
|
await db.prepare(`
|
|
1520
|
-
INSERT INTO tokens (id, slug, concept, domain, bloom_level, context, symbiosis_mode, source_link, question, provider, topic_id, created_at, updated_at)
|
|
1521
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1533
|
+
INSERT INTO tokens (id, slug, title, concept, domain, bloom_level, context, symbiosis_mode, source_link, question, provider, topic_id, created_at, updated_at)
|
|
1534
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1522
1535
|
`).run(
|
|
1523
1536
|
id,
|
|
1524
1537
|
input8.slug,
|
|
1538
|
+
title,
|
|
1525
1539
|
input8.concept,
|
|
1526
1540
|
input8.domain ?? "",
|
|
1527
1541
|
bloom,
|
|
@@ -1564,6 +1578,10 @@ async function updateToken(db, slug, updates) {
|
|
|
1564
1578
|
}
|
|
1565
1579
|
const fields = [];
|
|
1566
1580
|
const values = [];
|
|
1581
|
+
if (updates.title !== void 0) {
|
|
1582
|
+
fields.push("title = ?");
|
|
1583
|
+
values.push(updates.title ?? "");
|
|
1584
|
+
}
|
|
1567
1585
|
if (updates.concept !== void 0) {
|
|
1568
1586
|
fields.push("concept = ?");
|
|
1569
1587
|
values.push(updates.concept);
|
|
@@ -1669,13 +1687,14 @@ async function deleteToken(db, slug) {
|
|
|
1669
1687
|
await db.transaction(async (tx) => {
|
|
1670
1688
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1671
1689
|
const skillRows = await tx.prepare("SELECT id, token_slugs FROM agent_skills").all();
|
|
1690
|
+
const skillUpdateStmt = tx.prepare(
|
|
1691
|
+
"UPDATE agent_skills SET token_slugs = ?, updated_at = ? WHERE id = ?"
|
|
1692
|
+
);
|
|
1672
1693
|
for (const row of skillRows) {
|
|
1673
1694
|
const tokenSlugs = JSON.parse(row.token_slugs);
|
|
1674
1695
|
const filtered = tokenSlugs.filter((tokenSlug) => tokenSlug !== slug);
|
|
1675
1696
|
if (filtered.length !== tokenSlugs.length) {
|
|
1676
|
-
await
|
|
1677
|
-
"UPDATE agent_skills SET token_slugs = ?, updated_at = ? WHERE id = ?"
|
|
1678
|
-
).run(JSON.stringify(filtered), now, row.id);
|
|
1697
|
+
await skillUpdateStmt.run(JSON.stringify(filtered), now, row.id);
|
|
1679
1698
|
}
|
|
1680
1699
|
}
|
|
1681
1700
|
await tx.prepare("DELETE FROM tokens WHERE id = ?").run(token.id);
|
|
@@ -1689,10 +1708,16 @@ async function findTokens(db, query) {
|
|
|
1689
1708
|
const shortTerms = searchTokens.filter((t2) => t2.length <= 2);
|
|
1690
1709
|
const longTerms = searchTokens.filter((t2) => t2.length > 2);
|
|
1691
1710
|
const scoreMap = /* @__PURE__ */ new Map();
|
|
1692
|
-
const likeSQL = `SELECT * FROM tokens WHERE deprecated_at IS NULL AND (lower(slug) LIKE ? OR lower(concept) LIKE ? OR lower(domain) LIKE ?)`;
|
|
1711
|
+
const likeSQL = `SELECT * FROM tokens WHERE deprecated_at IS NULL AND (lower(slug) LIKE ? OR lower(title) LIKE ? OR lower(concept) LIKE ? OR lower(domain) LIKE ?)`;
|
|
1712
|
+
const likeStmt = db.prepare(likeSQL);
|
|
1693
1713
|
for (const term of longTerms) {
|
|
1694
1714
|
const pattern = `%${term}%`;
|
|
1695
|
-
const rows = await
|
|
1715
|
+
const rows = await likeStmt.all(
|
|
1716
|
+
pattern,
|
|
1717
|
+
pattern,
|
|
1718
|
+
pattern,
|
|
1719
|
+
pattern
|
|
1720
|
+
);
|
|
1696
1721
|
for (const row of rows) {
|
|
1697
1722
|
const entry = scoreMap.get(row.id);
|
|
1698
1723
|
if (entry) {
|
|
@@ -1705,7 +1730,7 @@ async function findTokens(db, query) {
|
|
|
1705
1730
|
if (shortTerms.length > 0 || longTerms.length === 0) {
|
|
1706
1731
|
const allTokens = await db.prepare("SELECT * FROM tokens WHERE deprecated_at IS NULL").all();
|
|
1707
1732
|
for (const token of allTokens) {
|
|
1708
|
-
const words = `${token.slug} ${token.concept} ${token.domain}`.toLowerCase().split(/[\s,.\-_/\\:;!?()[\]{}]+/).filter(Boolean);
|
|
1733
|
+
const words = `${token.slug} ${token.title} ${token.concept} ${token.domain}`.toLowerCase().split(/[\s,.\-_/\\:;!?()[\]{}]+/).filter(Boolean);
|
|
1709
1734
|
let matchCount = 0;
|
|
1710
1735
|
for (const term of shortTerms.length > 0 ? shortTerms : searchTokens) {
|
|
1711
1736
|
for (const w of words) {
|
|
@@ -1739,6 +1764,11 @@ async function listTokens(db, options) {
|
|
|
1739
1764
|
tokens = await db.prepare(
|
|
1740
1765
|
"SELECT * FROM tokens WHERE domain = ? AND deprecated_at IS NULL ORDER BY bloom_level, slug"
|
|
1741
1766
|
).all(options.domain);
|
|
1767
|
+
} else if (options?.domainPrefix) {
|
|
1768
|
+
const prefix = options.domainPrefix;
|
|
1769
|
+
tokens = await db.prepare(
|
|
1770
|
+
`SELECT * FROM tokens WHERE (domain = ? OR domain LIKE ?) AND deprecated_at IS NULL ORDER BY bloom_level, slug`
|
|
1771
|
+
).all(prefix, `${prefix}/%`);
|
|
1742
1772
|
} else {
|
|
1743
1773
|
tokens = await db.prepare(
|
|
1744
1774
|
"SELECT * FROM tokens WHERE deprecated_at IS NULL ORDER BY bloom_level, domain, slug"
|
|
@@ -1750,7 +1780,20 @@ async function listTokens(db, options) {
|
|
|
1750
1780
|
return tokens;
|
|
1751
1781
|
}
|
|
1752
1782
|
function slugify(text) {
|
|
1753
|
-
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
1783
|
+
return text.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
1784
|
+
}
|
|
1785
|
+
function getShortSlug(slug, domainPrefix) {
|
|
1786
|
+
if (domainPrefix) {
|
|
1787
|
+
const folded = domainPrefix.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1788
|
+
if (folded && slug.startsWith(`${folded}-`)) {
|
|
1789
|
+
return slug.substring(folded.length + 1);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
return slug;
|
|
1793
|
+
}
|
|
1794
|
+
function getDisplayTitle(t2, activeDomainScope) {
|
|
1795
|
+
if (t2.title?.trim()) return t2.title.trim();
|
|
1796
|
+
return getShortSlug(t2.slug, activeDomainScope);
|
|
1754
1797
|
}
|
|
1755
1798
|
async function generateTokenSlug(db, domain, concept, question) {
|
|
1756
1799
|
const baseText = question && question.trim().length > 0 ? question : concept;
|
|
@@ -1780,6 +1823,7 @@ async function listPersonalCards(db, userId, options) {
|
|
|
1780
1823
|
SELECT
|
|
1781
1824
|
t.id AS tokenId,
|
|
1782
1825
|
t.slug,
|
|
1826
|
+
t.title,
|
|
1783
1827
|
t.concept,
|
|
1784
1828
|
t.domain,
|
|
1785
1829
|
t.bloom_level AS bloomLevel,
|
|
@@ -1881,6 +1925,7 @@ async function importCurriculumCards(db, userId, cards) {
|
|
|
1881
1925
|
);
|
|
1882
1926
|
token = await createToken(tx, {
|
|
1883
1927
|
slug: finalSlug,
|
|
1928
|
+
title: card.title,
|
|
1884
1929
|
concept: card.concept,
|
|
1885
1930
|
domain: card.domain,
|
|
1886
1931
|
bloom_level: bloom,
|
|
@@ -1998,23 +2043,28 @@ async function confirmCardSplit(db, userId, originalSlug, action, originalQuesti
|
|
|
1998
2043
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
1999
2044
|
originalToken.id
|
|
2000
2045
|
);
|
|
2046
|
+
const insertPrereqStmt = tx.prepare(
|
|
2047
|
+
"INSERT OR IGNORE INTO prerequisites (token_id, requires_id) VALUES (?, ?)"
|
|
2048
|
+
);
|
|
2001
2049
|
for (const propToken of proposalTokens) {
|
|
2002
|
-
await
|
|
2003
|
-
"INSERT OR IGNORE INTO prerequisites (token_id, requires_id) VALUES (?, ?)"
|
|
2004
|
-
).run(originalToken.id, propToken.id);
|
|
2050
|
+
await insertPrereqStmt.run(originalToken.id, propToken.id);
|
|
2005
2051
|
}
|
|
2006
2052
|
await tx.prepare(
|
|
2007
2053
|
"UPDATE cards SET blocked = 1 WHERE token_id = ? AND user_id = ?"
|
|
2008
2054
|
).run(originalToken.id, userId);
|
|
2055
|
+
const checkPrereqsStmt = tx.prepare(
|
|
2056
|
+
"SELECT COUNT(*) as n FROM prerequisites WHERE token_id = ?"
|
|
2057
|
+
);
|
|
2058
|
+
const unblockCardStmt = tx.prepare(
|
|
2059
|
+
"UPDATE cards SET blocked = 0, due_at = ? WHERE id = ?"
|
|
2060
|
+
);
|
|
2009
2061
|
for (const propToken of proposalTokens) {
|
|
2010
2062
|
const card = await ensureCard(tx, propToken.id, userId);
|
|
2011
2063
|
if (card.blocked === 1) {
|
|
2012
|
-
const prereqOfPrereq = await
|
|
2013
|
-
"SELECT COUNT(*) as n FROM prerequisites WHERE token_id = ?"
|
|
2014
|
-
).get(propToken.id);
|
|
2064
|
+
const prereqOfPrereq = await checkPrereqsStmt.get(propToken.id);
|
|
2015
2065
|
if (prereqOfPrereq.n === 0) {
|
|
2016
2066
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2017
|
-
await
|
|
2067
|
+
await unblockCardStmt.run(now, card.id);
|
|
2018
2068
|
}
|
|
2019
2069
|
}
|
|
2020
2070
|
}
|
|
@@ -2085,6 +2135,7 @@ async function confirmFoundations(db, userId, originalSlug, proposals) {
|
|
|
2085
2135
|
);
|
|
2086
2136
|
token = await createToken(tx, {
|
|
2087
2137
|
slug: finalSlug,
|
|
2138
|
+
title: card.title,
|
|
2088
2139
|
concept: card.concept,
|
|
2089
2140
|
domain: card.domain,
|
|
2090
2141
|
bloom_level: bloom,
|
|
@@ -2151,6 +2202,7 @@ async function confirmSourceImport(db, userId, sourceId, proposals) {
|
|
|
2151
2202
|
}
|
|
2152
2203
|
token = await createToken(tx, {
|
|
2153
2204
|
slug: finalSlug,
|
|
2205
|
+
title: card.title,
|
|
2154
2206
|
concept: card.concept,
|
|
2155
2207
|
domain: card.domain,
|
|
2156
2208
|
bloom_level: bloom,
|
|
@@ -2253,7 +2305,7 @@ async function addPrerequisite(db, tokenId, requiresId) {
|
|
|
2253
2305
|
}
|
|
2254
2306
|
async function getPrerequisites(db, tokenId) {
|
|
2255
2307
|
return await db.prepare(
|
|
2256
|
-
`SELECT p.token_id, p.requires_id, t.slug, t.concept, t.domain, t.bloom_level
|
|
2308
|
+
`SELECT p.token_id, p.requires_id, t.slug, t.title, t.concept, t.domain, t.bloom_level
|
|
2257
2309
|
FROM prerequisites p
|
|
2258
2310
|
JOIN tokens t ON t.id = p.requires_id
|
|
2259
2311
|
WHERE p.token_id = ?`
|
|
@@ -2261,7 +2313,7 @@ async function getPrerequisites(db, tokenId) {
|
|
|
2261
2313
|
}
|
|
2262
2314
|
async function getDependents(db, tokenId) {
|
|
2263
2315
|
return await db.prepare(
|
|
2264
|
-
`SELECT p.token_id, p.requires_id, t.slug, t.concept, t.domain, t.bloom_level
|
|
2316
|
+
`SELECT p.token_id, p.requires_id, t.slug, t.title, t.concept, t.domain, t.bloom_level
|
|
2265
2317
|
FROM prerequisites p
|
|
2266
2318
|
JOIN tokens t ON t.id = p.token_id
|
|
2267
2319
|
WHERE p.requires_id = ?`
|
|
@@ -2292,6 +2344,7 @@ async function getTokenNeighborhood(db, tokenId, userId) {
|
|
|
2292
2344
|
const toNode = (t2, card) => ({
|
|
2293
2345
|
id: t2.id,
|
|
2294
2346
|
slug: t2.slug,
|
|
2347
|
+
title: t2.title ?? t2.slug,
|
|
2295
2348
|
concept: t2.concept,
|
|
2296
2349
|
domain: t2.domain,
|
|
2297
2350
|
bloom_level: t2.bloom_level,
|
|
@@ -2311,6 +2364,7 @@ async function getTokenNeighborhood(db, tokenId, userId) {
|
|
|
2311
2364
|
{
|
|
2312
2365
|
id: p.requires_id,
|
|
2313
2366
|
slug: p.slug,
|
|
2367
|
+
title: p.title,
|
|
2314
2368
|
concept: p.concept,
|
|
2315
2369
|
domain: p.domain,
|
|
2316
2370
|
bloom_level: p.bloom_level
|
|
@@ -2323,6 +2377,7 @@ async function getTokenNeighborhood(db, tokenId, userId) {
|
|
|
2323
2377
|
{
|
|
2324
2378
|
id: d.token_id,
|
|
2325
2379
|
slug: d.slug,
|
|
2380
|
+
title: d.title,
|
|
2326
2381
|
concept: d.concept,
|
|
2327
2382
|
domain: d.domain,
|
|
2328
2383
|
bloom_level: d.bloom_level
|
|
@@ -2437,7 +2492,8 @@ import { createHash as createHash2 } from "crypto";
|
|
|
2437
2492
|
function embeddingContentForToken(t2) {
|
|
2438
2493
|
return `${t2.concept}
|
|
2439
2494
|
${t2.question ?? ""}
|
|
2440
|
-
${t2.domain}
|
|
2495
|
+
${t2.domain}
|
|
2496
|
+
${t2.title ?? ""}`;
|
|
2441
2497
|
}
|
|
2442
2498
|
function computeContentHash(text) {
|
|
2443
2499
|
return createHash2("sha256").update(text, "utf8").digest("hex");
|
|
@@ -4601,6 +4657,7 @@ async function buildReviewQueue(db, options) {
|
|
|
4601
4657
|
c.id AS card_id,
|
|
4602
4658
|
c.token_id AS token_id,
|
|
4603
4659
|
t.slug AS slug,
|
|
4660
|
+
t.title AS title,
|
|
4604
4661
|
t.concept AS concept,
|
|
4605
4662
|
t.domain AS domain,
|
|
4606
4663
|
t.bloom_level AS bloom_level,
|
|
@@ -4622,6 +4679,7 @@ async function buildReviewQueue(db, options) {
|
|
|
4622
4679
|
c.id AS card_id,
|
|
4623
4680
|
c.token_id AS token_id,
|
|
4624
4681
|
t.slug AS slug,
|
|
4682
|
+
t.title AS title,
|
|
4625
4683
|
t.concept AS concept,
|
|
4626
4684
|
t.domain AS domain,
|
|
4627
4685
|
t.bloom_level AS bloom_level,
|
|
@@ -4681,6 +4739,7 @@ function rowToItem(row) {
|
|
|
4681
4739
|
cardId: row.card_id,
|
|
4682
4740
|
tokenId: row.token_id,
|
|
4683
4741
|
slug: row.slug,
|
|
4742
|
+
title: getDisplayTitle(row),
|
|
4684
4743
|
concept: row.concept,
|
|
4685
4744
|
domain: row.domain,
|
|
4686
4745
|
bloomLevel: row.bloom_level,
|
|
@@ -6640,6 +6699,11 @@ function parseGeneratedCardArray(responseText, label, limits) {
|
|
|
6640
6699
|
);
|
|
6641
6700
|
}
|
|
6642
6701
|
}
|
|
6702
|
+
if (card.title !== void 0 && (typeof card.title !== "string" || card.title.trim().length === 0)) {
|
|
6703
|
+
throw new Error(
|
|
6704
|
+
`Invalid ${label} card at index ${index}: title must be a non-empty string if provided`
|
|
6705
|
+
);
|
|
6706
|
+
}
|
|
6643
6707
|
if (typeof card.bloom_level !== "number" || !Number.isInteger(card.bloom_level) || card.bloom_level < 1 || card.bloom_level > 5) {
|
|
6644
6708
|
throw new Error(
|
|
6645
6709
|
`Invalid ${label} card at index ${index}: bloom_level must be an integer from 1 to 5`
|
|
@@ -6653,6 +6717,7 @@ function parseGeneratedCardArray(responseText, label, limits) {
|
|
|
6653
6717
|
return {
|
|
6654
6718
|
question: card.question,
|
|
6655
6719
|
concept: card.concept,
|
|
6720
|
+
title: card.title ? card.title : void 0,
|
|
6656
6721
|
domain: card.domain,
|
|
6657
6722
|
context: card.context,
|
|
6658
6723
|
bloom_level: card.bloom_level,
|
|
@@ -6703,10 +6768,11 @@ Your task is to analyze curriculum objectives, syllabus requirements, or textboo
|
|
|
6703
6768
|
For each extracted learning card, you MUST generate:
|
|
6704
6769
|
1. "question": A clear, concise active-recall question testing the concept. The question must not reveal the answer itself.
|
|
6705
6770
|
2. "concept": The reference answer, core fact, or target conceptual explanation.
|
|
6706
|
-
3. "
|
|
6707
|
-
4. "
|
|
6708
|
-
5. "
|
|
6709
|
-
6. "
|
|
6771
|
+
3. "title": A short, human-friendly title for the knowledge graph node. Concise name for the concept. No domain prefix. Human-readable, natural language. Support Unicode (umlauts, Chinese, Japanese, Arabic, etc.). Name the core idea as well as possible.
|
|
6772
|
+
4. "domain": The category of the card. Use "${targetCategory}" as the default, but you may refine it if a specific sub-topic is evident.
|
|
6773
|
+
5. "context": The exact sentence or short excerpt from the source text that this card is based on.
|
|
6774
|
+
6. "bloom_level": Initial Bloom cognitive level (1 = Remember, 2 = Understand, 3 = Apply, 4 = Analyze, 5 = Synthesize).
|
|
6775
|
+
7. "symbiosis_mode": ZAM agent symbiosis level: "shadowing" (reading/monitoring), "copilot" (interactive helper), or "autonomy" (autonomous tasks).
|
|
6710
6776
|
|
|
6711
6777
|
Guidelines:
|
|
6712
6778
|
- Break complex requirements into multiple separate, atomic cards.
|
|
@@ -7635,6 +7701,114 @@ async function ensureHighQualityQuestion(db, token) {
|
|
|
7635
7701
|
}
|
|
7636
7702
|
return null;
|
|
7637
7703
|
}
|
|
7704
|
+
async function generateTitleViaLLM(db, input8, opts = {}) {
|
|
7705
|
+
const cfg = await getProviderForRole(db, "text");
|
|
7706
|
+
const endpoint = await resolveUsableTextEndpoint(db);
|
|
7707
|
+
const systemPrompt = `You are an expert at naming knowledge items for a personal knowledge graph.
|
|
7708
|
+
Your task: given a knowledge token's concept (the full reference answer), question, domain, and optional context, produce a short, descriptive, human-friendly TITLE.
|
|
7709
|
+
|
|
7710
|
+
Strict rules from the project ADR:
|
|
7711
|
+
- Concise name for the concept (\u2264 80 chars ideal).
|
|
7712
|
+
- Thoughtful, memorable name \u2014 NOT a definition or the first sentence of the concept.
|
|
7713
|
+
- NEVER echo the domain name (e.g. do not say "Node Drain" if domain is "axon-ivy"; just "Node Drain Protection").
|
|
7714
|
+
- Prefer a name over spoiling the full concept.
|
|
7715
|
+
- Support Unicode (umlauts, etc.).
|
|
7716
|
+
- Output the title in the same language as the concept/content (e.g., German if the concept is German, English if the concept is English).
|
|
7717
|
+
- Output ONLY the raw title text. No preamble, quotes, explanations, or markdown.
|
|
7718
|
+
|
|
7719
|
+
Example good titles:
|
|
7720
|
+
- "RAG Retrieval Quality Evaluation" (for RAG failure modes focused on retrieval)
|
|
7721
|
+
- "Pythagorean Theorem Converse Proof"
|
|
7722
|
+
- "macOS Applications Directory Layout"
|
|
7723
|
+
- "Chronotype Sleep Preference"`;
|
|
7724
|
+
const userPrompt = `Domain: ${input8.domain}
|
|
7725
|
+
Slug: ${input8.slug}
|
|
7726
|
+
Question: ${input8.question || "(none)"}
|
|
7727
|
+
Concept: ${input8.concept}
|
|
7728
|
+
Context: ${input8.context || "(none)"}
|
|
7729
|
+
|
|
7730
|
+
Title:`;
|
|
7731
|
+
const url = `${endpoint.url}/chat/completions`;
|
|
7732
|
+
const apiKey = endpoint.apiKey;
|
|
7733
|
+
const model = endpoint.model;
|
|
7734
|
+
try {
|
|
7735
|
+
const res = await fetchWithInteractiveTimeout(url, {
|
|
7736
|
+
method: "POST",
|
|
7737
|
+
headers: {
|
|
7738
|
+
"Content-Type": "application/json",
|
|
7739
|
+
Authorization: `Bearer ${apiKey}`
|
|
7740
|
+
},
|
|
7741
|
+
body: JSON.stringify({
|
|
7742
|
+
model,
|
|
7743
|
+
messages: [
|
|
7744
|
+
{ role: "system", content: systemPrompt },
|
|
7745
|
+
{ role: "user", content: userPrompt }
|
|
7746
|
+
],
|
|
7747
|
+
temperature: 0.2,
|
|
7748
|
+
max_tokens: 100
|
|
7749
|
+
}),
|
|
7750
|
+
locale: cfg.locale,
|
|
7751
|
+
timeoutMs: opts.timeoutMs,
|
|
7752
|
+
hardTimeoutMs: opts.timeoutMs
|
|
7753
|
+
});
|
|
7754
|
+
const text = await readChatContent(res, "title generation");
|
|
7755
|
+
return {
|
|
7756
|
+
text: text.trim(),
|
|
7757
|
+
model,
|
|
7758
|
+
providerName: endpoint.providerName
|
|
7759
|
+
};
|
|
7760
|
+
} catch (_e) {
|
|
7761
|
+
return {
|
|
7762
|
+
text: input8.concept.split(/[.;]/)[0].trim().substring(0, 80),
|
|
7763
|
+
model: "fallback",
|
|
7764
|
+
providerName: "heuristic"
|
|
7765
|
+
};
|
|
7766
|
+
}
|
|
7767
|
+
}
|
|
7768
|
+
async function repairUmlautsViaLLM(db, input8, opts = {}) {
|
|
7769
|
+
const cfg = await getProviderForRole(db, "text");
|
|
7770
|
+
const endpoint = await resolveUsableTextEndpoint(db);
|
|
7771
|
+
const systemPrompt = `You are an expert editor specializing in German language orthography.
|
|
7772
|
+
Your task: given a text that may contain legacy ASCII-folded German umlauts (like 'ue' instead of '\xFC', 'ae' instead of '\xE4', 'oe' instead of '\xF6', 'Ue' instead of '\xDC', etc.), repair them back to proper German umlauts (\xE4, \xF6, \xFC, \xC4, \xD6, \xDC, \xDF) where appropriate.
|
|
7773
|
+
|
|
7774
|
+
Strict rules:
|
|
7775
|
+
- ONLY change character sequences that are clearly meant to be German umlauts (e.g. change "Ueber" to "\xDCber", "fuer" to "f\xFCr", "moeglich" to "m\xF6glich").
|
|
7776
|
+
- DO NOT change words that are correct in their current form (e.g. do NOT change "nahe" to "n\xE4he", do NOT change English words or technical terms, do NOT change names like "Ueda").
|
|
7777
|
+
- Output ONLY the repaired text. No preamble, no explanation, no markdown formatting.`;
|
|
7778
|
+
const userPrompt = `Text to repair:
|
|
7779
|
+
"""
|
|
7780
|
+
${input8.text}
|
|
7781
|
+
"""
|
|
7782
|
+
|
|
7783
|
+
Repaired text:`;
|
|
7784
|
+
const url = `${endpoint.url}/chat/completions`;
|
|
7785
|
+
const apiKey = endpoint.apiKey;
|
|
7786
|
+
const model = endpoint.model;
|
|
7787
|
+
try {
|
|
7788
|
+
const res = await fetchWithInteractiveTimeout(url, {
|
|
7789
|
+
method: "POST",
|
|
7790
|
+
headers: {
|
|
7791
|
+
"Content-Type": "application/json",
|
|
7792
|
+
Authorization: `Bearer ${apiKey}`
|
|
7793
|
+
},
|
|
7794
|
+
body: JSON.stringify({
|
|
7795
|
+
model,
|
|
7796
|
+
messages: [
|
|
7797
|
+
{ role: "system", content: systemPrompt },
|
|
7798
|
+
{ role: "user", content: userPrompt }
|
|
7799
|
+
],
|
|
7800
|
+
temperature: 0.1
|
|
7801
|
+
}),
|
|
7802
|
+
locale: cfg.locale,
|
|
7803
|
+
timeoutMs: opts.timeoutMs,
|
|
7804
|
+
hardTimeoutMs: opts.timeoutMs
|
|
7805
|
+
});
|
|
7806
|
+
const text = await readChatContent(res, "umlaut repair");
|
|
7807
|
+
return text.trim();
|
|
7808
|
+
} catch (_e) {
|
|
7809
|
+
return input8.text;
|
|
7810
|
+
}
|
|
7811
|
+
}
|
|
7638
7812
|
|
|
7639
7813
|
// src/cli/adapters/source-reader.ts
|
|
7640
7814
|
var MAX_SOURCE_BYTES = 2 * 1024 * 1024;
|
|
@@ -7946,7 +8120,7 @@ function cleanHtmlText(html) {
|
|
|
7946
8120
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
7947
8121
|
}
|
|
7948
8122
|
function normalizeForComparison(str) {
|
|
7949
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8123
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
7950
8124
|
}
|
|
7951
8125
|
|
|
7952
8126
|
// src/cli/curriculum/providers/bildungsplan-bw/manifest.ts
|
|
@@ -8117,7 +8291,7 @@ function cleanHtmlText2(html) {
|
|
|
8117
8291
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
8118
8292
|
}
|
|
8119
8293
|
function normalizeForComparison2(str) {
|
|
8120
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8294
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8121
8295
|
}
|
|
8122
8296
|
|
|
8123
8297
|
// src/cli/curriculum/providers/bildungsplan-hamburg/manifest.ts
|
|
@@ -8275,7 +8449,7 @@ function cleanHtmlText3(html) {
|
|
|
8275
8449
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
8276
8450
|
}
|
|
8277
8451
|
function normalizeForComparison3(str) {
|
|
8278
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8452
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8279
8453
|
}
|
|
8280
8454
|
|
|
8281
8455
|
// src/cli/curriculum/providers/fachanforderungen-sh/manifest.ts
|
|
@@ -8432,7 +8606,7 @@ function cleanHtmlText4(html) {
|
|
|
8432
8606
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
8433
8607
|
}
|
|
8434
8608
|
function normalizeForComparison4(str) {
|
|
8435
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8609
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8436
8610
|
}
|
|
8437
8611
|
|
|
8438
8612
|
// src/cli/curriculum/providers/kerncurriculum-hessen/manifest.ts
|
|
@@ -8626,7 +8800,7 @@ function cleanHtmlText5(html) {
|
|
|
8626
8800
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
8627
8801
|
}
|
|
8628
8802
|
function normalizeForComparison5(str) {
|
|
8629
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8803
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8630
8804
|
}
|
|
8631
8805
|
|
|
8632
8806
|
// src/cli/curriculum/providers/kerncurriculum-niedersachsen/manifest.ts
|
|
@@ -8798,7 +8972,7 @@ function cleanHtmlText6(html) {
|
|
|
8798
8972
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
8799
8973
|
}
|
|
8800
8974
|
function normalizeForComparison6(str) {
|
|
8801
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8975
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
8802
8976
|
}
|
|
8803
8977
|
|
|
8804
8978
|
// src/cli/curriculum/providers/kernlehrplan-nrw/manifest.ts
|
|
@@ -9013,7 +9187,7 @@ function cleanHtmlText7(html) {
|
|
|
9013
9187
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
9014
9188
|
}
|
|
9015
9189
|
function normalizeForComparison7(str) {
|
|
9016
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9190
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9017
9191
|
}
|
|
9018
9192
|
|
|
9019
9193
|
// src/cli/curriculum/providers/lehrplaene-rp/manifest.ts
|
|
@@ -9170,7 +9344,7 @@ function cleanHtmlText8(html) {
|
|
|
9170
9344
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
9171
9345
|
}
|
|
9172
9346
|
function normalizeForComparison8(str) {
|
|
9173
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9347
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9174
9348
|
}
|
|
9175
9349
|
|
|
9176
9350
|
// src/cli/curriculum/providers/lehrplan-saarland/manifest.ts
|
|
@@ -9327,7 +9501,7 @@ function cleanHtmlText9(html) {
|
|
|
9327
9501
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
9328
9502
|
}
|
|
9329
9503
|
function normalizeForComparison9(str) {
|
|
9330
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9504
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9331
9505
|
}
|
|
9332
9506
|
|
|
9333
9507
|
// src/cli/curriculum/providers/lehrplan-sachsen/manifest.ts
|
|
@@ -9512,7 +9686,7 @@ function cleanHtmlText10(html) {
|
|
|
9512
9686
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
9513
9687
|
}
|
|
9514
9688
|
function normalizeForComparison10(str) {
|
|
9515
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9689
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9516
9690
|
}
|
|
9517
9691
|
|
|
9518
9692
|
// src/cli/curriculum/providers/lehrplan-thueringen/manifest.ts
|
|
@@ -9667,7 +9841,7 @@ function cleanHtmlText11(html) {
|
|
|
9667
9841
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
9668
9842
|
}
|
|
9669
9843
|
function normalizeForComparison11(str) {
|
|
9670
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9844
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9671
9845
|
}
|
|
9672
9846
|
|
|
9673
9847
|
// src/cli/curriculum/providers/lehrplanplus-bayern/manifest.ts
|
|
@@ -9911,7 +10085,7 @@ function cleanHtmlText12(html) {
|
|
|
9911
10085
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
9912
10086
|
}
|
|
9913
10087
|
function normalizeForComparison12(str) {
|
|
9914
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
10088
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
9915
10089
|
}
|
|
9916
10090
|
|
|
9917
10091
|
// src/cli/curriculum/providers/rahmenlehrplan-berlin-brandenburg/manifest.ts
|
|
@@ -10096,7 +10270,7 @@ function cleanHtmlText13(html) {
|
|
|
10096
10270
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
10097
10271
|
}
|
|
10098
10272
|
function normalizeForComparison13(str) {
|
|
10099
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
10273
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
10100
10274
|
}
|
|
10101
10275
|
|
|
10102
10276
|
// src/cli/curriculum/providers/rahmenplan-mv/manifest.ts
|
|
@@ -10253,7 +10427,7 @@ function cleanHtmlText14(html) {
|
|
|
10253
10427
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
10254
10428
|
}
|
|
10255
10429
|
function normalizeForComparison14(str) {
|
|
10256
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
10430
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
10257
10431
|
}
|
|
10258
10432
|
|
|
10259
10433
|
// src/cli/curriculum/providers/rahmenrichtlinien-st/manifest.ts
|
|
@@ -10408,7 +10582,7 @@ function cleanHtmlText15(html) {
|
|
|
10408
10582
|
return text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
|
|
10409
10583
|
}
|
|
10410
10584
|
function normalizeForComparison15(str) {
|
|
10411
|
-
return str.toLowerCase().replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
10585
|
+
return str.toLowerCase().replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss").replace(/&[a-z0-9#]+;/gi, "").replace(/[^a-z0-9]/gi, "");
|
|
10412
10586
|
}
|
|
10413
10587
|
|
|
10414
10588
|
// src/cli/curriculum/registry.ts
|
|
@@ -10606,7 +10780,8 @@ async function findPossibleDuplicates(db, candidate, embed = embedQuery) {
|
|
|
10606
10780
|
const queryText = embeddingContentForToken({
|
|
10607
10781
|
concept: candidate.concept,
|
|
10608
10782
|
question: candidate.question ?? null,
|
|
10609
|
-
domain: candidate.domain ?? ""
|
|
10783
|
+
domain: candidate.domain ?? "",
|
|
10784
|
+
title: candidate.title ?? null
|
|
10610
10785
|
});
|
|
10611
10786
|
const q2 = await embed(db, queryText);
|
|
10612
10787
|
if (!q2) {
|
|
@@ -10637,6 +10812,7 @@ async function findPossibleDuplicates(db, candidate, embed = embedQuery) {
|
|
|
10637
10812
|
results.push({
|
|
10638
10813
|
slug: hit.slug,
|
|
10639
10814
|
concept: hit.concept,
|
|
10815
|
+
title: hit.title,
|
|
10640
10816
|
similarity: hit.similarity
|
|
10641
10817
|
});
|
|
10642
10818
|
}
|
|
@@ -12400,10 +12576,12 @@ bridgeCommand.command("add-token").description("Create a token + card from JSON
|
|
|
12400
12576
|
const possibleDuplicates = await findPossibleDuplicates(db, {
|
|
12401
12577
|
concept: data?.concept,
|
|
12402
12578
|
question: data?.question ?? null,
|
|
12403
|
-
domain: data?.domain
|
|
12579
|
+
domain: data?.domain,
|
|
12580
|
+
title: data?.title ?? null
|
|
12404
12581
|
});
|
|
12405
12582
|
const token = await createToken(db, {
|
|
12406
12583
|
slug: data?.slug,
|
|
12584
|
+
title: data?.title,
|
|
12407
12585
|
concept: data?.concept,
|
|
12408
12586
|
domain: data?.domain,
|
|
12409
12587
|
bloom_level: data?.bloom_level ?? 1,
|
|
@@ -12483,6 +12661,8 @@ bridgeCommand.command("relevant-tokens").description("Find tokens relevant to a
|
|
|
12483
12661
|
const card = await getCard(db, t2.id, userId);
|
|
12484
12662
|
tokens.push({
|
|
12485
12663
|
slug: t2.slug,
|
|
12664
|
+
title: t2.title,
|
|
12665
|
+
display_title: getDisplayTitle(t2),
|
|
12486
12666
|
concept: t2.concept,
|
|
12487
12667
|
domain: t2.domain,
|
|
12488
12668
|
bloom_level: t2.bloom_level,
|
|
@@ -12545,7 +12725,8 @@ bridgeCommand.command("suggest-foundations").description("Propose existing token
|
|
|
12545
12725
|
queryText = embeddingContentForToken({
|
|
12546
12726
|
concept: data.concept,
|
|
12547
12727
|
question: typeof data.question === "string" ? data.question : null,
|
|
12548
|
-
domain: typeof data.domain === "string" ? data.domain : ""
|
|
12728
|
+
domain: typeof data.domain === "string" ? data.domain : "",
|
|
12729
|
+
title: typeof data.title === "string" ? data.title : null
|
|
12549
12730
|
});
|
|
12550
12731
|
if (data.bloom_level !== void 0) {
|
|
12551
12732
|
if (typeof data.bloom_level !== "number" || !Number.isInteger(data.bloom_level) || data.bloom_level < 1 || data.bloom_level > 5) {
|
|
@@ -13777,12 +13958,18 @@ bridgeCommand.command("list-tokens").description(
|
|
|
13777
13958
|
).option(
|
|
13778
13959
|
"--user <id>",
|
|
13779
13960
|
"User ID (default: whoami) \u2014 when provided, includes personal card info"
|
|
13780
|
-
).option("--domain <domain>", "Filter by domain").
|
|
13961
|
+
).option("--domain <domain>", "Filter by exact domain").option(
|
|
13962
|
+
"--domain-prefix <prefix>",
|
|
13963
|
+
"Filter by domain prefix (e.g. docuware-cops) \u2014 uses / separator for hierarchy"
|
|
13964
|
+
).action(async (opts) => {
|
|
13781
13965
|
await withDb2(async (db) => {
|
|
13782
13966
|
const userId = opts.user ? await resolveUser(opts, db, { json: true }) : void 0;
|
|
13967
|
+
const listOpts = {};
|
|
13968
|
+
if (opts.domain) listOpts.domain = opts.domain;
|
|
13969
|
+
if (opts.domainPrefix) listOpts.domainPrefix = opts.domainPrefix;
|
|
13783
13970
|
const tokens = await listTokens(
|
|
13784
13971
|
db,
|
|
13785
|
-
|
|
13972
|
+
Object.keys(listOpts).length ? listOpts : void 0
|
|
13786
13973
|
);
|
|
13787
13974
|
const cardMap = /* @__PURE__ */ new Map();
|
|
13788
13975
|
if (userId && tokens.length > 0) {
|
|
@@ -13799,6 +13986,8 @@ bridgeCommand.command("list-tokens").description(
|
|
|
13799
13986
|
return {
|
|
13800
13987
|
id: t2.id,
|
|
13801
13988
|
slug: t2.slug,
|
|
13989
|
+
title: t2.title,
|
|
13990
|
+
display_title: getDisplayTitle(t2),
|
|
13802
13991
|
concept: t2.concept,
|
|
13803
13992
|
domain: t2.domain,
|
|
13804
13993
|
bloomLevel: t2.bloom_level,
|
|
@@ -13832,6 +14021,8 @@ bridgeCommand.command("get-neighborhood").description(
|
|
|
13832
14021
|
const mapToken = (nt) => ({
|
|
13833
14022
|
id: nt.id,
|
|
13834
14023
|
slug: nt.slug,
|
|
14024
|
+
title: nt.title,
|
|
14025
|
+
display_title: getDisplayTitle(nt),
|
|
13835
14026
|
concept: nt.concept,
|
|
13836
14027
|
domain: nt.domain,
|
|
13837
14028
|
bloomLevel: nt.bloom_level,
|
|
@@ -13863,7 +14054,7 @@ bridgeCommand.command("personal-card-list").description("List and search persona
|
|
|
13863
14054
|
jsonOut2({ cards });
|
|
13864
14055
|
});
|
|
13865
14056
|
});
|
|
13866
|
-
bridgeCommand.command("personal-card-create").description("Atomically create a token and its personal card (JSON)").option("--user <id>", "User ID (default: whoami)").requiredOption("--concept <concept>", "Concept description / answer").option("--domain <domain>", "Knowledge category / domain", "").option("--question <question>", "Question prompt for recall").option("--source-link <link>", "Source file path or reference URL").option("--bloom <level>", "Bloom taxonomy level (1-5)", "1").option(
|
|
14057
|
+
bridgeCommand.command("personal-card-create").description("Atomically create a token and its personal card (JSON)").option("--user <id>", "User ID (default: whoami)").requiredOption("--concept <concept>", "Concept description / answer").option("--title <title>", "Human-friendly display title").option("--domain <domain>", "Knowledge category / domain", "").option("--question <question>", "Question prompt for recall").option("--source-link <link>", "Source file path or reference URL").option("--bloom <level>", "Bloom taxonomy level (1-5)", "1").option(
|
|
13867
14058
|
"--mode <mode>",
|
|
13868
14059
|
"Symbiosis mode: shadowing | copilot | autonomy | none",
|
|
13869
14060
|
"none"
|
|
@@ -13891,6 +14082,7 @@ bridgeCommand.command("personal-card-create").description("Atomically create a t
|
|
|
13891
14082
|
const { token, card } = await db.transaction(async (tx) => {
|
|
13892
14083
|
const createdToken = await createToken(tx, {
|
|
13893
14084
|
slug,
|
|
14085
|
+
title: opts.title,
|
|
13894
14086
|
concept: opts.concept,
|
|
13895
14087
|
domain: opts.domain,
|
|
13896
14088
|
bloom_level: bloom,
|
|
@@ -13907,6 +14099,8 @@ bridgeCommand.command("personal-card-create").description("Atomically create a t
|
|
|
13907
14099
|
token: {
|
|
13908
14100
|
id: token.id,
|
|
13909
14101
|
slug: token.slug,
|
|
14102
|
+
title: token.title,
|
|
14103
|
+
display_title: getDisplayTitle(token),
|
|
13910
14104
|
concept: token.concept,
|
|
13911
14105
|
domain: token.domain,
|
|
13912
14106
|
bloomLevel: token.bloom_level,
|
|
@@ -13928,13 +14122,14 @@ bridgeCommand.command("personal-card-create").description("Atomically create a t
|
|
|
13928
14122
|
});
|
|
13929
14123
|
});
|
|
13930
14124
|
});
|
|
13931
|
-
bridgeCommand.command("personal-card-update").description("Update the mutable token fields of a personal card (JSON)").option("--user <id>", "User ID (default: whoami)").requiredOption("--slug <slug>", "Token slug to update").option("--concept <concept>", "Updated concept text").option("--domain <domain>", "Updated domain / category").option("--bloom <level>", "Updated Bloom taxonomy level (1-5)").option("--context <context>", "Updated context").option(
|
|
14125
|
+
bridgeCommand.command("personal-card-update").description("Update the mutable token fields of a personal card (JSON)").option("--user <id>", "User ID (default: whoami)").requiredOption("--slug <slug>", "Token slug to update").option("--title <title>", "Updated display title").option("--concept <concept>", "Updated concept text").option("--domain <domain>", "Updated domain / category").option("--bloom <level>", "Updated Bloom taxonomy level (1-5)").option("--context <context>", "Updated context").option(
|
|
13932
14126
|
"--mode <mode>",
|
|
13933
14127
|
"Updated symbiosis mode: shadowing | copilot | autonomy | none"
|
|
13934
14128
|
).option("--source-link <link>", "Updated source link").option("--question <question>", "Updated question text").action(async (opts) => {
|
|
13935
14129
|
await withDb2(async (db) => {
|
|
13936
14130
|
const userId = await resolveUser(opts, db, { json: true });
|
|
13937
14131
|
const updates = {};
|
|
14132
|
+
if (opts.title !== void 0) updates.title = opts.title;
|
|
13938
14133
|
if (opts.concept !== void 0) updates.concept = opts.concept;
|
|
13939
14134
|
if (opts.domain !== void 0) updates.domain = opts.domain;
|
|
13940
14135
|
if (opts.bloom !== void 0) {
|
|
@@ -13976,6 +14171,8 @@ bridgeCommand.command("personal-card-update").description("Update the mutable to
|
|
|
13976
14171
|
token: {
|
|
13977
14172
|
id: token.id,
|
|
13978
14173
|
slug: token.slug,
|
|
14174
|
+
title: token.title,
|
|
14175
|
+
display_title: getDisplayTitle(token),
|
|
13979
14176
|
concept: token.concept,
|
|
13980
14177
|
domain: token.domain,
|
|
13981
14178
|
bloomLevel: token.bloom_level,
|
|
@@ -14008,7 +14205,12 @@ bridgeCommand.command("personal-card-remove").description(
|
|
|
14008
14205
|
success: true,
|
|
14009
14206
|
preview: true,
|
|
14010
14207
|
requiresConfirmation: true,
|
|
14011
|
-
token: {
|
|
14208
|
+
token: {
|
|
14209
|
+
id: token.id,
|
|
14210
|
+
slug: token.slug,
|
|
14211
|
+
title: token.title,
|
|
14212
|
+
display_title: getDisplayTitle(token)
|
|
14213
|
+
},
|
|
14012
14214
|
impact
|
|
14013
14215
|
});
|
|
14014
14216
|
return;
|
|
@@ -14037,7 +14239,12 @@ bridgeCommand.command("personal-card-delete").description("Hard-delete a token a
|
|
|
14037
14239
|
success: true,
|
|
14038
14240
|
preview: true,
|
|
14039
14241
|
requiresConfirmation: true,
|
|
14040
|
-
token: {
|
|
14242
|
+
token: {
|
|
14243
|
+
id: token.id,
|
|
14244
|
+
slug: token.slug,
|
|
14245
|
+
title: token.title,
|
|
14246
|
+
display_title: getDisplayTitle(token)
|
|
14247
|
+
},
|
|
14041
14248
|
impact
|
|
14042
14249
|
});
|
|
14043
14250
|
return;
|
|
@@ -14929,11 +15136,567 @@ async function setupTurso(urlArg, tokenArg, mode) {
|
|
|
14929
15136
|
}
|
|
14930
15137
|
}
|
|
14931
15138
|
|
|
15139
|
+
// src/cli/commands/doctor.ts
|
|
15140
|
+
import { Command as Command5 } from "commander";
|
|
15141
|
+
function shouldApplyFixes(opts) {
|
|
15142
|
+
return opts.fix === true && opts.dryRun !== true;
|
|
15143
|
+
}
|
|
15144
|
+
async function confirmDeterministicChanges(opts, message) {
|
|
15145
|
+
if (opts.yes) return true;
|
|
15146
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
15147
|
+
console.error(
|
|
15148
|
+
"Cannot request confirmation in a non-interactive terminal. Re-run with --yes to apply deterministic fixes."
|
|
15149
|
+
);
|
|
15150
|
+
return false;
|
|
15151
|
+
}
|
|
15152
|
+
const { confirm: confirm5 } = await import("@inquirer/prompts");
|
|
15153
|
+
return confirm5({ message, default: false });
|
|
15154
|
+
}
|
|
15155
|
+
function canRunInteractiveChoices(opts, taskName) {
|
|
15156
|
+
if (opts.yes) {
|
|
15157
|
+
console.error(
|
|
15158
|
+
`--yes cannot choose how to resolve ${taskName}. Re-run without --yes in an interactive terminal.`
|
|
15159
|
+
);
|
|
15160
|
+
return false;
|
|
15161
|
+
}
|
|
15162
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
15163
|
+
console.error(
|
|
15164
|
+
`${taskName} fixes require an interactive terminal because each change needs an explicit choice.`
|
|
15165
|
+
);
|
|
15166
|
+
return false;
|
|
15167
|
+
}
|
|
15168
|
+
return true;
|
|
15169
|
+
}
|
|
15170
|
+
function getWeakTitleReason(t2) {
|
|
15171
|
+
const title = (t2.title || "").trim();
|
|
15172
|
+
if (!title) {
|
|
15173
|
+
return "Missing or empty title";
|
|
15174
|
+
}
|
|
15175
|
+
if (title === t2.slug) {
|
|
15176
|
+
return "Title is equal to technical slug";
|
|
15177
|
+
}
|
|
15178
|
+
if (title.length < 3) {
|
|
15179
|
+
return "Title is suspiciously short";
|
|
15180
|
+
}
|
|
15181
|
+
if (title.length > 100) {
|
|
15182
|
+
return "Title is too long (> 100 chars)";
|
|
15183
|
+
}
|
|
15184
|
+
if (t2.domain) {
|
|
15185
|
+
const domParts = t2.domain.split(/[-/]/).filter((p) => p.length > 2);
|
|
15186
|
+
const titleLower2 = title.toLowerCase();
|
|
15187
|
+
for (const part of domParts) {
|
|
15188
|
+
if (titleLower2.includes(part.toLowerCase())) {
|
|
15189
|
+
return `Domain echo (contains '${part}' from domain '${t2.domain}')`;
|
|
15190
|
+
}
|
|
15191
|
+
}
|
|
15192
|
+
}
|
|
15193
|
+
const conceptClean = (t2.concept || "").trim().toLowerCase();
|
|
15194
|
+
const titleLower = title.toLowerCase();
|
|
15195
|
+
if (conceptClean.startsWith(titleLower) && t2.concept.length > title.length + 5 && !/[.!?]/.test(title)) {
|
|
15196
|
+
const titleWords = titleLower.split(/\s+/).length;
|
|
15197
|
+
if (titleWords <= 5) {
|
|
15198
|
+
return "Concept-prefix copy";
|
|
15199
|
+
}
|
|
15200
|
+
}
|
|
15201
|
+
if (title.endsWith("?") || /^(what|how|why|who|where|when|which|is|are|was|were|can|do|does|did|ueber|fuer|nahe)\b/i.test(
|
|
15202
|
+
title
|
|
15203
|
+
)) {
|
|
15204
|
+
return "Question stump or interrogative structure";
|
|
15205
|
+
}
|
|
15206
|
+
if (title.length > 3 && /[a-zA-Z]/.test(title)) {
|
|
15207
|
+
if (title === title.toUpperCase()) {
|
|
15208
|
+
return "All uppercase";
|
|
15209
|
+
}
|
|
15210
|
+
if (title === title.toLowerCase()) {
|
|
15211
|
+
return "All lowercase";
|
|
15212
|
+
}
|
|
15213
|
+
}
|
|
15214
|
+
return null;
|
|
15215
|
+
}
|
|
15216
|
+
function stripLeadingDomainWords(value, domain) {
|
|
15217
|
+
let result = value.trim();
|
|
15218
|
+
for (const word of domain.split(/[-/]/).filter(Boolean)) {
|
|
15219
|
+
const escaped = word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15220
|
+
result = result.replace(new RegExp(`^${escaped}\\s+`, "i"), "");
|
|
15221
|
+
}
|
|
15222
|
+
return result.trim();
|
|
15223
|
+
}
|
|
15224
|
+
function truncateTitle(value) {
|
|
15225
|
+
const trimmed = value.trim().replace(/^["']+|["']+$/g, "");
|
|
15226
|
+
return trimmed.length <= 80 ? trimmed : `${trimmed.substring(0, 77).trimEnd()}...`;
|
|
15227
|
+
}
|
|
15228
|
+
function titleFromSlug(token) {
|
|
15229
|
+
return getShortSlug(token.slug, token.domain).split(/[-_]+/).filter(Boolean).map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`).join(" ");
|
|
15230
|
+
}
|
|
15231
|
+
function validatedTitleProposal(token, raw) {
|
|
15232
|
+
const candidates = [
|
|
15233
|
+
raw,
|
|
15234
|
+
(token.concept || "").split(/[.;]/)[0] || "",
|
|
15235
|
+
titleFromSlug(token)
|
|
15236
|
+
];
|
|
15237
|
+
for (const candidate of candidates) {
|
|
15238
|
+
const normalized = truncateTitle(
|
|
15239
|
+
stripLeadingDomainWords(candidate, token.domain || "")
|
|
15240
|
+
);
|
|
15241
|
+
if (normalized.length > 0 && getWeakTitleReason({ ...token, title: normalized }) === null) {
|
|
15242
|
+
return normalized;
|
|
15243
|
+
}
|
|
15244
|
+
}
|
|
15245
|
+
return null;
|
|
15246
|
+
}
|
|
15247
|
+
var doctorTasks = [
|
|
15248
|
+
{
|
|
15249
|
+
name: "titles",
|
|
15250
|
+
description: "Backfill missing/weak human-friendly titles (no domain echoes, descriptive names).",
|
|
15251
|
+
run: async (db, opts) => {
|
|
15252
|
+
const tokens = await listTokens(db, {});
|
|
15253
|
+
const needing = tokens.map((t2) => {
|
|
15254
|
+
const reason = getWeakTitleReason(t2);
|
|
15255
|
+
return reason ? { token: t2, reason } : null;
|
|
15256
|
+
}).filter((x) => x !== null);
|
|
15257
|
+
console.log(
|
|
15258
|
+
`Found ${needing.length} tokens needing title work (of ${tokens.length}).`
|
|
15259
|
+
);
|
|
15260
|
+
if (needing.length === 0) return;
|
|
15261
|
+
const cfg = await getLlmConfig(db);
|
|
15262
|
+
const useLLM = cfg.enabled && !opts.noLlm;
|
|
15263
|
+
console.log(
|
|
15264
|
+
`Title generation mode: ${useLLM ? "LLM" : "Heuristic fallback"}${opts.timeoutMs ? ` (timeout: ${opts.timeoutMs}ms)` : ""}`
|
|
15265
|
+
);
|
|
15266
|
+
console.log("Generating proposed titles...");
|
|
15267
|
+
const proposals = [];
|
|
15268
|
+
let rejected = 0;
|
|
15269
|
+
for (let i = 0; i < needing.length; i++) {
|
|
15270
|
+
const item = needing[i];
|
|
15271
|
+
process.stdout.write(
|
|
15272
|
+
`\r [${i + 1}/${needing.length}] ${item.token.slug.slice(0, 40)}...`
|
|
15273
|
+
);
|
|
15274
|
+
let rawProposal = "";
|
|
15275
|
+
if (useLLM) {
|
|
15276
|
+
try {
|
|
15277
|
+
const gen = await generateTitleViaLLM(
|
|
15278
|
+
db,
|
|
15279
|
+
{
|
|
15280
|
+
slug: item.token.slug,
|
|
15281
|
+
concept: item.token.concept,
|
|
15282
|
+
domain: item.token.domain,
|
|
15283
|
+
question: item.token.question,
|
|
15284
|
+
context: item.token.context
|
|
15285
|
+
},
|
|
15286
|
+
{ timeoutMs: opts.timeoutMs }
|
|
15287
|
+
);
|
|
15288
|
+
rawProposal = gen.text;
|
|
15289
|
+
} catch (_e) {
|
|
15290
|
+
rawProposal = (item.token.concept || "").split(/[.;]/)[0]?.trim() || item.token.slug.replace(/-/g, " ");
|
|
15291
|
+
}
|
|
15292
|
+
} else {
|
|
15293
|
+
rawProposal = (item.token.concept || "").split(/[.;]/)[0]?.trim() || item.token.slug.replace(/-/g, " ");
|
|
15294
|
+
}
|
|
15295
|
+
const proposed = validatedTitleProposal(item.token, rawProposal);
|
|
15296
|
+
if (!proposed) {
|
|
15297
|
+
rejected++;
|
|
15298
|
+
continue;
|
|
15299
|
+
}
|
|
15300
|
+
proposals.push({
|
|
15301
|
+
token: item.token,
|
|
15302
|
+
oldTitle: item.token.title,
|
|
15303
|
+
newTitle: proposed,
|
|
15304
|
+
reason: item.reason
|
|
15305
|
+
});
|
|
15306
|
+
}
|
|
15307
|
+
process.stdout.write("\n");
|
|
15308
|
+
if (rejected > 0) {
|
|
15309
|
+
console.warn(
|
|
15310
|
+
`Skipped ${rejected} token(s) because no proposal passed title quality validation.`
|
|
15311
|
+
);
|
|
15312
|
+
}
|
|
15313
|
+
if (proposals.length === 0) return;
|
|
15314
|
+
if (!shouldApplyFixes(opts)) {
|
|
15315
|
+
console.log("\nProposed changes (Dry run):");
|
|
15316
|
+
for (const prop of proposals) {
|
|
15317
|
+
console.log(
|
|
15318
|
+
` ${prop.token.slug}: "${prop.oldTitle}" -> "${prop.newTitle}" (Reason: ${prop.reason})`
|
|
15319
|
+
);
|
|
15320
|
+
}
|
|
15321
|
+
console.log("\nRun with --fix to apply these changes.");
|
|
15322
|
+
return;
|
|
15323
|
+
}
|
|
15324
|
+
if (!opts.yes) {
|
|
15325
|
+
console.log("\nProposed changes:");
|
|
15326
|
+
for (const prop of proposals) {
|
|
15327
|
+
console.log(
|
|
15328
|
+
` ${prop.token.slug}: "${prop.oldTitle}" -> "${prop.newTitle}" (Reason: ${prop.reason})`
|
|
15329
|
+
);
|
|
15330
|
+
}
|
|
15331
|
+
}
|
|
15332
|
+
const confirmed = await confirmDeterministicChanges(
|
|
15333
|
+
opts,
|
|
15334
|
+
`Apply these ${proposals.length} proposed titles?`
|
|
15335
|
+
);
|
|
15336
|
+
if (!confirmed) {
|
|
15337
|
+
console.log("Cancelled. No titles were updated.");
|
|
15338
|
+
return;
|
|
15339
|
+
}
|
|
15340
|
+
let fixed = 0;
|
|
15341
|
+
for (const prop of proposals) {
|
|
15342
|
+
await updateToken(db, prop.token.slug, { title: prop.newTitle });
|
|
15343
|
+
fixed++;
|
|
15344
|
+
console.log(`Set title for ${prop.token.slug} -> ${prop.newTitle}`);
|
|
15345
|
+
}
|
|
15346
|
+
console.log(`Successfully fixed ${fixed} titles.`);
|
|
15347
|
+
}
|
|
15348
|
+
},
|
|
15349
|
+
{
|
|
15350
|
+
name: "texts",
|
|
15351
|
+
description: "Repair legacy ASCII umlauts in prose fields (question/concept/context).",
|
|
15352
|
+
run: async (db, opts) => {
|
|
15353
|
+
const tokens = await listTokens(db, {});
|
|
15354
|
+
const cfg = await getLlmConfig(db);
|
|
15355
|
+
const useLLM = cfg.enabled && !opts.noLlm;
|
|
15356
|
+
const replacements = [
|
|
15357
|
+
[/\bUeber\b/g, "\xDCber"],
|
|
15358
|
+
[/\bueber\b/g, "\xFCber"],
|
|
15359
|
+
[/\bFuer\b/g, "F\xFCr"],
|
|
15360
|
+
[/\bfuer\b/g, "f\xFCr"],
|
|
15361
|
+
[/\bMuehe\b/g, "M\xFChe"],
|
|
15362
|
+
[/\bmuehe\b/g, "m\xFChe"],
|
|
15363
|
+
[/\bKuer\b/g, "K\xFCr"],
|
|
15364
|
+
[/\bkuer\b/g, "k\xFCr"],
|
|
15365
|
+
[/\bUebung\b/g, "\xDCbung"],
|
|
15366
|
+
[/\buebung\b/g, "\xFCbung"],
|
|
15367
|
+
[/\bMoeglich\b/g, "M\xF6glich"],
|
|
15368
|
+
[/\bmoeglich\b/g, "m\xF6glich"],
|
|
15369
|
+
[/\bMoeglichkeiten\b/g, "M\xF6glichkeiten"],
|
|
15370
|
+
[/\bmoeglichkeiten\b/g, "m\xF6glichkeiten"],
|
|
15371
|
+
[/\bSchoen\b/g, "Sch\xF6n"],
|
|
15372
|
+
[/\bschoen\b/g, "sch\xF6n"],
|
|
15373
|
+
[/\bKoennen\b/g, "K\xF6nnen"],
|
|
15374
|
+
[/\bkoennen\b/g, "k\xF6nnen"],
|
|
15375
|
+
[/\bMuessen\b/g, "M\xFCssen"],
|
|
15376
|
+
[/\bmuessen\b/g, "m\xFCssen"],
|
|
15377
|
+
[/\bDuerfen\b/g, "D\xFCrfen"],
|
|
15378
|
+
[/\bduerfen\b/g, "d\xFCrfen"],
|
|
15379
|
+
[/\bAerger\b/g, "\xC4rger"],
|
|
15380
|
+
[/\baerger\b/g, "\xE4rger"],
|
|
15381
|
+
[/\bGruende\b/g, "Gr\xFCnde"],
|
|
15382
|
+
[/\bgruende\b/g, "gr\xFCnde"]
|
|
15383
|
+
];
|
|
15384
|
+
const toFix = [];
|
|
15385
|
+
console.log("Analyzing prose text fields for legacy umlauts...");
|
|
15386
|
+
for (const t2 of tokens) {
|
|
15387
|
+
const fields = [
|
|
15388
|
+
{ key: "question", val: t2.question },
|
|
15389
|
+
{ key: "concept", val: t2.concept },
|
|
15390
|
+
{ key: "context", val: t2.context },
|
|
15391
|
+
{ key: "title", val: t2.title }
|
|
15392
|
+
];
|
|
15393
|
+
for (const f of fields) {
|
|
15394
|
+
if (!f.val || typeof f.val !== "string") continue;
|
|
15395
|
+
let repaired = f.val;
|
|
15396
|
+
if (useLLM) {
|
|
15397
|
+
const hasPossibleUmlautFold = /[aou]e/i.test(f.val);
|
|
15398
|
+
if (hasPossibleUmlautFold) {
|
|
15399
|
+
try {
|
|
15400
|
+
repaired = await repairUmlautsViaLLM(
|
|
15401
|
+
db,
|
|
15402
|
+
{ text: f.val },
|
|
15403
|
+
{ timeoutMs: opts.timeoutMs }
|
|
15404
|
+
);
|
|
15405
|
+
if (repaired === f.val) {
|
|
15406
|
+
for (const [from, to] of replacements) {
|
|
15407
|
+
repaired = repaired.replace(from, to);
|
|
15408
|
+
}
|
|
15409
|
+
}
|
|
15410
|
+
} catch (_e) {
|
|
15411
|
+
for (const [from, to] of replacements) {
|
|
15412
|
+
repaired = repaired.replace(from, to);
|
|
15413
|
+
}
|
|
15414
|
+
}
|
|
15415
|
+
}
|
|
15416
|
+
} else {
|
|
15417
|
+
for (const [from, to] of replacements) {
|
|
15418
|
+
repaired = repaired.replace(from, to);
|
|
15419
|
+
}
|
|
15420
|
+
}
|
|
15421
|
+
if (repaired !== f.val) {
|
|
15422
|
+
toFix.push({ token: t2, field: f.key, old: f.val, new: repaired });
|
|
15423
|
+
}
|
|
15424
|
+
}
|
|
15425
|
+
}
|
|
15426
|
+
console.log(`Found ${toFix.length} prose fields with legacy umlauts.`);
|
|
15427
|
+
if (toFix.length === 0) return;
|
|
15428
|
+
if (!shouldApplyFixes(opts)) {
|
|
15429
|
+
console.log("\nProposed changes (Dry run):");
|
|
15430
|
+
for (const fix of toFix) {
|
|
15431
|
+
console.log(` ${fix.token.slug} [${fix.field}]:`);
|
|
15432
|
+
console.log(` Old: "${fix.old}"`);
|
|
15433
|
+
console.log(` New: "${fix.new}"`);
|
|
15434
|
+
}
|
|
15435
|
+
console.log("\nRun with --fix to apply these changes.");
|
|
15436
|
+
return;
|
|
15437
|
+
}
|
|
15438
|
+
if (!opts.yes) {
|
|
15439
|
+
console.log("\nProposed changes:");
|
|
15440
|
+
for (const fix of toFix) {
|
|
15441
|
+
console.log(
|
|
15442
|
+
` ${fix.token.slug} [${fix.field}]: "${fix.old}" -> "${fix.new}"`
|
|
15443
|
+
);
|
|
15444
|
+
}
|
|
15445
|
+
}
|
|
15446
|
+
const confirmed = await confirmDeterministicChanges(
|
|
15447
|
+
opts,
|
|
15448
|
+
`Apply these ${toFix.length} prose fixes?`
|
|
15449
|
+
);
|
|
15450
|
+
if (!confirmed) {
|
|
15451
|
+
console.log("Cancelled. No changes made.");
|
|
15452
|
+
return;
|
|
15453
|
+
}
|
|
15454
|
+
let fixed = 0;
|
|
15455
|
+
for (const fix of toFix) {
|
|
15456
|
+
const updates = {};
|
|
15457
|
+
updates[fix.field] = fix.new;
|
|
15458
|
+
await updateToken(db, fix.token.slug, updates);
|
|
15459
|
+
fixed++;
|
|
15460
|
+
}
|
|
15461
|
+
console.log(
|
|
15462
|
+
`Fixed ${fixed} fields. (Re-embed will happen automatically for changed content.)`
|
|
15463
|
+
);
|
|
15464
|
+
}
|
|
15465
|
+
},
|
|
15466
|
+
{
|
|
15467
|
+
name: "duplicates",
|
|
15468
|
+
description: "List semantic duplicates for review/merge.",
|
|
15469
|
+
run: async (db, opts) => {
|
|
15470
|
+
const applyingFixes = shouldApplyFixes(opts);
|
|
15471
|
+
if (applyingFixes && !canRunInteractiveChoices(opts, "duplicate fixes")) {
|
|
15472
|
+
return;
|
|
15473
|
+
}
|
|
15474
|
+
const modelSetting = await getSetting(db, "llm.embedding.model");
|
|
15475
|
+
if (!modelSetting) {
|
|
15476
|
+
console.log(
|
|
15477
|
+
"No embedding model configured. Please configure an embedding model to scan duplicates."
|
|
15478
|
+
);
|
|
15479
|
+
return;
|
|
15480
|
+
}
|
|
15481
|
+
const model = canonicalEmbeddingModelId(modelSetting);
|
|
15482
|
+
if (model !== modelSetting.trim().toLowerCase()) {
|
|
15483
|
+
console.log(
|
|
15484
|
+
`Using canonical embedding model "${model}" for configured alias "${modelSetting}".`
|
|
15485
|
+
);
|
|
15486
|
+
}
|
|
15487
|
+
let coverage = await getEmbeddingCoverage(db, model);
|
|
15488
|
+
const initiallyIncomplete = coverage.missing + coverage.stale;
|
|
15489
|
+
if (initiallyIncomplete > 0) {
|
|
15490
|
+
if (applyingFixes) {
|
|
15491
|
+
console.log(
|
|
15492
|
+
`Embedding coverage is incomplete (${coverage.missing} missing, ${coverage.stale} stale). Attempting backfill...`
|
|
15493
|
+
);
|
|
15494
|
+
const result = await ensureTokenEmbeddings(db, {
|
|
15495
|
+
limit: coverage.tokens
|
|
15496
|
+
});
|
|
15497
|
+
coverage = await getEmbeddingCoverage(db, model);
|
|
15498
|
+
const remaining = coverage.missing + coverage.stale;
|
|
15499
|
+
if (remaining > 0) {
|
|
15500
|
+
console.warn(
|
|
15501
|
+
`Warning: Duplicate scan is incomplete: ${remaining} of ${coverage.tokens} active tokens still lack a fresh "${model}" embedding.${result.reason ? ` ${result.reason}` : ""}`
|
|
15502
|
+
);
|
|
15503
|
+
} else {
|
|
15504
|
+
console.log(
|
|
15505
|
+
`Embedding backfill complete (${result.embedded} updated).`
|
|
15506
|
+
);
|
|
15507
|
+
}
|
|
15508
|
+
} else {
|
|
15509
|
+
console.warn(
|
|
15510
|
+
`Warning: Duplicate scan is incomplete: ${initiallyIncomplete} of ${coverage.tokens} active tokens lack a fresh "${model}" embedding. Dry-run will not backfill them; run 'zam token reembed' first for a complete scan.`
|
|
15511
|
+
);
|
|
15512
|
+
}
|
|
15513
|
+
}
|
|
15514
|
+
console.log(`Listing embedded tokens for model "${model}"...`);
|
|
15515
|
+
const embedded = await listEmbeddedTokens(db, model);
|
|
15516
|
+
const threshold = await resolveDedupThreshold(db);
|
|
15517
|
+
console.log(
|
|
15518
|
+
`Scanning ${embedded.length} tokens for semantic duplicates (threshold: ${threshold})...`
|
|
15519
|
+
);
|
|
15520
|
+
const duplicates = [];
|
|
15521
|
+
const seenPairs = /* @__PURE__ */ new Set();
|
|
15522
|
+
for (let i = 0; i < embedded.length; i++) {
|
|
15523
|
+
for (let j = i + 1; j < embedded.length; j++) {
|
|
15524
|
+
const sim = cosineSimilarity(
|
|
15525
|
+
embedded[i].embedding,
|
|
15526
|
+
embedded[j].embedding
|
|
15527
|
+
);
|
|
15528
|
+
if (sim >= threshold) {
|
|
15529
|
+
const idA = embedded[i].token.id;
|
|
15530
|
+
const idB = embedded[j].token.id;
|
|
15531
|
+
const pairKey = idA < idB ? `${idA}:${idB}` : `${idB}:${idA}`;
|
|
15532
|
+
if (!seenPairs.has(pairKey)) {
|
|
15533
|
+
seenPairs.add(pairKey);
|
|
15534
|
+
duplicates.push({
|
|
15535
|
+
a: embedded[i].token,
|
|
15536
|
+
b: embedded[j].token,
|
|
15537
|
+
similarity: sim
|
|
15538
|
+
});
|
|
15539
|
+
}
|
|
15540
|
+
}
|
|
15541
|
+
}
|
|
15542
|
+
}
|
|
15543
|
+
console.log(`Found ${duplicates.length} duplicate pairs.`);
|
|
15544
|
+
if (duplicates.length === 0) return;
|
|
15545
|
+
if (!applyingFixes) {
|
|
15546
|
+
console.log("\nDuplicate pairs found (Dry run):");
|
|
15547
|
+
for (const pair of duplicates) {
|
|
15548
|
+
console.log(
|
|
15549
|
+
` - "${pair.a.slug}" and "${pair.b.slug}" (similarity: ${pair.similarity.toFixed(3)})`
|
|
15550
|
+
);
|
|
15551
|
+
}
|
|
15552
|
+
console.log("\nRun with --fix to resolve duplicates interactively.");
|
|
15553
|
+
return;
|
|
15554
|
+
}
|
|
15555
|
+
const { select: select3 } = await import("@inquirer/prompts");
|
|
15556
|
+
let deprecatedCount = 0;
|
|
15557
|
+
for (const pair of duplicates) {
|
|
15558
|
+
console.log(
|
|
15559
|
+
`
|
|
15560
|
+
Duplicate pair found (similarity: ${pair.similarity.toFixed(3)}):`
|
|
15561
|
+
);
|
|
15562
|
+
console.log(
|
|
15563
|
+
` 1. ${pair.a.slug}: "${pair.a.concept.substring(0, 100)}..."`
|
|
15564
|
+
);
|
|
15565
|
+
console.log(
|
|
15566
|
+
` 2. ${pair.b.slug}: "${pair.b.concept.substring(0, 100)}..."`
|
|
15567
|
+
);
|
|
15568
|
+
const choice = await select3({
|
|
15569
|
+
message: "Select action:",
|
|
15570
|
+
choices: [
|
|
15571
|
+
{ name: "Keep both", value: "keep" },
|
|
15572
|
+
{ name: `Deprecate ${pair.a.slug}`, value: "deprecate_a" },
|
|
15573
|
+
{ name: `Deprecate ${pair.b.slug}`, value: "deprecate_b" }
|
|
15574
|
+
]
|
|
15575
|
+
});
|
|
15576
|
+
if (choice === "deprecate_a") {
|
|
15577
|
+
await deprecateToken(db, pair.a.slug);
|
|
15578
|
+
console.log(` Deprecated ${pair.a.slug}`);
|
|
15579
|
+
deprecatedCount++;
|
|
15580
|
+
} else if (choice === "deprecate_b") {
|
|
15581
|
+
await deprecateToken(db, pair.b.slug);
|
|
15582
|
+
console.log(` Deprecated ${pair.b.slug}`);
|
|
15583
|
+
deprecatedCount++;
|
|
15584
|
+
}
|
|
15585
|
+
}
|
|
15586
|
+
console.log(
|
|
15587
|
+
`
|
|
15588
|
+
Duplicates scan complete. Deprecated ${deprecatedCount} token(s).`
|
|
15589
|
+
);
|
|
15590
|
+
}
|
|
15591
|
+
},
|
|
15592
|
+
{
|
|
15593
|
+
name: "domains",
|
|
15594
|
+
description: "Help rename/unify domains into / hierarchy (slugs untouched).",
|
|
15595
|
+
run: async (db, opts) => {
|
|
15596
|
+
const tokens = await listTokens(db, {});
|
|
15597
|
+
const domains = Array.from(
|
|
15598
|
+
new Set(tokens.map((t2) => t2.domain).filter(Boolean))
|
|
15599
|
+
).sort();
|
|
15600
|
+
console.log(`Current domains (${domains.length}):`);
|
|
15601
|
+
for (const d of domains) {
|
|
15602
|
+
const count2 = tokens.filter((t2) => t2.domain === d).length;
|
|
15603
|
+
const status = d.includes("/") ? "(hierarchical)" : "(flat, valid)";
|
|
15604
|
+
console.log(` ${d} \u2014 ${count2} token(s) ${status}`);
|
|
15605
|
+
}
|
|
15606
|
+
if (!shouldApplyFixes(opts)) {
|
|
15607
|
+
console.log("\nUse --fix to interactively rename/unify domains.");
|
|
15608
|
+
return;
|
|
15609
|
+
}
|
|
15610
|
+
if (!canRunInteractiveChoices(opts, "domain renames")) return;
|
|
15611
|
+
const { confirm: confirm5, input: input8 } = await import("@inquirer/prompts");
|
|
15612
|
+
const changes = [];
|
|
15613
|
+
for (const d of domains) {
|
|
15614
|
+
const count2 = tokens.filter((t2) => t2.domain === d).length;
|
|
15615
|
+
const wantRename = await confirm5({
|
|
15616
|
+
message: `Rename domain "${d}" (${count2} tokens)?`,
|
|
15617
|
+
default: false
|
|
15618
|
+
});
|
|
15619
|
+
if (wantRename) {
|
|
15620
|
+
const newDomain = await input8({
|
|
15621
|
+
message: `Enter new domain name for "${d}":`,
|
|
15622
|
+
validate: (val) => val.trim().length > 0 ? true : "Domain name cannot be empty"
|
|
15623
|
+
});
|
|
15624
|
+
const trimmed = newDomain.trim();
|
|
15625
|
+
if (trimmed === d) {
|
|
15626
|
+
console.log(" New name matches old name. Skipped.");
|
|
15627
|
+
continue;
|
|
15628
|
+
}
|
|
15629
|
+
changes.push({ oldDomain: d, newDomain: trimmed });
|
|
15630
|
+
}
|
|
15631
|
+
}
|
|
15632
|
+
if (changes.length > 0) {
|
|
15633
|
+
await db.transaction(async (tx) => {
|
|
15634
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15635
|
+
const stmt = tx.prepare(
|
|
15636
|
+
"UPDATE tokens SET domain = ?, updated_at = ? WHERE domain = ?"
|
|
15637
|
+
);
|
|
15638
|
+
for (const change of changes) {
|
|
15639
|
+
const res = await stmt.run(change.newDomain, now, change.oldDomain);
|
|
15640
|
+
console.log(
|
|
15641
|
+
` Renamed "${change.oldDomain}" to "${change.newDomain}" for ${res.changes} token(s).`
|
|
15642
|
+
);
|
|
15643
|
+
}
|
|
15644
|
+
});
|
|
15645
|
+
}
|
|
15646
|
+
console.log(
|
|
15647
|
+
`Domains rename complete. Renamed ${changes.length} domain(s).`
|
|
15648
|
+
);
|
|
15649
|
+
}
|
|
15650
|
+
}
|
|
15651
|
+
];
|
|
15652
|
+
function parseDoctorTimeout(value) {
|
|
15653
|
+
const parsed = Number(value);
|
|
15654
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
15655
|
+
throw new Error("--timeout must be a positive integer in milliseconds");
|
|
15656
|
+
}
|
|
15657
|
+
return parsed;
|
|
15658
|
+
}
|
|
15659
|
+
var doctorCommand = new Command5("doctor").description(
|
|
15660
|
+
"Diagnose and repair the knowledge base (titles, legacy data, duplicates, domains)."
|
|
15661
|
+
).option("--fix", "Apply changes (default is dry-run)").option("--dry-run", "Explicit dry run (default)").option(
|
|
15662
|
+
"--yes",
|
|
15663
|
+
"Auto-confirm deterministic title/text fixes (duplicate/domain choices remain interactive)"
|
|
15664
|
+
).option("--no-llm", "Skip LLM calls, use heuristic fallback only").option(
|
|
15665
|
+
"--timeout <ms>",
|
|
15666
|
+
"LLM timeout in ms per call (default: 20000)",
|
|
15667
|
+
"20000"
|
|
15668
|
+
).argument("[task]", "Specific task: titles, texts, duplicates, domains").action(async (taskName, opts) => {
|
|
15669
|
+
await withDb(async (db) => {
|
|
15670
|
+
const fix = !!opts.fix;
|
|
15671
|
+
const dryRun = !fix || !!opts.dryRun;
|
|
15672
|
+
const yes = !!opts.yes;
|
|
15673
|
+
const noLlm = opts.llm === false;
|
|
15674
|
+
const timeoutMs = parseDoctorTimeout(opts.timeout);
|
|
15675
|
+
if (!taskName) {
|
|
15676
|
+
console.log("Available doctor tasks:");
|
|
15677
|
+
for (const t2 of doctorTasks) {
|
|
15678
|
+
console.log(` ${t2.name} \u2014 ${t2.description}`);
|
|
15679
|
+
}
|
|
15680
|
+
console.log("\nRun with a task name, e.g. `zam doctor titles --fix`");
|
|
15681
|
+
return;
|
|
15682
|
+
}
|
|
15683
|
+
const task = doctorTasks.find((t2) => t2.name === taskName);
|
|
15684
|
+
if (!task) {
|
|
15685
|
+
console.error(`Unknown task: ${taskName}`);
|
|
15686
|
+
process.exit(1);
|
|
15687
|
+
}
|
|
15688
|
+
console.log(
|
|
15689
|
+
`Running doctor task: ${task.name} (fix=${fix}, dryRun=${dryRun}${noLlm ? ", noLlm=true" : ""})`
|
|
15690
|
+
);
|
|
15691
|
+
await task.run(db, { fix, dryRun, yes, noLlm, timeoutMs });
|
|
15692
|
+
});
|
|
15693
|
+
});
|
|
15694
|
+
|
|
14932
15695
|
// src/cli/commands/git-sync.ts
|
|
14933
15696
|
import { execSync as execSync5 } from "child_process";
|
|
14934
15697
|
import { chmodSync as chmodSync2, existsSync as existsSync18, writeFileSync as writeFileSync8 } from "fs";
|
|
14935
15698
|
import { join as join19 } from "path";
|
|
14936
|
-
import { Command as
|
|
15699
|
+
import { Command as Command6 } from "commander";
|
|
14937
15700
|
function installHook2() {
|
|
14938
15701
|
const gitDir = join19(process.cwd(), ".git");
|
|
14939
15702
|
if (!existsSync18(gitDir)) {
|
|
@@ -14963,7 +15726,7 @@ zam git-sync --commit HEAD --quiet
|
|
|
14963
15726
|
process.exit(1);
|
|
14964
15727
|
}
|
|
14965
15728
|
}
|
|
14966
|
-
var gitSyncCommand = new
|
|
15729
|
+
var gitSyncCommand = new Command6("git-sync").description("Sync learning cards with recent Git file modifications").option("--commit <hash>", "Git commit hash to check", "HEAD").option("--user <id>", "User ID (default: whoami)").option("--install", "Install git post-commit hook in current repo").option("--quiet", "Suppress verbose output").action(async (opts) => {
|
|
14967
15730
|
if (opts.install) {
|
|
14968
15731
|
installHook2();
|
|
14969
15732
|
return;
|
|
@@ -15055,7 +15818,7 @@ ZAM Auto-Stale Complete: Scanned ${changedFiles.length} file(s).`
|
|
|
15055
15818
|
import { existsSync as existsSync19, mkdirSync as mkdirSync11 } from "fs";
|
|
15056
15819
|
import { resolve as resolve5 } from "path";
|
|
15057
15820
|
import { input as input2 } from "@inquirer/prompts";
|
|
15058
|
-
import { Command as
|
|
15821
|
+
import { Command as Command7 } from "commander";
|
|
15059
15822
|
async function resolveGoalsDir() {
|
|
15060
15823
|
let goalsDir;
|
|
15061
15824
|
let db;
|
|
@@ -15068,7 +15831,7 @@ async function resolveGoalsDir() {
|
|
|
15068
15831
|
}
|
|
15069
15832
|
return goalsDir ? resolve5(goalsDir) : resolve5("goals");
|
|
15070
15833
|
}
|
|
15071
|
-
var goalCommand = new
|
|
15834
|
+
var goalCommand = new Command7("goal").description(
|
|
15072
15835
|
"Manage learning goals (markdown files)"
|
|
15073
15836
|
);
|
|
15074
15837
|
goalCommand.command("list").description("List all goals").option(
|
|
@@ -15241,7 +16004,7 @@ import { existsSync as existsSync20, mkdirSync as mkdirSync12, writeFileSync as
|
|
|
15241
16004
|
import { homedir as homedir11 } from "os";
|
|
15242
16005
|
import { join as join20, resolve as resolve6 } from "path";
|
|
15243
16006
|
import { confirm, input as input3 } from "@inquirer/prompts";
|
|
15244
|
-
import { Command as
|
|
16007
|
+
import { Command as Command8 } from "commander";
|
|
15245
16008
|
var HOME2 = homedir11();
|
|
15246
16009
|
function printLine(char = "\u2550", len = 60, color = "\x1B[36m") {
|
|
15247
16010
|
console.log(`${color}${char.repeat(len)}\x1B[0m`);
|
|
@@ -15278,7 +16041,7 @@ Here, I declare the core concepts and principles I want to master.
|
|
|
15278
16041
|
);
|
|
15279
16042
|
}
|
|
15280
16043
|
}
|
|
15281
|
-
var initCommand = new
|
|
16044
|
+
var initCommand = new Command8("init").description("Launch the guided interactive onboarding wizard").action(async () => {
|
|
15282
16045
|
printLine();
|
|
15283
16046
|
console.log(
|
|
15284
16047
|
"\x1B[1m\x1B[32m ZAM \u2014 The Symbiotic Learning Agent Onboarding\x1B[0m"
|
|
@@ -15455,7 +16218,7 @@ var initCommand = new Command7("init").description("Launch the guided interactiv
|
|
|
15455
16218
|
|
|
15456
16219
|
// src/cli/commands/learn.ts
|
|
15457
16220
|
import { input as input5 } from "@inquirer/prompts";
|
|
15458
|
-
import { Command as
|
|
16221
|
+
import { Command as Command9 } from "commander";
|
|
15459
16222
|
|
|
15460
16223
|
// src/cli/learn-format.ts
|
|
15461
16224
|
var BLOOM_VERBS3 = {
|
|
@@ -15471,16 +16234,22 @@ function clampBloom(n) {
|
|
|
15471
16234
|
function formatHeader(input8) {
|
|
15472
16235
|
const lvl = clampBloom(input8.bloomLevel);
|
|
15473
16236
|
const parts = [`${BLOOM_VERBS3[lvl]} (Bloom ${lvl})`];
|
|
15474
|
-
|
|
16237
|
+
const name = input8.title || input8.slug;
|
|
16238
|
+
if (name) {
|
|
16239
|
+
parts.push(name);
|
|
16240
|
+
} else if (input8.domain?.trim()) {
|
|
15475
16241
|
parts.push(input8.domain.trim());
|
|
15476
16242
|
}
|
|
15477
16243
|
return parts.join(" \xB7 ");
|
|
15478
16244
|
}
|
|
15479
16245
|
function formatReveal(input8) {
|
|
15480
16246
|
const lines = [
|
|
15481
|
-
`Token:
|
|
16247
|
+
`Token: ${input8.title || input8.slug}`,
|
|
15482
16248
|
`Concept: ${input8.concept}`
|
|
15483
16249
|
];
|
|
16250
|
+
if (input8.title) {
|
|
16251
|
+
lines.push(`ID: #${input8.slug}`);
|
|
16252
|
+
}
|
|
15484
16253
|
if (input8.context?.trim()) {
|
|
15485
16254
|
lines.push("", `Context: ${input8.context.trim()}`);
|
|
15486
16255
|
}
|
|
@@ -15793,7 +16562,7 @@ var STOP_WORDS = /* @__PURE__ */ new Set(["q", ":q", "quit", "stop"]);
|
|
|
15793
16562
|
function isExitPrompt(err) {
|
|
15794
16563
|
return err instanceof Error && err.name === "ExitPromptError";
|
|
15795
16564
|
}
|
|
15796
|
-
var learnCommand = new
|
|
16565
|
+
var learnCommand = new Command9("learn").description(
|
|
15797
16566
|
"Run a spoiler-free, in-process learning session (recall \u2192 reveal \u2192 self-rate)"
|
|
15798
16567
|
).option("--user <id>", "User ID (default: whoami)").option("--max-new <n>", "Maximum new cards", "10").option("--max-reviews <n>", "Maximum review cards", "50").option("--no-resolve", "Skip resolving source_link into the revealed answer").action(async (opts) => {
|
|
15799
16568
|
let db;
|
|
@@ -15867,7 +16636,7 @@ ${t(locale, "welcome", { count: queue.items.length })}`);
|
|
|
15867
16636
|
console.log(`
|
|
15868
16637
|
${"\u2500".repeat(50)}`);
|
|
15869
16638
|
console.log(
|
|
15870
|
-
`[${index + 1}/${queue.items.length}] ${formatHeader(item)}`
|
|
16639
|
+
`[${index + 1}/${queue.items.length}] ${formatHeader({ bloomLevel: item.bloomLevel, domain: item.domain })}`
|
|
15871
16640
|
);
|
|
15872
16641
|
console.log(`
|
|
15873
16642
|
${prompt.question}`);
|
|
@@ -15931,6 +16700,7 @@ ${t(locale, "eval_skipped", { reason: err.message })}`
|
|
|
15931
16700
|
);
|
|
15932
16701
|
const reveal = formatReveal({
|
|
15933
16702
|
slug: item.slug,
|
|
16703
|
+
title: item.title,
|
|
15934
16704
|
concept: item.concept,
|
|
15935
16705
|
context: token?.context,
|
|
15936
16706
|
resolved
|
|
@@ -16038,8 +16808,8 @@ learnCommand.command("open").description("Open a new terminal window running zam
|
|
|
16038
16808
|
});
|
|
16039
16809
|
|
|
16040
16810
|
// src/cli/commands/monitor.ts
|
|
16041
|
-
import { Command as
|
|
16042
|
-
var monitorCommand = new
|
|
16811
|
+
import { Command as Command10 } from "commander";
|
|
16812
|
+
var monitorCommand = new Command10("monitor").description(
|
|
16043
16813
|
"Shell observation for real-time task monitoring"
|
|
16044
16814
|
);
|
|
16045
16815
|
monitorCommand.command("start").description("Output shell hook code to install monitoring").requiredOption("--session <id>", "Session ID to monitor").option(
|
|
@@ -16221,8 +16991,8 @@ monitorCommand.command("open").description("Open a new monitored terminal window
|
|
|
16221
16991
|
});
|
|
16222
16992
|
|
|
16223
16993
|
// src/cli/commands/observer.ts
|
|
16224
|
-
import { Command as
|
|
16225
|
-
var observerCommand = new
|
|
16994
|
+
import { Command as Command11 } from "commander";
|
|
16995
|
+
var observerCommand = new Command11("observer").description(
|
|
16226
16996
|
"Configure what the UI observer may capture (Layer 2 policy)"
|
|
16227
16997
|
);
|
|
16228
16998
|
function applyObserverListChange(current, entry, op) {
|
|
@@ -16285,7 +17055,7 @@ observerCommand.command("revoke <process>").description("Remove a process from o
|
|
|
16285
17055
|
|
|
16286
17056
|
// src/cli/commands/profile.ts
|
|
16287
17057
|
import { dirname as dirname6, resolve as resolve7 } from "path";
|
|
16288
|
-
import { Command as
|
|
17058
|
+
import { Command as Command12 } from "commander";
|
|
16289
17059
|
var C2 = {
|
|
16290
17060
|
reset: "\x1B[0m",
|
|
16291
17061
|
bold: "\x1B[1m",
|
|
@@ -16302,7 +17072,7 @@ function render(profile) {
|
|
|
16302
17072
|
console.log(` data dir: ${C2.cyan}${profile.dataDir}${C2.reset}`);
|
|
16303
17073
|
console.log(` database: ${C2.cyan}${profile.dbPath}${C2.reset}`);
|
|
16304
17074
|
}
|
|
16305
|
-
var profileCommand = new
|
|
17075
|
+
var profileCommand = new Command12("profile").description("Show or change this machine's ZAM install profile").option("--mode <mode>", "Set install mode: developer | default").option("--dir <path>", "Set the personal-content folder").option("--json", "Output as JSON").action(async (opts) => {
|
|
16306
17076
|
if (opts.mode && opts.mode !== "developer" && opts.mode !== "default") {
|
|
16307
17077
|
console.error(`Invalid --mode: ${opts.mode}. Use developer or default.`);
|
|
16308
17078
|
process.exit(1);
|
|
@@ -16342,8 +17112,8 @@ var profileCommand = new Command11("profile").description("Show or change this m
|
|
|
16342
17112
|
|
|
16343
17113
|
// src/cli/commands/provider.ts
|
|
16344
17114
|
import { password as password2 } from "@inquirer/prompts";
|
|
16345
|
-
import { Command as
|
|
16346
|
-
var providerCommand = new
|
|
17115
|
+
import { Command as Command13 } from "commander";
|
|
17116
|
+
var providerCommand = new Command13("provider").description(
|
|
16347
17117
|
"Configure role-based AI providers (url/model/flavor/key, per role)"
|
|
16348
17118
|
);
|
|
16349
17119
|
providerCommand.command("list").description("Show configured providers, role bindings, and key status").option("--json", "Output as JSON").option("--machine", "Read machine-local providers from ~/.zam/config.json").action(async (opts) => {
|
|
@@ -16578,8 +17348,8 @@ providerCommand.command("clear-key <ref>").description("Remove a stored API key"
|
|
|
16578
17348
|
});
|
|
16579
17349
|
|
|
16580
17350
|
// src/cli/commands/review.ts
|
|
16581
|
-
import { Command as
|
|
16582
|
-
var reviewCommand = new
|
|
17351
|
+
import { Command as Command14 } from "commander";
|
|
17352
|
+
var reviewCommand = new Command14("review").description("Start an interactive review session").option("--user <id>", "User ID (default: whoami)").option("--max-new <n>", "Maximum new cards", "10").option("--max-reviews <n>", "Maximum review cards", "50").option("--no-resolve", "Skip resolving source_link into inline context").action(async (opts) => {
|
|
16583
17353
|
let db;
|
|
16584
17354
|
try {
|
|
16585
17355
|
db = await openDatabase();
|
|
@@ -16617,9 +17387,8 @@ Review session: ${queue.items.length} card(s)`);
|
|
|
16617
17387
|
});
|
|
16618
17388
|
console.log(
|
|
16619
17389
|
`
|
|
16620
|
-
[${index + 1}/${queue.items.length}] ${
|
|
17390
|
+
[${index + 1}/${queue.items.length}] ${formatHeader({ bloomLevel: item.bloomLevel, domain: item.domain })}`
|
|
16621
17391
|
);
|
|
16622
|
-
console.log(`Domain: ${prompt.domain || "(none)"}`);
|
|
16623
17392
|
if (prompt.sourceLink) {
|
|
16624
17393
|
console.log(`Source: ${prompt.sourceLink}`);
|
|
16625
17394
|
if (opts.resolve !== false) {
|
|
@@ -16694,8 +17463,8 @@ ${"\u2550".repeat(50)}`);
|
|
|
16694
17463
|
// src/cli/commands/session.ts
|
|
16695
17464
|
import { readFileSync as readFileSync13 } from "fs";
|
|
16696
17465
|
import { input as input6, select as select2 } from "@inquirer/prompts";
|
|
16697
|
-
import { Command as
|
|
16698
|
-
var sessionCommand = new
|
|
17466
|
+
import { Command as Command15 } from "commander";
|
|
17467
|
+
var sessionCommand = new Command15("session").description(
|
|
16699
17468
|
"Manage learning sessions"
|
|
16700
17469
|
);
|
|
16701
17470
|
sessionCommand.command("start").description("Start a new learning session (review \u2192 task)").option("--user <id>", "User ID (default: whoami)").option("--task <description>", "Task description (interactive if omitted)").option(
|
|
@@ -16819,9 +17588,8 @@ Time limit reached (${maxMinutes} min). Moving to task selection.`
|
|
|
16819
17588
|
});
|
|
16820
17589
|
const elapsed = Math.round((Date.now() - startTime) / 6e4);
|
|
16821
17590
|
console.log(
|
|
16822
|
-
`[${index + 1}/${queue.items.length}] ${
|
|
17591
|
+
`[${index + 1}/${queue.items.length}] ${formatHeader({ bloomLevel: item.bloomLevel, domain: item.domain })} (${elapsed}/${maxMinutes} min)`
|
|
16823
17592
|
);
|
|
16824
|
-
console.log(`Domain: ${prompt.domain || "(none)"}`);
|
|
16825
17593
|
console.log(`
|
|
16826
17594
|
${prompt.question}
|
|
16827
17595
|
`);
|
|
@@ -17075,8 +17843,8 @@ sessionCommand.command("end").description("End a session and show summary").requ
|
|
|
17075
17843
|
|
|
17076
17844
|
// src/cli/commands/settings.ts
|
|
17077
17845
|
import { existsSync as existsSync21 } from "fs";
|
|
17078
|
-
import { Command as
|
|
17079
|
-
var settingsCommand = new
|
|
17846
|
+
import { Command as Command16 } from "commander";
|
|
17847
|
+
var settingsCommand = new Command16("settings").description(
|
|
17080
17848
|
"Manage user settings"
|
|
17081
17849
|
);
|
|
17082
17850
|
var BOOLEAN_SETTING_KEYS = /* @__PURE__ */ new Set(["llm.enabled", "llm.vision.enabled"]);
|
|
@@ -17252,7 +18020,7 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
|
|
|
17252
18020
|
|
|
17253
18021
|
// src/cli/commands/setup.ts
|
|
17254
18022
|
import { resolve as resolve8 } from "path";
|
|
17255
|
-
import { Command as
|
|
18023
|
+
import { Command as Command17 } from "commander";
|
|
17256
18024
|
function formatDatabaseInitTarget(target) {
|
|
17257
18025
|
switch (target.kind) {
|
|
17258
18026
|
case "local":
|
|
@@ -17293,7 +18061,7 @@ async function activateMachineProviderConfig(db) {
|
|
|
17293
18061
|
` activate ${providerCount} machine-local provider(s) from ~/.zam/config.json`
|
|
17294
18062
|
);
|
|
17295
18063
|
}
|
|
17296
|
-
var setupCommand = new
|
|
18064
|
+
var setupCommand = new Command17("setup").description(
|
|
17297
18065
|
"Link ZAM skill directories into this workspace and initialize the database"
|
|
17298
18066
|
).option("--force", "replace an unmanaged existing ZAM skill directory", false).option("--skip-init", "skip database initialization", false).option("--skip-claude-md", "skip CLAUDE.md generation", false).option("--skip-agents-md", "skip AGENTS.md generation", false).option("--target <path>", "repository/workspace directory to set up").option(
|
|
17299
18067
|
"--agents <list>",
|
|
@@ -17347,8 +18115,8 @@ var setupCommand = new Command16("setup").description(
|
|
|
17347
18115
|
);
|
|
17348
18116
|
|
|
17349
18117
|
// src/cli/commands/skill.ts
|
|
17350
|
-
import { Command as
|
|
17351
|
-
var skillCommand = new
|
|
18118
|
+
import { Command as Command18 } from "commander";
|
|
18119
|
+
var skillCommand = new Command18("skill").description(
|
|
17352
18120
|
"Manage agent skill entries (task recipes)"
|
|
17353
18121
|
);
|
|
17354
18122
|
skillCommand.command("list").description("List all agent skills").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -17435,7 +18203,7 @@ skillCommand.command("add").description("Register a new agent skill").requiredOp
|
|
|
17435
18203
|
// src/cli/commands/snapshot.ts
|
|
17436
18204
|
import { existsSync as existsSync22, mkdirSync as mkdirSync13, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "fs";
|
|
17437
18205
|
import { dirname as dirname7, join as join21 } from "path";
|
|
17438
|
-
import { Command as
|
|
18206
|
+
import { Command as Command19 } from "commander";
|
|
17439
18207
|
function defaultOutName() {
|
|
17440
18208
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
|
|
17441
18209
|
return `zam-snapshot-${stamp}.sql`;
|
|
@@ -17449,7 +18217,7 @@ function summarize(tables) {
|
|
|
17449
18217
|
}
|
|
17450
18218
|
return { total, nonEmpty };
|
|
17451
18219
|
}
|
|
17452
|
-
var exportCmd = new
|
|
18220
|
+
var exportCmd = new Command19("export").description("Write a portable SQL-text snapshot of the database").option("--out <file>", "Output file (use - for stdout)").action(async (opts) => {
|
|
17453
18221
|
let db;
|
|
17454
18222
|
try {
|
|
17455
18223
|
db = await openDatabaseWithSync({ initialize: true });
|
|
@@ -17479,7 +18247,7 @@ var exportCmd = new Command18("export").description("Write a portable SQL-text s
|
|
|
17479
18247
|
process.exit(1);
|
|
17480
18248
|
}
|
|
17481
18249
|
});
|
|
17482
|
-
var importCmd = new
|
|
18250
|
+
var importCmd = new Command19("import").description("Restore a snapshot into the database").argument("<file>", "Snapshot file to restore").option("--force", "Overwrite a non-empty database", false).action(async (file, opts) => {
|
|
17483
18251
|
let db;
|
|
17484
18252
|
try {
|
|
17485
18253
|
if (!existsSync22(file)) {
|
|
@@ -17502,7 +18270,7 @@ var importCmd = new Command18("import").description("Restore a snapshot into the
|
|
|
17502
18270
|
process.exit(1);
|
|
17503
18271
|
}
|
|
17504
18272
|
});
|
|
17505
|
-
var verifyCmd = new
|
|
18273
|
+
var verifyCmd = new Command19("verify").description("Check a snapshot's manifest and checksum without importing").argument("<file>", "Snapshot file to verify").action((file) => {
|
|
17506
18274
|
try {
|
|
17507
18275
|
if (!existsSync22(file)) {
|
|
17508
18276
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
@@ -17520,11 +18288,11 @@ var verifyCmd = new Command18("verify").description("Check a snapshot's manifest
|
|
|
17520
18288
|
process.exit(1);
|
|
17521
18289
|
}
|
|
17522
18290
|
});
|
|
17523
|
-
var snapshotCommand = new
|
|
18291
|
+
var snapshotCommand = new Command19("snapshot").description("Export, import, or verify a portable database snapshot").addCommand(exportCmd).addCommand(importCmd).addCommand(verifyCmd);
|
|
17524
18292
|
|
|
17525
18293
|
// src/cli/commands/stats.ts
|
|
17526
|
-
import { Command as
|
|
17527
|
-
var statsCommand = new
|
|
18294
|
+
import { Command as Command20 } from "commander";
|
|
18295
|
+
var statsCommand = new Command20("stats").description("Show learning dashboard for a user").option("--user <id>", "User ID (default: whoami)").option("--json", "Output as JSON").action(async (opts) => {
|
|
17528
18296
|
await withDb(async (db) => {
|
|
17529
18297
|
const userId = await resolveUser(opts, db);
|
|
17530
18298
|
const stats = await getUserStats(db, userId);
|
|
@@ -17560,8 +18328,8 @@ var statsCommand = new Command19("stats").description("Show learning dashboard f
|
|
|
17560
18328
|
});
|
|
17561
18329
|
|
|
17562
18330
|
// src/cli/commands/token.ts
|
|
17563
|
-
import { Command as
|
|
17564
|
-
var tokenCommand = new
|
|
18331
|
+
import { Command as Command21 } from "commander";
|
|
18332
|
+
var tokenCommand = new Command21("token").description(
|
|
17565
18333
|
"Manage knowledge tokens"
|
|
17566
18334
|
);
|
|
17567
18335
|
tokenCommand.command("register").description("Register a new knowledge token").requiredOption("--slug <slug>", "Unique token slug").requiredOption("--concept <concept>", "Concept description").option("--domain <domain>", "Knowledge domain", "").option("--bloom <level>", "Bloom taxonomy level (1-5)", "1").option("--source-link <link>", "Source file path or reference URL", "").option("--question <question>", "Specific question prompt for recall", "").option("--user <id>", "Owner of the personal card (default: whoami)").option("--no-card", "Register the token only; do not create a personal card").option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action(async (opts) => {
|
|
@@ -17612,7 +18380,11 @@ tokenCommand.command("register").description("Register a new knowledge token").r
|
|
|
17612
18380
|
)
|
|
17613
18381
|
);
|
|
17614
18382
|
} else {
|
|
17615
|
-
console.log(`Registered token: ${token.
|
|
18383
|
+
console.log(`Registered token: ${token.title || token.slug}`);
|
|
18384
|
+
if (token.slug !== (token.title || token.slug)) {
|
|
18385
|
+
console.log(` Slug: ${token.slug}`);
|
|
18386
|
+
}
|
|
18387
|
+
console.log(` ID: ${token.id}`);
|
|
17616
18388
|
console.log(` Concept: ${token.concept}`);
|
|
17617
18389
|
console.log(` Domain: ${token.domain || "(none)"}`);
|
|
17618
18390
|
console.log(` Bloom: ${token.bloom_level}`);
|
|
@@ -17631,8 +18403,9 @@ tokenCommand.command("register").description("Register a new knowledge token").r
|
|
|
17631
18403
|
console.log(`
|
|
17632
18404
|
WARNING: Possible duplicate tokens found:`);
|
|
17633
18405
|
for (const dup of possibleDuplicates) {
|
|
18406
|
+
const name = dup.title || dup.slug;
|
|
17634
18407
|
console.log(
|
|
17635
|
-
` - ${dup.slug}
|
|
18408
|
+
` - ${name} (slug: ${dup.slug}, similarity: ${dup.similarity.toFixed(2)})`
|
|
17636
18409
|
);
|
|
17637
18410
|
}
|
|
17638
18411
|
}
|
|
@@ -17640,7 +18413,8 @@ WARNING: Possible duplicate tokens found:`);
|
|
|
17640
18413
|
const queryText = embeddingContentForToken({
|
|
17641
18414
|
concept: opts.concept,
|
|
17642
18415
|
question: question ?? null,
|
|
17643
|
-
domain: opts.domain ?? ""
|
|
18416
|
+
domain: opts.domain ?? "",
|
|
18417
|
+
title: opts.title ?? null
|
|
17644
18418
|
});
|
|
17645
18419
|
const q2 = await embedQuery(db, queryText);
|
|
17646
18420
|
if (q2) {
|
|
@@ -17707,16 +18481,18 @@ tokenCommand.command("find").description("Fuzzy search for tokens").requiredOpti
|
|
|
17707
18481
|
console.log(`Found ${results.length} token(s):
|
|
17708
18482
|
`);
|
|
17709
18483
|
console.log(
|
|
17710
|
-
"Score Sim
|
|
18484
|
+
"Score Sim Title Concept Domain Bloom"
|
|
17711
18485
|
);
|
|
17712
18486
|
console.log("\u2500".repeat(95));
|
|
17713
18487
|
for (const t2 of results) {
|
|
17714
18488
|
const scoreStr = t2.score.toFixed(3).padEnd(6);
|
|
17715
18489
|
const simStr = (t2.similarity?.toFixed(2) ?? "-").padEnd(4);
|
|
18490
|
+
const display = getDisplayTitle(t2);
|
|
17716
18491
|
console.log(
|
|
17717
|
-
`${scoreStr} ${simStr} ${
|
|
18492
|
+
`${scoreStr} ${simStr} ${display.padEnd(28)} ${t2.concept.slice(0, 31).padEnd(31)} ${(t2.domain || "-").padEnd(11)} ${t2.bloom_level}`
|
|
17718
18493
|
);
|
|
17719
18494
|
}
|
|
18495
|
+
console.log("\n(Use slug for CLI commands -- slugs are technical IDs)");
|
|
17720
18496
|
});
|
|
17721
18497
|
});
|
|
17722
18498
|
tokenCommand.command("list").description("List all tokens").option("--domain <domain>", "Filter by domain").option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action(async (opts) => {
|
|
@@ -17735,16 +18511,20 @@ tokenCommand.command("list").description("List all tokens").option("--domain <do
|
|
|
17735
18511
|
return;
|
|
17736
18512
|
}
|
|
17737
18513
|
console.log(
|
|
17738
|
-
"
|
|
18514
|
+
"Title Concept Domain Bloom"
|
|
17739
18515
|
);
|
|
17740
|
-
console.log("\u2500".repeat(
|
|
18516
|
+
console.log("\u2500".repeat(85));
|
|
17741
18517
|
for (const t2 of tokens) {
|
|
18518
|
+
const display = getDisplayTitle(t2);
|
|
17742
18519
|
console.log(
|
|
17743
|
-
`${
|
|
18520
|
+
`${display.padEnd(28)} ${t2.concept.slice(0, 31).padEnd(31)} ${(t2.domain || "-").padEnd(11)} ${t2.bloom_level}`
|
|
17744
18521
|
);
|
|
17745
18522
|
}
|
|
17746
18523
|
console.log(`
|
|
17747
18524
|
${tokens.length} token(s) total.`);
|
|
18525
|
+
console.log(
|
|
18526
|
+
"(Use --slug <slug> for commands; slugs are technical identifiers)"
|
|
18527
|
+
);
|
|
17748
18528
|
});
|
|
17749
18529
|
});
|
|
17750
18530
|
tokenCommand.command("edit").description("Edit a token's mutable fields").requiredOption("--slug <slug>", "Token slug").option("--concept <concept>", "Updated concept text").option("--domain <domain>", "Updated domain (blank allowed)").option("--bloom <level>", "Updated Bloom taxonomy level (1-5)").option("--context <context>", "Updated context (blank allowed)").option(
|
|
@@ -17753,7 +18533,10 @@ tokenCommand.command("edit").description("Edit a token's mutable fields").requir
|
|
|
17753
18533
|
).option(
|
|
17754
18534
|
"--source-link <link>",
|
|
17755
18535
|
"Updated source file path or reference URL (blank allowed)"
|
|
17756
|
-
).option("--question <question>", "Updated question text (blank allowed)").option(
|
|
18536
|
+
).option("--question <question>", "Updated question text (blank allowed)").option(
|
|
18537
|
+
"--title <title>",
|
|
18538
|
+
"Updated human-friendly title for graph display (blank allowed)"
|
|
18539
|
+
).option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action(async (opts) => {
|
|
17757
18540
|
await withDb(async (db) => {
|
|
17758
18541
|
const updates = {};
|
|
17759
18542
|
if (opts.concept !== void 0) updates.concept = opts.concept;
|
|
@@ -17767,6 +18550,9 @@ tokenCommand.command("edit").description("Edit a token's mutable fields").requir
|
|
|
17767
18550
|
if (opts.question !== void 0) {
|
|
17768
18551
|
updates.question = opts.question === "" ? null : opts.question;
|
|
17769
18552
|
}
|
|
18553
|
+
if (opts.title !== void 0) {
|
|
18554
|
+
updates.title = opts.title === "" ? "" : opts.title;
|
|
18555
|
+
}
|
|
17770
18556
|
if (opts.mode !== void 0) {
|
|
17771
18557
|
const validModes = ["shadowing", "copilot", "autonomy", "none"];
|
|
17772
18558
|
if (!validModes.includes(opts.mode)) {
|
|
@@ -17781,11 +18567,15 @@ tokenCommand.command("edit").description("Edit a token's mutable fields").requir
|
|
|
17781
18567
|
jsonOut(token);
|
|
17782
18568
|
return;
|
|
17783
18569
|
}
|
|
17784
|
-
console.log(`Updated token: ${token.slug}`);
|
|
18570
|
+
console.log(`Updated token: ${token.title || token.slug}`);
|
|
18571
|
+
if (token.slug !== (token.title || token.slug)) {
|
|
18572
|
+
console.log(` Slug: ${token.slug}`);
|
|
18573
|
+
}
|
|
17785
18574
|
console.log(` Concept: ${token.concept}`);
|
|
17786
18575
|
console.log(` Domain: ${token.domain || "(none)"}`);
|
|
17787
18576
|
console.log(` Bloom: ${token.bloom_level}`);
|
|
17788
18577
|
console.log(` Question: ${token.question || "(none)"}`);
|
|
18578
|
+
console.log(` Title: ${token.title || "(none)"}`);
|
|
17789
18579
|
console.log(` Context: ${token.context || "(none)"}`);
|
|
17790
18580
|
console.log(` Mode: ${token.symbiosis_mode ?? "none"}`);
|
|
17791
18581
|
console.log(` Source: ${token.source_link || "(none)"}`);
|
|
@@ -17904,7 +18694,11 @@ tokenCommand.command("status").description("Show full status of a token for a us
|
|
|
17904
18694
|
console.log(JSON.stringify(status, null, 2));
|
|
17905
18695
|
return;
|
|
17906
18696
|
}
|
|
17907
|
-
console.log(`Token: ${token.
|
|
18697
|
+
console.log(`Token: ${token.title || token.slug}`);
|
|
18698
|
+
if (token.slug !== (token.title || token.slug)) {
|
|
18699
|
+
console.log(` Slug: ${token.slug}`);
|
|
18700
|
+
}
|
|
18701
|
+
console.log(` ID: ${token.id}`);
|
|
17908
18702
|
console.log(` Concept: ${token.concept}`);
|
|
17909
18703
|
console.log(` Question: ${token.question || "(none)"}`);
|
|
17910
18704
|
console.log(` Domain: ${token.domain || "(none)"}`);
|
|
@@ -17929,7 +18723,9 @@ tokenCommand.command("status").description("Show full status of a token for a us
|
|
|
17929
18723
|
if (prereqs.length > 0) {
|
|
17930
18724
|
console.log("Prerequisites:");
|
|
17931
18725
|
for (const p of prereqs) {
|
|
17932
|
-
console.log(
|
|
18726
|
+
console.log(
|
|
18727
|
+
` - ${p.title || p.slug}: ${p.concept} (bloom ${p.bloom_level})`
|
|
18728
|
+
);
|
|
17933
18729
|
}
|
|
17934
18730
|
} else {
|
|
17935
18731
|
console.log("No prerequisites.");
|
|
@@ -17937,7 +18733,9 @@ tokenCommand.command("status").description("Show full status of a token for a us
|
|
|
17937
18733
|
if (dependents.length > 0) {
|
|
17938
18734
|
console.log("\nDependents:");
|
|
17939
18735
|
for (const d of dependents) {
|
|
17940
|
-
console.log(
|
|
18736
|
+
console.log(
|
|
18737
|
+
` - ${d.title || d.slug}: ${d.concept} (bloom ${d.bloom_level})`
|
|
18738
|
+
);
|
|
17941
18739
|
}
|
|
17942
18740
|
}
|
|
17943
18741
|
});
|
|
@@ -17999,7 +18797,7 @@ import { existsSync as existsSync23 } from "fs";
|
|
|
17999
18797
|
import { homedir as homedir12 } from "os";
|
|
18000
18798
|
import { dirname as dirname8, join as join22 } from "path";
|
|
18001
18799
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
18002
|
-
import { Command as
|
|
18800
|
+
import { Command as Command22 } from "commander";
|
|
18003
18801
|
var C3 = {
|
|
18004
18802
|
reset: "\x1B[0m",
|
|
18005
18803
|
red: "\x1B[31m",
|
|
@@ -18179,7 +18977,7 @@ function createShortcuts(appPath, repoRoot) {
|
|
|
18179
18977
|
console.error(`${C3.red}\u2717 Could not create shortcuts.${C3.reset}`);
|
|
18180
18978
|
}
|
|
18181
18979
|
}
|
|
18182
|
-
var uiCommand = new
|
|
18980
|
+
var uiCommand = new Command22("ui").description("Launch the ZAM Desktop GUI (Active-Recall Studio)").option("--dev", "Run in hot-reload development mode (needs Rust)").option(
|
|
18183
18981
|
"--build",
|
|
18184
18982
|
"Build the native installer for your OS (needs Rust, one-time)"
|
|
18185
18983
|
).option("--shortcut", "Create Desktop + Start-menu shortcuts to the GUI").action((opts) => {
|
|
@@ -18287,7 +19085,7 @@ import { existsSync as existsSync24, readFileSync as readFileSync15, realpathSyn
|
|
|
18287
19085
|
import { dirname as dirname9, join as join23 } from "path";
|
|
18288
19086
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
18289
19087
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
18290
|
-
import { Command as
|
|
19088
|
+
import { Command as Command23 } from "commander";
|
|
18291
19089
|
var GITHUB_REPO = "zam-os/zam";
|
|
18292
19090
|
var CHANNELS = [
|
|
18293
19091
|
"developer",
|
|
@@ -18365,7 +19163,7 @@ function render2(decision) {
|
|
|
18365
19163
|
);
|
|
18366
19164
|
}
|
|
18367
19165
|
}
|
|
18368
|
-
var checkCmd = new
|
|
19166
|
+
var checkCmd = new Command23("check").description("Check whether a newer ZAM has been released").option(
|
|
18369
19167
|
"--latest <version>",
|
|
18370
19168
|
"Compare against this version instead of fetching"
|
|
18371
19169
|
).option("--channel <channel>", "Override the detected install channel").option("--json", "Output as JSON").action(
|
|
@@ -18532,7 +19330,7 @@ ${C4.dim}This install updates through the desktop app's signed updater \u2014 op
|
|
|
18532
19330
|
process.exit(1);
|
|
18533
19331
|
}
|
|
18534
19332
|
}
|
|
18535
|
-
var updateCommand = new
|
|
19333
|
+
var updateCommand = new Command23("update").description(
|
|
18536
19334
|
"Update ZAM to the latest release (use `update check` to only check)"
|
|
18537
19335
|
).option("-y, --yes", "Apply without confirmation").option(
|
|
18538
19336
|
"--force",
|
|
@@ -18542,8 +19340,8 @@ var updateCommand = new Command22("update").description(
|
|
|
18542
19340
|
}).addCommand(checkCmd);
|
|
18543
19341
|
|
|
18544
19342
|
// src/cli/commands/whoami.ts
|
|
18545
|
-
import { Command as
|
|
18546
|
-
var whoamiCommand = new
|
|
19343
|
+
import { Command as Command24 } from "commander";
|
|
19344
|
+
var whoamiCommand = new Command24("whoami").description("Show or set the default user identity").option("--set <id>", "Set the default user ID").option("--clear", "Remove the default user ID").option("--json", "Output as JSON").action(async (opts) => {
|
|
18547
19345
|
await withDb(async (db) => {
|
|
18548
19346
|
if (opts.set) {
|
|
18549
19347
|
await setSetting(db, "user.id", opts.set);
|
|
@@ -18584,7 +19382,7 @@ import { existsSync as existsSync25, writeFileSync as writeFileSync11 } from "fs
|
|
|
18584
19382
|
import { homedir as homedir13 } from "os";
|
|
18585
19383
|
import { join as join24, resolve as resolve9 } from "path";
|
|
18586
19384
|
import { confirm as confirm4, input as input7 } from "@inquirer/prompts";
|
|
18587
|
-
import { Command as
|
|
19385
|
+
import { Command as Command25 } from "commander";
|
|
18588
19386
|
function runGit2(cwd, args) {
|
|
18589
19387
|
try {
|
|
18590
19388
|
return execFileSync4("git", args, {
|
|
@@ -18602,7 +19400,7 @@ function ghRepoCreateArgs(repoName, repoVisibility) {
|
|
|
18602
19400
|
function gitRemoteArgs(githubUrl, hasOrigin) {
|
|
18603
19401
|
return hasOrigin ? ["remote", "set-url", "origin", githubUrl] : ["remote", "add", "origin", githubUrl];
|
|
18604
19402
|
}
|
|
18605
|
-
var workspaceCommand = new
|
|
19403
|
+
var workspaceCommand = new Command25("workspace").description(
|
|
18606
19404
|
"Manage your ZAM learning workspace"
|
|
18607
19405
|
);
|
|
18608
19406
|
var WORKSPACE_KINDS = [
|
|
@@ -18957,13 +19755,14 @@ var __dirname = dirname10(fileURLToPath5(import.meta.url));
|
|
|
18957
19755
|
var pkg = JSON.parse(
|
|
18958
19756
|
readFileSync16(join25(__dirname, "..", "..", "package.json"), "utf-8")
|
|
18959
19757
|
);
|
|
18960
|
-
var program = new
|
|
19758
|
+
var program = new Command26();
|
|
18961
19759
|
program.name("zam").description(
|
|
18962
19760
|
"The Symbiotic Learning Kernel: Elevating Human Intelligence through AI Collaboration."
|
|
18963
19761
|
).version(pkg.version);
|
|
18964
19762
|
program.addCommand(initCommand);
|
|
18965
19763
|
program.addCommand(setupCommand);
|
|
18966
19764
|
program.addCommand(tokenCommand);
|
|
19765
|
+
program.addCommand(doctorCommand);
|
|
18967
19766
|
program.addCommand(cardCommand);
|
|
18968
19767
|
program.addCommand(sessionCommand);
|
|
18969
19768
|
program.addCommand(statsCommand);
|