zam-core 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1194 -136
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +78 -6
- package/dist/index.js +255 -50
- 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 Command27 } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/cli/commands/agent.ts
|
|
10
10
|
import { existsSync as existsSync13 } from "fs";
|
|
@@ -682,6 +682,22 @@ CREATE TABLE IF NOT EXISTS agent_skills (
|
|
|
682
682
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
683
683
|
);
|
|
684
684
|
|
|
685
|
+
-- Knowledge contexts: work, school, private
|
|
686
|
+
CREATE TABLE IF NOT EXISTS contexts (
|
|
687
|
+
id TEXT PRIMARY KEY,
|
|
688
|
+
name TEXT NOT NULL UNIQUE,
|
|
689
|
+
label TEXT,
|
|
690
|
+
language TEXT,
|
|
691
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
692
|
+
);
|
|
693
|
+
|
|
694
|
+
-- Join table mapping tokens to their knowledge contexts
|
|
695
|
+
CREATE TABLE IF NOT EXISTS token_contexts (
|
|
696
|
+
token_id TEXT NOT NULL REFERENCES tokens(id) ON DELETE CASCADE,
|
|
697
|
+
context_id TEXT NOT NULL REFERENCES contexts(id) ON DELETE CASCADE,
|
|
698
|
+
PRIMARY KEY (token_id, context_id)
|
|
699
|
+
);
|
|
700
|
+
|
|
685
701
|
-- Performance indexes
|
|
686
702
|
CREATE INDEX IF NOT EXISTS idx_tokens_domain ON tokens(domain);
|
|
687
703
|
CREATE INDEX IF NOT EXISTS idx_tokens_slug ON tokens(slug);
|
|
@@ -693,7 +709,7 @@ CREATE INDEX IF NOT EXISTS idx_review_logs_card ON review_logs(card_id);
|
|
|
693
709
|
CREATE INDEX IF NOT EXISTS idx_review_logs_user ON review_logs(user_id, reviewed_at);
|
|
694
710
|
CREATE INDEX IF NOT EXISTS idx_session_steps_session ON session_steps(session_id);
|
|
695
711
|
CREATE INDEX IF NOT EXISTS idx_tokens_title ON tokens(title);
|
|
696
|
-
CREATE INDEX IF NOT EXISTS
|
|
712
|
+
CREATE INDEX IF NOT EXISTS idx_token_contexts_context ON token_contexts(context_id);
|
|
697
713
|
`;
|
|
698
714
|
|
|
699
715
|
// src/kernel/db/sync-adapter.ts
|
|
@@ -1036,6 +1052,25 @@ async function runMigrations(db) {
|
|
|
1036
1052
|
await db.exec(
|
|
1037
1053
|
`CREATE INDEX IF NOT EXISTS idx_tokens_domain ON tokens(domain)`
|
|
1038
1054
|
);
|
|
1055
|
+
await db.exec(`
|
|
1056
|
+
CREATE TABLE IF NOT EXISTS contexts (
|
|
1057
|
+
id TEXT PRIMARY KEY,
|
|
1058
|
+
name TEXT NOT NULL UNIQUE,
|
|
1059
|
+
label TEXT,
|
|
1060
|
+
language TEXT,
|
|
1061
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1062
|
+
)
|
|
1063
|
+
`);
|
|
1064
|
+
await db.exec(`
|
|
1065
|
+
CREATE TABLE IF NOT EXISTS token_contexts (
|
|
1066
|
+
token_id TEXT NOT NULL REFERENCES tokens(id) ON DELETE CASCADE,
|
|
1067
|
+
context_id TEXT NOT NULL REFERENCES contexts(id) ON DELETE CASCADE,
|
|
1068
|
+
PRIMARY KEY (token_id, context_id)
|
|
1069
|
+
)
|
|
1070
|
+
`);
|
|
1071
|
+
await db.exec(`
|
|
1072
|
+
CREATE INDEX IF NOT EXISTS idx_token_contexts_context ON token_contexts(context_id)
|
|
1073
|
+
`);
|
|
1039
1074
|
}
|
|
1040
1075
|
|
|
1041
1076
|
// src/kernel/db/snapshot.ts
|
|
@@ -1499,31 +1534,98 @@ async function deleteCardForUser(db, tokenId, userId) {
|
|
|
1499
1534
|
await db.prepare("DELETE FROM cards WHERE id = ?").run(card.id);
|
|
1500
1535
|
return { card, impact };
|
|
1501
1536
|
}
|
|
1502
|
-
async function getDueCards(db, userId, now, domain) {
|
|
1537
|
+
async function getDueCards(db, userId, now, domain, knowledgeContext) {
|
|
1503
1538
|
const cutoff = now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1539
|
+
let sql = `SELECT c.*, t.slug, t.concept, t.domain, t.bloom_level
|
|
1540
|
+
FROM cards c
|
|
1541
|
+
JOIN tokens t ON t.id = c.token_id
|
|
1542
|
+
WHERE c.user_id = ? AND c.blocked = 0 AND c.due_at <= ?`;
|
|
1543
|
+
const params = [userId, cutoff];
|
|
1504
1544
|
if (domain) {
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
FROM cards c
|
|
1508
|
-
JOIN tokens t ON t.id = c.token_id
|
|
1509
|
-
WHERE c.user_id = ? AND c.blocked = 0 AND c.due_at <= ? AND t.domain = ?
|
|
1510
|
-
ORDER BY t.bloom_level ASC, c.due_at ASC`
|
|
1511
|
-
).all(userId, cutoff, domain);
|
|
1545
|
+
sql += " AND t.domain = ?";
|
|
1546
|
+
params.push(domain);
|
|
1512
1547
|
}
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1548
|
+
if (knowledgeContext) {
|
|
1549
|
+
sql += ` AND EXISTS (
|
|
1550
|
+
SELECT 1 FROM token_contexts tc
|
|
1551
|
+
INNER JOIN contexts context_filter ON context_filter.id = tc.context_id
|
|
1552
|
+
WHERE tc.token_id = t.id AND context_filter.name = ?
|
|
1553
|
+
)`;
|
|
1554
|
+
params.push(knowledgeContext);
|
|
1555
|
+
}
|
|
1556
|
+
sql += " ORDER BY t.bloom_level ASC, c.due_at ASC";
|
|
1557
|
+
return await db.prepare(sql).all(...params);
|
|
1520
1558
|
}
|
|
1521
1559
|
|
|
1522
|
-
// src/kernel/models/
|
|
1560
|
+
// src/kernel/models/knowledge-context.ts
|
|
1523
1561
|
import { ulid as ulid3 } from "ulid";
|
|
1524
|
-
|
|
1562
|
+
function normalizeContextName(name) {
|
|
1563
|
+
const normalized = name.trim();
|
|
1564
|
+
if (!normalized) {
|
|
1565
|
+
throw new Error("Context name cannot be empty");
|
|
1566
|
+
}
|
|
1567
|
+
return normalized;
|
|
1568
|
+
}
|
|
1569
|
+
function normalizeOptionalText(value) {
|
|
1570
|
+
if (value == null) return null;
|
|
1571
|
+
const normalized = value.trim();
|
|
1572
|
+
return normalized || null;
|
|
1573
|
+
}
|
|
1574
|
+
async function createKnowledgeContext(db, input8) {
|
|
1525
1575
|
const id = ulid3();
|
|
1526
1576
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1577
|
+
const name = normalizeContextName(input8.name);
|
|
1578
|
+
const existing = await getKnowledgeContextByName(db, name);
|
|
1579
|
+
if (existing) {
|
|
1580
|
+
throw new Error(`Knowledge context with name "${name}" already exists`);
|
|
1581
|
+
}
|
|
1582
|
+
await db.prepare(
|
|
1583
|
+
`INSERT INTO contexts (id, name, label, language, created_at)
|
|
1584
|
+
VALUES (?, ?, ?, ?, ?)`
|
|
1585
|
+
).run(
|
|
1586
|
+
id,
|
|
1587
|
+
name,
|
|
1588
|
+
normalizeOptionalText(input8.label),
|
|
1589
|
+
normalizeOptionalText(input8.language),
|
|
1590
|
+
now
|
|
1591
|
+
);
|
|
1592
|
+
const context = await getKnowledgeContextById(db, id);
|
|
1593
|
+
if (!context) {
|
|
1594
|
+
throw new Error(
|
|
1595
|
+
`Failed to retrieve newly created knowledge context: ${id}`
|
|
1596
|
+
);
|
|
1597
|
+
}
|
|
1598
|
+
return context;
|
|
1599
|
+
}
|
|
1600
|
+
async function getKnowledgeContextByName(db, name) {
|
|
1601
|
+
const normalized = name.trim();
|
|
1602
|
+
if (!normalized) return void 0;
|
|
1603
|
+
return await db.prepare("SELECT * FROM contexts WHERE name = ?").get(normalized);
|
|
1604
|
+
}
|
|
1605
|
+
async function getKnowledgeContextById(db, id) {
|
|
1606
|
+
return await db.prepare("SELECT * FROM contexts WHERE id = ?").get(id);
|
|
1607
|
+
}
|
|
1608
|
+
async function listKnowledgeContexts(db) {
|
|
1609
|
+
return await db.prepare("SELECT * FROM contexts ORDER BY name ASC").all();
|
|
1610
|
+
}
|
|
1611
|
+
async function deleteKnowledgeContext(db, id) {
|
|
1612
|
+
await db.prepare("DELETE FROM contexts WHERE id = ?").run(id);
|
|
1613
|
+
}
|
|
1614
|
+
async function assignTokenToContext(db, tokenId, contextId) {
|
|
1615
|
+
await db.prepare(
|
|
1616
|
+
`INSERT OR IGNORE INTO token_contexts (token_id, context_id)
|
|
1617
|
+
VALUES (?, ?)`
|
|
1618
|
+
).run(tokenId, contextId);
|
|
1619
|
+
}
|
|
1620
|
+
async function unassignTokenFromContext(db, tokenId, contextId) {
|
|
1621
|
+
await db.prepare("DELETE FROM token_contexts WHERE token_id = ? AND context_id = ?").run(tokenId, contextId);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
// src/kernel/models/token.ts
|
|
1625
|
+
import { ulid as ulid4 } from "ulid";
|
|
1626
|
+
async function createToken(db, input8) {
|
|
1627
|
+
const id = ulid4();
|
|
1628
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1527
1629
|
const bloom = input8.bloom_level ?? 1;
|
|
1528
1630
|
if (bloom < 1 || bloom > 5) {
|
|
1529
1631
|
throw new Error(`bloom_level must be between 1 and 5, got ${bloom}`);
|
|
@@ -1759,21 +1861,27 @@ async function findTokens(db, query) {
|
|
|
1759
1861
|
return scored;
|
|
1760
1862
|
}
|
|
1761
1863
|
async function listTokens(db, options) {
|
|
1762
|
-
|
|
1864
|
+
const whereClauses = ["deprecated_at IS NULL"];
|
|
1865
|
+
const params = [];
|
|
1763
1866
|
if (options?.domain) {
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
).all(options.domain);
|
|
1867
|
+
whereClauses.push("domain = ?");
|
|
1868
|
+
params.push(options.domain);
|
|
1767
1869
|
} else if (options?.domainPrefix) {
|
|
1768
1870
|
const prefix = options.domainPrefix;
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1871
|
+
whereClauses.push("(domain = ? OR domain LIKE ?)");
|
|
1872
|
+
params.push(prefix, `${prefix}/%`);
|
|
1873
|
+
}
|
|
1874
|
+
if (options?.knowledgeContext) {
|
|
1875
|
+
whereClauses.push(`EXISTS (
|
|
1876
|
+
SELECT 1 FROM token_contexts tc
|
|
1877
|
+
INNER JOIN contexts c ON c.id = tc.context_id
|
|
1878
|
+
WHERE tc.token_id = tokens.id AND c.name = ?
|
|
1879
|
+
)`);
|
|
1880
|
+
params.push(options.knowledgeContext);
|
|
1881
|
+
}
|
|
1882
|
+
const orderBy = options?.domain || options?.domainPrefix ? "ORDER BY bloom_level, slug" : "ORDER BY bloom_level, domain, slug";
|
|
1883
|
+
const sql = `SELECT * FROM tokens WHERE ${whereClauses.join(" AND ")} ${orderBy}`;
|
|
1884
|
+
const tokens = await db.prepare(sql).all(...params);
|
|
1777
1885
|
for (const token of tokens) {
|
|
1778
1886
|
parseTokenFallback(token);
|
|
1779
1887
|
}
|
|
@@ -1864,6 +1972,14 @@ async function listPersonalCards(db, userId, options) {
|
|
|
1864
1972
|
sql += " AND t.domain = ?";
|
|
1865
1973
|
values.push(options.domain);
|
|
1866
1974
|
}
|
|
1975
|
+
if (options?.knowledgeContext) {
|
|
1976
|
+
sql += ` AND EXISTS (
|
|
1977
|
+
SELECT 1 FROM token_contexts tc
|
|
1978
|
+
INNER JOIN contexts kc ON kc.id = tc.context_id
|
|
1979
|
+
WHERE tc.token_id = t.id AND kc.name = ?
|
|
1980
|
+
)`;
|
|
1981
|
+
values.push(options.knowledgeContext);
|
|
1982
|
+
}
|
|
1867
1983
|
if (options?.query) {
|
|
1868
1984
|
const terms = options.query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
1869
1985
|
for (const term of terms) {
|
|
@@ -2389,12 +2505,12 @@ async function getTokenNeighborhood(db, tokenId, userId) {
|
|
|
2389
2505
|
}
|
|
2390
2506
|
|
|
2391
2507
|
// src/kernel/models/review.ts
|
|
2392
|
-
import { ulid as
|
|
2508
|
+
import { ulid as ulid5 } from "ulid";
|
|
2393
2509
|
|
|
2394
2510
|
// src/kernel/models/session.ts
|
|
2395
|
-
import { ulid as
|
|
2511
|
+
import { ulid as ulid6 } from "ulid";
|
|
2396
2512
|
async function startSession(db, input8) {
|
|
2397
|
-
const id =
|
|
2513
|
+
const id = ulid6();
|
|
2398
2514
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2399
2515
|
const ctx = input8.execution_context ?? "shell";
|
|
2400
2516
|
await db.prepare(
|
|
@@ -2428,7 +2544,7 @@ async function logStep(db, input8) {
|
|
|
2428
2544
|
if (!session) {
|
|
2429
2545
|
throw new Error(`Session not found: ${input8.session_id}`);
|
|
2430
2546
|
}
|
|
2431
|
-
const id =
|
|
2547
|
+
const id = ulid6();
|
|
2432
2548
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2433
2549
|
await db.prepare(
|
|
2434
2550
|
`INSERT INTO session_steps (id, session_id, token_id, done_by, rating, notes, created_at)
|
|
@@ -3310,10 +3426,10 @@ async function syncObserverSidecarPolicy(db, dir = getUiObserverDir()) {
|
|
|
3310
3426
|
}
|
|
3311
3427
|
|
|
3312
3428
|
// src/kernel/observation/session-synthesis.ts
|
|
3313
|
-
import { ulid as
|
|
3429
|
+
import { ulid as ulid8 } from "ulid";
|
|
3314
3430
|
|
|
3315
3431
|
// src/kernel/recall/evaluator.ts
|
|
3316
|
-
import { ulid as
|
|
3432
|
+
import { ulid as ulid7 } from "ulid";
|
|
3317
3433
|
|
|
3318
3434
|
// src/kernel/scheduler/fsrs.ts
|
|
3319
3435
|
var DEFAULT_W = [
|
|
@@ -3492,7 +3608,7 @@ async function evaluateRatingWithinTransaction(db, input8) {
|
|
|
3492
3608
|
due_at: updated.dueAt.toISOString(),
|
|
3493
3609
|
last_review_at: now.toISOString()
|
|
3494
3610
|
});
|
|
3495
|
-
const reviewLogId = input8.reviewLogId ??
|
|
3611
|
+
const reviewLogId = input8.reviewLogId ?? ulid7();
|
|
3496
3612
|
await db.prepare(
|
|
3497
3613
|
`INSERT INTO review_logs (id, card_id, token_id, user_id, rating, response_time_ms, reviewed_at, scheduled_at, session_id)
|
|
3498
3614
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
@@ -3826,7 +3942,7 @@ async function applySessionSynthesis(db, input8) {
|
|
|
3826
3942
|
return { applied: false, record: parseSynthesisRow(existing) };
|
|
3827
3943
|
}
|
|
3828
3944
|
const card = await ensureCard(tx, token.id, session.user_id);
|
|
3829
|
-
const reviewLogId =
|
|
3945
|
+
const reviewLogId = ulid8();
|
|
3830
3946
|
await evaluateRatingWithinTransaction(tx, {
|
|
3831
3947
|
cardId: card.id,
|
|
3832
3948
|
tokenId: token.id,
|
|
@@ -4652,8 +4768,7 @@ async function buildReviewQueue(db, options) {
|
|
|
4652
4768
|
const maxReviews = options.maxReviews ?? 50;
|
|
4653
4769
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
4654
4770
|
const nowISO = now.toISOString();
|
|
4655
|
-
|
|
4656
|
-
`SELECT
|
|
4771
|
+
let dueSql = `SELECT
|
|
4657
4772
|
c.id AS card_id,
|
|
4658
4773
|
c.token_id AS token_id,
|
|
4659
4774
|
t.slug AS slug,
|
|
@@ -4671,11 +4786,19 @@ async function buildReviewQueue(db, options) {
|
|
|
4671
4786
|
AND c.blocked = 0
|
|
4672
4787
|
AND c.due_at <= ?
|
|
4673
4788
|
AND c.state IN ('review', 'relearning', 'learning')
|
|
4674
|
-
AND t.deprecated_at IS NULL
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4789
|
+
AND t.deprecated_at IS NULL`;
|
|
4790
|
+
const dueParams = [options.userId, nowISO];
|
|
4791
|
+
if (options.knowledgeContext) {
|
|
4792
|
+
dueSql += ` AND EXISTS (
|
|
4793
|
+
SELECT 1 FROM token_contexts tc
|
|
4794
|
+
INNER JOIN contexts ctx ON ctx.id = tc.context_id
|
|
4795
|
+
WHERE tc.token_id = t.id AND ctx.name = ?
|
|
4796
|
+
)`;
|
|
4797
|
+
dueParams.push(options.knowledgeContext);
|
|
4798
|
+
}
|
|
4799
|
+
dueSql += ` ORDER BY c.due_at ASC`;
|
|
4800
|
+
const dueRows = await db.prepare(dueSql).all(...dueParams);
|
|
4801
|
+
let newSql = `SELECT
|
|
4679
4802
|
c.id AS card_id,
|
|
4680
4803
|
c.token_id AS token_id,
|
|
4681
4804
|
t.slug AS slug,
|
|
@@ -4692,10 +4815,19 @@ async function buildReviewQueue(db, options) {
|
|
|
4692
4815
|
WHERE c.user_id = ?
|
|
4693
4816
|
AND c.blocked = 0
|
|
4694
4817
|
AND c.state = 'new'
|
|
4695
|
-
AND t.deprecated_at IS NULL
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4818
|
+
AND t.deprecated_at IS NULL`;
|
|
4819
|
+
const newParams = [options.userId];
|
|
4820
|
+
if (options.knowledgeContext) {
|
|
4821
|
+
newSql += ` AND EXISTS (
|
|
4822
|
+
SELECT 1 FROM token_contexts tc
|
|
4823
|
+
INNER JOIN contexts ctx ON ctx.id = tc.context_id
|
|
4824
|
+
WHERE tc.token_id = t.id AND ctx.name = ?
|
|
4825
|
+
)`;
|
|
4826
|
+
newParams.push(options.knowledgeContext);
|
|
4827
|
+
}
|
|
4828
|
+
newSql += ` ORDER BY t.bloom_level ASC, t.slug ASC LIMIT ?`;
|
|
4829
|
+
newParams.push(maxNew);
|
|
4830
|
+
const newRows = await db.prepare(newSql).all(...newParams);
|
|
4699
4831
|
const nowMs = now.getTime();
|
|
4700
4832
|
const sortedDue = [...dueRows].sort((a, b) => {
|
|
4701
4833
|
const overdueA = nowMs - new Date(a.due_at).getTime();
|
|
@@ -5426,6 +5558,27 @@ function detectSyncProvider(dir) {
|
|
|
5426
5558
|
}
|
|
5427
5559
|
return null;
|
|
5428
5560
|
}
|
|
5561
|
+
function getActiveWorkspaceContext(path = defaultConfigPath()) {
|
|
5562
|
+
const activeWorkspace = getActiveWorkspace(path);
|
|
5563
|
+
return activeWorkspace?.activeKnowledgeContext;
|
|
5564
|
+
}
|
|
5565
|
+
function setActiveWorkspaceContext(contextName, path = defaultConfigPath()) {
|
|
5566
|
+
const config = loadInstallConfig(path);
|
|
5567
|
+
const activeId = config.activeWorkspaceId;
|
|
5568
|
+
if (activeId && config.workspaces) {
|
|
5569
|
+
const workspace = config.workspaces.find((w) => w.id === activeId);
|
|
5570
|
+
if (workspace) {
|
|
5571
|
+
if (contextName) {
|
|
5572
|
+
workspace.activeKnowledgeContext = contextName;
|
|
5573
|
+
} else {
|
|
5574
|
+
delete workspace.activeKnowledgeContext;
|
|
5575
|
+
}
|
|
5576
|
+
saveInstallConfig(config, path);
|
|
5577
|
+
return true;
|
|
5578
|
+
}
|
|
5579
|
+
}
|
|
5580
|
+
return false;
|
|
5581
|
+
}
|
|
5429
5582
|
|
|
5430
5583
|
// src/kernel/system/installer.ts
|
|
5431
5584
|
import { execFileSync, execSync } from "child_process";
|
|
@@ -6292,7 +6445,7 @@ import { existsSync as existsSync17, readdirSync as readdirSync2, readFileSync a
|
|
|
6292
6445
|
import { homedir as homedir10, tmpdir as tmpdir3 } from "os";
|
|
6293
6446
|
import { join as join18, resolve as resolve4 } from "path";
|
|
6294
6447
|
import { Command as Command2 } from "commander";
|
|
6295
|
-
import { ulid as
|
|
6448
|
+
import { ulid as ulid9 } from "ulid";
|
|
6296
6449
|
|
|
6297
6450
|
// src/cli/adapters/source-reader.ts
|
|
6298
6451
|
import dns from "dns";
|
|
@@ -6819,20 +6972,28 @@ JSON Array Output:`;
|
|
|
6819
6972
|
);
|
|
6820
6973
|
return readChatContent(res, "LLM curriculum import");
|
|
6821
6974
|
}
|
|
6822
|
-
async function importCurriculumViaLLM(db, text, targetCategory, sourceUrl) {
|
|
6975
|
+
async function importCurriculumViaLLM(db, text, targetCategory, sourceUrl, options) {
|
|
6823
6976
|
if (text.length > MAX_IMPORT_TEXT_CHARS) {
|
|
6824
6977
|
throw new Error(
|
|
6825
6978
|
`Curriculum text exceeds the ${MAX_IMPORT_TEXT_CHARS.toLocaleString()} character limit`
|
|
6826
6979
|
);
|
|
6827
6980
|
}
|
|
6828
6981
|
const cfg = await getProviderForRole(db, "text");
|
|
6982
|
+
let locale = cfg.locale;
|
|
6983
|
+
const contextName = options?.knowledgeContext || getActiveWorkspaceContext();
|
|
6984
|
+
if (contextName) {
|
|
6985
|
+
const context = await getKnowledgeContextByName(db, contextName);
|
|
6986
|
+
if (context?.language) {
|
|
6987
|
+
locale = normalizeLocale(context.language);
|
|
6988
|
+
}
|
|
6989
|
+
}
|
|
6829
6990
|
const endpoint = await resolveUsableTextEndpoint(db);
|
|
6830
|
-
const langName = LANGUAGE_NAMES[
|
|
6991
|
+
const langName = LANGUAGE_NAMES[locale] || "English";
|
|
6831
6992
|
let responseText;
|
|
6832
6993
|
try {
|
|
6833
6994
|
responseText = await requestCurriculumCards(
|
|
6834
6995
|
endpoint,
|
|
6835
|
-
|
|
6996
|
+
locale,
|
|
6836
6997
|
langName,
|
|
6837
6998
|
text,
|
|
6838
6999
|
targetCategory,
|
|
@@ -6849,7 +7010,7 @@ async function importCurriculumViaLLM(db, text, targetCategory, sourceUrl) {
|
|
|
6849
7010
|
try {
|
|
6850
7011
|
chunkResponse = await requestCurriculumCards(
|
|
6851
7012
|
endpoint,
|
|
6852
|
-
|
|
7013
|
+
locale,
|
|
6853
7014
|
langName,
|
|
6854
7015
|
chunk,
|
|
6855
7016
|
targetCategory,
|
|
@@ -7703,6 +7864,14 @@ async function ensureHighQualityQuestion(db, token) {
|
|
|
7703
7864
|
}
|
|
7704
7865
|
async function generateTitleViaLLM(db, input8, opts = {}) {
|
|
7705
7866
|
const cfg = await getProviderForRole(db, "text");
|
|
7867
|
+
let locale = cfg.locale;
|
|
7868
|
+
const contextName = opts.knowledgeContext || getActiveWorkspaceContext();
|
|
7869
|
+
if (contextName) {
|
|
7870
|
+
const context = await getKnowledgeContextByName(db, contextName);
|
|
7871
|
+
if (context?.language) {
|
|
7872
|
+
locale = normalizeLocale(context.language);
|
|
7873
|
+
}
|
|
7874
|
+
}
|
|
7706
7875
|
const endpoint = await resolveUsableTextEndpoint(db);
|
|
7707
7876
|
const systemPrompt = `You are an expert at naming knowledge items for a personal knowledge graph.
|
|
7708
7877
|
Your task: given a knowledge token's concept (the full reference answer), question, domain, and optional context, produce a short, descriptive, human-friendly TITLE.
|
|
@@ -7710,7 +7879,7 @@ Your task: given a knowledge token's concept (the full reference answer), questi
|
|
|
7710
7879
|
Strict rules from the project ADR:
|
|
7711
7880
|
- Concise name for the concept (\u2264 80 chars ideal).
|
|
7712
7881
|
- 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 "
|
|
7882
|
+
- NEVER echo the domain name (e.g. do not say "Workflow Engine Node Drain" if domain is "workflow-engine"; just "Node Drain Protection").
|
|
7714
7883
|
- Prefer a name over spoiling the full concept.
|
|
7715
7884
|
- Support Unicode (umlauts, etc.).
|
|
7716
7885
|
- 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).
|
|
@@ -7747,7 +7916,7 @@ Title:`;
|
|
|
7747
7916
|
temperature: 0.2,
|
|
7748
7917
|
max_tokens: 100
|
|
7749
7918
|
}),
|
|
7750
|
-
locale
|
|
7919
|
+
locale,
|
|
7751
7920
|
timeoutMs: opts.timeoutMs,
|
|
7752
7921
|
hardTimeoutMs: opts.timeoutMs
|
|
7753
7922
|
});
|
|
@@ -7883,7 +8052,7 @@ async function readWebLink(url) {
|
|
|
7883
8052
|
redirect: "manual",
|
|
7884
8053
|
signal: controller.signal,
|
|
7885
8054
|
headers: {
|
|
7886
|
-
"User-Agent": "ZAM-Content-Studio/0.
|
|
8055
|
+
"User-Agent": "ZAM-Content-Studio/0.8.0"
|
|
7887
8056
|
}
|
|
7888
8057
|
});
|
|
7889
8058
|
if (res.status >= 300 && res.status < 400) {
|
|
@@ -10607,6 +10776,25 @@ function getCurriculumProvider(id) {
|
|
|
10607
10776
|
return CURRICULUM_PROVIDERS.find((provider) => provider.id === id);
|
|
10608
10777
|
}
|
|
10609
10778
|
|
|
10779
|
+
// src/cli/knowledge-contexts.ts
|
|
10780
|
+
async function resolveOperationKnowledgeContexts(db, requestedNames) {
|
|
10781
|
+
const explicitNames = [
|
|
10782
|
+
...new Set(requestedNames.map((name) => name.trim()).filter(Boolean))
|
|
10783
|
+
];
|
|
10784
|
+
const activeDefault = getActiveWorkspaceContext();
|
|
10785
|
+
const selectedNames = explicitNames.length > 0 ? explicitNames : activeDefault ? [activeDefault] : [];
|
|
10786
|
+
const contexts = [];
|
|
10787
|
+
for (const name of selectedNames) {
|
|
10788
|
+
const context = await getKnowledgeContextByName(db, name);
|
|
10789
|
+
if (!context) {
|
|
10790
|
+
const subject = explicitNames.length > 0 ? "Knowledge" : "Active knowledge";
|
|
10791
|
+
throw new Error(`${subject} context not found: ${name}`);
|
|
10792
|
+
}
|
|
10793
|
+
contexts.push(context);
|
|
10794
|
+
}
|
|
10795
|
+
return contexts;
|
|
10796
|
+
}
|
|
10797
|
+
|
|
10610
10798
|
// src/cli/llm/embedder.ts
|
|
10611
10799
|
var CANONICAL_EMBEDDING_MODEL_ID = "embeddinggemma-300m";
|
|
10612
10800
|
var EMBEDDINGGEMMA_ALIASES = /* @__PURE__ */ new Set([
|
|
@@ -11921,6 +12109,20 @@ function jsonError(message) {
|
|
|
11921
12109
|
console.log(JSON.stringify({ error: msg }, null, 2));
|
|
11922
12110
|
process.exit(1);
|
|
11923
12111
|
}
|
|
12112
|
+
function parseKnowledgeContextNames(value) {
|
|
12113
|
+
if (value == null) return [];
|
|
12114
|
+
if (!Array.isArray(value) || value.some((name) => typeof name !== "string" || !name.trim())) {
|
|
12115
|
+
jsonError("knowledgeContexts must be an array of non-empty context names");
|
|
12116
|
+
}
|
|
12117
|
+
return [...new Set(value.map((name) => name.trim()))];
|
|
12118
|
+
}
|
|
12119
|
+
async function resolveKnowledgeContexts(db, names) {
|
|
12120
|
+
try {
|
|
12121
|
+
return await resolveOperationKnowledgeContexts(db, names);
|
|
12122
|
+
} catch (error) {
|
|
12123
|
+
jsonError(error.message);
|
|
12124
|
+
}
|
|
12125
|
+
}
|
|
11924
12126
|
function parseNonNegativeIntegerOption(name, value) {
|
|
11925
12127
|
const parsed = Number(value);
|
|
11926
12128
|
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
@@ -11974,16 +12176,23 @@ function parseTokenUpdates(opts) {
|
|
|
11974
12176
|
var bridgeCommand = new Command2("bridge").description(
|
|
11975
12177
|
"Machine-readable JSON protocol for AI integration"
|
|
11976
12178
|
);
|
|
11977
|
-
bridgeCommand.command("check-due").description("Check due cards for a user (JSON)").option("--user <id>", "User ID (default: whoami)").option("--domain <domain>", "Filter by knowledge domain").action(async (opts) => {
|
|
12179
|
+
bridgeCommand.command("check-due").description("Check due cards for a user (JSON)").option("--user <id>", "User ID (default: whoami)").option("--domain <domain>", "Filter by knowledge domain").option("--knowledge-context <context>", "Filter by knowledge context").action(async (opts) => {
|
|
11978
12180
|
await withDb2(async (db) => {
|
|
11979
12181
|
const userId = await resolveUser(opts, db, { json: true });
|
|
11980
|
-
const dueCards = await getDueCards(
|
|
12182
|
+
const dueCards = await getDueCards(
|
|
12183
|
+
db,
|
|
12184
|
+
userId,
|
|
12185
|
+
void 0,
|
|
12186
|
+
opts.domain,
|
|
12187
|
+
opts.knowledgeContext
|
|
12188
|
+
);
|
|
11981
12189
|
const domains = [
|
|
11982
12190
|
...new Set(dueCards.map((c) => c.domain).filter(Boolean))
|
|
11983
12191
|
].sort();
|
|
11984
12192
|
jsonOut2({
|
|
11985
12193
|
userId,
|
|
11986
12194
|
domain: opts.domain ?? null,
|
|
12195
|
+
knowledgeContext: opts.knowledgeContext ?? null,
|
|
11987
12196
|
dueCount: dueCards.length,
|
|
11988
12197
|
domains,
|
|
11989
12198
|
cards: dueCards.map((c) => ({
|
|
@@ -12244,13 +12453,14 @@ bridgeCommand.command("agent-open").description("Launch an agent harness in the
|
|
|
12244
12453
|
bridgeCommand.command("get-review").description("Get next review card with prompt (JSON)").option("--user <id>", "User ID (default: whoami)").option("--no-resolve", "Skip resolving the token's source_link into context").option(
|
|
12245
12454
|
"--no-dynamic-question",
|
|
12246
12455
|
"Use the stored question without generating a fresh LLM question"
|
|
12247
|
-
).action(async (opts) => {
|
|
12456
|
+
).option("--knowledge-context <context>", "Filter cards by knowledge context").action(async (opts) => {
|
|
12248
12457
|
await withDb2(async (db) => {
|
|
12249
12458
|
const userId = await resolveUser(opts, db, { json: true });
|
|
12250
12459
|
const queue = await buildReviewQueue(db, {
|
|
12251
12460
|
userId,
|
|
12252
12461
|
maxReviews: 1,
|
|
12253
|
-
maxNew: 1
|
|
12462
|
+
maxNew: 1,
|
|
12463
|
+
knowledgeContext: opts.knowledgeContext || void 0
|
|
12254
12464
|
});
|
|
12255
12465
|
if (queue.items.length === 0) {
|
|
12256
12466
|
jsonOut2({
|
|
@@ -12305,7 +12515,10 @@ bridgeCommand.command("get-review").description("Get next review card with promp
|
|
|
12305
12515
|
resolvedContext = null;
|
|
12306
12516
|
}
|
|
12307
12517
|
}
|
|
12308
|
-
const fullQueue = await buildReviewQueue(db, {
|
|
12518
|
+
const fullQueue = await buildReviewQueue(db, {
|
|
12519
|
+
userId,
|
|
12520
|
+
knowledgeContext: opts.knowledgeContext || void 0
|
|
12521
|
+
});
|
|
12309
12522
|
jsonOut2({
|
|
12310
12523
|
userId,
|
|
12311
12524
|
hasReview: true,
|
|
@@ -12579,6 +12792,10 @@ bridgeCommand.command("add-token").description("Create a token + card from JSON
|
|
|
12579
12792
|
domain: data?.domain,
|
|
12580
12793
|
title: data?.title ?? null
|
|
12581
12794
|
});
|
|
12795
|
+
const ctxNames = parseKnowledgeContextNames(
|
|
12796
|
+
data?.knowledgeContexts ?? data?.knowledge_contexts
|
|
12797
|
+
);
|
|
12798
|
+
const assignedContexts = await resolveKnowledgeContexts(db, ctxNames);
|
|
12582
12799
|
const token = await createToken(db, {
|
|
12583
12800
|
slug: data?.slug,
|
|
12584
12801
|
title: data?.title,
|
|
@@ -12590,6 +12807,9 @@ bridgeCommand.command("add-token").description("Create a token + card from JSON
|
|
|
12590
12807
|
source_link: data?.source_link ?? null,
|
|
12591
12808
|
question: data?.question ?? null
|
|
12592
12809
|
});
|
|
12810
|
+
for (const context of assignedContexts) {
|
|
12811
|
+
await assignTokenToContext(db, token.id, context.id);
|
|
12812
|
+
}
|
|
12593
12813
|
const card = await ensureCard(db, token.id, userId);
|
|
12594
12814
|
try {
|
|
12595
12815
|
await ensureTokenEmbeddings(db, { limit: 8 });
|
|
@@ -12597,7 +12817,14 @@ bridgeCommand.command("add-token").description("Create a token + card from JSON
|
|
|
12597
12817
|
}
|
|
12598
12818
|
jsonOut2({
|
|
12599
12819
|
success: true,
|
|
12600
|
-
token
|
|
12820
|
+
token: {
|
|
12821
|
+
...token,
|
|
12822
|
+
knowledgeContexts: assignedContexts.map((c) => ({
|
|
12823
|
+
name: c.name,
|
|
12824
|
+
label: c.label,
|
|
12825
|
+
language: c.language
|
|
12826
|
+
}))
|
|
12827
|
+
},
|
|
12601
12828
|
card: {
|
|
12602
12829
|
id: card.id,
|
|
12603
12830
|
tokenId: card.token_id,
|
|
@@ -12657,6 +12884,22 @@ bridgeCommand.command("relevant-tokens").description("Find tokens relevant to a
|
|
|
12657
12884
|
limit
|
|
12658
12885
|
});
|
|
12659
12886
|
const tokens = [];
|
|
12887
|
+
const contextMap = /* @__PURE__ */ new Map();
|
|
12888
|
+
if (results.length > 0) {
|
|
12889
|
+
const ids = results.map((t2) => t2.id);
|
|
12890
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
12891
|
+
const mappings = await db.prepare(
|
|
12892
|
+
`SELECT tc.token_id, c.name, c.label, c.language
|
|
12893
|
+
FROM token_contexts tc
|
|
12894
|
+
INNER JOIN contexts c ON c.id = tc.context_id
|
|
12895
|
+
WHERE tc.token_id IN (${placeholders})`
|
|
12896
|
+
).all(...ids);
|
|
12897
|
+
for (const m of mappings) {
|
|
12898
|
+
const list = contextMap.get(m.token_id) ?? [];
|
|
12899
|
+
list.push({ name: m.name, label: m.label, language: m.language });
|
|
12900
|
+
contextMap.set(m.token_id, list);
|
|
12901
|
+
}
|
|
12902
|
+
}
|
|
12660
12903
|
for (const t2 of results) {
|
|
12661
12904
|
const card = await getCard(db, t2.id, userId);
|
|
12662
12905
|
tokens.push({
|
|
@@ -12668,6 +12911,7 @@ bridgeCommand.command("relevant-tokens").description("Find tokens relevant to a
|
|
|
12668
12911
|
bloom_level: t2.bloom_level,
|
|
12669
12912
|
score: t2.score,
|
|
12670
12913
|
similarity: t2.similarity,
|
|
12914
|
+
knowledgeContexts: contextMap.get(t2.id) ?? [],
|
|
12671
12915
|
card: card ? {
|
|
12672
12916
|
state: card.state,
|
|
12673
12917
|
due_at: card.due_at,
|
|
@@ -13960,13 +14204,15 @@ bridgeCommand.command("list-tokens").description(
|
|
|
13960
14204
|
"User ID (default: whoami) \u2014 when provided, includes personal card info"
|
|
13961
14205
|
).option("--domain <domain>", "Filter by exact domain").option(
|
|
13962
14206
|
"--domain-prefix <prefix>",
|
|
13963
|
-
"Filter by domain prefix (e.g.
|
|
13964
|
-
).action(async (opts) => {
|
|
14207
|
+
"Filter by domain prefix (e.g. company-team) \u2014 uses / separator for hierarchy"
|
|
14208
|
+
).option("--knowledge-context <context>", "Filter by knowledge context").action(async (opts) => {
|
|
13965
14209
|
await withDb2(async (db) => {
|
|
13966
14210
|
const userId = opts.user ? await resolveUser(opts, db, { json: true }) : void 0;
|
|
13967
14211
|
const listOpts = {};
|
|
13968
14212
|
if (opts.domain) listOpts.domain = opts.domain;
|
|
13969
14213
|
if (opts.domainPrefix) listOpts.domainPrefix = opts.domainPrefix;
|
|
14214
|
+
if (opts.knowledgeContext)
|
|
14215
|
+
listOpts.knowledgeContext = opts.knowledgeContext;
|
|
13970
14216
|
const tokens = await listTokens(
|
|
13971
14217
|
db,
|
|
13972
14218
|
Object.keys(listOpts).length ? listOpts : void 0
|
|
@@ -13981,6 +14227,22 @@ bridgeCommand.command("list-tokens").description(
|
|
|
13981
14227
|
).all(...ids, userId);
|
|
13982
14228
|
for (const c of cards) cardMap.set(c.token_id, c);
|
|
13983
14229
|
}
|
|
14230
|
+
const contextMap = /* @__PURE__ */ new Map();
|
|
14231
|
+
if (tokens.length > 0) {
|
|
14232
|
+
const ids = tokens.map((t2) => t2.id);
|
|
14233
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
14234
|
+
const mappings = await db.prepare(
|
|
14235
|
+
`SELECT tc.token_id, c.name, c.label, c.language
|
|
14236
|
+
FROM token_contexts tc
|
|
14237
|
+
INNER JOIN contexts c ON c.id = tc.context_id
|
|
14238
|
+
WHERE tc.token_id IN (${placeholders})`
|
|
14239
|
+
).all(...ids);
|
|
14240
|
+
for (const m of mappings) {
|
|
14241
|
+
const list = contextMap.get(m.token_id) ?? [];
|
|
14242
|
+
list.push({ name: m.name, label: m.label, language: m.language });
|
|
14243
|
+
contextMap.set(m.token_id, list);
|
|
14244
|
+
}
|
|
14245
|
+
}
|
|
13984
14246
|
const out = tokens.map((t2) => {
|
|
13985
14247
|
const c = cardMap.get(t2.id);
|
|
13986
14248
|
return {
|
|
@@ -13991,6 +14253,7 @@ bridgeCommand.command("list-tokens").description(
|
|
|
13991
14253
|
concept: t2.concept,
|
|
13992
14254
|
domain: t2.domain,
|
|
13993
14255
|
bloomLevel: t2.bloom_level,
|
|
14256
|
+
knowledgeContexts: contextMap.get(t2.id) ?? [],
|
|
13994
14257
|
card: c ? {
|
|
13995
14258
|
state: c.state,
|
|
13996
14259
|
reps: c.reps,
|
|
@@ -14018,6 +14281,23 @@ bridgeCommand.command("get-neighborhood").description(
|
|
|
14018
14281
|
jsonError(`Token not found: ${opts.focus}`);
|
|
14019
14282
|
}
|
|
14020
14283
|
const nb = await getTokenNeighborhood(db, token.id, userId);
|
|
14284
|
+
const allTokens = [nb.center, ...nb.prerequisites, ...nb.dependents];
|
|
14285
|
+
const contextMap = /* @__PURE__ */ new Map();
|
|
14286
|
+
if (allTokens.length > 0) {
|
|
14287
|
+
const ids = allTokens.map((t2) => t2.id);
|
|
14288
|
+
const placeholders = ids.map(() => "?").join(",");
|
|
14289
|
+
const mappings = await db.prepare(
|
|
14290
|
+
`SELECT tc.token_id, c.name, c.label, c.language
|
|
14291
|
+
FROM token_contexts tc
|
|
14292
|
+
INNER JOIN contexts c ON c.id = tc.context_id
|
|
14293
|
+
WHERE tc.token_id IN (${placeholders})`
|
|
14294
|
+
).all(...ids);
|
|
14295
|
+
for (const m of mappings) {
|
|
14296
|
+
const list = contextMap.get(m.token_id) ?? [];
|
|
14297
|
+
list.push({ name: m.name, label: m.label, language: m.language });
|
|
14298
|
+
contextMap.set(m.token_id, list);
|
|
14299
|
+
}
|
|
14300
|
+
}
|
|
14021
14301
|
const mapToken = (nt) => ({
|
|
14022
14302
|
id: nt.id,
|
|
14023
14303
|
slug: nt.slug,
|
|
@@ -14026,6 +14306,7 @@ bridgeCommand.command("get-neighborhood").description(
|
|
|
14026
14306
|
concept: nt.concept,
|
|
14027
14307
|
domain: nt.domain,
|
|
14028
14308
|
bloomLevel: nt.bloom_level,
|
|
14309
|
+
knowledgeContexts: contextMap.get(nt.id) ?? [],
|
|
14029
14310
|
card: nt.card ? {
|
|
14030
14311
|
state: nt.card.state,
|
|
14031
14312
|
reps: nt.card.reps,
|
|
@@ -14044,23 +14325,62 @@ bridgeCommand.command("get-neighborhood").description(
|
|
|
14044
14325
|
});
|
|
14045
14326
|
});
|
|
14046
14327
|
});
|
|
14047
|
-
bridgeCommand.command("personal-card-list").description("List and search personal learning cards (JSON)").option("--user <id>", "User ID (default: whoami)").option("--query <query>", "Text search query").option("--domain <domain>", "Filter by category/domain").action(async (opts) => {
|
|
14328
|
+
bridgeCommand.command("personal-card-list").description("List and search personal learning cards (JSON)").option("--user <id>", "User ID (default: whoami)").option("--query <query>", "Text search query").option("--domain <domain>", "Filter by category/domain").option("--knowledge-context <context>", "Filter by knowledge context").action(async (opts) => {
|
|
14048
14329
|
await withDb2(async (db) => {
|
|
14049
14330
|
const userId = await resolveUser(opts, db, { json: true });
|
|
14050
14331
|
const cards = await listPersonalCards(db, userId, {
|
|
14051
14332
|
query: opts.query,
|
|
14052
|
-
domain: opts.domain
|
|
14333
|
+
domain: opts.domain,
|
|
14334
|
+
knowledgeContext: opts.knowledgeContext
|
|
14335
|
+
});
|
|
14336
|
+
const contextMap = /* @__PURE__ */ new Map();
|
|
14337
|
+
if (cards.length > 0) {
|
|
14338
|
+
const tokenIds = cards.map((card) => card.tokenId);
|
|
14339
|
+
const placeholders = tokenIds.map(() => "?").join(",");
|
|
14340
|
+
const mappings = await db.prepare(
|
|
14341
|
+
`SELECT tc.token_id, c.name, c.label, c.language
|
|
14342
|
+
FROM token_contexts tc
|
|
14343
|
+
INNER JOIN contexts c ON c.id = tc.context_id
|
|
14344
|
+
WHERE tc.token_id IN (${placeholders})
|
|
14345
|
+
ORDER BY c.name`
|
|
14346
|
+
).all(...tokenIds);
|
|
14347
|
+
for (const mapping of mappings) {
|
|
14348
|
+
const contexts = contextMap.get(mapping.token_id) ?? [];
|
|
14349
|
+
contexts.push({
|
|
14350
|
+
name: mapping.name,
|
|
14351
|
+
label: mapping.label,
|
|
14352
|
+
language: mapping.language
|
|
14353
|
+
});
|
|
14354
|
+
contextMap.set(mapping.token_id, contexts);
|
|
14355
|
+
}
|
|
14356
|
+
}
|
|
14357
|
+
jsonOut2({
|
|
14358
|
+
cards: cards.map((card) => ({
|
|
14359
|
+
...card,
|
|
14360
|
+
knowledgeContexts: contextMap.get(card.tokenId) ?? []
|
|
14361
|
+
}))
|
|
14053
14362
|
});
|
|
14054
|
-
jsonOut2({ cards });
|
|
14055
14363
|
});
|
|
14056
14364
|
});
|
|
14057
14365
|
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(
|
|
14058
14366
|
"--mode <mode>",
|
|
14059
14367
|
"Symbiosis mode: shadowing | copilot | autonomy | none",
|
|
14060
14368
|
"none"
|
|
14061
|
-
).option("--context <context>", "Context description", "").
|
|
14369
|
+
).option("--context <context>", "Context description", "").option(
|
|
14370
|
+
"--knowledge-context <context>",
|
|
14371
|
+
"Assign token to a knowledge context (repeatable)",
|
|
14372
|
+
(val, memo) => {
|
|
14373
|
+
memo.push(val);
|
|
14374
|
+
return memo;
|
|
14375
|
+
},
|
|
14376
|
+
[]
|
|
14377
|
+
).action(async (opts) => {
|
|
14062
14378
|
await withDb2(async (db) => {
|
|
14063
14379
|
const userId = await resolveUser(opts, db, { json: true });
|
|
14380
|
+
const contextNames = parseKnowledgeContextNames(
|
|
14381
|
+
opts.knowledgeContext || []
|
|
14382
|
+
);
|
|
14383
|
+
const contexts = await resolveKnowledgeContexts(db, contextNames);
|
|
14064
14384
|
const bloom = Number(opts.bloom);
|
|
14065
14385
|
if (bloom < 1 || bloom > 5) {
|
|
14066
14386
|
jsonError("bloom must be between 1 and 5");
|
|
@@ -14091,6 +14411,9 @@ bridgeCommand.command("personal-card-create").description("Atomically create a t
|
|
|
14091
14411
|
source_link: opts.sourceLink || null,
|
|
14092
14412
|
question
|
|
14093
14413
|
});
|
|
14414
|
+
for (const context of contexts) {
|
|
14415
|
+
await assignTokenToContext(tx, createdToken.id, context.id);
|
|
14416
|
+
}
|
|
14094
14417
|
const createdCard = await ensureCard(tx, createdToken.id, userId);
|
|
14095
14418
|
return { token: createdToken, card: createdCard };
|
|
14096
14419
|
});
|
|
@@ -14109,7 +14432,12 @@ bridgeCommand.command("personal-card-create").description("Atomically create a t
|
|
|
14109
14432
|
sourceLink: token.source_link,
|
|
14110
14433
|
question: token.question,
|
|
14111
14434
|
createdAt: token.created_at,
|
|
14112
|
-
updatedAt: token.updated_at
|
|
14435
|
+
updatedAt: token.updated_at,
|
|
14436
|
+
knowledgeContexts: contexts.map((context) => ({
|
|
14437
|
+
name: context.name,
|
|
14438
|
+
label: context.label,
|
|
14439
|
+
language: context.language
|
|
14440
|
+
}))
|
|
14113
14441
|
},
|
|
14114
14442
|
card: {
|
|
14115
14443
|
id: card.id,
|
|
@@ -14263,14 +14591,28 @@ bridgeCommand.command("personal-card-delete").description("Hard-delete a token a
|
|
|
14263
14591
|
bridgeCommand.command("personal-card-import-curriculum").description("Parse curriculum text using LLM and import cards (JSON)").option("--user <id>", "User ID (default: whoami)").requiredOption("--text <text>", "Curriculum syllabus/content text").requiredOption(
|
|
14264
14592
|
"--domain <domain>",
|
|
14265
14593
|
"Default category/domain for imported cards"
|
|
14266
|
-
).option("--source <url>", "Provenance source link or URL").option("--preview", "Return parsed cards without saving them").
|
|
14594
|
+
).option("--source <url>", "Provenance source link or URL").option("--preview", "Return parsed cards without saving them").option(
|
|
14595
|
+
"--knowledge-context <context>",
|
|
14596
|
+
"Assign imported tokens to a knowledge context (repeatable)",
|
|
14597
|
+
(val, memo) => {
|
|
14598
|
+
memo.push(val);
|
|
14599
|
+
return memo;
|
|
14600
|
+
},
|
|
14601
|
+
[]
|
|
14602
|
+
).action(async (opts) => {
|
|
14267
14603
|
await withDb2(async (db) => {
|
|
14268
14604
|
const userId = await resolveUser(opts, db, { json: true });
|
|
14605
|
+
const contextNames = parseKnowledgeContextNames(
|
|
14606
|
+
opts.knowledgeContext || []
|
|
14607
|
+
);
|
|
14608
|
+
const contexts = await resolveKnowledgeContexts(db, contextNames);
|
|
14609
|
+
const firstContext = contexts[0]?.name;
|
|
14269
14610
|
const cards = await importCurriculumViaLLM(
|
|
14270
14611
|
db,
|
|
14271
14612
|
opts.text,
|
|
14272
14613
|
opts.domain,
|
|
14273
|
-
opts.source || null
|
|
14614
|
+
opts.source || null,
|
|
14615
|
+
{ knowledgeContext: firstContext }
|
|
14274
14616
|
);
|
|
14275
14617
|
if (opts.preview) {
|
|
14276
14618
|
jsonOut2({
|
|
@@ -14280,6 +14622,24 @@ bridgeCommand.command("personal-card-import-curriculum").description("Parse curr
|
|
|
14280
14622
|
return;
|
|
14281
14623
|
}
|
|
14282
14624
|
const result = await importCurriculumCards(db, userId, cards);
|
|
14625
|
+
for (const card of cards) {
|
|
14626
|
+
const baseText = card.question && card.question.trim().length > 0 ? card.question : card.concept;
|
|
14627
|
+
const cleanDomain = slugify(card.domain || "");
|
|
14628
|
+
const cleanBase = slugify(baseText);
|
|
14629
|
+
let baseSlug = cleanDomain ? `${cleanDomain}-${cleanBase}` : cleanBase;
|
|
14630
|
+
if (baseSlug.length > 60) {
|
|
14631
|
+
baseSlug = baseSlug.slice(0, 60).replace(/-$/, "");
|
|
14632
|
+
}
|
|
14633
|
+
if (!baseSlug) {
|
|
14634
|
+
baseSlug = "token";
|
|
14635
|
+
}
|
|
14636
|
+
const token = await getTokenBySlug(db, baseSlug);
|
|
14637
|
+
if (token) {
|
|
14638
|
+
for (const context of contexts) {
|
|
14639
|
+
await assignTokenToContext(db, token.id, context.id);
|
|
14640
|
+
}
|
|
14641
|
+
}
|
|
14642
|
+
}
|
|
14283
14643
|
jsonOut2({
|
|
14284
14644
|
success: true,
|
|
14285
14645
|
createdCount: result.createdCount,
|
|
@@ -14418,7 +14778,7 @@ bridgeCommand.command("personal-source-import").description(
|
|
|
14418
14778
|
} else {
|
|
14419
14779
|
throw new Error(`Invalid source type: ${opts.type}`);
|
|
14420
14780
|
}
|
|
14421
|
-
const sourceId =
|
|
14781
|
+
const sourceId = ulid9();
|
|
14422
14782
|
await db.prepare(
|
|
14423
14783
|
`INSERT INTO sources (id, type, uri, content)
|
|
14424
14784
|
VALUES (?, ?, ?, ?)
|
|
@@ -14440,16 +14800,46 @@ bridgeCommand.command("personal-source-confirm-import").description(
|
|
|
14440
14800
|
).option("--user <id>", "User ID (default: whoami)").requiredOption("--sourceId <id>", "Source database ID").requiredOption(
|
|
14441
14801
|
"--proposals <json>",
|
|
14442
14802
|
"JSON string representing proposals array"
|
|
14803
|
+
).option(
|
|
14804
|
+
"--knowledge-context <context>",
|
|
14805
|
+
"Assign confirmed tokens to a knowledge context (repeatable)",
|
|
14806
|
+
(val, memo) => {
|
|
14807
|
+
memo.push(val);
|
|
14808
|
+
return memo;
|
|
14809
|
+
},
|
|
14810
|
+
[]
|
|
14443
14811
|
).action(async (opts) => {
|
|
14444
14812
|
await withDb2(async (db) => {
|
|
14445
14813
|
const userId = await resolveUser(opts, db, { json: true });
|
|
14446
14814
|
const proposals = JSON.parse(opts.proposals);
|
|
14815
|
+
const contextNames = parseKnowledgeContextNames(
|
|
14816
|
+
opts.knowledgeContext || []
|
|
14817
|
+
);
|
|
14818
|
+
const contexts = await resolveKnowledgeContexts(db, contextNames);
|
|
14447
14819
|
const result = await confirmSourceImport(
|
|
14448
14820
|
db,
|
|
14449
14821
|
userId,
|
|
14450
14822
|
opts.sourceId,
|
|
14451
14823
|
proposals
|
|
14452
14824
|
);
|
|
14825
|
+
for (const p of proposals) {
|
|
14826
|
+
const baseText = p.question && p.question.trim().length > 0 ? p.question : p.concept;
|
|
14827
|
+
const cleanDomain = slugify(p.domain || "");
|
|
14828
|
+
const cleanBase = slugify(baseText);
|
|
14829
|
+
let baseSlug = cleanDomain ? `${cleanDomain}-${cleanBase}` : cleanBase;
|
|
14830
|
+
if (baseSlug.length > 60) {
|
|
14831
|
+
baseSlug = baseSlug.slice(0, 60).replace(/-$/, "");
|
|
14832
|
+
}
|
|
14833
|
+
if (!baseSlug) {
|
|
14834
|
+
baseSlug = "token";
|
|
14835
|
+
}
|
|
14836
|
+
const token = await getTokenBySlug(db, baseSlug);
|
|
14837
|
+
if (token) {
|
|
14838
|
+
for (const context of contexts) {
|
|
14839
|
+
await assignTokenToContext(db, token.id, context.id);
|
|
14840
|
+
}
|
|
14841
|
+
}
|
|
14842
|
+
}
|
|
14453
14843
|
jsonOut2({
|
|
14454
14844
|
success: true,
|
|
14455
14845
|
createdCount: result.createdCount,
|
|
@@ -14607,7 +14997,7 @@ bridgeCommand.command("curriculum-extract-topics").description(
|
|
|
14607
14997
|
}
|
|
14608
14998
|
}
|
|
14609
14999
|
const pageCleanedText = cleanHtml(rawHtml);
|
|
14610
|
-
const sourceId =
|
|
15000
|
+
const sourceId = ulid9();
|
|
14611
15001
|
await db.prepare(
|
|
14612
15002
|
`INSERT INTO sources (id, type, uri, content)
|
|
14613
15003
|
VALUES (?, 'web', ?, ?)
|
|
@@ -14656,6 +15046,71 @@ bridgeCommand.command("curriculum-set-last-selection").description("Persist the
|
|
|
14656
15046
|
jsonOut2({ success: true });
|
|
14657
15047
|
});
|
|
14658
15048
|
});
|
|
15049
|
+
bridgeCommand.command("list-knowledge-contexts").description("List all knowledge contexts (JSON)").action(async () => {
|
|
15050
|
+
await withDb2(async (db) => {
|
|
15051
|
+
const contexts = await listKnowledgeContexts(db);
|
|
15052
|
+
jsonOut2({ success: true, contexts });
|
|
15053
|
+
});
|
|
15054
|
+
});
|
|
15055
|
+
bridgeCommand.command("assign-knowledge-context").description("Assign a token to a knowledge context (JSON)").requiredOption("--token <slug>", "Token slug").requiredOption("--context <name>", "Context name").action(async (opts) => {
|
|
15056
|
+
await withDb2(async (db) => {
|
|
15057
|
+
const context = await getKnowledgeContextByName(db, opts.context);
|
|
15058
|
+
if (!context) {
|
|
15059
|
+
jsonError(`Knowledge context not found: ${opts.context}`);
|
|
15060
|
+
}
|
|
15061
|
+
const token = await getTokenBySlug(db, opts.token);
|
|
15062
|
+
if (!token) {
|
|
15063
|
+
jsonError(`Token not found: ${opts.token}`);
|
|
15064
|
+
}
|
|
15065
|
+
await assignTokenToContext(db, token.id, context.id);
|
|
15066
|
+
jsonOut2({ success: true, token: token.slug, context: context.name });
|
|
15067
|
+
});
|
|
15068
|
+
});
|
|
15069
|
+
bridgeCommand.command("unassign-knowledge-context").description("Remove a token from a knowledge context (JSON)").requiredOption("--token <slug>", "Token slug").requiredOption("--context <name>", "Context name").action(async (opts) => {
|
|
15070
|
+
await withDb2(async (db) => {
|
|
15071
|
+
const context = await getKnowledgeContextByName(db, opts.context);
|
|
15072
|
+
if (!context) {
|
|
15073
|
+
jsonError(`Knowledge context not found: ${opts.context}`);
|
|
15074
|
+
}
|
|
15075
|
+
const token = await getTokenBySlug(db, opts.token);
|
|
15076
|
+
if (!token) {
|
|
15077
|
+
jsonError(`Token not found: ${opts.token}`);
|
|
15078
|
+
}
|
|
15079
|
+
await unassignTokenFromContext(db, token.id, context.id);
|
|
15080
|
+
jsonOut2({ success: true, token: token.slug, context: context.name });
|
|
15081
|
+
});
|
|
15082
|
+
});
|
|
15083
|
+
bridgeCommand.command("get-active-knowledge-context").description("Get the active knowledge context name (JSON)").action(async () => {
|
|
15084
|
+
await withDb2(async (db) => {
|
|
15085
|
+
const configured = getActiveWorkspaceContext();
|
|
15086
|
+
const active = configured ? await getKnowledgeContextByName(db, configured) : void 0;
|
|
15087
|
+
jsonOut2({
|
|
15088
|
+
success: true,
|
|
15089
|
+
activeContext: active?.name ?? null,
|
|
15090
|
+
staleContext: configured && !active ? configured : null
|
|
15091
|
+
});
|
|
15092
|
+
});
|
|
15093
|
+
});
|
|
15094
|
+
bridgeCommand.command("set-active-knowledge-context").description("Set the active knowledge context name (JSON)").argument("[name]", "Context name to use (use empty/none to clear)").action(async (name) => {
|
|
15095
|
+
await withDb2(async (db) => {
|
|
15096
|
+
if (!name || name === "none" || name === "null" || name === "undefined") {
|
|
15097
|
+
if (!setActiveWorkspaceContext(void 0)) {
|
|
15098
|
+
jsonError("No active workspace configured");
|
|
15099
|
+
}
|
|
15100
|
+
jsonOut2({ success: true, activeContext: null });
|
|
15101
|
+
return;
|
|
15102
|
+
}
|
|
15103
|
+
const context = await getKnowledgeContextByName(db, name);
|
|
15104
|
+
if (!context) {
|
|
15105
|
+
jsonError(`Knowledge context not found: ${name}`);
|
|
15106
|
+
return;
|
|
15107
|
+
}
|
|
15108
|
+
if (!setActiveWorkspaceContext(context.name)) {
|
|
15109
|
+
jsonError("No active workspace configured");
|
|
15110
|
+
}
|
|
15111
|
+
jsonOut2({ success: true, activeContext: context.name });
|
|
15112
|
+
});
|
|
15113
|
+
});
|
|
14659
15114
|
bridgeCommand.command("serve").description("Start the persistent JSON-RPC stdin/stdout server").option("--stdin", "Use stdin/stdout for communication").action(async (_opts) => {
|
|
14660
15115
|
isServeMode = true;
|
|
14661
15116
|
const {
|
|
@@ -14790,10 +15245,16 @@ import { Command as Command3 } from "commander";
|
|
|
14790
15245
|
var cardCommand = new Command3("card").description(
|
|
14791
15246
|
"Manage spaced-repetition cards"
|
|
14792
15247
|
);
|
|
14793
|
-
cardCommand.command("due").description("Show due tokens for a user").option("--user <id>", "User ID (default: whoami)").option("--domain <domain>", "Filter by knowledge domain").option("--json", "Output as JSON").option("--summary", "Show only counts per domain (no slugs or concepts)").action(async (opts) => {
|
|
15248
|
+
cardCommand.command("due").description("Show due tokens for a user").option("--user <id>", "User ID (default: whoami)").option("--domain <domain>", "Filter by knowledge domain").option("--knowledge-context <context>", "Filter by knowledge context").option("--json", "Output as JSON").option("--summary", "Show only counts per domain (no slugs or concepts)").action(async (opts) => {
|
|
14794
15249
|
await withDb(async (db) => {
|
|
14795
15250
|
const userId = await resolveUser(opts, db);
|
|
14796
|
-
const dueCards = await getDueCards(
|
|
15251
|
+
const dueCards = await getDueCards(
|
|
15252
|
+
db,
|
|
15253
|
+
userId,
|
|
15254
|
+
void 0,
|
|
15255
|
+
opts.domain,
|
|
15256
|
+
opts.knowledgeContext
|
|
15257
|
+
);
|
|
14797
15258
|
if (opts.json) {
|
|
14798
15259
|
console.log(JSON.stringify(dueCards, null, 2));
|
|
14799
15260
|
return;
|
|
@@ -15141,6 +15602,48 @@ import { Command as Command5 } from "commander";
|
|
|
15141
15602
|
function shouldApplyFixes(opts) {
|
|
15142
15603
|
return opts.fix === true && opts.dryRun !== true;
|
|
15143
15604
|
}
|
|
15605
|
+
function stringifyDoctorOutput(value) {
|
|
15606
|
+
if (typeof value === "string") return value;
|
|
15607
|
+
try {
|
|
15608
|
+
return JSON.stringify(value);
|
|
15609
|
+
} catch {
|
|
15610
|
+
return String(value);
|
|
15611
|
+
}
|
|
15612
|
+
}
|
|
15613
|
+
async function captureDoctorTaskOutput(task, db, opts) {
|
|
15614
|
+
const chunks = [];
|
|
15615
|
+
const append = (value) => {
|
|
15616
|
+
for (const line of value.split(/[\r\n]+/)) {
|
|
15617
|
+
const trimmed = line.trim();
|
|
15618
|
+
if (trimmed) chunks.push(trimmed);
|
|
15619
|
+
}
|
|
15620
|
+
};
|
|
15621
|
+
const capture = (...args) => {
|
|
15622
|
+
append(args.map(stringifyDoctorOutput).join(" "));
|
|
15623
|
+
};
|
|
15624
|
+
const originalLog = console.log;
|
|
15625
|
+
const originalWarn = console.warn;
|
|
15626
|
+
const originalError = console.error;
|
|
15627
|
+
const originalWrite = process.stdout.write;
|
|
15628
|
+
console.log = capture;
|
|
15629
|
+
console.warn = capture;
|
|
15630
|
+
console.error = capture;
|
|
15631
|
+
process.stdout.write = ((chunk) => {
|
|
15632
|
+
append(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString());
|
|
15633
|
+
return true;
|
|
15634
|
+
});
|
|
15635
|
+
try {
|
|
15636
|
+
await task.run(db, opts);
|
|
15637
|
+
return { lines: chunks };
|
|
15638
|
+
} catch (error) {
|
|
15639
|
+
return { lines: chunks, error: error.message };
|
|
15640
|
+
} finally {
|
|
15641
|
+
console.log = originalLog;
|
|
15642
|
+
console.warn = originalWarn;
|
|
15643
|
+
console.error = originalError;
|
|
15644
|
+
process.stdout.write = originalWrite;
|
|
15645
|
+
}
|
|
15646
|
+
}
|
|
15144
15647
|
async function confirmDeterministicChanges(opts, message) {
|
|
15145
15648
|
if (opts.yes) return true;
|
|
15146
15649
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
@@ -15244,6 +15747,27 @@ function validatedTitleProposal(token, raw) {
|
|
|
15244
15747
|
}
|
|
15245
15748
|
return null;
|
|
15246
15749
|
}
|
|
15750
|
+
var LANGUAGE_MARKERS = {
|
|
15751
|
+
de: ["der", "die", "das", "und", "ist", "sind", "f\xFCr", "mit", "nicht"],
|
|
15752
|
+
en: ["the", "and", "is", "are", "for", "with", "not", "from", "that"],
|
|
15753
|
+
es: ["el", "la", "los", "las", "y", "es", "son", "para", "con"],
|
|
15754
|
+
fr: ["le", "la", "les", "et", "est", "sont", "pour", "avec"],
|
|
15755
|
+
pt: ["o", "a", "os", "as", "e", "\xE9", "s\xE3o", "para", "com"]
|
|
15756
|
+
};
|
|
15757
|
+
function inferEstablishedLanguage(token) {
|
|
15758
|
+
const text = [token.title, token.concept, token.question, token.context].filter(Boolean).join(" ").toLowerCase();
|
|
15759
|
+
if (/[-ヿ]/u.test(text)) return "ja";
|
|
15760
|
+
if (/[㐀-鿿]/u.test(text)) return "zh";
|
|
15761
|
+
const words = new Set(text.match(/\p{L}+/gu) ?? []);
|
|
15762
|
+
const scores = Object.entries(LANGUAGE_MARKERS).map(([language, markers]) => ({
|
|
15763
|
+
language,
|
|
15764
|
+
score: markers.filter((marker) => words.has(marker)).length
|
|
15765
|
+
})).sort((a, b) => b.score - a.score);
|
|
15766
|
+
return scores[0].score >= 2 && scores[0].score > scores[1].score ? scores[0].language : null;
|
|
15767
|
+
}
|
|
15768
|
+
function primaryLanguage(language) {
|
|
15769
|
+
return language?.trim().toLowerCase().split(/[-_]/)[0] || null;
|
|
15770
|
+
}
|
|
15247
15771
|
var doctorTasks = [
|
|
15248
15772
|
{
|
|
15249
15773
|
name: "titles",
|
|
@@ -15268,9 +15792,11 @@ var doctorTasks = [
|
|
|
15268
15792
|
let rejected = 0;
|
|
15269
15793
|
for (let i = 0; i < needing.length; i++) {
|
|
15270
15794
|
const item = needing[i];
|
|
15271
|
-
|
|
15272
|
-
|
|
15273
|
-
|
|
15795
|
+
if (!opts.json) {
|
|
15796
|
+
process.stdout.write(
|
|
15797
|
+
`\r [${i + 1}/${needing.length}] ${item.token.slug.slice(0, 40)}...`
|
|
15798
|
+
);
|
|
15799
|
+
}
|
|
15274
15800
|
let rawProposal = "";
|
|
15275
15801
|
if (useLLM) {
|
|
15276
15802
|
try {
|
|
@@ -15283,7 +15809,10 @@ var doctorTasks = [
|
|
|
15283
15809
|
question: item.token.question,
|
|
15284
15810
|
context: item.token.context
|
|
15285
15811
|
},
|
|
15286
|
-
{
|
|
15812
|
+
{
|
|
15813
|
+
timeoutMs: opts.timeoutMs,
|
|
15814
|
+
knowledgeContext: opts.knowledgeContext
|
|
15815
|
+
}
|
|
15287
15816
|
);
|
|
15288
15817
|
rawProposal = gen.text;
|
|
15289
15818
|
} catch (_e) {
|
|
@@ -15304,7 +15833,7 @@ var doctorTasks = [
|
|
|
15304
15833
|
reason: item.reason
|
|
15305
15834
|
});
|
|
15306
15835
|
}
|
|
15307
|
-
process.stdout.write("\n");
|
|
15836
|
+
if (!opts.json) process.stdout.write("\n");
|
|
15308
15837
|
if (rejected > 0) {
|
|
15309
15838
|
console.warn(
|
|
15310
15839
|
`Skipped ${rejected} token(s) because no proposal passed title quality validation.`
|
|
@@ -15647,6 +16176,194 @@ Duplicates scan complete. Deprecated ${deprecatedCount} token(s).`
|
|
|
15647
16176
|
`Domains rename complete. Renamed ${changes.length} domain(s).`
|
|
15648
16177
|
);
|
|
15649
16178
|
}
|
|
16179
|
+
},
|
|
16180
|
+
{
|
|
16181
|
+
name: "contexts",
|
|
16182
|
+
description: "Backfill contexts on old/unassigned tokens using heuristic rules.",
|
|
16183
|
+
run: async (db, opts) => {
|
|
16184
|
+
const tokensWithoutContext = await db.prepare(
|
|
16185
|
+
`SELECT t.* FROM tokens t
|
|
16186
|
+
WHERE NOT EXISTS (
|
|
16187
|
+
SELECT 1 FROM token_contexts tc WHERE tc.token_id = t.id
|
|
16188
|
+
)`
|
|
16189
|
+
).all();
|
|
16190
|
+
const allContexts = await listKnowledgeContexts(db);
|
|
16191
|
+
const contexts = opts.knowledgeContext ? allContexts.filter(
|
|
16192
|
+
(context) => context.name === opts.knowledgeContext
|
|
16193
|
+
) : allContexts;
|
|
16194
|
+
if (opts.knowledgeContext && contexts.length === 0) {
|
|
16195
|
+
throw new Error(
|
|
16196
|
+
`Knowledge context not found: ${opts.knowledgeContext}`
|
|
16197
|
+
);
|
|
16198
|
+
}
|
|
16199
|
+
if (contexts.length === 0) {
|
|
16200
|
+
if (opts.json) {
|
|
16201
|
+
console.log(
|
|
16202
|
+
JSON.stringify({
|
|
16203
|
+
success: false,
|
|
16204
|
+
error: "No knowledge contexts defined."
|
|
16205
|
+
})
|
|
16206
|
+
);
|
|
16207
|
+
} else {
|
|
16208
|
+
console.log(
|
|
16209
|
+
"No knowledge contexts defined. Run `zam kc create` first."
|
|
16210
|
+
);
|
|
16211
|
+
}
|
|
16212
|
+
return;
|
|
16213
|
+
}
|
|
16214
|
+
const proposals = [];
|
|
16215
|
+
for (const t2 of tokensWithoutContext) {
|
|
16216
|
+
const domain = (t2.domain || "").toLowerCase();
|
|
16217
|
+
const slug = t2.slug.toLowerCase();
|
|
16218
|
+
const title = (t2.title || "").toLowerCase();
|
|
16219
|
+
const source = (t2.source_link || "").toLowerCase();
|
|
16220
|
+
const establishedLanguage = inferEstablishedLanguage(t2);
|
|
16221
|
+
const languageMatches = establishedLanguage ? contexts.filter(
|
|
16222
|
+
(context) => primaryLanguage(context.language) === establishedLanguage
|
|
16223
|
+
) : [];
|
|
16224
|
+
for (const ctx of contexts) {
|
|
16225
|
+
const ctxName = ctx.name.toLowerCase();
|
|
16226
|
+
const ctxLabel = (ctx.label || "").toLowerCase();
|
|
16227
|
+
let match = false;
|
|
16228
|
+
let highConfidence = false;
|
|
16229
|
+
if (domain) {
|
|
16230
|
+
if (domain === ctxName) {
|
|
16231
|
+
match = true;
|
|
16232
|
+
highConfidence = true;
|
|
16233
|
+
} else if (ctxName.includes(domain) || domain.includes(ctxName)) {
|
|
16234
|
+
match = true;
|
|
16235
|
+
highConfidence = domain.length >= 2;
|
|
16236
|
+
} else if (ctxLabel && (ctxLabel.includes(domain) || domain.includes(ctxLabel))) {
|
|
16237
|
+
match = true;
|
|
16238
|
+
highConfidence = domain.length >= 2;
|
|
16239
|
+
}
|
|
16240
|
+
}
|
|
16241
|
+
if (slug.includes(ctxName) || title.includes(ctxName)) {
|
|
16242
|
+
match = true;
|
|
16243
|
+
}
|
|
16244
|
+
if (ctxLabel) {
|
|
16245
|
+
const labelWords = ctxLabel.split(/\s+/).filter((w) => w.length > 3);
|
|
16246
|
+
for (const word of labelWords) {
|
|
16247
|
+
if (domain.includes(word) || slug.includes(word) || title.includes(word)) {
|
|
16248
|
+
match = true;
|
|
16249
|
+
}
|
|
16250
|
+
}
|
|
16251
|
+
}
|
|
16252
|
+
if (source && (source.includes(ctxName) || ctxLabel.split(/\s+/).filter((word) => word.length > 3).some((word) => source.includes(word)))) {
|
|
16253
|
+
match = true;
|
|
16254
|
+
highConfidence = true;
|
|
16255
|
+
}
|
|
16256
|
+
if (languageMatches.length === 1 && languageMatches[0].id === ctx.id) {
|
|
16257
|
+
match = true;
|
|
16258
|
+
}
|
|
16259
|
+
if (match) {
|
|
16260
|
+
proposals.push({
|
|
16261
|
+
token: t2,
|
|
16262
|
+
context: ctx,
|
|
16263
|
+
highConfidence
|
|
16264
|
+
});
|
|
16265
|
+
}
|
|
16266
|
+
}
|
|
16267
|
+
}
|
|
16268
|
+
const applying = shouldApplyFixes(opts);
|
|
16269
|
+
if (opts.json) {
|
|
16270
|
+
if (!applying) {
|
|
16271
|
+
const formatted = proposals.map((p) => ({
|
|
16272
|
+
tokenSlug: p.token.slug,
|
|
16273
|
+
contextName: p.context.name,
|
|
16274
|
+
highConfidence: p.highConfidence
|
|
16275
|
+
}));
|
|
16276
|
+
console.log(
|
|
16277
|
+
JSON.stringify({
|
|
16278
|
+
success: true,
|
|
16279
|
+
task: "contexts",
|
|
16280
|
+
proposals: formatted,
|
|
16281
|
+
totalUnassigned: tokensWithoutContext.length
|
|
16282
|
+
})
|
|
16283
|
+
);
|
|
16284
|
+
return;
|
|
16285
|
+
}
|
|
16286
|
+
} else {
|
|
16287
|
+
console.log(
|
|
16288
|
+
`Found ${proposals.length} proposed context assignments for ${tokensWithoutContext.length} unassigned tokens.`
|
|
16289
|
+
);
|
|
16290
|
+
}
|
|
16291
|
+
if (proposals.length === 0) {
|
|
16292
|
+
if (opts.json && applying) {
|
|
16293
|
+
console.log(
|
|
16294
|
+
JSON.stringify({
|
|
16295
|
+
success: true,
|
|
16296
|
+
task: "contexts",
|
|
16297
|
+
applied: [],
|
|
16298
|
+
totalApplied: 0
|
|
16299
|
+
})
|
|
16300
|
+
);
|
|
16301
|
+
}
|
|
16302
|
+
return;
|
|
16303
|
+
}
|
|
16304
|
+
if (!applying) {
|
|
16305
|
+
console.log("\nProposed context assignments (Dry run):");
|
|
16306
|
+
for (const prop of proposals) {
|
|
16307
|
+
const confStr = prop.highConfidence ? " [High Confidence]" : "";
|
|
16308
|
+
console.log(
|
|
16309
|
+
` - Assign token "${prop.token.slug}" to context "${prop.context.name}"${confStr}`
|
|
16310
|
+
);
|
|
16311
|
+
}
|
|
16312
|
+
console.log("\nRun with --fix to apply these assignments.");
|
|
16313
|
+
return;
|
|
16314
|
+
}
|
|
16315
|
+
if (opts.yes) {
|
|
16316
|
+
let applied = 0;
|
|
16317
|
+
const appliedProposals = [];
|
|
16318
|
+
for (const prop of proposals) {
|
|
16319
|
+
if (prop.highConfidence) {
|
|
16320
|
+
await assignTokenToContext(db, prop.token.id, prop.context.id);
|
|
16321
|
+
if (!opts.json) {
|
|
16322
|
+
console.log(
|
|
16323
|
+
`Auto-assigned: "${prop.token.slug}" -> context "${prop.context.name}"`
|
|
16324
|
+
);
|
|
16325
|
+
}
|
|
16326
|
+
appliedProposals.push({
|
|
16327
|
+
tokenSlug: prop.token.slug,
|
|
16328
|
+
contextName: prop.context.name
|
|
16329
|
+
});
|
|
16330
|
+
applied++;
|
|
16331
|
+
}
|
|
16332
|
+
}
|
|
16333
|
+
if (opts.json) {
|
|
16334
|
+
console.log(
|
|
16335
|
+
JSON.stringify({
|
|
16336
|
+
success: true,
|
|
16337
|
+
task: "contexts",
|
|
16338
|
+
applied: appliedProposals,
|
|
16339
|
+
totalApplied: applied
|
|
16340
|
+
})
|
|
16341
|
+
);
|
|
16342
|
+
} else {
|
|
16343
|
+
console.log(`Auto-assigned ${applied} high-confidence contexts.`);
|
|
16344
|
+
}
|
|
16345
|
+
return;
|
|
16346
|
+
}
|
|
16347
|
+
const { confirm: confirm5 } = await import("@inquirer/prompts");
|
|
16348
|
+
let assignedCount = 0;
|
|
16349
|
+
for (const prop of proposals) {
|
|
16350
|
+
const confStr = prop.highConfidence ? " (high confidence)" : "";
|
|
16351
|
+
const ans = await confirm5({
|
|
16352
|
+
message: `Assign token "${prop.token.slug}" to context "${prop.context.name}"${confStr}?`,
|
|
16353
|
+
default: prop.highConfidence
|
|
16354
|
+
});
|
|
16355
|
+
if (ans) {
|
|
16356
|
+
await assignTokenToContext(db, prop.token.id, prop.context.id);
|
|
16357
|
+
console.log(
|
|
16358
|
+
`Assigned "${prop.token.slug}" -> context "${prop.context.name}"`
|
|
16359
|
+
);
|
|
16360
|
+
assignedCount++;
|
|
16361
|
+
}
|
|
16362
|
+
}
|
|
16363
|
+
console.log(
|
|
16364
|
+
`Completed contexts backfill. Assigned ${assignedCount} tokens.`
|
|
16365
|
+
);
|
|
16366
|
+
}
|
|
15650
16367
|
}
|
|
15651
16368
|
];
|
|
15652
16369
|
function parseDoctorTimeout(value) {
|
|
@@ -15665,30 +16382,128 @@ var doctorCommand = new Command5("doctor").description(
|
|
|
15665
16382
|
"--timeout <ms>",
|
|
15666
16383
|
"LLM timeout in ms per call (default: 20000)",
|
|
15667
16384
|
"20000"
|
|
15668
|
-
).
|
|
16385
|
+
).option(
|
|
16386
|
+
"--knowledge-context <context>",
|
|
16387
|
+
"Knowledge context to limit/guide doctor task"
|
|
16388
|
+
).option("--json", "Emit report in JSON format").argument(
|
|
16389
|
+
"[task]",
|
|
16390
|
+
"Specific task: titles, texts, duplicates, domains, contexts"
|
|
16391
|
+
).action(async (taskName, opts) => {
|
|
15669
16392
|
await withDb(async (db) => {
|
|
15670
16393
|
const fix = !!opts.fix;
|
|
15671
16394
|
const dryRun = !fix || !!opts.dryRun;
|
|
15672
16395
|
const yes = !!opts.yes;
|
|
15673
16396
|
const noLlm = opts.llm === false;
|
|
15674
16397
|
const timeoutMs = parseDoctorTimeout(opts.timeout);
|
|
16398
|
+
const knowledgeContext = opts.knowledgeContext;
|
|
16399
|
+
const json = !!opts.json;
|
|
15675
16400
|
if (!taskName) {
|
|
15676
|
-
|
|
15677
|
-
|
|
15678
|
-
|
|
16401
|
+
const diagnosisOptions = {
|
|
16402
|
+
fix: false,
|
|
16403
|
+
dryRun: true,
|
|
16404
|
+
yes: false,
|
|
16405
|
+
noLlm: true,
|
|
16406
|
+
timeoutMs,
|
|
16407
|
+
knowledgeContext,
|
|
16408
|
+
json: false
|
|
16409
|
+
};
|
|
16410
|
+
if (json) {
|
|
16411
|
+
const reports = [];
|
|
16412
|
+
for (const task2 of doctorTasks) {
|
|
16413
|
+
const report = await captureDoctorTaskOutput(
|
|
16414
|
+
task2,
|
|
16415
|
+
db,
|
|
16416
|
+
diagnosisOptions
|
|
16417
|
+
);
|
|
16418
|
+
reports.push({
|
|
16419
|
+
name: task2.name,
|
|
16420
|
+
description: task2.description,
|
|
16421
|
+
...report
|
|
16422
|
+
});
|
|
16423
|
+
}
|
|
16424
|
+
const success = reports.every((report) => !report.error);
|
|
16425
|
+
console.log(
|
|
16426
|
+
JSON.stringify({ success, readOnly: true, tasks: reports })
|
|
16427
|
+
);
|
|
16428
|
+
if (!success) process.exitCode = 1;
|
|
16429
|
+
} else {
|
|
16430
|
+
console.log("ZAM doctor read-only diagnosis (LLM disabled):");
|
|
16431
|
+
for (const task2 of doctorTasks) {
|
|
16432
|
+
console.log(`
|
|
16433
|
+
[${task2.name}] ${task2.description}`);
|
|
16434
|
+
await task2.run(db, diagnosisOptions);
|
|
16435
|
+
}
|
|
15679
16436
|
}
|
|
15680
|
-
console.log("\nRun with a task name, e.g. `zam doctor titles --fix`");
|
|
15681
16437
|
return;
|
|
15682
16438
|
}
|
|
15683
16439
|
const task = doctorTasks.find((t2) => t2.name === taskName);
|
|
15684
16440
|
if (!task) {
|
|
15685
|
-
|
|
16441
|
+
if (json) {
|
|
16442
|
+
console.log(
|
|
16443
|
+
JSON.stringify({
|
|
16444
|
+
success: false,
|
|
16445
|
+
error: `Unknown task: ${taskName}`
|
|
16446
|
+
})
|
|
16447
|
+
);
|
|
16448
|
+
} else {
|
|
16449
|
+
console.error(`Unknown task: ${taskName}`);
|
|
16450
|
+
}
|
|
15686
16451
|
process.exit(1);
|
|
15687
16452
|
}
|
|
15688
|
-
|
|
15689
|
-
|
|
15690
|
-
|
|
15691
|
-
|
|
16453
|
+
if (!json) {
|
|
16454
|
+
console.log(
|
|
16455
|
+
`Running doctor task: ${task.name} (fix=${fix}, dryRun=${dryRun}${noLlm ? ", noLlm=true" : ""})`
|
|
16456
|
+
);
|
|
16457
|
+
}
|
|
16458
|
+
const taskOptions = {
|
|
16459
|
+
fix,
|
|
16460
|
+
dryRun,
|
|
16461
|
+
yes,
|
|
16462
|
+
noLlm,
|
|
16463
|
+
timeoutMs,
|
|
16464
|
+
knowledgeContext,
|
|
16465
|
+
json
|
|
16466
|
+
};
|
|
16467
|
+
if (json && fix && !yes) {
|
|
16468
|
+
console.log(
|
|
16469
|
+
JSON.stringify({
|
|
16470
|
+
success: false,
|
|
16471
|
+
error: "--json --fix requires --yes; interactive prompts are not machine-readable"
|
|
16472
|
+
})
|
|
16473
|
+
);
|
|
16474
|
+
process.exitCode = 1;
|
|
16475
|
+
return;
|
|
16476
|
+
}
|
|
16477
|
+
if (json && fix && yes && (task.name === "duplicates" || task.name === "domains")) {
|
|
16478
|
+
console.log(
|
|
16479
|
+
JSON.stringify({
|
|
16480
|
+
success: false,
|
|
16481
|
+
error: `${task.name} fixes require interactive choices and cannot use --json --yes`
|
|
16482
|
+
})
|
|
16483
|
+
);
|
|
16484
|
+
process.exitCode = 1;
|
|
16485
|
+
return;
|
|
16486
|
+
}
|
|
16487
|
+
if (json) {
|
|
16488
|
+
const report = await captureDoctorTaskOutput(task, db, taskOptions);
|
|
16489
|
+
let payload = {
|
|
16490
|
+
success: report.error === void 0,
|
|
16491
|
+
task: task.name,
|
|
16492
|
+
...report
|
|
16493
|
+
};
|
|
16494
|
+
if (task.name === "contexts" && !report.error && report.lines.length === 1) {
|
|
16495
|
+
try {
|
|
16496
|
+
payload = JSON.parse(report.lines[0]);
|
|
16497
|
+
} catch {
|
|
16498
|
+
}
|
|
16499
|
+
}
|
|
16500
|
+
console.log(JSON.stringify(payload));
|
|
16501
|
+
if (report.error || payload.success === false) {
|
|
16502
|
+
process.exitCode = 1;
|
|
16503
|
+
}
|
|
16504
|
+
return;
|
|
16505
|
+
}
|
|
16506
|
+
await task.run(db, taskOptions);
|
|
15692
16507
|
});
|
|
15693
16508
|
});
|
|
15694
16509
|
|
|
@@ -16216,9 +17031,202 @@ var initCommand = new Command8("init").description("Launch the guided interactiv
|
|
|
16216
17031
|
printLine();
|
|
16217
17032
|
});
|
|
16218
17033
|
|
|
17034
|
+
// src/cli/commands/knowledge-context.ts
|
|
17035
|
+
import { Command as Command9 } from "commander";
|
|
17036
|
+
var knowledgeContextCommand = new Command9("knowledge-context").alias("kc").description("Manage knowledge contexts (work, school, private)");
|
|
17037
|
+
function commandError(message, json) {
|
|
17038
|
+
if (json) {
|
|
17039
|
+
jsonOut({ success: false, error: message });
|
|
17040
|
+
} else {
|
|
17041
|
+
console.error(message);
|
|
17042
|
+
}
|
|
17043
|
+
process.exit(1);
|
|
17044
|
+
}
|
|
17045
|
+
knowledgeContextCommand.command("list").description("List all knowledge contexts").option("--json", "Output as JSON").action(async (opts) => {
|
|
17046
|
+
await withDb(async (db) => {
|
|
17047
|
+
const contexts = await listKnowledgeContexts(db);
|
|
17048
|
+
if (opts.json) {
|
|
17049
|
+
jsonOut(contexts);
|
|
17050
|
+
return;
|
|
17051
|
+
}
|
|
17052
|
+
if (contexts.length === 0) {
|
|
17053
|
+
console.log("No knowledge contexts registered yet.");
|
|
17054
|
+
return;
|
|
17055
|
+
}
|
|
17056
|
+
console.log(`Knowledge Contexts (${contexts.length})`);
|
|
17057
|
+
console.log("\u2500".repeat(60));
|
|
17058
|
+
for (const c of contexts) {
|
|
17059
|
+
const lang = c.language ? ` (${c.language})` : "";
|
|
17060
|
+
const label = c.label ? ` - ${c.label}` : "";
|
|
17061
|
+
console.log(` ${c.name.padEnd(20)}${label}${lang}`);
|
|
17062
|
+
}
|
|
17063
|
+
});
|
|
17064
|
+
});
|
|
17065
|
+
knowledgeContextCommand.command("create").description("Create a new knowledge context").requiredOption(
|
|
17066
|
+
"--name <name>",
|
|
17067
|
+
"Unique context identifier (e.g. work-company)"
|
|
17068
|
+
).option("--label <label>", "Display name").option(
|
|
17069
|
+
"--language <language>",
|
|
17070
|
+
"Default language (BCP-47 code, e.g. en, de)"
|
|
17071
|
+
).option("--json", "Output as JSON").action(async (opts) => {
|
|
17072
|
+
await withDb(async (db) => {
|
|
17073
|
+
const context = await createKnowledgeContext(db, {
|
|
17074
|
+
name: opts.name,
|
|
17075
|
+
label: opts.label || null,
|
|
17076
|
+
language: opts.language || null
|
|
17077
|
+
});
|
|
17078
|
+
if (opts.json) {
|
|
17079
|
+
jsonOut({ success: true, context });
|
|
17080
|
+
return;
|
|
17081
|
+
}
|
|
17082
|
+
console.log(`Created knowledge context: ${context.name}`);
|
|
17083
|
+
if (context.label) console.log(` Label: ${context.label}`);
|
|
17084
|
+
if (context.language) console.log(` Language: ${context.language}`);
|
|
17085
|
+
});
|
|
17086
|
+
});
|
|
17087
|
+
knowledgeContextCommand.command("delete").description("Delete a knowledge context").requiredOption("--name <name>", "Context name").option(
|
|
17088
|
+
"--confirm",
|
|
17089
|
+
"Confirm removal of the context and its token assignments"
|
|
17090
|
+
).option("--json", "Output as JSON").action(async (opts) => {
|
|
17091
|
+
await withDb(async (db) => {
|
|
17092
|
+
const context = await getKnowledgeContextByName(db, opts.name);
|
|
17093
|
+
if (!context) {
|
|
17094
|
+
commandError(`Knowledge context not found: ${opts.name}`, !!opts.json);
|
|
17095
|
+
}
|
|
17096
|
+
const assignmentCount = (await db.prepare(
|
|
17097
|
+
"SELECT COUNT(*) AS n FROM token_contexts WHERE context_id = ?"
|
|
17098
|
+
).get(context.id)).n;
|
|
17099
|
+
if (!opts.confirm) {
|
|
17100
|
+
if (opts.json) {
|
|
17101
|
+
jsonOut({
|
|
17102
|
+
success: true,
|
|
17103
|
+
preview: true,
|
|
17104
|
+
requiresConfirmation: true,
|
|
17105
|
+
name: context.name,
|
|
17106
|
+
tokenAssignments: assignmentCount
|
|
17107
|
+
});
|
|
17108
|
+
} else {
|
|
17109
|
+
console.log(
|
|
17110
|
+
`Deleting "${context.name}" removes ${assignmentCount} token assignment(s). Re-run with --confirm.`
|
|
17111
|
+
);
|
|
17112
|
+
}
|
|
17113
|
+
return;
|
|
17114
|
+
}
|
|
17115
|
+
await deleteKnowledgeContext(db, context.id);
|
|
17116
|
+
if (getActiveWorkspaceContext() === context.name) {
|
|
17117
|
+
setActiveWorkspaceContext(void 0);
|
|
17118
|
+
}
|
|
17119
|
+
if (opts.json) {
|
|
17120
|
+
jsonOut({
|
|
17121
|
+
success: true,
|
|
17122
|
+
name: context.name,
|
|
17123
|
+
tokenAssignmentsRemoved: assignmentCount
|
|
17124
|
+
});
|
|
17125
|
+
return;
|
|
17126
|
+
}
|
|
17127
|
+
console.log(`Deleted knowledge context: ${context.name}`);
|
|
17128
|
+
});
|
|
17129
|
+
});
|
|
17130
|
+
knowledgeContextCommand.command("assign").description("Assign a token to a knowledge context").requiredOption("--token <slug>", "Token slug").requiredOption("--context <name>", "Context name").option("--json", "Output as JSON").action(async (opts) => {
|
|
17131
|
+
await withDb(async (db) => {
|
|
17132
|
+
const context = await getKnowledgeContextByName(db, opts.context);
|
|
17133
|
+
if (!context) {
|
|
17134
|
+
commandError(
|
|
17135
|
+
`Knowledge context not found: ${opts.context}`,
|
|
17136
|
+
!!opts.json
|
|
17137
|
+
);
|
|
17138
|
+
}
|
|
17139
|
+
const token = await getTokenBySlug(db, opts.token);
|
|
17140
|
+
if (!token) {
|
|
17141
|
+
commandError(`Token not found: ${opts.token}`, !!opts.json);
|
|
17142
|
+
}
|
|
17143
|
+
await assignTokenToContext(db, token.id, context.id);
|
|
17144
|
+
if (opts.json) {
|
|
17145
|
+
jsonOut({ success: true, token: token.slug, context: context.name });
|
|
17146
|
+
return;
|
|
17147
|
+
}
|
|
17148
|
+
console.log(
|
|
17149
|
+
`Assigned token "${token.slug}" to context "${context.name}"`
|
|
17150
|
+
);
|
|
17151
|
+
});
|
|
17152
|
+
});
|
|
17153
|
+
knowledgeContextCommand.command("unassign").description("Remove a token from a knowledge context").requiredOption("--token <slug>", "Token slug").requiredOption("--context <name>", "Context name").option("--json", "Output as JSON").action(async (opts) => {
|
|
17154
|
+
await withDb(async (db) => {
|
|
17155
|
+
const context = await getKnowledgeContextByName(db, opts.context);
|
|
17156
|
+
if (!context) {
|
|
17157
|
+
commandError(
|
|
17158
|
+
`Knowledge context not found: ${opts.context}`,
|
|
17159
|
+
!!opts.json
|
|
17160
|
+
);
|
|
17161
|
+
}
|
|
17162
|
+
const token = await getTokenBySlug(db, opts.token);
|
|
17163
|
+
if (!token) {
|
|
17164
|
+
commandError(`Token not found: ${opts.token}`, !!opts.json);
|
|
17165
|
+
}
|
|
17166
|
+
await unassignTokenFromContext(db, token.id, context.id);
|
|
17167
|
+
if (opts.json) {
|
|
17168
|
+
jsonOut({ success: true, token: token.slug, context: context.name });
|
|
17169
|
+
return;
|
|
17170
|
+
}
|
|
17171
|
+
console.log(
|
|
17172
|
+
`Unassigned token "${token.slug}" from context "${context.name}"`
|
|
17173
|
+
);
|
|
17174
|
+
});
|
|
17175
|
+
});
|
|
17176
|
+
knowledgeContextCommand.command("use").description("Set the active knowledge context default for this device").argument("[name]", "Context name to use (use without argument to clear)").option("--json", "Output as JSON").action(async (name, opts) => {
|
|
17177
|
+
await withDb(async (db) => {
|
|
17178
|
+
if (!name) {
|
|
17179
|
+
if (!setActiveWorkspaceContext(void 0)) {
|
|
17180
|
+
commandError("No active workspace configured", !!opts.json);
|
|
17181
|
+
}
|
|
17182
|
+
if (opts.json) {
|
|
17183
|
+
jsonOut({ success: true, activeContext: null });
|
|
17184
|
+
return;
|
|
17185
|
+
}
|
|
17186
|
+
console.log("Cleared active knowledge context default.");
|
|
17187
|
+
return;
|
|
17188
|
+
}
|
|
17189
|
+
const context = await getKnowledgeContextByName(db, name);
|
|
17190
|
+
if (!context) {
|
|
17191
|
+
commandError(`Knowledge context not found: ${name}`, !!opts.json);
|
|
17192
|
+
}
|
|
17193
|
+
if (!setActiveWorkspaceContext(context.name)) {
|
|
17194
|
+
commandError("No active workspace configured", !!opts.json);
|
|
17195
|
+
}
|
|
17196
|
+
if (opts.json) {
|
|
17197
|
+
jsonOut({ success: true, activeContext: context.name });
|
|
17198
|
+
return;
|
|
17199
|
+
}
|
|
17200
|
+
console.log(`Active knowledge context set to: ${context.name}`);
|
|
17201
|
+
});
|
|
17202
|
+
});
|
|
17203
|
+
knowledgeContextCommand.command("show").description("Show the active knowledge context default for this device").option("--json", "Output as JSON").action(async (opts) => {
|
|
17204
|
+
await withDb(async (db) => {
|
|
17205
|
+
const configured = getActiveWorkspaceContext();
|
|
17206
|
+
const active = configured ? await getKnowledgeContextByName(db, configured) : void 0;
|
|
17207
|
+
if (opts.json) {
|
|
17208
|
+
jsonOut({
|
|
17209
|
+
success: true,
|
|
17210
|
+
activeContext: active?.name ?? null,
|
|
17211
|
+
staleContext: configured && !active ? configured : null
|
|
17212
|
+
});
|
|
17213
|
+
return;
|
|
17214
|
+
}
|
|
17215
|
+
if (active) {
|
|
17216
|
+
console.log(`Active knowledge context: ${active.name}`);
|
|
17217
|
+
} else if (configured) {
|
|
17218
|
+
console.warn(
|
|
17219
|
+
`Configured knowledge context no longer exists: ${configured}`
|
|
17220
|
+
);
|
|
17221
|
+
} else {
|
|
17222
|
+
console.log("No active knowledge context default set.");
|
|
17223
|
+
}
|
|
17224
|
+
});
|
|
17225
|
+
});
|
|
17226
|
+
|
|
16219
17227
|
// src/cli/commands/learn.ts
|
|
16220
17228
|
import { input as input5 } from "@inquirer/prompts";
|
|
16221
|
-
import { Command as
|
|
17229
|
+
import { Command as Command10 } from "commander";
|
|
16222
17230
|
|
|
16223
17231
|
// src/cli/learn-format.ts
|
|
16224
17232
|
var BLOOM_VERBS3 = {
|
|
@@ -16562,7 +17570,7 @@ var STOP_WORDS = /* @__PURE__ */ new Set(["q", ":q", "quit", "stop"]);
|
|
|
16562
17570
|
function isExitPrompt(err) {
|
|
16563
17571
|
return err instanceof Error && err.name === "ExitPromptError";
|
|
16564
17572
|
}
|
|
16565
|
-
var learnCommand = new
|
|
17573
|
+
var learnCommand = new Command10("learn").description(
|
|
16566
17574
|
"Run a spoiler-free, in-process learning session (recall \u2192 reveal \u2192 self-rate)"
|
|
16567
17575
|
).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) => {
|
|
16568
17576
|
let db;
|
|
@@ -16808,8 +17816,8 @@ learnCommand.command("open").description("Open a new terminal window running zam
|
|
|
16808
17816
|
});
|
|
16809
17817
|
|
|
16810
17818
|
// src/cli/commands/monitor.ts
|
|
16811
|
-
import { Command as
|
|
16812
|
-
var monitorCommand = new
|
|
17819
|
+
import { Command as Command11 } from "commander";
|
|
17820
|
+
var monitorCommand = new Command11("monitor").description(
|
|
16813
17821
|
"Shell observation for real-time task monitoring"
|
|
16814
17822
|
);
|
|
16815
17823
|
monitorCommand.command("start").description("Output shell hook code to install monitoring").requiredOption("--session <id>", "Session ID to monitor").option(
|
|
@@ -16991,8 +17999,8 @@ monitorCommand.command("open").description("Open a new monitored terminal window
|
|
|
16991
17999
|
});
|
|
16992
18000
|
|
|
16993
18001
|
// src/cli/commands/observer.ts
|
|
16994
|
-
import { Command as
|
|
16995
|
-
var observerCommand = new
|
|
18002
|
+
import { Command as Command12 } from "commander";
|
|
18003
|
+
var observerCommand = new Command12("observer").description(
|
|
16996
18004
|
"Configure what the UI observer may capture (Layer 2 policy)"
|
|
16997
18005
|
);
|
|
16998
18006
|
function applyObserverListChange(current, entry, op) {
|
|
@@ -17055,7 +18063,7 @@ observerCommand.command("revoke <process>").description("Remove a process from o
|
|
|
17055
18063
|
|
|
17056
18064
|
// src/cli/commands/profile.ts
|
|
17057
18065
|
import { dirname as dirname6, resolve as resolve7 } from "path";
|
|
17058
|
-
import { Command as
|
|
18066
|
+
import { Command as Command13 } from "commander";
|
|
17059
18067
|
var C2 = {
|
|
17060
18068
|
reset: "\x1B[0m",
|
|
17061
18069
|
bold: "\x1B[1m",
|
|
@@ -17072,7 +18080,7 @@ function render(profile) {
|
|
|
17072
18080
|
console.log(` data dir: ${C2.cyan}${profile.dataDir}${C2.reset}`);
|
|
17073
18081
|
console.log(` database: ${C2.cyan}${profile.dbPath}${C2.reset}`);
|
|
17074
18082
|
}
|
|
17075
|
-
var profileCommand = new
|
|
18083
|
+
var profileCommand = new Command13("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) => {
|
|
17076
18084
|
if (opts.mode && opts.mode !== "developer" && opts.mode !== "default") {
|
|
17077
18085
|
console.error(`Invalid --mode: ${opts.mode}. Use developer or default.`);
|
|
17078
18086
|
process.exit(1);
|
|
@@ -17112,8 +18120,8 @@ var profileCommand = new Command12("profile").description("Show or change this m
|
|
|
17112
18120
|
|
|
17113
18121
|
// src/cli/commands/provider.ts
|
|
17114
18122
|
import { password as password2 } from "@inquirer/prompts";
|
|
17115
|
-
import { Command as
|
|
17116
|
-
var providerCommand = new
|
|
18123
|
+
import { Command as Command14 } from "commander";
|
|
18124
|
+
var providerCommand = new Command14("provider").description(
|
|
17117
18125
|
"Configure role-based AI providers (url/model/flavor/key, per role)"
|
|
17118
18126
|
);
|
|
17119
18127
|
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) => {
|
|
@@ -17348,16 +18356,33 @@ providerCommand.command("clear-key <ref>").description("Remove a stored API key"
|
|
|
17348
18356
|
});
|
|
17349
18357
|
|
|
17350
18358
|
// src/cli/commands/review.ts
|
|
17351
|
-
import { Command as
|
|
17352
|
-
var reviewCommand = new
|
|
18359
|
+
import { Command as Command15 } from "commander";
|
|
18360
|
+
var reviewCommand = new Command15("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").option(
|
|
18361
|
+
"--knowledge-context <context>",
|
|
18362
|
+
"Filter review queue by knowledge context"
|
|
18363
|
+
).action(async (opts) => {
|
|
17353
18364
|
let db;
|
|
17354
18365
|
try {
|
|
17355
18366
|
db = await openDatabase();
|
|
17356
18367
|
const userId = await resolveUser(opts, db);
|
|
18368
|
+
let resolvedContext;
|
|
18369
|
+
if (opts.knowledgeContext) {
|
|
18370
|
+
const context = await getKnowledgeContextByName(
|
|
18371
|
+
db,
|
|
18372
|
+
opts.knowledgeContext
|
|
18373
|
+
);
|
|
18374
|
+
if (!context) {
|
|
18375
|
+
throw new Error(
|
|
18376
|
+
`Knowledge context not found: ${opts.knowledgeContext}`
|
|
18377
|
+
);
|
|
18378
|
+
}
|
|
18379
|
+
resolvedContext = context.name;
|
|
18380
|
+
}
|
|
17357
18381
|
const queue = await buildReviewQueue(db, {
|
|
17358
18382
|
userId,
|
|
17359
18383
|
maxNew: Number(opts.maxNew),
|
|
17360
|
-
maxReviews: Number(opts.maxReviews)
|
|
18384
|
+
maxReviews: Number(opts.maxReviews),
|
|
18385
|
+
knowledgeContext: resolvedContext
|
|
17361
18386
|
});
|
|
17362
18387
|
if (queue.items.length === 0) {
|
|
17363
18388
|
console.log("No cards due for review. You're all caught up!");
|
|
@@ -17366,6 +18391,9 @@ var reviewCommand = new Command14("review").description("Start an interactive re
|
|
|
17366
18391
|
}
|
|
17367
18392
|
console.log(`
|
|
17368
18393
|
Review session: ${queue.items.length} card(s)`);
|
|
18394
|
+
if (resolvedContext) {
|
|
18395
|
+
console.log(` Context: ${resolvedContext}`);
|
|
18396
|
+
}
|
|
17369
18397
|
console.log(
|
|
17370
18398
|
` New: ${queue.newCount} Review: ${queue.reviewCount} Relearn: ${queue.relearnCount}`
|
|
17371
18399
|
);
|
|
@@ -17463,8 +18491,8 @@ ${"\u2550".repeat(50)}`);
|
|
|
17463
18491
|
// src/cli/commands/session.ts
|
|
17464
18492
|
import { readFileSync as readFileSync13 } from "fs";
|
|
17465
18493
|
import { input as input6, select as select2 } from "@inquirer/prompts";
|
|
17466
|
-
import { Command as
|
|
17467
|
-
var sessionCommand = new
|
|
18494
|
+
import { Command as Command16 } from "commander";
|
|
18495
|
+
var sessionCommand = new Command16("session").description(
|
|
17468
18496
|
"Manage learning sessions"
|
|
17469
18497
|
);
|
|
17470
18498
|
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(
|
|
@@ -17843,8 +18871,8 @@ sessionCommand.command("end").description("End a session and show summary").requ
|
|
|
17843
18871
|
|
|
17844
18872
|
// src/cli/commands/settings.ts
|
|
17845
18873
|
import { existsSync as existsSync21 } from "fs";
|
|
17846
|
-
import { Command as
|
|
17847
|
-
var settingsCommand = new
|
|
18874
|
+
import { Command as Command17 } from "commander";
|
|
18875
|
+
var settingsCommand = new Command17("settings").description(
|
|
17848
18876
|
"Manage user settings"
|
|
17849
18877
|
);
|
|
17850
18878
|
var BOOLEAN_SETTING_KEYS = /* @__PURE__ */ new Set(["llm.enabled", "llm.vision.enabled"]);
|
|
@@ -18020,7 +19048,7 @@ settingsCommand.command("repos").description("Show or set Personal, Team, and Or
|
|
|
18020
19048
|
|
|
18021
19049
|
// src/cli/commands/setup.ts
|
|
18022
19050
|
import { resolve as resolve8 } from "path";
|
|
18023
|
-
import { Command as
|
|
19051
|
+
import { Command as Command18 } from "commander";
|
|
18024
19052
|
function formatDatabaseInitTarget(target) {
|
|
18025
19053
|
switch (target.kind) {
|
|
18026
19054
|
case "local":
|
|
@@ -18061,7 +19089,7 @@ async function activateMachineProviderConfig(db) {
|
|
|
18061
19089
|
` activate ${providerCount} machine-local provider(s) from ~/.zam/config.json`
|
|
18062
19090
|
);
|
|
18063
19091
|
}
|
|
18064
|
-
var setupCommand = new
|
|
19092
|
+
var setupCommand = new Command18("setup").description(
|
|
18065
19093
|
"Link ZAM skill directories into this workspace and initialize the database"
|
|
18066
19094
|
).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(
|
|
18067
19095
|
"--agents <list>",
|
|
@@ -18115,8 +19143,8 @@ var setupCommand = new Command17("setup").description(
|
|
|
18115
19143
|
);
|
|
18116
19144
|
|
|
18117
19145
|
// src/cli/commands/skill.ts
|
|
18118
|
-
import { Command as
|
|
18119
|
-
var skillCommand = new
|
|
19146
|
+
import { Command as Command19 } from "commander";
|
|
19147
|
+
var skillCommand = new Command19("skill").description(
|
|
18120
19148
|
"Manage agent skill entries (task recipes)"
|
|
18121
19149
|
);
|
|
18122
19150
|
skillCommand.command("list").description("List all agent skills").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -18203,7 +19231,7 @@ skillCommand.command("add").description("Register a new agent skill").requiredOp
|
|
|
18203
19231
|
// src/cli/commands/snapshot.ts
|
|
18204
19232
|
import { existsSync as existsSync22, mkdirSync as mkdirSync13, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "fs";
|
|
18205
19233
|
import { dirname as dirname7, join as join21 } from "path";
|
|
18206
|
-
import { Command as
|
|
19234
|
+
import { Command as Command20 } from "commander";
|
|
18207
19235
|
function defaultOutName() {
|
|
18208
19236
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
|
|
18209
19237
|
return `zam-snapshot-${stamp}.sql`;
|
|
@@ -18217,7 +19245,7 @@ function summarize(tables) {
|
|
|
18217
19245
|
}
|
|
18218
19246
|
return { total, nonEmpty };
|
|
18219
19247
|
}
|
|
18220
|
-
var exportCmd = new
|
|
19248
|
+
var exportCmd = new Command20("export").description("Write a portable SQL-text snapshot of the database").option("--out <file>", "Output file (use - for stdout)").action(async (opts) => {
|
|
18221
19249
|
let db;
|
|
18222
19250
|
try {
|
|
18223
19251
|
db = await openDatabaseWithSync({ initialize: true });
|
|
@@ -18247,7 +19275,7 @@ var exportCmd = new Command19("export").description("Write a portable SQL-text s
|
|
|
18247
19275
|
process.exit(1);
|
|
18248
19276
|
}
|
|
18249
19277
|
});
|
|
18250
|
-
var importCmd = new
|
|
19278
|
+
var importCmd = new Command20("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) => {
|
|
18251
19279
|
let db;
|
|
18252
19280
|
try {
|
|
18253
19281
|
if (!existsSync22(file)) {
|
|
@@ -18270,7 +19298,7 @@ var importCmd = new Command19("import").description("Restore a snapshot into the
|
|
|
18270
19298
|
process.exit(1);
|
|
18271
19299
|
}
|
|
18272
19300
|
});
|
|
18273
|
-
var verifyCmd = new
|
|
19301
|
+
var verifyCmd = new Command20("verify").description("Check a snapshot's manifest and checksum without importing").argument("<file>", "Snapshot file to verify").action((file) => {
|
|
18274
19302
|
try {
|
|
18275
19303
|
if (!existsSync22(file)) {
|
|
18276
19304
|
console.error(`Error: Snapshot file not found: ${file}`);
|
|
@@ -18288,11 +19316,11 @@ var verifyCmd = new Command19("verify").description("Check a snapshot's manifest
|
|
|
18288
19316
|
process.exit(1);
|
|
18289
19317
|
}
|
|
18290
19318
|
});
|
|
18291
|
-
var snapshotCommand = new
|
|
19319
|
+
var snapshotCommand = new Command20("snapshot").description("Export, import, or verify a portable database snapshot").addCommand(exportCmd).addCommand(importCmd).addCommand(verifyCmd);
|
|
18292
19320
|
|
|
18293
19321
|
// src/cli/commands/stats.ts
|
|
18294
|
-
import { Command as
|
|
18295
|
-
var statsCommand = new
|
|
19322
|
+
import { Command as Command21 } from "commander";
|
|
19323
|
+
var statsCommand = new Command21("stats").description("Show learning dashboard for a user").option("--user <id>", "User ID (default: whoami)").option("--json", "Output as JSON").action(async (opts) => {
|
|
18296
19324
|
await withDb(async (db) => {
|
|
18297
19325
|
const userId = await resolveUser(opts, db);
|
|
18298
19326
|
const stats = await getUserStats(db, userId);
|
|
@@ -18328,11 +19356,19 @@ var statsCommand = new Command20("stats").description("Show learning dashboard f
|
|
|
18328
19356
|
});
|
|
18329
19357
|
|
|
18330
19358
|
// src/cli/commands/token.ts
|
|
18331
|
-
import { Command as
|
|
18332
|
-
var tokenCommand = new
|
|
19359
|
+
import { Command as Command22 } from "commander";
|
|
19360
|
+
var tokenCommand = new Command22("token").description(
|
|
18333
19361
|
"Manage knowledge tokens"
|
|
18334
19362
|
);
|
|
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(
|
|
19363
|
+
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(
|
|
19364
|
+
"--knowledge-context <context>",
|
|
19365
|
+
"Assign token to a knowledge context (repeatable)",
|
|
19366
|
+
(val, memo) => {
|
|
19367
|
+
memo.push(val);
|
|
19368
|
+
return memo;
|
|
19369
|
+
},
|
|
19370
|
+
[]
|
|
19371
|
+
).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) => {
|
|
18336
19372
|
await withDb(async (db) => {
|
|
18337
19373
|
let question = opts.question || null;
|
|
18338
19374
|
if (!question) {
|
|
@@ -18342,6 +19378,10 @@ tokenCommand.command("register").description("Register a new knowledge token").r
|
|
|
18342
19378
|
opts.domain
|
|
18343
19379
|
);
|
|
18344
19380
|
}
|
|
19381
|
+
const assignedContexts = await resolveOperationKnowledgeContexts(
|
|
19382
|
+
db,
|
|
19383
|
+
opts.knowledgeContext || []
|
|
19384
|
+
);
|
|
18345
19385
|
const possibleDuplicates = await findPossibleDuplicates(db, {
|
|
18346
19386
|
concept: opts.concept,
|
|
18347
19387
|
question,
|
|
@@ -18355,6 +19395,9 @@ tokenCommand.command("register").description("Register a new knowledge token").r
|
|
|
18355
19395
|
source_link: opts.sourceLink || null,
|
|
18356
19396
|
question
|
|
18357
19397
|
});
|
|
19398
|
+
for (const context of assignedContexts) {
|
|
19399
|
+
await assignTokenToContext(db, token.id, context.id);
|
|
19400
|
+
}
|
|
18358
19401
|
let cardUserId = null;
|
|
18359
19402
|
if (opts.card !== false) {
|
|
18360
19403
|
cardUserId = opts.user ?? await getSetting(db, "user.id");
|
|
@@ -18373,7 +19416,12 @@ tokenCommand.command("register").description("Register a new knowledge token").r
|
|
|
18373
19416
|
{
|
|
18374
19417
|
...token,
|
|
18375
19418
|
card: cardUserId ? { userId: cardUserId } : null,
|
|
18376
|
-
possible_duplicates: possibleDuplicates
|
|
19419
|
+
possible_duplicates: possibleDuplicates,
|
|
19420
|
+
knowledgeContexts: assignedContexts.map((c) => ({
|
|
19421
|
+
name: c.name,
|
|
19422
|
+
label: c.label,
|
|
19423
|
+
language: c.language
|
|
19424
|
+
}))
|
|
18377
19425
|
},
|
|
18378
19426
|
null,
|
|
18379
19427
|
2
|
|
@@ -18389,6 +19437,11 @@ tokenCommand.command("register").description("Register a new knowledge token").r
|
|
|
18389
19437
|
console.log(` Domain: ${token.domain || "(none)"}`);
|
|
18390
19438
|
console.log(` Bloom: ${token.bloom_level}`);
|
|
18391
19439
|
console.log(` Question: ${token.question}`);
|
|
19440
|
+
if (assignedContexts.length > 0) {
|
|
19441
|
+
console.log(
|
|
19442
|
+
` Contexts: ${assignedContexts.map((c) => c.name).join(", ")}`
|
|
19443
|
+
);
|
|
19444
|
+
}
|
|
18392
19445
|
if (token.source_link) {
|
|
18393
19446
|
console.log(` Source: ${token.source_link}`);
|
|
18394
19447
|
}
|
|
@@ -18495,11 +19548,15 @@ tokenCommand.command("find").description("Fuzzy search for tokens").requiredOpti
|
|
|
18495
19548
|
console.log("\n(Use slug for CLI commands -- slugs are technical IDs)");
|
|
18496
19549
|
});
|
|
18497
19550
|
});
|
|
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) => {
|
|
19551
|
+
tokenCommand.command("list").description("List all tokens").option("--domain <domain>", "Filter by domain").option("--knowledge-context <context>", "Filter by knowledge context").option("--json", "Output as JSON").option("--quiet", "Suppress output (exit code only)").action(async (opts) => {
|
|
18499
19552
|
await withDb(async (db) => {
|
|
19553
|
+
const filterOpts = {};
|
|
19554
|
+
if (opts.domain) filterOpts.domain = opts.domain;
|
|
19555
|
+
if (opts.knowledgeContext)
|
|
19556
|
+
filterOpts.knowledgeContext = opts.knowledgeContext;
|
|
18500
19557
|
const tokens = await listTokens(
|
|
18501
19558
|
db,
|
|
18502
|
-
|
|
19559
|
+
Object.keys(filterOpts).length ? filterOpts : void 0
|
|
18503
19560
|
);
|
|
18504
19561
|
if (opts.quiet) return;
|
|
18505
19562
|
if (opts.json) {
|
|
@@ -18797,7 +19854,7 @@ import { existsSync as existsSync23 } from "fs";
|
|
|
18797
19854
|
import { homedir as homedir12 } from "os";
|
|
18798
19855
|
import { dirname as dirname8, join as join22 } from "path";
|
|
18799
19856
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
18800
|
-
import { Command as
|
|
19857
|
+
import { Command as Command23 } from "commander";
|
|
18801
19858
|
var C3 = {
|
|
18802
19859
|
reset: "\x1B[0m",
|
|
18803
19860
|
red: "\x1B[31m",
|
|
@@ -18977,7 +20034,7 @@ function createShortcuts(appPath, repoRoot) {
|
|
|
18977
20034
|
console.error(`${C3.red}\u2717 Could not create shortcuts.${C3.reset}`);
|
|
18978
20035
|
}
|
|
18979
20036
|
}
|
|
18980
|
-
var uiCommand = new
|
|
20037
|
+
var uiCommand = new Command23("ui").description("Launch the ZAM Desktop GUI (Active-Recall Studio)").option("--dev", "Run in hot-reload development mode (needs Rust)").option(
|
|
18981
20038
|
"--build",
|
|
18982
20039
|
"Build the native installer for your OS (needs Rust, one-time)"
|
|
18983
20040
|
).option("--shortcut", "Create Desktop + Start-menu shortcuts to the GUI").action((opts) => {
|
|
@@ -19085,7 +20142,7 @@ import { existsSync as existsSync24, readFileSync as readFileSync15, realpathSyn
|
|
|
19085
20142
|
import { dirname as dirname9, join as join23 } from "path";
|
|
19086
20143
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
19087
20144
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
19088
|
-
import { Command as
|
|
20145
|
+
import { Command as Command24 } from "commander";
|
|
19089
20146
|
var GITHUB_REPO = "zam-os/zam";
|
|
19090
20147
|
var CHANNELS = [
|
|
19091
20148
|
"developer",
|
|
@@ -19163,7 +20220,7 @@ function render2(decision) {
|
|
|
19163
20220
|
);
|
|
19164
20221
|
}
|
|
19165
20222
|
}
|
|
19166
|
-
var checkCmd = new
|
|
20223
|
+
var checkCmd = new Command24("check").description("Check whether a newer ZAM has been released").option(
|
|
19167
20224
|
"--latest <version>",
|
|
19168
20225
|
"Compare against this version instead of fetching"
|
|
19169
20226
|
).option("--channel <channel>", "Override the detected install channel").option("--json", "Output as JSON").action(
|
|
@@ -19330,7 +20387,7 @@ ${C4.dim}This install updates through the desktop app's signed updater \u2014 op
|
|
|
19330
20387
|
process.exit(1);
|
|
19331
20388
|
}
|
|
19332
20389
|
}
|
|
19333
|
-
var updateCommand = new
|
|
20390
|
+
var updateCommand = new Command24("update").description(
|
|
19334
20391
|
"Update ZAM to the latest release (use `update check` to only check)"
|
|
19335
20392
|
).option("-y, --yes", "Apply without confirmation").option(
|
|
19336
20393
|
"--force",
|
|
@@ -19340,8 +20397,8 @@ var updateCommand = new Command23("update").description(
|
|
|
19340
20397
|
}).addCommand(checkCmd);
|
|
19341
20398
|
|
|
19342
20399
|
// src/cli/commands/whoami.ts
|
|
19343
|
-
import { Command as
|
|
19344
|
-
var whoamiCommand = new
|
|
20400
|
+
import { Command as Command25 } from "commander";
|
|
20401
|
+
var whoamiCommand = new Command25("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) => {
|
|
19345
20402
|
await withDb(async (db) => {
|
|
19346
20403
|
if (opts.set) {
|
|
19347
20404
|
await setSetting(db, "user.id", opts.set);
|
|
@@ -19382,7 +20439,7 @@ import { existsSync as existsSync25, writeFileSync as writeFileSync11 } from "fs
|
|
|
19382
20439
|
import { homedir as homedir13 } from "os";
|
|
19383
20440
|
import { join as join24, resolve as resolve9 } from "path";
|
|
19384
20441
|
import { confirm as confirm4, input as input7 } from "@inquirer/prompts";
|
|
19385
|
-
import { Command as
|
|
20442
|
+
import { Command as Command26 } from "commander";
|
|
19386
20443
|
function runGit2(cwd, args) {
|
|
19387
20444
|
try {
|
|
19388
20445
|
return execFileSync4("git", args, {
|
|
@@ -19400,7 +20457,7 @@ function ghRepoCreateArgs(repoName, repoVisibility) {
|
|
|
19400
20457
|
function gitRemoteArgs(githubUrl, hasOrigin) {
|
|
19401
20458
|
return hasOrigin ? ["remote", "set-url", "origin", githubUrl] : ["remote", "add", "origin", githubUrl];
|
|
19402
20459
|
}
|
|
19403
|
-
var workspaceCommand = new
|
|
20460
|
+
var workspaceCommand = new Command26("workspace").description(
|
|
19404
20461
|
"Manage your ZAM learning workspace"
|
|
19405
20462
|
);
|
|
19406
20463
|
var WORKSPACE_KINDS = [
|
|
@@ -19755,13 +20812,14 @@ var __dirname = dirname10(fileURLToPath5(import.meta.url));
|
|
|
19755
20812
|
var pkg = JSON.parse(
|
|
19756
20813
|
readFileSync16(join25(__dirname, "..", "..", "package.json"), "utf-8")
|
|
19757
20814
|
);
|
|
19758
|
-
var program = new
|
|
20815
|
+
var program = new Command27();
|
|
19759
20816
|
program.name("zam").description(
|
|
19760
20817
|
"The Symbiotic Learning Kernel: Elevating Human Intelligence through AI Collaboration."
|
|
19761
20818
|
).version(pkg.version);
|
|
19762
20819
|
program.addCommand(initCommand);
|
|
19763
20820
|
program.addCommand(setupCommand);
|
|
19764
20821
|
program.addCommand(tokenCommand);
|
|
20822
|
+
program.addCommand(knowledgeContextCommand);
|
|
19765
20823
|
program.addCommand(doctorCommand);
|
|
19766
20824
|
program.addCommand(cardCommand);
|
|
19767
20825
|
program.addCommand(sessionCommand);
|