threadnote 0.7.12 → 1.0.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/mcp_server.cjs +344 -74
- package/dist/threadnote.cjs +726 -288
- package/docs/index.html +5 -5
- package/docs/migration.md +1 -1
- package/docs/troubleshooting.md +6 -5
- package/manager/app.css +42 -1
- package/manager/app.js +144 -59
- package/package.json +1 -1
package/dist/threadnote.cjs
CHANGED
|
@@ -3472,7 +3472,7 @@ var {
|
|
|
3472
3472
|
// src/constants.ts
|
|
3473
3473
|
var DEFAULT_HOST = "127.0.0.1";
|
|
3474
3474
|
var DEFAULT_PORT = 1933;
|
|
3475
|
-
var DEFAULT_OPENVIKING_VERSION = "0.3.
|
|
3475
|
+
var DEFAULT_OPENVIKING_VERSION = "0.3.24";
|
|
3476
3476
|
var DEFAULT_ACCOUNT = "local";
|
|
3477
3477
|
var DEFAULT_AGENT_ID = "threadnote";
|
|
3478
3478
|
var OPENVIKING_PACKAGE_NAME = "openviking[local-embed]";
|
|
@@ -3523,8 +3523,8 @@ var DEFAULT_SEED_PATTERNS = [
|
|
|
3523
3523
|
];
|
|
3524
3524
|
|
|
3525
3525
|
// src/hooks.ts
|
|
3526
|
-
var
|
|
3527
|
-
var
|
|
3526
|
+
var import_promises8 = require("node:fs/promises");
|
|
3527
|
+
var import_node_path10 = require("node:path");
|
|
3528
3528
|
|
|
3529
3529
|
// src/mcp.ts
|
|
3530
3530
|
var import_node_fs2 = require("node:fs");
|
|
@@ -4773,8 +4773,8 @@ function existsSyncDirectory(path) {
|
|
|
4773
4773
|
}
|
|
4774
4774
|
|
|
4775
4775
|
// src/memory.ts
|
|
4776
|
-
var
|
|
4777
|
-
var
|
|
4776
|
+
var import_promises6 = require("node:fs/promises");
|
|
4777
|
+
var import_node_path7 = require("node:path");
|
|
4778
4778
|
|
|
4779
4779
|
// src/manifest.ts
|
|
4780
4780
|
var import_promises3 = require("node:fs/promises");
|
|
@@ -7472,8 +7472,284 @@ function readStringArray(object, key) {
|
|
|
7472
7472
|
return value;
|
|
7473
7473
|
}
|
|
7474
7474
|
|
|
7475
|
-
// src/
|
|
7475
|
+
// src/index_repair.ts
|
|
7476
|
+
var import_promises4 = require("node:fs/promises");
|
|
7477
|
+
var import_node_path4 = require("node:path");
|
|
7478
|
+
|
|
7479
|
+
// src/runtime.ts
|
|
7480
|
+
var import_node_fs3 = require("node:fs");
|
|
7481
|
+
var import_node_os3 = require("node:os");
|
|
7476
7482
|
var import_node_path3 = require("node:path");
|
|
7483
|
+
function getRuntimeConfig(program2, manifestOverride) {
|
|
7484
|
+
const options = program2.opts();
|
|
7485
|
+
const threadnoteHome = expandPath(options.home ?? process.env.THREADNOTE_HOME ?? "~/.openviking");
|
|
7486
|
+
const manifestPath = expandPath(
|
|
7487
|
+
manifestOverride ?? options.manifest ?? process.env.THREADNOTE_MANIFEST ?? defaultManifestPath(threadnoteHome)
|
|
7488
|
+
);
|
|
7489
|
+
return {
|
|
7490
|
+
account: process.env.THREADNOTE_ACCOUNT ?? DEFAULT_ACCOUNT,
|
|
7491
|
+
agentContextHome: threadnoteHome,
|
|
7492
|
+
agentId: process.env.THREADNOTE_AGENT_ID ?? DEFAULT_AGENT_ID,
|
|
7493
|
+
host: options.host ?? process.env.THREADNOTE_HOST ?? DEFAULT_HOST,
|
|
7494
|
+
manifestPath,
|
|
7495
|
+
openVikingVersion: process.env.THREADNOTE_OPENVIKING_VERSION ?? DEFAULT_OPENVIKING_VERSION,
|
|
7496
|
+
port: options.port ?? parsePort(process.env.THREADNOTE_PORT ?? String(DEFAULT_PORT)),
|
|
7497
|
+
user: process.env.THREADNOTE_USER ?? (0, import_node_os3.userInfo)().username
|
|
7498
|
+
};
|
|
7499
|
+
}
|
|
7500
|
+
function defaultManifestPath(agentContextHome) {
|
|
7501
|
+
const userManifest = (0, import_node_path3.join)(agentContextHome, USER_MANIFEST_NAME);
|
|
7502
|
+
return (0, import_node_fs3.existsSync)(userManifest) ? userManifest : builtInExampleManifestPath();
|
|
7503
|
+
}
|
|
7504
|
+
function builtInExampleManifestPath() {
|
|
7505
|
+
return (0, import_node_path3.join)(toolRoot(), "config", "seed-manifest.example.yaml");
|
|
7506
|
+
}
|
|
7507
|
+
function openVikingHealthUrl(config) {
|
|
7508
|
+
return `http://${config.host}:${config.port}/health`;
|
|
7509
|
+
}
|
|
7510
|
+
function openVikingLogPath(config) {
|
|
7511
|
+
return (0, import_node_path3.join)(config.agentContextHome, "logs", "server.log");
|
|
7512
|
+
}
|
|
7513
|
+
function openVikingServerArgs(config) {
|
|
7514
|
+
return ["--config", (0, import_node_path3.join)(config.agentContextHome, "ov.conf"), "--host", config.host, "--port", String(config.port)];
|
|
7515
|
+
}
|
|
7516
|
+
function withIdentity(config, args) {
|
|
7517
|
+
return [...args, "--account", config.account, "--user", config.user, "--agent-id", config.agentId];
|
|
7518
|
+
}
|
|
7519
|
+
function renderTemplate(template, config, extras = {}) {
|
|
7520
|
+
let rendered = template.replaceAll("{{THREADNOTE_HOME}}", config.agentContextHome).replaceAll("{{OPENVIKING_ACCOUNT}}", config.account).replaceAll("{{OPENVIKING_AGENT_ID}}", config.agentId).replaceAll("{{OPENVIKING_HOST}}", config.host).replaceAll("{{OPENVIKING_PORT}}", String(config.port)).replaceAll("{{OPENVIKING_USER}}", config.user);
|
|
7521
|
+
for (const [key, value] of Object.entries(extras)) {
|
|
7522
|
+
rendered = rendered.replaceAll(`{{${key}}}`, () => value);
|
|
7523
|
+
}
|
|
7524
|
+
return rendered;
|
|
7525
|
+
}
|
|
7526
|
+
|
|
7527
|
+
// src/index_repair.ts
|
|
7528
|
+
var AUTO_REPAIR_STATE_FILE = "index-auto-repair.json";
|
|
7529
|
+
var AUTO_REPAIR_TTL_MS = 6 * 60 * 60 * 1e3;
|
|
7530
|
+
var MAX_SCAN_DEPTH = 5;
|
|
7531
|
+
var MAX_REPAIR_TARGETS = 4;
|
|
7532
|
+
var MAINTENANCE_MAX_REPAIR_TARGETS = 16;
|
|
7533
|
+
async function repairStaleRecallIndex(config, ov, options = {}) {
|
|
7534
|
+
const targets = await findStaleRecallIndexTargets(config, options);
|
|
7535
|
+
if (targets.length === 0) {
|
|
7536
|
+
return { repairedUris: [], skippedRecentUris: [], warnings: [] };
|
|
7537
|
+
}
|
|
7538
|
+
const state = options.dryRun === true ? { entries: {}, version: 1 } : await readAutoRepairState(config);
|
|
7539
|
+
const now = Date.now();
|
|
7540
|
+
const repairedUris = [];
|
|
7541
|
+
const skippedRecentUris = [];
|
|
7542
|
+
const warnings = [];
|
|
7543
|
+
for (const target of targets.slice(0, options.maxTargets ?? MAX_REPAIR_TARGETS)) {
|
|
7544
|
+
const previous = state.entries[target.uri];
|
|
7545
|
+
if (previous?.signature === target.signature && now - Date.parse(previous.repairedAt) < AUTO_REPAIR_TTL_MS && options.dryRun !== true && options.ignoreBackoff !== true) {
|
|
7546
|
+
skippedRecentUris.push(target.uri);
|
|
7547
|
+
continue;
|
|
7548
|
+
}
|
|
7549
|
+
if (options.dryRun === true) {
|
|
7550
|
+
repairedUris.push(target.uri);
|
|
7551
|
+
continue;
|
|
7552
|
+
}
|
|
7553
|
+
const result = await runCommand(
|
|
7554
|
+
ov,
|
|
7555
|
+
withIdentity(config, ["reindex", target.uri, "--mode", "semantic_and_vectors", "--wait", "true"]),
|
|
7556
|
+
{ allowFailure: true }
|
|
7557
|
+
);
|
|
7558
|
+
if (result.exitCode === 0) {
|
|
7559
|
+
repairedUris.push(target.uri);
|
|
7560
|
+
state.entries[target.uri] = { repairedAt: new Date(now).toISOString(), signature: target.signature };
|
|
7561
|
+
} else {
|
|
7562
|
+
warnings.push(indexRepairWarning(target.uri, result));
|
|
7563
|
+
}
|
|
7564
|
+
}
|
|
7565
|
+
if (options.dryRun !== true && repairedUris.length > 0) {
|
|
7566
|
+
await writeAutoRepairState(config, state);
|
|
7567
|
+
}
|
|
7568
|
+
return { repairedUris, skippedRecentUris, warnings };
|
|
7569
|
+
}
|
|
7570
|
+
async function findStaleRecallIndexTargets(config, options = {}) {
|
|
7571
|
+
const roots = await scanRoots(config, options);
|
|
7572
|
+
const byUri = /* @__PURE__ */ new Map();
|
|
7573
|
+
for (const root of roots) {
|
|
7574
|
+
if (!await exists(root.path)) {
|
|
7575
|
+
continue;
|
|
7576
|
+
}
|
|
7577
|
+
const sidecars = await staleSidecars(root.path, root.uri);
|
|
7578
|
+
if (options.collapseToRoots === true) {
|
|
7579
|
+
if (sidecars.length === 0) {
|
|
7580
|
+
continue;
|
|
7581
|
+
}
|
|
7582
|
+
const parts = sidecars.map((sidecar) => `${sidecar.relativePath}
|
|
7583
|
+
${sidecar.content}`);
|
|
7584
|
+
byUri.set(root.uri, {
|
|
7585
|
+
parts,
|
|
7586
|
+
staleCount: sidecars.length,
|
|
7587
|
+
uri: root.uri
|
|
7588
|
+
});
|
|
7589
|
+
continue;
|
|
7590
|
+
}
|
|
7591
|
+
for (const sidecar of sidecars) {
|
|
7592
|
+
const current = byUri.get(sidecar.uri) ?? { parts: [], staleCount: 0, uri: sidecar.uri };
|
|
7593
|
+
current.parts.push(`${sidecar.relativePath}
|
|
7594
|
+
${sidecar.content}`);
|
|
7595
|
+
current.staleCount += 1;
|
|
7596
|
+
byUri.set(sidecar.uri, current);
|
|
7597
|
+
}
|
|
7598
|
+
}
|
|
7599
|
+
return [...byUri.values()].map((target) => ({
|
|
7600
|
+
signature: sha256([target.uri, ...target.parts.sort()].join("\n---\n")),
|
|
7601
|
+
staleCount: target.staleCount,
|
|
7602
|
+
uri: target.uri
|
|
7603
|
+
}));
|
|
7604
|
+
}
|
|
7605
|
+
function formatRecallIndexRepairMessages(result, options = {}) {
|
|
7606
|
+
const messages = [];
|
|
7607
|
+
for (const uri of result.repairedUris) {
|
|
7608
|
+
messages.push(
|
|
7609
|
+
`${options.dryRun === true ? "Would auto-reindex stale recall scope" : "Auto-reindexed stale recall scope"}: ${uri}`
|
|
7610
|
+
);
|
|
7611
|
+
}
|
|
7612
|
+
for (const warning2 of result.warnings) {
|
|
7613
|
+
messages.push(`Auto-index repair warning: ${warning2}`);
|
|
7614
|
+
}
|
|
7615
|
+
return messages;
|
|
7616
|
+
}
|
|
7617
|
+
function filterStaleRecallSummaryRows(output2) {
|
|
7618
|
+
return output2.split(/\r?\n/).filter((line) => !isStaleRecallSummaryRow(line)).join("\n").trim();
|
|
7619
|
+
}
|
|
7620
|
+
function isStaleRecallSummaryRow(line) {
|
|
7621
|
+
return /viking:\/\/\S+\/\.(?:abstract|overview)\.md\b/.test(line) && isStaleSummary(line);
|
|
7622
|
+
}
|
|
7623
|
+
async function scanRoots(config, options) {
|
|
7624
|
+
const accountRoot = (0, import_node_path4.join)(config.agentContextHome, "data", "viking", config.account);
|
|
7625
|
+
const roots = [
|
|
7626
|
+
{
|
|
7627
|
+
path: (0, import_node_path4.join)(accountRoot, "user", uriSegment(config.user), "memories"),
|
|
7628
|
+
uri: `viking://user/${uriSegment(config.user)}/memories`
|
|
7629
|
+
}
|
|
7630
|
+
];
|
|
7631
|
+
const query = options.query;
|
|
7632
|
+
if (query) {
|
|
7633
|
+
const project = await inferProjectFromQuery(config.manifestPath, query);
|
|
7634
|
+
const projectPath = project?.uri.startsWith("viking://resources/") ? (0, import_node_path4.join)(accountRoot, "resources", ...project.uri.slice("viking://resources/".length).split("/")) : void 0;
|
|
7635
|
+
if (project && projectPath) {
|
|
7636
|
+
roots.push({ path: projectPath, uri: project.uri });
|
|
7637
|
+
}
|
|
7638
|
+
}
|
|
7639
|
+
if (options.includeManifestResources === true) {
|
|
7640
|
+
roots.push(...await manifestResourceRoots(config, accountRoot));
|
|
7641
|
+
}
|
|
7642
|
+
const scanAgentSkills = options.includeAgentSkills === true || (query ? /\bskills?\b/.test(query.toLowerCase()) : false);
|
|
7643
|
+
if (scanAgentSkills) {
|
|
7644
|
+
roots.push({ path: (0, import_node_path4.join)(accountRoot, "resources", "agent-skills"), uri: "viking://resources/agent-skills" });
|
|
7645
|
+
}
|
|
7646
|
+
return dedupeRoots(roots);
|
|
7647
|
+
}
|
|
7648
|
+
async function manifestResourceRoots(config, accountRoot) {
|
|
7649
|
+
try {
|
|
7650
|
+
const manifest = await readSeedManifest(config.manifestPath);
|
|
7651
|
+
return manifest.projects.filter((project) => project.uri.startsWith("viking://resources/")).map((project) => ({
|
|
7652
|
+
path: (0, import_node_path4.join)(accountRoot, "resources", ...project.uri.slice("viking://resources/".length).split("/")),
|
|
7653
|
+
uri: project.uri
|
|
7654
|
+
}));
|
|
7655
|
+
} catch (_err) {
|
|
7656
|
+
return [];
|
|
7657
|
+
}
|
|
7658
|
+
}
|
|
7659
|
+
function dedupeRoots(roots) {
|
|
7660
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7661
|
+
const deduped = [];
|
|
7662
|
+
for (const root of roots) {
|
|
7663
|
+
if (seen.has(root.uri)) {
|
|
7664
|
+
continue;
|
|
7665
|
+
}
|
|
7666
|
+
seen.add(root.uri);
|
|
7667
|
+
deduped.push(root);
|
|
7668
|
+
}
|
|
7669
|
+
return deduped;
|
|
7670
|
+
}
|
|
7671
|
+
async function staleSidecars(rootPath, rootUri) {
|
|
7672
|
+
const results = [];
|
|
7673
|
+
async function visit(path, depth) {
|
|
7674
|
+
let entries;
|
|
7675
|
+
try {
|
|
7676
|
+
entries = await (0, import_promises4.readdir)(path, { withFileTypes: true });
|
|
7677
|
+
} catch (_err) {
|
|
7678
|
+
return;
|
|
7679
|
+
}
|
|
7680
|
+
for (const entry of entries) {
|
|
7681
|
+
const childPath = (0, import_node_path4.join)(path, entry.name);
|
|
7682
|
+
if (entry.isFile() && isSummarySidecar(entry.name)) {
|
|
7683
|
+
let content;
|
|
7684
|
+
try {
|
|
7685
|
+
content = await (0, import_promises4.readFile)(childPath, "utf8");
|
|
7686
|
+
} catch (_err) {
|
|
7687
|
+
continue;
|
|
7688
|
+
}
|
|
7689
|
+
if (!isStaleSummary(content)) {
|
|
7690
|
+
continue;
|
|
7691
|
+
}
|
|
7692
|
+
const parentPath = (0, import_node_path4.dirname)(childPath);
|
|
7693
|
+
const parentRelative = toPosixPath((0, import_node_path4.relative)(rootPath, parentPath));
|
|
7694
|
+
const relativePath = toPosixPath((0, import_node_path4.relative)(rootPath, childPath));
|
|
7695
|
+
results.push({
|
|
7696
|
+
content: content.trim(),
|
|
7697
|
+
relativePath,
|
|
7698
|
+
uri: parentRelative ? `${trimLocalRootUri(rootUri)}/${parentRelative}` : trimLocalRootUri(rootUri)
|
|
7699
|
+
});
|
|
7700
|
+
} else if (entry.isDirectory() && depth < MAX_SCAN_DEPTH) {
|
|
7701
|
+
await visit(childPath, depth + 1);
|
|
7702
|
+
}
|
|
7703
|
+
}
|
|
7704
|
+
}
|
|
7705
|
+
await visit(rootPath, 0);
|
|
7706
|
+
return results;
|
|
7707
|
+
}
|
|
7708
|
+
function isSummarySidecar(name) {
|
|
7709
|
+
return name === ".abstract.md" || name === ".overview.md";
|
|
7710
|
+
}
|
|
7711
|
+
function isStaleSummary(content) {
|
|
7712
|
+
return content.includes("[Directory overview is not ready]") || content.includes("[Directory abstract is not ready]");
|
|
7713
|
+
}
|
|
7714
|
+
function trimLocalRootUri(uri) {
|
|
7715
|
+
return uri.endsWith("/") ? uri.slice(0, -1) : uri;
|
|
7716
|
+
}
|
|
7717
|
+
async function readAutoRepairState(config) {
|
|
7718
|
+
const raw = await readFileIfExists(autoRepairStatePath(config));
|
|
7719
|
+
if (!raw) {
|
|
7720
|
+
return { entries: {}, version: 1 };
|
|
7721
|
+
}
|
|
7722
|
+
try {
|
|
7723
|
+
const parsed = JSON.parse(raw);
|
|
7724
|
+
if (!isJsonObject(parsed) || parsed.version !== 1 || !isJsonObject(parsed.entries)) {
|
|
7725
|
+
return { entries: {}, version: 1 };
|
|
7726
|
+
}
|
|
7727
|
+
const entries = {};
|
|
7728
|
+
for (const [uri, entry] of Object.entries(parsed.entries)) {
|
|
7729
|
+
if (isJsonObject(entry) && typeof entry.signature === "string" && typeof entry.repairedAt === "string") {
|
|
7730
|
+
entries[uri] = { repairedAt: entry.repairedAt, signature: entry.signature };
|
|
7731
|
+
}
|
|
7732
|
+
}
|
|
7733
|
+
return { entries, version: 1 };
|
|
7734
|
+
} catch (_err) {
|
|
7735
|
+
return { entries: {}, version: 1 };
|
|
7736
|
+
}
|
|
7737
|
+
}
|
|
7738
|
+
async function writeAutoRepairState(config, state) {
|
|
7739
|
+
const path = autoRepairStatePath(config);
|
|
7740
|
+
await ensureDirectory((0, import_node_path4.dirname)(path), false);
|
|
7741
|
+
await (0, import_promises4.writeFile)(path, `${JSON.stringify(state, void 0, 2)}
|
|
7742
|
+
`, { encoding: "utf8", mode: 384 });
|
|
7743
|
+
}
|
|
7744
|
+
function autoRepairStatePath(config) {
|
|
7745
|
+
return (0, import_node_path4.join)(config.agentContextHome, AUTO_REPAIR_STATE_FILE);
|
|
7746
|
+
}
|
|
7747
|
+
function indexRepairWarning(uri, result) {
|
|
7748
|
+
return `${uri}: ${result.stderr.trim() || result.stdout.trim() || "reindex failed"}`;
|
|
7749
|
+
}
|
|
7750
|
+
|
|
7751
|
+
// src/memory_hygiene.ts
|
|
7752
|
+
var import_node_path5 = require("node:path");
|
|
7477
7753
|
var HYGIENE_SOURCES_HEADING = "## Threadnote Hygiene Sources";
|
|
7478
7754
|
var STALE_HANDOFF_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
7479
7755
|
function parseMemoryDocument(uri, content) {
|
|
@@ -7766,7 +8042,7 @@ function branchFromBody(body) {
|
|
|
7766
8042
|
return branch?.split(/\s+/)[0]?.replace(/[.,;:]+$/g, "");
|
|
7767
8043
|
}
|
|
7768
8044
|
function topicFromUri(uri) {
|
|
7769
|
-
const name = (0,
|
|
8045
|
+
const name = (0, import_node_path5.basename)(uri).replace(/\.md$/, "");
|
|
7770
8046
|
return name.startsWith("threadnote-") ? void 0 : name;
|
|
7771
8047
|
}
|
|
7772
8048
|
function parseProjectFromUri(uri) {
|
|
@@ -7796,7 +8072,7 @@ function preferredKeepRecord(records, topic) {
|
|
|
7796
8072
|
}
|
|
7797
8073
|
function isStableRecord(record, topic) {
|
|
7798
8074
|
const recordTopic = topic ?? topicForRecord(record);
|
|
7799
|
-
return recordTopic !== void 0 && (0,
|
|
8075
|
+
return recordTopic !== void 0 && (0, import_node_path5.basename)(record.uri) === `${uriSegment(recordTopic)}.md`;
|
|
7800
8076
|
}
|
|
7801
8077
|
function sortedNewestFirst(records) {
|
|
7802
8078
|
return [...records].sort((left, right) => {
|
|
@@ -7907,58 +8183,10 @@ function formatPlanSection(title, lines) {
|
|
|
7907
8183
|
return [`${title}:`, ...lines.map((line) => `- ${line}`)].join("\n");
|
|
7908
8184
|
}
|
|
7909
8185
|
|
|
7910
|
-
// src/runtime.ts
|
|
7911
|
-
var import_node_fs3 = require("node:fs");
|
|
7912
|
-
var import_node_os3 = require("node:os");
|
|
7913
|
-
var import_node_path4 = require("node:path");
|
|
7914
|
-
function getRuntimeConfig(program2, manifestOverride) {
|
|
7915
|
-
const options = program2.opts();
|
|
7916
|
-
const threadnoteHome = expandPath(options.home ?? process.env.THREADNOTE_HOME ?? "~/.openviking");
|
|
7917
|
-
const manifestPath = expandPath(
|
|
7918
|
-
manifestOverride ?? options.manifest ?? process.env.THREADNOTE_MANIFEST ?? defaultManifestPath(threadnoteHome)
|
|
7919
|
-
);
|
|
7920
|
-
return {
|
|
7921
|
-
account: process.env.THREADNOTE_ACCOUNT ?? DEFAULT_ACCOUNT,
|
|
7922
|
-
agentContextHome: threadnoteHome,
|
|
7923
|
-
agentId: process.env.THREADNOTE_AGENT_ID ?? DEFAULT_AGENT_ID,
|
|
7924
|
-
host: options.host ?? process.env.THREADNOTE_HOST ?? DEFAULT_HOST,
|
|
7925
|
-
manifestPath,
|
|
7926
|
-
openVikingVersion: process.env.THREADNOTE_OPENVIKING_VERSION ?? DEFAULT_OPENVIKING_VERSION,
|
|
7927
|
-
port: options.port ?? parsePort(process.env.THREADNOTE_PORT ?? String(DEFAULT_PORT)),
|
|
7928
|
-
user: process.env.THREADNOTE_USER ?? (0, import_node_os3.userInfo)().username
|
|
7929
|
-
};
|
|
7930
|
-
}
|
|
7931
|
-
function defaultManifestPath(agentContextHome) {
|
|
7932
|
-
const userManifest = (0, import_node_path4.join)(agentContextHome, USER_MANIFEST_NAME);
|
|
7933
|
-
return (0, import_node_fs3.existsSync)(userManifest) ? userManifest : builtInExampleManifestPath();
|
|
7934
|
-
}
|
|
7935
|
-
function builtInExampleManifestPath() {
|
|
7936
|
-
return (0, import_node_path4.join)(toolRoot(), "config", "seed-manifest.example.yaml");
|
|
7937
|
-
}
|
|
7938
|
-
function openVikingHealthUrl(config) {
|
|
7939
|
-
return `http://${config.host}:${config.port}/health`;
|
|
7940
|
-
}
|
|
7941
|
-
function openVikingLogPath(config) {
|
|
7942
|
-
return (0, import_node_path4.join)(config.agentContextHome, "logs", "server.log");
|
|
7943
|
-
}
|
|
7944
|
-
function openVikingServerArgs(config) {
|
|
7945
|
-
return ["--config", (0, import_node_path4.join)(config.agentContextHome, "ov.conf"), "--host", config.host, "--port", String(config.port)];
|
|
7946
|
-
}
|
|
7947
|
-
function withIdentity(config, args) {
|
|
7948
|
-
return [...args, "--account", config.account, "--user", config.user, "--agent-id", config.agentId];
|
|
7949
|
-
}
|
|
7950
|
-
function renderTemplate(template, config, extras = {}) {
|
|
7951
|
-
let rendered = template.replaceAll("{{THREADNOTE_HOME}}", config.agentContextHome).replaceAll("{{OPENVIKING_ACCOUNT}}", config.account).replaceAll("{{OPENVIKING_AGENT_ID}}", config.agentId).replaceAll("{{OPENVIKING_HOST}}", config.host).replaceAll("{{OPENVIKING_PORT}}", String(config.port)).replaceAll("{{OPENVIKING_USER}}", config.user);
|
|
7952
|
-
for (const [key, value] of Object.entries(extras)) {
|
|
7953
|
-
rendered = rendered.replaceAll(`{{${key}}}`, () => value);
|
|
7954
|
-
}
|
|
7955
|
-
return rendered;
|
|
7956
|
-
}
|
|
7957
|
-
|
|
7958
8186
|
// src/share.ts
|
|
7959
|
-
var
|
|
8187
|
+
var import_promises5 = require("node:fs/promises");
|
|
7960
8188
|
var import_node_os4 = require("node:os");
|
|
7961
|
-
var
|
|
8189
|
+
var import_node_path6 = require("node:path");
|
|
7962
8190
|
var TEAMS_FILE_VERSION = 1;
|
|
7963
8191
|
var SHARED_SEGMENT = "shared";
|
|
7964
8192
|
var SHAREABLE_MEMORY_KIND_DIRS = ["durable"];
|
|
@@ -8027,8 +8255,8 @@ async function runShareInit(config, remoteUrl, options) {
|
|
|
8027
8255
|
if (await exists(gitdir)) {
|
|
8028
8256
|
throw new Error(`Gitdir already exists at ${gitdir}; remove it or pick a different team name.`);
|
|
8029
8257
|
}
|
|
8030
|
-
await ensureDirectory((0,
|
|
8031
|
-
await ensureDirectory((0,
|
|
8258
|
+
await ensureDirectory((0, import_node_path6.dirname)(worktree), dryRun);
|
|
8259
|
+
await ensureDirectory((0, import_node_path6.dirname)(gitdir), dryRun);
|
|
8032
8260
|
const git = await requiredExecutable("git");
|
|
8033
8261
|
await maybeRun(dryRun, git, ["clone", `--separate-git-dir=${gitdir}`, "--", remoteUrl, worktree]);
|
|
8034
8262
|
const newConfig = {
|
|
@@ -8059,7 +8287,7 @@ async function runShareInit(config, remoteUrl, options) {
|
|
|
8059
8287
|
var SHARED_GITIGNORE_PATTERNS = ["**/.abstract.md", "**/.overview.md"];
|
|
8060
8288
|
var SHARED_GITIGNORE_HEADER = "# Threadnote: ignore OpenViking-generated directory summaries.";
|
|
8061
8289
|
async function ensureSharedGitignore(worktree, git, push) {
|
|
8062
|
-
const gitignorePath = (0,
|
|
8290
|
+
const gitignorePath = (0, import_node_path6.join)(worktree, ".gitignore");
|
|
8063
8291
|
const existing = await readFileIfExists(gitignorePath) ?? "";
|
|
8064
8292
|
const lines = existing.split("\n").map((line) => line.trim());
|
|
8065
8293
|
const missingPatterns = SHARED_GITIGNORE_PATTERNS.filter((pattern) => !lines.includes(pattern));
|
|
@@ -8078,7 +8306,7 @@ async function ensureSharedGitignore(worktree, git, push) {
|
|
|
8078
8306
|
segments.push(SHARED_GITIGNORE_HEADER, "\n");
|
|
8079
8307
|
}
|
|
8080
8308
|
segments.push(missingPatterns.join("\n"), "\n");
|
|
8081
|
-
await (0,
|
|
8309
|
+
await (0, import_promises5.writeFile)(gitignorePath, `${existing}${segments.join("")}`, { encoding: "utf8" });
|
|
8082
8310
|
console.log(`Added ${missingPatterns.join(", ")} to ${portablePath(gitignorePath)}`);
|
|
8083
8311
|
await maybeRun(false, git, ["-C", worktree, "add", ".gitignore"]);
|
|
8084
8312
|
const commitResult = await runCommand(
|
|
@@ -8160,7 +8388,7 @@ function autoShareState(config) {
|
|
|
8160
8388
|
return state;
|
|
8161
8389
|
}
|
|
8162
8390
|
function pendingReindexesPath(config) {
|
|
8163
|
-
return (0,
|
|
8391
|
+
return (0, import_node_path6.join)(config.agentContextHome, "share", "auto-sync-pending-reindexes.json");
|
|
8164
8392
|
}
|
|
8165
8393
|
async function loadPendingReindexes(config, state) {
|
|
8166
8394
|
const raw = await readFileIfExists(pendingReindexesPath(config));
|
|
@@ -8197,18 +8425,18 @@ async function loadPendingReindexes(config, state) {
|
|
|
8197
8425
|
async function writePendingReindexes(config, state) {
|
|
8198
8426
|
const path = pendingReindexesPath(config);
|
|
8199
8427
|
if (state.pendingReindexes.size === 0) {
|
|
8200
|
-
await (0,
|
|
8428
|
+
await (0, import_promises5.rm)(path, { force: true });
|
|
8201
8429
|
return;
|
|
8202
8430
|
}
|
|
8203
8431
|
const contents = {
|
|
8204
8432
|
teams: Object.fromEntries(state.pendingReindexes),
|
|
8205
8433
|
version: 1
|
|
8206
8434
|
};
|
|
8207
|
-
await (0,
|
|
8435
|
+
await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(path), { recursive: true });
|
|
8208
8436
|
const tempPath = `${path}.${process.pid}.tmp`;
|
|
8209
|
-
await (0,
|
|
8437
|
+
await (0, import_promises5.writeFile)(tempPath, `${JSON.stringify(contents, void 0, 2)}
|
|
8210
8438
|
`, { encoding: "utf8", mode: 384 });
|
|
8211
|
-
await (0,
|
|
8439
|
+
await (0, import_promises5.rename)(tempPath, path);
|
|
8212
8440
|
}
|
|
8213
8441
|
async function refreshShareUpdateStateLocked(config, state, options) {
|
|
8214
8442
|
const now = Date.now();
|
|
@@ -8297,7 +8525,7 @@ async function runShareSync(config, options) {
|
|
|
8297
8525
|
if (dryRun) {
|
|
8298
8526
|
console.log(`Would run: ${formatShellCommand(git, ["-C", worktree, "pull", "--rebase", DEFAULT_GIT_REMOTE_NAME])}`);
|
|
8299
8527
|
} else if (pullResult && pullResult.exitCode !== 0) {
|
|
8300
|
-
if (await exists((0,
|
|
8528
|
+
if (await exists((0, import_node_path6.join)(team.config.gitdir, "rebase-merge")) || await exists((0, import_node_path6.join)(team.config.gitdir, "rebase-apply"))) {
|
|
8301
8529
|
throw new Error(
|
|
8302
8530
|
`git pull --rebase reported conflicts in ${worktree}. The worktree is in a rebase-in-progress state.
|
|
8303
8531
|
Resolve the conflicts in-place, run \`git -C ${worktree} rebase --continue\` (or --abort), then re-run \`threadnote share sync\`.`
|
|
@@ -8359,7 +8587,7 @@ async function runShareSyncQuiet(config, state, options) {
|
|
|
8359
8587
|
const beforeRev = await gitOutput(worktree, ["rev-parse", "HEAD"], false);
|
|
8360
8588
|
const pullResult = await runCommand(git, ["-C", worktree, "rebase", "@{u}"], { allowFailure: true });
|
|
8361
8589
|
if (pullResult.exitCode !== 0) {
|
|
8362
|
-
if (await exists((0,
|
|
8590
|
+
if (await exists((0, import_node_path6.join)(team.config.gitdir, "rebase-merge")) || await exists((0, import_node_path6.join)(team.config.gitdir, "rebase-apply"))) {
|
|
8363
8591
|
throw new Error(
|
|
8364
8592
|
`Automatic share sync hit git conflicts in ${worktree}. Resolve them in-place, run \`git -C ${worktree} rebase --continue\` (or --abort), then rerun recall/read.`
|
|
8365
8593
|
);
|
|
@@ -8598,8 +8826,8 @@ async function runShareRename(config, options) {
|
|
|
8598
8826
|
console.log(`Would write teams file: ${teamsFilePath(config)}`);
|
|
8599
8827
|
return;
|
|
8600
8828
|
}
|
|
8601
|
-
await (0,
|
|
8602
|
-
await (0,
|
|
8829
|
+
await (0, import_promises5.rename)(oldTeam.config.worktree, newWorktree);
|
|
8830
|
+
await (0, import_promises5.rename)(oldTeam.config.gitdir, newGitdir);
|
|
8603
8831
|
await writeTeamsFile(config, updatedFile);
|
|
8604
8832
|
const git = await requiredExecutable("git");
|
|
8605
8833
|
await runCommand(git, ["-C", newWorktree, "config", "core.worktree", newWorktree]);
|
|
@@ -8681,12 +8909,12 @@ async function preserveSharedMemoriesLocally(config, team, dryRun) {
|
|
|
8681
8909
|
const files = await walkMemoryFiles(team.worktree);
|
|
8682
8910
|
let preserved = 0;
|
|
8683
8911
|
for (const file of files) {
|
|
8684
|
-
const rel = (0,
|
|
8912
|
+
const rel = (0, import_node_path6.relative)(team.worktree, file).split(import_node_path6.sep).join("/");
|
|
8685
8913
|
if (!rel.startsWith("durable/")) {
|
|
8686
8914
|
continue;
|
|
8687
8915
|
}
|
|
8688
8916
|
const targetUri = `viking://user/${uriSegment(config.user)}/memories/${rel}`;
|
|
8689
|
-
const content = await (0,
|
|
8917
|
+
const content = await (0, import_promises5.readFile)(file, "utf8");
|
|
8690
8918
|
if (dryRun) {
|
|
8691
8919
|
console.log(`Would preserve ${rel} -> ${targetUri}`);
|
|
8692
8920
|
} else {
|
|
@@ -8725,10 +8953,10 @@ function normalizeTeamName(input2) {
|
|
|
8725
8953
|
return candidate;
|
|
8726
8954
|
}
|
|
8727
8955
|
function teamsFilePath(config) {
|
|
8728
|
-
return (0,
|
|
8956
|
+
return (0, import_node_path6.join)(config.agentContextHome, "share", "teams.json");
|
|
8729
8957
|
}
|
|
8730
8958
|
function teamWorktreePath(config, team) {
|
|
8731
|
-
return (0,
|
|
8959
|
+
return (0, import_node_path6.join)(
|
|
8732
8960
|
config.agentContextHome,
|
|
8733
8961
|
"data",
|
|
8734
8962
|
"viking",
|
|
@@ -8741,7 +8969,7 @@ function teamWorktreePath(config, team) {
|
|
|
8741
8969
|
);
|
|
8742
8970
|
}
|
|
8743
8971
|
function teamGitdirPath(config, team) {
|
|
8744
|
-
return (0,
|
|
8972
|
+
return (0, import_node_path6.join)(config.agentContextHome, "share", "teams", `${team}.gitdir`);
|
|
8745
8973
|
}
|
|
8746
8974
|
async function readTeamsFile(config) {
|
|
8747
8975
|
const path = teamsFilePath(config);
|
|
@@ -8784,13 +9012,13 @@ async function readTeamsFile(config) {
|
|
|
8784
9012
|
}
|
|
8785
9013
|
async function writeTeamsFile(config, contents) {
|
|
8786
9014
|
const path = teamsFilePath(config);
|
|
8787
|
-
await (0,
|
|
9015
|
+
await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(path), { recursive: true });
|
|
8788
9016
|
const serializable = {
|
|
8789
9017
|
defaultTeam: contents.defaultTeam,
|
|
8790
9018
|
teams: contents.teams,
|
|
8791
9019
|
version: contents.version
|
|
8792
9020
|
};
|
|
8793
|
-
await (0,
|
|
9021
|
+
await (0, import_promises5.writeFile)(path, `${JSON.stringify(serializable, void 0, 2)}
|
|
8794
9022
|
`, { encoding: "utf8", mode: 384 });
|
|
8795
9023
|
}
|
|
8796
9024
|
async function resolveTeam(config, requested) {
|
|
@@ -8820,7 +9048,7 @@ async function assertWorktreeUsable(worktree) {
|
|
|
8820
9048
|
if (!await isDirectory(worktree)) {
|
|
8821
9049
|
throw new Error(`Cannot use ${worktree} as a worktree: not a directory.`);
|
|
8822
9050
|
}
|
|
8823
|
-
const entries = await (0,
|
|
9051
|
+
const entries = await (0, import_promises5.readdir)(worktree);
|
|
8824
9052
|
if (entries.length > 0) {
|
|
8825
9053
|
const preview = entries.slice(0, 5).join(", ");
|
|
8826
9054
|
const suffix = entries.length > 5 ? `, +${entries.length - 5} more` : "";
|
|
@@ -8844,7 +9072,7 @@ async function walkMemoryFiles(root) {
|
|
|
8844
9072
|
async function visit(path, depth) {
|
|
8845
9073
|
let entries;
|
|
8846
9074
|
try {
|
|
8847
|
-
entries = await (0,
|
|
9075
|
+
entries = await (0, import_promises5.readdir)(path, { withFileTypes: true });
|
|
8848
9076
|
} catch (err) {
|
|
8849
9077
|
console.warn(`Skipping ${path} during shared-tree walk: ${err instanceof Error ? err.message : String(err)}`);
|
|
8850
9078
|
return;
|
|
@@ -8853,7 +9081,7 @@ async function walkMemoryFiles(root) {
|
|
|
8853
9081
|
if (entry.name === ".git") {
|
|
8854
9082
|
continue;
|
|
8855
9083
|
}
|
|
8856
|
-
const full = (0,
|
|
9084
|
+
const full = (0, import_node_path6.join)(path, entry.name);
|
|
8857
9085
|
if (entry.isDirectory()) {
|
|
8858
9086
|
if (depth === 0 && !SHAREABLE_MEMORY_KIND_DIRS.includes(entry.name)) {
|
|
8859
9087
|
continue;
|
|
@@ -8877,7 +9105,7 @@ async function walkMemoryFiles(root) {
|
|
|
8877
9105
|
return out;
|
|
8878
9106
|
}
|
|
8879
9107
|
function workfileToVikingUri(config, team, filePath) {
|
|
8880
|
-
const rel = (0,
|
|
9108
|
+
const rel = (0, import_node_path6.relative)(team.worktree, filePath).split(import_node_path6.sep).join("/");
|
|
8881
9109
|
return `viking://user/${uriSegment(config.user)}/memories/${SHARED_SEGMENT}/${team.name}/${rel}`;
|
|
8882
9110
|
}
|
|
8883
9111
|
function isInSharedNamespace(config, uri) {
|
|
@@ -9025,13 +9253,14 @@ async function writeMemoryFile(config, ov, uri, content, initialMode, dryRun, op
|
|
|
9025
9253
|
}
|
|
9026
9254
|
return;
|
|
9027
9255
|
}
|
|
9028
|
-
const stagingDir = await (0,
|
|
9029
|
-
const tempPath = (0,
|
|
9256
|
+
const stagingDir = await (0, import_promises5.mkdtemp)((0, import_node_path6.join)((0, import_node_os4.tmpdir)(), "threadnote-share-"));
|
|
9257
|
+
const tempPath = (0, import_node_path6.join)(stagingDir, "body.txt");
|
|
9030
9258
|
try {
|
|
9031
|
-
await (0,
|
|
9259
|
+
await (0, import_promises5.writeFile)(tempPath, content, { encoding: "utf8", mode: 384 });
|
|
9032
9260
|
await writeOvFileWithRetry(config, ov, uri, tempPath, initialMode, options);
|
|
9261
|
+
await refreshMemoryIndex(config, ov, uri, options);
|
|
9033
9262
|
} finally {
|
|
9034
|
-
await (0,
|
|
9263
|
+
await (0, import_promises5.rm)(stagingDir, { force: true, recursive: true });
|
|
9035
9264
|
}
|
|
9036
9265
|
}
|
|
9037
9266
|
var BUSY_RETRY_BACKOFF_MS = [2e3, 5e3, 1e4, 2e4, 3e4];
|
|
@@ -9086,6 +9315,27 @@ async function writeOvFileWithRetry(config, ov, uri, fromFile, initialMode, opti
|
|
|
9086
9315
|
}
|
|
9087
9316
|
}
|
|
9088
9317
|
}
|
|
9318
|
+
async function refreshMemoryIndex(config, ov, uri, options = {}) {
|
|
9319
|
+
const result = await runCommand(
|
|
9320
|
+
ov,
|
|
9321
|
+
withIdentity(config, ["reindex", uri, "--mode", "semantic_and_vectors", "--wait", "true"]),
|
|
9322
|
+
{ allowFailure: true }
|
|
9323
|
+
);
|
|
9324
|
+
if (result.exitCode === 0) {
|
|
9325
|
+
if (options.quiet !== true && result.stdout.trim()) {
|
|
9326
|
+
console.log(result.stdout.trim());
|
|
9327
|
+
}
|
|
9328
|
+
if (options.quiet !== true && result.stderr.trim()) {
|
|
9329
|
+
console.error(result.stderr.trim());
|
|
9330
|
+
}
|
|
9331
|
+
return;
|
|
9332
|
+
}
|
|
9333
|
+
if (options.quiet !== true) {
|
|
9334
|
+
console.error(
|
|
9335
|
+
`Memory stored, but index refresh failed for ${uri}: ${result.stderr.trim() || result.stdout.trim()}`
|
|
9336
|
+
);
|
|
9337
|
+
}
|
|
9338
|
+
}
|
|
9089
9339
|
async function waitForOvQueue(ov, config, options = {}) {
|
|
9090
9340
|
const result = await runCommand(ov, withIdentity(config, ["wait", "--timeout", "120"]), { allowFailure: true });
|
|
9091
9341
|
if (options.quiet !== true && result.stdout.trim()) {
|
|
@@ -9106,7 +9356,7 @@ ${stdout2}`.toLowerCase();
|
|
|
9106
9356
|
return output2.includes("resource is busy") || output2.includes("resource is being processed");
|
|
9107
9357
|
}
|
|
9108
9358
|
async function ingestSingleFile(ov, config, uri, filePath, initialMode, options = {}) {
|
|
9109
|
-
const content = await (0,
|
|
9359
|
+
const content = await (0, import_promises5.readFile)(filePath, "utf8");
|
|
9110
9360
|
await writeMemoryFile(config, ov, uri, content, initialMode, false, options);
|
|
9111
9361
|
}
|
|
9112
9362
|
async function removeMemoryUri(config, ov, uri, dryRun, options = {}) {
|
|
@@ -9171,8 +9421,8 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
|
|
|
9171
9421
|
const oldRel = entries[index + 1];
|
|
9172
9422
|
const newRel = entries[index + 2];
|
|
9173
9423
|
if (oldRel && newRel) {
|
|
9174
|
-
changes.push({ path: (0,
|
|
9175
|
-
changes.push({ path: (0,
|
|
9424
|
+
changes.push({ path: (0, import_node_path6.join)(worktree, oldRel), relativePath: oldRel, status: "removed" });
|
|
9425
|
+
changes.push({ path: (0, import_node_path6.join)(worktree, newRel), relativePath: newRel, status: "added" });
|
|
9176
9426
|
}
|
|
9177
9427
|
index += 3;
|
|
9178
9428
|
continue;
|
|
@@ -9180,7 +9430,7 @@ async function listChangedFiles(worktree, beforeRev, afterRev) {
|
|
|
9180
9430
|
const rel = entries[index + 1];
|
|
9181
9431
|
if (rel) {
|
|
9182
9432
|
const status = head === "A" ? "added" : head === "D" ? "removed" : "modified";
|
|
9183
|
-
changes.push({ path: (0,
|
|
9433
|
+
changes.push({ path: (0, import_node_path6.join)(worktree, rel), relativePath: rel, status });
|
|
9184
9434
|
}
|
|
9185
9435
|
index += 2;
|
|
9186
9436
|
}
|
|
@@ -9302,7 +9552,7 @@ async function runMigrateMemories(config, options) {
|
|
|
9302
9552
|
const candidates = await legacyMemoryCandidates(config, sourceAccounts);
|
|
9303
9553
|
const existingHashes = await existingDurableMemoryHashes(config);
|
|
9304
9554
|
const ov = await openVikingCliForMode(dryRun);
|
|
9305
|
-
const migrationPath = (0,
|
|
9555
|
+
const migrationPath = (0, import_node_path7.join)(config.agentContextHome, "legacy-memory-migration.txt");
|
|
9306
9556
|
let duplicateCount = 0;
|
|
9307
9557
|
let migratedCount = 0;
|
|
9308
9558
|
let sensitiveCount = 0;
|
|
@@ -9338,8 +9588,8 @@ async function runMigrateMemories(config, options) {
|
|
|
9338
9588
|
}
|
|
9339
9589
|
console.log(`${dryRun ? "Would migrate" : "Migrating"} ${legacySourceLabel(candidate)} -> ${memoryUri}`);
|
|
9340
9590
|
if (!dryRun) {
|
|
9341
|
-
await (0,
|
|
9342
|
-
await (0,
|
|
9591
|
+
await (0, import_promises6.writeFile)(migrationPath, candidate.text, { encoding: "utf8", mode: 384 });
|
|
9592
|
+
await (0, import_promises6.chmod)(migrationPath, 384);
|
|
9343
9593
|
await writeDurableMemoryFile(ov, config, memoryUri, migrationPath, "create");
|
|
9344
9594
|
existingHashes.add(candidate.hash);
|
|
9345
9595
|
}
|
|
@@ -9347,7 +9597,7 @@ async function runMigrateMemories(config, options) {
|
|
|
9347
9597
|
}
|
|
9348
9598
|
} finally {
|
|
9349
9599
|
if (!dryRun) {
|
|
9350
|
-
await (0,
|
|
9600
|
+
await (0, import_promises6.rm)(migrationPath, { force: true });
|
|
9351
9601
|
}
|
|
9352
9602
|
}
|
|
9353
9603
|
console.log(
|
|
@@ -9365,7 +9615,7 @@ async function runMigrateLifecycle(config, options) {
|
|
|
9365
9615
|
const limit = options.limit ? parsePositiveInteger(options.limit, "lifecycle migration limit") : void 0;
|
|
9366
9616
|
const ov = await openVikingCliForMode(dryRun);
|
|
9367
9617
|
const candidates = await legacyLifecycleHandoffCandidates(config);
|
|
9368
|
-
const migrationPath = (0,
|
|
9618
|
+
const migrationPath = (0, import_node_path7.join)(config.agentContextHome, "lifecycle-memory-migration.txt");
|
|
9369
9619
|
let existingCount = 0;
|
|
9370
9620
|
let migratedCount = 0;
|
|
9371
9621
|
let skippedCount = 0;
|
|
@@ -9386,8 +9636,8 @@ async function runMigrateLifecycle(config, options) {
|
|
|
9386
9636
|
existingCount += 1;
|
|
9387
9637
|
console.log(`Archived copy already exists; cleaning up legacy source: ${candidate.sourceUri}`);
|
|
9388
9638
|
} else {
|
|
9389
|
-
await (0,
|
|
9390
|
-
await (0,
|
|
9639
|
+
await (0, import_promises6.writeFile)(migrationPath, migratedMemory, { encoding: "utf8", mode: 384 });
|
|
9640
|
+
await (0, import_promises6.chmod)(migrationPath, 384);
|
|
9391
9641
|
await ensureMemoryDirectory(ov, config, memoryDirectoryUri(config, candidate.metadata));
|
|
9392
9642
|
await writeDurableMemoryFile(ov, config, destinationUri, migrationPath, "create");
|
|
9393
9643
|
}
|
|
@@ -9403,7 +9653,7 @@ async function runMigrateLifecycle(config, options) {
|
|
|
9403
9653
|
}
|
|
9404
9654
|
} finally {
|
|
9405
9655
|
if (!dryRun) {
|
|
9406
|
-
await (0,
|
|
9656
|
+
await (0, import_promises6.rm)(migrationPath, { force: true });
|
|
9407
9657
|
}
|
|
9408
9658
|
}
|
|
9409
9659
|
console.log(
|
|
@@ -9423,6 +9673,19 @@ async function runRecall(config, options) {
|
|
|
9423
9673
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
9424
9674
|
const query = await enrichRecallQueryWithWorkspaceContext(options.query);
|
|
9425
9675
|
const projectQuery = await enrichRecallQueryWithWorkspaceProjectContext(options.query);
|
|
9676
|
+
let indexRepairMessages;
|
|
9677
|
+
try {
|
|
9678
|
+
const indexRepair = await repairStaleRecallIndex(config, ov, {
|
|
9679
|
+
dryRun: options.dryRun === true,
|
|
9680
|
+
query: projectQuery
|
|
9681
|
+
});
|
|
9682
|
+
indexRepairMessages = formatRecallIndexRepairMessages(indexRepair, { dryRun: options.dryRun === true });
|
|
9683
|
+
} catch (err) {
|
|
9684
|
+
indexRepairMessages = [`Auto-index repair warning: ${err instanceof Error ? err.message : String(err)}`];
|
|
9685
|
+
}
|
|
9686
|
+
for (const message of indexRepairMessages) {
|
|
9687
|
+
console.log(message);
|
|
9688
|
+
}
|
|
9426
9689
|
const inferredUri = options.uri ?? (options.inferScope === false ? void 0 : await inferRecallUri(config, projectQuery));
|
|
9427
9690
|
const args = ["search", query];
|
|
9428
9691
|
if (inferredUri) {
|
|
@@ -9433,9 +9696,9 @@ async function runRecall(config, options) {
|
|
|
9433
9696
|
args.push("--node-limit", String(parsePositiveInteger(options.nodeLimit, "node limit")));
|
|
9434
9697
|
}
|
|
9435
9698
|
const recallOutputs = [];
|
|
9436
|
-
const
|
|
9437
|
-
if (
|
|
9438
|
-
recallOutputs.push(
|
|
9699
|
+
const baseOutput = await runRecallSearch(config, ov, args, { dryRun: options.dryRun === true });
|
|
9700
|
+
if (baseOutput) {
|
|
9701
|
+
recallOutputs.push(baseOutput);
|
|
9439
9702
|
}
|
|
9440
9703
|
const seededOutput = await augmentRecallWithSeededResources(
|
|
9441
9704
|
config,
|
|
@@ -9474,8 +9737,20 @@ async function augmentRecallWithSeededResources(config, ov, options, inferredUri
|
|
|
9474
9737
|
}
|
|
9475
9738
|
console.log(`
|
|
9476
9739
|
Also searching seeded resources: ${projectResourceUri}`);
|
|
9477
|
-
|
|
9478
|
-
|
|
9740
|
+
return runRecallSearch(config, ov, args, { dryRun: options.dryRun === true });
|
|
9741
|
+
}
|
|
9742
|
+
async function runRecallSearch(config, ov, args, options) {
|
|
9743
|
+
const fullArgs = withIdentity(config, args);
|
|
9744
|
+
console.log(`${options.dryRun ? "Would run" : "Running"}: ${formatShellCommand(ov, fullArgs)}`);
|
|
9745
|
+
if (options.dryRun) {
|
|
9746
|
+
return void 0;
|
|
9747
|
+
}
|
|
9748
|
+
const result = await runCommand(ov, fullArgs);
|
|
9749
|
+
const output2 = filterStaleRecallSummaryRows([result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n"));
|
|
9750
|
+
if (output2) {
|
|
9751
|
+
console.log(output2);
|
|
9752
|
+
}
|
|
9753
|
+
return output2 || void 0;
|
|
9479
9754
|
}
|
|
9480
9755
|
async function runRead(config, uri, options) {
|
|
9481
9756
|
assertVikingUri(uri);
|
|
@@ -9541,15 +9816,15 @@ async function runCompact(config, options) {
|
|
|
9541
9816
|
return;
|
|
9542
9817
|
}
|
|
9543
9818
|
const ov = await openVikingCliForMode(false);
|
|
9544
|
-
const updatePath = (0,
|
|
9819
|
+
const updatePath = (0, import_node_path7.join)(config.agentContextHome, "compact-memory-update.txt");
|
|
9545
9820
|
try {
|
|
9546
9821
|
for (const action of plan.keepUpdates) {
|
|
9547
|
-
await (0,
|
|
9548
|
-
await (0,
|
|
9822
|
+
await (0, import_promises6.writeFile)(updatePath, action.content, { encoding: "utf8", mode: 384 });
|
|
9823
|
+
await (0, import_promises6.chmod)(updatePath, 384);
|
|
9549
9824
|
await writeDurableMemoryFile(ov, config, action.uri, updatePath, "replace");
|
|
9550
9825
|
}
|
|
9551
9826
|
} finally {
|
|
9552
|
-
await (0,
|
|
9827
|
+
await (0, import_promises6.rm)(updatePath, { force: true });
|
|
9553
9828
|
}
|
|
9554
9829
|
for (const action of plan.archives) {
|
|
9555
9830
|
await runArchive(config, action.uri, {
|
|
@@ -9605,7 +9880,7 @@ async function scopedCompactRecords(config, options) {
|
|
|
9605
9880
|
const uriDirectory = memoryUriDirectoryForCompact(config, kind, options.project);
|
|
9606
9881
|
let entries;
|
|
9607
9882
|
try {
|
|
9608
|
-
entries = await (0,
|
|
9883
|
+
entries = await (0, import_promises6.readdir)(directory, { withFileTypes: true });
|
|
9609
9884
|
} catch (_err) {
|
|
9610
9885
|
continue;
|
|
9611
9886
|
}
|
|
@@ -9613,7 +9888,7 @@ async function scopedCompactRecords(config, options) {
|
|
|
9613
9888
|
if (!entry.isFile() || entry.name.startsWith(".") || !entry.name.endsWith(".md")) {
|
|
9614
9889
|
continue;
|
|
9615
9890
|
}
|
|
9616
|
-
const content = await readTextIfExists((0,
|
|
9891
|
+
const content = await readTextIfExists((0, import_node_path7.join)(directory, entry.name));
|
|
9617
9892
|
if (!content) {
|
|
9618
9893
|
continue;
|
|
9619
9894
|
}
|
|
@@ -9651,11 +9926,11 @@ function localMemoryDirectoryForCompact(config, kind, project) {
|
|
|
9651
9926
|
const projectSegment = uriSegment(project);
|
|
9652
9927
|
switch (kind) {
|
|
9653
9928
|
case "durable":
|
|
9654
|
-
return (0,
|
|
9929
|
+
return (0, import_node_path7.join)(root, "durable", "projects", projectSegment);
|
|
9655
9930
|
case "handoff":
|
|
9656
|
-
return (0,
|
|
9931
|
+
return (0, import_node_path7.join)(root, "handoffs", "active", projectSegment);
|
|
9657
9932
|
case "incident":
|
|
9658
|
-
return (0,
|
|
9933
|
+
return (0, import_node_path7.join)(root, "incidents", "active", projectSegment);
|
|
9659
9934
|
}
|
|
9660
9935
|
}
|
|
9661
9936
|
function memoryUriDirectoryForCompact(config, kind, project) {
|
|
@@ -9675,11 +9950,11 @@ function localMemoryPathForUri(config, uri) {
|
|
|
9675
9950
|
if (!uri.startsWith(prefix) || uri.includes("/shared/")) {
|
|
9676
9951
|
return void 0;
|
|
9677
9952
|
}
|
|
9678
|
-
const
|
|
9679
|
-
if (
|
|
9953
|
+
const relative5 = uri.slice(prefix.length);
|
|
9954
|
+
if (relative5.includes("..") || relative5.startsWith("/")) {
|
|
9680
9955
|
return void 0;
|
|
9681
9956
|
}
|
|
9682
|
-
return (0,
|
|
9957
|
+
return (0, import_node_path7.join)(localUserMemoriesRoot(config), ...relative5.split("/"));
|
|
9683
9958
|
}
|
|
9684
9959
|
async function runList(config, uri, options) {
|
|
9685
9960
|
assertVikingUri(uri);
|
|
@@ -9773,7 +10048,7 @@ async function runForget(config, uri, options) {
|
|
|
9773
10048
|
}
|
|
9774
10049
|
async function runExportPack(config, options) {
|
|
9775
10050
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
9776
|
-
const defaultPath = (0,
|
|
10051
|
+
const defaultPath = (0, import_node_path7.join)(config.agentContextHome, `threadnote-${safeTimestamp()}.ovpack`);
|
|
9777
10052
|
const outputPath = expandPath(options.path ?? defaultPath);
|
|
9778
10053
|
await maybeRun(options.dryRun === true, ov, withIdentity(config, ["export", options.uri ?? "viking://", outputPath]));
|
|
9779
10054
|
}
|
|
@@ -9789,12 +10064,25 @@ async function runImportPack(config, options) {
|
|
|
9789
10064
|
);
|
|
9790
10065
|
}
|
|
9791
10066
|
async function inferRecallUri(config, query) {
|
|
9792
|
-
if (
|
|
10067
|
+
if (!hasAgentSkillCatalogIntent(query)) {
|
|
9793
10068
|
return void 0;
|
|
9794
10069
|
}
|
|
9795
10070
|
const project = await inferProjectFromQuery(config.manifestPath, query);
|
|
9796
10071
|
return project ? `viking://resources/agent-skills/repo-local-${uriSegment(project.name)}` : "viking://resources/agent-skills";
|
|
9797
10072
|
}
|
|
10073
|
+
function hasAgentSkillCatalogIntent(query) {
|
|
10074
|
+
const normalized = query.toLowerCase();
|
|
10075
|
+
if (!/\bskills?\b/.test(normalized)) {
|
|
10076
|
+
return false;
|
|
10077
|
+
}
|
|
10078
|
+
if (/\bseed[- ]skills?\b/.test(normalized) || /\bskills?\s+seed(?:ing)?\b/.test(normalized)) {
|
|
10079
|
+
return false;
|
|
10080
|
+
}
|
|
10081
|
+
if (/^\s*skills?\s*$/.test(normalized)) {
|
|
10082
|
+
return true;
|
|
10083
|
+
}
|
|
10084
|
+
return /\b(find|list|show|search|recall|use|choose|select)\b.{0,48}\bskills?\b/.test(normalized) || /\bskills?\b.{0,48}\b(for|to|that|which|about)\b/.test(normalized);
|
|
10085
|
+
}
|
|
9798
10086
|
async function printExactMemoryMatches(config, ov, query, options) {
|
|
9799
10087
|
const terms = exactRecallTerms(query);
|
|
9800
10088
|
if (terms.length === 0) {
|
|
@@ -9833,7 +10121,7 @@ async function storeMemory(config, options) {
|
|
|
9833
10121
|
await storeSharedMemoryReplacement(config, ov, options, options.replaceUri);
|
|
9834
10122
|
return;
|
|
9835
10123
|
}
|
|
9836
|
-
const memoryPath = (0,
|
|
10124
|
+
const memoryPath = (0, import_node_path7.join)(config.agentContextHome, "last-memory.txt");
|
|
9837
10125
|
const candidateMetadata = { ...options.metadata, supersedes: options.replaceUri };
|
|
9838
10126
|
const candidateMemory = formatMemoryDocument2(options.title, candidateMetadata, options.bodyText);
|
|
9839
10127
|
const memoryUri = memoryUriFor(config, candidateMemory, candidateMetadata);
|
|
@@ -9865,8 +10153,8 @@ async function storeMemory(config, options) {
|
|
|
9865
10153
|
}
|
|
9866
10154
|
return;
|
|
9867
10155
|
}
|
|
9868
|
-
await (0,
|
|
9869
|
-
await (0,
|
|
10156
|
+
await (0, import_promises6.writeFile)(memoryPath, memory, { encoding: "utf8", mode: 384 });
|
|
10157
|
+
await (0, import_promises6.chmod)(memoryPath, 384);
|
|
9870
10158
|
await ensureMemoryDirectory(ov, config, memoryDirectoryUri(config, finalMetadata));
|
|
9871
10159
|
await writeDurableMemoryFile(ov, config, memoryUri, memoryPath, writeMode);
|
|
9872
10160
|
console.log(`Stored memory: ${memoryUri}`);
|
|
@@ -9925,7 +10213,7 @@ async function storeSharedMemoryReplacement(config, ov, options, targetUri) {
|
|
|
9925
10213
|
console.log(`Updated shared memory: ${targetUri}`);
|
|
9926
10214
|
}
|
|
9927
10215
|
async function writeDurableMemoryFile(ov, config, memoryUri, memoryPath, writeMode) {
|
|
9928
|
-
const content = await (0,
|
|
10216
|
+
const content = await (0, import_promises6.readFile)(memoryPath, "utf8");
|
|
9929
10217
|
await writeMemoryFile(config, ov, memoryUri, content, writeMode, false);
|
|
9930
10218
|
}
|
|
9931
10219
|
async function removeVikingResourceWithRetry(ov, config, uri) {
|
|
@@ -9982,10 +10270,10 @@ async function hasLegacyLifecycleHandoffCandidates(config) {
|
|
|
9982
10270
|
return (await legacyLifecycleHandoffCandidates(config, 1)).length > 0;
|
|
9983
10271
|
}
|
|
9984
10272
|
async function legacyLifecycleHandoffCandidates(config, limit) {
|
|
9985
|
-
const eventsRoot = (0,
|
|
10273
|
+
const eventsRoot = (0, import_node_path7.join)(localUserMemoriesRoot(config), "events");
|
|
9986
10274
|
let entries;
|
|
9987
10275
|
try {
|
|
9988
|
-
entries = await (0,
|
|
10276
|
+
entries = await (0, import_promises6.readdir)(eventsRoot, { withFileTypes: true });
|
|
9989
10277
|
} catch (_err) {
|
|
9990
10278
|
return [];
|
|
9991
10279
|
}
|
|
@@ -9994,7 +10282,7 @@ async function legacyLifecycleHandoffCandidates(config, limit) {
|
|
|
9994
10282
|
if (!entry.isFile() || entry.name.startsWith(".") || !entry.name.endsWith(".md")) {
|
|
9995
10283
|
continue;
|
|
9996
10284
|
}
|
|
9997
|
-
const sourcePath = (0,
|
|
10285
|
+
const sourcePath = (0, import_node_path7.join)(eventsRoot, entry.name);
|
|
9998
10286
|
const original = await readTextIfExists(sourcePath);
|
|
9999
10287
|
if (!original || !isClearLegacyHandoffMemory(original) || sensitiveMemoryReason(original)) {
|
|
10000
10288
|
continue;
|
|
@@ -10135,7 +10423,7 @@ function inferLegacyProject(memory) {
|
|
|
10135
10423
|
return "general";
|
|
10136
10424
|
}
|
|
10137
10425
|
const trimmed = explicit.trim().replace(/[`.,;]+$/g, "");
|
|
10138
|
-
return trimmed.includes("/") ? (0,
|
|
10426
|
+
return trimmed.includes("/") ? (0, import_node_path7.basename)(trimmed) : trimmed;
|
|
10139
10427
|
}
|
|
10140
10428
|
function parseOptionalMemoryKind2(value) {
|
|
10141
10429
|
if (!value) {
|
|
@@ -10175,14 +10463,14 @@ async function legacySourceAccounts(config, options) {
|
|
|
10175
10463
|
async function legacyMemoryCandidates(config, sourceAccounts) {
|
|
10176
10464
|
const candidates = [];
|
|
10177
10465
|
for (const sourceAccount of sourceAccounts) {
|
|
10178
|
-
const sessionRoot = (0,
|
|
10466
|
+
const sessionRoot = (0, import_node_path7.join)(localVikingDataRoot(config), sourceAccount, "session");
|
|
10179
10467
|
for (const sourceSession of await childDirectoryNames(sessionRoot)) {
|
|
10180
|
-
const historyRoot = (0,
|
|
10468
|
+
const historyRoot = (0, import_node_path7.join)(sessionRoot, sourceSession, "history");
|
|
10181
10469
|
for (const sourceArchive of await childDirectoryNames(historyRoot)) {
|
|
10182
10470
|
if (!sourceArchive.startsWith("archive_")) {
|
|
10183
10471
|
continue;
|
|
10184
10472
|
}
|
|
10185
|
-
const sourcePath = (0,
|
|
10473
|
+
const sourcePath = (0, import_node_path7.join)(historyRoot, sourceArchive, "messages.jsonl");
|
|
10186
10474
|
for (const text of await legacyMemoryTexts(sourcePath)) {
|
|
10187
10475
|
candidates.push({
|
|
10188
10476
|
comparableHash: sha256(comparableMemoryText(text)),
|
|
@@ -10245,12 +10533,12 @@ async function existingDurableMemoryHashes(config) {
|
|
|
10245
10533
|
async function collectDurableMemoryHashes(root, hashes) {
|
|
10246
10534
|
let entries;
|
|
10247
10535
|
try {
|
|
10248
|
-
entries = await (0,
|
|
10536
|
+
entries = await (0, import_promises6.readdir)(root, { withFileTypes: true });
|
|
10249
10537
|
} catch (_err) {
|
|
10250
10538
|
return;
|
|
10251
10539
|
}
|
|
10252
10540
|
for (const entry of entries) {
|
|
10253
|
-
const path = (0,
|
|
10541
|
+
const path = (0, import_node_path7.join)(root, entry.name);
|
|
10254
10542
|
if (entry.isDirectory()) {
|
|
10255
10543
|
await collectDurableMemoryHashes(path, hashes);
|
|
10256
10544
|
continue;
|
|
@@ -10267,12 +10555,12 @@ async function collectDurableMemoryHashes(root, hashes) {
|
|
|
10267
10555
|
}
|
|
10268
10556
|
}
|
|
10269
10557
|
function isDurableMemoryPath(path) {
|
|
10270
|
-
return path.split(
|
|
10558
|
+
return path.split(import_node_path7.sep).includes("memories");
|
|
10271
10559
|
}
|
|
10272
10560
|
async function childDirectoryNames(path) {
|
|
10273
10561
|
let entries;
|
|
10274
10562
|
try {
|
|
10275
|
-
entries = await (0,
|
|
10563
|
+
entries = await (0, import_promises6.readdir)(path, { withFileTypes: true });
|
|
10276
10564
|
} catch (_err) {
|
|
10277
10565
|
return [];
|
|
10278
10566
|
}
|
|
@@ -10280,11 +10568,11 @@ async function childDirectoryNames(path) {
|
|
|
10280
10568
|
}
|
|
10281
10569
|
async function readTextIfExists(path) {
|
|
10282
10570
|
try {
|
|
10283
|
-
const pathStat = await (0,
|
|
10571
|
+
const pathStat = await (0, import_promises6.stat)(path);
|
|
10284
10572
|
if (!pathStat.isFile()) {
|
|
10285
10573
|
return void 0;
|
|
10286
10574
|
}
|
|
10287
|
-
return await (0,
|
|
10575
|
+
return await (0, import_promises6.readFile)(path, "utf8");
|
|
10288
10576
|
} catch (_err) {
|
|
10289
10577
|
return void 0;
|
|
10290
10578
|
}
|
|
@@ -10311,10 +10599,10 @@ function legacySourceLabel(candidate) {
|
|
|
10311
10599
|
return `${candidate.sourceAccount}/${candidate.sourceSession}/${candidate.sourceArchive}`;
|
|
10312
10600
|
}
|
|
10313
10601
|
function localVikingDataRoot(config) {
|
|
10314
|
-
return (0,
|
|
10602
|
+
return (0, import_node_path7.join)(config.agentContextHome, "data", "viking");
|
|
10315
10603
|
}
|
|
10316
10604
|
function localUserMemoriesRoot(config) {
|
|
10317
|
-
return (0,
|
|
10605
|
+
return (0, import_node_path7.join)(localVikingDataRoot(config), config.account, "user", uriSegment(config.user), "memories");
|
|
10318
10606
|
}
|
|
10319
10607
|
function uniqueStrings(values) {
|
|
10320
10608
|
return [...new Set(values)].sort();
|
|
@@ -10330,7 +10618,7 @@ async function buildHandoff(options) {
|
|
|
10330
10618
|
const status = await gitValue(["status", "--short"], repoRoot) ?? "";
|
|
10331
10619
|
const diffStat = await gitValue(["diff", "--stat", "HEAD"], repoRoot) ?? "";
|
|
10332
10620
|
const touchedFiles = await gitTouchedFiles(repoRoot);
|
|
10333
|
-
const repoName = await resolveRepoName(repoRoot) ?? (0,
|
|
10621
|
+
const repoName = await resolveRepoName(repoRoot) ?? (0, import_node_path7.basename)(repoRoot);
|
|
10334
10622
|
const topicBranch = branch && branch !== "unknown" ? branch : "current";
|
|
10335
10623
|
const metadata = {
|
|
10336
10624
|
kind: "handoff",
|
|
@@ -10390,8 +10678,8 @@ function formatBlock(value, emptyValue) {
|
|
|
10390
10678
|
|
|
10391
10679
|
// src/update-check.ts
|
|
10392
10680
|
var import_node_child_process2 = require("node:child_process");
|
|
10393
|
-
var
|
|
10394
|
-
var
|
|
10681
|
+
var import_promises7 = require("node:fs/promises");
|
|
10682
|
+
var import_node_path8 = require("node:path");
|
|
10395
10683
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
10396
10684
|
var NPM_LATEST_URL = "https://registry.npmjs.org/threadnote/latest";
|
|
10397
10685
|
var FETCH_TIMEOUT_MS = 3e3;
|
|
@@ -10458,7 +10746,7 @@ function isCacheFresh(cache) {
|
|
|
10458
10746
|
}
|
|
10459
10747
|
async function readUpdateCache(cachePath) {
|
|
10460
10748
|
try {
|
|
10461
|
-
const raw = await (0,
|
|
10749
|
+
const raw = await (0, import_promises7.readFile)(cachePath, "utf8");
|
|
10462
10750
|
const parsed = JSON.parse(raw);
|
|
10463
10751
|
if (parsed.version !== 1 || typeof parsed.latestVersion !== "string" || typeof parsed.checkedAt !== "string") {
|
|
10464
10752
|
return void 0;
|
|
@@ -10470,8 +10758,8 @@ async function readUpdateCache(cachePath) {
|
|
|
10470
10758
|
}
|
|
10471
10759
|
async function writeUpdateCache(cachePath, contents) {
|
|
10472
10760
|
try {
|
|
10473
|
-
await (0,
|
|
10474
|
-
await (0,
|
|
10761
|
+
await (0, import_promises7.mkdir)((0, import_node_path8.dirname)(cachePath), { recursive: true });
|
|
10762
|
+
await (0, import_promises7.writeFile)(cachePath, `${JSON.stringify(contents)}
|
|
10475
10763
|
`, { encoding: "utf8", mode: 384 });
|
|
10476
10764
|
} catch {
|
|
10477
10765
|
}
|
|
@@ -10494,14 +10782,14 @@ async function fetchLatestVersion() {
|
|
|
10494
10782
|
|
|
10495
10783
|
// src/version.ts
|
|
10496
10784
|
var import_node_fs4 = require("node:fs");
|
|
10497
|
-
var
|
|
10785
|
+
var import_node_path9 = require("node:path");
|
|
10498
10786
|
var cachedVersion;
|
|
10499
10787
|
function getThreadnoteVersion() {
|
|
10500
10788
|
if (cachedVersion !== void 0) {
|
|
10501
10789
|
return cachedVersion;
|
|
10502
10790
|
}
|
|
10503
10791
|
try {
|
|
10504
|
-
const packageJsonPath = (0,
|
|
10792
|
+
const packageJsonPath = (0, import_node_path9.join)(__dirname, "..", "package.json");
|
|
10505
10793
|
const parsed = JSON.parse((0, import_node_fs4.readFileSync)(packageJsonPath, "utf8"));
|
|
10506
10794
|
cachedVersion = typeof parsed.version === "string" && parsed.version.length > 0 ? parsed.version : "unknown";
|
|
10507
10795
|
} catch {
|
|
@@ -10543,7 +10831,7 @@ async function runHooksInstall(config, agent, options) {
|
|
|
10543
10831
|
}
|
|
10544
10832
|
async function runClaudeHooksInstall(options) {
|
|
10545
10833
|
const path = expandPath(CLAUDE_SETTINGS_PATH);
|
|
10546
|
-
const existingRaw = await exists(path) ? await (0,
|
|
10834
|
+
const existingRaw = await exists(path) ? await (0, import_promises8.readFile)(path, "utf8") : "{}";
|
|
10547
10835
|
const parsed = parseJsonConfigObject(existingRaw) ?? {};
|
|
10548
10836
|
const next = options.remove ? withoutThreadnoteHooks(parsed) : withThreadnoteHooks(parsed);
|
|
10549
10837
|
const before = JSON.stringify(parsed);
|
|
@@ -10560,11 +10848,11 @@ async function runClaudeHooksInstall(options) {
|
|
|
10560
10848
|
console.log("\nRe-run with --apply to actually modify the file.");
|
|
10561
10849
|
return;
|
|
10562
10850
|
}
|
|
10563
|
-
await (0,
|
|
10851
|
+
await (0, import_promises8.mkdir)((0, import_node_path10.dirname)(path), { recursive: true });
|
|
10564
10852
|
const serialized = `${JSON.stringify(next, void 0, 2)}
|
|
10565
10853
|
`;
|
|
10566
|
-
await (0,
|
|
10567
|
-
await (0,
|
|
10854
|
+
await (0, import_promises8.writeFile)(path, serialized, { encoding: "utf8", mode: 384 });
|
|
10855
|
+
await (0, import_promises8.chmod)(path, 384);
|
|
10568
10856
|
console.log(`${options.remove ? "Removed" : "Installed"} threadnote-managed Claude hooks.`);
|
|
10569
10857
|
}
|
|
10570
10858
|
function withThreadnoteHooks(input2) {
|
|
@@ -10642,7 +10930,7 @@ async function hasManagedClaudeHooks() {
|
|
|
10642
10930
|
if (!await exists(path)) {
|
|
10643
10931
|
return false;
|
|
10644
10932
|
}
|
|
10645
|
-
const raw = await (0,
|
|
10933
|
+
const raw = await (0, import_promises8.readFile)(path, "utf8");
|
|
10646
10934
|
const parsed = parseJsonConfigObject(raw);
|
|
10647
10935
|
if (!parsed || !isJsonObject(parsed.hooks)) {
|
|
10648
10936
|
return false;
|
|
@@ -10704,7 +10992,7 @@ async function runSessionStartHook(config, options = {}) {
|
|
|
10704
10992
|
async function emitUpdateBannerIfOutdated(config) {
|
|
10705
10993
|
try {
|
|
10706
10994
|
const result = await checkForThreadnoteUpdate({
|
|
10707
|
-
cachePath: (0,
|
|
10995
|
+
cachePath: (0, import_node_path10.join)(config.agentContextHome, ".update-state.json"),
|
|
10708
10996
|
currentVersion: getThreadnoteVersion()
|
|
10709
10997
|
});
|
|
10710
10998
|
if (!result || !result.outdated) {
|
|
@@ -10729,13 +11017,13 @@ async function emitUpdateBannerIfOutdated(config) {
|
|
|
10729
11017
|
}
|
|
10730
11018
|
|
|
10731
11019
|
// src/seeding.ts
|
|
10732
|
-
var
|
|
10733
|
-
var
|
|
11020
|
+
var import_promises9 = require("node:fs/promises");
|
|
11021
|
+
var import_node_path11 = require("node:path");
|
|
10734
11022
|
async function runSeed(config, options) {
|
|
10735
11023
|
const manifest = await readSeedManifest(config.manifestPath);
|
|
10736
11024
|
const ignorePatterns = await loadIgnorePatterns();
|
|
10737
11025
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
10738
|
-
const statePath = (0,
|
|
11026
|
+
const statePath = (0, import_node_path11.join)(config.agentContextHome, SEED_STATE_FILE);
|
|
10739
11027
|
const state = options.force === true ? { files: {}, version: 1 } : await readSeedState(statePath);
|
|
10740
11028
|
const projects = filterProjects(manifest.projects, options.only);
|
|
10741
11029
|
let importedCount = 0;
|
|
@@ -10760,7 +11048,15 @@ async function runSeed(config, options) {
|
|
|
10760
11048
|
skippedCount += 1;
|
|
10761
11049
|
continue;
|
|
10762
11050
|
}
|
|
10763
|
-
const args = withIdentity(config, [
|
|
11051
|
+
const args = withIdentity(config, [
|
|
11052
|
+
"add-resource",
|
|
11053
|
+
importPath,
|
|
11054
|
+
"--to",
|
|
11055
|
+
candidate.destinationUri,
|
|
11056
|
+
"--reason",
|
|
11057
|
+
seedResourceReason(candidate),
|
|
11058
|
+
"--wait"
|
|
11059
|
+
]);
|
|
10764
11060
|
await maybeRun(options.dryRun === true, ov, args);
|
|
10765
11061
|
importedCount += 1;
|
|
10766
11062
|
if (fileStat && options.dryRun !== true) {
|
|
@@ -10790,7 +11086,7 @@ function filterProjects(projects, only) {
|
|
|
10790
11086
|
}
|
|
10791
11087
|
async function statSeedFile(path) {
|
|
10792
11088
|
try {
|
|
10793
|
-
const result = await (0,
|
|
11089
|
+
const result = await (0, import_promises9.stat)(path);
|
|
10794
11090
|
return { mtimeMs: result.mtimeMs, size: result.size };
|
|
10795
11091
|
} catch (_err) {
|
|
10796
11092
|
return void 0;
|
|
@@ -10798,7 +11094,7 @@ async function statSeedFile(path) {
|
|
|
10798
11094
|
}
|
|
10799
11095
|
async function readSeedState(path) {
|
|
10800
11096
|
try {
|
|
10801
|
-
const raw = await (0,
|
|
11097
|
+
const raw = await (0, import_promises9.readFile)(path, "utf8");
|
|
10802
11098
|
const parsed = JSON.parse(raw);
|
|
10803
11099
|
if (parsed.version !== 1 || !isJsonObject(parsed.files)) {
|
|
10804
11100
|
return { files: {}, version: 1 };
|
|
@@ -10815,13 +11111,13 @@ async function readSeedState(path) {
|
|
|
10815
11111
|
}
|
|
10816
11112
|
}
|
|
10817
11113
|
async function writeSeedState(path, state) {
|
|
10818
|
-
await ensureDirectory((0,
|
|
10819
|
-
await (0,
|
|
11114
|
+
await ensureDirectory((0, import_node_path11.dirname)(path), false);
|
|
11115
|
+
await (0, import_promises9.writeFile)(path, `${JSON.stringify(state, void 0, 2)}
|
|
10820
11116
|
`, { encoding: "utf8", mode: 384 });
|
|
10821
11117
|
}
|
|
10822
11118
|
async function runInitManifest(config, options) {
|
|
10823
11119
|
const manifestPath = expandPath(
|
|
10824
|
-
options.path ?? process.env.THREADNOTE_MANIFEST ?? (0,
|
|
11120
|
+
options.path ?? process.env.THREADNOTE_MANIFEST ?? (0, import_node_path11.join)(config.agentContextHome, USER_MANIFEST_NAME)
|
|
10825
11121
|
);
|
|
10826
11122
|
const repoInputs = options.repo && options.repo.length > 0 ? options.repo : [getInvocationCwd()];
|
|
10827
11123
|
const existingManifest = options.replace === true || !await exists(manifestPath) ? void 0 : await readSeedManifest(manifestPath);
|
|
@@ -10864,9 +11160,9 @@ async function runInitManifest(config, options) {
|
|
|
10864
11160
|
console.log(output2.trimEnd());
|
|
10865
11161
|
return;
|
|
10866
11162
|
}
|
|
10867
|
-
await ensureDirectory((0,
|
|
10868
|
-
await (0,
|
|
10869
|
-
await (0,
|
|
11163
|
+
await ensureDirectory((0, import_node_path11.dirname)(manifestPath), false);
|
|
11164
|
+
await (0, import_promises9.writeFile)(manifestPath, output2, { encoding: "utf8", mode: 384 });
|
|
11165
|
+
await (0, import_promises9.chmod)(manifestPath, 384);
|
|
10870
11166
|
console.log(`Wrote manifest: ${manifestPath}`);
|
|
10871
11167
|
console.log("Seed with:");
|
|
10872
11168
|
console.log(" threadnote seed --dry-run");
|
|
@@ -10874,17 +11170,33 @@ async function runInitManifest(config, options) {
|
|
|
10874
11170
|
}
|
|
10875
11171
|
async function runSeedSkills(config, options) {
|
|
10876
11172
|
const ov = await openVikingCliForMode(options.dryRun === true);
|
|
10877
|
-
const
|
|
11173
|
+
const catalogItems = await collectSkillCandidates(config);
|
|
10878
11174
|
const nativeMode = options.native === true;
|
|
10879
11175
|
console.log(
|
|
10880
11176
|
nativeMode ? "Skill seed mode: native OpenViking skills. This requires a working VLM config." : "Skill seed mode: resource catalog. Use --native only after configuring a working VLM provider."
|
|
10881
11177
|
);
|
|
10882
|
-
|
|
10883
|
-
|
|
10884
|
-
|
|
11178
|
+
let skippedCount = 0;
|
|
11179
|
+
for (const skill of catalogItems) {
|
|
11180
|
+
console.log(`${skill.kind === "command" ? "Command" : "Skill"} ${skill.source}: ${skill.filePath}`);
|
|
11181
|
+
if (nativeMode && skill.kind === "command") {
|
|
11182
|
+
skippedCount += 1;
|
|
11183
|
+
console.log(`SKIP command in native skill mode: ${skill.filePath}`);
|
|
11184
|
+
continue;
|
|
11185
|
+
}
|
|
11186
|
+
const args = nativeMode ? ["add-skill", skill.filePath, "--wait"] : [
|
|
11187
|
+
"add-resource",
|
|
11188
|
+
skill.filePath,
|
|
11189
|
+
"--to",
|
|
11190
|
+
skillResourceUri(skill),
|
|
11191
|
+
"--reason",
|
|
11192
|
+
skillResourceReason(skill),
|
|
11193
|
+
"--wait"
|
|
11194
|
+
];
|
|
10885
11195
|
await maybeRun(options.dryRun === true, ov, withIdentity(config, args));
|
|
10886
11196
|
}
|
|
10887
|
-
console.log(
|
|
11197
|
+
console.log(
|
|
11198
|
+
`Skill seed complete: ${catalogItems.length - skippedCount} unique catalog item(s)${skippedCount > 0 ? `, ${skippedCount} skipped` : ""}.`
|
|
11199
|
+
);
|
|
10888
11200
|
}
|
|
10889
11201
|
async function resolveRepoRoot(repoInput) {
|
|
10890
11202
|
const inputPath = expandPath(repoInput);
|
|
@@ -10896,13 +11208,13 @@ async function resolveRepoRoot(repoInput) {
|
|
|
10896
11208
|
async function projectIdentity(path) {
|
|
10897
11209
|
const expanded = expandPath(path);
|
|
10898
11210
|
try {
|
|
10899
|
-
return await (0,
|
|
11211
|
+
return await (0, import_promises9.realpath)(expanded);
|
|
10900
11212
|
} catch (_err) {
|
|
10901
11213
|
return expanded;
|
|
10902
11214
|
}
|
|
10903
11215
|
}
|
|
10904
11216
|
function projectManifestForRepo(repoRoot, existingProjects) {
|
|
10905
|
-
const baseName = uriSegment((0,
|
|
11217
|
+
const baseName = uriSegment((0, import_node_path11.basename)(repoRoot));
|
|
10906
11218
|
const usedNames = new Set(existingProjects.map((project) => project.name));
|
|
10907
11219
|
const usedUris = new Set(existingProjects.map((project) => project.uri));
|
|
10908
11220
|
let name = baseName;
|
|
@@ -10924,7 +11236,7 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
|
|
|
10924
11236
|
for (const pattern of project.seed) {
|
|
10925
11237
|
const files = await resolveProjectPattern(projectRoot, pattern);
|
|
10926
11238
|
for (const filePath of files) {
|
|
10927
|
-
const relativePath = toPosixPath((0,
|
|
11239
|
+
const relativePath = toPosixPath((0, import_node_path11.relative)(projectRoot, filePath));
|
|
10928
11240
|
if (seen.has(relativePath) || matchesIgnore(relativePath, ignorePatterns)) {
|
|
10929
11241
|
continue;
|
|
10930
11242
|
}
|
|
@@ -10942,20 +11254,20 @@ async function collectSeedCandidates(project, projectRoot, ignorePatterns) {
|
|
|
10942
11254
|
async function resolveProjectPattern(projectRoot, pattern) {
|
|
10943
11255
|
const normalizedPattern = toPosixPath(pattern);
|
|
10944
11256
|
if (!hasGlob(normalizedPattern)) {
|
|
10945
|
-
const filePath = (0,
|
|
11257
|
+
const filePath = (0, import_node_path11.join)(projectRoot, normalizedPattern);
|
|
10946
11258
|
return await isFile(filePath) ? [filePath] : [];
|
|
10947
11259
|
}
|
|
10948
11260
|
const globBase = getGlobBase(normalizedPattern);
|
|
10949
|
-
const basePath = (0,
|
|
11261
|
+
const basePath = (0, import_node_path11.join)(projectRoot, globBase);
|
|
10950
11262
|
if (!await exists(basePath)) {
|
|
10951
11263
|
return [];
|
|
10952
11264
|
}
|
|
10953
11265
|
const regex = globToRegExp(normalizedPattern);
|
|
10954
11266
|
const files = await walkFiles(basePath);
|
|
10955
|
-
return files.filter((filePath) => regex.test(toPosixPath((0,
|
|
11267
|
+
return files.filter((filePath) => regex.test(toPosixPath((0, import_node_path11.relative)(projectRoot, filePath))));
|
|
10956
11268
|
}
|
|
10957
11269
|
async function prepareSeedFile(config, candidate, dryRun) {
|
|
10958
|
-
const content = await (0,
|
|
11270
|
+
const content = await (0, import_promises9.readFile)(candidate.filePath, "utf8");
|
|
10959
11271
|
const redactedContent = shouldRedactPath(candidate.relativePath) ? redactContent(candidate.relativePath, content) : content;
|
|
10960
11272
|
const secretMatches = detectSecretMatches(redactedContent);
|
|
10961
11273
|
if (secretMatches.length > 0) {
|
|
@@ -10967,39 +11279,54 @@ async function prepareSeedFile(config, candidate, dryRun) {
|
|
|
10967
11279
|
if (redactedContent === content) {
|
|
10968
11280
|
return candidate.filePath;
|
|
10969
11281
|
}
|
|
10970
|
-
const redactedPath = (0,
|
|
11282
|
+
const redactedPath = (0, import_node_path11.join)(config.agentContextHome, "redacted", candidate.projectName, candidate.relativePath);
|
|
10971
11283
|
if (dryRun) {
|
|
10972
11284
|
console.log(`Would write redacted copy: ${redactedPath}`);
|
|
10973
11285
|
return redactedPath;
|
|
10974
11286
|
}
|
|
10975
|
-
await ensureDirectory((0,
|
|
10976
|
-
await (0,
|
|
10977
|
-
await (0,
|
|
11287
|
+
await ensureDirectory((0, import_node_path11.dirname)(redactedPath), false);
|
|
11288
|
+
await (0, import_promises9.writeFile)(redactedPath, redactedContent, { encoding: "utf8", mode: 384 });
|
|
11289
|
+
await (0, import_promises9.chmod)(redactedPath, 384);
|
|
10978
11290
|
return redactedPath;
|
|
10979
11291
|
}
|
|
11292
|
+
function seedResourceReason(candidate) {
|
|
11293
|
+
return `Project guidance for ${candidate.projectName}: ${candidate.relativePath}`;
|
|
11294
|
+
}
|
|
11295
|
+
function skillResourceReason(skill) {
|
|
11296
|
+
return `${skill.kind === "command" ? "Agent command" : "Agent skill"} catalog item from ${skill.source}: ${(0, import_node_path11.basename)(
|
|
11297
|
+
skill.filePath
|
|
11298
|
+
)}`;
|
|
11299
|
+
}
|
|
10980
11300
|
async function collectSkillCandidates(config) {
|
|
10981
11301
|
const sources = [
|
|
10982
|
-
{ pattern: "~/.codex/skills/**/SKILL.md", source: "codex-global" },
|
|
10983
|
-
{ pattern: "~/.codex/plugins/cache/**/skills/**/SKILL.md", source: "codex-plugin-cache" },
|
|
10984
|
-
{ pattern: "~/.claude/skills/**/SKILL.md", source: "claude-global" }
|
|
11302
|
+
{ kind: "skill", pattern: "~/.codex/skills/**/SKILL.md", source: "codex-global" },
|
|
11303
|
+
{ kind: "skill", pattern: "~/.codex/plugins/cache/**/skills/**/SKILL.md", source: "codex-plugin-cache" },
|
|
11304
|
+
{ kind: "skill", pattern: "~/.claude/skills/**/SKILL.md", source: "claude-global" },
|
|
11305
|
+
{ kind: "command", pattern: "~/.claude/commands/**/*.md", source: "claude-commands-global" }
|
|
10985
11306
|
];
|
|
10986
11307
|
try {
|
|
10987
11308
|
const manifest = await readSeedManifest(config.manifestPath);
|
|
10988
11309
|
for (const project of manifest.projects) {
|
|
10989
11310
|
sources.push({
|
|
11311
|
+
kind: "skill",
|
|
10990
11312
|
pattern: `${project.path}/.claude/skills/**/SKILL.md`,
|
|
10991
11313
|
source: `repo-local:${project.name}`
|
|
10992
11314
|
});
|
|
11315
|
+
sources.push({
|
|
11316
|
+
kind: "command",
|
|
11317
|
+
pattern: `${project.path}/.claude/commands/**/*.md`,
|
|
11318
|
+
source: `repo-local:${project.name}:claude-commands`
|
|
11319
|
+
});
|
|
10993
11320
|
}
|
|
10994
11321
|
} catch (err) {
|
|
10995
|
-
console.log(`WARN cannot read manifest for repo-local skill discovery: ${errorMessage(err)}`);
|
|
11322
|
+
console.log(`WARN cannot read manifest for repo-local skill/command discovery: ${errorMessage(err)}`);
|
|
10996
11323
|
}
|
|
10997
11324
|
const seenHashes = /* @__PURE__ */ new Set();
|
|
10998
11325
|
const skills = [];
|
|
10999
11326
|
for (const source of sources) {
|
|
11000
11327
|
const files = await resolveAbsolutePattern(expandPath(source.pattern));
|
|
11001
11328
|
for (const filePath of files) {
|
|
11002
|
-
const content = await (0,
|
|
11329
|
+
const content = await (0, import_promises9.readFile)(filePath, "utf8");
|
|
11003
11330
|
const matches = detectSecretMatches(content);
|
|
11004
11331
|
if (matches.length > 0) {
|
|
11005
11332
|
console.log(`SKIP skill with possible secret: ${filePath}`);
|
|
@@ -11010,7 +11337,7 @@ async function collectSkillCandidates(config) {
|
|
|
11010
11337
|
continue;
|
|
11011
11338
|
}
|
|
11012
11339
|
seenHashes.add(hash);
|
|
11013
|
-
skills.push({ filePath, hash, source: source.source });
|
|
11340
|
+
skills.push({ filePath, hash, kind: source.kind, source: source.source });
|
|
11014
11341
|
}
|
|
11015
11342
|
}
|
|
11016
11343
|
return skills;
|
|
@@ -11030,10 +11357,19 @@ async function resolveAbsolutePattern(pattern) {
|
|
|
11030
11357
|
return files.filter((filePath) => regex.test(toPosixPath(filePath)));
|
|
11031
11358
|
}
|
|
11032
11359
|
function skillResourceUri(skill) {
|
|
11033
|
-
return `viking://resources/agent-skills/${uriSegment(skill.source)}/${
|
|
11360
|
+
return `viking://resources/agent-skills/${uriSegment(skill.source)}/${skillResourceName(skill)}-${skill.hash.slice(0, 12)}.md`;
|
|
11361
|
+
}
|
|
11362
|
+
function skillResourceName(skill) {
|
|
11363
|
+
const fileName = (0, import_node_path11.basename)(skill.filePath);
|
|
11364
|
+
if (fileName.toLowerCase() === "skill.md") {
|
|
11365
|
+
return uriSegment((0, import_node_path11.basename)((0, import_node_path11.dirname)(skill.filePath)));
|
|
11366
|
+
}
|
|
11367
|
+
const extensionIndex = fileName.lastIndexOf(".");
|
|
11368
|
+
const stem = extensionIndex > 0 ? fileName.slice(0, extensionIndex) : fileName;
|
|
11369
|
+
return uriSegment(stem);
|
|
11034
11370
|
}
|
|
11035
11371
|
async function loadIgnorePatterns() {
|
|
11036
|
-
const raw = await (0,
|
|
11372
|
+
const raw = await (0, import_promises9.readFile)((0, import_node_path11.join)(toolRoot(), ".threadnoteignore"), "utf8");
|
|
11037
11373
|
return raw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
11038
11374
|
}
|
|
11039
11375
|
function matchesIgnore(relativePath, patterns) {
|
|
@@ -11106,18 +11442,18 @@ function detectSecretMatches(content) {
|
|
|
11106
11442
|
// src/lifecycle.ts
|
|
11107
11443
|
var import_node_child_process3 = require("node:child_process");
|
|
11108
11444
|
var import_node_fs6 = require("node:fs");
|
|
11109
|
-
var
|
|
11445
|
+
var import_promises12 = require("node:fs/promises");
|
|
11110
11446
|
var import_node_os6 = require("node:os");
|
|
11111
|
-
var
|
|
11447
|
+
var import_node_path13 = require("node:path");
|
|
11112
11448
|
var import_node_process3 = require("node:process");
|
|
11113
|
-
var
|
|
11449
|
+
var import_promises13 = require("node:readline/promises");
|
|
11114
11450
|
|
|
11115
11451
|
// src/update.ts
|
|
11116
11452
|
var import_node_fs5 = require("node:fs");
|
|
11117
|
-
var
|
|
11453
|
+
var import_promises10 = require("node:fs/promises");
|
|
11118
11454
|
var import_node_os5 = require("node:os");
|
|
11119
|
-
var
|
|
11120
|
-
var
|
|
11455
|
+
var import_node_path12 = require("node:path");
|
|
11456
|
+
var import_promises11 = require("node:readline/promises");
|
|
11121
11457
|
var import_node_process2 = require("node:process");
|
|
11122
11458
|
|
|
11123
11459
|
// src/release_notes.ts
|
|
@@ -11388,7 +11724,7 @@ async function ensurePinnedOpenVikingInstalled(config, options) {
|
|
|
11388
11724
|
}
|
|
11389
11725
|
console.log("");
|
|
11390
11726
|
console.log(`Upgrading OpenViking ${installedVersion} -> ${pinned} (pinned by Threadnote).`);
|
|
11391
|
-
console.log("Picks up upstream
|
|
11727
|
+
console.log("Picks up upstream CLI, resource-ingestion, and index reliability fixes.");
|
|
11392
11728
|
const wasRunning = await isOpenVikingHealthy(config);
|
|
11393
11729
|
const usingLaunchd = await isLaunchAgentInstalled();
|
|
11394
11730
|
const threadnoteCommand = currentThreadnoteCommand() ?? await findExecutable([NPM_PACKAGE_NAME]) ?? NPM_PACKAGE_NAME;
|
|
@@ -11449,14 +11785,14 @@ async function waitForOpenVikingHealthy(config, timeoutMs) {
|
|
|
11449
11785
|
return isOpenVikingHealthy(config);
|
|
11450
11786
|
}
|
|
11451
11787
|
function launchAgentPlistPath() {
|
|
11452
|
-
return (0,
|
|
11788
|
+
return (0, import_node_path12.join)((0, import_node_os5.homedir)(), "Library", "LaunchAgents", "io.threadnote.openviking.plist");
|
|
11453
11789
|
}
|
|
11454
11790
|
async function isLaunchAgentInstalled() {
|
|
11455
11791
|
if (process.platform !== "darwin") {
|
|
11456
11792
|
return false;
|
|
11457
11793
|
}
|
|
11458
11794
|
try {
|
|
11459
|
-
await (0,
|
|
11795
|
+
await (0, import_promises10.access)(launchAgentPlistPath(), import_node_fs5.constants.F_OK);
|
|
11460
11796
|
return true;
|
|
11461
11797
|
} catch (_err) {
|
|
11462
11798
|
return false;
|
|
@@ -11485,7 +11821,7 @@ async function getUpdateInfo(config, options) {
|
|
|
11485
11821
|
};
|
|
11486
11822
|
}
|
|
11487
11823
|
async function currentPackageVersion() {
|
|
11488
|
-
const rawPackage = await (0,
|
|
11824
|
+
const rawPackage = await (0, import_promises10.readFile)((0, import_node_path12.join)(toolRoot(), "package.json"), "utf8");
|
|
11489
11825
|
const parsed = JSON.parse(rawPackage);
|
|
11490
11826
|
if (!isJsonObject(parsed) || typeof parsed.version !== "string") {
|
|
11491
11827
|
throw new Error("Could not read current threadnote package version.");
|
|
@@ -11535,11 +11871,11 @@ async function readFreshCache(config, registry) {
|
|
|
11535
11871
|
}
|
|
11536
11872
|
async function writeUpdateCache2(config, cache) {
|
|
11537
11873
|
await ensureDirectory(config.agentContextHome, false);
|
|
11538
|
-
await (0,
|
|
11874
|
+
await (0, import_promises10.writeFile)(updateCachePath(config), `${JSON.stringify(cache, null, 2)}
|
|
11539
11875
|
`, { encoding: "utf8", mode: 384 });
|
|
11540
11876
|
}
|
|
11541
11877
|
function updateCachePath(config) {
|
|
11542
|
-
return (0,
|
|
11878
|
+
return (0, import_node_path12.join)(config.agentContextHome, "update-check.json");
|
|
11543
11879
|
}
|
|
11544
11880
|
async function runApplicablePostUpdateMigrations(config, options) {
|
|
11545
11881
|
const state = await readPostUpdateState(config);
|
|
@@ -11603,7 +11939,7 @@ async function applicablePostUpdateMigrations(config, options) {
|
|
|
11603
11939
|
return applicable;
|
|
11604
11940
|
}
|
|
11605
11941
|
async function readPostUpdateMigrations() {
|
|
11606
|
-
const raw = await readFileIfExists((0,
|
|
11942
|
+
const raw = await readFileIfExists((0, import_node_path12.join)(toolRoot(), "config", POST_UPDATE_MIGRATIONS_FILE));
|
|
11607
11943
|
if (!raw) {
|
|
11608
11944
|
return [];
|
|
11609
11945
|
}
|
|
@@ -11642,7 +11978,7 @@ function printPostUpdateMigration(migration) {
|
|
|
11642
11978
|
}
|
|
11643
11979
|
}
|
|
11644
11980
|
async function confirmPostUpdateMigration(prompt, defaultYes = false) {
|
|
11645
|
-
const readline = (0,
|
|
11981
|
+
const readline = (0, import_promises11.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
|
|
11646
11982
|
try {
|
|
11647
11983
|
const answer = (await readline.question(prompt)).trim().toLowerCase();
|
|
11648
11984
|
if (answer === "") {
|
|
@@ -11677,11 +12013,11 @@ async function readPostUpdateState(config) {
|
|
|
11677
12013
|
}
|
|
11678
12014
|
async function writePostUpdateState(config, state) {
|
|
11679
12015
|
await ensureDirectory(config.agentContextHome, false);
|
|
11680
|
-
await (0,
|
|
12016
|
+
await (0, import_promises10.writeFile)(postUpdateStatePath(config), `${JSON.stringify(state, null, 2)}
|
|
11681
12017
|
`, { encoding: "utf8", mode: 384 });
|
|
11682
12018
|
}
|
|
11683
12019
|
function postUpdateStatePath(config) {
|
|
11684
|
-
return (0,
|
|
12020
|
+
return (0, import_node_path12.join)(config.agentContextHome, POST_UPDATE_STATE_FILE);
|
|
11685
12021
|
}
|
|
11686
12022
|
async function resolveUpdateRuntime(runtime) {
|
|
11687
12023
|
if (runtime !== "auto") {
|
|
@@ -11711,14 +12047,14 @@ async function runtimeThreadnoteBin(runtime) {
|
|
|
11711
12047
|
if (runtime === "npm") {
|
|
11712
12048
|
const result = await runCommand("npm", ["prefix", "--global"], { allowFailure: true });
|
|
11713
12049
|
const prefix = result.stdout.trim();
|
|
11714
|
-
return prefix ? (0,
|
|
12050
|
+
return prefix ? (0, import_node_path12.join)(prefix, "bin", NPM_PACKAGE_NAME) : void 0;
|
|
11715
12051
|
}
|
|
11716
12052
|
if (runtime === "bun") {
|
|
11717
12053
|
const result = await runCommand("bun", ["pm", "bin", "-g"], { allowFailure: true });
|
|
11718
12054
|
const binDir = result.stdout.trim();
|
|
11719
|
-
return binDir ? (0,
|
|
12055
|
+
return binDir ? (0, import_node_path12.join)(binDir, NPM_PACKAGE_NAME) : void 0;
|
|
11720
12056
|
}
|
|
11721
|
-
return (0,
|
|
12057
|
+
return (0, import_node_path12.join)(process.env.DENO_INSTALL ?? (0, import_node_path12.join)((0, import_node_os5.homedir)(), ".deno"), "bin", NPM_PACKAGE_NAME);
|
|
11722
12058
|
}
|
|
11723
12059
|
function updatePackageCommand(runtime, registry) {
|
|
11724
12060
|
if (runtime === "npm") {
|
|
@@ -11786,9 +12122,10 @@ async function collectDoctorChecks(config, options = {}) {
|
|
|
11786
12122
|
checks.push(await commandShimCheck());
|
|
11787
12123
|
checks.push(...await userAgentInstructionsChecks());
|
|
11788
12124
|
checks.push(await manifestCheck(config.manifestPath));
|
|
11789
|
-
checks.push(await
|
|
11790
|
-
checks.push(await fileCheck((0,
|
|
11791
|
-
checks.push(await fileCheck((0,
|
|
12125
|
+
checks.push(await recallIndexFreshnessCheck(config));
|
|
12126
|
+
checks.push(await fileCheck((0, import_node_path13.join)(toolRoot(), ".threadnoteignore"), "ignore file"));
|
|
12127
|
+
checks.push(await fileCheck((0, import_node_path13.join)(toolRoot(), "config", "ov.conf.template.json"), "server config template"));
|
|
12128
|
+
checks.push(await fileCheck((0, import_node_path13.join)(toolRoot(), "config", "ovcli.conf.template.json"), "cli config template"));
|
|
11792
12129
|
checks.push(await healthCheck(config));
|
|
11793
12130
|
return checks;
|
|
11794
12131
|
}
|
|
@@ -11796,9 +12133,9 @@ async function runInstall(config, options) {
|
|
|
11796
12133
|
const repairInvalidConfigs = options.repairInvalidConfigs === true;
|
|
11797
12134
|
const dryRun = options.dryRun === true;
|
|
11798
12135
|
await ensureDirectory(config.agentContextHome, dryRun);
|
|
11799
|
-
await ensureDirectory((0,
|
|
11800
|
-
await ensureDirectory((0,
|
|
11801
|
-
await ensureDirectory((0,
|
|
12136
|
+
await ensureDirectory((0, import_node_path13.join)(config.agentContextHome, "logs"), dryRun);
|
|
12137
|
+
await ensureDirectory((0, import_node_path13.join)(config.agentContextHome, "redacted"), dryRun);
|
|
12138
|
+
await ensureDirectory((0, import_node_path13.join)(config.agentContextHome, "mcp"), dryRun);
|
|
11802
12139
|
await installCommandShim(dryRun);
|
|
11803
12140
|
await installUserAgentInstructions(dryRun);
|
|
11804
12141
|
const serverPath = await findOpenVikingServer();
|
|
@@ -11835,18 +12172,19 @@ async function runInstall(config, options) {
|
|
|
11835
12172
|
}
|
|
11836
12173
|
await writeTemplateIfMissing({
|
|
11837
12174
|
config,
|
|
11838
|
-
destinationPath: (0,
|
|
12175
|
+
destinationPath: (0, import_node_path13.join)(config.agentContextHome, "ov.conf"),
|
|
11839
12176
|
dryRun,
|
|
11840
12177
|
shouldRepair: (content) => shouldRepairOpenVikingConfig(content, config) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
11841
|
-
templatePath: (0,
|
|
12178
|
+
templatePath: (0, import_node_path13.join)(toolRoot(), "config", "ov.conf.template.json")
|
|
11842
12179
|
});
|
|
11843
12180
|
await writeTemplateIfMissing({
|
|
11844
12181
|
config,
|
|
11845
|
-
destinationPath: (0,
|
|
12182
|
+
destinationPath: (0, import_node_path13.join)(config.agentContextHome, "ovcli.conf"),
|
|
11846
12183
|
dryRun,
|
|
11847
12184
|
shouldRepair: (content) => shouldRepairLegacyOvCliConfig(content) || repairInvalidConfigs && parseJsonConfigObject(content) === void 0,
|
|
11848
|
-
templatePath: (0,
|
|
12185
|
+
templatePath: (0, import_node_path13.join)(toolRoot(), "config", "ovcli.conf.template.json")
|
|
11849
12186
|
});
|
|
12187
|
+
await configureOpenVikingCliLanguage(config, dryRun);
|
|
11850
12188
|
if (options.start !== false) {
|
|
11851
12189
|
const healthy = await repairServerHealth(config, dryRun);
|
|
11852
12190
|
if (!healthy && !dryRun) {
|
|
@@ -11873,6 +12211,7 @@ async function runRepair(config, options) {
|
|
|
11873
12211
|
} else {
|
|
11874
12212
|
console.log("Skipping server health repair because --no-start was provided.");
|
|
11875
12213
|
}
|
|
12214
|
+
await repairRecallIndex(config, dryRun);
|
|
11876
12215
|
const mcpClients = await resolveMcpClients(options.mcp ?? "available", "repair");
|
|
11877
12216
|
if (mcpClients.length === 0) {
|
|
11878
12217
|
console.log("Skipping MCP config repair.");
|
|
@@ -11899,7 +12238,7 @@ async function runUninstall(config, options) {
|
|
|
11899
12238
|
}
|
|
11900
12239
|
console.log("Uninstalling local Threadnote setup.");
|
|
11901
12240
|
await runStop(config, { dryRun });
|
|
11902
|
-
await removePathIfExists((0,
|
|
12241
|
+
await removePathIfExists((0, import_node_path13.join)(config.agentContextHome, "openviking-server.pid"), "pid file", dryRun);
|
|
11903
12242
|
await removeLaunchAgent(dryRun);
|
|
11904
12243
|
await removeMcpConfigs(options.mcp ?? "available", dryRun);
|
|
11905
12244
|
await removeMcpSnippets(config, dryRun);
|
|
@@ -11956,25 +12295,73 @@ async function repairManifest(config, dryRun) {
|
|
|
11956
12295
|
console.log(output2.trimEnd());
|
|
11957
12296
|
return;
|
|
11958
12297
|
}
|
|
11959
|
-
await ensureDirectory((0,
|
|
12298
|
+
await ensureDirectory((0, import_node_path13.dirname)(config.manifestPath), false);
|
|
11960
12299
|
const currentContent = await readFileIfExists(config.manifestPath);
|
|
11961
12300
|
if (currentContent !== void 0) {
|
|
11962
12301
|
const backupPath = `${config.manifestPath}.legacy-${safeTimestamp()}`;
|
|
11963
|
-
await (0,
|
|
11964
|
-
await (0,
|
|
12302
|
+
await (0, import_promises12.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
|
|
12303
|
+
await (0, import_promises12.chmod)(backupPath, 384);
|
|
11965
12304
|
console.log(`Backup: ${backupPath}`);
|
|
11966
12305
|
}
|
|
11967
|
-
await (0,
|
|
11968
|
-
await (0,
|
|
12306
|
+
await (0, import_promises12.writeFile)(config.manifestPath, output2, { encoding: "utf8", mode: 384 });
|
|
12307
|
+
await (0, import_promises12.chmod)(config.manifestPath, 384);
|
|
11969
12308
|
console.log(`Wrote replacement manifest: ${config.manifestPath}`);
|
|
11970
12309
|
}
|
|
12310
|
+
async function repairRecallIndex(config, dryRun) {
|
|
12311
|
+
console.log("\nRepairing recall index freshness.");
|
|
12312
|
+
const ov = dryRun ? await findExecutable(["ov", "openviking"]) ?? "ov" : await findExecutable(["ov", "openviking"]);
|
|
12313
|
+
if (!ov) {
|
|
12314
|
+
console.log("Skipping recall index repair: neither ov nor openviking was found in PATH.");
|
|
12315
|
+
return;
|
|
12316
|
+
}
|
|
12317
|
+
try {
|
|
12318
|
+
const result = await repairStaleRecallIndex(config, ov, {
|
|
12319
|
+
collapseToRoots: true,
|
|
12320
|
+
dryRun,
|
|
12321
|
+
ignoreBackoff: true,
|
|
12322
|
+
includeAgentSkills: true,
|
|
12323
|
+
includeManifestResources: true,
|
|
12324
|
+
maxTargets: MAINTENANCE_MAX_REPAIR_TARGETS
|
|
12325
|
+
});
|
|
12326
|
+
const messages = formatRecallIndexRepairMessages(result, { dryRun });
|
|
12327
|
+
if (messages.length === 0) {
|
|
12328
|
+
console.log("Recall index freshness OK.");
|
|
12329
|
+
return;
|
|
12330
|
+
}
|
|
12331
|
+
for (const message of messages) {
|
|
12332
|
+
console.log(message);
|
|
12333
|
+
}
|
|
12334
|
+
} catch (err) {
|
|
12335
|
+
console.log(`WARN could not repair recall index freshness: ${errorMessage(err)}`);
|
|
12336
|
+
}
|
|
12337
|
+
}
|
|
12338
|
+
async function configureOpenVikingCliLanguage(config, dryRun) {
|
|
12339
|
+
const ov = dryRun ? await findExecutable(["ov", "openviking"]) ?? "ov" : await findExecutable(["ov", "openviking"]);
|
|
12340
|
+
if (!ov) {
|
|
12341
|
+
return;
|
|
12342
|
+
}
|
|
12343
|
+
const installedVersion = dryRun ? void 0 : await readOpenVikingCliVersion2(ov);
|
|
12344
|
+
const effectiveVersion = installedVersion ?? config.openVikingVersion;
|
|
12345
|
+
if (compareVersions(effectiveVersion, "0.3.23") < 0) {
|
|
12346
|
+
return;
|
|
12347
|
+
}
|
|
12348
|
+
await maybeRun(dryRun, ov, ["language", "en"], { allowFailure: true });
|
|
12349
|
+
}
|
|
12350
|
+
async function readOpenVikingCliVersion2(ov) {
|
|
12351
|
+
const result = await runCommand(ov, ["version"], { allowFailure: true });
|
|
12352
|
+
if (result.exitCode !== 0) {
|
|
12353
|
+
return void 0;
|
|
12354
|
+
}
|
|
12355
|
+
const match = result.stdout.match(/^\s*CLI:\s*(\S+)/m);
|
|
12356
|
+
return match ? match[1] : void 0;
|
|
12357
|
+
}
|
|
11971
12358
|
async function findOpenVikingServer() {
|
|
11972
12359
|
const onPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
|
|
11973
12360
|
if (onPath) {
|
|
11974
12361
|
return onPath;
|
|
11975
12362
|
}
|
|
11976
12363
|
for (const candidateDir of await openVikingServerCandidateDirs()) {
|
|
11977
|
-
const candidate = (0,
|
|
12364
|
+
const candidate = (0, import_node_path13.join)(candidateDir, OPENVIKING_SERVER_COMMAND);
|
|
11978
12365
|
if (await isExecutable(candidate)) {
|
|
11979
12366
|
return candidate;
|
|
11980
12367
|
}
|
|
@@ -12020,7 +12407,7 @@ async function maybePrintOpenVikingPathHint(serverPath) {
|
|
|
12020
12407
|
if (onPath) {
|
|
12021
12408
|
return;
|
|
12022
12409
|
}
|
|
12023
|
-
const binDir = (0,
|
|
12410
|
+
const binDir = (0, import_node_path13.dirname)(serverPath);
|
|
12024
12411
|
const rcHint = suggestedShellRc(process.env.SHELL, (0, import_node_os6.platform)());
|
|
12025
12412
|
console.log(
|
|
12026
12413
|
`Note: ${serverPath} is installed but ${binDir} is not on this shell's PATH. Add \`export PATH="${binDir}:$PATH"\` to ${rcHint} so other tools can find openviking-server.`
|
|
@@ -12064,7 +12451,7 @@ async function runStart(config, options) {
|
|
|
12064
12451
|
);
|
|
12065
12452
|
}
|
|
12066
12453
|
const logPath = openVikingLogPath(config);
|
|
12067
|
-
await ensureDirectory((0,
|
|
12454
|
+
await ensureDirectory((0, import_node_path13.dirname)(logPath), false);
|
|
12068
12455
|
if (options.foreground === true) {
|
|
12069
12456
|
const result = await runInteractive(server, args);
|
|
12070
12457
|
process.exitCode = result;
|
|
@@ -12073,7 +12460,7 @@ async function runStart(config, options) {
|
|
|
12073
12460
|
const logFd = (0, import_node_fs6.openSync)(logPath, "a");
|
|
12074
12461
|
const child = spawnDetachedServerWithLog(server, args, logFd);
|
|
12075
12462
|
child.unref();
|
|
12076
|
-
await (0,
|
|
12463
|
+
await (0, import_promises12.writeFile)((0, import_node_path13.join)(config.agentContextHome, "openviking-server.pid"), `${child.pid}
|
|
12077
12464
|
`, "utf8");
|
|
12078
12465
|
const health = await waitForOpenVikingHealth(config, START_HEALTH_TIMEOUT_MS);
|
|
12079
12466
|
if (health) {
|
|
@@ -12106,7 +12493,7 @@ async function runStop(config, options) {
|
|
|
12106
12493
|
console.log(`No LaunchAgent found: ${launchAgentPath}`);
|
|
12107
12494
|
}
|
|
12108
12495
|
}
|
|
12109
|
-
const pidPath = (0,
|
|
12496
|
+
const pidPath = (0, import_node_path13.join)(config.agentContextHome, "openviking-server.pid");
|
|
12110
12497
|
const pidText = await readFileIfExists(pidPath);
|
|
12111
12498
|
if (!pidText) {
|
|
12112
12499
|
console.log("No pid file found for detached OpenViking server.");
|
|
@@ -12148,7 +12535,7 @@ async function openVikingServerCheck() {
|
|
|
12148
12535
|
}
|
|
12149
12536
|
const result = await runCommand(executable, ["--help"], { allowFailure: true });
|
|
12150
12537
|
const onPath = await findExecutable([OPENVIKING_SERVER_COMMAND]);
|
|
12151
|
-
const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0,
|
|
12538
|
+
const detail = onPath ? executable : `${executable} (found outside PATH; add ${(0, import_node_path13.dirname)(executable)} to PATH)`;
|
|
12152
12539
|
return {
|
|
12153
12540
|
name,
|
|
12154
12541
|
status: result.exitCode === 0 ? "ok" : "warn",
|
|
@@ -12217,7 +12604,7 @@ async function pythonSystemCertificatesCheck() {
|
|
|
12217
12604
|
};
|
|
12218
12605
|
}
|
|
12219
12606
|
async function commandShimCheck() {
|
|
12220
|
-
const shimPath = (0,
|
|
12607
|
+
const shimPath = (0, import_node_path13.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
12221
12608
|
const content = await readFileIfExists(shimPath);
|
|
12222
12609
|
if (content === void 0) {
|
|
12223
12610
|
return { name: "threadnote shim", status: "warn", detail: `${shimPath} missing; repair will create it` };
|
|
@@ -12283,11 +12670,11 @@ async function hasPythonModule(serverPath, moduleName) {
|
|
|
12283
12670
|
async function siblingPythonForExecutable(executablePath) {
|
|
12284
12671
|
let resolvedPath;
|
|
12285
12672
|
try {
|
|
12286
|
-
resolvedPath = await (0,
|
|
12673
|
+
resolvedPath = await (0, import_promises12.realpath)(executablePath);
|
|
12287
12674
|
} catch (_err) {
|
|
12288
12675
|
return void 0;
|
|
12289
12676
|
}
|
|
12290
|
-
const pythonPath = (0,
|
|
12677
|
+
const pythonPath = (0, import_node_path13.join)((0, import_node_path13.dirname)(resolvedPath), "python");
|
|
12291
12678
|
return await exists(pythonPath) ? pythonPath : void 0;
|
|
12292
12679
|
}
|
|
12293
12680
|
async function manifestCheck(path) {
|
|
@@ -12298,6 +12685,29 @@ async function manifestCheck(path) {
|
|
|
12298
12685
|
return { name: "manifest", status: "fail", detail: errorMessage(err) };
|
|
12299
12686
|
}
|
|
12300
12687
|
}
|
|
12688
|
+
async function recallIndexFreshnessCheck(config) {
|
|
12689
|
+
try {
|
|
12690
|
+
const targets = await findStaleRecallIndexTargets(config, {
|
|
12691
|
+
collapseToRoots: true,
|
|
12692
|
+
includeAgentSkills: true,
|
|
12693
|
+
includeManifestResources: true
|
|
12694
|
+
});
|
|
12695
|
+
if (targets.length === 0) {
|
|
12696
|
+
return { name: "recall index freshness", status: "ok", detail: "no stale generated summaries found" };
|
|
12697
|
+
}
|
|
12698
|
+
const staleSummaryCount = targets.reduce((total, target) => total + target.staleCount, 0);
|
|
12699
|
+
const sampleUris = targets.slice(0, 3).map((target) => target.uri);
|
|
12700
|
+
const extraCount = targets.length - sampleUris.length;
|
|
12701
|
+
const sample = `${sampleUris.join(", ")}${extraCount > 0 ? `, +${extraCount} more` : ""}`;
|
|
12702
|
+
return {
|
|
12703
|
+
name: "recall index freshness",
|
|
12704
|
+
status: "warn",
|
|
12705
|
+
detail: `${staleSummaryCount} stale generated summary file(s) under ${targets.length} scope(s); run repair to reindex ${sample}`
|
|
12706
|
+
};
|
|
12707
|
+
} catch (err) {
|
|
12708
|
+
return { name: "recall index freshness", status: "warn", detail: errorMessage(err) };
|
|
12709
|
+
}
|
|
12710
|
+
}
|
|
12301
12711
|
async function fileCheck(path, label) {
|
|
12302
12712
|
return await exists(path) ? { name: label, status: "ok", detail: path } : { name: label, status: "fail", detail: `${path} missing` };
|
|
12303
12713
|
}
|
|
@@ -12358,7 +12768,7 @@ async function offerToInstallUv() {
|
|
|
12358
12768
|
);
|
|
12359
12769
|
return false;
|
|
12360
12770
|
}
|
|
12361
|
-
const readline = (0,
|
|
12771
|
+
const readline = (0, import_promises13.createInterface)({ input: import_node_process3.stdin, output: import_node_process3.stdout });
|
|
12362
12772
|
let answer;
|
|
12363
12773
|
try {
|
|
12364
12774
|
answer = (await readline.question(
|
|
@@ -12483,38 +12893,38 @@ function printInstallNextSteps(options) {
|
|
|
12483
12893
|
}
|
|
12484
12894
|
async function writeTemplateIfMissing(options) {
|
|
12485
12895
|
if (await exists(options.destinationPath)) {
|
|
12486
|
-
const currentContent = await (0,
|
|
12896
|
+
const currentContent = await (0, import_promises12.readFile)(options.destinationPath, "utf8");
|
|
12487
12897
|
if (options.shouldRepair?.(currentContent) !== true) {
|
|
12488
12898
|
console.log(`Already exists: ${options.destinationPath}`);
|
|
12489
12899
|
return;
|
|
12490
12900
|
}
|
|
12491
|
-
const rendered2 = renderTemplate(await (0,
|
|
12901
|
+
const rendered2 = renderTemplate(await (0, import_promises12.readFile)(options.templatePath, "utf8"), options.config);
|
|
12492
12902
|
if (options.dryRun) {
|
|
12493
12903
|
console.log(`Would repair generated config: ${options.destinationPath}`);
|
|
12494
12904
|
return;
|
|
12495
12905
|
}
|
|
12496
12906
|
const backupPath = `${options.destinationPath}.legacy-${safeTimestamp()}`;
|
|
12497
|
-
await (0,
|
|
12498
|
-
await (0,
|
|
12499
|
-
await (0,
|
|
12500
|
-
await (0,
|
|
12907
|
+
await (0, import_promises12.writeFile)(backupPath, currentContent, { encoding: "utf8", mode: 384 });
|
|
12908
|
+
await (0, import_promises12.chmod)(backupPath, 384);
|
|
12909
|
+
await (0, import_promises12.writeFile)(options.destinationPath, rendered2, { encoding: "utf8", mode: 384 });
|
|
12910
|
+
await (0, import_promises12.chmod)(options.destinationPath, 384);
|
|
12501
12911
|
console.log(`Repaired generated config: ${options.destinationPath}`);
|
|
12502
12912
|
console.log(`Backup: ${backupPath}`);
|
|
12503
12913
|
return;
|
|
12504
12914
|
}
|
|
12505
|
-
const rendered = renderTemplate(await (0,
|
|
12915
|
+
const rendered = renderTemplate(await (0, import_promises12.readFile)(options.templatePath, "utf8"), options.config);
|
|
12506
12916
|
if (options.dryRun) {
|
|
12507
12917
|
console.log(`Would write ${options.destinationPath}`);
|
|
12508
12918
|
return;
|
|
12509
12919
|
}
|
|
12510
|
-
await ensureDirectory((0,
|
|
12511
|
-
await (0,
|
|
12512
|
-
await (0,
|
|
12920
|
+
await ensureDirectory((0, import_node_path13.dirname)(options.destinationPath), false);
|
|
12921
|
+
await (0, import_promises12.writeFile)(options.destinationPath, rendered, { encoding: "utf8", mode: 384 });
|
|
12922
|
+
await (0, import_promises12.chmod)(options.destinationPath, 384);
|
|
12513
12923
|
console.log(`Wrote ${options.destinationPath}`);
|
|
12514
12924
|
}
|
|
12515
12925
|
async function installCommandShim(dryRun) {
|
|
12516
12926
|
const binDir = expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin");
|
|
12517
|
-
const shimPath = (0,
|
|
12927
|
+
const shimPath = (0, import_node_path13.join)(binDir, "threadnote");
|
|
12518
12928
|
const existingContent = await readFileIfExists(shimPath);
|
|
12519
12929
|
if (existingContent && !isManagedCommandShim(existingContent)) {
|
|
12520
12930
|
console.log(`WARN not overwriting existing command shim: ${shimPath}`);
|
|
@@ -12530,12 +12940,12 @@ async function installCommandShim(dryRun) {
|
|
|
12530
12940
|
return;
|
|
12531
12941
|
}
|
|
12532
12942
|
await ensureDirectory(binDir, false);
|
|
12533
|
-
await (0,
|
|
12534
|
-
await (0,
|
|
12943
|
+
await (0, import_promises12.writeFile)(shimPath, content, { encoding: "utf8", mode: 493 });
|
|
12944
|
+
await (0, import_promises12.chmod)(shimPath, 493);
|
|
12535
12945
|
console.log(`Wrote command shim: ${shimPath}`);
|
|
12536
12946
|
}
|
|
12537
12947
|
async function removeCommandShim(dryRun) {
|
|
12538
|
-
const shimPath = (0,
|
|
12948
|
+
const shimPath = (0, import_node_path13.join)(expandPath(process.env.THREADNOTE_BIN_DIR ?? "~/.local/bin"), "threadnote");
|
|
12539
12949
|
const content = await readFileIfExists(shimPath);
|
|
12540
12950
|
if (content === void 0) {
|
|
12541
12951
|
console.log(`Already absent: ${shimPath}`);
|
|
@@ -12569,8 +12979,8 @@ async function installUserAgentInstructions(dryRun) {
|
|
|
12569
12979
|
console.log(currentContent === void 0 ? `Would write ${targetPath}` : `Would update ${targetPath}`);
|
|
12570
12980
|
continue;
|
|
12571
12981
|
}
|
|
12572
|
-
await ensureDirectory((0,
|
|
12573
|
-
await (0,
|
|
12982
|
+
await ensureDirectory((0, import_node_path13.dirname)(targetPath), false);
|
|
12983
|
+
await (0, import_promises12.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
12574
12984
|
console.log(currentContent === void 0 ? `Wrote ${targetPath}` : `Updated ${targetPath}`);
|
|
12575
12985
|
}
|
|
12576
12986
|
}
|
|
@@ -12607,7 +13017,7 @@ async function removeUserAgentInstructions(dryRun) {
|
|
|
12607
13017
|
console.log(`Would update ${targetPath}`);
|
|
12608
13018
|
continue;
|
|
12609
13019
|
}
|
|
12610
|
-
await (0,
|
|
13020
|
+
await (0, import_promises12.writeFile)(targetPath, nextContent, { encoding: "utf8", mode: 420 });
|
|
12611
13021
|
console.log(`Updated ${targetPath}`);
|
|
12612
13022
|
}
|
|
12613
13023
|
}
|
|
@@ -12627,7 +13037,7 @@ async function renderUserAgentInstructions(target) {
|
|
|
12627
13037
|
].join("\n");
|
|
12628
13038
|
}
|
|
12629
13039
|
async function renderUserAgentInstructionsBlock() {
|
|
12630
|
-
const instructions = (await (0,
|
|
13040
|
+
const instructions = (await (0, import_promises12.readFile)((0, import_node_path13.join)(toolRoot(), "docs", "agent-instructions.md"), "utf8")).trim();
|
|
12631
13041
|
return `${USER_INSTRUCTIONS_START_MARKER}
|
|
12632
13042
|
${instructions}
|
|
12633
13043
|
${USER_INSTRUCTIONS_END_MARKER}`;
|
|
@@ -12711,9 +13121,9 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
12711
13121
|
`Cannot install LaunchAgent: ${OPENVIKING_SERVER_COMMAND} was not found in PATH, uv tool bin dir, $UV_TOOL_BIN_DIR, or ~/.local/bin. Run \`threadnote install\` first.`
|
|
12712
13122
|
);
|
|
12713
13123
|
}
|
|
12714
|
-
const source = (0,
|
|
13124
|
+
const source = (0, import_node_path13.join)(toolRoot(), "config", "launchd", `${LAUNCHD_LABEL}.plist.template`);
|
|
12715
13125
|
const destination = expandPath(`~/Library/LaunchAgents/${LAUNCHD_LABEL}.plist`);
|
|
12716
|
-
const rendered = renderTemplate(await (0,
|
|
13126
|
+
const rendered = renderTemplate(await (0, import_promises12.readFile)(source, "utf8"), config, {
|
|
12717
13127
|
OPENVIKING_SERVER_PATH: resolvedServer ?? OPENVIKING_SERVER_COMMAND
|
|
12718
13128
|
});
|
|
12719
13129
|
if (dryRun) {
|
|
@@ -12725,9 +13135,9 @@ async function installLaunchAgent(config, dryRun) {
|
|
|
12725
13135
|
console.log(`Would run: launchctl start ${LAUNCHD_LABEL}`);
|
|
12726
13136
|
return;
|
|
12727
13137
|
}
|
|
12728
|
-
await ensureDirectory((0,
|
|
12729
|
-
await ensureDirectory((0,
|
|
12730
|
-
await (0,
|
|
13138
|
+
await ensureDirectory((0, import_node_path13.dirname)(destination), false);
|
|
13139
|
+
await ensureDirectory((0, import_node_path13.dirname)(openVikingLogPath(config)), false);
|
|
13140
|
+
await (0, import_promises12.writeFile)(destination, rendered, "utf8");
|
|
12731
13141
|
await maybeRun(false, "launchctl", ["unload", destination], { allowFailure: true });
|
|
12732
13142
|
await maybeRun(false, "launchctl", ["load", destination]);
|
|
12733
13143
|
await maybeRun(false, "launchctl", ["start", LAUNCHD_LABEL]);
|
|
@@ -12790,7 +13200,7 @@ function isGeneratedLocalPilotConfig(parsed, config) {
|
|
|
12790
13200
|
if (typeof parsed.default_user !== "string") {
|
|
12791
13201
|
return false;
|
|
12792
13202
|
}
|
|
12793
|
-
if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0,
|
|
13203
|
+
if (!isJsonObject(parsed.storage) || parsed.storage.workspace !== (0, import_node_path13.join)(config.agentContextHome, "data")) {
|
|
12794
13204
|
return false;
|
|
12795
13205
|
}
|
|
12796
13206
|
return isJsonObject(parsed.server) && parsed.server.host === config.host && String(parsed.server.port) === String(config.port);
|
|
@@ -12852,9 +13262,9 @@ function printWhatsNew(whatsNew) {
|
|
|
12852
13262
|
// src/manager.ts
|
|
12853
13263
|
var import_node_http2 = require("node:http");
|
|
12854
13264
|
var import_node_crypto2 = require("node:crypto");
|
|
12855
|
-
var
|
|
13265
|
+
var import_promises14 = require("node:fs/promises");
|
|
12856
13266
|
var import_node_os7 = require("node:os");
|
|
12857
|
-
var
|
|
13267
|
+
var import_node_path14 = require("node:path");
|
|
12858
13268
|
var STATIC_FILES = {
|
|
12859
13269
|
"/": { contentType: "text/html; charset=utf-8", path: "index.html" },
|
|
12860
13270
|
"/index.html": { contentType: "text/html; charset=utf-8", path: "index.html" },
|
|
@@ -12897,24 +13307,46 @@ async function memoryTree(config) {
|
|
|
12897
13307
|
const root = localMemoriesRoot(config);
|
|
12898
13308
|
return readTree(config, root, `viking://user/${uriSegment(config.user)}/memories`, "");
|
|
12899
13309
|
}
|
|
13310
|
+
async function resourcesTree(config) {
|
|
13311
|
+
const root = localResourcesRoot(config);
|
|
13312
|
+
try {
|
|
13313
|
+
return await readTree(config, root, "viking://resources", "", {
|
|
13314
|
+
parseMemoryDocuments: false,
|
|
13315
|
+
rootName: "resources"
|
|
13316
|
+
});
|
|
13317
|
+
} catch (err) {
|
|
13318
|
+
if (isMissingPathError(err)) {
|
|
13319
|
+
return {
|
|
13320
|
+
children: [],
|
|
13321
|
+
isDir: true,
|
|
13322
|
+
isShared: false,
|
|
13323
|
+
isSystem: false,
|
|
13324
|
+
name: "resources",
|
|
13325
|
+
relativePath: "",
|
|
13326
|
+
uri: "viking://resources"
|
|
13327
|
+
};
|
|
13328
|
+
}
|
|
13329
|
+
throw err;
|
|
13330
|
+
}
|
|
13331
|
+
}
|
|
12900
13332
|
async function readManagedMemory(config, uri) {
|
|
12901
13333
|
assertVikingUri(uri);
|
|
12902
13334
|
const path = localPathForMemoryUri(config, uri);
|
|
12903
13335
|
if (!path) {
|
|
12904
13336
|
throw new Error(`Manager can only read current-user memory URIs: ${uri}`);
|
|
12905
13337
|
}
|
|
12906
|
-
const [content, pathStat] = await Promise.all([(0,
|
|
12907
|
-
const relativePath = (0,
|
|
13338
|
+
const [content, pathStat] = await Promise.all([(0, import_promises14.readFile)(path, "utf8"), (0, import_promises14.stat)(path)]);
|
|
13339
|
+
const relativePath = (0, import_node_path14.relative)(localMemoriesRoot(config), path).split(import_node_path14.sep).join("/");
|
|
12908
13340
|
const record = parseMemoryDocument(uri, content);
|
|
12909
13341
|
return {
|
|
12910
13342
|
content,
|
|
12911
13343
|
node: {
|
|
12912
13344
|
isDir: false,
|
|
12913
13345
|
isShared: isInSharedNamespace(config, uri),
|
|
12914
|
-
isSystem: isSystemMemoryName(path.split(
|
|
13346
|
+
isSystem: isSystemMemoryName(path.split(import_node_path14.sep).at(-1) ?? ""),
|
|
12915
13347
|
metadata: record?.metadata,
|
|
12916
13348
|
modTime: pathStat.mtime.toISOString(),
|
|
12917
|
-
name: path.split(
|
|
13349
|
+
name: path.split(import_node_path14.sep).at(-1) ?? uri,
|
|
12918
13350
|
relativePath,
|
|
12919
13351
|
sharedTeam: sharedTeamNameForUri(config, uri),
|
|
12920
13352
|
size: pathStat.size,
|
|
@@ -12981,7 +13413,8 @@ async function handleRequest(context, request, response) {
|
|
|
12981
13413
|
return;
|
|
12982
13414
|
}
|
|
12983
13415
|
if (request.method === "GET" && url.pathname === "/api/tree") {
|
|
12984
|
-
|
|
13416
|
+
const [tree, resourceTree] = await Promise.all([memoryTree(context.config), resourcesTree(context.config)]);
|
|
13417
|
+
writeJson(response, 200, { resourcesTree: resourceTree, tree });
|
|
12985
13418
|
return;
|
|
12986
13419
|
}
|
|
12987
13420
|
if (request.method === "GET" && url.pathname === "/api/memory") {
|
|
@@ -13200,21 +13633,20 @@ async function handleRequest(context, request, response) {
|
|
|
13200
13633
|
}
|
|
13201
13634
|
async function serveStatic(context, url, response) {
|
|
13202
13635
|
const file = STATIC_FILES[url.pathname] ?? STATIC_FILES["/"];
|
|
13203
|
-
const content = await (0,
|
|
13636
|
+
const content = await (0, import_promises14.readFile)((0, import_node_path14.join)(toolRoot(), file.root ?? "manager", file.path));
|
|
13204
13637
|
const headers = { "content-type": file.contentType };
|
|
13205
|
-
if (
|
|
13638
|
+
if (file.root !== "docs") {
|
|
13206
13639
|
headers["cache-control"] = "no-store";
|
|
13207
13640
|
}
|
|
13208
13641
|
response.writeHead(200, headers);
|
|
13209
13642
|
response.end(content);
|
|
13210
13643
|
}
|
|
13211
|
-
async function readTree(config, path, uri, relativePath) {
|
|
13212
|
-
const pathStat = await (0,
|
|
13213
|
-
const name = relativePath ? relativePath.split("/").at(-1) ?? relativePath : "memories";
|
|
13644
|
+
async function readTree(config, path, uri, relativePath, options = {}) {
|
|
13645
|
+
const pathStat = await (0, import_promises14.stat)(path);
|
|
13646
|
+
const name = relativePath ? relativePath.split("/").at(-1) ?? relativePath : options.rootName ?? "memories";
|
|
13214
13647
|
const isDir = pathStat.isDirectory();
|
|
13215
13648
|
if (!isDir) {
|
|
13216
|
-
const
|
|
13217
|
-
const record = parseMemoryDocument(uri, content);
|
|
13649
|
+
const record = options.parseMemoryDocuments === false ? void 0 : parseMemoryDocument(uri, await (0, import_promises14.readFile)(path, "utf8").catch(() => ""));
|
|
13218
13650
|
return {
|
|
13219
13651
|
isDir: false,
|
|
13220
13652
|
isShared: isInSharedNamespace(config, uri),
|
|
@@ -13228,13 +13660,13 @@ async function readTree(config, path, uri, relativePath) {
|
|
|
13228
13660
|
uri
|
|
13229
13661
|
};
|
|
13230
13662
|
}
|
|
13231
|
-
const entries = await (0,
|
|
13663
|
+
const entries = await (0, import_promises14.readdir)(path, { withFileTypes: true });
|
|
13232
13664
|
const children = await Promise.all(
|
|
13233
13665
|
entries.sort(
|
|
13234
13666
|
(left, right) => Number(right.isDirectory()) - Number(left.isDirectory()) || left.name.localeCompare(right.name)
|
|
13235
13667
|
).map((entry) => {
|
|
13236
13668
|
const childRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
13237
|
-
return readTree(config, (0,
|
|
13669
|
+
return readTree(config, (0, import_node_path14.join)(path, entry.name), `${uri}/${entry.name}`, childRelative, options);
|
|
13238
13670
|
})
|
|
13239
13671
|
);
|
|
13240
13672
|
return {
|
|
@@ -13408,27 +13840,27 @@ async function removeManagedFolder(config, uri) {
|
|
|
13408
13840
|
if (!path) {
|
|
13409
13841
|
throw new Error(`Manager can only remove current-user memory folders: ${uri}`);
|
|
13410
13842
|
}
|
|
13411
|
-
const pathStat = await (0,
|
|
13843
|
+
const pathStat = await (0, import_promises14.stat)(path);
|
|
13412
13844
|
if (!pathStat.isDirectory()) {
|
|
13413
13845
|
throw new Error(`Not a folder: ${uri}`);
|
|
13414
13846
|
}
|
|
13415
|
-
const relativePath = (0,
|
|
13416
|
-
if (!relativePath || relativePath.startsWith("..") || relativePath.split(
|
|
13847
|
+
const relativePath = (0, import_node_path14.relative)(localMemoriesRoot(config), path);
|
|
13848
|
+
if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path14.sep).includes("..")) {
|
|
13417
13849
|
throw new Error("Refusing to remove a folder outside the memories tree.");
|
|
13418
13850
|
}
|
|
13419
13851
|
const fileUris = await fileUrisUnderFolder(config, path);
|
|
13420
13852
|
for (const fileUri of fileUris) {
|
|
13421
13853
|
await runForget(config, fileUri, {});
|
|
13422
13854
|
}
|
|
13423
|
-
await (0,
|
|
13855
|
+
await (0, import_promises14.rm)(path, { force: true, recursive: true });
|
|
13424
13856
|
console.log(`Removed folder: ${uri}`);
|
|
13425
13857
|
console.log(`Forgot ${fileUris.length} file${fileUris.length === 1 ? "" : "s"}.`);
|
|
13426
13858
|
}
|
|
13427
13859
|
async function fileUrisUnderFolder(config, folderPath) {
|
|
13428
|
-
const entries = await (0,
|
|
13860
|
+
const entries = await (0, import_promises14.readdir)(folderPath, { withFileTypes: true });
|
|
13429
13861
|
const uris = [];
|
|
13430
13862
|
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
13431
|
-
const path = (0,
|
|
13863
|
+
const path = (0, import_node_path14.join)(folderPath, entry.name);
|
|
13432
13864
|
if (entry.isDirectory()) {
|
|
13433
13865
|
uris.push(...await fileUrisUnderFolder(config, path));
|
|
13434
13866
|
} else if (entry.isFile()) {
|
|
@@ -13526,11 +13958,11 @@ async function runConsolidationAgent(agent, sources) {
|
|
|
13526
13958
|
throw new Error(`${agent} executable was not found.`);
|
|
13527
13959
|
}
|
|
13528
13960
|
const prompt = consolidationPrompt(sources);
|
|
13529
|
-
const stagingDir = await (0,
|
|
13530
|
-
const promptPath = (0,
|
|
13961
|
+
const stagingDir = await (0, import_promises14.mkdtemp)((0, import_node_path14.join)((0, import_node_os7.tmpdir)(), "threadnote-consolidate-"));
|
|
13962
|
+
const promptPath = (0, import_node_path14.join)(stagingDir, "prompt.txt");
|
|
13531
13963
|
try {
|
|
13532
|
-
await (0,
|
|
13533
|
-
await (0,
|
|
13964
|
+
await (0, import_promises14.writeFile)(promptPath, prompt, { encoding: "utf8", mode: 384 });
|
|
13965
|
+
await (0, import_promises14.chmod)(promptPath, 384);
|
|
13534
13966
|
const script = consolidationAgentScript(agent, executable);
|
|
13535
13967
|
const result = await runCommand("sh", ["-lc", script, "threadnote-consolidate", promptPath], {
|
|
13536
13968
|
allowFailure: true,
|
|
@@ -13546,7 +13978,7 @@ async function runConsolidationAgent(agent, sources) {
|
|
|
13546
13978
|
}
|
|
13547
13979
|
return draft;
|
|
13548
13980
|
} finally {
|
|
13549
|
-
await (0,
|
|
13981
|
+
await (0, import_promises14.rm)(stagingDir, { force: true, recursive: true });
|
|
13550
13982
|
}
|
|
13551
13983
|
}
|
|
13552
13984
|
function consolidationAgentScript(agent, executable) {
|
|
@@ -13684,7 +14116,10 @@ function memoryDirectoryUri2(config, kind, status, projectSegment) {
|
|
|
13684
14116
|
}
|
|
13685
14117
|
}
|
|
13686
14118
|
function localMemoriesRoot(config) {
|
|
13687
|
-
return (0,
|
|
14119
|
+
return (0, import_node_path14.join)(config.agentContextHome, "data", "viking", config.account, "user", uriSegment(config.user), "memories");
|
|
14120
|
+
}
|
|
14121
|
+
function localResourcesRoot(config) {
|
|
14122
|
+
return (0, import_node_path14.join)(config.agentContextHome, "data", "viking", config.account, "resources");
|
|
13688
14123
|
}
|
|
13689
14124
|
function localPathForMemoryUri(config, uri) {
|
|
13690
14125
|
const prefix = `viking://user/${uriSegment(config.user)}/memories`;
|
|
@@ -13696,14 +14131,17 @@ function localPathForMemoryUri(config, uri) {
|
|
|
13696
14131
|
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
13697
14132
|
return void 0;
|
|
13698
14133
|
}
|
|
13699
|
-
return (0,
|
|
14134
|
+
return (0, import_node_path14.join)(localMemoriesRoot(config), ...segments);
|
|
14135
|
+
}
|
|
14136
|
+
function isMissingPathError(err) {
|
|
14137
|
+
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
13700
14138
|
}
|
|
13701
14139
|
function localPathToMemoryUri(config, path) {
|
|
13702
|
-
const relativePath = (0,
|
|
13703
|
-
if (!relativePath || relativePath.startsWith("..") || relativePath.split(
|
|
14140
|
+
const relativePath = (0, import_node_path14.relative)(localMemoriesRoot(config), path);
|
|
14141
|
+
if (!relativePath || relativePath.startsWith("..") || relativePath.split(import_node_path14.sep).includes("..")) {
|
|
13704
14142
|
throw new Error(`Path is outside the memories tree: ${path}`);
|
|
13705
14143
|
}
|
|
13706
|
-
return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(
|
|
14144
|
+
return `viking://user/${uriSegment(config.user)}/memories/${relativePath.split(import_node_path14.sep).join("/")}`;
|
|
13707
14145
|
}
|
|
13708
14146
|
async function ensurePersonalDirectoryChain2(config, ov, directoryUri) {
|
|
13709
14147
|
const prefix = "viking://";
|
|
@@ -13908,7 +14346,7 @@ async function main() {
|
|
|
13908
14346
|
program2.command("init-manifest").description("Create or update a per-developer seed manifest from one or more repo roots").option("--dry-run", "Print the manifest without writing it").option("--path <path>", "Manifest path; defaults to THREADNOTE_MANIFEST or ~/.openviking/seed-manifest.yaml").option("--replace", "Replace the manifest instead of merging with existing projects").option("--repo <path>", "Repo root to include; repeat for multiple repos", collectOption, []).action(async (options) => {
|
|
13909
14347
|
await runInitManifest(getRuntimeConfig(program2), options);
|
|
13910
14348
|
});
|
|
13911
|
-
program2.command("seed-skills").description("Seed Codex
|
|
14349
|
+
program2.command("seed-skills").description("Seed Codex/Claude skills and Claude command markdown files as a searchable catalog").option("--dry-run", "Print skill files and ov commands without importing").option("--manifest <path>", "Manifest path for repo-local skill discovery").option("--native", "Use native OpenViking skill ingestion; requires a working VLM config").action(async (options) => {
|
|
13912
14350
|
await runSeedSkills(getRuntimeConfig(program2, options.manifest), options);
|
|
13913
14351
|
});
|
|
13914
14352
|
program2.command("mcp-install").description("Install OpenViking MCP config for a supported agent").argument("<agent>", "codex, claude, cursor, or copilot").option("--apply", "Actually modify the selected agent config").option("--name <name>", "MCP server name", OPENVIKING_MCP_NAME).option("--native-http", "Install OpenViking native HTTP MCP endpoint instead of the local stdio adapter").option("--scope <scope>", "Claude MCP config scope: user, local, or project", parseClaudeMcpScope, "user").option("--url <url>", "OpenViking native HTTP MCP URL").option("--bearer-token-env-var <name>", "Environment variable containing the local API key").action(async (agent, options) => {
|