@cerefox/memory 1.5.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 +13 -4
- package/AGENT_QUICK_REFERENCE.md +14 -3
- package/README.md +3 -2
- package/dist/bin/cerefox.js +318 -136
- package/dist/frontend/assets/{index-D9z5yV9u.js → index-DcWOeGAh.js} +2 -2
- 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 +3 -3
- package/dist/server-assets/_shared/mcp-tools/get-help-content.ts +4 -4
- package/dist/server-assets/_shared/mcp-tools/get-help.ts +15 -1
- package/dist/server-assets/_shared/mcp-tools/index.ts +3 -0
- package/dist/server-assets/_shared/mcp-tools/set-document-metadata.ts +133 -0
- package/dist/server-assets/db/migrations/0023_set_document_metadata.sql +16 -0
- package/dist/server-assets/db/rpcs.sql +116 -1
- package/dist/server-assets/db/schema.sql +1 -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-D9z5yV9u.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
|
|
@@ -25731,7 +25731,7 @@ var init_bundled_docs = __esm(() => {
|
|
|
25731
25731
|
});
|
|
25732
25732
|
|
|
25733
25733
|
// ../../_shared/ef-meta/index.ts
|
|
25734
|
-
var EF_VERSION = "1.
|
|
25734
|
+
var EF_VERSION = "1.6.0", CEREFOX_VERSION = "1.6.0", EF_LAST_CHANGED = "1.6.0";
|
|
25735
25735
|
var init_ef_meta = () => {};
|
|
25736
25736
|
|
|
25737
25737
|
// ../../_shared/compatibility/index.ts
|
|
@@ -41580,10 +41580,10 @@ var require_flate = __commonJS((exports) => {
|
|
|
41580
41580
|
var GenericWorker = require_GenericWorker();
|
|
41581
41581
|
var ARRAY_TYPE = USE_TYPEDARRAY ? "uint8array" : "array";
|
|
41582
41582
|
exports.magic = "\b\x00";
|
|
41583
|
-
function FlateWorker(
|
|
41584
|
-
GenericWorker.call(this, "FlateWorker/" +
|
|
41583
|
+
function FlateWorker(action19, options) {
|
|
41584
|
+
GenericWorker.call(this, "FlateWorker/" + action19);
|
|
41585
41585
|
this._pako = null;
|
|
41586
|
-
this._pakoAction =
|
|
41586
|
+
this._pakoAction = action19;
|
|
41587
41587
|
this._pakoOptions = options;
|
|
41588
41588
|
this.meta = {};
|
|
41589
41589
|
}
|
|
@@ -55522,10 +55522,10 @@ var init_partial_edits2 = __esm(() => {
|
|
|
55522
55522
|
});
|
|
55523
55523
|
|
|
55524
55524
|
// ../../_shared/mcp-tools/get-help-content.ts
|
|
55525
|
-
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\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- **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. Call `cerefox_get_help(topic: "server")`:\n it reports the server\'s own version and the operations it registers. 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;
|
|
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;
|
|
55526
55526
|
var init_get_help_content = __esm(() => {
|
|
55527
55527
|
HELP_SECTIONS = {
|
|
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_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.",
|
|
55529
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.',
|
|
55530
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`.',
|
|
55531
55531
|
"Update Workflow (ID-based -- preferred)": `## Update Workflow (ID-based -- preferred)
|
|
@@ -55539,7 +55539,7 @@ ingest(title="Same Title", content="...", document_id="abc123",
|
|
|
55539
55539
|
On a **conflict** error: get_document again (fresh content + fresh hash) -> merge your changes -> retry with the new hash.`,
|
|
55540
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```',
|
|
55541
55541
|
"Catch-Up Workflow": '## Catch-Up Workflow\n\n```\nmetadata_search(metadata_filter={"type": "decision-log"}, updated_since="2026-03-28T00:00:00Z")\n```',
|
|
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_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.',
|
|
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
55543
|
"Timestamps are UTC": `## Timestamps are UTC
|
|
55544
55544
|
|
|
55545
55545
|
Every timestamp Cerefox returns — \`created_at\` on audit entries, version
|
|
@@ -55589,6 +55589,13 @@ Each of these comes from a real agent session, and each is easy to make.
|
|
|
55589
55589
|
not catch it if your replacement text is longer than what it replaced, since
|
|
55590
55590
|
there is then no net loss to report.
|
|
55591
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
|
+
|
|
55592
55599
|
- **Never partial-edit to fix a partial edit.** If a write leaves unexpected
|
|
55593
55600
|
structure, stop. Use \`cerefox_list_versions\`, retrieve the last good version,
|
|
55594
55601
|
and re-ingest cleanly. Repairing edits with more edits compounds the damage.
|
|
@@ -55611,8 +55618,10 @@ Each of these comes from a real agent session, and each is easy to make.
|
|
|
55611
55618
|
needs \`cerefox_ingest\`.
|
|
55612
55619
|
|
|
55613
55620
|
- **If a capability seems missing from one server, suspect your client first.**
|
|
55614
|
-
Local and remote run the same code.
|
|
55615
|
-
|
|
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
|
|
55616
55625
|
disagrees with your tool list, the client is holding a list it fetched before
|
|
55617
55626
|
an upgrade — clients cache it at connect time. Ask the user to restart the
|
|
55618
55627
|
client. Do not record a capability difference between servers as a fact; every
|
|
@@ -55674,13 +55683,21 @@ async function handler3(supabase, args, ctx) {
|
|
|
55674
55683
|
const t = topic.toLowerCase();
|
|
55675
55684
|
const matched = HELP_SECTION_HEADINGS.filter((h) => h.toLowerCase().includes(t));
|
|
55676
55685
|
if (matched.length === 0) {
|
|
55677
|
-
return
|
|
55686
|
+
return serverIdentity() + `
|
|
55687
|
+
|
|
55688
|
+
---
|
|
55689
|
+
|
|
55690
|
+
` + `No help topic matched "${topic}".
|
|
55678
55691
|
|
|
55679
55692
|
` + `Available topics:
|
|
55680
55693
|
` + HELP_SECTION_HEADINGS.map((h) => ` - ${h}`).join(`
|
|
55681
55694
|
`) + "\n\nCall `cerefox_get_help()` with no topic for the full document.";
|
|
55682
55695
|
}
|
|
55683
|
-
return
|
|
55696
|
+
return serverIdentity() + `
|
|
55697
|
+
|
|
55698
|
+
---
|
|
55699
|
+
|
|
55700
|
+
` + matched.map((h) => HELP_SECTIONS[h]).join(`
|
|
55684
55701
|
|
|
55685
55702
|
---
|
|
55686
55703
|
|
|
@@ -56432,8 +56449,92 @@ var init_search = __esm(() => {
|
|
|
56432
56449
|
};
|
|
56433
56450
|
});
|
|
56434
56451
|
|
|
56435
|
-
// ../../_shared/mcp-tools/set-document-
|
|
56452
|
+
// ../../_shared/mcp-tools/set-document-metadata.ts
|
|
56436
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) {
|
|
56437
56538
|
const document_id = args.document_id?.trim();
|
|
56438
56539
|
const project_names_raw = args.project_names;
|
|
56439
56540
|
const author = args.author ?? "mcp-agent";
|
|
@@ -56494,7 +56595,7 @@ var init_set_document_projects = __esm(() => {
|
|
|
56494
56595
|
}
|
|
56495
56596
|
}
|
|
56496
56597
|
},
|
|
56497
|
-
handler:
|
|
56598
|
+
handler: handler11
|
|
56498
56599
|
};
|
|
56499
56600
|
});
|
|
56500
56601
|
|
|
@@ -56524,6 +56625,7 @@ var init_mcp_tools = __esm(() => {
|
|
|
56524
56625
|
init_list_versions();
|
|
56525
56626
|
init_metadata_search();
|
|
56526
56627
|
init_search();
|
|
56628
|
+
init_set_document_metadata();
|
|
56527
56629
|
init_set_document_projects();
|
|
56528
56630
|
init_types3();
|
|
56529
56631
|
init_types3();
|
|
@@ -56537,6 +56639,7 @@ var init_mcp_tools = __esm(() => {
|
|
|
56537
56639
|
metadataSearchTool,
|
|
56538
56640
|
listMetadataKeysTool,
|
|
56539
56641
|
listProjectsTool,
|
|
56642
|
+
setDocumentMetadataTool,
|
|
56540
56643
|
setDocumentProjectsTool,
|
|
56541
56644
|
auditLogTool,
|
|
56542
56645
|
setRelationTool,
|
|
@@ -57377,11 +57480,11 @@ async function runSyncSelfDocs(options = {}) {
|
|
|
57377
57480
|
printTable(outcomes.filter((o) => o.status === "error").map((o) => ({ topic: o.topic, error: o.detail.slice(0, 100) })));
|
|
57378
57481
|
}
|
|
57379
57482
|
}
|
|
57380
|
-
async function
|
|
57483
|
+
async function action21(options) {
|
|
57381
57484
|
await runSyncSelfDocs(options);
|
|
57382
57485
|
}
|
|
57383
57486
|
function registerSyncSelfDocs(program2) {
|
|
57384
|
-
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);
|
|
57385
57488
|
}
|
|
57386
57489
|
var init_sync_self_docs = __esm(() => {
|
|
57387
57490
|
init_cli_core();
|
|
@@ -62610,25 +62713,25 @@ class Protocol {
|
|
|
62610
62713
|
const error3 = McpError.fromError(ErrorCode.ConnectionClosed, "Connection closed");
|
|
62611
62714
|
this._transport = undefined;
|
|
62612
62715
|
this.onclose?.();
|
|
62613
|
-
for (const
|
|
62614
|
-
|
|
62716
|
+
for (const handler12 of responseHandlers.values()) {
|
|
62717
|
+
handler12(error3);
|
|
62615
62718
|
}
|
|
62616
62719
|
}
|
|
62617
62720
|
_onerror(error3) {
|
|
62618
62721
|
this.onerror?.(error3);
|
|
62619
62722
|
}
|
|
62620
62723
|
_onnotification(notification) {
|
|
62621
|
-
const
|
|
62622
|
-
if (
|
|
62724
|
+
const handler12 = this._notificationHandlers.get(notification.method) ?? this.fallbackNotificationHandler;
|
|
62725
|
+
if (handler12 === undefined) {
|
|
62623
62726
|
return;
|
|
62624
62727
|
}
|
|
62625
|
-
Promise.resolve().then(() =>
|
|
62728
|
+
Promise.resolve().then(() => handler12(notification)).catch((error3) => this._onerror(new Error(`Uncaught error in notification handler: ${error3}`)));
|
|
62626
62729
|
}
|
|
62627
62730
|
_onrequest(request, extra) {
|
|
62628
|
-
const
|
|
62731
|
+
const handler12 = this._requestHandlers.get(request.method) ?? this.fallbackRequestHandler;
|
|
62629
62732
|
const capturedTransport = this._transport;
|
|
62630
62733
|
const relatedTaskId = request.params?._meta?.[RELATED_TASK_META_KEY]?.taskId;
|
|
62631
|
-
if (
|
|
62734
|
+
if (handler12 === undefined) {
|
|
62632
62735
|
const errorResponse = {
|
|
62633
62736
|
jsonrpc: "2.0",
|
|
62634
62737
|
id: request.id,
|
|
@@ -62692,7 +62795,7 @@ class Protocol {
|
|
|
62692
62795
|
if (taskCreationParams) {
|
|
62693
62796
|
this.assertTaskHandlerCapability(request.method);
|
|
62694
62797
|
}
|
|
62695
|
-
}).then(() =>
|
|
62798
|
+
}).then(() => handler12(request, fullExtra)).then(async (result) => {
|
|
62696
62799
|
if (abortController.signal.aborted) {
|
|
62697
62800
|
return;
|
|
62698
62801
|
}
|
|
@@ -62741,8 +62844,8 @@ class Protocol {
|
|
|
62741
62844
|
_onprogress(notification) {
|
|
62742
62845
|
const { progressToken, ...params } = notification.params;
|
|
62743
62846
|
const messageId = Number(progressToken);
|
|
62744
|
-
const
|
|
62745
|
-
if (!
|
|
62847
|
+
const handler12 = this._progressHandlers.get(messageId);
|
|
62848
|
+
if (!handler12) {
|
|
62746
62849
|
this._onerror(new Error(`Received a progress notification for an unknown token: ${JSON.stringify(notification)}`));
|
|
62747
62850
|
return;
|
|
62748
62851
|
}
|
|
@@ -62759,7 +62862,7 @@ class Protocol {
|
|
|
62759
62862
|
return;
|
|
62760
62863
|
}
|
|
62761
62864
|
}
|
|
62762
|
-
|
|
62865
|
+
handler12(params);
|
|
62763
62866
|
}
|
|
62764
62867
|
_onresponse(response) {
|
|
62765
62868
|
const messageId = Number(response.id);
|
|
@@ -62774,8 +62877,8 @@ class Protocol {
|
|
|
62774
62877
|
}
|
|
62775
62878
|
return;
|
|
62776
62879
|
}
|
|
62777
|
-
const
|
|
62778
|
-
if (
|
|
62880
|
+
const handler12 = this._responseHandlers.get(messageId);
|
|
62881
|
+
if (handler12 === undefined) {
|
|
62779
62882
|
this._onerror(new Error(`Received a response for an unknown message ID: ${JSON.stringify(response)}`));
|
|
62780
62883
|
return;
|
|
62781
62884
|
}
|
|
@@ -62796,10 +62899,10 @@ class Protocol {
|
|
|
62796
62899
|
this._progressHandlers.delete(messageId);
|
|
62797
62900
|
}
|
|
62798
62901
|
if (isJSONRPCResultResponse(response)) {
|
|
62799
|
-
|
|
62902
|
+
handler12(response);
|
|
62800
62903
|
} else {
|
|
62801
62904
|
const error3 = McpError.fromError(response.error.code, response.error.message, response.error.data);
|
|
62802
|
-
|
|
62905
|
+
handler12(error3);
|
|
62803
62906
|
}
|
|
62804
62907
|
}
|
|
62805
62908
|
get transport() {
|
|
@@ -62962,9 +63065,9 @@ class Protocol {
|
|
|
62962
63065
|
const relatedTaskId = relatedTask?.taskId;
|
|
62963
63066
|
if (relatedTaskId) {
|
|
62964
63067
|
const responseResolver = (response) => {
|
|
62965
|
-
const
|
|
62966
|
-
if (
|
|
62967
|
-
|
|
63068
|
+
const handler12 = this._responseHandlers.get(messageId);
|
|
63069
|
+
if (handler12) {
|
|
63070
|
+
handler12(response);
|
|
62968
63071
|
} else {
|
|
62969
63072
|
this._onerror(new Error(`Response handler missing for side-channeled request ${messageId}`));
|
|
62970
63073
|
}
|
|
@@ -63073,12 +63176,12 @@ class Protocol {
|
|
|
63073
63176
|
}
|
|
63074
63177
|
await this._transport.send(jsonrpcNotification, options);
|
|
63075
63178
|
}
|
|
63076
|
-
setRequestHandler(requestSchema,
|
|
63179
|
+
setRequestHandler(requestSchema, handler12) {
|
|
63077
63180
|
const method = getMethodLiteral(requestSchema);
|
|
63078
63181
|
this.assertRequestHandlerCapability(method);
|
|
63079
63182
|
this._requestHandlers.set(method, (request, extra) => {
|
|
63080
63183
|
const parsed = parseWithCompat(requestSchema, request);
|
|
63081
|
-
return Promise.resolve(
|
|
63184
|
+
return Promise.resolve(handler12(parsed, extra));
|
|
63082
63185
|
});
|
|
63083
63186
|
}
|
|
63084
63187
|
removeRequestHandler(method) {
|
|
@@ -63089,11 +63192,11 @@ class Protocol {
|
|
|
63089
63192
|
throw new Error(`A request handler for ${method} already exists, which would be overridden`);
|
|
63090
63193
|
}
|
|
63091
63194
|
}
|
|
63092
|
-
setNotificationHandler(notificationSchema,
|
|
63195
|
+
setNotificationHandler(notificationSchema, handler12) {
|
|
63093
63196
|
const method = getMethodLiteral(notificationSchema);
|
|
63094
63197
|
this._notificationHandlers.set(method, (notification) => {
|
|
63095
63198
|
const parsed = parseWithCompat(notificationSchema, notification);
|
|
63096
|
-
return Promise.resolve(
|
|
63199
|
+
return Promise.resolve(handler12(parsed));
|
|
63097
63200
|
});
|
|
63098
63201
|
}
|
|
63099
63202
|
removeNotificationHandler(method) {
|
|
@@ -70035,7 +70138,7 @@ var init_server2 = __esm(() => {
|
|
|
70035
70138
|
}
|
|
70036
70139
|
this._capabilities = mergeCapabilities(this._capabilities, capabilities);
|
|
70037
70140
|
}
|
|
70038
|
-
setRequestHandler(requestSchema,
|
|
70141
|
+
setRequestHandler(requestSchema, handler12) {
|
|
70039
70142
|
const shape = getObjectShape(requestSchema);
|
|
70040
70143
|
const methodSchema = shape?.method;
|
|
70041
70144
|
if (!methodSchema) {
|
|
@@ -70054,7 +70157,7 @@ var init_server2 = __esm(() => {
|
|
|
70054
70157
|
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${errorMessage}`);
|
|
70055
70158
|
}
|
|
70056
70159
|
const { params } = validatedRequest.data;
|
|
70057
|
-
const result = await Promise.resolve(
|
|
70160
|
+
const result = await Promise.resolve(handler12(request, extra));
|
|
70058
70161
|
if (params.task) {
|
|
70059
70162
|
const taskValidationResult = safeParse2(CreateTaskResultSchema, result);
|
|
70060
70163
|
if (!taskValidationResult.success) {
|
|
@@ -70072,7 +70175,7 @@ var init_server2 = __esm(() => {
|
|
|
70072
70175
|
};
|
|
70073
70176
|
return super.setRequestHandler(requestSchema, wrappedHandler);
|
|
70074
70177
|
}
|
|
70075
|
-
return super.setRequestHandler(requestSchema,
|
|
70178
|
+
return super.setRequestHandler(requestSchema, handler12);
|
|
70076
70179
|
}
|
|
70077
70180
|
assertCapabilityForMethod(method) {
|
|
70078
70181
|
switch (method) {
|
|
@@ -73024,11 +73127,89 @@ function registerDocumentRestore(parent) {
|
|
|
73024
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);
|
|
73025
73128
|
}
|
|
73026
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
|
+
|
|
73027
73208
|
// src/cli/commands/document-set-projects.ts
|
|
73028
73209
|
init_cli_core();
|
|
73029
73210
|
init__projects();
|
|
73030
73211
|
init_client();
|
|
73031
|
-
async function
|
|
73212
|
+
async function action13(documentId, projectNames, options) {
|
|
73032
73213
|
const names = projectNames ?? [];
|
|
73033
73214
|
if (options.clear && names.length > 0) {
|
|
73034
73215
|
throw userError("Pass either project names or --clear, not both.", "Use --clear on its own to remove the document from all projects.");
|
|
@@ -73058,7 +73239,7 @@ async function action12(documentId, projectNames, options) {
|
|
|
73058
73239
|
println(c.dim(" This REPLACED the previous set — any project not listed is no longer associated."));
|
|
73059
73240
|
}
|
|
73060
73241
|
function registerDocumentSetProjects(parent) {
|
|
73061
|
-
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);
|
|
73062
73243
|
}
|
|
73063
73244
|
|
|
73064
73245
|
// src/cli/commands/guides.ts
|
|
@@ -73108,7 +73289,7 @@ function registerGuides(parent) {
|
|
|
73108
73289
|
// src/cli/commands/project-create.ts
|
|
73109
73290
|
init_cli_core();
|
|
73110
73291
|
init_client();
|
|
73111
|
-
async function
|
|
73292
|
+
async function action14(name, options) {
|
|
73112
73293
|
const trimmed = name.trim();
|
|
73113
73294
|
if (!trimmed)
|
|
73114
73295
|
throw userError("Project name is required.");
|
|
@@ -73120,14 +73301,14 @@ async function action13(name, options) {
|
|
|
73120
73301
|
println(c.green(`✓ Created project "${data.name}" (id: ${data.id}).`));
|
|
73121
73302
|
}
|
|
73122
73303
|
function registerProjectCreate(parent) {
|
|
73123
|
-
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);
|
|
73124
73305
|
}
|
|
73125
73306
|
|
|
73126
73307
|
// src/cli/commands/project-edit.ts
|
|
73127
73308
|
init_cli_core();
|
|
73128
73309
|
init_client();
|
|
73129
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;
|
|
73130
|
-
async function
|
|
73311
|
+
async function action15(target, options) {
|
|
73131
73312
|
const update = {};
|
|
73132
73313
|
if (options.name !== undefined) {
|
|
73133
73314
|
const n = options.name.trim();
|
|
@@ -73154,7 +73335,7 @@ async function action14(target, options) {
|
|
|
73154
73335
|
println(c.green(`✓ Updated project "${data.name}" (id: ${data.id}).`));
|
|
73155
73336
|
}
|
|
73156
73337
|
function registerProjectEdit(parent) {
|
|
73157
|
-
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);
|
|
73158
73339
|
}
|
|
73159
73340
|
|
|
73160
73341
|
// src/cli/commands/version-archive.ts
|
|
@@ -77261,7 +77442,7 @@ function symbol(status) {
|
|
|
77261
77442
|
return cErr.dim("ℹ");
|
|
77262
77443
|
}
|
|
77263
77444
|
}
|
|
77264
|
-
async function
|
|
77445
|
+
async function action16(options) {
|
|
77265
77446
|
const useSpinner = !options.json && process.stderr.isTTY;
|
|
77266
77447
|
const spinner = useSpinner ? ora({ text: "Starting checks…", spinner: "dots", stream: process.stderr }).start() : null;
|
|
77267
77448
|
const results = await runAllChecks({
|
|
@@ -77325,13 +77506,13 @@ async function action15(options) {
|
|
|
77325
77506
|
process.exit(1);
|
|
77326
77507
|
}
|
|
77327
77508
|
function registerDoctor(program2) {
|
|
77328
|
-
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);
|
|
77329
77510
|
}
|
|
77330
77511
|
|
|
77331
77512
|
// src/cli/commands/get-audit-log.ts
|
|
77332
77513
|
init_cli_core();
|
|
77333
77514
|
init_client();
|
|
77334
|
-
async function
|
|
77515
|
+
async function action17(options) {
|
|
77335
77516
|
const limit = parsePositiveInt(options.limit, "--limit", 50);
|
|
77336
77517
|
const client = getClient();
|
|
77337
77518
|
const data = await client.rpc("cerefox_list_audit_entries", {
|
|
@@ -77369,7 +77550,7 @@ async function action16(options) {
|
|
|
77369
77550
|
})));
|
|
77370
77551
|
}
|
|
77371
77552
|
function registerGetAuditLog(program2) {
|
|
77372
|
-
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);
|
|
77373
77554
|
}
|
|
77374
77555
|
|
|
77375
77556
|
// src/cli/commands/relation.ts
|
|
@@ -77505,7 +77686,7 @@ init_cli_core();
|
|
|
77505
77686
|
init_cli_core();
|
|
77506
77687
|
init_partial_edits();
|
|
77507
77688
|
init_client();
|
|
77508
|
-
async function
|
|
77689
|
+
async function action18(documentId, options) {
|
|
77509
77690
|
const section = (options.section ?? "").trim() || null;
|
|
77510
77691
|
if (section && options.outline) {
|
|
77511
77692
|
throw userError("Pass either --outline (the whole structure) or --section (one section's text), not both.");
|
|
@@ -77602,7 +77783,7 @@ async function action17(documentId, options) {
|
|
|
77602
77783
|
println(doc.full_content);
|
|
77603
77784
|
}
|
|
77604
77785
|
function registerGetDoc(program2) {
|
|
77605
|
-
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(
|
|
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);
|
|
77606
77787
|
}
|
|
77607
77788
|
|
|
77608
77789
|
// src/cli/commands/ingest.ts
|
|
@@ -78236,7 +78417,7 @@ async function readContent(path, paste) {
|
|
|
78236
78417
|
const titleFromPath = basename2(path, extname3(path));
|
|
78237
78418
|
return { content, titleFromPath };
|
|
78238
78419
|
}
|
|
78239
|
-
async function
|
|
78420
|
+
async function action19(path, options) {
|
|
78240
78421
|
const { content, titleFromPath } = await readContent(path, Boolean(options.paste));
|
|
78241
78422
|
const updatingById = Boolean(options.documentId);
|
|
78242
78423
|
let title = options.title ?? (updatingById ? null : titleFromPath);
|
|
@@ -78327,7 +78508,7 @@ async function action18(path, options) {
|
|
|
78327
78508
|
}
|
|
78328
78509
|
}
|
|
78329
78510
|
function registerIngest(program2) {
|
|
78330
|
-
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);
|
|
78331
78512
|
}
|
|
78332
78513
|
|
|
78333
78514
|
// src/cli/commands/document-partial-edit.ts
|
|
@@ -78430,7 +78611,7 @@ function walk(dir, extensions) {
|
|
|
78430
78611
|
}
|
|
78431
78612
|
return files;
|
|
78432
78613
|
}
|
|
78433
|
-
async function
|
|
78614
|
+
async function action20(dir, options) {
|
|
78434
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));
|
|
78435
78616
|
const files = walk(dir, extensions);
|
|
78436
78617
|
if (files.length === 0) {
|
|
@@ -78505,7 +78686,7 @@ async function action19(dir, options) {
|
|
|
78505
78686
|
}
|
|
78506
78687
|
}
|
|
78507
78688
|
function registerIngestDir(program2) {
|
|
78508
|
-
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);
|
|
78509
78690
|
}
|
|
78510
78691
|
|
|
78511
78692
|
// src/cli/commands/init.ts
|
|
@@ -78836,7 +79017,7 @@ function writeAnswersTo(target, answers) {
|
|
|
78836
79017
|
}
|
|
78837
79018
|
}
|
|
78838
79019
|
}
|
|
78839
|
-
async function
|
|
79020
|
+
async function action22(options) {
|
|
78840
79021
|
const homeEnv = join11(homedir7(), USER_STATE_DIR_NAME, ".env");
|
|
78841
79022
|
const cwdEnv = join11(process.cwd(), ".env");
|
|
78842
79023
|
const explicitDir = (process.env.CEREFOX_CONFIG_DIR ?? "").trim();
|
|
@@ -78945,13 +79126,13 @@ async function action21(options) {
|
|
|
78945
79126
|
await postWriteLifecycle(target, options);
|
|
78946
79127
|
}
|
|
78947
79128
|
function registerInit(program2) {
|
|
78948
|
-
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);
|
|
78949
79130
|
}
|
|
78950
79131
|
|
|
78951
79132
|
// src/cli/commands/list-docs.ts
|
|
78952
79133
|
init_cli_core();
|
|
78953
79134
|
init_client();
|
|
78954
|
-
async function
|
|
79135
|
+
async function action23(options) {
|
|
78955
79136
|
const deleted = !!options.deleted;
|
|
78956
79137
|
const limit = parsePositiveInt(options.limit, "--limit", 100);
|
|
78957
79138
|
const client = getClient();
|
|
@@ -79007,13 +79188,13 @@ async function action22(options) {
|
|
|
79007
79188
|
}));
|
|
79008
79189
|
}
|
|
79009
79190
|
function registerListDocs(program2) {
|
|
79010
|
-
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);
|
|
79011
79192
|
}
|
|
79012
79193
|
|
|
79013
79194
|
// src/cli/commands/list-metadata-keys.ts
|
|
79014
79195
|
init_cli_core();
|
|
79015
79196
|
init_client();
|
|
79016
|
-
async function
|
|
79197
|
+
async function action24(options) {
|
|
79017
79198
|
const client = getClient();
|
|
79018
79199
|
const data = await client.rpc("cerefox_list_metadata_keys");
|
|
79019
79200
|
if (data === null) {
|
|
@@ -79041,13 +79222,13 @@ async function action23(options) {
|
|
|
79041
79222
|
})));
|
|
79042
79223
|
}
|
|
79043
79224
|
function registerListMetadataKeys(program2) {
|
|
79044
|
-
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);
|
|
79045
79226
|
}
|
|
79046
79227
|
|
|
79047
79228
|
// src/cli/commands/list-projects.ts
|
|
79048
79229
|
init_cli_core();
|
|
79049
79230
|
init_client();
|
|
79050
|
-
async function
|
|
79231
|
+
async function action25(options) {
|
|
79051
79232
|
const client = getClient();
|
|
79052
79233
|
const { data, error: error2 } = await client.raw.from("cerefox_projects").select("id, name, description, created_at").order("name", { ascending: true });
|
|
79053
79234
|
if (error2) {
|
|
@@ -79076,13 +79257,13 @@ async function action24(options) {
|
|
|
79076
79257
|
})), "(no projects)");
|
|
79077
79258
|
}
|
|
79078
79259
|
function registerListProjects(program2) {
|
|
79079
|
-
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);
|
|
79080
79261
|
}
|
|
79081
79262
|
|
|
79082
79263
|
// src/cli/commands/list-versions.ts
|
|
79083
79264
|
init_cli_core();
|
|
79084
79265
|
init_client();
|
|
79085
|
-
async function
|
|
79266
|
+
async function action26(documentId, options) {
|
|
79086
79267
|
const client = getClient();
|
|
79087
79268
|
const data = await client.rpc("cerefox_list_document_versions", {
|
|
79088
79269
|
p_document_id: documentId
|
|
@@ -79123,7 +79304,7 @@ async function action25(documentId, options) {
|
|
|
79123
79304
|
})));
|
|
79124
79305
|
}
|
|
79125
79306
|
function registerListVersions(program2) {
|
|
79126
|
-
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);
|
|
79127
79308
|
}
|
|
79128
79309
|
|
|
79129
79310
|
// src/cli/commands/mcp.ts
|
|
@@ -79155,7 +79336,7 @@ function registerEmbedderWarmup(program2) {
|
|
|
79155
79336
|
// src/cli/commands/metadata-search.ts
|
|
79156
79337
|
init_cli_core();
|
|
79157
79338
|
init_client();
|
|
79158
|
-
async function
|
|
79339
|
+
async function action27(options) {
|
|
79159
79340
|
const metadataFilter = parseJsonObjectArg(options.metadataFilter, "--metadata-filter") ?? {};
|
|
79160
79341
|
if (Object.keys(metadataFilter).length === 0 && !options.projectName && !options.updatedSince && !options.createdSince) {
|
|
79161
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).`);
|
|
@@ -79218,7 +79399,7 @@ async function action26(options) {
|
|
|
79218
79399
|
}
|
|
79219
79400
|
}
|
|
79220
79401
|
function registerMetadataSearch(program2) {
|
|
79221
|
-
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);
|
|
79222
79403
|
}
|
|
79223
79404
|
|
|
79224
79405
|
// src/cli/commands/reindex.ts
|
|
@@ -79244,7 +79425,7 @@ function warnLargeBulkWrite(opts) {
|
|
|
79244
79425
|
}
|
|
79245
79426
|
|
|
79246
79427
|
// src/cli/commands/reindex.ts
|
|
79247
|
-
async function
|
|
79428
|
+
async function action28(options) {
|
|
79248
79429
|
const settings = loadSettings();
|
|
79249
79430
|
if (!settings.supabaseUrl || !settings.supabaseKey) {
|
|
79250
79431
|
throw userError("Supabase credentials not configured — run `cerefox init` first.");
|
|
@@ -79334,7 +79515,7 @@ ${c2.content}`;
|
|
|
79334
79515
|
}
|
|
79335
79516
|
}
|
|
79336
79517
|
function registerReindex(program2) {
|
|
79337
|
-
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);
|
|
79338
79519
|
}
|
|
79339
79520
|
|
|
79340
79521
|
// src/cli/commands/migrate-format.ts
|
|
@@ -79342,7 +79523,7 @@ init_cli_core();
|
|
|
79342
79523
|
init_config();
|
|
79343
79524
|
init_client();
|
|
79344
79525
|
var CURRENT_FORMAT = 2;
|
|
79345
|
-
async function
|
|
79526
|
+
async function action29(options) {
|
|
79346
79527
|
const settings = loadSettings();
|
|
79347
79528
|
const client = getClient();
|
|
79348
79529
|
const supabase = client.raw;
|
|
@@ -79469,7 +79650,7 @@ async function action28(options) {
|
|
|
79469
79650
|
}
|
|
79470
79651
|
}
|
|
79471
79652
|
function registerMigrateFormat(program2) {
|
|
79472
|
-
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);
|
|
79473
79654
|
}
|
|
79474
79655
|
|
|
79475
79656
|
// src/cli/commands/restore.ts
|
|
@@ -79499,7 +79680,7 @@ function resolveBackupFile(target) {
|
|
|
79499
79680
|
}
|
|
79500
79681
|
return join12(path, candidates[0].name);
|
|
79501
79682
|
}
|
|
79502
|
-
async function
|
|
79683
|
+
async function action30(target, options) {
|
|
79503
79684
|
const file = resolveBackupFile(target);
|
|
79504
79685
|
let payload;
|
|
79505
79686
|
try {
|
|
@@ -79634,7 +79815,7 @@ async function action29(target, options) {
|
|
|
79634
79815
|
}
|
|
79635
79816
|
}
|
|
79636
79817
|
function registerRestore(program2) {
|
|
79637
|
-
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);
|
|
79638
79819
|
}
|
|
79639
79820
|
|
|
79640
79821
|
// src/cli/commands/search.ts
|
|
@@ -79659,7 +79840,7 @@ async function embedQuery(query) {
|
|
|
79659
79840
|
}
|
|
79660
79841
|
|
|
79661
79842
|
// src/cli/commands/search.ts
|
|
79662
|
-
async function
|
|
79843
|
+
async function action31(query, options) {
|
|
79663
79844
|
if (!query || query.trim() === "") {
|
|
79664
79845
|
throw userError("Empty query.");
|
|
79665
79846
|
}
|
|
@@ -79825,7 +80006,7 @@ async function action30(query, options) {
|
|
|
79825
80006
|
}
|
|
79826
80007
|
}
|
|
79827
80008
|
function registerSearch(program2) {
|
|
79828
|
-
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);
|
|
79829
80010
|
}
|
|
79830
80011
|
|
|
79831
80012
|
// src/cli/commands/self-update.ts
|
|
@@ -79872,7 +80053,7 @@ async function fetchLatestVersion() {
|
|
|
79872
80053
|
}
|
|
79873
80054
|
return body.version;
|
|
79874
80055
|
}
|
|
79875
|
-
async function
|
|
80056
|
+
async function action32(options) {
|
|
79876
80057
|
let target;
|
|
79877
80058
|
try {
|
|
79878
80059
|
target = options.version ?? await fetchLatestVersion();
|
|
@@ -79925,7 +80106,7 @@ async function action31(options) {
|
|
|
79925
80106
|
}
|
|
79926
80107
|
function registerSelfUpdate(program2) {
|
|
79927
80108
|
const desc = "Upgrade Cerefox in place. Alias: `cerefox upgrade`.";
|
|
79928
|
-
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);
|
|
79929
80110
|
declaration(program2.command("self-update"));
|
|
79930
80111
|
declaration(program2.command("upgrade"));
|
|
79931
80112
|
}
|
|
@@ -79944,7 +80125,7 @@ function symbol2(status) {
|
|
|
79944
80125
|
return cErr.dim("ℹ");
|
|
79945
80126
|
}
|
|
79946
80127
|
}
|
|
79947
|
-
async function
|
|
80128
|
+
async function action33(options) {
|
|
79948
80129
|
const useSpinner = !options.json && process.stderr.isTTY;
|
|
79949
80130
|
const spinner = useSpinner ? ora({ text: "Starting checks…", spinner: "dots", stream: process.stderr }).start() : null;
|
|
79950
80131
|
const results = await runFastChecks({
|
|
@@ -79965,7 +80146,7 @@ async function action32(options) {
|
|
|
79965
80146
|
}
|
|
79966
80147
|
}
|
|
79967
80148
|
function registerStatus(program2) {
|
|
79968
|
-
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);
|
|
79969
80150
|
}
|
|
79970
80151
|
|
|
79971
80152
|
// src/cli/commands/token.ts
|
|
@@ -79998,10 +80179,10 @@ function upsertEnvVar(path, key, value, opts = {}) {
|
|
|
79998
80179
|
}
|
|
79999
80180
|
const re = new RegExp(`^(\\s*)${escapeRegExp(key)}=.*$`, "m");
|
|
80000
80181
|
let next;
|
|
80001
|
-
let
|
|
80182
|
+
let action34;
|
|
80002
80183
|
if (re.test(original)) {
|
|
80003
80184
|
next = original.replace(re, `$1${line}`);
|
|
80004
|
-
|
|
80185
|
+
action34 = "updated";
|
|
80005
80186
|
} else {
|
|
80006
80187
|
const base = original.endsWith(`
|
|
80007
80188
|
`) ? original : `${original}
|
|
@@ -80009,10 +80190,10 @@ function upsertEnvVar(path, key, value, opts = {}) {
|
|
|
80009
80190
|
next = `${base}
|
|
80010
80191
|
${header}${line}
|
|
80011
80192
|
`;
|
|
80012
|
-
|
|
80193
|
+
action34 = "added";
|
|
80013
80194
|
}
|
|
80014
80195
|
writeFileSync5(path, next);
|
|
80015
|
-
return { path, action:
|
|
80196
|
+
return { path, action: action34, backupPath };
|
|
80016
80197
|
}
|
|
80017
80198
|
function readEnvVar(path, key) {
|
|
80018
80199
|
if (!existsSync14(path))
|
|
@@ -80190,13 +80371,13 @@ var WSContext = class {
|
|
|
80190
80371
|
this.#init.close(code, reason);
|
|
80191
80372
|
}
|
|
80192
80373
|
};
|
|
80193
|
-
var defineWebSocketHelper = (
|
|
80374
|
+
var defineWebSocketHelper = (handler12) => {
|
|
80194
80375
|
return (...args) => {
|
|
80195
80376
|
if (typeof args[0] === "function") {
|
|
80196
80377
|
const [createEvents, options] = args;
|
|
80197
80378
|
return async function upgradeWebSocket(c2, next) {
|
|
80198
80379
|
const events = await createEvents(c2);
|
|
80199
|
-
const result = await
|
|
80380
|
+
const result = await handler12(c2, events, options);
|
|
80200
80381
|
if (result) {
|
|
80201
80382
|
return result;
|
|
80202
80383
|
}
|
|
@@ -80205,7 +80386,7 @@ var defineWebSocketHelper = (handler11) => {
|
|
|
80205
80386
|
} else {
|
|
80206
80387
|
const [c2, events, options] = args;
|
|
80207
80388
|
return (async () => {
|
|
80208
|
-
const upgraded = await
|
|
80389
|
+
const upgraded = await handler12(c2, events, options);
|
|
80209
80390
|
if (!upgraded) {
|
|
80210
80391
|
throw new Error("Failed to upgrade WebSocket");
|
|
80211
80392
|
}
|
|
@@ -81798,16 +81979,16 @@ var compose = (middleware, onError, onNotFound) => {
|
|
|
81798
81979
|
index = i;
|
|
81799
81980
|
let res;
|
|
81800
81981
|
let isError = false;
|
|
81801
|
-
let
|
|
81982
|
+
let handler12;
|
|
81802
81983
|
if (middleware[i]) {
|
|
81803
|
-
|
|
81984
|
+
handler12 = middleware[i][0][0];
|
|
81804
81985
|
context2.req.routeIndex = i;
|
|
81805
81986
|
} else {
|
|
81806
|
-
|
|
81987
|
+
handler12 = i === middleware.length && next || undefined;
|
|
81807
81988
|
}
|
|
81808
|
-
if (
|
|
81989
|
+
if (handler12) {
|
|
81809
81990
|
try {
|
|
81810
|
-
res = await
|
|
81991
|
+
res = await handler12(context2, () => dispatch(i + 1));
|
|
81811
81992
|
} catch (err) {
|
|
81812
81993
|
if (err instanceof Error && onError) {
|
|
81813
81994
|
context2.error = err;
|
|
@@ -82491,8 +82672,8 @@ var Hono = class _Hono {
|
|
|
82491
82672
|
} else {
|
|
82492
82673
|
this.#addRoute(method, this.#path, args1);
|
|
82493
82674
|
}
|
|
82494
|
-
args.forEach((
|
|
82495
|
-
this.#addRoute(method, this.#path,
|
|
82675
|
+
args.forEach((handler12) => {
|
|
82676
|
+
this.#addRoute(method, this.#path, handler12);
|
|
82496
82677
|
});
|
|
82497
82678
|
return this;
|
|
82498
82679
|
};
|
|
@@ -82501,8 +82682,8 @@ var Hono = class _Hono {
|
|
|
82501
82682
|
for (const p of [path].flat()) {
|
|
82502
82683
|
this.#path = p;
|
|
82503
82684
|
for (const m of [method].flat()) {
|
|
82504
|
-
handlers.map((
|
|
82505
|
-
this.#addRoute(m.toUpperCase(), this.#path,
|
|
82685
|
+
handlers.map((handler12) => {
|
|
82686
|
+
this.#addRoute(m.toUpperCase(), this.#path, handler12);
|
|
82506
82687
|
});
|
|
82507
82688
|
}
|
|
82508
82689
|
}
|
|
@@ -82515,8 +82696,8 @@ var Hono = class _Hono {
|
|
|
82515
82696
|
this.#path = "*";
|
|
82516
82697
|
handlers.unshift(arg1);
|
|
82517
82698
|
}
|
|
82518
|
-
handlers.forEach((
|
|
82519
|
-
this.#addRoute(METHOD_NAME_ALL, this.#path,
|
|
82699
|
+
handlers.forEach((handler12) => {
|
|
82700
|
+
this.#addRoute(METHOD_NAME_ALL, this.#path, handler12);
|
|
82520
82701
|
});
|
|
82521
82702
|
return this;
|
|
82522
82703
|
};
|
|
@@ -82539,14 +82720,14 @@ var Hono = class _Hono {
|
|
|
82539
82720
|
route(path, app) {
|
|
82540
82721
|
const subApp = this.basePath(path);
|
|
82541
82722
|
app.routes.map((r) => {
|
|
82542
|
-
let
|
|
82723
|
+
let handler12;
|
|
82543
82724
|
if (app.errorHandler === errorHandler) {
|
|
82544
|
-
|
|
82725
|
+
handler12 = r.handler;
|
|
82545
82726
|
} else {
|
|
82546
|
-
|
|
82547
|
-
|
|
82727
|
+
handler12 = async (c2, next) => (await compose([], app.errorHandler)(c2, () => r.handler(c2, next))).res;
|
|
82728
|
+
handler12[COMPOSED_HANDLER] = r.handler;
|
|
82548
82729
|
}
|
|
82549
|
-
subApp.#addRoute(r.method, r.path,
|
|
82730
|
+
subApp.#addRoute(r.method, r.path, handler12, r.basePath);
|
|
82550
82731
|
});
|
|
82551
82732
|
return this;
|
|
82552
82733
|
}
|
|
@@ -82555,12 +82736,12 @@ var Hono = class _Hono {
|
|
|
82555
82736
|
subApp._basePath = mergePath(this._basePath, path);
|
|
82556
82737
|
return subApp;
|
|
82557
82738
|
}
|
|
82558
|
-
onError = (
|
|
82559
|
-
this.errorHandler =
|
|
82739
|
+
onError = (handler12) => {
|
|
82740
|
+
this.errorHandler = handler12;
|
|
82560
82741
|
return this;
|
|
82561
82742
|
};
|
|
82562
|
-
notFound = (
|
|
82563
|
-
this.#notFoundHandler =
|
|
82743
|
+
notFound = (handler12) => {
|
|
82744
|
+
this.#notFoundHandler = handler12;
|
|
82564
82745
|
return this;
|
|
82565
82746
|
};
|
|
82566
82747
|
mount(path, applicationHandler, options) {
|
|
@@ -82597,26 +82778,26 @@ var Hono = class _Hono {
|
|
|
82597
82778
|
return new Request(url, request);
|
|
82598
82779
|
};
|
|
82599
82780
|
})();
|
|
82600
|
-
const
|
|
82781
|
+
const handler12 = async (c2, next) => {
|
|
82601
82782
|
const res = await applicationHandler(replaceRequest(c2.req.raw), ...getOptions(c2));
|
|
82602
82783
|
if (res) {
|
|
82603
82784
|
return res;
|
|
82604
82785
|
}
|
|
82605
82786
|
await next();
|
|
82606
82787
|
};
|
|
82607
|
-
this.#addRoute(METHOD_NAME_ALL, mergePath(path, "*"),
|
|
82788
|
+
this.#addRoute(METHOD_NAME_ALL, mergePath(path, "*"), handler12);
|
|
82608
82789
|
return this;
|
|
82609
82790
|
}
|
|
82610
|
-
#addRoute(method, path,
|
|
82791
|
+
#addRoute(method, path, handler12, baseRoutePath) {
|
|
82611
82792
|
method = method.toUpperCase();
|
|
82612
82793
|
path = mergePath(this._basePath, path);
|
|
82613
82794
|
const r = {
|
|
82614
82795
|
basePath: baseRoutePath !== undefined ? mergePath(this._basePath, baseRoutePath) : this._basePath,
|
|
82615
82796
|
path,
|
|
82616
82797
|
method,
|
|
82617
|
-
handler:
|
|
82798
|
+
handler: handler12
|
|
82618
82799
|
};
|
|
82619
|
-
this.router.add(method, path, [
|
|
82800
|
+
this.router.add(method, path, [handler12, r]);
|
|
82620
82801
|
this.routes.push(r);
|
|
82621
82802
|
}
|
|
82622
82803
|
#handleError(err, c2) {
|
|
@@ -82941,7 +83122,7 @@ var RegExpRouter = class {
|
|
|
82941
83122
|
this.#middleware = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
|
|
82942
83123
|
this.#routes = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
|
|
82943
83124
|
}
|
|
82944
|
-
add(method, path,
|
|
83125
|
+
add(method, path, handler12) {
|
|
82945
83126
|
const middleware = this.#middleware;
|
|
82946
83127
|
const routes = this.#routes;
|
|
82947
83128
|
if (!middleware || !routes) {
|
|
@@ -82971,13 +83152,13 @@ var RegExpRouter = class {
|
|
|
82971
83152
|
Object.keys(middleware).forEach((m) => {
|
|
82972
83153
|
if (method === METHOD_NAME_ALL || method === m) {
|
|
82973
83154
|
Object.keys(middleware[m]).forEach((p) => {
|
|
82974
|
-
re.test(p) && middleware[m][p].push([
|
|
83155
|
+
re.test(p) && middleware[m][p].push([handler12, paramCount]);
|
|
82975
83156
|
});
|
|
82976
83157
|
}
|
|
82977
83158
|
});
|
|
82978
83159
|
Object.keys(routes).forEach((m) => {
|
|
82979
83160
|
if (method === METHOD_NAME_ALL || method === m) {
|
|
82980
|
-
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]));
|
|
82981
83162
|
}
|
|
82982
83163
|
});
|
|
82983
83164
|
return;
|
|
@@ -82990,7 +83171,7 @@ var RegExpRouter = class {
|
|
|
82990
83171
|
routes[m][path2] ||= [
|
|
82991
83172
|
...findMiddleware(middleware[m], path2) || findMiddleware(middleware[METHOD_NAME_ALL], path2) || []
|
|
82992
83173
|
];
|
|
82993
|
-
routes[m][path2].push([
|
|
83174
|
+
routes[m][path2].push([handler12, paramCount - len + i + 1]);
|
|
82994
83175
|
}
|
|
82995
83176
|
});
|
|
82996
83177
|
}
|
|
@@ -83039,21 +83220,21 @@ var PreparedRegExpRouter = class {
|
|
|
83039
83220
|
matcher[1].forEach((list) => list && list.push(handlerData));
|
|
83040
83221
|
Object.values(matcher[2]).forEach((list) => list[0].push(handlerData));
|
|
83041
83222
|
}
|
|
83042
|
-
#addPath(method, path,
|
|
83223
|
+
#addPath(method, path, handler12, indexes, map2) {
|
|
83043
83224
|
const matcher = this.#matchers[method];
|
|
83044
83225
|
if (!map2) {
|
|
83045
|
-
matcher[2][path][0].push([
|
|
83226
|
+
matcher[2][path][0].push([handler12, {}]);
|
|
83046
83227
|
} else {
|
|
83047
83228
|
indexes.forEach((index) => {
|
|
83048
83229
|
if (typeof index === "number") {
|
|
83049
|
-
matcher[1][index].push([
|
|
83230
|
+
matcher[1][index].push([handler12, map2]);
|
|
83050
83231
|
} else {
|
|
83051
|
-
matcher[2][index || path][0].push([
|
|
83232
|
+
matcher[2][index || path][0].push([handler12, map2]);
|
|
83052
83233
|
}
|
|
83053
83234
|
});
|
|
83054
83235
|
}
|
|
83055
83236
|
}
|
|
83056
|
-
add(method, path,
|
|
83237
|
+
add(method, path, handler12) {
|
|
83057
83238
|
if (!this.#matchers[method]) {
|
|
83058
83239
|
const all = this.#matchers[METHOD_NAME_ALL];
|
|
83059
83240
|
const staticMap = {};
|
|
@@ -83067,7 +83248,7 @@ var PreparedRegExpRouter = class {
|
|
|
83067
83248
|
];
|
|
83068
83249
|
}
|
|
83069
83250
|
if (path === "/*" || path === "*") {
|
|
83070
|
-
const handlerData = [
|
|
83251
|
+
const handlerData = [handler12, {}];
|
|
83071
83252
|
if (method === METHOD_NAME_ALL) {
|
|
83072
83253
|
for (const m in this.#matchers) {
|
|
83073
83254
|
this.#addWildcard(m, handlerData);
|
|
@@ -83084,10 +83265,10 @@ var PreparedRegExpRouter = class {
|
|
|
83084
83265
|
for (const [indexes, map2] of data) {
|
|
83085
83266
|
if (method === METHOD_NAME_ALL) {
|
|
83086
83267
|
for (const m in this.#matchers) {
|
|
83087
|
-
this.#addPath(m, path,
|
|
83268
|
+
this.#addPath(m, path, handler12, indexes, map2);
|
|
83088
83269
|
}
|
|
83089
83270
|
} else {
|
|
83090
|
-
this.#addPath(method, path,
|
|
83271
|
+
this.#addPath(method, path, handler12, indexes, map2);
|
|
83091
83272
|
}
|
|
83092
83273
|
}
|
|
83093
83274
|
}
|
|
@@ -83105,11 +83286,11 @@ var SmartRouter = class {
|
|
|
83105
83286
|
constructor(init) {
|
|
83106
83287
|
this.#routers = init.routers;
|
|
83107
83288
|
}
|
|
83108
|
-
add(method, path,
|
|
83289
|
+
add(method, path, handler12) {
|
|
83109
83290
|
if (!this.#routes) {
|
|
83110
83291
|
throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
|
|
83111
83292
|
}
|
|
83112
|
-
this.#routes.push([method, path,
|
|
83293
|
+
this.#routes.push([method, path, handler12]);
|
|
83113
83294
|
}
|
|
83114
83295
|
match(method, path) {
|
|
83115
83296
|
if (!this.#routes) {
|
|
@@ -83166,17 +83347,17 @@ var Node2 = class _Node2 {
|
|
|
83166
83347
|
#patterns;
|
|
83167
83348
|
#order = 0;
|
|
83168
83349
|
#params = emptyParams;
|
|
83169
|
-
constructor(method,
|
|
83350
|
+
constructor(method, handler12, children) {
|
|
83170
83351
|
this.#children = children || /* @__PURE__ */ Object.create(null);
|
|
83171
83352
|
this.#methods = [];
|
|
83172
|
-
if (method &&
|
|
83353
|
+
if (method && handler12) {
|
|
83173
83354
|
const m = /* @__PURE__ */ Object.create(null);
|
|
83174
|
-
m[method] = { handler:
|
|
83355
|
+
m[method] = { handler: handler12, possibleKeys: [], score: 0 };
|
|
83175
83356
|
this.#methods = [m];
|
|
83176
83357
|
}
|
|
83177
83358
|
this.#patterns = [];
|
|
83178
83359
|
}
|
|
83179
|
-
insert(method, path,
|
|
83360
|
+
insert(method, path, handler12) {
|
|
83180
83361
|
this.#order = ++this.#order;
|
|
83181
83362
|
let curNode = this;
|
|
83182
83363
|
const parts = splitRoutingPath(path);
|
|
@@ -83202,7 +83383,7 @@ var Node2 = class _Node2 {
|
|
|
83202
83383
|
}
|
|
83203
83384
|
curNode.#methods.push({
|
|
83204
83385
|
[method]: {
|
|
83205
|
-
handler:
|
|
83386
|
+
handler: handler12,
|
|
83206
83387
|
possibleKeys: possibleKeys.filter((v, i, a) => a.indexOf(v) === i),
|
|
83207
83388
|
score: this.#order
|
|
83208
83389
|
}
|
|
@@ -83320,7 +83501,7 @@ var Node2 = class _Node2 {
|
|
|
83320
83501
|
return a.score - b2.score;
|
|
83321
83502
|
});
|
|
83322
83503
|
}
|
|
83323
|
-
return [handlerSets.map(({ handler:
|
|
83504
|
+
return [handlerSets.map(({ handler: handler12, params }) => [handler12, params])];
|
|
83324
83505
|
}
|
|
83325
83506
|
};
|
|
83326
83507
|
|
|
@@ -83331,15 +83512,15 @@ var TrieRouter = class {
|
|
|
83331
83512
|
constructor() {
|
|
83332
83513
|
this.#node = new Node2;
|
|
83333
83514
|
}
|
|
83334
|
-
add(method, path,
|
|
83515
|
+
add(method, path, handler12) {
|
|
83335
83516
|
const results = checkOptionalParameter(path);
|
|
83336
83517
|
if (results) {
|
|
83337
83518
|
for (let i = 0, len = results.length;i < len; i++) {
|
|
83338
|
-
this.#node.insert(method, results[i],
|
|
83519
|
+
this.#node.insert(method, results[i], handler12);
|
|
83339
83520
|
}
|
|
83340
83521
|
return;
|
|
83341
83522
|
}
|
|
83342
|
-
this.#node.insert(method, path,
|
|
83523
|
+
this.#node.insert(method, path, handler12);
|
|
83343
83524
|
}
|
|
83344
83525
|
match(method, path) {
|
|
83345
83526
|
return this.#node.search(method, path);
|
|
@@ -85792,6 +85973,7 @@ Learn more:
|
|
|
85792
85973
|
registerDocumentRestore(document2);
|
|
85793
85974
|
registerDocumentEdit(document2);
|
|
85794
85975
|
registerDocumentSetProjects(document2);
|
|
85976
|
+
registerDocumentSetMetadata(document2);
|
|
85795
85977
|
moveInto(document2, registerIngest, "ingest");
|
|
85796
85978
|
registerDocumentInsert(document2);
|
|
85797
85979
|
registerDocumentEditParts(document2);
|