@cerefox/memory 1.4.0 → 1.6.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/AGENT_GUIDE.md +90 -2
- package/AGENT_QUICK_REFERENCE.md +91 -1
- package/README.md +3 -2
- package/dist/bin/cerefox.js +843 -489
- package/dist/frontend/assets/{index-B1pgikxA.js → index-DcWOeGAh.js} +30 -30
- package/dist/frontend/assets/index-DcWOeGAh.js.map +1 -0
- package/dist/frontend/assets/{index-C1JXZA9m.css → index-DiDeaiM6.css} +1 -1
- package/dist/frontend/index.html +2 -2
- package/dist/server-assets/_shared/ef-meta/index.ts +20 -2
- package/dist/server-assets/_shared/mcp-tools/audit-log.ts +14 -1
- package/dist/server-assets/_shared/mcp-tools/get-help-content.ts +6 -4
- package/dist/server-assets/_shared/mcp-tools/get-help.ts +71 -1
- package/dist/server-assets/_shared/mcp-tools/index.ts +3 -0
- package/dist/server-assets/_shared/mcp-tools/list-versions.ts +12 -1
- package/dist/server-assets/_shared/mcp-tools/set-document-metadata.ts +133 -0
- package/dist/server-assets/_shared/partial-edits/index.ts +49 -0
- package/dist/server-assets/db/migrations/0022_rls_on_document_relations.sql +38 -0
- package/dist/server-assets/db/migrations/0023_set_document_metadata.sql +16 -0
- package/dist/server-assets/db/rpcs.sql +118 -1
- package/dist/server-assets/db/schema.sql +8 -1
- package/docs/guides/cli.md +54 -1
- package/docs/guides/connect-agents.md +11 -8
- package/docs/guides/operational-cost.md +1 -1
- package/package.json +1 -1
- package/dist/frontend/assets/index-B1pgikxA.js.map +0 -1
package/dist/bin/cerefox.js
CHANGED
|
@@ -7438,7 +7438,7 @@ var exports_meta = {};
|
|
|
7438
7438
|
__export(exports_meta, {
|
|
7439
7439
|
PKG_VERSION: () => PKG_VERSION
|
|
7440
7440
|
});
|
|
7441
|
-
var PKG_VERSION = "1.
|
|
7441
|
+
var PKG_VERSION = "1.6.0";
|
|
7442
7442
|
var init_meta = () => {};
|
|
7443
7443
|
|
|
7444
7444
|
// ../../_shared/config/paths.ts
|
|
@@ -25730,6 +25730,10 @@ var init_bundled_docs = __esm(() => {
|
|
|
25730
25730
|
PACKAGE_ROOT = findPackageRoot();
|
|
25731
25731
|
});
|
|
25732
25732
|
|
|
25733
|
+
// ../../_shared/ef-meta/index.ts
|
|
25734
|
+
var EF_VERSION = "1.6.0", CEREFOX_VERSION = "1.6.0", EF_LAST_CHANGED = "1.6.0";
|
|
25735
|
+
var init_ef_meta = () => {};
|
|
25736
|
+
|
|
25733
25737
|
// ../../_shared/compatibility/index.ts
|
|
25734
25738
|
function compareSemver(a, b2) {
|
|
25735
25739
|
const split = (v) => {
|
|
@@ -26318,6 +26322,19 @@ function extractSection(content, anchorHeading, sectionPart) {
|
|
|
26318
26322
|
section_part: sectionPart ?? null
|
|
26319
26323
|
};
|
|
26320
26324
|
}
|
|
26325
|
+
function firstMeaningfulLine(text) {
|
|
26326
|
+
for (const line of text.split(`
|
|
26327
|
+
`)) {
|
|
26328
|
+
if (line.trim() !== "")
|
|
26329
|
+
return canonicalHeading(line);
|
|
26330
|
+
}
|
|
26331
|
+
return "";
|
|
26332
|
+
}
|
|
26333
|
+
function assertNoDuplicateHeading(text, node, opName) {
|
|
26334
|
+
if (firstMeaningfulLine(text) === node.heading) {
|
|
26335
|
+
throw new DuplicateHeadingError(node.heading, opName);
|
|
26336
|
+
}
|
|
26337
|
+
}
|
|
26321
26338
|
function firstChild(outline, node) {
|
|
26322
26339
|
for (const n of outline) {
|
|
26323
26340
|
if (n.start >= node.bodyStart && n.start < node.subtreeEnd)
|
|
@@ -26373,9 +26390,11 @@ function applyOne(content, operation) {
|
|
|
26373
26390
|
at = node2.start;
|
|
26374
26391
|
detail = "insert before_heading";
|
|
26375
26392
|
} else if (position === "after_heading") {
|
|
26393
|
+
assertNoDuplicateHeading(text, node2, "insert");
|
|
26376
26394
|
at = node2.bodyStart;
|
|
26377
26395
|
detail = "insert after_heading";
|
|
26378
26396
|
} else {
|
|
26397
|
+
assertNoDuplicateHeading(text, node2, "insert");
|
|
26379
26398
|
at = resolveSectionEnd(content, outline, node2, operation.section_part, "end_of_section insert");
|
|
26380
26399
|
detail = `insert at end_of_section` + (operation.section_part ? ` (${operation.section_part})` : "");
|
|
26381
26400
|
}
|
|
@@ -26386,6 +26405,7 @@ function applyOne(content, operation) {
|
|
|
26386
26405
|
}
|
|
26387
26406
|
if (operation.op === "replace_section") {
|
|
26388
26407
|
const node2 = resolveAnchor(outline, operation.anchor_heading);
|
|
26408
|
+
assertNoDuplicateHeading(operation.text, node2, "replace_section");
|
|
26389
26409
|
const to2 = resolveSectionEnd(content, outline, node2, operation.section_part, "replace_section");
|
|
26390
26410
|
return {
|
|
26391
26411
|
content: spliceBlock(content, node2.bodyStart, to2, operation.text),
|
|
@@ -26512,7 +26532,7 @@ function applyOperations(content, operations) {
|
|
|
26512
26532
|
}
|
|
26513
26533
|
return { content: current, applied };
|
|
26514
26534
|
}
|
|
26515
|
-
var AnchorNotFoundError, AmbiguousAnchorError, AmbiguousPositionError, HeadingLevelChangeError, InvalidOperationError, ATX_HEADING, FENCE_OPEN;
|
|
26535
|
+
var AnchorNotFoundError, AmbiguousAnchorError, AmbiguousPositionError, HeadingLevelChangeError, DuplicateHeadingError, InvalidOperationError, ATX_HEADING, FENCE_OPEN;
|
|
26516
26536
|
var init_partial_edits = __esm(() => {
|
|
26517
26537
|
AnchorNotFoundError = class AnchorNotFoundError extends Error {
|
|
26518
26538
|
constructor(anchor, outline, reads = false) {
|
|
@@ -26562,6 +26582,12 @@ ${outline.map((n) => ` ${n.path}`).join(`
|
|
|
26562
26582
|
this.name = "HeadingLevelChangeError";
|
|
26563
26583
|
}
|
|
26564
26584
|
};
|
|
26585
|
+
DuplicateHeadingError = class DuplicateHeadingError extends Error {
|
|
26586
|
+
constructor(heading, opName) {
|
|
26587
|
+
super(`The text you passed to ${opName} starts with the anchor heading itself ` + `(${JSON.stringify(heading)}). No write was performed. The heading is kept ` + `automatically — ${opName === "replace_section" ? "replace_section preserves it" : "insert places your text inside the section"}, ` + `so including it would produce two. Send only the new content, without that ` + `heading line. A DEEPER sub-heading inside your text is fine.`);
|
|
26588
|
+
this.name = "DuplicateHeadingError";
|
|
26589
|
+
}
|
|
26590
|
+
};
|
|
26565
26591
|
InvalidOperationError = class InvalidOperationError extends Error {
|
|
26566
26592
|
constructor(index, message) {
|
|
26567
26593
|
super(`Invalid operation at index ${index}: ${message}. No write was performed.`);
|
|
@@ -41554,10 +41580,10 @@ var require_flate = __commonJS((exports) => {
|
|
|
41554
41580
|
var GenericWorker = require_GenericWorker();
|
|
41555
41581
|
var ARRAY_TYPE = USE_TYPEDARRAY ? "uint8array" : "array";
|
|
41556
41582
|
exports.magic = "\b\x00";
|
|
41557
|
-
function FlateWorker(
|
|
41558
|
-
GenericWorker.call(this, "FlateWorker/" +
|
|
41583
|
+
function FlateWorker(action19, options) {
|
|
41584
|
+
GenericWorker.call(this, "FlateWorker/" + action19);
|
|
41559
41585
|
this._pako = null;
|
|
41560
|
-
this._pakoAction =
|
|
41586
|
+
this._pakoAction = action19;
|
|
41561
41587
|
this._pakoOptions = options;
|
|
41562
41588
|
this.meta = {};
|
|
41563
41589
|
}
|
|
@@ -54698,6 +54724,10 @@ var require_lib7 = __commonJS((exports) => {
|
|
|
54698
54724
|
});
|
|
54699
54725
|
|
|
54700
54726
|
// ../../_shared/mcp-tools/audit-log.ts
|
|
54727
|
+
function utcStamp2(iso) {
|
|
54728
|
+
const trimmed = iso.slice(0, 19);
|
|
54729
|
+
return trimmed.includes("T") ? `${trimmed}Z` : `${trimmed} UTC`;
|
|
54730
|
+
}
|
|
54701
54731
|
async function handler(supabase, args, ctx) {
|
|
54702
54732
|
const params = {};
|
|
54703
54733
|
if (args.document_id)
|
|
@@ -54727,7 +54757,7 @@ async function handler(supabase, args, ctx) {
|
|
|
54727
54757
|
const lines = entries.map((e) => {
|
|
54728
54758
|
const docLabel = e.doc_title ?? (e.document_id ? e.document_id.slice(0, 8) + "..." : "(deleted)");
|
|
54729
54759
|
const sizeInfo = e.size_before != null && e.size_after != null ? ` | ${e.size_before} -> ${e.size_after} chars` : e.size_after != null ? ` | ${e.size_after} chars` : "";
|
|
54730
|
-
return `${e.created_at
|
|
54760
|
+
return `${utcStamp2(e.created_at)} | ${e.operation} | ${e.author} (${e.author_type}) | ${docLabel}${sizeInfo} | ${e.description}`;
|
|
54731
54761
|
});
|
|
54732
54762
|
return `Audit log (${entries.length} entries, newest first):
|
|
54733
54763
|
|
|
@@ -55156,11 +55186,346 @@ var init_get_document = __esm(() => {
|
|
|
55156
55186
|
};
|
|
55157
55187
|
});
|
|
55158
55188
|
|
|
55189
|
+
// ../../_shared/mcp-tools/_chunker.ts
|
|
55190
|
+
function normalizeContent(text) {
|
|
55191
|
+
return text.trim().replace(/\r\n/g, `
|
|
55192
|
+
`).replace(/\r/g, `
|
|
55193
|
+
`).replace(/\n{3,}/g, `
|
|
55194
|
+
|
|
55195
|
+
`);
|
|
55196
|
+
}
|
|
55197
|
+
async function sha256hex(text) {
|
|
55198
|
+
const bytes = new TextEncoder().encode(text);
|
|
55199
|
+
const hash = await crypto.subtle.digest("SHA-256", bytes);
|
|
55200
|
+
return Array.from(new Uint8Array(hash)).map((b2) => b2.toString(16).padStart(2, "0")).join("");
|
|
55201
|
+
}
|
|
55202
|
+
var init__chunker = () => {};
|
|
55203
|
+
|
|
55204
|
+
// ../../_shared/mcp-tools/partial-edits.ts
|
|
55205
|
+
function resolveAuthorType2(ctx, args) {
|
|
55206
|
+
if (ctx.accessPath !== "cli")
|
|
55207
|
+
return "agent";
|
|
55208
|
+
return args.author_type === "agent" ? "agent" : "user";
|
|
55209
|
+
}
|
|
55210
|
+
function defaultRequestor(ctx) {
|
|
55211
|
+
return ctx.accessPath === "cli" ? "cli-user" : "mcp-agent";
|
|
55212
|
+
}
|
|
55213
|
+
function touchedTrailingSection(applied) {
|
|
55214
|
+
return applied.some((a) => a.reachedEnd === true);
|
|
55215
|
+
}
|
|
55216
|
+
function shrinkNote(before, afterChars, applied) {
|
|
55217
|
+
const beforeChars = [...before].length;
|
|
55218
|
+
const lost = beforeChars - afterChars;
|
|
55219
|
+
if (lost <= 0)
|
|
55220
|
+
return "";
|
|
55221
|
+
const pct = Math.round(lost / Math.max(beforeChars, 1) * 100);
|
|
55222
|
+
const trailing = touchedTrailingSection(applied);
|
|
55223
|
+
if (pct < 25 && !trailing) {
|
|
55224
|
+
return `This edit removed ${lost} characters. cerefox_list_versions has the previous content.
|
|
55225
|
+
`;
|
|
55226
|
+
}
|
|
55227
|
+
const why = trailing ? `You replaced or deleted the LAST section, and a section runs to the next ` + `heading of the same or higher level — or to the end of the document — so ` + `anything appended after it was inside it. ` : `If you did not intend that, note that a section runs to the next heading ` + `of the same or higher level — or to the end of the document — so replacing ` + `or deleting the LAST section also removes anything appended after it. `;
|
|
55228
|
+
return `⚠ This edit removed ${lost} characters (${pct}% smaller). ${why}cerefox_list_versions has the previous content.
|
|
55229
|
+
`;
|
|
55230
|
+
}
|
|
55231
|
+
function conflictError(documentId, expectedHash, currentHash) {
|
|
55232
|
+
return new Error(`Conflict: document ${documentId} changed since you read it ` + `(your base hash: ${expectedHash}, current hash: ${currentHash}). No write was performed. ` + `To resolve: (1) cerefox_get_document("${documentId}", outline=true) to see the current ` + `structure and hash cheaply, or a full read if you need the text, (2) decide whether your ` + `edit still applies — another writer may have already made it, or made something it ` + `contradicts, (3) retry with expected_content_hash set to the current hash. ` + `These tools have no last-write-wins: the other writer's work is not yours to discard.`);
|
|
55233
|
+
}
|
|
55234
|
+
async function readDocument(supabase, documentId) {
|
|
55235
|
+
const { data, error: error2 } = await supabase.rpc("cerefox_get_document", {
|
|
55236
|
+
p_document_id: documentId,
|
|
55237
|
+
p_version_id: null
|
|
55238
|
+
});
|
|
55239
|
+
if (error2)
|
|
55240
|
+
throw new Error(`Could not read document: ${error2.message}`);
|
|
55241
|
+
const row = data?.[0] ?? undefined;
|
|
55242
|
+
if (!row || row.full_content === undefined) {
|
|
55243
|
+
throw new McpInvalidParams(`Document not found: ${documentId}. Partial edits apply to an existing document; ` + `use cerefox_ingest to create one.`);
|
|
55244
|
+
}
|
|
55245
|
+
return {
|
|
55246
|
+
title: row.doc_title ?? "Untitled",
|
|
55247
|
+
content: row.full_content,
|
|
55248
|
+
hash: row.content_hash ?? ""
|
|
55249
|
+
};
|
|
55250
|
+
}
|
|
55251
|
+
async function applyAndWrite(supabase, ctx, args) {
|
|
55252
|
+
const { documentId, operations, expectedHash, requestor, toolLabel, authorType } = args;
|
|
55253
|
+
if (!ctx.openaiApiKey && resolveEmbedderKind() !== "local") {
|
|
55254
|
+
throw new Error("OpenAI API key not configured. Set OPENAI_API_KEY (Edge Function) or CEREFOX_OPENAI_API_KEY (.env, local).");
|
|
55255
|
+
}
|
|
55256
|
+
const doc = await readDocument(supabase, documentId);
|
|
55257
|
+
if (doc.hash && expectedHash !== doc.hash) {
|
|
55258
|
+
throw conflictError(documentId, expectedHash, doc.hash);
|
|
55259
|
+
}
|
|
55260
|
+
let assembled;
|
|
55261
|
+
let applied;
|
|
55262
|
+
try {
|
|
55263
|
+
const result = applyOperations(doc.content, operations);
|
|
55264
|
+
assembled = result.content;
|
|
55265
|
+
applied = result.applied;
|
|
55266
|
+
} catch (err) {
|
|
55267
|
+
throw new McpInvalidParams(err instanceof Error ? err.message : String(err));
|
|
55268
|
+
}
|
|
55269
|
+
if (assembled === doc.content) {
|
|
55270
|
+
return `No change: the ${toolLabel} produced content identical to the current document ` + `"${doc.title}" (id: ${documentId}). content_hash: ${doc.hash} (unchanged).`;
|
|
55271
|
+
}
|
|
55272
|
+
const newHash = await sha256hex(normalizeContent(assembled));
|
|
55273
|
+
const chunks = chunkMarkdown(assembled);
|
|
55274
|
+
if (chunks.length === 0) {
|
|
55275
|
+
throw new McpInvalidParams("The result of this edit produced no chunks (the document would be empty). " + "To remove a document use cerefox_ingest or the delete path, not a partial edit.");
|
|
55276
|
+
}
|
|
55277
|
+
const texts = chunks.map((c2) => embeddingInputFor(doc.title, c2));
|
|
55278
|
+
const embeddings = await embedBatch(texts, ctx.openaiApiKey ?? "");
|
|
55279
|
+
const totalChars = chunks.reduce((s, c2) => s + c2.char_count, 0);
|
|
55280
|
+
const chunkData = chunks.map((chunk, i) => ({
|
|
55281
|
+
chunk_index: i,
|
|
55282
|
+
heading_path: chunk.heading_path,
|
|
55283
|
+
heading_level: chunk.heading_level,
|
|
55284
|
+
title: chunk.title,
|
|
55285
|
+
content: chunk.content,
|
|
55286
|
+
char_count: chunk.char_count,
|
|
55287
|
+
embedding: embeddings[i],
|
|
55288
|
+
embedder: activeEmbedderName()
|
|
55289
|
+
}));
|
|
55290
|
+
const { data, error: error2 } = await supabase.rpc("cerefox_ingest_document", {
|
|
55291
|
+
p_document_id: documentId,
|
|
55292
|
+
p_title: doc.title,
|
|
55293
|
+
p_source: null,
|
|
55294
|
+
p_content_hash: newHash,
|
|
55295
|
+
p_metadata: null,
|
|
55296
|
+
p_review_status: authorType === "agent" ? "pending_review" : "approved",
|
|
55297
|
+
p_chunks: chunkData,
|
|
55298
|
+
p_author: requestor,
|
|
55299
|
+
p_author_type: authorType,
|
|
55300
|
+
p_source_label: authorType === "user" ? "manual" : "agent",
|
|
55301
|
+
p_expected_content_hash: expectedHash,
|
|
55302
|
+
p_last_write_wins: false,
|
|
55303
|
+
p_content_format: CONTENT_FORMAT_BLIND_STITCH,
|
|
55304
|
+
p_operations: applied.map((a) => ({ op: AUDIT_OP[a.op], detail: a.detail }))
|
|
55305
|
+
});
|
|
55306
|
+
if (error2) {
|
|
55307
|
+
const message = error2.message ?? "";
|
|
55308
|
+
if (message.includes("CEREFOX_CONFLICT")) {
|
|
55309
|
+
const current = message.match(/current hash ([0-9a-f]{64})/)?.[1] ?? "unknown";
|
|
55310
|
+
throw conflictError(documentId, expectedHash, current);
|
|
55311
|
+
}
|
|
55312
|
+
if (message.includes("cerefox_documents_hash_unique")) {
|
|
55313
|
+
throw new Error(`This edit would make "${doc.title}" (id: ${documentId}) byte-identical to another ` + `document in the store, and content must be unique. No write was performed. ` + `Usually this means the two documents have converged and one should be removed or ` + `merged, or that this edit was already applied to the other one — ` + `cerefox_search for the resulting content to find it.`);
|
|
55314
|
+
}
|
|
55315
|
+
if (message.includes("does not exist") && message.includes("cerefox_ingest_document")) {
|
|
55316
|
+
throw new Error(`This server is behind: partial edits need schema 0.11.0 or newer. ` + `Run \`cerefox server deploy\`, then retry. (${message})`);
|
|
55317
|
+
}
|
|
55318
|
+
throw new Error(`Edit failed: ${message}`);
|
|
55319
|
+
}
|
|
55320
|
+
const row = data?.[0] ?? undefined;
|
|
55321
|
+
logUsage(supabase, {
|
|
55322
|
+
operation: toolLabel,
|
|
55323
|
+
accessPath: ctx.accessPath,
|
|
55324
|
+
requestor,
|
|
55325
|
+
document_id: documentId,
|
|
55326
|
+
result_count: applied.length
|
|
55327
|
+
});
|
|
55328
|
+
const summary = applied.map((a, i) => ` ${i + 1}. ${a.detail} → ${a.path}`).join(`
|
|
55329
|
+
`);
|
|
55330
|
+
const warning2 = row?.size_warning ? `
|
|
55331
|
+
|
|
55332
|
+
⚠ This document has passed the configured size threshold ` + `(document_size_warning_chars). Consider splitting it.` : "";
|
|
55333
|
+
return `Applied ${applied.length} operation(s) to "${doc.title}" (id: ${documentId}):
|
|
55334
|
+
${summary}
|
|
55335
|
+
|
|
55336
|
+
` + `New content_hash: ${row?.content_hash ?? newHash}
|
|
55337
|
+
` + `Size: ${row?.total_chars ?? totalChars} chars (was ${doc.content.length}), ${chunks.length} chunk(s).
|
|
55338
|
+
` + shrinkNote(doc.content, row?.total_chars ?? totalChars, applied) + `Pass the new content_hash as expected_content_hash on your next edit.${warning2}`;
|
|
55339
|
+
}
|
|
55340
|
+
async function insertHandler(supabase, args, ctx) {
|
|
55341
|
+
const documentId = args.document_id?.trim();
|
|
55342
|
+
const text = args.text;
|
|
55343
|
+
const position = args.position;
|
|
55344
|
+
const expectedHash = args.expected_content_hash?.trim();
|
|
55345
|
+
if (!documentId)
|
|
55346
|
+
throw new McpInvalidParams("document_id is required");
|
|
55347
|
+
if (!text?.trim())
|
|
55348
|
+
throw new McpInvalidParams("text is required and cannot be empty");
|
|
55349
|
+
if (!expectedHash) {
|
|
55350
|
+
throw new McpInvalidParams("expected_content_hash is required. It is the content_hash of the version you are " + "basing this insert on — returned by cerefox_get_document (including outline mode), " + "cerefox_search, cerefox_metadata_search, and by every write. There is no " + "last-write-wins here: knowing the document changed under you is the point.");
|
|
55351
|
+
}
|
|
55352
|
+
const operations = validateOperations([
|
|
55353
|
+
{
|
|
55354
|
+
op: "insert",
|
|
55355
|
+
text,
|
|
55356
|
+
position,
|
|
55357
|
+
...args.anchor_heading !== undefined ? { anchor_heading: args.anchor_heading } : {},
|
|
55358
|
+
...args.section_part !== undefined ? { section_part: args.section_part } : {}
|
|
55359
|
+
}
|
|
55360
|
+
]);
|
|
55361
|
+
return applyAndWrite(supabase, ctx, {
|
|
55362
|
+
documentId,
|
|
55363
|
+
operations,
|
|
55364
|
+
expectedHash,
|
|
55365
|
+
requestor: args.requestor ?? defaultRequestor(ctx),
|
|
55366
|
+
toolLabel: "insert",
|
|
55367
|
+
authorType: resolveAuthorType2(ctx, args)
|
|
55368
|
+
});
|
|
55369
|
+
}
|
|
55370
|
+
async function editHandler(supabase, args, ctx) {
|
|
55371
|
+
const documentId = args.document_id?.trim();
|
|
55372
|
+
const expectedHash = args.expected_content_hash?.trim();
|
|
55373
|
+
if (!documentId)
|
|
55374
|
+
throw new McpInvalidParams("document_id is required");
|
|
55375
|
+
if (!expectedHash) {
|
|
55376
|
+
throw new McpInvalidParams("expected_content_hash is required. It is the content_hash of the version you are " + "basing these edits on — returned by cerefox_get_document (including outline mode), " + "cerefox_search, cerefox_metadata_search, and by every write.");
|
|
55377
|
+
}
|
|
55378
|
+
let operations;
|
|
55379
|
+
try {
|
|
55380
|
+
operations = validateOperations(args.operations);
|
|
55381
|
+
} catch (err) {
|
|
55382
|
+
throw new McpInvalidParams(err instanceof Error ? err.message : String(err));
|
|
55383
|
+
}
|
|
55384
|
+
return applyAndWrite(supabase, ctx, {
|
|
55385
|
+
documentId,
|
|
55386
|
+
operations,
|
|
55387
|
+
expectedHash,
|
|
55388
|
+
requestor: args.requestor ?? defaultRequestor(ctx),
|
|
55389
|
+
toolLabel: "edit",
|
|
55390
|
+
authorType: resolveAuthorType2(ctx, args)
|
|
55391
|
+
});
|
|
55392
|
+
}
|
|
55393
|
+
var AUDIT_OP, insertTool, editTool;
|
|
55394
|
+
var init_partial_edits2 = __esm(() => {
|
|
55395
|
+
init_partial_edits();
|
|
55396
|
+
init__chunker();
|
|
55397
|
+
init_types3();
|
|
55398
|
+
AUDIT_OP = {
|
|
55399
|
+
insert: "insert",
|
|
55400
|
+
replace_section: "replace-section",
|
|
55401
|
+
delete_section: "delete-section",
|
|
55402
|
+
rename_section: "rename-section"
|
|
55403
|
+
};
|
|
55404
|
+
insertTool = {
|
|
55405
|
+
name: "cerefox_insert",
|
|
55406
|
+
description: "Add text to a document without resending the whole thing. Purely additive: it cannot " + "remove or overwrite existing content, so it is the safe way to append. Positions: " + "end_of_document (a plain append), end_of_section (add to the end of a section's body — " + "the most common mid-document add), after_heading (lead-in text), before_heading (a new " + "block above a section). Anchors are the exact heading line ('## Intake') or a parent path " + "('## Intake > ### Notes') when a heading appears more than once. Requires " + "expected_content_hash; returns the new hash, not the document.",
|
|
55407
|
+
annotations: {
|
|
55408
|
+
title: "Insert into document",
|
|
55409
|
+
readOnlyHint: false,
|
|
55410
|
+
destructiveHint: false,
|
|
55411
|
+
idempotentHint: false,
|
|
55412
|
+
openWorldHint: false
|
|
55413
|
+
},
|
|
55414
|
+
inputSchema: {
|
|
55415
|
+
type: "object",
|
|
55416
|
+
required: ["document_id", "text", "position", "expected_content_hash"],
|
|
55417
|
+
properties: {
|
|
55418
|
+
document_id: { type: "string", description: "UUID of the document to add to" },
|
|
55419
|
+
text: { type: "string", description: "Markdown to insert. Sent as-is; blank-line separation is handled for you." },
|
|
55420
|
+
position: {
|
|
55421
|
+
type: "string",
|
|
55422
|
+
enum: ["end_of_document", "end_of_section", "after_heading", "before_heading"],
|
|
55423
|
+
description: "Where to insert. end_of_document needs no anchor; the other three require anchor_heading."
|
|
55424
|
+
},
|
|
55425
|
+
anchor_heading: {
|
|
55426
|
+
type: "string",
|
|
55427
|
+
description: "Exact heading line, or a ' > ' path for a heading that appears more than once. Required unless position is end_of_document."
|
|
55428
|
+
},
|
|
55429
|
+
section_part: {
|
|
55430
|
+
type: "string",
|
|
55431
|
+
enum: ["own_body", "subtree"],
|
|
55432
|
+
description: "Only for end_of_section when the target section HAS CHILD SECTIONS: own_body = before the first child, subtree = after everything nested under it. These can be far apart, so the tool refuses rather than choosing. Omit it otherwise; you will be told (with both options) whenever it is needed."
|
|
55433
|
+
},
|
|
55434
|
+
expected_content_hash: {
|
|
55435
|
+
type: "string",
|
|
55436
|
+
description: "content_hash of the version you are basing this on. Required — no last-write-wins."
|
|
55437
|
+
},
|
|
55438
|
+
requestor: {
|
|
55439
|
+
type: "string",
|
|
55440
|
+
description: 'Agent or user making this request. Recorded in the usage log. Defaults to "mcp-agent".'
|
|
55441
|
+
},
|
|
55442
|
+
author_type: {
|
|
55443
|
+
type: "string",
|
|
55444
|
+
enum: ["user", "agent"],
|
|
55445
|
+
description: "Honoured on the CLI only, where a human ran the command; over MCP the write is always recorded as an agent write regardless of what is passed."
|
|
55446
|
+
}
|
|
55447
|
+
}
|
|
55448
|
+
},
|
|
55449
|
+
handler: insertHandler
|
|
55450
|
+
};
|
|
55451
|
+
editTool = {
|
|
55452
|
+
name: "cerefox_edit",
|
|
55453
|
+
description: "Change parts of a document without resending the whole thing: one or many operations " + "applied ATOMICALLY in one write. Operations: insert (same positions as cerefox_insert), " + "replace_section (swap a section's body, heading kept), delete_section (remove a section, " + "scope body_only or heading_and_body), rename_section (change a heading's text, leaving " + "its body and position untouched — for headings that go stale, like a dated one). " + "Use one call for changes that belong together — a " + "half-applied edit is impossible, so a row and the total it feeds cannot disagree. " + "Operations apply in order and each sees the previous one's result. To change a single " + "line, replace_section on its smallest enclosing heading. Requires expected_content_hash; " + "returns the new hash, not the document.",
|
|
55454
|
+
annotations: {
|
|
55455
|
+
title: "Edit document sections",
|
|
55456
|
+
readOnlyHint: false,
|
|
55457
|
+
destructiveHint: true,
|
|
55458
|
+
idempotentHint: false,
|
|
55459
|
+
openWorldHint: false
|
|
55460
|
+
},
|
|
55461
|
+
inputSchema: {
|
|
55462
|
+
type: "object",
|
|
55463
|
+
required: ["document_id", "operations", "expected_content_hash"],
|
|
55464
|
+
properties: {
|
|
55465
|
+
document_id: { type: "string", description: "UUID of the document to edit" },
|
|
55466
|
+
operations: {
|
|
55467
|
+
type: "array",
|
|
55468
|
+
minItems: 1,
|
|
55469
|
+
description: "Operations applied in order, all-or-nothing. If any fails (bad anchor, ambiguity), nothing is written.",
|
|
55470
|
+
items: {
|
|
55471
|
+
type: "object",
|
|
55472
|
+
required: ["op"],
|
|
55473
|
+
properties: {
|
|
55474
|
+
op: {
|
|
55475
|
+
type: "string",
|
|
55476
|
+
enum: ["insert", "replace_section", "delete_section", "rename_section"]
|
|
55477
|
+
},
|
|
55478
|
+
text: { type: "string", description: "Markdown. Required for insert and replace_section." },
|
|
55479
|
+
position: {
|
|
55480
|
+
type: "string",
|
|
55481
|
+
enum: ["end_of_document", "end_of_section", "after_heading", "before_heading"],
|
|
55482
|
+
description: "Required for insert."
|
|
55483
|
+
},
|
|
55484
|
+
anchor_heading: {
|
|
55485
|
+
type: "string",
|
|
55486
|
+
description: "Exact heading line, or a ' > ' path when the heading is not unique. Required for replace_section, delete_section, and any insert other than end_of_document."
|
|
55487
|
+
},
|
|
55488
|
+
section_part: {
|
|
55489
|
+
type: "string",
|
|
55490
|
+
enum: ["own_body", "subtree"],
|
|
55491
|
+
description: "Only when the target section has child sections. You will be told (with both options) whenever it is needed."
|
|
55492
|
+
},
|
|
55493
|
+
scope: {
|
|
55494
|
+
type: "string",
|
|
55495
|
+
enum: ["body_only", "heading_and_body"],
|
|
55496
|
+
description: "delete_section only. Defaults to body_only, which keeps the heading."
|
|
55497
|
+
},
|
|
55498
|
+
new_heading: {
|
|
55499
|
+
type: "string",
|
|
55500
|
+
description: "rename_section only: the replacement heading LINE, at the same level (## stays ##). Changes the heading text and nothing else — the body and the section's position are untouched, which is the point: renaming via delete + insert would risk both. Use it for headings that go stale, like '## OPEN TODOs (as of 2026-08-08)'. A rename changes the anchor, so a later operation in the same call must target the NEW heading."
|
|
55501
|
+
}
|
|
55502
|
+
}
|
|
55503
|
+
}
|
|
55504
|
+
},
|
|
55505
|
+
expected_content_hash: {
|
|
55506
|
+
type: "string",
|
|
55507
|
+
description: "content_hash of the version you are basing these edits on. Required — no last-write-wins."
|
|
55508
|
+
},
|
|
55509
|
+
requestor: {
|
|
55510
|
+
type: "string",
|
|
55511
|
+
description: 'Agent or user making this request. Recorded in the usage log. Defaults to "mcp-agent".'
|
|
55512
|
+
},
|
|
55513
|
+
author_type: {
|
|
55514
|
+
type: "string",
|
|
55515
|
+
enum: ["user", "agent"],
|
|
55516
|
+
description: "Honoured on the CLI only, where a human ran the command; over MCP the write is always recorded as an agent write regardless of what is passed."
|
|
55517
|
+
}
|
|
55518
|
+
}
|
|
55519
|
+
},
|
|
55520
|
+
handler: editHandler
|
|
55521
|
+
};
|
|
55522
|
+
});
|
|
55523
|
+
|
|
55159
55524
|
// ../../_shared/mcp-tools/get-help-content.ts
|
|
55160
|
-
var HELP_FULL = '# Cerefox Knowledge Base -- Agent Quick Reference\n\nCerefox is a persistent, shared knowledge base. You have **16 MCP tools** (15 of them have CLI equivalents — `cerefox_get_help` is MCP-only). For the full guide, search Cerefox for "How AI Agents Use Cerefox" or call `cerefox_get_help` to retrieve this content over MCP.\n\n## Tools\n\n| Tool | Purpose | Key params |\n|------|---------|------------|\n| `cerefox_search` | Find documents (hybrid FTS + semantic) | `query` (required), `project_name`, `metadata_filter`, `requestor` |\n| `cerefox_ingest` | Save or update a document | `title`, `content` (required), `document_id` (update by ID), `expected_content_hash` (**required on content updates** — see rule 9), `last_write_wins`, `update_if_exists`, `project_name` (single, non-destructive add on update), `project_names` (list, destructive replace on update), `metadata` (omit on update to keep existing tags; `{}` clears), `author` |\n| `cerefox_insert` | **Add** to a document without resending it. Cannot destroy content. | `document_id`, `text`, `position` (`end_of_document`/`end_of_section`/`after_heading`/`before_heading`), `expected_content_hash` (required), `anchor_heading` (unless `end_of_document`), `section_part` |\n| `cerefox_edit` | **Change** parts of a document: 1..n operations applied atomically | `document_id`, `operations` (`insert`/`replace_section`/`delete_section`/`rename_section`), `expected_content_hash` (required) |\n| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token), or with `outline: true` just its heading paths, sizes and hash, or with `section: "## Heading"` one section\'s text | `document_id` (required), `outline`, `section`, `section_part` |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required) |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | Find or list docs by metadata, project, or time (no text query) | `metadata_filter`, `project_name` (list a project\'s docs), `updated_since`, `include_content` — **at least one** of metadata_filter/project_name/updated_since/created_since |\n| `cerefox_list_metadata_keys` | Discover available metadata keys | (none required) |\n| `cerefox_list_projects` | List all projects | (none required) |\n| `cerefox_set_document_projects` | Set doc\'s project memberships to exactly the given list (destructive replace; metadata-only, no content change) | `document_id`, `project_names` (required) |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `author`, `operation`, `since` |\n| `cerefox_get_help` | Retrieve Cerefox conventions (this reference) over MCP. **Call this whenever uncertain.** | `topic` (optional, case-insensitive H2 substring match) |\n\n⚑ **Opt-in — usually absent.** The four relation tools are hidden unless the\noperator enables them (`relations_enabled`). **Trust your own tool list**: if\nthey are not in it, the feature is switched off for this deployment. That is\nnormal, not an error, and not something to work around.\n\n## Editing part of a document (prefer this over re-sending)\n\n**Re-sending a whole document to change part of it is the main way agents lose\ndata.** You have to reproduce the untouched remainder verbatim, and any drift\nsilently rewrites content nobody asked you to touch — which the caller cannot\ndiff. Use the partial-edit tools instead:\n\n1. **Learn the anchors** — `cerefox_get_document(document_id, outline: true)`.\n Returns heading paths, per-section sizes and the `content_hash`, without the\n body. The paths it returns are exactly what `anchor_heading` accepts.\n2. **Add** → `cerefox_insert`. `end_of_document` is a plain append;\n `end_of_section` adds inside a named section. It is structurally incapable of\n removing anything, so "I meant to append" cannot become "I replaced the file".\n3. **Look before you overwrite** — `cerefox_get_document(document_id,\n section: "## Heading")` returns exactly the text a `replace_section` on that\n anchor would destroy. The outline gives you a section\'s *size*, never its\n *text*, so on a document you did not write yourself this is the difference\n between a replace and a blind overwrite.\n4. **Change or remove** → `cerefox_edit`. Put changes that belong together in\n ONE call: they apply atomically, so a table row and the total it feeds cannot\n end up disagreeing. To change a single line, `replace_section` on its\n smallest enclosing heading — that is the intended granularity, not a\n workaround. To fix a stale heading (`## OPEN TODOs (as of ...)`), use\n `rename_section`: it changes the heading text and leaves the body and\n position alone.\n5. All of them require `expected_content_hash` and **have no last-write-wins**. A\n conflict means someone else changed the document; re-read and decide, do not\n force it.\n\n**A section runs to the next same-or-higher heading, or to the end of the\ndocument.** So `end_of_document` inserts land inside the *last* section, and\nreplacing or deleting that section removes them too. A large shrink in the\nresponse is your warning; `cerefox_list_versions` has the previous content.\n\n**When an anchor is ambiguous the tool refuses and hands you the options** — a\nrepeated heading returns the qualifying paths, and a section with both its own\ncontent and sub-sections returns both `section_part` choices. That is a\nrecoverable answer, not a failure: retry with what it gave you.\n\n## Essential Rules\n\n1. **Search before ingesting** -- check if the document exists first.\n2. **Prefer ID-based updates** -- pass `document_id` from search results for deterministic updates. Falls back to title-matching with `update_if_exists: true`.\n3. **Set `author`/`requestor`** to your name on every call (e.g., "Claude Code", "archiver"). On MCP, pass as parameters. On CLI, pass `--author`/`--author-type`/`--requestor` flags, or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE`/`CEREFOX_REQUESTOR_NAME` env vars set in the user\'s `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **Add metadata** -- at minimum `type` ("decision-log", "research", "design-doc") and `status` ("active", "draft").\n6. **Write structured Markdown** with H1/H2/H3 headings for good chunking and search.\n7. **Deletes are soft (recoverable); purge is web-UI-only.** If you decide to delete, surface it to the user (`I soft-deleted X — recoverable from the Cerefox web UI trash`). You cannot un-do your own delete from agent code by design.\n8. **Cross-doc links inside content**: **always use `[Text](document-uuid)`.** UUIDs are the only fully reliable link form — stable across title changes, never ambiguous, no encoding gotchas. Every `cerefox_search` result shows `[id: <uuid>]` after the title; grab it and use it. Title-based linking (`[Text](<Title With Spaces>)`) is fragile (breaks on colons, parens, ampersands, brackets — silently navigates to wrong page) — **don\'t write title-based links**; do an extra search to get the UUID instead. Repo-path forms (`[Text](docs/path.md)`) exist for repo-ingested files; don\'t construct manually. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n9. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you last saw — every read shows one (`cerefox_get_document` incl. outline mode, `cerefox_search`, `cerefox_metadata_search`) and **every write returns the new one, including create** (v1.3.0, #189), so after writing you already hold the token for your next edit; no re-read needed. If it\'s stale you get a **conflict** — re-read the document, merge your changes into the latest content, retry with the new hash. **Never resolve a conflict by overwriting blindly** — the current content includes another writer\'s work. `last_write_wins: true` skips the check; use it ONLY when an external source of truth makes conflicts meaningless (file re-sync), never to silence a conflict.\n10. **Search: prefer a few distinctive terms; heed `below confidence`.** When nothing clears the relevance threshold, `cerefox_search` returns the closest candidates prefixed with a `below confidence` warning instead of an empty set — that flag means **weak signal, not absent knowledge**: check the candidates\' scores and titles before concluding the KB lacks the content. A truly empty response means nothing even weakly related exists.\n11. **Relations express how documents relate; lifecycle tells you if knowledge is still good.** Use `cerefox_set_relation` when one document supersedes, contradicts, references, or continues another. `supersedes` marks the target **superseded**; `contradicts` marks **both** stale; `related_to`/`duplicates`/`contradicts` are symmetric (both directions written). Any other type string is accepted without special behaviour. When a search result or `cerefox_get_relations` shows a neighbour marked `[superseded]` or `[stale]`, say so rather than presenting it as current.\n12. **Project memberships — non-destructive by default**: on `cerefox_ingest` updates, **`project_name` (singular) is a non-destructive add** (ensures membership, preserves others). Use **`project_names` (list)** when you want to set the doc\'s full project set in one call (destructive replace). For metadata-only project changes without writing content, use **`cerefox_set_document_projects(document_id, project_names)`** — that tool is the destructive-replace contract made explicit. Never call `cerefox_set_document_projects` with a single name when you mean "add" — that would REMOVE the doc from all other projects. When in doubt, use `cerefox_ingest` with singular `project_name`.\n\n## Update Workflow (ID-based -- preferred)\n\n```\nsearch("topic") -> find doc [id: abc123] -> get_document(abc123) -> note its content_hash -> modify ->\ningest(title="Same Title", content="...", document_id="abc123",\n expected_content_hash="<the hash you read>", author="my-agent")\n```\n\nOn a **conflict** error: get_document again (fresh content + fresh hash) -> merge your changes -> retry with the new hash.\n\n## Update Workflow (title-based -- fallback)\n\n```\nsearch("topic") -> find doc (note its hash) -> modify ->\ningest(title="Same Title", content="...", update_if_exists=true,\n expected_content_hash="<the hash you read>", author="my-agent")\n```\n\n## Catch-Up Workflow\n\n```\nmetadata_search(metadata_filter={"type": "decision-log"}, updated_since="2026-03-28T00:00:00Z")\n```\n\n## CLI fallback (when MCP is unavailable)\n\nIf `cerefox_search` is not in your tool list, your user has likely installed the Cerefox CLI. The canonical invocation is plain **`cerefox <subcommand>`** (the TypeScript CLI, installed via `npm install -g @cerefox/memory`). It uses a resource-verb shape (`cerefox document get`, `cerefox project list`, …).\n\nSame operations, same conventions. Full reference: [`docs/guides/cli.md`](docs/guides/cli.md). CLI flag names match MCP parameter names exactly (e.g. `metadata_filter` ↔ `--metadata-filter`); common flags also have single-letter short forms (`-f`, `-p`, `-c`, `-m`, `-u`, `-a`, `-r`). Use the canonical long name (what `--help` shows) or its short form — there are no long-form aliases like `--filter` or `--count`.\n\n| MCP tool | CLI |\n|---|---|\n| `cerefox_search` | `cerefox search "<q>" --requestor "<your-name>"` |\n| `cerefox_ingest` (paste) | `printf \'...\' \\| cerefox document ingest --paste --title "<t>" --author "<your-name>" --author-type agent` |\n| `cerefox_ingest` (update by ID) | `printf \'...\' \\| cerefox document ingest --paste --title "<t>" --document-id "<uuid>" --expected-content-hash "<hash>" --author "<your-name>" --author-type agent` |\n| `cerefox_get_document` | `cerefox document get <id> --version-id <vid> --requestor "<your-name>"` |\n| `cerefox_list_versions` | `cerefox document version list <id> --requestor "<your-name>"` |\n| `cerefox_list_projects` | `cerefox project list --requestor "<your-name>"` |\n| `cerefox_list_metadata_keys` | `cerefox metadata keys` |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | `cerefox metadata search --metadata-filter \'<json>\' --requestor "<your-name>"` (list a project: `cerefox document list --project <name>`) |\n| `cerefox_set_document_projects` | `cerefox document set-projects <id> <name...> --author "<your-name>" --author-type agent` (or `--clear` to remove all) |\n| `cerefox_get_audit_log` | `cerefox audit list --requestor "<your-name>"` (add `--json` for scripted access) |\n| `cerefox_get_help` | `cerefox guides show agent-quick-reference` (or `cerefox guides list` for the full bundled-docs index) |\n\n**Set identity on every call**, exactly as you would on MCP:\n- Writes (`document ingest`, `document ingest-dir`): `--author "<your-name>" --author-type agent`\n- Reads: `--requestor "<your-name>"`\n\nOr have your user set `CEREFOX_AUTHOR_NAME` / `CEREFOX_AUTHOR_TYPE` / `CEREFOX_REQUESTOR_NAME` in their `.env` to apply defaults once.\n', HELP_SECTIONS, HELP_SECTION_HEADINGS;
|
|
55525
|
+
var HELP_FULL = '# Cerefox Knowledge Base -- Agent Quick Reference\n\nCerefox is a persistent, shared knowledge base. You have **17 MCP tools** (16 of them have CLI equivalents — `cerefox_get_help` is MCP-only). For the full guide, search Cerefox for "How AI Agents Use Cerefox" or call `cerefox_get_help` to retrieve this content over MCP.\n\n## Tools\n\n| Tool | Purpose | Key params |\n|------|---------|------------|\n| `cerefox_search` | Find documents (hybrid FTS + semantic) | `query` (required), `project_name`, `metadata_filter`, `requestor` |\n| `cerefox_ingest` | Save or update a document | `title`, `content` (required), `document_id` (update by ID), `expected_content_hash` (**required on content updates** — see rule 9), `last_write_wins`, `update_if_exists`, `project_name` (single, non-destructive add on update), `project_names` (list, destructive replace on update), `metadata` (omit on update to keep existing tags; `{}` clears), `author` |\n| `cerefox_insert` | **Add** to a document without resending it. Cannot destroy content. | `document_id`, `text`, `position` (`end_of_document`/`end_of_section`/`after_heading`/`before_heading`), `expected_content_hash` (required), `anchor_heading` (unless `end_of_document`), `section_part` |\n| `cerefox_edit` | **Change** parts of a document: 1..n operations applied atomically | `document_id`, `operations` (`insert`/`replace_section`/`delete_section`/`rename_section`), `expected_content_hash` (required) |\n| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token), or with `outline: true` just its heading paths, sizes and hash, or with `section: "## Heading"` one section\'s text | `document_id` (required), `outline`, `section`, `section_part` |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required) |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | Find or list docs by metadata, project, or time (no text query) | `metadata_filter`, `project_name` (list a project\'s docs), `updated_since`, `include_content` — **at least one** of metadata_filter/project_name/updated_since/created_since |\n| `cerefox_list_metadata_keys` | Discover available metadata keys | (none required) |\n| `cerefox_list_projects` | List all projects | (none required) |\n| `cerefox_set_document_metadata` | Change tags WITHOUT resending content. **Merges** by default; a `null` value removes a key | `document_id`, `metadata` (required), `replace` (rare: set exactly this object), `author` |\n| `cerefox_set_document_projects` | Set doc\'s project memberships to exactly the given list (destructive replace; metadata-only, no content change) | `document_id`, `project_names` (required) |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `author`, `operation`, `since` |\n| `cerefox_get_help` | Retrieve Cerefox conventions (this reference) over MCP. **Call this whenever uncertain.** | `topic` (optional, case-insensitive H2 substring match) |\n\n⚑ **Opt-in — usually absent.** The four relation tools are hidden unless the\noperator enables them (`relations_enabled`). **Trust your own tool list**: if\nthey are not in it, the feature is switched off for this deployment. That is\nnormal, not an error, and not something to work around.\n\n## Editing part of a document (prefer this over re-sending)\n\n**Re-sending a whole document to change part of it is the main way agents lose\ndata.** You have to reproduce the untouched remainder verbatim, and any drift\nsilently rewrites content nobody asked you to touch — which the caller cannot\ndiff. Use the partial-edit tools instead:\n\n1. **Learn the anchors** — `cerefox_get_document(document_id, outline: true)`.\n Returns heading paths, per-section sizes and the `content_hash`, without the\n body. The paths it returns are exactly what `anchor_heading` accepts.\n2. **Add** → `cerefox_insert`. `end_of_document` is a plain append;\n `end_of_section` adds inside a named section. It is structurally incapable of\n removing anything, so "I meant to append" cannot become "I replaced the file".\n3. **Look before you overwrite** — `cerefox_get_document(document_id,\n section: "## Heading")` returns exactly the text a `replace_section` on that\n anchor would destroy. The outline gives you a section\'s *size*, never its\n *text*, so on a document you did not write yourself this is the difference\n between a replace and a blind overwrite.\n4. **Change or remove** → `cerefox_edit`. Put changes that belong together in\n ONE call: they apply atomically, so a table row and the total it feeds cannot\n end up disagreeing. To change a single line, `replace_section` on its\n smallest enclosing heading — that is the intended granularity, not a\n workaround. To fix a stale heading (`## OPEN TODOs (as of ...)`), use\n `rename_section`: it changes the heading text and leaves the body and\n position alone.\n5. All of them require `expected_content_hash` and **have no last-write-wins**. A\n conflict means someone else changed the document; re-read and decide, do not\n force it.\n\n**A section runs to the next same-or-higher heading, or to the end of the\ndocument.** So `end_of_document` inserts land inside the *last* section, and\nreplacing or deleting that section removes them too. A large shrink in the\nresponse is your warning; `cerefox_list_versions` has the previous content.\n\n**When an anchor is ambiguous the tool refuses and hands you the options** — a\nrepeated heading returns the qualifying paths, and a section with both its own\ncontent and sub-sections returns both `section_part` choices. That is a\nrecoverable answer, not a failure: retry with what it gave you.\n\n## Essential Rules\n\n1. **Search before ingesting** -- check if the document exists first.\n2. **Prefer ID-based updates** -- pass `document_id` from search results for deterministic updates. Falls back to title-matching with `update_if_exists: true`.\n3. **Set `author`/`requestor`** to your name on every call (e.g., "Claude Code", "archiver"). On MCP, pass as parameters. On CLI, pass `--author`/`--author-type`/`--requestor` flags, or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE`/`CEREFOX_REQUESTOR_NAME` env vars set in the user\'s `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **Add metadata** -- at minimum `type` ("decision-log", "research", "design-doc") and `status` ("active", "draft").\n6. **Write structured Markdown** with H1/H2/H3 headings for good chunking and search.\n7. **Deletes are soft (recoverable); purge is web-UI-only.** If you decide to delete, surface it to the user (`I soft-deleted X — recoverable from the Cerefox web UI trash`). You cannot un-do your own delete from agent code by design.\n8. **Cross-doc links inside content**: **always use `[Text](document-uuid)`.** UUIDs are the only fully reliable link form — stable across title changes, never ambiguous, no encoding gotchas. Every `cerefox_search` result shows `[id: <uuid>]` after the title; grab it and use it. Title-based linking (`[Text](<Title With Spaces>)`) is fragile (breaks on colons, parens, ampersands, brackets — silently navigates to wrong page) — **don\'t write title-based links**; do an extra search to get the UUID instead. Repo-path forms (`[Text](docs/path.md)`) exist for repo-ingested files; don\'t construct manually. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n9. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you last saw — every read shows one (`cerefox_get_document` incl. outline mode, `cerefox_search`, `cerefox_metadata_search`) and **every write returns the new one, including create** (v1.3.0, #189), so after writing you already hold the token for your next edit; no re-read needed. If it\'s stale you get a **conflict** — re-read the document, merge your changes into the latest content, retry with the new hash. **Never resolve a conflict by overwriting blindly** — the current content includes another writer\'s work. `last_write_wins: true` skips the check; use it ONLY when an external source of truth makes conflicts meaningless (file re-sync), never to silence a conflict.\n10. **Search: prefer a few distinctive terms; heed `below confidence`.** When nothing clears the relevance threshold, `cerefox_search` returns the closest candidates prefixed with a `below confidence` warning instead of an empty set — that flag means **weak signal, not absent knowledge**: check the candidates\' scores and titles before concluding the KB lacks the content. A truly empty response means nothing even weakly related exists.\n11. **Relations express how documents relate; lifecycle tells you if knowledge is still good.** Use `cerefox_set_relation` when one document supersedes, contradicts, references, or continues another. `supersedes` marks the target **superseded**; `contradicts` marks **both** stale; `related_to`/`duplicates`/`contradicts` are symmetric (both directions written). Any other type string is accepted without special behaviour. When a search result or `cerefox_get_relations` shows a neighbour marked `[superseded]` or `[stale]`, say so rather than presenting it as current.\n12. **Project memberships — non-destructive by default**: on `cerefox_ingest` updates, **`project_name` (singular) is a non-destructive add** (ensures membership, preserves others). Use **`project_names` (list)** when you want to set the doc\'s full project set in one call (destructive replace). For metadata-only project changes without writing content, use **`cerefox_set_document_projects(document_id, project_names)`** — that tool is the destructive-replace contract made explicit. Never call `cerefox_set_document_projects` with a single name when you mean "add" — that would REMOVE the doc from all other projects. When in doubt, use `cerefox_ingest` with singular `project_name`.\n\n## Update Workflow (ID-based -- preferred)\n\n```\nsearch("topic") -> find doc [id: abc123] -> get_document(abc123) -> note its content_hash -> modify ->\ningest(title="Same Title", content="...", document_id="abc123",\n expected_content_hash="<the hash you read>", author="my-agent")\n```\n\nOn a **conflict** error: get_document again (fresh content + fresh hash) -> merge your changes -> retry with the new hash.\n\n## Update Workflow (title-based -- fallback)\n\n```\nsearch("topic") -> find doc (note its hash) -> modify ->\ningest(title="Same Title", content="...", update_if_exists=true,\n expected_content_hash="<the hash you read>", author="my-agent")\n```\n\n## Catch-Up Workflow\n\n```\nmetadata_search(metadata_filter={"type": "decision-log"}, updated_since="2026-03-28T00:00:00Z")\n```\n\n## CLI fallback (when MCP is unavailable)\n\nIf `cerefox_search` is not in your tool list, your user has likely installed the Cerefox CLI. The canonical invocation is plain **`cerefox <subcommand>`** (the TypeScript CLI, installed via `npm install -g @cerefox/memory`). It uses a resource-verb shape (`cerefox document get`, `cerefox project list`, …).\n\nSame operations, same conventions. Full reference: [`docs/guides/cli.md`](docs/guides/cli.md). CLI flag names match MCP parameter names exactly (e.g. `metadata_filter` ↔ `--metadata-filter`); common flags also have single-letter short forms (`-f`, `-p`, `-c`, `-m`, `-u`, `-a`, `-r`). Use the canonical long name (what `--help` shows) or its short form — there are no long-form aliases like `--filter` or `--count`.\n\n| MCP tool | CLI |\n|---|---|\n| `cerefox_search` | `cerefox search "<q>" --requestor "<your-name>"` |\n| `cerefox_ingest` (paste) | `printf \'...\' \\| cerefox document ingest --paste --title "<t>" --author "<your-name>" --author-type agent` |\n| `cerefox_ingest` (update by ID) | `printf \'...\' \\| cerefox document ingest --paste --title "<t>" --document-id "<uuid>" --expected-content-hash "<hash>" --author "<your-name>" --author-type agent` |\n| `cerefox_get_document` | `cerefox document get <id> --version-id <vid> --requestor "<your-name>"` |\n| `cerefox_list_versions` | `cerefox document version list <id> --requestor "<your-name>"` |\n| `cerefox_list_projects` | `cerefox project list --requestor "<your-name>"` |\n| `cerefox_list_metadata_keys` | `cerefox metadata keys` |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | `cerefox metadata search --metadata-filter \'<json>\' --requestor "<your-name>"` (list a project: `cerefox document list --project <name>`) |\n| `cerefox_set_document_metadata` | `cerefox document set-metadata <id> --set key=value` (also `--remove key`, `--json \'{...}\'`, `--replace`) |\n| `cerefox_set_document_projects` | `cerefox document set-projects <id> <name...> --author "<your-name>" --author-type agent` (or `--clear` to remove all) |\n| `cerefox_get_audit_log` | `cerefox audit list --requestor "<your-name>"` (add `--json` for scripted access) |\n| `cerefox_get_help` | `cerefox guides show agent-quick-reference` (or `cerefox guides list` for the full bundled-docs index) |\n\n**Set identity on every call**, exactly as you would on MCP:\n- Writes (`document ingest`, `document ingest-dir`): `--author "<your-name>" --author-type agent`\n- Reads: `--requestor "<your-name>"`\n\nOr have your user set `CEREFOX_AUTHOR_NAME` / `CEREFOX_AUTHOR_TYPE` / `CEREFOX_REQUESTOR_NAME` in their `.env` to apply defaults once.\n\n## Timestamps are UTC\n\nEvery timestamp Cerefox returns — `created_at` on audit entries, version\nhistory, document metadata — is **UTC**, and now carries its `Z` marker so it\ncannot be mistaken for local time.\n\n**When you write a date into a document\'s CONTENT, use your own clock, not a\nCerefox timestamp.** These are different things: a timestamp records when the\nserver stored something; a date in a log entry or a heading is authored content\nand belongs to your timezone. An agent working a Pacific afternoon read\n`2026-08-11` from version history, wrote "8/11" into its entries, and put a\nday\'s work in the future — the timestamp was correct, and copying it into\ncontent was not.\n\nCerefox deliberately does not convert to local time on the API or MCP paths.\n"Local" has no server-side meaning: the remote MCP server runs in a cloud\nfunction whose local time *is* UTC, while a local MCP server runs in yours, so\nthe same document would report two different times depending on transport. The\nweb UI converts because a browser knows the viewer\'s timezone; nothing\nserver-side does.\n\n## Mistakes that have actually happened\n\nEach of these comes from a real agent session, and each is easy to make.\n\n- **`cerefox_ingest` always replaces the ENTIRE document.** Never a section.\n Before sending, check that the tool name matches the intent: if the intent is\n "change one section", the call is `cerefox_edit` with `replace_section`. A\n section-sized edit sent as a full ingest truncated a 13,000-character index to\n a single word. It was recovered from version history within the minute, but\n only because it was noticed immediately.\n\n- **Do not include the anchor\'s own heading in your text.** `replace_section`\n keeps the heading and `insert` places your text inside the section, so\n including it produces two. This is now refused rather than silently applied,\n but the shape is worth knowing: it happened twice in one session, the second\n time while trying to repair the first. A *deeper* sub-heading inside your text\n is fine.\n\n- **Content between sections belongs to the section ABOVE it.** A section runs\n to the next heading of the same or higher level, so a `---` rule, a note, or\n any trailing text sitting just above the next heading is part of the section\n before it — even when it visually reads as belonging below. Replacing that\n section takes it too. An agent hit exactly this: a `---` that separated two\n major sections disappeared when the section above it was replaced. The write\n was correct by the addressing rules; the surprise is that "the end of this\n section" is further down the page than it looks. Note the loss warning will\n not catch it if your replacement text is longer than what it replaced, since\n there is then no net loss to report.\n\n- **To change only tags, use `cerefox_set_document_metadata`, never `cerefox_ingest`.**\n Ingest replaces the whole document, so re-sending it to set one tag carries the\n full transcription risk for no reason. The metadata tool merges: the keys you\n pass are set, everything else is left alone, so you do not need to read the\n document first and cannot drop a tag another agent set. Pass `null` as a value\n to remove a key.\n\n- **Never partial-edit to fix a partial edit.** If a write leaves unexpected\n structure, stop. Use `cerefox_list_versions`, retrieve the last good version,\n and re-ingest cleanly. Repairing edits with more edits compounds the damage.\n\n- **A rejected batch is safe.** Operations in one `cerefox_edit` are\n all-or-nothing: if any is invalid, nothing is written. A refusal costs you a\n retry, not data — so prefer one call for changes that belong together, and do\n not split a batch to "make it more likely to succeed".\n\n- **Read before replacing.** `cerefox_get_document(section: "## Heading")`\n returns exactly what a `replace_section` on that anchor would overwrite. Use it\n for any section you did not write in this session. The outline gives a\n section\'s *size*, never its *text*.\n\n- **Verify after writing** — read the result back before reporting success, and\n report what the read actually shows.\n\n- **Partial edits cannot change a document\'s stored TITLE.** `rename_section`\n changes a heading inside the content; the title is a separate field and still\n needs `cerefox_ingest`.\n\n- **If a capability seems missing from one server, suspect your client first.**\n Local and remote run the same code. **Every `cerefox_get_help()` response\n begins with the server\'s version and the operations it registers** — you do\n not need a special topic, and the *absence* of that block is itself an answer:\n a server that does not print it predates v1.5.0. If that\n disagrees with your tool list, the client is holding a list it fetched before\n an upgrade — clients cache it at connect time. Ask the user to restart the\n client. Do not record a capability difference between servers as a fact; every\n such report so far has been a stale client.\n', HELP_SECTIONS, HELP_SECTION_HEADINGS;
|
|
55161
55526
|
var init_get_help_content = __esm(() => {
|
|
55162
55527
|
HELP_SECTIONS = {
|
|
55163
|
-
Tools: "## Tools\n\n| Tool | Purpose | Key params |\n|------|---------|------------|\n| `cerefox_search` | Find documents (hybrid FTS + semantic) | `query` (required), `project_name`, `metadata_filter`, `requestor` |\n| `cerefox_ingest` | Save or update a document | `title`, `content` (required), `document_id` (update by ID), `expected_content_hash` (**required on content updates** — see rule 9), `last_write_wins`, `update_if_exists`, `project_name` (single, non-destructive add on update), `project_names` (list, destructive replace on update), `metadata` (omit on update to keep existing tags; `{}` clears), `author` |\n| `cerefox_insert` | **Add** to a document without resending it. Cannot destroy content. | `document_id`, `text`, `position` (`end_of_document`/`end_of_section`/`after_heading`/`before_heading`), `expected_content_hash` (required), `anchor_heading` (unless `end_of_document`), `section_part` |\n| `cerefox_edit` | **Change** parts of a document: 1..n operations applied atomically | `document_id`, `operations` (`insert`/`replace_section`/`delete_section`/`rename_section`), `expected_content_hash` (required) |\n| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token), or with `outline: true` just its heading paths, sizes and hash, or with `section: \"## Heading\"` one section's text | `document_id` (required), `outline`, `section`, `section_part` |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required) |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | Find or list docs by metadata, project, or time (no text query) | `metadata_filter`, `project_name` (list a project's docs), `updated_since`, `include_content` — **at least one** of metadata_filter/project_name/updated_since/created_since |\n| `cerefox_list_metadata_keys` | Discover available metadata keys | (none required) |\n| `cerefox_list_projects` | List all projects | (none required) |\n| `cerefox_set_document_projects` | Set doc's project memberships to exactly the given list (destructive replace; metadata-only, no content change) | `document_id`, `project_names` (required) |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `author`, `operation`, `since` |\n| `cerefox_get_help` | Retrieve Cerefox conventions (this reference) over MCP. **Call this whenever uncertain.** | `topic` (optional, case-insensitive H2 substring match) |\n\n⚑ **Opt-in — usually absent.** The four relation tools are hidden unless the\noperator enables them (`relations_enabled`). **Trust your own tool list**: if\nthey are not in it, the feature is switched off for this deployment. That is\nnormal, not an error, and not something to work around.",
|
|
55528
|
+
Tools: "## Tools\n\n| Tool | Purpose | Key params |\n|------|---------|------------|\n| `cerefox_search` | Find documents (hybrid FTS + semantic) | `query` (required), `project_name`, `metadata_filter`, `requestor` |\n| `cerefox_ingest` | Save or update a document | `title`, `content` (required), `document_id` (update by ID), `expected_content_hash` (**required on content updates** — see rule 9), `last_write_wins`, `update_if_exists`, `project_name` (single, non-destructive add on update), `project_names` (list, destructive replace on update), `metadata` (omit on update to keep existing tags; `{}` clears), `author` |\n| `cerefox_insert` | **Add** to a document without resending it. Cannot destroy content. | `document_id`, `text`, `position` (`end_of_document`/`end_of_section`/`after_heading`/`before_heading`), `expected_content_hash` (required), `anchor_heading` (unless `end_of_document`), `section_part` |\n| `cerefox_edit` | **Change** parts of a document: 1..n operations applied atomically | `document_id`, `operations` (`insert`/`replace_section`/`delete_section`/`rename_section`), `expected_content_hash` (required) |\n| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token), or with `outline: true` just its heading paths, sizes and hash, or with `section: \"## Heading\"` one section's text | `document_id` (required), `outline`, `section`, `section_part` |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required) |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | Find or list docs by metadata, project, or time (no text query) | `metadata_filter`, `project_name` (list a project's docs), `updated_since`, `include_content` — **at least one** of metadata_filter/project_name/updated_since/created_since |\n| `cerefox_list_metadata_keys` | Discover available metadata keys | (none required) |\n| `cerefox_list_projects` | List all projects | (none required) |\n| `cerefox_set_document_metadata` | Change tags WITHOUT resending content. **Merges** by default; a `null` value removes a key | `document_id`, `metadata` (required), `replace` (rare: set exactly this object), `author` |\n| `cerefox_set_document_projects` | Set doc's project memberships to exactly the given list (destructive replace; metadata-only, no content change) | `document_id`, `project_names` (required) |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `author`, `operation`, `since` |\n| `cerefox_get_help` | Retrieve Cerefox conventions (this reference) over MCP. **Call this whenever uncertain.** | `topic` (optional, case-insensitive H2 substring match) |\n\n⚑ **Opt-in — usually absent.** The four relation tools are hidden unless the\noperator enables them (`relations_enabled`). **Trust your own tool list**: if\nthey are not in it, the feature is switched off for this deployment. That is\nnormal, not an error, and not something to work around.",
|
|
55164
55529
|
"Editing part of a document (prefer this over re-sending)": '## Editing part of a document (prefer this over re-sending)\n\n**Re-sending a whole document to change part of it is the main way agents lose\ndata.** You have to reproduce the untouched remainder verbatim, and any drift\nsilently rewrites content nobody asked you to touch — which the caller cannot\ndiff. Use the partial-edit tools instead:\n\n1. **Learn the anchors** — `cerefox_get_document(document_id, outline: true)`.\n Returns heading paths, per-section sizes and the `content_hash`, without the\n body. The paths it returns are exactly what `anchor_heading` accepts.\n2. **Add** → `cerefox_insert`. `end_of_document` is a plain append;\n `end_of_section` adds inside a named section. It is structurally incapable of\n removing anything, so "I meant to append" cannot become "I replaced the file".\n3. **Look before you overwrite** — `cerefox_get_document(document_id,\n section: "## Heading")` returns exactly the text a `replace_section` on that\n anchor would destroy. The outline gives you a section\'s *size*, never its\n *text*, so on a document you did not write yourself this is the difference\n between a replace and a blind overwrite.\n4. **Change or remove** → `cerefox_edit`. Put changes that belong together in\n ONE call: they apply atomically, so a table row and the total it feeds cannot\n end up disagreeing. To change a single line, `replace_section` on its\n smallest enclosing heading — that is the intended granularity, not a\n workaround. To fix a stale heading (`## OPEN TODOs (as of ...)`), use\n `rename_section`: it changes the heading text and leaves the body and\n position alone.\n5. All of them require `expected_content_hash` and **have no last-write-wins**. A\n conflict means someone else changed the document; re-read and decide, do not\n force it.\n\n**A section runs to the next same-or-higher heading, or to the end of the\ndocument.** So `end_of_document` inserts land inside the *last* section, and\nreplacing or deleting that section removes them too. A large shrink in the\nresponse is your warning; `cerefox_list_versions` has the previous content.\n\n**When an anchor is ambiguous the tool refuses and hands you the options** — a\nrepeated heading returns the qualifying paths, and a section with both its own\ncontent and sub-sections returns both `section_part` choices. That is a\nrecoverable answer, not a failure: retry with what it gave you.',
|
|
55165
55530
|
"Essential Rules": '## Essential Rules\n\n1. **Search before ingesting** -- check if the document exists first.\n2. **Prefer ID-based updates** -- pass `document_id` from search results for deterministic updates. Falls back to title-matching with `update_if_exists: true`.\n3. **Set `author`/`requestor`** to your name on every call (e.g., "Claude Code", "archiver"). On MCP, pass as parameters. On CLI, pass `--author`/`--author-type`/`--requestor` flags, or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE`/`CEREFOX_REQUESTOR_NAME` env vars set in the user\'s `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **Add metadata** -- at minimum `type` ("decision-log", "research", "design-doc") and `status` ("active", "draft").\n6. **Write structured Markdown** with H1/H2/H3 headings for good chunking and search.\n7. **Deletes are soft (recoverable); purge is web-UI-only.** If you decide to delete, surface it to the user (`I soft-deleted X — recoverable from the Cerefox web UI trash`). You cannot un-do your own delete from agent code by design.\n8. **Cross-doc links inside content**: **always use `[Text](document-uuid)`.** UUIDs are the only fully reliable link form — stable across title changes, never ambiguous, no encoding gotchas. Every `cerefox_search` result shows `[id: <uuid>]` after the title; grab it and use it. Title-based linking (`[Text](<Title With Spaces>)`) is fragile (breaks on colons, parens, ampersands, brackets — silently navigates to wrong page) — **don\'t write title-based links**; do an extra search to get the UUID instead. Repo-path forms (`[Text](docs/path.md)`) exist for repo-ingested files; don\'t construct manually. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n9. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you last saw — every read shows one (`cerefox_get_document` incl. outline mode, `cerefox_search`, `cerefox_metadata_search`) and **every write returns the new one, including create** (v1.3.0, #189), so after writing you already hold the token for your next edit; no re-read needed. If it\'s stale you get a **conflict** — re-read the document, merge your changes into the latest content, retry with the new hash. **Never resolve a conflict by overwriting blindly** — the current content includes another writer\'s work. `last_write_wins: true` skips the check; use it ONLY when an external source of truth makes conflicts meaningless (file re-sync), never to silence a conflict.\n10. **Search: prefer a few distinctive terms; heed `below confidence`.** When nothing clears the relevance threshold, `cerefox_search` returns the closest candidates prefixed with a `below confidence` warning instead of an empty set — that flag means **weak signal, not absent knowledge**: check the candidates\' scores and titles before concluding the KB lacks the content. A truly empty response means nothing even weakly related exists.\n11. **Relations express how documents relate; lifecycle tells you if knowledge is still good.** Use `cerefox_set_relation` when one document supersedes, contradicts, references, or continues another. `supersedes` marks the target **superseded**; `contradicts` marks **both** stale; `related_to`/`duplicates`/`contradicts` are symmetric (both directions written). Any other type string is accepted without special behaviour. When a search result or `cerefox_get_relations` shows a neighbour marked `[superseded]` or `[stale]`, say so rather than presenting it as current.\n12. **Project memberships — non-destructive by default**: on `cerefox_ingest` updates, **`project_name` (singular) is a non-destructive add** (ensures membership, preserves others). Use **`project_names` (list)** when you want to set the doc\'s full project set in one call (destructive replace). For metadata-only project changes without writing content, use **`cerefox_set_document_projects(document_id, project_names)`** — that tool is the destructive-replace contract made explicit. Never call `cerefox_set_document_projects` with a single name when you mean "add" — that would REMOVE the doc from all other projects. When in doubt, use `cerefox_ingest` with singular `project_name`.',
|
|
55166
55531
|
"Update Workflow (ID-based -- preferred)": `## Update Workflow (ID-based -- preferred)
|
|
@@ -55174,12 +55539,119 @@ ingest(title="Same Title", content="...", document_id="abc123",
|
|
|
55174
55539
|
On a **conflict** error: get_document again (fresh content + fresh hash) -> merge your changes -> retry with the new hash.`,
|
|
55175
55540
|
"Update Workflow (title-based -- fallback)": '## Update Workflow (title-based -- fallback)\n\n```\nsearch("topic") -> find doc (note its hash) -> modify ->\ningest(title="Same Title", content="...", update_if_exists=true,\n expected_content_hash="<the hash you read>", author="my-agent")\n```',
|
|
55176
55541
|
"Catch-Up Workflow": '## Catch-Up Workflow\n\n```\nmetadata_search(metadata_filter={"type": "decision-log"}, updated_since="2026-03-28T00:00:00Z")\n```',
|
|
55177
|
-
"CLI fallback (when MCP is unavailable)": '## CLI fallback (when MCP is unavailable)\n\nIf `cerefox_search` is not in your tool list, your user has likely installed the Cerefox CLI. The canonical invocation is plain **`cerefox <subcommand>`** (the TypeScript CLI, installed via `npm install -g @cerefox/memory`). It uses a resource-verb shape (`cerefox document get`, `cerefox project list`, …).\n\nSame operations, same conventions. Full reference: [`docs/guides/cli.md`](docs/guides/cli.md). CLI flag names match MCP parameter names exactly (e.g. `metadata_filter` ↔ `--metadata-filter`); common flags also have single-letter short forms (`-f`, `-p`, `-c`, `-m`, `-u`, `-a`, `-r`). Use the canonical long name (what `--help` shows) or its short form — there are no long-form aliases like `--filter` or `--count`.\n\n| MCP tool | CLI |\n|---|---|\n| `cerefox_search` | `cerefox search "<q>" --requestor "<your-name>"` |\n| `cerefox_ingest` (paste) | `printf \'...\' \\| cerefox document ingest --paste --title "<t>" --author "<your-name>" --author-type agent` |\n| `cerefox_ingest` (update by ID) | `printf \'...\' \\| cerefox document ingest --paste --title "<t>" --document-id "<uuid>" --expected-content-hash "<hash>" --author "<your-name>" --author-type agent` |\n| `cerefox_get_document` | `cerefox document get <id> --version-id <vid> --requestor "<your-name>"` |\n| `cerefox_list_versions` | `cerefox document version list <id> --requestor "<your-name>"` |\n| `cerefox_list_projects` | `cerefox project list --requestor "<your-name>"` |\n| `cerefox_list_metadata_keys` | `cerefox metadata keys` |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | `cerefox metadata search --metadata-filter \'<json>\' --requestor "<your-name>"` (list a project: `cerefox document list --project <name>`) |\n| `cerefox_set_document_projects` | `cerefox document set-projects <id> <name...> --author "<your-name>" --author-type agent` (or `--clear` to remove all) |\n| `cerefox_get_audit_log` | `cerefox audit list --requestor "<your-name>"` (add `--json` for scripted access) |\n| `cerefox_get_help` | `cerefox guides show agent-quick-reference` (or `cerefox guides list` for the full bundled-docs index) |\n\n**Set identity on every call**, exactly as you would on MCP:\n- Writes (`document ingest`, `document ingest-dir`): `--author "<your-name>" --author-type agent`\n- Reads: `--requestor "<your-name>"`\n\nOr have your user set `CEREFOX_AUTHOR_NAME` / `CEREFOX_AUTHOR_TYPE` / `CEREFOX_REQUESTOR_NAME` in their `.env` to apply defaults once.'
|
|
55178
|
-
|
|
55179
|
-
|
|
55542
|
+
"CLI fallback (when MCP is unavailable)": '## CLI fallback (when MCP is unavailable)\n\nIf `cerefox_search` is not in your tool list, your user has likely installed the Cerefox CLI. The canonical invocation is plain **`cerefox <subcommand>`** (the TypeScript CLI, installed via `npm install -g @cerefox/memory`). It uses a resource-verb shape (`cerefox document get`, `cerefox project list`, …).\n\nSame operations, same conventions. Full reference: [`docs/guides/cli.md`](docs/guides/cli.md). CLI flag names match MCP parameter names exactly (e.g. `metadata_filter` ↔ `--metadata-filter`); common flags also have single-letter short forms (`-f`, `-p`, `-c`, `-m`, `-u`, `-a`, `-r`). Use the canonical long name (what `--help` shows) or its short form — there are no long-form aliases like `--filter` or `--count`.\n\n| MCP tool | CLI |\n|---|---|\n| `cerefox_search` | `cerefox search "<q>" --requestor "<your-name>"` |\n| `cerefox_ingest` (paste) | `printf \'...\' \\| cerefox document ingest --paste --title "<t>" --author "<your-name>" --author-type agent` |\n| `cerefox_ingest` (update by ID) | `printf \'...\' \\| cerefox document ingest --paste --title "<t>" --document-id "<uuid>" --expected-content-hash "<hash>" --author "<your-name>" --author-type agent` |\n| `cerefox_get_document` | `cerefox document get <id> --version-id <vid> --requestor "<your-name>"` |\n| `cerefox_list_versions` | `cerefox document version list <id> --requestor "<your-name>"` |\n| `cerefox_list_projects` | `cerefox project list --requestor "<your-name>"` |\n| `cerefox_list_metadata_keys` | `cerefox metadata keys` |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | `cerefox metadata search --metadata-filter \'<json>\' --requestor "<your-name>"` (list a project: `cerefox document list --project <name>`) |\n| `cerefox_set_document_metadata` | `cerefox document set-metadata <id> --set key=value` (also `--remove key`, `--json \'{...}\'`, `--replace`) |\n| `cerefox_set_document_projects` | `cerefox document set-projects <id> <name...> --author "<your-name>" --author-type agent` (or `--clear` to remove all) |\n| `cerefox_get_audit_log` | `cerefox audit list --requestor "<your-name>"` (add `--json` for scripted access) |\n| `cerefox_get_help` | `cerefox guides show agent-quick-reference` (or `cerefox guides list` for the full bundled-docs index) |\n\n**Set identity on every call**, exactly as you would on MCP:\n- Writes (`document ingest`, `document ingest-dir`): `--author "<your-name>" --author-type agent`\n- Reads: `--requestor "<your-name>"`\n\nOr have your user set `CEREFOX_AUTHOR_NAME` / `CEREFOX_AUTHOR_TYPE` / `CEREFOX_REQUESTOR_NAME` in their `.env` to apply defaults once.',
|
|
55543
|
+
"Timestamps are UTC": `## Timestamps are UTC
|
|
55544
|
+
|
|
55545
|
+
Every timestamp Cerefox returns — \`created_at\` on audit entries, version
|
|
55546
|
+
history, document metadata — is **UTC**, and now carries its \`Z\` marker so it
|
|
55547
|
+
cannot be mistaken for local time.
|
|
55548
|
+
|
|
55549
|
+
**When you write a date into a document's CONTENT, use your own clock, not a
|
|
55550
|
+
Cerefox timestamp.** These are different things: a timestamp records when the
|
|
55551
|
+
server stored something; a date in a log entry or a heading is authored content
|
|
55552
|
+
and belongs to your timezone. An agent working a Pacific afternoon read
|
|
55553
|
+
\`2026-08-11\` from version history, wrote "8/11" into its entries, and put a
|
|
55554
|
+
day's work in the future — the timestamp was correct, and copying it into
|
|
55555
|
+
content was not.
|
|
55556
|
+
|
|
55557
|
+
Cerefox deliberately does not convert to local time on the API or MCP paths.
|
|
55558
|
+
"Local" has no server-side meaning: the remote MCP server runs in a cloud
|
|
55559
|
+
function whose local time *is* UTC, while a local MCP server runs in yours, so
|
|
55560
|
+
the same document would report two different times depending on transport. The
|
|
55561
|
+
web UI converts because a browser knows the viewer's timezone; nothing
|
|
55562
|
+
server-side does.`,
|
|
55563
|
+
"Mistakes that have actually happened": `## Mistakes that have actually happened
|
|
55564
|
+
|
|
55565
|
+
Each of these comes from a real agent session, and each is easy to make.
|
|
55566
|
+
|
|
55567
|
+
- **\`cerefox_ingest\` always replaces the ENTIRE document.** Never a section.
|
|
55568
|
+
Before sending, check that the tool name matches the intent: if the intent is
|
|
55569
|
+
"change one section", the call is \`cerefox_edit\` with \`replace_section\`. A
|
|
55570
|
+
section-sized edit sent as a full ingest truncated a 13,000-character index to
|
|
55571
|
+
a single word. It was recovered from version history within the minute, but
|
|
55572
|
+
only because it was noticed immediately.
|
|
55573
|
+
|
|
55574
|
+
- **Do not include the anchor's own heading in your text.** \`replace_section\`
|
|
55575
|
+
keeps the heading and \`insert\` places your text inside the section, so
|
|
55576
|
+
including it produces two. This is now refused rather than silently applied,
|
|
55577
|
+
but the shape is worth knowing: it happened twice in one session, the second
|
|
55578
|
+
time while trying to repair the first. A *deeper* sub-heading inside your text
|
|
55579
|
+
is fine.
|
|
55580
|
+
|
|
55581
|
+
- **Content between sections belongs to the section ABOVE it.** A section runs
|
|
55582
|
+
to the next heading of the same or higher level, so a \`---\` rule, a note, or
|
|
55583
|
+
any trailing text sitting just above the next heading is part of the section
|
|
55584
|
+
before it — even when it visually reads as belonging below. Replacing that
|
|
55585
|
+
section takes it too. An agent hit exactly this: a \`---\` that separated two
|
|
55586
|
+
major sections disappeared when the section above it was replaced. The write
|
|
55587
|
+
was correct by the addressing rules; the surprise is that "the end of this
|
|
55588
|
+
section" is further down the page than it looks. Note the loss warning will
|
|
55589
|
+
not catch it if your replacement text is longer than what it replaced, since
|
|
55590
|
+
there is then no net loss to report.
|
|
55591
|
+
|
|
55592
|
+
- **To change only tags, use \`cerefox_set_document_metadata\`, never \`cerefox_ingest\`.**
|
|
55593
|
+
Ingest replaces the whole document, so re-sending it to set one tag carries the
|
|
55594
|
+
full transcription risk for no reason. The metadata tool merges: the keys you
|
|
55595
|
+
pass are set, everything else is left alone, so you do not need to read the
|
|
55596
|
+
document first and cannot drop a tag another agent set. Pass \`null\` as a value
|
|
55597
|
+
to remove a key.
|
|
55598
|
+
|
|
55599
|
+
- **Never partial-edit to fix a partial edit.** If a write leaves unexpected
|
|
55600
|
+
structure, stop. Use \`cerefox_list_versions\`, retrieve the last good version,
|
|
55601
|
+
and re-ingest cleanly. Repairing edits with more edits compounds the damage.
|
|
55602
|
+
|
|
55603
|
+
- **A rejected batch is safe.** Operations in one \`cerefox_edit\` are
|
|
55604
|
+
all-or-nothing: if any is invalid, nothing is written. A refusal costs you a
|
|
55605
|
+
retry, not data — so prefer one call for changes that belong together, and do
|
|
55606
|
+
not split a batch to "make it more likely to succeed".
|
|
55607
|
+
|
|
55608
|
+
- **Read before replacing.** \`cerefox_get_document(section: "## Heading")\`
|
|
55609
|
+
returns exactly what a \`replace_section\` on that anchor would overwrite. Use it
|
|
55610
|
+
for any section you did not write in this session. The outline gives a
|
|
55611
|
+
section's *size*, never its *text*.
|
|
55612
|
+
|
|
55613
|
+
- **Verify after writing** — read the result back before reporting success, and
|
|
55614
|
+
report what the read actually shows.
|
|
55615
|
+
|
|
55616
|
+
- **Partial edits cannot change a document's stored TITLE.** \`rename_section\`
|
|
55617
|
+
changes a heading inside the content; the title is a separate field and still
|
|
55618
|
+
needs \`cerefox_ingest\`.
|
|
55619
|
+
|
|
55620
|
+
- **If a capability seems missing from one server, suspect your client first.**
|
|
55621
|
+
Local and remote run the same code. **Every \`cerefox_get_help()\` response
|
|
55622
|
+
begins with the server's version and the operations it registers** — you do
|
|
55623
|
+
not need a special topic, and the *absence* of that block is itself an answer:
|
|
55624
|
+
a server that does not print it predates v1.5.0. If that
|
|
55625
|
+
disagrees with your tool list, the client is holding a list it fetched before
|
|
55626
|
+
an upgrade — clients cache it at connect time. Ask the user to restart the
|
|
55627
|
+
client. Do not record a capability difference between servers as a fact; every
|
|
55628
|
+
such report so far has been a stale client.`
|
|
55629
|
+
};
|
|
55630
|
+
HELP_SECTION_HEADINGS = ["Tools", "Editing part of a document (prefer this over re-sending)", "Essential Rules", "Update Workflow (ID-based -- preferred)", "Update Workflow (title-based -- fallback)", "Catch-Up Workflow", "CLI fallback (when MCP is unavailable)", "Timestamps are UTC", "Mistakes that have actually happened"];
|
|
55180
55631
|
});
|
|
55181
55632
|
|
|
55182
55633
|
// ../../_shared/mcp-tools/get-help.ts
|
|
55634
|
+
function editOperations() {
|
|
55635
|
+
const schema = editTool.inputSchema;
|
|
55636
|
+
return schema.properties?.operations?.items?.properties?.op?.enum ?? [];
|
|
55637
|
+
}
|
|
55638
|
+
function serverIdentity() {
|
|
55639
|
+
return [
|
|
55640
|
+
"## This server",
|
|
55641
|
+
"",
|
|
55642
|
+
`- **Version**: ${CEREFOX_VERSION}`,
|
|
55643
|
+
`- **cerefox_edit operations**: ${editOperations().join(", ")}`,
|
|
55644
|
+
"",
|
|
55645
|
+
"**If your tool list disagrees with this block, your CLIENT is out of date, not the server.**",
|
|
55646
|
+
"MCP clients fetch the tool list once when they connect and cache it, so a server",
|
|
55647
|
+
"upgraded mid-session is invisible until the client reconnects. Ask the user to restart",
|
|
55648
|
+
"the client — and if it stays missing after a restart, the client config may pin an old",
|
|
55649
|
+
"version of the package. Do not record a capability difference between the local and",
|
|
55650
|
+
"remote servers: they run the same code, and every such report so far has been a stale",
|
|
55651
|
+
"client."
|
|
55652
|
+
].join(`
|
|
55653
|
+
`);
|
|
55654
|
+
}
|
|
55183
55655
|
async function handler3(supabase, args, ctx) {
|
|
55184
55656
|
const topic = args.topic?.trim();
|
|
55185
55657
|
logUsage(supabase, {
|
|
@@ -55192,7 +55664,11 @@ async function handler3(supabase, args, ctx) {
|
|
|
55192
55664
|
if (!topic) {
|
|
55193
55665
|
const idx = HELP_SECTION_HEADINGS.map((h) => ` - ${h}`).join(`
|
|
55194
55666
|
`);
|
|
55195
|
-
return
|
|
55667
|
+
return serverIdentity() + `
|
|
55668
|
+
|
|
55669
|
+
---
|
|
55670
|
+
|
|
55671
|
+
` + HELP_FULL + `
|
|
55196
55672
|
|
|
55197
55673
|
---
|
|
55198
55674
|
|
|
@@ -55202,16 +55678,26 @@ async function handler3(supabase, args, ctx) {
|
|
|
55202
55678
|
|
|
55203
55679
|
(Topic match is case-insensitive substring on the headings above.)`;
|
|
55204
55680
|
}
|
|
55681
|
+
if (/^(server|version|stale|client)$/i.test(topic))
|
|
55682
|
+
return serverIdentity();
|
|
55205
55683
|
const t = topic.toLowerCase();
|
|
55206
55684
|
const matched = HELP_SECTION_HEADINGS.filter((h) => h.toLowerCase().includes(t));
|
|
55207
55685
|
if (matched.length === 0) {
|
|
55208
|
-
return
|
|
55686
|
+
return serverIdentity() + `
|
|
55687
|
+
|
|
55688
|
+
---
|
|
55689
|
+
|
|
55690
|
+
` + `No help topic matched "${topic}".
|
|
55209
55691
|
|
|
55210
55692
|
` + `Available topics:
|
|
55211
55693
|
` + HELP_SECTION_HEADINGS.map((h) => ` - ${h}`).join(`
|
|
55212
55694
|
`) + "\n\nCall `cerefox_get_help()` with no topic for the full document.";
|
|
55213
55695
|
}
|
|
55214
|
-
return
|
|
55696
|
+
return serverIdentity() + `
|
|
55697
|
+
|
|
55698
|
+
---
|
|
55699
|
+
|
|
55700
|
+
` + matched.map((h) => HELP_SECTIONS[h]).join(`
|
|
55215
55701
|
|
|
55216
55702
|
---
|
|
55217
55703
|
|
|
@@ -55219,6 +55705,8 @@ async function handler3(supabase, args, ctx) {
|
|
|
55219
55705
|
}
|
|
55220
55706
|
var getHelpTool;
|
|
55221
55707
|
var init_get_help = __esm(() => {
|
|
55708
|
+
init_ef_meta();
|
|
55709
|
+
init_partial_edits2();
|
|
55222
55710
|
init_get_help_content();
|
|
55223
55711
|
getHelpTool = {
|
|
55224
55712
|
name: "cerefox_get_help",
|
|
@@ -55246,30 +55734,15 @@ var init_get_help = __esm(() => {
|
|
|
55246
55734
|
};
|
|
55247
55735
|
});
|
|
55248
55736
|
|
|
55249
|
-
// ../../_shared/mcp-tools/_chunker.ts
|
|
55250
|
-
function normalizeContent(text) {
|
|
55251
|
-
return text.trim().replace(/\r\n/g, `
|
|
55252
|
-
`).replace(/\r/g, `
|
|
55253
|
-
`).replace(/\n{3,}/g, `
|
|
55254
|
-
|
|
55255
|
-
`);
|
|
55256
|
-
}
|
|
55257
|
-
async function sha256hex(text) {
|
|
55258
|
-
const bytes = new TextEncoder().encode(text);
|
|
55259
|
-
const hash = await crypto.subtle.digest("SHA-256", bytes);
|
|
55260
|
-
return Array.from(new Uint8Array(hash)).map((b2) => b2.toString(16).padStart(2, "0")).join("");
|
|
55261
|
-
}
|
|
55262
|
-
var init__chunker = () => {};
|
|
55263
|
-
|
|
55264
55737
|
// ../../_shared/mcp-tools/ingest.ts
|
|
55265
|
-
function
|
|
55738
|
+
function conflictError2(documentId, expectedHash, currentHash) {
|
|
55266
55739
|
return new Error(`Conflict: document ${documentId} changed since you read it ` + `(your base hash: ${expectedHash}, current hash: ${currentHash}). ` + `To resolve: (1) cerefox_get_document("${documentId}") to fetch the latest content ` + `and its content_hash, (2) merge your changes into it, (3) retry cerefox_ingest ` + `with expected_content_hash set to the new hash. Do not overwrite blindly — ` + `the current content may include another writer's work.`);
|
|
55267
55740
|
}
|
|
55268
55741
|
function mapIngestRpcError(message, documentId) {
|
|
55269
55742
|
if (message.includes("CEREFOX_CONFLICT")) {
|
|
55270
55743
|
const current = message.match(/current hash ([0-9a-f]{64})/)?.[1] ?? "unknown";
|
|
55271
55744
|
const expected = message.match(/expected hash ([0-9a-f]{64})/)?.[1] ?? "unknown";
|
|
55272
|
-
return
|
|
55745
|
+
return conflictError2(documentId, expected, current);
|
|
55273
55746
|
}
|
|
55274
55747
|
if (message.includes("CEREFOX_TOKEN_REQUIRED")) {
|
|
55275
55748
|
const current = message.match(/Current hash: ([0-9a-f]{64})/)?.[1];
|
|
@@ -55316,7 +55789,7 @@ async function handler4(supabase, args, ctx) {
|
|
|
55316
55789
|
return `Document already up-to-date: "${existingDoc.title}" (id: ${existingDoc.id}). Content hash unchanged (${contentHash2}).${note2}`;
|
|
55317
55790
|
}
|
|
55318
55791
|
if (!last_write_wins && expected_content_hash && expected_content_hash !== existingDoc.content_hash) {
|
|
55319
|
-
throw
|
|
55792
|
+
throw conflictError2(existingDoc.id, expected_content_hash, existingDoc.content_hash);
|
|
55320
55793
|
}
|
|
55321
55794
|
const chunks2 = chunkMarkdown(content);
|
|
55322
55795
|
if (chunks2.length === 0)
|
|
@@ -55374,7 +55847,7 @@ async function handler4(supabase, args, ctx) {
|
|
|
55374
55847
|
return `Document already up-to-date: "${existingDoc.title}" (id: ${existingDoc.id}). Content hash unchanged (${contentHash2}).`;
|
|
55375
55848
|
}
|
|
55376
55849
|
if (!last_write_wins && expected_content_hash && expected_content_hash !== existingDoc.content_hash) {
|
|
55377
|
-
throw
|
|
55850
|
+
throw conflictError2(existingDoc.id, expected_content_hash, existingDoc.content_hash);
|
|
55378
55851
|
}
|
|
55379
55852
|
const chunks2 = chunkMarkdown(content);
|
|
55380
55853
|
if (chunks2.length === 0)
|
|
@@ -55533,326 +56006,6 @@ var init_ingest = __esm(() => {
|
|
|
55533
56006
|
};
|
|
55534
56007
|
});
|
|
55535
56008
|
|
|
55536
|
-
// ../../_shared/mcp-tools/partial-edits.ts
|
|
55537
|
-
function resolveAuthorType2(ctx, args) {
|
|
55538
|
-
if (ctx.accessPath !== "cli")
|
|
55539
|
-
return "agent";
|
|
55540
|
-
return args.author_type === "agent" ? "agent" : "user";
|
|
55541
|
-
}
|
|
55542
|
-
function defaultRequestor(ctx) {
|
|
55543
|
-
return ctx.accessPath === "cli" ? "cli-user" : "mcp-agent";
|
|
55544
|
-
}
|
|
55545
|
-
function touchedTrailingSection(applied) {
|
|
55546
|
-
return applied.some((a) => a.reachedEnd === true);
|
|
55547
|
-
}
|
|
55548
|
-
function shrinkNote(before, afterChars, applied) {
|
|
55549
|
-
const beforeChars = [...before].length;
|
|
55550
|
-
const lost = beforeChars - afterChars;
|
|
55551
|
-
if (lost <= 0)
|
|
55552
|
-
return "";
|
|
55553
|
-
const pct = Math.round(lost / Math.max(beforeChars, 1) * 100);
|
|
55554
|
-
const trailing = touchedTrailingSection(applied);
|
|
55555
|
-
if (pct < 25 && !trailing) {
|
|
55556
|
-
return `This edit removed ${lost} characters. cerefox_list_versions has the previous content.
|
|
55557
|
-
`;
|
|
55558
|
-
}
|
|
55559
|
-
const why = trailing ? `You replaced or deleted the LAST section, and a section runs to the next ` + `heading of the same or higher level — or to the end of the document — so ` + `anything appended after it was inside it. ` : `If you did not intend that, note that a section runs to the next heading ` + `of the same or higher level — or to the end of the document — so replacing ` + `or deleting the LAST section also removes anything appended after it. `;
|
|
55560
|
-
return `⚠ This edit removed ${lost} characters (${pct}% smaller). ${why}cerefox_list_versions has the previous content.
|
|
55561
|
-
`;
|
|
55562
|
-
}
|
|
55563
|
-
function conflictError2(documentId, expectedHash, currentHash) {
|
|
55564
|
-
return new Error(`Conflict: document ${documentId} changed since you read it ` + `(your base hash: ${expectedHash}, current hash: ${currentHash}). No write was performed. ` + `To resolve: (1) cerefox_get_document("${documentId}", outline=true) to see the current ` + `structure and hash cheaply, or a full read if you need the text, (2) decide whether your ` + `edit still applies — another writer may have already made it, or made something it ` + `contradicts, (3) retry with expected_content_hash set to the current hash. ` + `These tools have no last-write-wins: the other writer's work is not yours to discard.`);
|
|
55565
|
-
}
|
|
55566
|
-
async function readDocument(supabase, documentId) {
|
|
55567
|
-
const { data, error: error2 } = await supabase.rpc("cerefox_get_document", {
|
|
55568
|
-
p_document_id: documentId,
|
|
55569
|
-
p_version_id: null
|
|
55570
|
-
});
|
|
55571
|
-
if (error2)
|
|
55572
|
-
throw new Error(`Could not read document: ${error2.message}`);
|
|
55573
|
-
const row = data?.[0] ?? undefined;
|
|
55574
|
-
if (!row || row.full_content === undefined) {
|
|
55575
|
-
throw new McpInvalidParams(`Document not found: ${documentId}. Partial edits apply to an existing document; ` + `use cerefox_ingest to create one.`);
|
|
55576
|
-
}
|
|
55577
|
-
return {
|
|
55578
|
-
title: row.doc_title ?? "Untitled",
|
|
55579
|
-
content: row.full_content,
|
|
55580
|
-
hash: row.content_hash ?? ""
|
|
55581
|
-
};
|
|
55582
|
-
}
|
|
55583
|
-
async function applyAndWrite(supabase, ctx, args) {
|
|
55584
|
-
const { documentId, operations, expectedHash, requestor, toolLabel, authorType } = args;
|
|
55585
|
-
if (!ctx.openaiApiKey && resolveEmbedderKind() !== "local") {
|
|
55586
|
-
throw new Error("OpenAI API key not configured. Set OPENAI_API_KEY (Edge Function) or CEREFOX_OPENAI_API_KEY (.env, local).");
|
|
55587
|
-
}
|
|
55588
|
-
const doc = await readDocument(supabase, documentId);
|
|
55589
|
-
if (doc.hash && expectedHash !== doc.hash) {
|
|
55590
|
-
throw conflictError2(documentId, expectedHash, doc.hash);
|
|
55591
|
-
}
|
|
55592
|
-
let assembled;
|
|
55593
|
-
let applied;
|
|
55594
|
-
try {
|
|
55595
|
-
const result = applyOperations(doc.content, operations);
|
|
55596
|
-
assembled = result.content;
|
|
55597
|
-
applied = result.applied;
|
|
55598
|
-
} catch (err) {
|
|
55599
|
-
throw new McpInvalidParams(err instanceof Error ? err.message : String(err));
|
|
55600
|
-
}
|
|
55601
|
-
if (assembled === doc.content) {
|
|
55602
|
-
return `No change: the ${toolLabel} produced content identical to the current document ` + `"${doc.title}" (id: ${documentId}). content_hash: ${doc.hash} (unchanged).`;
|
|
55603
|
-
}
|
|
55604
|
-
const newHash = await sha256hex(normalizeContent(assembled));
|
|
55605
|
-
const chunks = chunkMarkdown(assembled);
|
|
55606
|
-
if (chunks.length === 0) {
|
|
55607
|
-
throw new McpInvalidParams("The result of this edit produced no chunks (the document would be empty). " + "To remove a document use cerefox_ingest or the delete path, not a partial edit.");
|
|
55608
|
-
}
|
|
55609
|
-
const texts = chunks.map((c2) => embeddingInputFor(doc.title, c2));
|
|
55610
|
-
const embeddings = await embedBatch(texts, ctx.openaiApiKey ?? "");
|
|
55611
|
-
const totalChars = chunks.reduce((s, c2) => s + c2.char_count, 0);
|
|
55612
|
-
const chunkData = chunks.map((chunk, i) => ({
|
|
55613
|
-
chunk_index: i,
|
|
55614
|
-
heading_path: chunk.heading_path,
|
|
55615
|
-
heading_level: chunk.heading_level,
|
|
55616
|
-
title: chunk.title,
|
|
55617
|
-
content: chunk.content,
|
|
55618
|
-
char_count: chunk.char_count,
|
|
55619
|
-
embedding: embeddings[i],
|
|
55620
|
-
embedder: activeEmbedderName()
|
|
55621
|
-
}));
|
|
55622
|
-
const { data, error: error2 } = await supabase.rpc("cerefox_ingest_document", {
|
|
55623
|
-
p_document_id: documentId,
|
|
55624
|
-
p_title: doc.title,
|
|
55625
|
-
p_source: null,
|
|
55626
|
-
p_content_hash: newHash,
|
|
55627
|
-
p_metadata: null,
|
|
55628
|
-
p_review_status: authorType === "agent" ? "pending_review" : "approved",
|
|
55629
|
-
p_chunks: chunkData,
|
|
55630
|
-
p_author: requestor,
|
|
55631
|
-
p_author_type: authorType,
|
|
55632
|
-
p_source_label: authorType === "user" ? "manual" : "agent",
|
|
55633
|
-
p_expected_content_hash: expectedHash,
|
|
55634
|
-
p_last_write_wins: false,
|
|
55635
|
-
p_content_format: CONTENT_FORMAT_BLIND_STITCH,
|
|
55636
|
-
p_operations: applied.map((a) => ({ op: AUDIT_OP[a.op], detail: a.detail }))
|
|
55637
|
-
});
|
|
55638
|
-
if (error2) {
|
|
55639
|
-
const message = error2.message ?? "";
|
|
55640
|
-
if (message.includes("CEREFOX_CONFLICT")) {
|
|
55641
|
-
const current = message.match(/current hash ([0-9a-f]{64})/)?.[1] ?? "unknown";
|
|
55642
|
-
throw conflictError2(documentId, expectedHash, current);
|
|
55643
|
-
}
|
|
55644
|
-
if (message.includes("cerefox_documents_hash_unique")) {
|
|
55645
|
-
throw new Error(`This edit would make "${doc.title}" (id: ${documentId}) byte-identical to another ` + `document in the store, and content must be unique. No write was performed. ` + `Usually this means the two documents have converged and one should be removed or ` + `merged, or that this edit was already applied to the other one — ` + `cerefox_search for the resulting content to find it.`);
|
|
55646
|
-
}
|
|
55647
|
-
if (message.includes("does not exist") && message.includes("cerefox_ingest_document")) {
|
|
55648
|
-
throw new Error(`This server is behind: partial edits need schema 0.11.0 or newer. ` + `Run \`cerefox server deploy\`, then retry. (${message})`);
|
|
55649
|
-
}
|
|
55650
|
-
throw new Error(`Edit failed: ${message}`);
|
|
55651
|
-
}
|
|
55652
|
-
const row = data?.[0] ?? undefined;
|
|
55653
|
-
logUsage(supabase, {
|
|
55654
|
-
operation: toolLabel,
|
|
55655
|
-
accessPath: ctx.accessPath,
|
|
55656
|
-
requestor,
|
|
55657
|
-
document_id: documentId,
|
|
55658
|
-
result_count: applied.length
|
|
55659
|
-
});
|
|
55660
|
-
const summary = applied.map((a, i) => ` ${i + 1}. ${a.detail} → ${a.path}`).join(`
|
|
55661
|
-
`);
|
|
55662
|
-
const warning2 = row?.size_warning ? `
|
|
55663
|
-
|
|
55664
|
-
⚠ This document has passed the configured size threshold ` + `(document_size_warning_chars). Consider splitting it.` : "";
|
|
55665
|
-
return `Applied ${applied.length} operation(s) to "${doc.title}" (id: ${documentId}):
|
|
55666
|
-
${summary}
|
|
55667
|
-
|
|
55668
|
-
` + `New content_hash: ${row?.content_hash ?? newHash}
|
|
55669
|
-
` + `Size: ${row?.total_chars ?? totalChars} chars (was ${doc.content.length}), ${chunks.length} chunk(s).
|
|
55670
|
-
` + shrinkNote(doc.content, row?.total_chars ?? totalChars, applied) + `Pass the new content_hash as expected_content_hash on your next edit.${warning2}`;
|
|
55671
|
-
}
|
|
55672
|
-
async function insertHandler(supabase, args, ctx) {
|
|
55673
|
-
const documentId = args.document_id?.trim();
|
|
55674
|
-
const text = args.text;
|
|
55675
|
-
const position = args.position;
|
|
55676
|
-
const expectedHash = args.expected_content_hash?.trim();
|
|
55677
|
-
if (!documentId)
|
|
55678
|
-
throw new McpInvalidParams("document_id is required");
|
|
55679
|
-
if (!text?.trim())
|
|
55680
|
-
throw new McpInvalidParams("text is required and cannot be empty");
|
|
55681
|
-
if (!expectedHash) {
|
|
55682
|
-
throw new McpInvalidParams("expected_content_hash is required. It is the content_hash of the version you are " + "basing this insert on — returned by cerefox_get_document (including outline mode), " + "cerefox_search, cerefox_metadata_search, and by every write. There is no " + "last-write-wins here: knowing the document changed under you is the point.");
|
|
55683
|
-
}
|
|
55684
|
-
const operations = validateOperations([
|
|
55685
|
-
{
|
|
55686
|
-
op: "insert",
|
|
55687
|
-
text,
|
|
55688
|
-
position,
|
|
55689
|
-
...args.anchor_heading !== undefined ? { anchor_heading: args.anchor_heading } : {},
|
|
55690
|
-
...args.section_part !== undefined ? { section_part: args.section_part } : {}
|
|
55691
|
-
}
|
|
55692
|
-
]);
|
|
55693
|
-
return applyAndWrite(supabase, ctx, {
|
|
55694
|
-
documentId,
|
|
55695
|
-
operations,
|
|
55696
|
-
expectedHash,
|
|
55697
|
-
requestor: args.requestor ?? defaultRequestor(ctx),
|
|
55698
|
-
toolLabel: "insert",
|
|
55699
|
-
authorType: resolveAuthorType2(ctx, args)
|
|
55700
|
-
});
|
|
55701
|
-
}
|
|
55702
|
-
async function editHandler(supabase, args, ctx) {
|
|
55703
|
-
const documentId = args.document_id?.trim();
|
|
55704
|
-
const expectedHash = args.expected_content_hash?.trim();
|
|
55705
|
-
if (!documentId)
|
|
55706
|
-
throw new McpInvalidParams("document_id is required");
|
|
55707
|
-
if (!expectedHash) {
|
|
55708
|
-
throw new McpInvalidParams("expected_content_hash is required. It is the content_hash of the version you are " + "basing these edits on — returned by cerefox_get_document (including outline mode), " + "cerefox_search, cerefox_metadata_search, and by every write.");
|
|
55709
|
-
}
|
|
55710
|
-
let operations;
|
|
55711
|
-
try {
|
|
55712
|
-
operations = validateOperations(args.operations);
|
|
55713
|
-
} catch (err) {
|
|
55714
|
-
throw new McpInvalidParams(err instanceof Error ? err.message : String(err));
|
|
55715
|
-
}
|
|
55716
|
-
return applyAndWrite(supabase, ctx, {
|
|
55717
|
-
documentId,
|
|
55718
|
-
operations,
|
|
55719
|
-
expectedHash,
|
|
55720
|
-
requestor: args.requestor ?? defaultRequestor(ctx),
|
|
55721
|
-
toolLabel: "edit",
|
|
55722
|
-
authorType: resolveAuthorType2(ctx, args)
|
|
55723
|
-
});
|
|
55724
|
-
}
|
|
55725
|
-
var AUDIT_OP, insertTool, editTool;
|
|
55726
|
-
var init_partial_edits2 = __esm(() => {
|
|
55727
|
-
init_partial_edits();
|
|
55728
|
-
init__chunker();
|
|
55729
|
-
init_types3();
|
|
55730
|
-
AUDIT_OP = {
|
|
55731
|
-
insert: "insert",
|
|
55732
|
-
replace_section: "replace-section",
|
|
55733
|
-
delete_section: "delete-section",
|
|
55734
|
-
rename_section: "rename-section"
|
|
55735
|
-
};
|
|
55736
|
-
insertTool = {
|
|
55737
|
-
name: "cerefox_insert",
|
|
55738
|
-
description: "Add text to a document without resending the whole thing. Purely additive: it cannot " + "remove or overwrite existing content, so it is the safe way to append. Positions: " + "end_of_document (a plain append), end_of_section (add to the end of a section's body — " + "the most common mid-document add), after_heading (lead-in text), before_heading (a new " + "block above a section). Anchors are the exact heading line ('## Intake') or a parent path " + "('## Intake > ### Notes') when a heading appears more than once. Requires " + "expected_content_hash; returns the new hash, not the document.",
|
|
55739
|
-
annotations: {
|
|
55740
|
-
title: "Insert into document",
|
|
55741
|
-
readOnlyHint: false,
|
|
55742
|
-
destructiveHint: false,
|
|
55743
|
-
idempotentHint: false,
|
|
55744
|
-
openWorldHint: false
|
|
55745
|
-
},
|
|
55746
|
-
inputSchema: {
|
|
55747
|
-
type: "object",
|
|
55748
|
-
required: ["document_id", "text", "position", "expected_content_hash"],
|
|
55749
|
-
properties: {
|
|
55750
|
-
document_id: { type: "string", description: "UUID of the document to add to" },
|
|
55751
|
-
text: { type: "string", description: "Markdown to insert. Sent as-is; blank-line separation is handled for you." },
|
|
55752
|
-
position: {
|
|
55753
|
-
type: "string",
|
|
55754
|
-
enum: ["end_of_document", "end_of_section", "after_heading", "before_heading"],
|
|
55755
|
-
description: "Where to insert. end_of_document needs no anchor; the other three require anchor_heading."
|
|
55756
|
-
},
|
|
55757
|
-
anchor_heading: {
|
|
55758
|
-
type: "string",
|
|
55759
|
-
description: "Exact heading line, or a ' > ' path for a heading that appears more than once. Required unless position is end_of_document."
|
|
55760
|
-
},
|
|
55761
|
-
section_part: {
|
|
55762
|
-
type: "string",
|
|
55763
|
-
enum: ["own_body", "subtree"],
|
|
55764
|
-
description: "Only for end_of_section when the target section HAS CHILD SECTIONS: own_body = before the first child, subtree = after everything nested under it. These can be far apart, so the tool refuses rather than choosing. Omit it otherwise; you will be told (with both options) whenever it is needed."
|
|
55765
|
-
},
|
|
55766
|
-
expected_content_hash: {
|
|
55767
|
-
type: "string",
|
|
55768
|
-
description: "content_hash of the version you are basing this on. Required — no last-write-wins."
|
|
55769
|
-
},
|
|
55770
|
-
requestor: {
|
|
55771
|
-
type: "string",
|
|
55772
|
-
description: 'Agent or user making this request. Recorded in the usage log. Defaults to "mcp-agent".'
|
|
55773
|
-
},
|
|
55774
|
-
author_type: {
|
|
55775
|
-
type: "string",
|
|
55776
|
-
enum: ["user", "agent"],
|
|
55777
|
-
description: "Honoured on the CLI only, where a human ran the command; over MCP the write is always recorded as an agent write regardless of what is passed."
|
|
55778
|
-
}
|
|
55779
|
-
}
|
|
55780
|
-
},
|
|
55781
|
-
handler: insertHandler
|
|
55782
|
-
};
|
|
55783
|
-
editTool = {
|
|
55784
|
-
name: "cerefox_edit",
|
|
55785
|
-
description: "Change parts of a document without resending the whole thing: one or many operations " + "applied ATOMICALLY in one write. Operations: insert (same positions as cerefox_insert), " + "replace_section (swap a section's body, heading kept), delete_section (remove a section, " + "scope body_only or heading_and_body), rename_section (change a heading's text, leaving " + "its body and position untouched — for headings that go stale, like a dated one). " + "Use one call for changes that belong together — a " + "half-applied edit is impossible, so a row and the total it feeds cannot disagree. " + "Operations apply in order and each sees the previous one's result. To change a single " + "line, replace_section on its smallest enclosing heading. Requires expected_content_hash; " + "returns the new hash, not the document.",
|
|
55786
|
-
annotations: {
|
|
55787
|
-
title: "Edit document sections",
|
|
55788
|
-
readOnlyHint: false,
|
|
55789
|
-
destructiveHint: true,
|
|
55790
|
-
idempotentHint: false,
|
|
55791
|
-
openWorldHint: false
|
|
55792
|
-
},
|
|
55793
|
-
inputSchema: {
|
|
55794
|
-
type: "object",
|
|
55795
|
-
required: ["document_id", "operations", "expected_content_hash"],
|
|
55796
|
-
properties: {
|
|
55797
|
-
document_id: { type: "string", description: "UUID of the document to edit" },
|
|
55798
|
-
operations: {
|
|
55799
|
-
type: "array",
|
|
55800
|
-
minItems: 1,
|
|
55801
|
-
description: "Operations applied in order, all-or-nothing. If any fails (bad anchor, ambiguity), nothing is written.",
|
|
55802
|
-
items: {
|
|
55803
|
-
type: "object",
|
|
55804
|
-
required: ["op"],
|
|
55805
|
-
properties: {
|
|
55806
|
-
op: {
|
|
55807
|
-
type: "string",
|
|
55808
|
-
enum: ["insert", "replace_section", "delete_section", "rename_section"]
|
|
55809
|
-
},
|
|
55810
|
-
text: { type: "string", description: "Markdown. Required for insert and replace_section." },
|
|
55811
|
-
position: {
|
|
55812
|
-
type: "string",
|
|
55813
|
-
enum: ["end_of_document", "end_of_section", "after_heading", "before_heading"],
|
|
55814
|
-
description: "Required for insert."
|
|
55815
|
-
},
|
|
55816
|
-
anchor_heading: {
|
|
55817
|
-
type: "string",
|
|
55818
|
-
description: "Exact heading line, or a ' > ' path when the heading is not unique. Required for replace_section, delete_section, and any insert other than end_of_document."
|
|
55819
|
-
},
|
|
55820
|
-
section_part: {
|
|
55821
|
-
type: "string",
|
|
55822
|
-
enum: ["own_body", "subtree"],
|
|
55823
|
-
description: "Only when the target section has child sections. You will be told (with both options) whenever it is needed."
|
|
55824
|
-
},
|
|
55825
|
-
scope: {
|
|
55826
|
-
type: "string",
|
|
55827
|
-
enum: ["body_only", "heading_and_body"],
|
|
55828
|
-
description: "delete_section only. Defaults to body_only, which keeps the heading."
|
|
55829
|
-
},
|
|
55830
|
-
new_heading: {
|
|
55831
|
-
type: "string",
|
|
55832
|
-
description: "rename_section only: the replacement heading LINE, at the same level (## stays ##). Changes the heading text and nothing else — the body and the section's position are untouched, which is the point: renaming via delete + insert would risk both. Use it for headings that go stale, like '## OPEN TODOs (as of 2026-08-08)'. A rename changes the anchor, so a later operation in the same call must target the NEW heading."
|
|
55833
|
-
}
|
|
55834
|
-
}
|
|
55835
|
-
}
|
|
55836
|
-
},
|
|
55837
|
-
expected_content_hash: {
|
|
55838
|
-
type: "string",
|
|
55839
|
-
description: "content_hash of the version you are basing these edits on. Required — no last-write-wins."
|
|
55840
|
-
},
|
|
55841
|
-
requestor: {
|
|
55842
|
-
type: "string",
|
|
55843
|
-
description: 'Agent or user making this request. Recorded in the usage log. Defaults to "mcp-agent".'
|
|
55844
|
-
},
|
|
55845
|
-
author_type: {
|
|
55846
|
-
type: "string",
|
|
55847
|
-
enum: ["user", "agent"],
|
|
55848
|
-
description: "Honoured on the CLI only, where a human ran the command; over MCP the write is always recorded as an agent write regardless of what is passed."
|
|
55849
|
-
}
|
|
55850
|
-
}
|
|
55851
|
-
},
|
|
55852
|
-
handler: editHandler
|
|
55853
|
-
};
|
|
55854
|
-
});
|
|
55855
|
-
|
|
55856
56009
|
// ../../_shared/mcp-tools/list-metadata-keys.ts
|
|
55857
56010
|
async function handler5(supabase, args, ctx) {
|
|
55858
56011
|
const { data, error: error2 } = await supabase.rpc("cerefox_list_metadata_keys");
|
|
@@ -55941,6 +56094,10 @@ var init_list_projects = __esm(() => {
|
|
|
55941
56094
|
});
|
|
55942
56095
|
|
|
55943
56096
|
// ../../_shared/mcp-tools/list-versions.ts
|
|
56097
|
+
function utcStamp3(iso) {
|
|
56098
|
+
const trimmed = iso.slice(0, 19);
|
|
56099
|
+
return trimmed.includes("T") ? `${trimmed}Z` : `${trimmed} UTC`;
|
|
56100
|
+
}
|
|
55944
56101
|
async function handler7(supabase, args, ctx) {
|
|
55945
56102
|
const document_id = args.document_id;
|
|
55946
56103
|
if (!document_id)
|
|
@@ -55960,7 +56117,7 @@ async function handler7(supabase, args, ctx) {
|
|
|
55960
56117
|
});
|
|
55961
56118
|
if (!versions.length)
|
|
55962
56119
|
return "No archived versions found for this document.";
|
|
55963
|
-
const lines = versions.map((v) => `v${v.version_number} | ${v.created_at
|
|
56120
|
+
const lines = versions.map((v) => `v${v.version_number} | ${utcStamp3(v.created_at)} | ${v.source} | ${v.chunk_count} chunks / ${v.total_chars.toLocaleString()} chars | id: ${v.version_id}`);
|
|
55964
56121
|
return `Archived versions (newest first):
|
|
55965
56122
|
|
|
55966
56123
|
${lines.join(`
|
|
@@ -56292,8 +56449,92 @@ var init_search = __esm(() => {
|
|
|
56292
56449
|
};
|
|
56293
56450
|
});
|
|
56294
56451
|
|
|
56295
|
-
// ../../_shared/mcp-tools/set-document-
|
|
56452
|
+
// ../../_shared/mcp-tools/set-document-metadata.ts
|
|
56296
56453
|
async function handler10(supabase, args, ctx) {
|
|
56454
|
+
const document_id = args.document_id;
|
|
56455
|
+
const metadata = args.metadata;
|
|
56456
|
+
const replace = args.replace ?? false;
|
|
56457
|
+
if (!document_id)
|
|
56458
|
+
throw new McpInvalidParams("document_id is required");
|
|
56459
|
+
if (metadata === undefined || metadata === null) {
|
|
56460
|
+
throw new McpInvalidParams("metadata is required: an object of keys to set. Use null as a value to REMOVE a key.");
|
|
56461
|
+
}
|
|
56462
|
+
if (typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
56463
|
+
throw new McpInvalidParams("metadata must be an object, not an array or scalar");
|
|
56464
|
+
}
|
|
56465
|
+
if (Object.keys(metadata).length === 0 && !replace) {
|
|
56466
|
+
throw new McpInvalidParams("metadata is empty, which would change nothing. To clear all metadata pass replace: true with {}.");
|
|
56467
|
+
}
|
|
56468
|
+
const author = args.author ?? args.requestor;
|
|
56469
|
+
const authorType = ctx.accessPath === "cli" ? "user" : "agent";
|
|
56470
|
+
const { data, error: error2 } = await supabase.rpc("cerefox_set_document_metadata", {
|
|
56471
|
+
p_document_id: document_id,
|
|
56472
|
+
p_metadata: metadata,
|
|
56473
|
+
p_replace: replace,
|
|
56474
|
+
p_author: author ?? "unknown",
|
|
56475
|
+
p_author_type: authorType
|
|
56476
|
+
});
|
|
56477
|
+
if (error2)
|
|
56478
|
+
throw new Error(`RPC error: ${error2.message}`);
|
|
56479
|
+
const row = data?.[0];
|
|
56480
|
+
if (!row)
|
|
56481
|
+
throw new Error("cerefox_set_document_metadata returned no data");
|
|
56482
|
+
logUsage(supabase, {
|
|
56483
|
+
operation: "update_metadata",
|
|
56484
|
+
accessPath: ctx.accessPath,
|
|
56485
|
+
requestor: args.requestor,
|
|
56486
|
+
document_id,
|
|
56487
|
+
result_count: 1
|
|
56488
|
+
});
|
|
56489
|
+
const set = row.keys_set ?? 0;
|
|
56490
|
+
const removed = row.keys_removed ?? 0;
|
|
56491
|
+
const summary = set === 0 && removed === 0 ? "No change: every key already held that value." : `${set} key(s) set, ${removed} removed.`;
|
|
56492
|
+
return `Metadata ${replace ? "replaced" : "merged"} on ${document_id}. ${summary}
|
|
56493
|
+
` + `Now: ${JSON.stringify(row.metadata ?? {})}
|
|
56494
|
+
` + `Content untouched — no new version, no re-embedding.`;
|
|
56495
|
+
}
|
|
56496
|
+
var setDocumentMetadataTool;
|
|
56497
|
+
var init_set_document_metadata = __esm(() => {
|
|
56498
|
+
init_types3();
|
|
56499
|
+
setDocumentMetadataTool = {
|
|
56500
|
+
name: "cerefox_set_document_metadata",
|
|
56501
|
+
description: `Change a document's metadata WITHOUT resending its content. MERGES by default: the keys you pass are set, every other key is left alone — so you do not need to read the document first, and you cannot accidentally drop tags another agent set. To REMOVE a key, pass it with a null value ({"stale_key": null}). Pass replace: true to set the metadata to exactly the object given, discarding everything else (rare; the same destructive contract as cerefox_set_document_projects). Content, chunks and embeddings are untouched and no new version is created. Use this instead of cerefox_ingest whenever only the tags are changing.`,
|
|
56502
|
+
annotations: {
|
|
56503
|
+
title: "Set document metadata",
|
|
56504
|
+
readOnlyHint: false,
|
|
56505
|
+
destructiveHint: true,
|
|
56506
|
+
idempotentHint: true,
|
|
56507
|
+
openWorldHint: false
|
|
56508
|
+
},
|
|
56509
|
+
inputSchema: {
|
|
56510
|
+
type: "object",
|
|
56511
|
+
required: ["document_id", "metadata"],
|
|
56512
|
+
properties: {
|
|
56513
|
+
document_id: { type: "string", description: "UUID of the document to tag." },
|
|
56514
|
+
metadata: {
|
|
56515
|
+
type: "object",
|
|
56516
|
+
description: 'Keys to set. Values are JSON strings by convention (a metadata_filter matches JSONB as strings, so a boolean true will not match "true"). A null value REMOVES that key. Keys you do not mention are left alone unless replace is true.'
|
|
56517
|
+
},
|
|
56518
|
+
replace: {
|
|
56519
|
+
type: "boolean",
|
|
56520
|
+
description: "Set the metadata to EXACTLY this object, discarding any key not listed. Defaults to false (merge). Use only when you mean to reset a document's tags wholesale."
|
|
56521
|
+
},
|
|
56522
|
+
author: {
|
|
56523
|
+
type: "string",
|
|
56524
|
+
description: "Who is making this change. Recorded in the audit log."
|
|
56525
|
+
},
|
|
56526
|
+
requestor: {
|
|
56527
|
+
type: "string",
|
|
56528
|
+
description: "Name of the agent or user making this request. Recorded in the usage log."
|
|
56529
|
+
}
|
|
56530
|
+
}
|
|
56531
|
+
},
|
|
56532
|
+
handler: handler10
|
|
56533
|
+
};
|
|
56534
|
+
});
|
|
56535
|
+
|
|
56536
|
+
// ../../_shared/mcp-tools/set-document-projects.ts
|
|
56537
|
+
async function handler11(supabase, args, ctx) {
|
|
56297
56538
|
const document_id = args.document_id?.trim();
|
|
56298
56539
|
const project_names_raw = args.project_names;
|
|
56299
56540
|
const author = args.author ?? "mcp-agent";
|
|
@@ -56354,7 +56595,7 @@ var init_set_document_projects = __esm(() => {
|
|
|
56354
56595
|
}
|
|
56355
56596
|
}
|
|
56356
56597
|
},
|
|
56357
|
-
handler:
|
|
56598
|
+
handler: handler11
|
|
56358
56599
|
};
|
|
56359
56600
|
});
|
|
56360
56601
|
|
|
@@ -56384,6 +56625,7 @@ var init_mcp_tools = __esm(() => {
|
|
|
56384
56625
|
init_list_versions();
|
|
56385
56626
|
init_metadata_search();
|
|
56386
56627
|
init_search();
|
|
56628
|
+
init_set_document_metadata();
|
|
56387
56629
|
init_set_document_projects();
|
|
56388
56630
|
init_types3();
|
|
56389
56631
|
init_types3();
|
|
@@ -56397,6 +56639,7 @@ var init_mcp_tools = __esm(() => {
|
|
|
56397
56639
|
metadataSearchTool,
|
|
56398
56640
|
listMetadataKeysTool,
|
|
56399
56641
|
listProjectsTool,
|
|
56642
|
+
setDocumentMetadataTool,
|
|
56400
56643
|
setDocumentProjectsTool,
|
|
56401
56644
|
auditLogTool,
|
|
56402
56645
|
setRelationTool,
|
|
@@ -57237,11 +57480,11 @@ async function runSyncSelfDocs(options = {}) {
|
|
|
57237
57480
|
printTable(outcomes.filter((o) => o.status === "error").map((o) => ({ topic: o.topic, error: o.detail.slice(0, 100) })));
|
|
57238
57481
|
}
|
|
57239
57482
|
}
|
|
57240
|
-
async function
|
|
57483
|
+
async function action21(options) {
|
|
57241
57484
|
await runSyncSelfDocs(options);
|
|
57242
57485
|
}
|
|
57243
57486
|
function registerSyncSelfDocs(program2) {
|
|
57244
|
-
program2.command("sync-self-docs").description("Ingest bundled Cerefox docs under the _cerefox-self-docs project.").option("--dry-run", "List what would be ingested without writing.").option("--project <name>", "Override the target project name.", "_cerefox-self-docs").action(
|
|
57487
|
+
program2.command("sync-self-docs").description("Ingest bundled Cerefox docs under the _cerefox-self-docs project.").option("--dry-run", "List what would be ingested without writing.").option("--project <name>", "Override the target project name.", "_cerefox-self-docs").action(action21);
|
|
57245
57488
|
}
|
|
57246
57489
|
var init_sync_self_docs = __esm(() => {
|
|
57247
57490
|
init_cli_core();
|
|
@@ -62470,25 +62713,25 @@ class Protocol {
|
|
|
62470
62713
|
const error3 = McpError.fromError(ErrorCode.ConnectionClosed, "Connection closed");
|
|
62471
62714
|
this._transport = undefined;
|
|
62472
62715
|
this.onclose?.();
|
|
62473
|
-
for (const
|
|
62474
|
-
|
|
62716
|
+
for (const handler12 of responseHandlers.values()) {
|
|
62717
|
+
handler12(error3);
|
|
62475
62718
|
}
|
|
62476
62719
|
}
|
|
62477
62720
|
_onerror(error3) {
|
|
62478
62721
|
this.onerror?.(error3);
|
|
62479
62722
|
}
|
|
62480
62723
|
_onnotification(notification) {
|
|
62481
|
-
const
|
|
62482
|
-
if (
|
|
62724
|
+
const handler12 = this._notificationHandlers.get(notification.method) ?? this.fallbackNotificationHandler;
|
|
62725
|
+
if (handler12 === undefined) {
|
|
62483
62726
|
return;
|
|
62484
62727
|
}
|
|
62485
|
-
Promise.resolve().then(() =>
|
|
62728
|
+
Promise.resolve().then(() => handler12(notification)).catch((error3) => this._onerror(new Error(`Uncaught error in notification handler: ${error3}`)));
|
|
62486
62729
|
}
|
|
62487
62730
|
_onrequest(request, extra) {
|
|
62488
|
-
const
|
|
62731
|
+
const handler12 = this._requestHandlers.get(request.method) ?? this.fallbackRequestHandler;
|
|
62489
62732
|
const capturedTransport = this._transport;
|
|
62490
62733
|
const relatedTaskId = request.params?._meta?.[RELATED_TASK_META_KEY]?.taskId;
|
|
62491
|
-
if (
|
|
62734
|
+
if (handler12 === undefined) {
|
|
62492
62735
|
const errorResponse = {
|
|
62493
62736
|
jsonrpc: "2.0",
|
|
62494
62737
|
id: request.id,
|
|
@@ -62552,7 +62795,7 @@ class Protocol {
|
|
|
62552
62795
|
if (taskCreationParams) {
|
|
62553
62796
|
this.assertTaskHandlerCapability(request.method);
|
|
62554
62797
|
}
|
|
62555
|
-
}).then(() =>
|
|
62798
|
+
}).then(() => handler12(request, fullExtra)).then(async (result) => {
|
|
62556
62799
|
if (abortController.signal.aborted) {
|
|
62557
62800
|
return;
|
|
62558
62801
|
}
|
|
@@ -62601,8 +62844,8 @@ class Protocol {
|
|
|
62601
62844
|
_onprogress(notification) {
|
|
62602
62845
|
const { progressToken, ...params } = notification.params;
|
|
62603
62846
|
const messageId = Number(progressToken);
|
|
62604
|
-
const
|
|
62605
|
-
if (!
|
|
62847
|
+
const handler12 = this._progressHandlers.get(messageId);
|
|
62848
|
+
if (!handler12) {
|
|
62606
62849
|
this._onerror(new Error(`Received a progress notification for an unknown token: ${JSON.stringify(notification)}`));
|
|
62607
62850
|
return;
|
|
62608
62851
|
}
|
|
@@ -62619,7 +62862,7 @@ class Protocol {
|
|
|
62619
62862
|
return;
|
|
62620
62863
|
}
|
|
62621
62864
|
}
|
|
62622
|
-
|
|
62865
|
+
handler12(params);
|
|
62623
62866
|
}
|
|
62624
62867
|
_onresponse(response) {
|
|
62625
62868
|
const messageId = Number(response.id);
|
|
@@ -62634,8 +62877,8 @@ class Protocol {
|
|
|
62634
62877
|
}
|
|
62635
62878
|
return;
|
|
62636
62879
|
}
|
|
62637
|
-
const
|
|
62638
|
-
if (
|
|
62880
|
+
const handler12 = this._responseHandlers.get(messageId);
|
|
62881
|
+
if (handler12 === undefined) {
|
|
62639
62882
|
this._onerror(new Error(`Received a response for an unknown message ID: ${JSON.stringify(response)}`));
|
|
62640
62883
|
return;
|
|
62641
62884
|
}
|
|
@@ -62656,10 +62899,10 @@ class Protocol {
|
|
|
62656
62899
|
this._progressHandlers.delete(messageId);
|
|
62657
62900
|
}
|
|
62658
62901
|
if (isJSONRPCResultResponse(response)) {
|
|
62659
|
-
|
|
62902
|
+
handler12(response);
|
|
62660
62903
|
} else {
|
|
62661
62904
|
const error3 = McpError.fromError(response.error.code, response.error.message, response.error.data);
|
|
62662
|
-
|
|
62905
|
+
handler12(error3);
|
|
62663
62906
|
}
|
|
62664
62907
|
}
|
|
62665
62908
|
get transport() {
|
|
@@ -62822,9 +63065,9 @@ class Protocol {
|
|
|
62822
63065
|
const relatedTaskId = relatedTask?.taskId;
|
|
62823
63066
|
if (relatedTaskId) {
|
|
62824
63067
|
const responseResolver = (response) => {
|
|
62825
|
-
const
|
|
62826
|
-
if (
|
|
62827
|
-
|
|
63068
|
+
const handler12 = this._responseHandlers.get(messageId);
|
|
63069
|
+
if (handler12) {
|
|
63070
|
+
handler12(response);
|
|
62828
63071
|
} else {
|
|
62829
63072
|
this._onerror(new Error(`Response handler missing for side-channeled request ${messageId}`));
|
|
62830
63073
|
}
|
|
@@ -62933,12 +63176,12 @@ class Protocol {
|
|
|
62933
63176
|
}
|
|
62934
63177
|
await this._transport.send(jsonrpcNotification, options);
|
|
62935
63178
|
}
|
|
62936
|
-
setRequestHandler(requestSchema,
|
|
63179
|
+
setRequestHandler(requestSchema, handler12) {
|
|
62937
63180
|
const method = getMethodLiteral(requestSchema);
|
|
62938
63181
|
this.assertRequestHandlerCapability(method);
|
|
62939
63182
|
this._requestHandlers.set(method, (request, extra) => {
|
|
62940
63183
|
const parsed = parseWithCompat(requestSchema, request);
|
|
62941
|
-
return Promise.resolve(
|
|
63184
|
+
return Promise.resolve(handler12(parsed, extra));
|
|
62942
63185
|
});
|
|
62943
63186
|
}
|
|
62944
63187
|
removeRequestHandler(method) {
|
|
@@ -62949,11 +63192,11 @@ class Protocol {
|
|
|
62949
63192
|
throw new Error(`A request handler for ${method} already exists, which would be overridden`);
|
|
62950
63193
|
}
|
|
62951
63194
|
}
|
|
62952
|
-
setNotificationHandler(notificationSchema,
|
|
63195
|
+
setNotificationHandler(notificationSchema, handler12) {
|
|
62953
63196
|
const method = getMethodLiteral(notificationSchema);
|
|
62954
63197
|
this._notificationHandlers.set(method, (notification) => {
|
|
62955
63198
|
const parsed = parseWithCompat(notificationSchema, notification);
|
|
62956
|
-
return Promise.resolve(
|
|
63199
|
+
return Promise.resolve(handler12(parsed));
|
|
62957
63200
|
});
|
|
62958
63201
|
}
|
|
62959
63202
|
removeNotificationHandler(method) {
|
|
@@ -69895,7 +70138,7 @@ var init_server2 = __esm(() => {
|
|
|
69895
70138
|
}
|
|
69896
70139
|
this._capabilities = mergeCapabilities(this._capabilities, capabilities);
|
|
69897
70140
|
}
|
|
69898
|
-
setRequestHandler(requestSchema,
|
|
70141
|
+
setRequestHandler(requestSchema, handler12) {
|
|
69899
70142
|
const shape = getObjectShape(requestSchema);
|
|
69900
70143
|
const methodSchema = shape?.method;
|
|
69901
70144
|
if (!methodSchema) {
|
|
@@ -69914,7 +70157,7 @@ var init_server2 = __esm(() => {
|
|
|
69914
70157
|
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${errorMessage}`);
|
|
69915
70158
|
}
|
|
69916
70159
|
const { params } = validatedRequest.data;
|
|
69917
|
-
const result = await Promise.resolve(
|
|
70160
|
+
const result = await Promise.resolve(handler12(request, extra));
|
|
69918
70161
|
if (params.task) {
|
|
69919
70162
|
const taskValidationResult = safeParse2(CreateTaskResultSchema, result);
|
|
69920
70163
|
if (!taskValidationResult.success) {
|
|
@@ -69932,7 +70175,7 @@ var init_server2 = __esm(() => {
|
|
|
69932
70175
|
};
|
|
69933
70176
|
return super.setRequestHandler(requestSchema, wrappedHandler);
|
|
69934
70177
|
}
|
|
69935
|
-
return super.setRequestHandler(requestSchema,
|
|
70178
|
+
return super.setRequestHandler(requestSchema, handler12);
|
|
69936
70179
|
}
|
|
69937
70180
|
assertCapabilityForMethod(method) {
|
|
69938
70181
|
switch (method) {
|
|
@@ -72221,7 +72464,7 @@ function action6(options) {
|
|
|
72221
72464
|
entry: options.local ? localCerefoxEntry() : undefined
|
|
72222
72465
|
});
|
|
72223
72466
|
if (options.json) {
|
|
72224
|
-
printJson(result);
|
|
72467
|
+
printJson({ ...result, serverName: mcpServerName() });
|
|
72225
72468
|
return;
|
|
72226
72469
|
}
|
|
72227
72470
|
if (options.dryRun) {
|
|
@@ -72884,11 +73127,89 @@ function registerDocumentRestore(parent) {
|
|
|
72884
73127
|
parent.command("restore").description("Restore a soft-deleted document from the trash (inverse of `document delete`).").argument("<document-id>", "UUID of the soft-deleted document.").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").action(action11);
|
|
72885
73128
|
}
|
|
72886
73129
|
|
|
73130
|
+
// src/cli/commands/document-set-metadata.ts
|
|
73131
|
+
init_cli_core();
|
|
73132
|
+
init_client();
|
|
73133
|
+
async function action12(documentId, options) {
|
|
73134
|
+
const patch = {};
|
|
73135
|
+
if (options.json) {
|
|
73136
|
+
let parsed;
|
|
73137
|
+
try {
|
|
73138
|
+
parsed = JSON.parse(options.json);
|
|
73139
|
+
} catch (err) {
|
|
73140
|
+
throw userError(`--json is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
73141
|
+
}
|
|
73142
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
73143
|
+
throw userError(`--json must be a JSON object, e.g. '{"type":"note","stale":null}'`);
|
|
73144
|
+
}
|
|
73145
|
+
Object.assign(patch, parsed);
|
|
73146
|
+
}
|
|
73147
|
+
for (const pair of options.set ?? []) {
|
|
73148
|
+
const eq = pair.indexOf("=");
|
|
73149
|
+
if (eq <= 0) {
|
|
73150
|
+
throw userError(`--set expects key=value, got ${JSON.stringify(pair)}`);
|
|
73151
|
+
}
|
|
73152
|
+
const key = pair.slice(0, eq);
|
|
73153
|
+
const raw = pair.slice(eq + 1);
|
|
73154
|
+
if (raw === "null") {
|
|
73155
|
+
throw userError(`--set ${key}=null is ambiguous: over MCP a null REMOVES the key, but on the ` + `command line it could mean the literal text "null". ` + `Use --remove ${key} to delete it, or --json '{"${key}":"null"}' to store the word.`);
|
|
73156
|
+
}
|
|
73157
|
+
let value = raw;
|
|
73158
|
+
if (raw.startsWith('"') && raw.endsWith('"') && raw.length >= 2) {
|
|
73159
|
+
try {
|
|
73160
|
+
value = JSON.parse(raw);
|
|
73161
|
+
} catch {}
|
|
73162
|
+
}
|
|
73163
|
+
patch[key] = value;
|
|
73164
|
+
}
|
|
73165
|
+
for (const key of options.remove ?? []) {
|
|
73166
|
+
patch[key] = null;
|
|
73167
|
+
}
|
|
73168
|
+
if (Object.keys(patch).length === 0 && !options.replace) {
|
|
73169
|
+
throw userError("Nothing to change. Pass --set key=value, --remove key, or --json '{...}'. " + "To clear all metadata use --replace --json '{}'.");
|
|
73170
|
+
}
|
|
73171
|
+
const client = getClient();
|
|
73172
|
+
let rows;
|
|
73173
|
+
try {
|
|
73174
|
+
rows = await client.rpc("cerefox_set_document_metadata", {
|
|
73175
|
+
p_document_id: documentId,
|
|
73176
|
+
p_metadata: patch,
|
|
73177
|
+
p_replace: Boolean(options.replace),
|
|
73178
|
+
p_author: options.author ?? "cli-user",
|
|
73179
|
+
p_author_type: options.authorType === "agent" ? "agent" : "user"
|
|
73180
|
+
});
|
|
73181
|
+
} catch (err) {
|
|
73182
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
73183
|
+
if (/not found/i.test(msg))
|
|
73184
|
+
throw notFound(`Document ${documentId} not found (or is deleted).`);
|
|
73185
|
+
throw systemError(`Could not set metadata: ${msg}`);
|
|
73186
|
+
}
|
|
73187
|
+
if (!rows || rows.length === 0) {
|
|
73188
|
+
throw systemError("cerefox_set_document_metadata returned no data.", "Verify the RPC is deployed: `cerefox server deploy`.");
|
|
73189
|
+
}
|
|
73190
|
+
const row = rows[0];
|
|
73191
|
+
if (options.jsonOut) {
|
|
73192
|
+
printJson(row);
|
|
73193
|
+
return;
|
|
73194
|
+
}
|
|
73195
|
+
const verb = options.replace ? "Replaced" : "Merged";
|
|
73196
|
+
if (row.keys_set === 0 && row.keys_removed === 0) {
|
|
73197
|
+
println(c.green(`✓ ${verb} metadata on ${documentId} — no change (every key already held that value).`));
|
|
73198
|
+
} else {
|
|
73199
|
+
println(c.green(`✓ ${verb} metadata on ${documentId}: ${row.keys_set} key(s) set, ${row.keys_removed} removed.`));
|
|
73200
|
+
}
|
|
73201
|
+
println(c.dim(` Now: ${JSON.stringify(row.metadata ?? {})}`));
|
|
73202
|
+
println(c.dim(" Content untouched — no new version, no re-embedding."));
|
|
73203
|
+
}
|
|
73204
|
+
function registerDocumentSetMetadata(parent) {
|
|
73205
|
+
parent.command("set-metadata").description("Change a document's metadata without resending its content (merges by default).").argument("<document-id>", "UUID of the document.").option("-s, --set <key=value...>", "Set a key. Repeatable. Values are stored as JSON strings; quote to force JSON parsing.").option("-r, --remove <key...>", "Remove a key. Repeatable. (Sends a JSON null.)").option("--json <object>", "A JSON object of keys to set; a null value removes that key.").option("--replace", "Set the metadata to EXACTLY what was given, discarding every key not listed. Default is merge.").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").option("--json-out", "Emit the result as JSON.").action(action12);
|
|
73206
|
+
}
|
|
73207
|
+
|
|
72887
73208
|
// src/cli/commands/document-set-projects.ts
|
|
72888
73209
|
init_cli_core();
|
|
72889
73210
|
init__projects();
|
|
72890
73211
|
init_client();
|
|
72891
|
-
async function
|
|
73212
|
+
async function action13(documentId, projectNames, options) {
|
|
72892
73213
|
const names = projectNames ?? [];
|
|
72893
73214
|
if (options.clear && names.length > 0) {
|
|
72894
73215
|
throw userError("Pass either project names or --clear, not both.", "Use --clear on its own to remove the document from all projects.");
|
|
@@ -72918,7 +73239,7 @@ async function action12(documentId, projectNames, options) {
|
|
|
72918
73239
|
println(c.dim(" This REPLACED the previous set — any project not listed is no longer associated."));
|
|
72919
73240
|
}
|
|
72920
73241
|
function registerDocumentSetProjects(parent) {
|
|
72921
|
-
parent.command("set-projects").description("Replace a document's project memberships with exactly the given set (or --clear to remove all).").argument("<document-id>", "UUID of the document.").argument("[project-names...]", "Project names to set (created if missing). Omit and pass --clear to remove all.").option("--clear", "Remove the document from all projects.").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").action(
|
|
73242
|
+
parent.command("set-projects").description("Replace a document's project memberships with exactly the given set (or --clear to remove all).").argument("<document-id>", "UUID of the document.").argument("[project-names...]", "Project names to set (created if missing). Omit and pass --clear to remove all.").option("--clear", "Remove the document from all projects.").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").action(action13);
|
|
72922
73243
|
}
|
|
72923
73244
|
|
|
72924
73245
|
// src/cli/commands/guides.ts
|
|
@@ -72968,7 +73289,7 @@ function registerGuides(parent) {
|
|
|
72968
73289
|
// src/cli/commands/project-create.ts
|
|
72969
73290
|
init_cli_core();
|
|
72970
73291
|
init_client();
|
|
72971
|
-
async function
|
|
73292
|
+
async function action14(name, options) {
|
|
72972
73293
|
const trimmed = name.trim();
|
|
72973
73294
|
if (!trimmed)
|
|
72974
73295
|
throw userError("Project name is required.");
|
|
@@ -72980,14 +73301,14 @@ async function action13(name, options) {
|
|
|
72980
73301
|
println(c.green(`✓ Created project "${data.name}" (id: ${data.id}).`));
|
|
72981
73302
|
}
|
|
72982
73303
|
function registerProjectCreate(parent) {
|
|
72983
|
-
parent.command("create").description("Create a new (empty) project.").argument("<name>", "Project name (must be unique).").option("--description <text>", "Optional project description.").action(
|
|
73304
|
+
parent.command("create").description("Create a new (empty) project.").argument("<name>", "Project name (must be unique).").option("--description <text>", "Optional project description.").action(action14);
|
|
72984
73305
|
}
|
|
72985
73306
|
|
|
72986
73307
|
// src/cli/commands/project-edit.ts
|
|
72987
73308
|
init_cli_core();
|
|
72988
73309
|
init_client();
|
|
72989
73310
|
var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
72990
|
-
async function
|
|
73311
|
+
async function action15(target, options) {
|
|
72991
73312
|
const update = {};
|
|
72992
73313
|
if (options.name !== undefined) {
|
|
72993
73314
|
const n = options.name.trim();
|
|
@@ -73014,7 +73335,7 @@ async function action14(target, options) {
|
|
|
73014
73335
|
println(c.green(`✓ Updated project "${data.name}" (id: ${data.id}).`));
|
|
73015
73336
|
}
|
|
73016
73337
|
function registerProjectEdit(parent) {
|
|
73017
|
-
parent.command("edit").description("Rename a project and/or change its description.").argument("<name-or-id>", "Project name (exact match) or UUID.").option("--name <new-name>", "New project name.").option("--description <text>", "New project description.").action(
|
|
73338
|
+
parent.command("edit").description("Rename a project and/or change its description.").argument("<name-or-id>", "Project name (exact match) or UUID.").option("--name <new-name>", "New project name.").option("--description <text>", "New project description.").action(action15);
|
|
73018
73339
|
}
|
|
73019
73340
|
|
|
73020
73341
|
// src/cli/commands/version-archive.ts
|
|
@@ -76456,19 +76777,14 @@ init_cli_core();
|
|
|
76456
76777
|
|
|
76457
76778
|
// src/cli/util/checks.ts
|
|
76458
76779
|
init_meta();
|
|
76459
|
-
|
|
76460
|
-
import { homedir as homedir6 } from "node:os";
|
|
76461
|
-
import { join as join9 } from "node:path";
|
|
76462
|
-
|
|
76463
|
-
// ../../_shared/ef-meta/index.ts
|
|
76464
|
-
var EF_VERSION = "1.4.0";
|
|
76465
|
-
var EF_LAST_CHANGED = "1.4.0";
|
|
76466
|
-
|
|
76467
|
-
// src/cli/util/checks.ts
|
|
76780
|
+
init_ef_meta();
|
|
76468
76781
|
init_config();
|
|
76469
76782
|
init_config();
|
|
76470
76783
|
init_compatibility();
|
|
76471
76784
|
init_server_assets();
|
|
76785
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7, realpathSync, statSync as statSync2 } from "node:fs";
|
|
76786
|
+
import { homedir as homedir6 } from "node:os";
|
|
76787
|
+
import { join as join9 } from "node:path";
|
|
76472
76788
|
function checkBinary() {
|
|
76473
76789
|
return {
|
|
76474
76790
|
name: "binary",
|
|
@@ -77126,7 +77442,7 @@ function symbol(status) {
|
|
|
77126
77442
|
return cErr.dim("ℹ");
|
|
77127
77443
|
}
|
|
77128
77444
|
}
|
|
77129
|
-
async function
|
|
77445
|
+
async function action16(options) {
|
|
77130
77446
|
const useSpinner = !options.json && process.stderr.isTTY;
|
|
77131
77447
|
const spinner = useSpinner ? ora({ text: "Starting checks…", spinner: "dots", stream: process.stderr }).start() : null;
|
|
77132
77448
|
const results = await runAllChecks({
|
|
@@ -77190,13 +77506,13 @@ async function action15(options) {
|
|
|
77190
77506
|
process.exit(1);
|
|
77191
77507
|
}
|
|
77192
77508
|
function registerDoctor(program2) {
|
|
77193
|
-
program2.command("doctor").description("Run diagnostic checks against the installed Cerefox.").option("--json", "Emit machine-readable JSON (no colours, structured output).").option("--strict", "Exit non-zero when any check warns (default: only errors fail).").action(
|
|
77509
|
+
program2.command("doctor").description("Run diagnostic checks against the installed Cerefox.").option("--json", "Emit machine-readable JSON (no colours, structured output).").option("--strict", "Exit non-zero when any check warns (default: only errors fail).").action(action16);
|
|
77194
77510
|
}
|
|
77195
77511
|
|
|
77196
77512
|
// src/cli/commands/get-audit-log.ts
|
|
77197
77513
|
init_cli_core();
|
|
77198
77514
|
init_client();
|
|
77199
|
-
async function
|
|
77515
|
+
async function action17(options) {
|
|
77200
77516
|
const limit = parsePositiveInt(options.limit, "--limit", 50);
|
|
77201
77517
|
const client = getClient();
|
|
77202
77518
|
const data = await client.rpc("cerefox_list_audit_entries", {
|
|
@@ -77226,7 +77542,7 @@ async function action16(options) {
|
|
|
77226
77542
|
return;
|
|
77227
77543
|
}
|
|
77228
77544
|
printTable(data.map((row) => ({
|
|
77229
|
-
when: (row.created_at ?? "").slice(0, 19).replace("T", " ")
|
|
77545
|
+
when: `${(row.created_at ?? "").slice(0, 19).replace("T", " ")}Z`,
|
|
77230
77546
|
operation: row.operation,
|
|
77231
77547
|
doc: (row.doc_title ?? (row.document_id ?? "?").slice(0, 8) + "…").slice(0, 40),
|
|
77232
77548
|
author: (row.author ?? "") + (row.author_type ? `(${row.author_type})` : ""),
|
|
@@ -77234,7 +77550,7 @@ async function action16(options) {
|
|
|
77234
77550
|
})));
|
|
77235
77551
|
}
|
|
77236
77552
|
function registerGetAuditLog(program2) {
|
|
77237
|
-
program2.command("get-audit-log").description("Query the audit log with optional filters.").option("-d, --document-id <uuid>", "Filter by document.").option("-a, --author <name>", "Filter by author.").option("-o, --operation <type>", "Filter by operation: create, update-content, update-metadata, delete, restore.").option("--since <iso>", "Lower-bound ISO timestamp.").option("--until <iso>", "Upper-bound ISO timestamp.").option("-l, --limit <n>", "Maximum entries (max 200).", "50").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(
|
|
77553
|
+
program2.command("get-audit-log").description("Query the audit log with optional filters.").option("-d, --document-id <uuid>", "Filter by document.").option("-a, --author <name>", "Filter by author.").option("-o, --operation <type>", "Filter by operation: create, update-content, update-metadata, delete, restore.").option("--since <iso>", "Lower-bound ISO timestamp.").option("--until <iso>", "Upper-bound ISO timestamp.").option("-l, --limit <n>", "Maximum entries (max 200).", "50").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(action17);
|
|
77238
77554
|
}
|
|
77239
77555
|
|
|
77240
77556
|
// src/cli/commands/relation.ts
|
|
@@ -77370,7 +77686,14 @@ init_cli_core();
|
|
|
77370
77686
|
init_cli_core();
|
|
77371
77687
|
init_partial_edits();
|
|
77372
77688
|
init_client();
|
|
77373
|
-
async function
|
|
77689
|
+
async function action18(documentId, options) {
|
|
77690
|
+
const section = (options.section ?? "").trim() || null;
|
|
77691
|
+
if (section && options.outline) {
|
|
77692
|
+
throw userError("Pass either --outline (the whole structure) or --section (one section's text), not both.");
|
|
77693
|
+
}
|
|
77694
|
+
if (options.sectionPart && !section) {
|
|
77695
|
+
throw userError("--section-part only applies together with --section.");
|
|
77696
|
+
}
|
|
77374
77697
|
const client = getClient();
|
|
77375
77698
|
const rows = await client.rpc("cerefox_get_document", {
|
|
77376
77699
|
p_document_id: documentId,
|
|
@@ -77418,6 +77741,35 @@ async function action17(documentId, options) {
|
|
|
77418
77741
|
println(c.dim(` --anchor ${JSON.stringify(nodes[nodes.length - 1].path)}`));
|
|
77419
77742
|
return;
|
|
77420
77743
|
}
|
|
77744
|
+
if (section) {
|
|
77745
|
+
let extracted;
|
|
77746
|
+
try {
|
|
77747
|
+
extracted = extractSection(doc.full_content ?? "", section, options.sectionPart);
|
|
77748
|
+
} catch (err) {
|
|
77749
|
+
throw userError(err instanceof Error ? err.message : String(err));
|
|
77750
|
+
}
|
|
77751
|
+
const archived = Boolean(options.versionId);
|
|
77752
|
+
if (options.json) {
|
|
77753
|
+
printJson({
|
|
77754
|
+
title: doc.doc_title,
|
|
77755
|
+
heading: extracted.heading,
|
|
77756
|
+
path: extracted.path,
|
|
77757
|
+
level: extracted.level,
|
|
77758
|
+
section_part: extracted.section_part,
|
|
77759
|
+
chars: extracted.chars,
|
|
77760
|
+
content_hash: archived ? null : doc.content_hash ?? null,
|
|
77761
|
+
text: extracted.text
|
|
77762
|
+
});
|
|
77763
|
+
return;
|
|
77764
|
+
}
|
|
77765
|
+
println(c.bold(extracted.heading));
|
|
77766
|
+
println(c.dim(`[${doc.document_id}] · ${extracted.path} · ${extracted.chars} chars` + (extracted.section_part ? ` · ${extracted.section_part}` : "") + (archived ? " · archived" : "")));
|
|
77767
|
+
if (!archived && doc.content_hash)
|
|
77768
|
+
println(c.dim(`content_hash: ${doc.content_hash}`));
|
|
77769
|
+
println("");
|
|
77770
|
+
println(extracted.text);
|
|
77771
|
+
return;
|
|
77772
|
+
}
|
|
77421
77773
|
if (options.json) {
|
|
77422
77774
|
printJson(doc);
|
|
77423
77775
|
return;
|
|
@@ -77431,7 +77783,7 @@ async function action17(documentId, options) {
|
|
|
77431
77783
|
println(doc.full_content);
|
|
77432
77784
|
}
|
|
77433
77785
|
function registerGetDoc(program2) {
|
|
77434
|
-
program2.command("get-doc").description("Retrieve the full content of a document by ID.").argument("<document-id>", "UUID of the document.").option("--version-id <uuid>", "Specific archived version (default: current).").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").option("--outline", "Show the heading structure, per-section sizes and content_hash instead of the content. Cheap, and the paths are the anchors the edit commands take.").action(
|
|
77786
|
+
program2.command("get-doc").description("Retrieve the full content of a document by ID.").argument("<document-id>", "UUID of the document.").option("--version-id <uuid>", "Specific archived version (default: current).").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").option("--outline", "Show the heading structure, per-section sizes and content_hash instead of the content. Cheap, and the paths are the anchors the edit commands take.").option("--section <anchor>", "Show ONE section's text instead of the whole document: exactly what a replace_section on this anchor would overwrite. Pass the bare heading line when it is unique, or the full ' > ' path from --outline when it repeats.").option("--section-part <part>", "own_body | subtree — only when the target section has child sections, where 'the end' means two different places. You are told (with both options) whenever it is needed.").action(action18);
|
|
77435
77787
|
}
|
|
77436
77788
|
|
|
77437
77789
|
// src/cli/commands/ingest.ts
|
|
@@ -78065,7 +78417,7 @@ async function readContent(path, paste) {
|
|
|
78065
78417
|
const titleFromPath = basename2(path, extname3(path));
|
|
78066
78418
|
return { content, titleFromPath };
|
|
78067
78419
|
}
|
|
78068
|
-
async function
|
|
78420
|
+
async function action19(path, options) {
|
|
78069
78421
|
const { content, titleFromPath } = await readContent(path, Boolean(options.paste));
|
|
78070
78422
|
const updatingById = Boolean(options.documentId);
|
|
78071
78423
|
let title = options.title ?? (updatingById ? null : titleFromPath);
|
|
@@ -78156,7 +78508,7 @@ async function action18(path, options) {
|
|
|
78156
78508
|
}
|
|
78157
78509
|
}
|
|
78158
78510
|
function registerIngest(program2) {
|
|
78159
|
-
program2.command("ingest").description("Ingest a file (or stdin paste) into the knowledge base.").argument("[path]", "Path to the file to ingest. Omit when using --paste.").option("--paste", "Read content from stdin instead of a file.").option("-t, --title <title>", "Document title (required with --paste; defaults to filename without extension).").option("-p, --project-name <name>", "Single project membership (non-destructive on update).").option("-P, --project-names <names>", "Comma-separated full project membership set (destructive replace on update).").option("-m, --metadata <json>", "JSON metadata object.").option("--source <label>", 'Origin label. Omit it on an update and the document keeps the source it already has (#193); omit it on a create and it is recorded as "cli".').option("-u, --update-if-exists", "Update an existing doc with the same title.").option("-i, --document-id <uuid>", "Update a specific document by UUID (overrides --update-if-exists).").option("--expected-content-hash <sha256>", "Optimistic-concurrency token: the content_hash of the version this edit is based on (shown by `document get` / `search`). Required on content updates unless --last-write-wins.").option("--last-write-wins", "Skip the concurrency check and overwrite regardless of concurrent changes (recorded in the audit log).").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").action(
|
|
78511
|
+
program2.command("ingest").description("Ingest a file (or stdin paste) into the knowledge base.").argument("[path]", "Path to the file to ingest. Omit when using --paste.").option("--paste", "Read content from stdin instead of a file.").option("-t, --title <title>", "Document title (required with --paste; defaults to filename without extension).").option("-p, --project-name <name>", "Single project membership (non-destructive on update).").option("-P, --project-names <names>", "Comma-separated full project membership set (destructive replace on update).").option("-m, --metadata <json>", "JSON metadata object.").option("--source <label>", 'Origin label. Omit it on an update and the document keeps the source it already has (#193); omit it on a create and it is recorded as "cli".').option("-u, --update-if-exists", "Update an existing doc with the same title.").option("-i, --document-id <uuid>", "Update a specific document by UUID (overrides --update-if-exists).").option("--expected-content-hash <sha256>", "Optimistic-concurrency token: the content_hash of the version this edit is based on (shown by `document get` / `search`). Required on content updates unless --last-write-wins.").option("--last-write-wins", "Skip the concurrency check and overwrite regardless of concurrent changes (recorded in the audit log).").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").action(action19);
|
|
78160
78512
|
}
|
|
78161
78513
|
|
|
78162
78514
|
// src/cli/commands/document-partial-edit.ts
|
|
@@ -78259,7 +78611,7 @@ function walk(dir, extensions) {
|
|
|
78259
78611
|
}
|
|
78260
78612
|
return files;
|
|
78261
78613
|
}
|
|
78262
|
-
async function
|
|
78614
|
+
async function action20(dir, options) {
|
|
78263
78615
|
const extensions = new Set((options.extensions ?? ".md,.txt").split(",").map((e) => e.trim().toLowerCase()).map((e) => e.startsWith(".") ? e : "." + e).filter((e) => e.length > 0));
|
|
78264
78616
|
const files = walk(dir, extensions);
|
|
78265
78617
|
if (files.length === 0) {
|
|
@@ -78334,7 +78686,7 @@ async function action19(dir, options) {
|
|
|
78334
78686
|
}
|
|
78335
78687
|
}
|
|
78336
78688
|
function registerIngestDir(program2) {
|
|
78337
|
-
program2.command("ingest-dir").description("Recursively ingest a directory of markdown / text files.").argument("<dir>", "Root directory to walk.").option("-p, --project-name <name>", "Project membership for all ingested docs.").option("-m, --metadata <json>", "JSON metadata applied to every doc.").option("--source <label>", 'Origin label. Omit it and each matched document keeps the source it already has (#193); newly created ones are recorded as "cli".').option("-u, --update-if-exists", "Update an existing doc with the same title.").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").option("-e, --extensions <list>", "Comma-separated file extensions to ingest.", ".md,.txt").action(
|
|
78689
|
+
program2.command("ingest-dir").description("Recursively ingest a directory of markdown / text files.").argument("<dir>", "Root directory to walk.").option("-p, --project-name <name>", "Project membership for all ingested docs.").option("-m, --metadata <json>", "JSON metadata applied to every doc.").option("--source <label>", 'Origin label. Omit it and each matched document keeps the source it already has (#193); newly created ones are recorded as "cli".').option("-u, --update-if-exists", "Update an existing doc with the same title.").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").option("-e, --extensions <list>", "Comma-separated file extensions to ingest.", ".md,.txt").action(action20);
|
|
78338
78690
|
}
|
|
78339
78691
|
|
|
78340
78692
|
// src/cli/commands/init.ts
|
|
@@ -78665,7 +79017,7 @@ function writeAnswersTo(target, answers) {
|
|
|
78665
79017
|
}
|
|
78666
79018
|
}
|
|
78667
79019
|
}
|
|
78668
|
-
async function
|
|
79020
|
+
async function action22(options) {
|
|
78669
79021
|
const homeEnv = join11(homedir7(), USER_STATE_DIR_NAME, ".env");
|
|
78670
79022
|
const cwdEnv = join11(process.cwd(), ".env");
|
|
78671
79023
|
const explicitDir = (process.env.CEREFOX_CONFIG_DIR ?? "").trim();
|
|
@@ -78774,13 +79126,13 @@ async function action21(options) {
|
|
|
78774
79126
|
await postWriteLifecycle(target, options);
|
|
78775
79127
|
}
|
|
78776
79128
|
function registerInit(program2) {
|
|
78777
|
-
program2.command("init").description("Interactive first-run setup (config, schema deploy stub, optional MCP wiring).").option("-c, --config <file>", "Non-interactive mode: read answers from a JSON file.").option("--force", "Overwrite existing configuration without prompting.").option("--skip-schema", "Skip the schema deploy step.").option("--skip-self-docs", "Skip the bundled self-doc ingest.").option("--skip-agent-config", "Skip the optional MCP agent wiring.").action(
|
|
79129
|
+
program2.command("init").description("Interactive first-run setup (config, schema deploy stub, optional MCP wiring).").option("-c, --config <file>", "Non-interactive mode: read answers from a JSON file.").option("--force", "Overwrite existing configuration without prompting.").option("--skip-schema", "Skip the schema deploy step.").option("--skip-self-docs", "Skip the bundled self-doc ingest.").option("--skip-agent-config", "Skip the optional MCP agent wiring.").action(action22);
|
|
78778
79130
|
}
|
|
78779
79131
|
|
|
78780
79132
|
// src/cli/commands/list-docs.ts
|
|
78781
79133
|
init_cli_core();
|
|
78782
79134
|
init_client();
|
|
78783
|
-
async function
|
|
79135
|
+
async function action23(options) {
|
|
78784
79136
|
const deleted = !!options.deleted;
|
|
78785
79137
|
const limit = parsePositiveInt(options.limit, "--limit", 100);
|
|
78786
79138
|
const client = getClient();
|
|
@@ -78836,13 +79188,13 @@ async function action22(options) {
|
|
|
78836
79188
|
}));
|
|
78837
79189
|
}
|
|
78838
79190
|
function registerListDocs(program2) {
|
|
78839
|
-
program2.command("list-docs").description("List documents in the knowledge base.").option("-p, --project <name>", "Filter to a specific project.").option("-l, --limit <n>", "Maximum docs to return.", "100").option("--deleted", "List soft-deleted (trashed) documents instead of active ones.").option("--json", "Emit machine-readable JSON.").action(
|
|
79191
|
+
program2.command("list-docs").description("List documents in the knowledge base.").option("-p, --project <name>", "Filter to a specific project.").option("-l, --limit <n>", "Maximum docs to return.", "100").option("--deleted", "List soft-deleted (trashed) documents instead of active ones.").option("--json", "Emit machine-readable JSON.").action(action23);
|
|
78840
79192
|
}
|
|
78841
79193
|
|
|
78842
79194
|
// src/cli/commands/list-metadata-keys.ts
|
|
78843
79195
|
init_cli_core();
|
|
78844
79196
|
init_client();
|
|
78845
|
-
async function
|
|
79197
|
+
async function action24(options) {
|
|
78846
79198
|
const client = getClient();
|
|
78847
79199
|
const data = await client.rpc("cerefox_list_metadata_keys");
|
|
78848
79200
|
if (data === null) {
|
|
@@ -78870,13 +79222,13 @@ async function action23(options) {
|
|
|
78870
79222
|
})));
|
|
78871
79223
|
}
|
|
78872
79224
|
function registerListMetadataKeys(program2) {
|
|
78873
|
-
program2.command("list-metadata-keys").description("List all metadata keys with document counts and example values.").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(
|
|
79225
|
+
program2.command("list-metadata-keys").description("List all metadata keys with document counts and example values.").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(action24);
|
|
78874
79226
|
}
|
|
78875
79227
|
|
|
78876
79228
|
// src/cli/commands/list-projects.ts
|
|
78877
79229
|
init_cli_core();
|
|
78878
79230
|
init_client();
|
|
78879
|
-
async function
|
|
79231
|
+
async function action25(options) {
|
|
78880
79232
|
const client = getClient();
|
|
78881
79233
|
const { data, error: error2 } = await client.raw.from("cerefox_projects").select("id, name, description, created_at").order("name", { ascending: true });
|
|
78882
79234
|
if (error2) {
|
|
@@ -78905,13 +79257,13 @@ async function action24(options) {
|
|
|
78905
79257
|
})), "(no projects)");
|
|
78906
79258
|
}
|
|
78907
79259
|
function registerListProjects(program2) {
|
|
78908
|
-
program2.command("list-projects").description("List all projects in the knowledge base.").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(
|
|
79260
|
+
program2.command("list-projects").description("List all projects in the knowledge base.").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(action25);
|
|
78909
79261
|
}
|
|
78910
79262
|
|
|
78911
79263
|
// src/cli/commands/list-versions.ts
|
|
78912
79264
|
init_cli_core();
|
|
78913
79265
|
init_client();
|
|
78914
|
-
async function
|
|
79266
|
+
async function action26(documentId, options) {
|
|
78915
79267
|
const client = getClient();
|
|
78916
79268
|
const data = await client.rpc("cerefox_list_document_versions", {
|
|
78917
79269
|
p_document_id: documentId
|
|
@@ -78952,7 +79304,7 @@ async function action25(documentId, options) {
|
|
|
78952
79304
|
})));
|
|
78953
79305
|
}
|
|
78954
79306
|
function registerListVersions(program2) {
|
|
78955
|
-
program2.command("list-versions").description("List archived versions of a document.").argument("<document-id>", "UUID of the document.").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(
|
|
79307
|
+
program2.command("list-versions").description("List archived versions of a document.").argument("<document-id>", "UUID of the document.").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(action26);
|
|
78956
79308
|
}
|
|
78957
79309
|
|
|
78958
79310
|
// src/cli/commands/mcp.ts
|
|
@@ -78984,7 +79336,7 @@ function registerEmbedderWarmup(program2) {
|
|
|
78984
79336
|
// src/cli/commands/metadata-search.ts
|
|
78985
79337
|
init_cli_core();
|
|
78986
79338
|
init_client();
|
|
78987
|
-
async function
|
|
79339
|
+
async function action27(options) {
|
|
78988
79340
|
const metadataFilter = parseJsonObjectArg(options.metadataFilter, "--metadata-filter") ?? {};
|
|
78989
79341
|
if (Object.keys(metadataFilter).length === 0 && !options.projectName && !options.updatedSince && !options.createdSince) {
|
|
78990
79342
|
throw userError("Provide at least one of: --metadata-filter, --project-name, --updated-since, or --created-since.", `Examples: --metadata-filter '{"type":"decision-log"}' · --project-name "research" (lists that project's docs).`);
|
|
@@ -79047,7 +79399,7 @@ async function action26(options) {
|
|
|
79047
79399
|
}
|
|
79048
79400
|
}
|
|
79049
79401
|
function registerMetadataSearch(program2) {
|
|
79050
|
-
program2.command("metadata-search").description("Find or list documents by metadata, project, or time criteria (no text query).").option("-f, --metadata-filter <json>", "JSON object; only docs whose metadata contains ALL pairs are returned. Optional — omit to list by --project-name / time range alone (at least one criterion is required).").option("-p, --project-name <name>", "Filter to a specific project.").option("--updated-since <iso>", "Only docs updated on/after this ISO timestamp.").option("--created-since <iso>", "Only docs created on/after this ISO timestamp.").option("--include-content", "Include full document text in results.").option("-l, --limit <n>", "Maximum docs to return.", "10").option("--max-bytes <n>", "Response size budget in bytes (with --include-content).", "200000").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(
|
|
79402
|
+
program2.command("metadata-search").description("Find or list documents by metadata, project, or time criteria (no text query).").option("-f, --metadata-filter <json>", "JSON object; only docs whose metadata contains ALL pairs are returned. Optional — omit to list by --project-name / time range alone (at least one criterion is required).").option("-p, --project-name <name>", "Filter to a specific project.").option("--updated-since <iso>", "Only docs updated on/after this ISO timestamp.").option("--created-since <iso>", "Only docs created on/after this ISO timestamp.").option("--include-content", "Include full document text in results.").option("-l, --limit <n>", "Maximum docs to return.", "10").option("--max-bytes <n>", "Response size budget in bytes (with --include-content).", "200000").option("-r, --requestor <name>", "Agent / user name (usage log).").option("--json", "Emit machine-readable JSON.").action(action27);
|
|
79051
79403
|
}
|
|
79052
79404
|
|
|
79053
79405
|
// src/cli/commands/reindex.ts
|
|
@@ -79073,7 +79425,7 @@ function warnLargeBulkWrite(opts) {
|
|
|
79073
79425
|
}
|
|
79074
79426
|
|
|
79075
79427
|
// src/cli/commands/reindex.ts
|
|
79076
|
-
async function
|
|
79428
|
+
async function action28(options) {
|
|
79077
79429
|
const settings = loadSettings();
|
|
79078
79430
|
if (!settings.supabaseUrl || !settings.supabaseKey) {
|
|
79079
79431
|
throw userError("Supabase credentials not configured — run `cerefox init` first.");
|
|
@@ -79163,7 +79515,7 @@ ${c2.content}`;
|
|
|
79163
79515
|
}
|
|
79164
79516
|
}
|
|
79165
79517
|
function registerReindex(program2) {
|
|
79166
|
-
program2.command("reindex").description("Re-embed existing document chunks (v0.7+).").option("--all", "Reindex every chunk regardless of embedder.").option("--batch <n>", "Chunks per OpenAI batch call. Capped at 96 internally.", "32").option("--dry-run", "Show counts without re-embedding.").option("-i, --document-id <uuid>", "Limit reindex to a single document.").action(
|
|
79518
|
+
program2.command("reindex").description("Re-embed existing document chunks (v0.7+).").option("--all", "Reindex every chunk regardless of embedder.").option("--batch <n>", "Chunks per OpenAI batch call. Capped at 96 internally.", "32").option("--dry-run", "Show counts without re-embedding.").option("-i, --document-id <uuid>", "Limit reindex to a single document.").action(action28);
|
|
79167
79519
|
}
|
|
79168
79520
|
|
|
79169
79521
|
// src/cli/commands/migrate-format.ts
|
|
@@ -79171,7 +79523,7 @@ init_cli_core();
|
|
|
79171
79523
|
init_config();
|
|
79172
79524
|
init_client();
|
|
79173
79525
|
var CURRENT_FORMAT = 2;
|
|
79174
|
-
async function
|
|
79526
|
+
async function action29(options) {
|
|
79175
79527
|
const settings = loadSettings();
|
|
79176
79528
|
const client = getClient();
|
|
79177
79529
|
const supabase = client.raw;
|
|
@@ -79298,7 +79650,7 @@ async function action28(options) {
|
|
|
79298
79650
|
}
|
|
79299
79651
|
}
|
|
79300
79652
|
function registerMigrateFormat(program2) {
|
|
79301
|
-
program2.command("migrate-format").description("Convert legacy-format documents to the current chunk format (re-chunks + re-embeds).").option("--dry-run", "Report how many documents would be converted; write nothing.").option("-l, --limit <n>", "Convert at most N documents (re-run to continue).").option("--document-id <uuid>", "Convert a single document.").option("--author <name>", "Recorded in the audit log for each conversion.").action(
|
|
79653
|
+
program2.command("migrate-format").description("Convert legacy-format documents to the current chunk format (re-chunks + re-embeds).").option("--dry-run", "Report how many documents would be converted; write nothing.").option("-l, --limit <n>", "Convert at most N documents (re-run to continue).").option("--document-id <uuid>", "Convert a single document.").option("--author <name>", "Recorded in the audit log for each conversion.").action(action29);
|
|
79302
79654
|
}
|
|
79303
79655
|
|
|
79304
79656
|
// src/cli/commands/restore.ts
|
|
@@ -79328,7 +79680,7 @@ function resolveBackupFile(target) {
|
|
|
79328
79680
|
}
|
|
79329
79681
|
return join12(path, candidates[0].name);
|
|
79330
79682
|
}
|
|
79331
|
-
async function
|
|
79683
|
+
async function action30(target, options) {
|
|
79332
79684
|
const file = resolveBackupFile(target);
|
|
79333
79685
|
let payload;
|
|
79334
79686
|
try {
|
|
@@ -79463,7 +79815,7 @@ async function action29(target, options) {
|
|
|
79463
79815
|
}
|
|
79464
79816
|
}
|
|
79465
79817
|
function registerRestore(program2) {
|
|
79466
|
-
program2.command("restore").description("Restore a JSON-snapshot backup into the knowledge base.").argument("<snapshot>", "Backup file (or directory; most recent is picked) produced by `cerefox backup`.").option("--dry-run", "Print what would be restored without writing.").option("-p, --project-name <name>", "Reserved for future use; currently ignored. Project memberships are restored from the backup itself (format 2+).").action(
|
|
79818
|
+
program2.command("restore").description("Restore a JSON-snapshot backup into the knowledge base.").argument("<snapshot>", "Backup file (or directory; most recent is picked) produced by `cerefox backup`.").option("--dry-run", "Print what would be restored without writing.").option("-p, --project-name <name>", "Reserved for future use; currently ignored. Project memberships are restored from the backup itself (format 2+).").action(action30);
|
|
79467
79819
|
}
|
|
79468
79820
|
|
|
79469
79821
|
// src/cli/commands/search.ts
|
|
@@ -79488,7 +79840,7 @@ async function embedQuery(query) {
|
|
|
79488
79840
|
}
|
|
79489
79841
|
|
|
79490
79842
|
// src/cli/commands/search.ts
|
|
79491
|
-
async function
|
|
79843
|
+
async function action31(query, options) {
|
|
79492
79844
|
if (!query || query.trim() === "") {
|
|
79493
79845
|
throw userError("Empty query.");
|
|
79494
79846
|
}
|
|
@@ -79654,7 +80006,7 @@ async function action30(query, options) {
|
|
|
79654
80006
|
}
|
|
79655
80007
|
}
|
|
79656
80008
|
function registerSearch(program2) {
|
|
79657
|
-
program2.command("search").description("Search the knowledge base (hybrid FTS + semantic).").argument("<query>", "Natural-language search query.").option("-c, --match-count <n>", "Maximum number of documents to return.", "5").option("-p, --project-name <name>", "Filter results to a specific project.").option("-f, --metadata-filter <json>", "JSON containment filter; only docs whose metadata contains ALL pairs are returned.").option("--mode <mode>", "Search mode: docs (default), hybrid, fts.", "docs").option("--alpha <float>", "Semantic weight 0..1 (default: CEREFOX_SEARCH_ALPHA; else 0.7).").option("--min-score <float>", "Minimum cosine similarity threshold (default: CEREFOX_MIN_SEARCH_SCORE; else 0.5, or 0.6 with the local embedder).").option("--min-term-coverage <float>", "OR-fallback keyword matches must cover at least this fraction of the query's meaningful terms to count as confident hits (default: CEREFOX_MIN_TERM_COVERAGE; else the server default 0.5; needs schema ≥ 0.9.1).").option("--max-bytes <n>", "Response size budget in bytes (default: CEREFOX_MAX_RESPONSE_BYTES or 200000).").option("-r, --requestor <name>", "Agent / user name (recorded in usage log).").option("--json", "Emit machine-readable JSON instead of the default text.").option("--only-metadata", "List matching docs (id, score, chunks, chars, partial/full) WITHOUT their content — like the web UI's collapsed result list. Grab a [id:…] then `cerefox document get <id>`.").action(
|
|
80009
|
+
program2.command("search").description("Search the knowledge base (hybrid FTS + semantic).").argument("<query>", "Natural-language search query.").option("-c, --match-count <n>", "Maximum number of documents to return.", "5").option("-p, --project-name <name>", "Filter results to a specific project.").option("-f, --metadata-filter <json>", "JSON containment filter; only docs whose metadata contains ALL pairs are returned.").option("--mode <mode>", "Search mode: docs (default), hybrid, fts.", "docs").option("--alpha <float>", "Semantic weight 0..1 (default: CEREFOX_SEARCH_ALPHA; else 0.7).").option("--min-score <float>", "Minimum cosine similarity threshold (default: CEREFOX_MIN_SEARCH_SCORE; else 0.5, or 0.6 with the local embedder).").option("--min-term-coverage <float>", "OR-fallback keyword matches must cover at least this fraction of the query's meaningful terms to count as confident hits (default: CEREFOX_MIN_TERM_COVERAGE; else the server default 0.5; needs schema ≥ 0.9.1).").option("--max-bytes <n>", "Response size budget in bytes (default: CEREFOX_MAX_RESPONSE_BYTES or 200000).").option("-r, --requestor <name>", "Agent / user name (recorded in usage log).").option("--json", "Emit machine-readable JSON instead of the default text.").option("--only-metadata", "List matching docs (id, score, chunks, chars, partial/full) WITHOUT their content — like the web UI's collapsed result list. Grab a [id:…] then `cerefox document get <id>`.").action(action31);
|
|
79658
80010
|
}
|
|
79659
80011
|
|
|
79660
80012
|
// src/cli/commands/self-update.ts
|
|
@@ -79701,7 +80053,7 @@ async function fetchLatestVersion() {
|
|
|
79701
80053
|
}
|
|
79702
80054
|
return body.version;
|
|
79703
80055
|
}
|
|
79704
|
-
async function
|
|
80056
|
+
async function action32(options) {
|
|
79705
80057
|
let target;
|
|
79706
80058
|
try {
|
|
79707
80059
|
target = options.version ?? await fetchLatestVersion();
|
|
@@ -79754,7 +80106,7 @@ async function action31(options) {
|
|
|
79754
80106
|
}
|
|
79755
80107
|
function registerSelfUpdate(program2) {
|
|
79756
80108
|
const desc = "Upgrade Cerefox in place. Alias: `cerefox upgrade`.";
|
|
79757
|
-
const declaration = (cmd) => cmd.description(desc).option("--check", "Print current vs latest; do nothing.").option("--yes", "Non-interactive (skip confirmation).").option("--version <version>", "Pin a specific version (e.g. 0.5.1 or 0.6.0-rc.1).").action(
|
|
80109
|
+
const declaration = (cmd) => cmd.description(desc).option("--check", "Print current vs latest; do nothing.").option("--yes", "Non-interactive (skip confirmation).").option("--version <version>", "Pin a specific version (e.g. 0.5.1 or 0.6.0-rc.1).").action(action32);
|
|
79758
80110
|
declaration(program2.command("self-update"));
|
|
79759
80111
|
declaration(program2.command("upgrade"));
|
|
79760
80112
|
}
|
|
@@ -79773,7 +80125,7 @@ function symbol2(status) {
|
|
|
79773
80125
|
return cErr.dim("ℹ");
|
|
79774
80126
|
}
|
|
79775
80127
|
}
|
|
79776
|
-
async function
|
|
80128
|
+
async function action33(options) {
|
|
79777
80129
|
const useSpinner = !options.json && process.stderr.isTTY;
|
|
79778
80130
|
const spinner = useSpinner ? ora({ text: "Starting checks…", spinner: "dots", stream: process.stderr }).start() : null;
|
|
79779
80131
|
const results = await runFastChecks({
|
|
@@ -79794,7 +80146,7 @@ async function action32(options) {
|
|
|
79794
80146
|
}
|
|
79795
80147
|
}
|
|
79796
80148
|
function registerStatus(program2) {
|
|
79797
|
-
program2.command("status").description("Quick sanity check (fast subset of `cerefox doctor`).").option("--json", "Emit machine-readable JSON.").action(
|
|
80149
|
+
program2.command("status").description("Quick sanity check (fast subset of `cerefox doctor`).").option("--json", "Emit machine-readable JSON.").action(action33);
|
|
79798
80150
|
}
|
|
79799
80151
|
|
|
79800
80152
|
// src/cli/commands/token.ts
|
|
@@ -79827,10 +80179,10 @@ function upsertEnvVar(path, key, value, opts = {}) {
|
|
|
79827
80179
|
}
|
|
79828
80180
|
const re = new RegExp(`^(\\s*)${escapeRegExp(key)}=.*$`, "m");
|
|
79829
80181
|
let next;
|
|
79830
|
-
let
|
|
80182
|
+
let action34;
|
|
79831
80183
|
if (re.test(original)) {
|
|
79832
80184
|
next = original.replace(re, `$1${line}`);
|
|
79833
|
-
|
|
80185
|
+
action34 = "updated";
|
|
79834
80186
|
} else {
|
|
79835
80187
|
const base = original.endsWith(`
|
|
79836
80188
|
`) ? original : `${original}
|
|
@@ -79838,10 +80190,10 @@ function upsertEnvVar(path, key, value, opts = {}) {
|
|
|
79838
80190
|
next = `${base}
|
|
79839
80191
|
${header}${line}
|
|
79840
80192
|
`;
|
|
79841
|
-
|
|
80193
|
+
action34 = "added";
|
|
79842
80194
|
}
|
|
79843
80195
|
writeFileSync5(path, next);
|
|
79844
|
-
return { path, action:
|
|
80196
|
+
return { path, action: action34, backupPath };
|
|
79845
80197
|
}
|
|
79846
80198
|
function readEnvVar(path, key) {
|
|
79847
80199
|
if (!existsSync14(path))
|
|
@@ -80019,13 +80371,13 @@ var WSContext = class {
|
|
|
80019
80371
|
this.#init.close(code, reason);
|
|
80020
80372
|
}
|
|
80021
80373
|
};
|
|
80022
|
-
var defineWebSocketHelper = (
|
|
80374
|
+
var defineWebSocketHelper = (handler12) => {
|
|
80023
80375
|
return (...args) => {
|
|
80024
80376
|
if (typeof args[0] === "function") {
|
|
80025
80377
|
const [createEvents, options] = args;
|
|
80026
80378
|
return async function upgradeWebSocket(c2, next) {
|
|
80027
80379
|
const events = await createEvents(c2);
|
|
80028
|
-
const result = await
|
|
80380
|
+
const result = await handler12(c2, events, options);
|
|
80029
80381
|
if (result) {
|
|
80030
80382
|
return result;
|
|
80031
80383
|
}
|
|
@@ -80034,7 +80386,7 @@ var defineWebSocketHelper = (handler11) => {
|
|
|
80034
80386
|
} else {
|
|
80035
80387
|
const [c2, events, options] = args;
|
|
80036
80388
|
return (async () => {
|
|
80037
|
-
const upgraded = await
|
|
80389
|
+
const upgraded = await handler12(c2, events, options);
|
|
80038
80390
|
if (!upgraded) {
|
|
80039
80391
|
throw new Error("Failed to upgrade WebSocket");
|
|
80040
80392
|
}
|
|
@@ -81627,16 +81979,16 @@ var compose = (middleware, onError, onNotFound) => {
|
|
|
81627
81979
|
index = i;
|
|
81628
81980
|
let res;
|
|
81629
81981
|
let isError = false;
|
|
81630
|
-
let
|
|
81982
|
+
let handler12;
|
|
81631
81983
|
if (middleware[i]) {
|
|
81632
|
-
|
|
81984
|
+
handler12 = middleware[i][0][0];
|
|
81633
81985
|
context2.req.routeIndex = i;
|
|
81634
81986
|
} else {
|
|
81635
|
-
|
|
81987
|
+
handler12 = i === middleware.length && next || undefined;
|
|
81636
81988
|
}
|
|
81637
|
-
if (
|
|
81989
|
+
if (handler12) {
|
|
81638
81990
|
try {
|
|
81639
|
-
res = await
|
|
81991
|
+
res = await handler12(context2, () => dispatch(i + 1));
|
|
81640
81992
|
} catch (err) {
|
|
81641
81993
|
if (err instanceof Error && onError) {
|
|
81642
81994
|
context2.error = err;
|
|
@@ -82320,8 +82672,8 @@ var Hono = class _Hono {
|
|
|
82320
82672
|
} else {
|
|
82321
82673
|
this.#addRoute(method, this.#path, args1);
|
|
82322
82674
|
}
|
|
82323
|
-
args.forEach((
|
|
82324
|
-
this.#addRoute(method, this.#path,
|
|
82675
|
+
args.forEach((handler12) => {
|
|
82676
|
+
this.#addRoute(method, this.#path, handler12);
|
|
82325
82677
|
});
|
|
82326
82678
|
return this;
|
|
82327
82679
|
};
|
|
@@ -82330,8 +82682,8 @@ var Hono = class _Hono {
|
|
|
82330
82682
|
for (const p of [path].flat()) {
|
|
82331
82683
|
this.#path = p;
|
|
82332
82684
|
for (const m of [method].flat()) {
|
|
82333
|
-
handlers.map((
|
|
82334
|
-
this.#addRoute(m.toUpperCase(), this.#path,
|
|
82685
|
+
handlers.map((handler12) => {
|
|
82686
|
+
this.#addRoute(m.toUpperCase(), this.#path, handler12);
|
|
82335
82687
|
});
|
|
82336
82688
|
}
|
|
82337
82689
|
}
|
|
@@ -82344,8 +82696,8 @@ var Hono = class _Hono {
|
|
|
82344
82696
|
this.#path = "*";
|
|
82345
82697
|
handlers.unshift(arg1);
|
|
82346
82698
|
}
|
|
82347
|
-
handlers.forEach((
|
|
82348
|
-
this.#addRoute(METHOD_NAME_ALL, this.#path,
|
|
82699
|
+
handlers.forEach((handler12) => {
|
|
82700
|
+
this.#addRoute(METHOD_NAME_ALL, this.#path, handler12);
|
|
82349
82701
|
});
|
|
82350
82702
|
return this;
|
|
82351
82703
|
};
|
|
@@ -82368,14 +82720,14 @@ var Hono = class _Hono {
|
|
|
82368
82720
|
route(path, app) {
|
|
82369
82721
|
const subApp = this.basePath(path);
|
|
82370
82722
|
app.routes.map((r) => {
|
|
82371
|
-
let
|
|
82723
|
+
let handler12;
|
|
82372
82724
|
if (app.errorHandler === errorHandler) {
|
|
82373
|
-
|
|
82725
|
+
handler12 = r.handler;
|
|
82374
82726
|
} else {
|
|
82375
|
-
|
|
82376
|
-
|
|
82727
|
+
handler12 = async (c2, next) => (await compose([], app.errorHandler)(c2, () => r.handler(c2, next))).res;
|
|
82728
|
+
handler12[COMPOSED_HANDLER] = r.handler;
|
|
82377
82729
|
}
|
|
82378
|
-
subApp.#addRoute(r.method, r.path,
|
|
82730
|
+
subApp.#addRoute(r.method, r.path, handler12, r.basePath);
|
|
82379
82731
|
});
|
|
82380
82732
|
return this;
|
|
82381
82733
|
}
|
|
@@ -82384,12 +82736,12 @@ var Hono = class _Hono {
|
|
|
82384
82736
|
subApp._basePath = mergePath(this._basePath, path);
|
|
82385
82737
|
return subApp;
|
|
82386
82738
|
}
|
|
82387
|
-
onError = (
|
|
82388
|
-
this.errorHandler =
|
|
82739
|
+
onError = (handler12) => {
|
|
82740
|
+
this.errorHandler = handler12;
|
|
82389
82741
|
return this;
|
|
82390
82742
|
};
|
|
82391
|
-
notFound = (
|
|
82392
|
-
this.#notFoundHandler =
|
|
82743
|
+
notFound = (handler12) => {
|
|
82744
|
+
this.#notFoundHandler = handler12;
|
|
82393
82745
|
return this;
|
|
82394
82746
|
};
|
|
82395
82747
|
mount(path, applicationHandler, options) {
|
|
@@ -82426,26 +82778,26 @@ var Hono = class _Hono {
|
|
|
82426
82778
|
return new Request(url, request);
|
|
82427
82779
|
};
|
|
82428
82780
|
})();
|
|
82429
|
-
const
|
|
82781
|
+
const handler12 = async (c2, next) => {
|
|
82430
82782
|
const res = await applicationHandler(replaceRequest(c2.req.raw), ...getOptions(c2));
|
|
82431
82783
|
if (res) {
|
|
82432
82784
|
return res;
|
|
82433
82785
|
}
|
|
82434
82786
|
await next();
|
|
82435
82787
|
};
|
|
82436
|
-
this.#addRoute(METHOD_NAME_ALL, mergePath(path, "*"),
|
|
82788
|
+
this.#addRoute(METHOD_NAME_ALL, mergePath(path, "*"), handler12);
|
|
82437
82789
|
return this;
|
|
82438
82790
|
}
|
|
82439
|
-
#addRoute(method, path,
|
|
82791
|
+
#addRoute(method, path, handler12, baseRoutePath) {
|
|
82440
82792
|
method = method.toUpperCase();
|
|
82441
82793
|
path = mergePath(this._basePath, path);
|
|
82442
82794
|
const r = {
|
|
82443
82795
|
basePath: baseRoutePath !== undefined ? mergePath(this._basePath, baseRoutePath) : this._basePath,
|
|
82444
82796
|
path,
|
|
82445
82797
|
method,
|
|
82446
|
-
handler:
|
|
82798
|
+
handler: handler12
|
|
82447
82799
|
};
|
|
82448
|
-
this.router.add(method, path, [
|
|
82800
|
+
this.router.add(method, path, [handler12, r]);
|
|
82449
82801
|
this.routes.push(r);
|
|
82450
82802
|
}
|
|
82451
82803
|
#handleError(err, c2) {
|
|
@@ -82770,7 +83122,7 @@ var RegExpRouter = class {
|
|
|
82770
83122
|
this.#middleware = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
|
|
82771
83123
|
this.#routes = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
|
|
82772
83124
|
}
|
|
82773
|
-
add(method, path,
|
|
83125
|
+
add(method, path, handler12) {
|
|
82774
83126
|
const middleware = this.#middleware;
|
|
82775
83127
|
const routes = this.#routes;
|
|
82776
83128
|
if (!middleware || !routes) {
|
|
@@ -82800,13 +83152,13 @@ var RegExpRouter = class {
|
|
|
82800
83152
|
Object.keys(middleware).forEach((m) => {
|
|
82801
83153
|
if (method === METHOD_NAME_ALL || method === m) {
|
|
82802
83154
|
Object.keys(middleware[m]).forEach((p) => {
|
|
82803
|
-
re.test(p) && middleware[m][p].push([
|
|
83155
|
+
re.test(p) && middleware[m][p].push([handler12, paramCount]);
|
|
82804
83156
|
});
|
|
82805
83157
|
}
|
|
82806
83158
|
});
|
|
82807
83159
|
Object.keys(routes).forEach((m) => {
|
|
82808
83160
|
if (method === METHOD_NAME_ALL || method === m) {
|
|
82809
|
-
Object.keys(routes[m]).forEach((p) => re.test(p) && routes[m][p].push([
|
|
83161
|
+
Object.keys(routes[m]).forEach((p) => re.test(p) && routes[m][p].push([handler12, paramCount]));
|
|
82810
83162
|
}
|
|
82811
83163
|
});
|
|
82812
83164
|
return;
|
|
@@ -82819,7 +83171,7 @@ var RegExpRouter = class {
|
|
|
82819
83171
|
routes[m][path2] ||= [
|
|
82820
83172
|
...findMiddleware(middleware[m], path2) || findMiddleware(middleware[METHOD_NAME_ALL], path2) || []
|
|
82821
83173
|
];
|
|
82822
|
-
routes[m][path2].push([
|
|
83174
|
+
routes[m][path2].push([handler12, paramCount - len + i + 1]);
|
|
82823
83175
|
}
|
|
82824
83176
|
});
|
|
82825
83177
|
}
|
|
@@ -82868,21 +83220,21 @@ var PreparedRegExpRouter = class {
|
|
|
82868
83220
|
matcher[1].forEach((list) => list && list.push(handlerData));
|
|
82869
83221
|
Object.values(matcher[2]).forEach((list) => list[0].push(handlerData));
|
|
82870
83222
|
}
|
|
82871
|
-
#addPath(method, path,
|
|
83223
|
+
#addPath(method, path, handler12, indexes, map2) {
|
|
82872
83224
|
const matcher = this.#matchers[method];
|
|
82873
83225
|
if (!map2) {
|
|
82874
|
-
matcher[2][path][0].push([
|
|
83226
|
+
matcher[2][path][0].push([handler12, {}]);
|
|
82875
83227
|
} else {
|
|
82876
83228
|
indexes.forEach((index) => {
|
|
82877
83229
|
if (typeof index === "number") {
|
|
82878
|
-
matcher[1][index].push([
|
|
83230
|
+
matcher[1][index].push([handler12, map2]);
|
|
82879
83231
|
} else {
|
|
82880
|
-
matcher[2][index || path][0].push([
|
|
83232
|
+
matcher[2][index || path][0].push([handler12, map2]);
|
|
82881
83233
|
}
|
|
82882
83234
|
});
|
|
82883
83235
|
}
|
|
82884
83236
|
}
|
|
82885
|
-
add(method, path,
|
|
83237
|
+
add(method, path, handler12) {
|
|
82886
83238
|
if (!this.#matchers[method]) {
|
|
82887
83239
|
const all = this.#matchers[METHOD_NAME_ALL];
|
|
82888
83240
|
const staticMap = {};
|
|
@@ -82896,7 +83248,7 @@ var PreparedRegExpRouter = class {
|
|
|
82896
83248
|
];
|
|
82897
83249
|
}
|
|
82898
83250
|
if (path === "/*" || path === "*") {
|
|
82899
|
-
const handlerData = [
|
|
83251
|
+
const handlerData = [handler12, {}];
|
|
82900
83252
|
if (method === METHOD_NAME_ALL) {
|
|
82901
83253
|
for (const m in this.#matchers) {
|
|
82902
83254
|
this.#addWildcard(m, handlerData);
|
|
@@ -82913,10 +83265,10 @@ var PreparedRegExpRouter = class {
|
|
|
82913
83265
|
for (const [indexes, map2] of data) {
|
|
82914
83266
|
if (method === METHOD_NAME_ALL) {
|
|
82915
83267
|
for (const m in this.#matchers) {
|
|
82916
|
-
this.#addPath(m, path,
|
|
83268
|
+
this.#addPath(m, path, handler12, indexes, map2);
|
|
82917
83269
|
}
|
|
82918
83270
|
} else {
|
|
82919
|
-
this.#addPath(method, path,
|
|
83271
|
+
this.#addPath(method, path, handler12, indexes, map2);
|
|
82920
83272
|
}
|
|
82921
83273
|
}
|
|
82922
83274
|
}
|
|
@@ -82934,11 +83286,11 @@ var SmartRouter = class {
|
|
|
82934
83286
|
constructor(init) {
|
|
82935
83287
|
this.#routers = init.routers;
|
|
82936
83288
|
}
|
|
82937
|
-
add(method, path,
|
|
83289
|
+
add(method, path, handler12) {
|
|
82938
83290
|
if (!this.#routes) {
|
|
82939
83291
|
throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
|
|
82940
83292
|
}
|
|
82941
|
-
this.#routes.push([method, path,
|
|
83293
|
+
this.#routes.push([method, path, handler12]);
|
|
82942
83294
|
}
|
|
82943
83295
|
match(method, path) {
|
|
82944
83296
|
if (!this.#routes) {
|
|
@@ -82995,17 +83347,17 @@ var Node2 = class _Node2 {
|
|
|
82995
83347
|
#patterns;
|
|
82996
83348
|
#order = 0;
|
|
82997
83349
|
#params = emptyParams;
|
|
82998
|
-
constructor(method,
|
|
83350
|
+
constructor(method, handler12, children) {
|
|
82999
83351
|
this.#children = children || /* @__PURE__ */ Object.create(null);
|
|
83000
83352
|
this.#methods = [];
|
|
83001
|
-
if (method &&
|
|
83353
|
+
if (method && handler12) {
|
|
83002
83354
|
const m = /* @__PURE__ */ Object.create(null);
|
|
83003
|
-
m[method] = { handler:
|
|
83355
|
+
m[method] = { handler: handler12, possibleKeys: [], score: 0 };
|
|
83004
83356
|
this.#methods = [m];
|
|
83005
83357
|
}
|
|
83006
83358
|
this.#patterns = [];
|
|
83007
83359
|
}
|
|
83008
|
-
insert(method, path,
|
|
83360
|
+
insert(method, path, handler12) {
|
|
83009
83361
|
this.#order = ++this.#order;
|
|
83010
83362
|
let curNode = this;
|
|
83011
83363
|
const parts = splitRoutingPath(path);
|
|
@@ -83031,7 +83383,7 @@ var Node2 = class _Node2 {
|
|
|
83031
83383
|
}
|
|
83032
83384
|
curNode.#methods.push({
|
|
83033
83385
|
[method]: {
|
|
83034
|
-
handler:
|
|
83386
|
+
handler: handler12,
|
|
83035
83387
|
possibleKeys: possibleKeys.filter((v, i, a) => a.indexOf(v) === i),
|
|
83036
83388
|
score: this.#order
|
|
83037
83389
|
}
|
|
@@ -83149,7 +83501,7 @@ var Node2 = class _Node2 {
|
|
|
83149
83501
|
return a.score - b2.score;
|
|
83150
83502
|
});
|
|
83151
83503
|
}
|
|
83152
|
-
return [handlerSets.map(({ handler:
|
|
83504
|
+
return [handlerSets.map(({ handler: handler12, params }) => [handler12, params])];
|
|
83153
83505
|
}
|
|
83154
83506
|
};
|
|
83155
83507
|
|
|
@@ -83160,15 +83512,15 @@ var TrieRouter = class {
|
|
|
83160
83512
|
constructor() {
|
|
83161
83513
|
this.#node = new Node2;
|
|
83162
83514
|
}
|
|
83163
|
-
add(method, path,
|
|
83515
|
+
add(method, path, handler12) {
|
|
83164
83516
|
const results = checkOptionalParameter(path);
|
|
83165
83517
|
if (results) {
|
|
83166
83518
|
for (let i = 0, len = results.length;i < len; i++) {
|
|
83167
|
-
this.#node.insert(method, results[i],
|
|
83519
|
+
this.#node.insert(method, results[i], handler12);
|
|
83168
83520
|
}
|
|
83169
83521
|
return;
|
|
83170
83522
|
}
|
|
83171
|
-
this.#node.insert(method, path,
|
|
83523
|
+
this.#node.insert(method, path, handler12);
|
|
83172
83524
|
}
|
|
83173
83525
|
match(method, path) {
|
|
83174
83526
|
return this.#node.search(method, path);
|
|
@@ -85149,6 +85501,7 @@ var ROOT_REDIRECT_HTML = `<!DOCTYPE html>
|
|
|
85149
85501
|
|
|
85150
85502
|
// src/web/server.ts
|
|
85151
85503
|
init_meta();
|
|
85504
|
+
init_ef_meta();
|
|
85152
85505
|
init_cli_core();
|
|
85153
85506
|
init_config();
|
|
85154
85507
|
init_compatibility();
|
|
@@ -85620,6 +85973,7 @@ Learn more:
|
|
|
85620
85973
|
registerDocumentRestore(document2);
|
|
85621
85974
|
registerDocumentEdit(document2);
|
|
85622
85975
|
registerDocumentSetProjects(document2);
|
|
85976
|
+
registerDocumentSetMetadata(document2);
|
|
85623
85977
|
moveInto(document2, registerIngest, "ingest");
|
|
85624
85978
|
registerDocumentInsert(document2);
|
|
85625
85979
|
registerDocumentEditParts(document2);
|