@cerefox/memory 1.14.1 → 1.14.3

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.
@@ -7400,7 +7400,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
7400
7400
  });
7401
7401
 
7402
7402
  // src/meta.ts
7403
- var PKG_VERSION = "1.14.1";
7403
+ var PKG_VERSION = "1.14.3";
7404
7404
  var init_meta = () => {};
7405
7405
 
7406
7406
  // ../../_shared/config/paths.ts
@@ -23278,21 +23278,6 @@ function getConfiguredSearchAlpha() {
23278
23278
  function getMinTermCoverage() {
23279
23279
  return;
23280
23280
  }
23281
- function applyByteBudget(rows, maxBytes) {
23282
- const accepted = [];
23283
- let usedBytes = 0;
23284
- let truncated = false;
23285
- for (const row of rows) {
23286
- const rowBytes = new TextEncoder().encode(JSON.stringify(row)).length;
23287
- if (usedBytes + rowBytes > maxBytes) {
23288
- truncated = true;
23289
- break;
23290
- }
23291
- accepted.push(row);
23292
- usedBytes += rowBytes;
23293
- }
23294
- return { accepted, truncated, usedBytes };
23295
- }
23296
23281
  function extractConflictHashes(message) {
23297
23282
  return {
23298
23283
  expected: message.match(/expected hash ([0-9a-f]{64})/)?.[1] ?? "unknown",
@@ -25774,7 +25759,7 @@ var init_bundled_docs = __esm(() => {
25774
25759
  });
25775
25760
 
25776
25761
  // ../../_shared/ef-meta/index.ts
25777
- var EF_VERSION = "1.14.1", CEREFOX_VERSION = "1.14.1", EF_LAST_CHANGED = "1.14.0";
25762
+ var EF_VERSION = "1.14.3", CEREFOX_VERSION = "1.14.3", EF_LAST_CHANGED = "1.14.3";
25778
25763
  var init_ef_meta = () => {};
25779
25764
 
25780
25765
  // ../../_shared/compatibility/index.ts
@@ -25923,9 +25908,9 @@ __export(exports_onnx_embedder, {
25923
25908
  onnxEmbed: () => onnxEmbed,
25924
25909
  warmup: () => warmup
25925
25910
  });
25926
- import { existsSync as existsSync9, mkdirSync as mkdirSync3 } from "node:fs";
25927
- import { homedir as homedir5 } from "node:os";
25928
- import { join as join8 } from "node:path";
25911
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4 } from "node:fs";
25912
+ import { homedir as homedir6 } from "node:os";
25913
+ import { join as join9 } from "node:path";
25929
25914
  function nomicPrefix(role) {
25930
25915
  return role === "query" ? "search_query: " : "search_document: ";
25931
25916
  }
@@ -25937,7 +25922,7 @@ function getCacheDir() {
25937
25922
  const env2 = globalThis.process?.env ?? {};
25938
25923
  if (env2.CEREFOX_MODELS_DIR)
25939
25924
  return env2.CEREFOX_MODELS_DIR;
25940
- return join8(homedir5(), ".cerefox", "models");
25925
+ return join9(homedir6(), ".cerefox", "models");
25941
25926
  }
25942
25927
  async function loadTransformers() {
25943
25928
  if (transformersModule)
@@ -25945,8 +25930,8 @@ async function loadTransformers() {
25945
25930
  const spec = "@huggingface/transformers";
25946
25931
  transformersModule = await import(spec);
25947
25932
  const dir = getCacheDir();
25948
- if (!existsSync9(dir))
25949
- mkdirSync3(dir, { recursive: true });
25933
+ if (!existsSync10(dir))
25934
+ mkdirSync4(dir, { recursive: true });
25950
25935
  transformersModule.env.cacheDir = dir;
25951
25936
  transformersModule.env.allowLocalModels = true;
25952
25937
  transformersModule.env.allowRemoteModels = true;
@@ -55761,12 +55746,12 @@ var init_partial_edits2 = __esm(() => {
55761
55746
  });
55762
55747
 
55763
55748
  // ../../_shared/mcp-tools/get-help-content.ts
55764
- var HELP_FULL = '# Cerefox Knowledge Base -- Agent Quick Reference\n\nCerefox is a persistent, shared knowledge base. You have **15 core MCP tools** (14 with CLI equivalents — `cerefox_get_help` is MCP-only), plus 4 dormant relation tools that appear only when `relations_enabled` is on. 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`, `author` |\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`, `author` |\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), `author` |\n| `cerefox_delete_document` | **Soft**-delete a document (to trash; excluded from search; permanent purge is human-only) | `document_id`, `expected_content_hash` (**required** — a delete must follow a read), `reason` (recorded in the audit log — give one), `author` |\n| `cerefox_restore_document` | Restore a soft-deleted document from the trash (audited inverse of delete; no-op if not deleted) | `document_id` (required), `reason` (recorded in the audit log), `author` |\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`, `author` |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required), `author` |\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`, `author` |\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), `author` |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `by_author` (filter), `operation`, `since`, `author` |\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`** to your name on every call, reads and writes alike (e.g., "Claude Code", "archiver"). Same parameter on every tool. (`requestor` is still accepted everywhere as the pre-1.13.1 alias.) On the CLI it is `--author` on every command too (plus `--author-type` on writes); or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE` 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); permanent purge is web-UI-only.** `cerefox_delete_document` requires the document\'s `content_hash` as you read it (read before you delete) and takes a `reason` — give one; it is what the human reviewing the trash sees. `cerefox_restore_document` undoes a mistaken delete (also audited, also takes a `reason`). Always surface deletes AND restores to the user. Once a human purges from the web UI, the document is gone for good.\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. **The server validates `](uuid)` links on every write** (v1.7.0): a link to a nonexistent id rejects the write, naming the offender — that means you mangled the UUID; re-read the source and correct it, do not retry unchanged. Example ids go in backticks (code is not validated). `[[Wikilinks]]` may dangle. 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`). 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>" --author "<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> --author "<your-name>"` |\n| `cerefox_list_versions` | `cerefox document version list <id> --author "<your-name>"` |\n| `cerefox_list_projects` | `cerefox project list --author "<your-name>"` |\n| `cerefox_list_metadata_keys` | `cerefox metadata keys` |\n| `cerefox_insert` | `cerefox document insert <id> -t "<text>" -p <position> -a "<anchor-heading>" -e "<hash>" --author "<your-name>" --author-type agent` |\n| `cerefox_edit` | `cerefox document edit-parts <id> --operations \'<json>\' -e "<hash>" --author "<your-name>" --author-type agent` |\n| `cerefox_delete_document` | `cerefox document delete <id> --reason "<why>" --author "<your-name>" --author-type agent --yes` (confirms interactively instead of requiring the hash) |\n| `cerefox_restore_document` | `cerefox document restore <id> --reason "<why>" --author "<your-name>" --author-type agent` |\n| `cerefox_metadata_search` | `cerefox metadata search --metadata-filter \'<json>\' --author "<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 --author "<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: `--author "<your-name>"` (the same flag; `--requestor` still works as a hidden alias)\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- Long inline bodies can arrive with literal `\\n`/`\\"` (the author over-escaped; Cerefox stores bytes faithfully). For long or quote-dense content, ingest from a file or build incrementally with `cerefox_insert`; read back multi-line writes.\n', HELP_SECTIONS, HELP_SECTION_HEADINGS;
55749
+ var HELP_FULL = '# Cerefox Knowledge Base -- Agent Quick Reference\n\nCerefox is a persistent, shared knowledge base. You have **15 core MCP tools** (14 with CLI equivalents — `cerefox_get_help` is MCP-only), plus 4 dormant relation tools that appear only when `relations_enabled` is on. 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`, `author` |\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`, `author` |\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), `author` |\n| `cerefox_delete_document` | **Soft**-delete a document (to trash; excluded from search; permanent purge is human-only) | `document_id`, `expected_content_hash` (**required** — a delete must follow a read), `reason` (recorded in the audit log — give one), `author` |\n| `cerefox_restore_document` | Restore a soft-deleted document from the trash (audited inverse of delete; no-op if not deleted) | `document_id` (required), `reason` (recorded in the audit log), `author` |\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`, `author` |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required), `author` |\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`, `author` |\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), `author` |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `by_author` (filter), `operation`, `since`, `author` |\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`** to your name on every call, reads and writes alike (e.g., "Claude Code", "archiver"). Same parameter on every tool. (`requestor` is still accepted everywhere as the pre-1.13.1 alias.) On the CLI it is `--author` on every command too (plus `--author-type` on writes); or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE` env vars set in the user\'s `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **`max_bytes` has to fit a whole document.** Search returns COMPLETE documents, so a budget smaller than the top hit fits nothing. You get a header-only list saying so (never "No results found." — that means the store really has nothing). Raise `max_bytes`, or read one document with `cerefox_get_document` using `outline: true` or `section`.\n6. **Add metadata** -- at minimum `type` ("decision-log", "research", "design-doc") and `status` ("active", "draft").\n7. **Write structured Markdown** with H1/H2/H3 headings for good chunking and search.\n8. **Deletes are soft (recoverable); permanent purge is web-UI-only.** `cerefox_delete_document` requires the document\'s `content_hash` as you read it (read before you delete) and takes a `reason` — give one; it is what the human reviewing the trash sees. `cerefox_restore_document` undoes a mistaken delete (also audited, also takes a `reason`). Always surface deletes AND restores to the user. Once a human purges from the web UI, the document is gone for good.\n9. **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. **The server validates `](uuid)` links on every write** (v1.7.0): a link to a nonexistent id rejects the write, naming the offender — that means you mangled the UUID; re-read the source and correct it, do not retry unchanged. Example ids go in backticks (code is not validated). `[[Wikilinks]]` may dangle. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n10. **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.\n11. **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.\n12. **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.\n13. **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`). 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>" --author "<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> --author "<your-name>"` |\n| `cerefox_list_versions` | `cerefox document version list <id> --author "<your-name>"` |\n| `cerefox_list_projects` | `cerefox project list --author "<your-name>"` |\n| `cerefox_list_metadata_keys` | `cerefox metadata keys` |\n| `cerefox_insert` | `cerefox document insert <id> -t "<text>" -p <position> -a "<anchor-heading>" -e "<hash>" --author "<your-name>" --author-type agent` |\n| `cerefox_edit` | `cerefox document edit-parts <id> --operations \'<json>\' -e "<hash>" --author "<your-name>" --author-type agent` |\n| `cerefox_delete_document` | `cerefox document delete <id> --reason "<why>" --author "<your-name>" --author-type agent --yes` (confirms interactively instead of requiring the hash) |\n| `cerefox_restore_document` | `cerefox document restore <id> --reason "<why>" --author "<your-name>" --author-type agent` |\n| `cerefox_metadata_search` | `cerefox metadata search --metadata-filter \'<json>\' --author "<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 --author "<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: `--author "<your-name>"` (the same flag; `--requestor` still works as a hidden alias)\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- Long inline bodies can arrive with literal `\\n`/`\\"` (the author over-escaped; Cerefox stores bytes faithfully). For long or quote-dense content, ingest from a file or build incrementally with `cerefox_insert`; read back multi-line writes.\n', HELP_SECTIONS, HELP_SECTION_HEADINGS;
55765
55750
  var init_get_help_content = __esm(() => {
55766
55751
  HELP_SECTIONS = {
55767
55752
  Tools: "## Tools\n\n| Tool | Purpose | Key params |\n|------|---------|------------|\n| `cerefox_search` | Find documents (hybrid FTS + semantic) | `query` (required), `project_name`, `metadata_filter`, `author` |\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`, `author` |\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), `author` |\n| `cerefox_delete_document` | **Soft**-delete a document (to trash; excluded from search; permanent purge is human-only) | `document_id`, `expected_content_hash` (**required** — a delete must follow a read), `reason` (recorded in the audit log — give one), `author` |\n| `cerefox_restore_document` | Restore a soft-deleted document from the trash (audited inverse of delete; no-op if not deleted) | `document_id` (required), `reason` (recorded in the audit log), `author` |\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`, `author` |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required), `author` |\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`, `author` |\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), `author` |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `by_author` (filter), `operation`, `since`, `author` |\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.",
55768
55753
  "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.',
55769
- "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`** to your name on every call, reads and writes alike (e.g., "Claude Code", "archiver"). Same parameter on every tool. (`requestor` is still accepted everywhere as the pre-1.13.1 alias.) On the CLI it is `--author` on every command too (plus `--author-type` on writes); or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE` 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); permanent purge is web-UI-only.** `cerefox_delete_document` requires the document\'s `content_hash` as you read it (read before you delete) and takes a `reason` — give one; it is what the human reviewing the trash sees. `cerefox_restore_document` undoes a mistaken delete (also audited, also takes a `reason`). Always surface deletes AND restores to the user. Once a human purges from the web UI, the document is gone for good.\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. **The server validates `](uuid)` links on every write** (v1.7.0): a link to a nonexistent id rejects the write, naming the offender — that means you mangled the UUID; re-read the source and correct it, do not retry unchanged. Example ids go in backticks (code is not validated). `[[Wikilinks]]` may dangle. 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`.',
55754
+ "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`** to your name on every call, reads and writes alike (e.g., "Claude Code", "archiver"). Same parameter on every tool. (`requestor` is still accepted everywhere as the pre-1.13.1 alias.) On the CLI it is `--author` on every command too (plus `--author-type` on writes); or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE` env vars set in the user\'s `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **`max_bytes` has to fit a whole document.** Search returns COMPLETE documents, so a budget smaller than the top hit fits nothing. You get a header-only list saying so (never "No results found." — that means the store really has nothing). Raise `max_bytes`, or read one document with `cerefox_get_document` using `outline: true` or `section`.\n6. **Add metadata** -- at minimum `type` ("decision-log", "research", "design-doc") and `status` ("active", "draft").\n7. **Write structured Markdown** with H1/H2/H3 headings for good chunking and search.\n8. **Deletes are soft (recoverable); permanent purge is web-UI-only.** `cerefox_delete_document` requires the document\'s `content_hash` as you read it (read before you delete) and takes a `reason` — give one; it is what the human reviewing the trash sees. `cerefox_restore_document` undoes a mistaken delete (also audited, also takes a `reason`). Always surface deletes AND restores to the user. Once a human purges from the web UI, the document is gone for good.\n9. **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. **The server validates `](uuid)` links on every write** (v1.7.0): a link to a nonexistent id rejects the write, naming the offender — that means you mangled the UUID; re-read the source and correct it, do not retry unchanged. Example ids go in backticks (code is not validated). `[[Wikilinks]]` may dangle. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n10. **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.\n11. **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.\n12. **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.\n13. **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`.',
55770
55755
  "Update Workflow (ID-based -- preferred)": `## Update Workflow (ID-based -- preferred)
55771
55756
 
55772
55757
  \`\`\`
@@ -56409,7 +56394,7 @@ async function handler10(supabase, args, ctx) {
56409
56394
  const project_name = args.project_name;
56410
56395
  const updated_since = args.updated_since;
56411
56396
  const created_since = args.created_since;
56412
- const limit = args.limit ?? 10;
56397
+ const limit = Math.min(Math.max(1, Math.floor(Number(args.limit)) || 10), 500);
56413
56398
  const include_content = args.include_content ?? false;
56414
56399
  const requested_max_bytes = args.max_bytes;
56415
56400
  if (metadata_filter !== undefined && (typeof metadata_filter !== "object" || Array.isArray(metadata_filter))) {
@@ -56426,7 +56411,8 @@ async function handler10(supabase, args, ctx) {
56426
56411
  throw new Error(`Project not found: ${project_name}`);
56427
56412
  }
56428
56413
  const ceiling = getMaxResponseBytes();
56429
- const max_bytes = include_content ? Math.min(requested_max_bytes ?? ceiling, ceiling) : null;
56414
+ const requestedBytes = Math.floor(Number(requested_max_bytes));
56415
+ const max_bytes = include_content ? Math.min(Number.isFinite(requestedBytes) ? Math.max(requestedBytes, 1) : ceiling, ceiling) : null;
56430
56416
  const params = {
56431
56417
  p_metadata_filter: metadata_filter ?? {},
56432
56418
  p_project_id: projectId,
@@ -56441,16 +56427,45 @@ async function handler10(supabase, args, ctx) {
56441
56427
  if (error)
56442
56428
  throw new Error(`RPC error: ${error.message}`);
56443
56429
  const rows = data ?? [];
56444
- logUsage(supabase, {
56430
+ const log = (result_count, extra) => logUsage(supabase, {
56445
56431
  operation: "metadata_search",
56446
56432
  accessPath: ctx.accessPath,
56447
56433
  requestor: callerIdentity(args),
56448
56434
  query_text: JSON.stringify(metadata_filter ?? {}),
56449
56435
  project_id: projectId,
56450
- result_count: rows.length
56436
+ result_count,
56437
+ ...extra ? { extra } : {}
56451
56438
  });
56452
- if (rows.length === 0)
56439
+ if (rows.length === 0) {
56440
+ if (include_content && max_bytes !== null) {
56441
+ const { data: headers, error: probeError } = await supabase.rpc("cerefox_metadata_search", { ...params, p_include_content: false, p_max_bytes: null });
56442
+ if (probeError) {
56443
+ log(0, { degraded_probe_failed: true });
56444
+ return `⚠ Nothing fit max_bytes=${max_bytes} with include_content, and the ` + `follow-up query that lists what matched failed: ${probeError.message}. ` + `This is NOT a confirmed empty result — retry with a larger max_bytes, ` + `or with include_content: false.`;
56445
+ }
56446
+ const headerRows = headers ?? [];
56447
+ if (headerRows.length > 0) {
56448
+ const lead = `⚠ ${headerRows.length} document(s) match, but none fit max_bytes=${max_bytes} ` + `with include_content. This is NOT an empty result. Listing them without ` + `content — raise max_bytes, or read one with cerefox_get_document ` + `(outline: true for structure).`;
56449
+ const lines = [];
56450
+ let used = new TextEncoder().encode(lead).length;
56451
+ for (const r of headerRows) {
56452
+ const line = `## ${r.title} [id: ${r.document_id}]`;
56453
+ const size = new TextEncoder().encode(line).length + 1;
56454
+ if (used + size > max_bytes)
56455
+ break;
56456
+ lines.push(line);
56457
+ used += size;
56458
+ }
56459
+ log(headerRows.length, { returned: lines.length, degraded: true });
56460
+ return lines.length > 0 ? `${lead}
56461
+ ${lines.join(`
56462
+ `)}` : lead;
56463
+ }
56464
+ }
56465
+ log(0);
56453
56466
  return "No documents match the given criteria.";
56467
+ }
56468
+ log(rows.length);
56454
56469
  const showReview = await reviewWorkflowEnabled(supabase);
56455
56470
  const parts = rows.map((row) => {
56456
56471
  const projects = row.project_names?.length ? ` | projects: ${row.project_names.join(", ")}` : "";
@@ -56522,10 +56537,71 @@ var init_metadata_search = __esm(() => {
56522
56537
  });
56523
56538
 
56524
56539
  // ../../_shared/mcp-tools/search.ts
56540
+ function rowContent(row) {
56541
+ return row.full_content ?? row.content ?? "";
56542
+ }
56543
+ function rowHeading(row) {
56544
+ const doc = row.doc_title ?? "Untitled";
56545
+ const path = [...row.heading_path ?? []];
56546
+ if (path.length > 0 && path[0] === doc)
56547
+ path.shift();
56548
+ const section = path.length ? path.filter(Boolean).join(" › ") : row.title && row.title !== doc ? row.title : "";
56549
+ const docId = row.document_id ? ` [id: ${row.document_id}]` : "";
56550
+ const chunk = row.chunk_index != null ? ` (chunk ${row.chunk_index})` : "";
56551
+ return `${doc}${section ? ` › ${section}` : ""}${docId}${chunk}`;
56552
+ }
56553
+ function shortLabel(row) {
56554
+ const doc = row.doc_title ?? "Untitled";
56555
+ const path = [...row.heading_path ?? []];
56556
+ if (path.length > 0 && path[0] === doc)
56557
+ path.shift();
56558
+ const leaf = path.filter(Boolean).at(-1) ?? (row.title && row.title !== doc ? row.title : "");
56559
+ const chunk = row.chunk_index != null ? ` (chunk ${row.chunk_index})` : "";
56560
+ const id = row.document_id ? ` [id: ${row.document_id}]` : "";
56561
+ return `${leaf ? `${doc} › ${leaf}` : doc}${chunk}${id}`;
56562
+ }
56563
+ function renderRow(row) {
56564
+ const raw = row.best_score ?? row.score;
56565
+ const score = raw != null ? ` (score: ${raw.toFixed(3)})` : "";
56566
+ const partial = row.is_partial ? ` -- partial (${row.chunk_count} of ${(row.total_chars ?? 0).toLocaleString()} chars)` : "";
56567
+ const hash = row.content_hash ? `
56568
+ hash: ${row.content_hash}` : "";
56569
+ return `## ${rowHeading(row)}${score}${partial}${hash}
56570
+
56571
+ ${rowContent(row)}`;
56572
+ }
56573
+ function headerLine(row) {
56574
+ const raw = row.best_score ?? row.score;
56575
+ const score = raw != null ? ` (score: ${raw.toFixed(3)})` : "";
56576
+ const size = row.total_chars != null ? ` -- ${row.total_chars.toLocaleString()} chars` : "";
56577
+ const hash = row.content_hash ? `
56578
+ hash: ${row.content_hash}` : "";
56579
+ return `## ${rowHeading(row)}${score}${size}${hash}`;
56580
+ }
56581
+ function degradedToHeaders(matched, rendered, maxBytes, belowConfidence) {
56582
+ const biggest = Math.max(...rendered.map((t) => new TextEncoder().encode(t).length));
56583
+ const confidence = belowConfidence ? "None of these cleared the confidence threshold — they are the closest " + "candidates, so judge relevance from the scores. " : "";
56584
+ const lead = `⚠ ${matched.length} result(s) matched, but none fit max_bytes=${maxBytes} ` + `(the largest is ${biggest.toLocaleString()} bytes). ${confidence}This is NOT an ` + `empty knowledge base. Listing what matched, without content — raise max_bytes ` + `to read it, or read one document with cerefox_get_document (outline: true for ` + `structure, or section: "## Heading" for one part).`;
56585
+ const lines = [];
56586
+ let used = new TextEncoder().encode(lead).length;
56587
+ for (const row of matched) {
56588
+ const line = headerLine(row);
56589
+ const size = new TextEncoder().encode(line).length + 2;
56590
+ if (used + size > maxBytes)
56591
+ break;
56592
+ lines.push(line);
56593
+ used += size;
56594
+ }
56595
+ return lines.length > 0 ? `${lead}
56596
+
56597
+ ${lines.join(`
56598
+
56599
+ `)}` : lead;
56600
+ }
56525
56601
  async function handler11(supabase, args, ctx) {
56526
56602
  const query = args.query;
56527
56603
  const project_name = args.project_name;
56528
- const match_count = args.match_count ?? 5;
56604
+ const match_count = Math.min(Math.max(1, Math.floor(Number(args.match_count)) || 5), 200);
56529
56605
  const mode = args.mode ?? "docs";
56530
56606
  const alpha = args.alpha ?? getConfiguredSearchAlpha();
56531
56607
  const min_score = args.min_score ?? getConfiguredMinSearchScore();
@@ -56536,7 +56612,8 @@ async function handler11(supabase, args, ctx) {
56536
56612
  const metadata_filter = args.metadata_filter ?? null;
56537
56613
  const requested_max_bytes = args.max_bytes;
56538
56614
  const ceiling = getMaxResponseBytes();
56539
- const max_bytes = Math.min(requested_max_bytes ?? ceiling, ceiling);
56615
+ const requestedBytes = Math.floor(Number(requested_max_bytes));
56616
+ const max_bytes = Math.min(Number.isFinite(requestedBytes) ? Math.max(requestedBytes, 1) : ceiling, ceiling);
56540
56617
  if (metadata_filter !== null && metadata_filter !== undefined && (typeof metadata_filter !== "object" || Array.isArray(metadata_filter))) {
56541
56618
  throw new McpInvalidParams("metadata_filter must be a JSON object or null");
56542
56619
  }
@@ -56596,46 +56673,103 @@ async function handler11(supabase, args, ctx) {
56596
56673
  const { data, error } = await supabase.rpc(rpcName, rpcParams);
56597
56674
  if (error)
56598
56675
  throw new Error(`RPC error: ${error.message}`);
56599
- const { accepted, truncated, usedBytes } = applyByteBudget(data ?? [], max_bytes);
56600
- logUsage(supabase, {
56601
- operation: "search",
56602
- accessPath: ctx.accessPath,
56603
- requestor: callerIdentity(args),
56604
- query_text: query,
56605
- project_id: projectId,
56606
- result_count: accepted.length
56607
- });
56608
- if (accepted.length === 0)
56676
+ const matched = data ?? [];
56677
+ const belowConfidence = matched.length > 0 && matched.every((r) => r.below_confidence === true);
56678
+ if (matched.length === 0) {
56679
+ logUsage(supabase, {
56680
+ operation: "search",
56681
+ accessPath: ctx.accessPath,
56682
+ requestor: callerIdentity(args),
56683
+ query_text: query,
56684
+ project_id: projectId,
56685
+ result_count: 0
56686
+ });
56609
56687
  return "No results found.";
56610
- const rows = accepted;
56611
- const belowConfidence = rows.length > 0 && rows.every((r) => r.below_confidence === true);
56612
- const parts = rows.map((row) => {
56613
- const title = row.doc_title ?? "Untitled";
56614
- const docId = row.document_id ? ` [id: ${row.document_id}]` : "";
56615
- const rawScore = row.best_score ?? row.score;
56616
- const score = rawScore != null ? ` (score: ${rawScore.toFixed(3)})` : "";
56617
- const partial = row.is_partial ? ` -- partial (${row.chunk_count} of ${(row.total_chars ?? 0).toLocaleString()} chars)` : "";
56618
- const hash = row.content_hash ? `
56619
- hash: ${row.content_hash}` : "";
56620
- return `## ${title}${docId}${score}${partial}${hash}
56621
-
56622
- ${row.full_content ?? ""}`;
56623
- });
56624
- let output = parts.join(`
56688
+ }
56689
+ const rendered = matched.map(renderRow);
56690
+ const size = (t) => new TextEncoder().encode(t).length;
56691
+ const SEP = `
56625
56692
 
56626
56693
  ---
56627
56694
 
56628
- `);
56629
- if (belowConfidence) {
56630
- output = `⚠ No results cleared the confidence threshold. Showing the closest ${rows.length} ` + `candidate(s) with scores — judge relevance yourself; a low score means weak signal, ` + `not necessarily absent knowledge.
56695
+ `;
56696
+ const banner = (take, short) => !belowConfidence ? "" : short ? `⚠ Below the confidence threshold — judge relevance from the scores.
56631
56697
 
56632
- ` + output;
56633
- }
56634
- if (truncated) {
56635
- output += `
56698
+ ` : `⚠ No results cleared the confidence threshold. Showing the closest ${take} ` + `candidate(s) with scores — judge relevance yourself; a low score means weak ` + `signal, not necessarily absent knowledge.
56636
56699
 
56637
- [Results truncated at ${usedBytes} bytes. Use a more specific query or a smaller match_count to see more.]`;
56700
+ `;
56701
+ const assemble = (keptCount, short) => {
56702
+ const kept = keptIdx.slice(0, keptCount);
56703
+ const body = banner(keptCount, short) + kept.map((i) => rendered[i]).join(SEP);
56704
+ if (kept.length === matched.length)
56705
+ return body;
56706
+ const droppedRows = matched.filter((_, i) => !kept.includes(i));
56707
+ const room = max_bytes - size(body);
56708
+ const footer = (named) => {
56709
+ const labels = droppedRows.slice(0, named).map(shortLabel);
56710
+ const rest = droppedRows.length - labels.length;
56711
+ const naming = labels.length ? `: ${labels.join(", ")}${rest > 0 ? ` and ${rest} more` : ""}` : "";
56712
+ return `
56713
+
56714
+ [${kept.length} of ${matched.length} result(s) shown; ${droppedRows.length} did ` + `not fit max_bytes=${max_bytes}${naming}. Raise max_bytes, narrow the query, or ` + `lower match_count.]`;
56715
+ };
56716
+ for (let named = Math.min(5, droppedRows.length);named >= 1; named--) {
56717
+ const candidate = footer(named);
56718
+ if (size(candidate) <= room)
56719
+ return body + candidate;
56720
+ }
56721
+ const bare = footer(0);
56722
+ if (size(bare) <= room)
56723
+ return body + bare;
56724
+ return body + `
56725
+
56726
+ [${kept.length} of ${matched.length} shown; raise max_bytes]`;
56727
+ };
56728
+ const sepBytes = size(SEP);
56729
+ const keptIdx = [];
56730
+ let acc = 0;
56731
+ for (let i = 0;i < rendered.length; i++) {
56732
+ const add = size(rendered[i]) + (keptIdx.length > 0 ? sepBytes : 0);
56733
+ if (acc + add > max_bytes)
56734
+ continue;
56735
+ acc += add;
56736
+ keptIdx.push(i);
56638
56737
  }
56738
+ if (keptIdx.length === 0) {
56739
+ logUsage(supabase, {
56740
+ operation: "search",
56741
+ accessPath: ctx.accessPath,
56742
+ requestor: callerIdentity(args),
56743
+ query_text: query,
56744
+ project_id: projectId,
56745
+ result_count: matched.length,
56746
+ extra: { returned: 0, truncated: true, degraded: true }
56747
+ });
56748
+ return degradedToHeaders(matched, rendered, max_bytes, belowConfidence);
56749
+ }
56750
+ let keptCount = keptIdx.length;
56751
+ let short = false;
56752
+ let output = assemble(keptCount, short);
56753
+ while (size(output) > max_bytes) {
56754
+ if (belowConfidence && !short)
56755
+ short = true;
56756
+ else if (keptCount > 1) {
56757
+ keptCount -= 1;
56758
+ short = belowConfidence;
56759
+ } else
56760
+ break;
56761
+ output = assemble(keptCount, short);
56762
+ }
56763
+ const take = keptCount;
56764
+ logUsage(supabase, {
56765
+ operation: "search",
56766
+ accessPath: ctx.accessPath,
56767
+ requestor: callerIdentity(args),
56768
+ query_text: query,
56769
+ project_id: projectId,
56770
+ result_count: matched.length,
56771
+ ...take < matched.length ? { extra: { returned: take, truncated: true } } : {}
56772
+ });
56639
56773
  return output;
56640
56774
  }
56641
56775
  var searchTool;
@@ -56660,7 +56794,7 @@ var init_search = __esm(() => {
56660
56794
  query: { type: "string", description: "Natural-language search query" },
56661
56795
  match_count: {
56662
56796
  type: "integer",
56663
- description: "Maximum number of documents to return (default: 5)"
56797
+ description: "Maximum number of documents to return (default: 5, maximum: 200)"
56664
56798
  },
56665
56799
  project_name: {
56666
56800
  type: "string",
@@ -57660,7 +57794,7 @@ var require_cli_progress = __commonJS(function(exports2, module2) {
57660
57794
  });
57661
57795
 
57662
57796
  // src/cli/commands/sync-self-docs.ts
57663
- import { readFileSync as readFileSync11 } from "node:fs";
57797
+ import { readFileSync as readFileSync12 } from "node:fs";
57664
57798
  import { basename as basename4, extname as extname5 } from "node:path";
57665
57799
  async function runSyncSelfDocs(options = {}) {
57666
57800
  const project = options.project ?? "_cerefox-self-docs";
@@ -57687,7 +57821,7 @@ async function runSyncSelfDocs(options = {}) {
57687
57821
  const authorType = resolveAuthorType("agent");
57688
57822
  const outcomes = [];
57689
57823
  for (const doc of docs) {
57690
- const content = readFileSync11(doc.path, "utf8");
57824
+ const content = readFileSync12(doc.path, "utf8");
57691
57825
  const m = content.match(/^#\s+(.+)$/m);
57692
57826
  const title = m ? m[1].trim() : basename4(doc.path, extname5(doc.path));
57693
57827
  try {
@@ -70871,7 +71005,7 @@ var init_stdio2 = __esm(() => {
70871
71005
  });
70872
71006
 
70873
71007
  // src/server.ts
70874
- import { existsSync as existsSync12, readFileSync as readFileSync13 } from "node:fs";
71008
+ import { existsSync as existsSync13, readFileSync as readFileSync14 } from "node:fs";
70875
71009
  function buildServer() {
70876
71010
  const settings = loadSettings();
70877
71011
  if (!settings.supabaseUrl || !settings.supabaseKey) {
@@ -70925,8 +71059,8 @@ async function warnIfSchemaVersionMismatch(supabase) {
70925
71059
  let bundled = null;
70926
71060
  try {
70927
71061
  const assets = resolveServerAssets();
70928
- if (existsSync12(assets.schemaFile)) {
70929
- const m = readFileSync13(assets.schemaFile, "utf8").match(SCHEMA_VERSION_RE2);
71062
+ if (existsSync13(assets.schemaFile)) {
71063
+ const m = readFileSync14(assets.schemaFile, "utf8").match(SCHEMA_VERSION_RE2);
70930
71064
  bundled = m ? m[1] : null;
70931
71065
  }
70932
71066
  } catch {}
@@ -77474,9 +77608,177 @@ init_config();
77474
77608
  init_config();
77475
77609
  init_compatibility();
77476
77610
  init_server_assets();
77477
- import { existsSync as existsSync10, readFileSync as readFileSync7, realpathSync, statSync as statSync2 } from "node:fs";
77478
- import { homedir as homedir6 } from "node:os";
77479
- import { join as join9 } from "node:path";
77611
+ import { existsSync as existsSync11, readFileSync as readFileSync8, realpathSync, statSync as statSync2 } from "node:fs";
77612
+ import { homedir as homedir7 } from "node:os";
77613
+ import { join as join10 } from "node:path";
77614
+
77615
+ // src/web/daemon.ts
77616
+ import { spawn } from "node:child_process";
77617
+ import {
77618
+ existsSync as existsSync9,
77619
+ mkdirSync as mkdirSync3,
77620
+ openSync,
77621
+ readFileSync as readFileSync7,
77622
+ rmSync,
77623
+ writeFileSync as writeFileSync4
77624
+ } from "node:fs";
77625
+ import { homedir as homedir5 } from "node:os";
77626
+ import { join as join8 } from "node:path";
77627
+ function resolveStateDir(override = process.env.CEREFOX_CONFIG_DIR, home = homedir5()) {
77628
+ override = (override ?? "").trim();
77629
+ if (!override)
77630
+ return join8(home, ".cerefox");
77631
+ return override === "~" || override.startsWith("~/") ? join8(home, override.slice(2)) : override;
77632
+ }
77633
+ var STATE_DIR = resolveStateDir();
77634
+ var PID_FILE = join8(STATE_DIR, "web.pid");
77635
+ var LOG_FILE = join8(STATE_DIR, "web.log");
77636
+ var daemonPaths = { stateDir: STATE_DIR, pidFile: PID_FILE, logFile: LOG_FILE };
77637
+ function ensureStateDir() {
77638
+ if (!existsSync9(STATE_DIR))
77639
+ mkdirSync3(STATE_DIR, { recursive: true });
77640
+ }
77641
+ function readPidFile() {
77642
+ if (!existsSync9(PID_FILE))
77643
+ return null;
77644
+ try {
77645
+ const parsed = JSON.parse(readFileSync7(PID_FILE, "utf8"));
77646
+ if (typeof parsed.pid !== "number")
77647
+ return null;
77648
+ return {
77649
+ pid: parsed.pid,
77650
+ port: typeof parsed.port === "number" ? parsed.port : 8000,
77651
+ host: typeof parsed.host === "string" ? parsed.host : "127.0.0.1",
77652
+ startedAt: typeof parsed.startedAt === "string" ? parsed.startedAt : "unknown"
77653
+ };
77654
+ } catch {
77655
+ return null;
77656
+ }
77657
+ }
77658
+ function writePidFile(info) {
77659
+ ensureStateDir();
77660
+ writeFileSync4(PID_FILE, JSON.stringify(info, null, 2) + `
77661
+ `, "utf8");
77662
+ }
77663
+ function removePidFile() {
77664
+ rmSync(PID_FILE, { force: true });
77665
+ }
77666
+ function isProcessAlive(pid) {
77667
+ try {
77668
+ process.kill(pid, 0);
77669
+ return true;
77670
+ } catch (err) {
77671
+ return err.code === "EPERM";
77672
+ }
77673
+ }
77674
+ async function probeVersion(host, port, timeoutMs = 1500) {
77675
+ const probeHost = host === "0.0.0.0" ? "127.0.0.1" : host;
77676
+ try {
77677
+ const ctrl = new AbortController;
77678
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs);
77679
+ try {
77680
+ const resp = await fetch(`http://${probeHost}:${port}/api/v1/version`, {
77681
+ signal: ctrl.signal
77682
+ });
77683
+ if (!resp.ok)
77684
+ return { responding: false, version: null };
77685
+ const body = await resp.json().catch(() => null);
77686
+ return {
77687
+ responding: true,
77688
+ version: typeof body?.version === "string" ? body.version : null
77689
+ };
77690
+ } finally {
77691
+ clearTimeout(timer);
77692
+ }
77693
+ } catch {
77694
+ return { responding: false, version: null };
77695
+ }
77696
+ }
77697
+ async function isResponding(host, port, timeoutMs = 1500) {
77698
+ return (await probeVersion(host, port, timeoutMs)).responding;
77699
+ }
77700
+ var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
77701
+ function assertUnix() {
77702
+ if (process.platform === "win32") {
77703
+ throw new Error("Daemon mode (`cerefox web start/stop/status`) is not supported on Windows yet. " + "Run `cerefox web` in the foreground, or set up a Windows service manually.");
77704
+ }
77705
+ }
77706
+ async function startDaemon(opts) {
77707
+ assertUnix();
77708
+ ensureStateDir();
77709
+ const existing = readPidFile();
77710
+ if (existing && isProcessAlive(existing.pid)) {
77711
+ return existing.port === opts.port ? { kind: "already-running", info: existing } : { kind: "port-conflict", info: existing };
77712
+ }
77713
+ if (existing)
77714
+ removePidFile();
77715
+ const logFd = openSync(LOG_FILE, "a");
77716
+ const child = spawn(opts.runtime, [opts.scriptPath, "web", "--host", opts.host, "--port", String(opts.port)], { detached: true, stdio: ["ignore", logFd, logFd] });
77717
+ child.unref();
77718
+ if (typeof child.pid !== "number") {
77719
+ throw new Error("Failed to spawn the web server process (no pid).");
77720
+ }
77721
+ writePidFile({
77722
+ pid: child.pid,
77723
+ port: opts.port,
77724
+ host: opts.host,
77725
+ startedAt: new Date().toISOString()
77726
+ });
77727
+ let responding = false;
77728
+ for (let i = 0;i < 20; i++) {
77729
+ if (!isProcessAlive(child.pid))
77730
+ break;
77731
+ if (await isResponding(opts.host, opts.port)) {
77732
+ responding = true;
77733
+ break;
77734
+ }
77735
+ await sleep2(250);
77736
+ }
77737
+ return { kind: "started", pid: child.pid, responding };
77738
+ }
77739
+ async function stopDaemon() {
77740
+ assertUnix();
77741
+ const info = readPidFile();
77742
+ if (!info || !isProcessAlive(info.pid)) {
77743
+ removePidFile();
77744
+ return { kind: "not-running" };
77745
+ }
77746
+ try {
77747
+ process.kill(info.pid, "SIGTERM");
77748
+ } catch {}
77749
+ let forced = false;
77750
+ let alive = true;
77751
+ for (let i = 0;i < 12; i++) {
77752
+ await sleep2(250);
77753
+ if (!isProcessAlive(info.pid)) {
77754
+ alive = false;
77755
+ break;
77756
+ }
77757
+ }
77758
+ if (alive) {
77759
+ try {
77760
+ process.kill(info.pid, "SIGKILL");
77761
+ forced = true;
77762
+ } catch {}
77763
+ }
77764
+ removePidFile();
77765
+ return { kind: "stopped", pid: info.pid, forced };
77766
+ }
77767
+ function restartCommand(host, port) {
77768
+ const flags = `${host === "127.0.0.1" ? "" : ` --host ${host}`}${port === 8000 ? "" : ` --port ${port}`}`;
77769
+ return `cerefox web stop && cerefox web start${flags}`;
77770
+ }
77771
+ async function statusDaemon() {
77772
+ const info = readPidFile();
77773
+ if (!info)
77774
+ return { kind: "stopped" };
77775
+ if (!isProcessAlive(info.pid))
77776
+ return { kind: "stale", info };
77777
+ const { responding, version } = await probeVersion(info.host, info.port);
77778
+ return { kind: "running", info, responding, version };
77779
+ }
77780
+
77781
+ // src/cli/util/checks.ts
77480
77782
  function checkBinary() {
77481
77783
  return {
77482
77784
  name: "binary",
@@ -77527,7 +77829,7 @@ function checkConfig() {
77527
77829
  hint: "Run `cerefox init` to bootstrap."
77528
77830
  };
77529
77831
  }
77530
- if (!existsSync10(envPath)) {
77832
+ if (!existsSync11(envPath)) {
77531
77833
  const settings = loadSettings();
77532
77834
  if (settings.supabaseUrl && settings.supabaseKey) {
77533
77835
  return {
@@ -77739,9 +78041,9 @@ var SCHEMA_VERSION_RE = /^--\s*@version:\s*(\S+)/m;
77739
78041
  function readBundledSchemaVersion() {
77740
78042
  try {
77741
78043
  const assets = resolveServerAssets();
77742
- if (!existsSync10(assets.schemaFile))
78044
+ if (!existsSync11(assets.schemaFile))
77743
78045
  return null;
77744
- const m = readFileSync7(assets.schemaFile, "utf8").match(SCHEMA_VERSION_RE);
78046
+ const m = readFileSync8(assets.schemaFile, "utf8").match(SCHEMA_VERSION_RE);
77745
78047
  return m ? m[1] : null;
77746
78048
  } catch {
77747
78049
  return null;
@@ -77984,21 +78286,69 @@ async function checkMetadataHealth() {
77984
78286
  };
77985
78287
  }
77986
78288
  function hasCerefoxInJsonFile(path) {
77987
- if (!existsSync10(path))
78289
+ if (!existsSync11(path))
77988
78290
  return false;
77989
78291
  try {
77990
- const parsed = JSON.parse(readFileSync7(path, "utf8"));
78292
+ const parsed = JSON.parse(readFileSync8(path, "utf8"));
77991
78293
  const mcpServers = parsed.mcpServers;
77992
78294
  return Boolean(mcpServers && typeof mcpServers === "object" && "cerefox" in mcpServers);
77993
78295
  } catch {
77994
78296
  return false;
77995
78297
  }
77996
78298
  }
78299
+ async function checkWebDaemon() {
78300
+ let status;
78301
+ try {
78302
+ status = await statusDaemon();
78303
+ } catch {
78304
+ return { name: "web server", status: "skipped", detail: "could not read the daemon pidfile" };
78305
+ }
78306
+ if (status.kind === "stopped") {
78307
+ return { name: "web server", status: "skipped", detail: "no background daemon running" };
78308
+ }
78309
+ if (status.kind === "stale") {
78310
+ return {
78311
+ name: "web server",
78312
+ status: "skipped",
78313
+ detail: `no daemon running (stale pidfile for process ${status.info.pid}).`,
78314
+ hint: "Clean it up: cerefox web stop"
78315
+ };
78316
+ }
78317
+ if (!status.responding) {
78318
+ return {
78319
+ name: "web server",
78320
+ status: "warn",
78321
+ detail: `process ${status.info.pid} is alive but not answering on :${status.info.port}.`,
78322
+ hint: `Check the log, then: ${restartCommand(status.info.host, status.info.port)}`
78323
+ };
78324
+ }
78325
+ if (status.version && status.version !== PKG_VERSION) {
78326
+ return {
78327
+ name: "web server",
78328
+ status: "warn",
78329
+ detail: `running v${status.version} on :${status.info.port}, but this client is v${PKG_VERSION} ` + `— it is still serving the previous build.`,
78330
+ hint: `Restart it: ${restartCommand(status.info.host, status.info.port)}`
78331
+ };
78332
+ }
78333
+ if (!status.version) {
78334
+ return {
78335
+ name: "web server",
78336
+ status: "warn",
78337
+ detail: `responding on :${status.info.port} (pid ${status.info.pid}) but did not report a version.`,
78338
+ hint: `Confirm it is Cerefox: curl http://${status.info.host}:${status.info.port}/api/v1/version`
78339
+ };
78340
+ }
78341
+ return {
78342
+ name: "web server",
78343
+ status: "ok",
78344
+ detail: `v${status.version} on :${status.info.port} (pid ${status.info.pid})`
78345
+ };
78346
+ }
77997
78347
  function checkMcpConfigs() {
77998
- const home = homedir6();
77999
- const claudeCodeUser = join9(home, ".claude.json");
78000
- const claudeCodeProj = join9(process.cwd(), ".mcp.json");
78001
- const claudeDesktop = process.platform === "darwin" ? join9(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : process.platform === "win32" ? join9(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json") : join9(home, ".config", "Claude", "claude_desktop_config.json");
78348
+ const home = homedir7();
78349
+ const claudeCodeUser = join10(home, ".claude.json");
78350
+ const claudeCodeProj = join10(process.cwd(), ".mcp.json");
78351
+ const claudeDesktop = process.platform === "darwin" ? join10(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") : process.platform === "win32" ? join10(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json") : join10(home, ".config", "Claude", "claude_desktop_config.json");
78002
78352
  const found = [];
78003
78353
  if (hasCerefoxInJsonFile(claudeCodeUser))
78004
78354
  found.push("Claude Code (user)");
@@ -78022,8 +78372,8 @@ function checkMcpConfigs() {
78022
78372
  }
78023
78373
  function checkLegacyShadowEnv(opts = {}) {
78024
78374
  const activeEnv = resolveEnvFile(opts);
78025
- const cwdEnv = join9(opts.cwd ?? process.cwd(), ".env");
78026
- if (!existsSync10(activeEnv) || !existsSync10(cwdEnv))
78375
+ const cwdEnv = join10(opts.cwd ?? process.cwd(), ".env");
78376
+ if (!existsSync11(activeEnv) || !existsSync11(cwdEnv))
78027
78377
  return null;
78028
78378
  try {
78029
78379
  if (realpathSync(activeEnv) === realpathSync(cwdEnv))
@@ -78197,6 +78547,7 @@ async function runAllChecks(opts = {}) {
78197
78547
  { name: "metadata health", phase: "Checking metadata well-formedness", run: () => checkMetadataHealth() },
78198
78548
  { name: "edge functions", phase: "Probing Edge Function versions", run: () => checkEdgeFunctionsCompat() },
78199
78549
  { name: "postgres", phase: "Probing Postgres DDL endpoint", run: () => checkPostgres() },
78550
+ { name: "web server", phase: "Probing the web daemon", run: () => checkWebDaemon() },
78200
78551
  { name: "mcp clients", phase: "Scanning MCP client configs", run: () => checkMcpConfigs() }
78201
78552
  ];
78202
78553
  return runSteps(steps, opts);
@@ -78592,11 +78943,11 @@ function registerGetDoc(program) {
78592
78943
  init_dist4();
78593
78944
  init_cli_core();
78594
78945
  init_config();
78595
- import { readFileSync as readFileSync9 } from "node:fs";
78946
+ import { readFileSync as readFileSync10 } from "node:fs";
78596
78947
  import { basename as basename2, extname as extname3 } from "node:path";
78597
78948
 
78598
78949
  // src/ingestion/pipeline.ts
78599
- import { readFileSync as readFileSync8 } from "node:fs";
78950
+ import { readFileSync as readFileSync9 } from "node:fs";
78600
78951
  import { basename, extname as extname2, resolve as resolve3 } from "node:path";
78601
78952
  // ../../_shared/ingest/pipeline-helpers.ts
78602
78953
  import { createHash } from "node:crypto";
@@ -79259,7 +79610,7 @@ class IngestionPipeline {
79259
79610
  };
79260
79611
  }
79261
79612
  async ingestFile(path, opts = {}) {
79262
- const text = await fileToMarkdown(path, readFileSync8(path));
79613
+ const text = await fileToMarkdown(path, readFileSync9(path));
79263
79614
  const absPath = resolve3(path);
79264
79615
  const stem = basename(absPath, extname2(absPath));
79265
79616
  return this.ingestText({
@@ -79294,7 +79645,7 @@ async function readContent(path, paste) {
79294
79645
  }
79295
79646
  let content;
79296
79647
  try {
79297
- content = readFileSync9(path, "utf8");
79648
+ content = readFileSync10(path, "utf8");
79298
79649
  } catch (err) {
79299
79650
  const msg = err instanceof Error ? err.message : String(err);
79300
79651
  throw userError(`Cannot read ${path}: ${msg}`);
@@ -79404,14 +79755,14 @@ init_cli_core();
79404
79755
  init_mcp_tools();
79405
79756
  init_config();
79406
79757
  init_client();
79407
- import { readFileSync as readFileSync10 } from "node:fs";
79758
+ import { readFileSync as readFileSync11 } from "node:fs";
79408
79759
  function resolveText(value, what) {
79409
79760
  if (value === undefined)
79410
79761
  throw userError(`${what} is required.`);
79411
79762
  if (value === "-")
79412
- return readFileSync10(0, "utf8");
79763
+ return readFileSync11(0, "utf8");
79413
79764
  if (value.startsWith("@"))
79414
- return readFileSync10(value.slice(1), "utf8");
79765
+ return readFileSync11(value.slice(1), "utf8");
79415
79766
  return value;
79416
79767
  }
79417
79768
  function context() {
@@ -79472,7 +79823,7 @@ init_cli_core();
79472
79823
  init_config();
79473
79824
  var import_cli_progress = __toESM(require_cli_progress(), 1);
79474
79825
  import { readdirSync as readdirSync4, statSync as statSync3 } from "node:fs";
79475
- import { basename as basename3, extname as extname4, join as join10 } from "node:path";
79826
+ import { basename as basename3, extname as extname4, join as join11 } from "node:path";
79476
79827
  function walk(dir, extensions) {
79477
79828
  let entries;
79478
79829
  try {
@@ -79482,7 +79833,7 @@ function walk(dir, extensions) {
79482
79833
  }
79483
79834
  const files = [];
79484
79835
  for (const name of entries) {
79485
- const full = join10(dir, name);
79836
+ const full = join11(dir, name);
79486
79837
  let stat;
79487
79838
  try {
79488
79839
  stat = statSync3(full);
@@ -79585,20 +79936,20 @@ import { spawnSync as spawnSync4 } from "node:child_process";
79585
79936
  import {
79586
79937
  chmodSync,
79587
79938
  copyFileSync as copyFileSync2,
79588
- existsSync as existsSync11,
79589
- mkdirSync as mkdirSync4,
79590
- readFileSync as readFileSync12,
79591
- writeFileSync as writeFileSync4
79939
+ existsSync as existsSync12,
79940
+ mkdirSync as mkdirSync5,
79941
+ readFileSync as readFileSync13,
79942
+ writeFileSync as writeFileSync5
79592
79943
  } from "node:fs";
79593
- import { homedir as homedir7 } from "node:os";
79594
- import { dirname as dirname4, join as join11 } from "node:path";
79944
+ import { homedir as homedir8 } from "node:os";
79945
+ import { dirname as dirname4, join as join12 } from "node:path";
79595
79946
  async function readConfigFile(path) {
79596
- if (!existsSync11(path)) {
79947
+ if (!existsSync12(path)) {
79597
79948
  throw userError(`--config file not found: ${path}`);
79598
79949
  }
79599
79950
  let parsed;
79600
79951
  try {
79601
- parsed = JSON.parse(readFileSync12(path, "utf8"));
79952
+ parsed = JSON.parse(readFileSync13(path, "utf8"));
79602
79953
  } catch (err) {
79603
79954
  throw userError(`--config: invalid JSON in ${path}: ${err instanceof Error ? err.message : String(err)}`);
79604
79955
  }
@@ -79640,7 +79991,7 @@ function parseDotEnvFile(content) {
79640
79991
  return map;
79641
79992
  }
79642
79993
  function answersFromEnvFile(path) {
79643
- const parsed = parseDotEnvFile(readFileSync12(path, "utf8"));
79994
+ const parsed = parseDotEnvFile(readFileSync13(path, "utf8"));
79644
79995
  const required = ["CEREFOX_SUPABASE_URL", "CEREFOX_SUPABASE_KEY", "OPENAI_API_KEY"];
79645
79996
  for (const key of required) {
79646
79997
  if (!parsed[key] || parsed[key].trim() === "") {
@@ -79895,8 +80246,8 @@ async function postWriteLifecycle(envPath, options) {
79895
80246
  println(c.dim(` Config in effect: ${envPath}`));
79896
80247
  }
79897
80248
  function writeAnswersTo(target, answers) {
79898
- mkdirSync4(dirname4(target), { recursive: true });
79899
- writeFileSync4(target, buildEnvFile(answers), "utf8");
80249
+ mkdirSync5(dirname4(target), { recursive: true });
80250
+ writeFileSync5(target, buildEnvFile(answers), "utf8");
79900
80251
  if (process.platform !== "win32") {
79901
80252
  try {
79902
80253
  chmodSync(target, 384);
@@ -79906,12 +80257,12 @@ function writeAnswersTo(target, answers) {
79906
80257
  }
79907
80258
  }
79908
80259
  async function action23(options) {
79909
- const homeEnv = join11(homedir7(), USER_STATE_DIR_NAME, ".env");
79910
- const cwdEnv = join11(process.cwd(), ".env");
80260
+ const homeEnv = join12(homedir8(), USER_STATE_DIR_NAME, ".env");
80261
+ const cwdEnv = join12(process.cwd(), ".env");
79911
80262
  const explicitDir = (process.env.CEREFOX_CONFIG_DIR ?? "").trim();
79912
80263
  if (explicitDir) {
79913
80264
  const target = resolveEnvFile();
79914
- if (existsSync11(target) && !options.force) {
80265
+ if (existsSync12(target) && !options.force) {
79915
80266
  println(c.yellow(`⚠ Config already exists at ${target}.`));
79916
80267
  const ok = await confirm("Overwrite?", true);
79917
80268
  if (!ok) {
@@ -79933,7 +80284,7 @@ async function action23(options) {
79933
80284
  await postWriteLifecycle(target, options);
79934
80285
  return;
79935
80286
  }
79936
- if (existsSync11(homeEnv) && !options.force) {
80287
+ if (existsSync12(homeEnv) && !options.force) {
79937
80288
  println(c.yellow(`⚠ Config already exists at ${homeEnv}.`));
79938
80289
  const ok = await confirm("Overwrite?", true);
79939
80290
  if (!ok) {
@@ -79954,12 +80305,12 @@ async function action23(options) {
79954
80305
  await postWriteLifecycle(homeEnv, options);
79955
80306
  return;
79956
80307
  }
79957
- if (existsSync11(cwdEnv) && !options.force && !options.config) {
80308
+ if (existsSync12(cwdEnv) && !options.force && !options.config) {
79958
80309
  printMigrationMenu(cwdEnv, homeEnv);
79959
80310
  const ch = await promptMigrationChoice();
79960
80311
  println("");
79961
80312
  if (ch === "c") {
79962
- mkdirSync4(dirname4(homeEnv), { recursive: true });
80313
+ mkdirSync5(dirname4(homeEnv), { recursive: true });
79963
80314
  copyFileSync2(cwdEnv, homeEnv);
79964
80315
  if (process.platform !== "win32") {
79965
80316
  try {
@@ -80554,35 +80905,35 @@ function registerMigrateFormat(program) {
80554
80905
  // src/cli/commands/restore.ts
80555
80906
  init_cli_core();
80556
80907
  init_client();
80557
- import { existsSync as existsSync13, readFileSync as readFileSync14, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
80558
- import { homedir as homedir8 } from "node:os";
80559
- import { join as join12, resolve as resolve4 } from "node:path";
80908
+ import { existsSync as existsSync14, readFileSync as readFileSync15, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
80909
+ import { homedir as homedir9 } from "node:os";
80910
+ import { join as join13, resolve as resolve4 } from "node:path";
80560
80911
  function expandHome2(path) {
80561
80912
  if (path === "~")
80562
- return homedir8();
80913
+ return homedir9();
80563
80914
  if (path.startsWith("~/"))
80564
- return join12(homedir8(), path.slice(2));
80915
+ return join13(homedir9(), path.slice(2));
80565
80916
  return path;
80566
80917
  }
80567
80918
  function resolveBackupFile(target) {
80568
80919
  const path = resolve4(expandHome2(target));
80569
- if (!existsSync13(path)) {
80920
+ if (!existsSync14(path)) {
80570
80921
  throw userError(`Backup path not found: ${target}`);
80571
80922
  }
80572
80923
  const stat = statSync4(path);
80573
80924
  if (stat.isFile())
80574
80925
  return path;
80575
- const candidates = readdirSync5(path).filter((n) => n.endsWith(".json") && n.startsWith("cerefox-")).map((n) => ({ name: n, mtime: statSync4(join12(path, n)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
80926
+ const candidates = readdirSync5(path).filter((n) => n.endsWith(".json") && n.startsWith("cerefox-")).map((n) => ({ name: n, mtime: statSync4(join13(path, n)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
80576
80927
  if (candidates.length === 0) {
80577
80928
  throw userError(`No cerefox-*.json files in ${path}`);
80578
80929
  }
80579
- return join12(path, candidates[0].name);
80930
+ return join13(path, candidates[0].name);
80580
80931
  }
80581
80932
  async function action31(target, options) {
80582
80933
  const file = resolveBackupFile(target);
80583
80934
  let payload;
80584
80935
  try {
80585
- payload = JSON.parse(readFileSync14(file, "utf8"));
80936
+ payload = JSON.parse(readFileSync15(file, "utf8"));
80586
80937
  } catch (err) {
80587
80938
  throw userError(`Could not parse backup file ${file}: ${err instanceof Error ? err.message : String(err)}`);
80588
80939
  }
@@ -80993,6 +81344,12 @@ async function action33(options) {
80993
81344
  }
80994
81345
  println("");
80995
81346
  println(c.green(`✓ Upgraded to ${target}.`));
81347
+ const daemon = await statusDaemon().catch(() => null);
81348
+ if (daemon?.kind === "running" && daemon.version !== target) {
81349
+ println("");
81350
+ println(c.yellow(`⚠ The web server is still running the previous build ` + `(pid ${daemon.info.pid} on :${daemon.info.port}` + `${daemon.version ? `, v${daemon.version}` : ""}).`));
81351
+ println(" Restart it: " + c.bold(restartCommand(daemon.info.host, daemon.info.port)));
81352
+ }
80996
81353
  println("");
80997
81354
  println("Next steps:");
80998
81355
  println(" 1. " + c.bold("cerefox server deploy") + " apply this release's schema/RPC/EF updates");
@@ -81050,7 +81407,7 @@ init_config();
81050
81407
  import { randomBytes } from "node:crypto";
81051
81408
 
81052
81409
  // src/cli/util/env-file.ts
81053
- import { copyFileSync as copyFileSync3, existsSync as existsSync14, readFileSync as readFileSync15, writeFileSync as writeFileSync5 } from "node:fs";
81410
+ import { copyFileSync as copyFileSync3, existsSync as existsSync15, readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "node:fs";
81054
81411
  import { dirname as dirname5 } from "node:path";
81055
81412
  import { spawnSync as spawnSync6 } from "node:child_process";
81056
81413
  function escapeRegExp(s) {
@@ -81060,12 +81417,12 @@ function upsertEnvVar(path, key, value, opts = {}) {
81060
81417
  const line = `${key}=${value}`;
81061
81418
  const header = opts.comment ? `# ${opts.comment}
81062
81419
  ` : "";
81063
- if (!existsSync14(path)) {
81064
- writeFileSync5(path, `${header}${line}
81420
+ if (!existsSync15(path)) {
81421
+ writeFileSync6(path, `${header}${line}
81065
81422
  `, { mode: 384 });
81066
81423
  return { path, action: "created" };
81067
81424
  }
81068
- const original = readFileSync15(path, "utf8");
81425
+ const original = readFileSync16(path, "utf8");
81069
81426
  let backupPath;
81070
81427
  if (!opts.noBackup) {
81071
81428
  backupPath = `${path}.pre-cerefox.bak`;
@@ -81086,13 +81443,13 @@ ${header}${line}
81086
81443
  `;
81087
81444
  action = "added";
81088
81445
  }
81089
- writeFileSync5(path, next);
81446
+ writeFileSync6(path, next);
81090
81447
  return { path, action, backupPath };
81091
81448
  }
81092
81449
  function readEnvVar(path, key) {
81093
- if (!existsSync14(path))
81450
+ if (!existsSync15(path))
81094
81451
  return null;
81095
- const m = readFileSync15(path, "utf8").match(new RegExp(`^\\s*${escapeRegExp(key)}=(.*)$`, "m"));
81452
+ const m = readFileSync16(path, "utf8").match(new RegExp(`^\\s*${escapeRegExp(key)}=(.*)$`, "m"));
81096
81453
  return m ? m[1].trim() : null;
81097
81454
  }
81098
81455
  function envGitignoreWarning(path) {
@@ -81113,9 +81470,9 @@ function envGitignoreWarning(path) {
81113
81470
  }
81114
81471
 
81115
81472
  // src/web/auth.ts
81116
- import { existsSync as existsSync15 } from "node:fs";
81473
+ import { existsSync as existsSync16 } from "node:fs";
81117
81474
 
81118
- // ../../node_modules/.bun/@hono+node-server@2.0.12+19b9adbcbc3b652f/node_modules/@hono/node-server/dist/conninfo.mjs
81475
+ // ../../node_modules/.bun/@hono+node-server@2.1.1+2ac783cc5e75a70c/node_modules/@hono/node-server/dist/conninfo.mjs
81119
81476
  var getConnInfo = (c) => {
81120
81477
  const bindings = c.env.server ? c.env.server : c.env;
81121
81478
  const address = bindings.incoming.socket.remoteAddress;
@@ -81178,7 +81535,7 @@ var LOOPBACK_ADDRESSES = new Set([
81178
81535
  ]);
81179
81536
  function isContainerised() {
81180
81537
  try {
81181
- return existsSync15("/.dockerenv");
81538
+ return existsSync16("/.dockerenv");
81182
81539
  } catch {
81183
81540
  return false;
81184
81541
  }
@@ -81458,15 +81815,15 @@ init_sync_self_docs();
81458
81815
  init_cli_core();
81459
81816
  import { readFileSync as readFileSync20 } from "node:fs";
81460
81817
 
81461
- // ../../node_modules/.bun/@hono+node-server@2.0.12+19b9adbcbc3b652f/node_modules/@hono/node-server/dist/constants-BLSFu_RU.mjs
81818
+ // ../../node_modules/.bun/@hono+node-server@2.1.1+2ac783cc5e75a70c/node_modules/@hono/node-server/dist/constants-BLSFu_RU.mjs
81462
81819
  var X_ALREADY_SENT = "x-hono-already-sent";
81463
81820
 
81464
- // ../../node_modules/.bun/@hono+node-server@2.0.12+19b9adbcbc3b652f/node_modules/@hono/node-server/dist/index.mjs
81821
+ // ../../node_modules/.bun/@hono+node-server@2.1.1+2ac783cc5e75a70c/node_modules/@hono/node-server/dist/index.mjs
81465
81822
  import { STATUS_CODES, createServer } from "node:http";
81466
81823
  import { Http2ServerRequest, constants } from "node:http2";
81467
81824
  import { Readable } from "node:stream";
81468
81825
 
81469
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/helper/websocket/index.js
81826
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/helper/websocket/index.js
81470
81827
  var defineWebSocketHelper = (handler) => {
81471
81828
  return (...args) => {
81472
81829
  if (typeof args[0] === "function") {
@@ -81492,13 +81849,174 @@ var defineWebSocketHelper = (handler) => {
81492
81849
  };
81493
81850
  };
81494
81851
 
81495
- // ../../node_modules/.bun/@hono+node-server@2.0.12+19b9adbcbc3b652f/node_modules/@hono/node-server/dist/index.mjs
81852
+ // ../../node_modules/.bun/@hono+node-server@2.1.1+2ac783cc5e75a70c/node_modules/@hono/node-server/dist/index.mjs
81496
81853
  var RequestError = class extends Error {
81497
81854
  constructor(message, options) {
81498
81855
  super(message, options);
81499
81856
  this.name = "RequestError";
81500
81857
  }
81501
81858
  };
81859
+ var nonJoinedHeaders = new Set([
81860
+ "age",
81861
+ "authorization",
81862
+ "content-length",
81863
+ "content-type",
81864
+ "etag",
81865
+ "expires",
81866
+ "from",
81867
+ "host",
81868
+ "if-modified-since",
81869
+ "if-unmodified-since",
81870
+ "last-modified",
81871
+ "location",
81872
+ "max-forwards",
81873
+ "proxy-authorization",
81874
+ "referer",
81875
+ "retry-after",
81876
+ "server",
81877
+ "user-agent"
81878
+ ]);
81879
+ var validHeaderName = /^[!#$%&'*+\-.^_`|~\dA-Za-z]+$/;
81880
+ var isHttpWhitespace = (code) => code === 9 || code === 10 || code === 13 || code === 32;
81881
+ var normalizeHeaderValue = (value) => {
81882
+ if (!isHttpWhitespace(value.charCodeAt(0)) && !isHttpWhitespace(value.charCodeAt(value.length - 1)))
81883
+ return value;
81884
+ let start = 0;
81885
+ let end = value.length;
81886
+ while (start < end && isHttpWhitespace(value.charCodeAt(start)))
81887
+ start++;
81888
+ while (end > start && isHttpWhitespace(value.charCodeAt(end - 1)))
81889
+ end--;
81890
+ return value.slice(start, end);
81891
+ };
81892
+ var forbiddenHeaderValue = /[\0\r\n]/;
81893
+ var GlobalHeaders = globalThis.Headers;
81894
+ var materializeHeaders = (rawHeaders, HeadersCtor = GlobalHeaders) => {
81895
+ const headers = new HeadersCtor;
81896
+ for (let i = 0;i < rawHeaders.length; i += 2) {
81897
+ const name = rawHeaders[i];
81898
+ if (!name.startsWith(":"))
81899
+ headers.append(name, rawHeaders[i + 1]);
81900
+ }
81901
+ return headers;
81902
+ };
81903
+ var RequestHeaders = class {
81904
+ #incoming;
81905
+ #rawHeaders;
81906
+ #headers;
81907
+ #invalidValue;
81908
+ constructor(incoming) {
81909
+ this.#incoming = incoming;
81910
+ if (incoming instanceof Http2ServerRequest)
81911
+ this.#rawHeaders = incoming.rawHeaders.slice();
81912
+ }
81913
+ get #lazyRawHeaders() {
81914
+ return this.#rawHeaders ??= this.#incoming.rawHeaders.slice();
81915
+ }
81916
+ get #native() {
81917
+ if (!this.#headers) {
81918
+ this.#headers = materializeHeaders(this.#lazyRawHeaders);
81919
+ this.#rawHeaders = undefined;
81920
+ }
81921
+ return this.#headers;
81922
+ }
81923
+ #normalizedName(name) {
81924
+ if (typeof name !== "string")
81925
+ return;
81926
+ if (!validHeaderName.test(name))
81927
+ throw new TypeError(`Invalid header name: ${name}`);
81928
+ return name.toLowerCase();
81929
+ }
81930
+ #lookupHttp1(lowerName) {
81931
+ const headers = this.#incoming instanceof Http2ServerRequest ? undefined : this.#incoming.headers;
81932
+ if (!headers || nonJoinedHeaders.has(lowerName) || lowerName === "set-cookie" || lowerName === "__proto__")
81933
+ return;
81934
+ if (!Object.hasOwn(headers, lowerName))
81935
+ return null;
81936
+ const rawValue = headers[lowerName];
81937
+ if (typeof rawValue === "string") {
81938
+ const value = normalizeHeaderValue(rawValue);
81939
+ return forbiddenHeaderValue.test(value) ? undefined : value;
81940
+ }
81941
+ }
81942
+ #lookup(rawHeaders, lowerName) {
81943
+ const separator = lowerName === "cookie" ? "; " : ", ";
81944
+ let value = null;
81945
+ for (let i = 0;i < rawHeaders.length; i += 2) {
81946
+ const rawName = rawHeaders[i];
81947
+ if (rawName.length === lowerName.length && rawName.toLowerCase() === lowerName) {
81948
+ const rawValue = normalizeHeaderValue(rawHeaders[i + 1]);
81949
+ if (forbiddenHeaderValue.test(rawValue)) {
81950
+ this.#invalidValue = true;
81951
+ return;
81952
+ }
81953
+ value = value === null ? rawValue : value + separator + rawValue;
81954
+ }
81955
+ }
81956
+ return value;
81957
+ }
81958
+ append(name, value) {
81959
+ this.#native.append(name, value);
81960
+ }
81961
+ delete(name) {
81962
+ this.#native.delete(name);
81963
+ }
81964
+ get(name) {
81965
+ const lowerName = this.#normalizedName(name);
81966
+ if (lowerName && !this.#headers && !this.#invalidValue) {
81967
+ const http1Value = this.#lookupHttp1(lowerName);
81968
+ if (http1Value !== undefined)
81969
+ return http1Value;
81970
+ const value = this.#lookup(this.#lazyRawHeaders, lowerName);
81971
+ if (value !== undefined)
81972
+ return value;
81973
+ }
81974
+ return this.#native.get(name);
81975
+ }
81976
+ has(name) {
81977
+ const lowerName = this.#normalizedName(name);
81978
+ if (lowerName && !this.#headers && !this.#invalidValue) {
81979
+ const http1Value = this.#lookupHttp1(lowerName);
81980
+ if (http1Value !== undefined)
81981
+ return http1Value !== null;
81982
+ const value = this.#lookup(this.#lazyRawHeaders, lowerName);
81983
+ if (value !== undefined)
81984
+ return value !== null;
81985
+ }
81986
+ return this.#native.has(name);
81987
+ }
81988
+ set(name, value) {
81989
+ this.#native.set(name, value);
81990
+ }
81991
+ getSetCookie() {
81992
+ return this.#native.getSetCookie();
81993
+ }
81994
+ keys() {
81995
+ return this.#native.keys();
81996
+ }
81997
+ values() {
81998
+ return this.#native.values();
81999
+ }
82000
+ entries() {
82001
+ return this.#native.entries();
82002
+ }
82003
+ forEach(callback, thisArg) {
82004
+ this.#native.forEach((value, key) => {
82005
+ callback.call(thisArg, value, key, this);
82006
+ });
82007
+ }
82008
+ [Symbol.iterator]() {
82009
+ return this.entries();
82010
+ }
82011
+ };
82012
+ Object.defineProperty(RequestHeaders.prototype, Symbol.for("nodejs.util.inspect.custom"), { value: function(depth, options, inspectFn) {
82013
+ return `Headers (lightweight) ${inspectFn(Object.fromEntries(this), {
82014
+ ...options,
82015
+ depth: depth == null ? null : depth - 1
82016
+ })}`;
82017
+ } });
82018
+ Object.setPrototypeOf(RequestHeaders.prototype, GlobalHeaders.prototype);
82019
+ var newHeadersFromIncoming = (incoming) => globalThis.Headers === GlobalHeaders ? new RequestHeaders(incoming) : materializeHeaders(incoming.rawHeaders, globalThis.Headers);
81502
82020
  var reValidRequestUrl = /^\/[!#$&-;=?-\[\]_a-z~]*$/;
81503
82021
  var reDotSegment = /\/\.\.?(?:[/?#]|$)/;
81504
82022
  var reValidHost = /^[a-z0-9._-]+(?::(?:[1-5]\d{3,4}|[6-9]\d{3}))?$/;
@@ -81538,16 +82056,6 @@ var Request$1 = class extends GlobalRequest {
81538
82056
  super(input, options);
81539
82057
  }
81540
82058
  };
81541
- var newHeadersFromIncoming = (incoming) => {
81542
- const headerRecord = [];
81543
- const rawHeaders = incoming.rawHeaders;
81544
- for (let i = 0, len = rawHeaders.length;i < len; i += 2) {
81545
- const key = rawHeaders[i];
81546
- if (key.charCodeAt(0) !== 58)
81547
- headerRecord.push([key, rawHeaders[i + 1]]);
81548
- }
81549
- return new Headers(headerRecord);
81550
- };
81551
82059
  var wrapBodyStream = Symbol("wrapBodyStream");
81552
82060
  var byteExactEncodings = new Set([
81553
82061
  "latin1",
@@ -82248,8 +82756,12 @@ var drainIncoming = (incoming) => {
82248
82756
  const forceClose = () => {
82249
82757
  cleanup();
82250
82758
  const socket = incoming.socket;
82251
- if (socket && !socket.destroyed)
82252
- socket.destroySoon();
82759
+ if (socket && !socket.destroyed) {
82760
+ if (typeof socket.destroySoon === "function")
82761
+ socket.destroySoon();
82762
+ else if (typeof socket.destroy === "function")
82763
+ socket.destroy();
82764
+ }
82253
82765
  };
82254
82766
  const timer = setTimeout(forceClose, DRAIN_TIMEOUT_MS);
82255
82767
  timer.unref?.();
@@ -82766,7 +83278,7 @@ var serve = (options, listeningListener) => {
82766
83278
  return server;
82767
83279
  };
82768
83280
 
82769
- // ../../node_modules/.bun/@hono+node-server@2.0.12+19b9adbcbc3b652f/node_modules/@hono/node-server/dist/utils/stream.mjs
83281
+ // ../../node_modules/.bun/@hono+node-server@2.1.1+2ac783cc5e75a70c/node_modules/@hono/node-server/dist/utils/stream.mjs
82770
83282
  import { Readable as Readable2 } from "node:stream";
82771
83283
  import { versions as versions2 } from "node:process";
82772
83284
  var pr54206Applied = () => {
@@ -82832,7 +83344,7 @@ var createStreamBody = (stream, useNativeReadableToWeb = useReadableToWeb) => {
82832
83344
  });
82833
83345
  };
82834
83346
 
82835
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/utils/mime.js
83347
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/mime.js
82836
83348
  var getMimeType = (filename, mimes = baseMimes) => {
82837
83349
  const regexp = /\.([a-zA-Z0-9]+?)$/;
82838
83350
  const match = filename.match(regexp);
@@ -82901,9 +83413,9 @@ var _baseMimes = {
82901
83413
  };
82902
83414
  var baseMimes = _baseMimes;
82903
83415
 
82904
- // ../../node_modules/.bun/@hono+node-server@2.0.12+19b9adbcbc3b652f/node_modules/@hono/node-server/dist/serve-static.mjs
82905
- import { createReadStream, existsSync as existsSync16, statSync as statSync5 } from "node:fs";
82906
- import { join as join13 } from "node:path";
83416
+ // ../../node_modules/.bun/@hono+node-server@2.1.1+2ac783cc5e75a70c/node_modules/@hono/node-server/dist/serve-static.mjs
83417
+ import { createReadStream, existsSync as existsSync17, statSync as statSync5 } from "node:fs";
83418
+ import { join as join14 } from "node:path";
82907
83419
  var COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i;
82908
83420
  var ENCODINGS = {
82909
83421
  br: ".br",
@@ -82976,7 +83488,7 @@ var tryDecodeURI = (str) => tryDecode(str, decodeURI);
82976
83488
  var serveStatic = (options = { root: "" }) => {
82977
83489
  const root = options.root || "";
82978
83490
  const optionPath = options.path;
82979
- if (root !== "" && !existsSync16(root))
83491
+ if (root !== "" && !existsSync17(root))
82980
83492
  console.error(`serveStatic: root path '${root}' is not found, are you sure it's correct?`);
82981
83493
  return async (c, next) => {
82982
83494
  if (c.finalized)
@@ -82993,11 +83505,11 @@ var serveStatic = (options = { root: "" }) => {
82993
83505
  await options.onNotFound?.(c.req.path, c);
82994
83506
  return next();
82995
83507
  }
82996
- let path = join13(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c) : filename);
83508
+ let path = join14(root, !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c) : filename);
82997
83509
  let stats = getStats(path);
82998
83510
  if (stats && stats.isDirectory()) {
82999
83511
  const indexFile = options.index ?? "index.html";
83000
- path = join13(path, indexFile);
83512
+ path = join14(path, indexFile);
83001
83513
  stats = getStats(path);
83002
83514
  }
83003
83515
  if (!stats) {
@@ -83059,11 +83571,11 @@ var serveStatic = (options = { root: "" }) => {
83059
83571
  };
83060
83572
 
83061
83573
  // src/web/server.ts
83062
- import { existsSync as existsSync20 } from "node:fs";
83063
- import { readFileSync as readFileSync18 } from "node:fs";
83064
- import { join as join17 } from "node:path";
83574
+ import { existsSync as existsSync21 } from "node:fs";
83575
+ import { readFileSync as readFileSync19, statSync as statSync8 } from "node:fs";
83576
+ import { join as join18 } from "node:path";
83065
83577
 
83066
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/compose.js
83578
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/compose.js
83067
83579
  var compose = (middleware, onError, onNotFound) => {
83068
83580
  return (context, next) => {
83069
83581
  let index = -1;
@@ -83107,10 +83619,10 @@ var compose = (middleware, onError, onNotFound) => {
83107
83619
  };
83108
83620
  };
83109
83621
 
83110
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/request/constants.js
83622
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/request/constants.js
83111
83623
  var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
83112
83624
 
83113
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/utils/buffer.js
83625
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/buffer.js
83114
83626
  var bufferToFormData = (arrayBuffer, contentType) => {
83115
83627
  const response = new Response(arrayBuffer, {
83116
83628
  headers: {
@@ -83120,7 +83632,9 @@ var bufferToFormData = (arrayBuffer, contentType) => {
83120
83632
  return response.formData();
83121
83633
  };
83122
83634
 
83123
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/utils/body.js
83635
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/body.js
83636
+ var MAX_NESTING_DEPTH = 32;
83637
+ var MAX_NESTED_OBJECTS = 1e4;
83124
83638
  var isRawRequest = (request) => ("headers" in request);
83125
83639
  var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
83126
83640
  const { all = false, dot = false } = options;
@@ -83150,6 +83664,7 @@ async function parseFormData(request, options) {
83150
83664
  }
83151
83665
  function convertFormDataToBodyData(formData, options) {
83152
83666
  const form = /* @__PURE__ */ Object.create(null);
83667
+ const nestingState = { count: 0 };
83153
83668
  formData.forEach((value, key) => {
83154
83669
  const shouldParseAllValues = options.all || key.endsWith("[]");
83155
83670
  if (!shouldParseAllValues) {
@@ -83162,7 +83677,7 @@ function convertFormDataToBodyData(formData, options) {
83162
83677
  Object.entries(form).forEach(([key, value]) => {
83163
83678
  const shouldParseDotValues = key.includes(".");
83164
83679
  if (shouldParseDotValues) {
83165
- handleParsingNestedValues(form, key, value);
83680
+ handleParsingNestedValues(form, key, value, nestingState);
83166
83681
  delete form[key];
83167
83682
  }
83168
83683
  });
@@ -83184,25 +83699,34 @@ var handleParsingAllValues = (form, key, value) => {
83184
83699
  }
83185
83700
  }
83186
83701
  };
83187
- var handleParsingNestedValues = (form, key, value) => {
83702
+ var handleParsingNestedValues = (form, key, value, state) => {
83188
83703
  if (/(?:^|\.)__proto__\./.test(key)) {
83189
83704
  return;
83190
83705
  }
83191
83706
  let nestedForm = form;
83192
- const keys = key.split(".");
83707
+ const keys = key.split(".", MAX_NESTING_DEPTH + 2);
83708
+ if (keys.length > MAX_NESTING_DEPTH + 1) {
83709
+ throwNestingLimitExceeded();
83710
+ }
83193
83711
  keys.forEach((key2, index) => {
83194
83712
  if (index === keys.length - 1) {
83195
83713
  nestedForm[key2] = value;
83196
83714
  } else {
83197
83715
  if (!nestedForm[key2] || typeof nestedForm[key2] !== "object" || Array.isArray(nestedForm[key2]) || nestedForm[key2] instanceof File) {
83716
+ if (state.count++ >= MAX_NESTED_OBJECTS) {
83717
+ throwNestingLimitExceeded();
83718
+ }
83198
83719
  nestedForm[key2] = /* @__PURE__ */ Object.create(null);
83199
83720
  }
83200
83721
  nestedForm = nestedForm[key2];
83201
83722
  }
83202
83723
  });
83203
83724
  };
83725
+ var throwNestingLimitExceeded = () => {
83726
+ throw new Error("Nesting limit exceeded");
83727
+ };
83204
83728
 
83205
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/utils/url.js
83729
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/url.js
83206
83730
  var splitPath = (path) => {
83207
83731
  const paths = path.split("/");
83208
83732
  if (paths[0] === "") {
@@ -83308,13 +83832,13 @@ var checkOptionalParameter = (path) => {
83308
83832
  if (segment !== "" && !/\:/.test(segment)) {
83309
83833
  basePath += "/" + segment;
83310
83834
  } else if (/\:/.test(segment)) {
83311
- if (/\?/.test(segment)) {
83835
+ if (segment.charCodeAt(segment.length - 1) === 63) {
83312
83836
  if (results.length === 0 && basePath === "") {
83313
83837
  results.push("/");
83314
83838
  } else {
83315
83839
  results.push(basePath);
83316
83840
  }
83317
- const optionalSegment = segment.replace("?", "");
83841
+ const optionalSegment = segment.slice(0, -1);
83318
83842
  basePath += "/" + optionalSegment;
83319
83843
  results.push(basePath);
83320
83844
  } else {
@@ -83324,18 +83848,20 @@ var checkOptionalParameter = (path) => {
83324
83848
  });
83325
83849
  return results.filter((v, i, a) => a.indexOf(v) === i);
83326
83850
  };
83851
+ var tryDecodeURIComponent = (str) => str.indexOf("%") !== -1 ? tryDecode2(str, decodeURIComponent_) : str;
83327
83852
  var _decodeURI = (value) => {
83328
- if (!/[%+]/.test(value)) {
83329
- return value;
83330
- }
83331
83853
  if (value.indexOf("+") !== -1) {
83332
83854
  value = value.replace(/\+/g, " ");
83333
83855
  }
83334
- return value.indexOf("%") !== -1 ? tryDecode2(value, decodeURIComponent_) : value;
83856
+ return tryDecodeURIComponent(value);
83335
83857
  };
83336
83858
  var _getQueryParam = (url, key, multiple) => {
83859
+ const hashIndex = url.indexOf("#", 8);
83860
+ if (hashIndex !== -1) {
83861
+ url = url.slice(0, hashIndex);
83862
+ }
83337
83863
  let encoded;
83338
- if (!multiple && key && !/[%+]/.test(key)) {
83864
+ if (!multiple && key && key.indexOf("%") === -1 && key.indexOf("+") === -1) {
83339
83865
  let keyIndex2 = url.indexOf("?", 8);
83340
83866
  if (keyIndex2 === -1) {
83341
83867
  return;
@@ -83402,8 +83928,7 @@ var getQueryParams = (url, key) => {
83402
83928
  };
83403
83929
  var decodeURIComponent_ = decodeURIComponent;
83404
83930
 
83405
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/request.js
83406
- var tryDecodeURIComponent = (str) => tryDecode2(str, decodeURIComponent_);
83931
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/request.js
83407
83932
  var HonoRequest = class {
83408
83933
  raw;
83409
83934
  #validatedData;
@@ -83415,23 +83940,22 @@ var HonoRequest = class {
83415
83940
  this.raw = request;
83416
83941
  this.path = path;
83417
83942
  this.#matchResult = matchResult;
83418
- this.#validatedData = {};
83419
83943
  }
83420
83944
  param(key) {
83421
83945
  return key ? this.#getDecodedParam(key) : this.#getAllDecodedParams();
83422
83946
  }
83423
83947
  #getDecodedParam(key) {
83424
- const paramKey = this.#matchResult[0][this.routeIndex][1][key];
83948
+ const paramKey = this.#matchResult[0][this.routeIndex]?.[1][key];
83425
83949
  const param = this.#getParamValue(paramKey);
83426
- return param && /\%/.test(param) ? tryDecodeURIComponent(param) : param;
83950
+ return param && tryDecodeURIComponent(param);
83427
83951
  }
83428
83952
  #getAllDecodedParams() {
83429
83953
  const decoded = {};
83430
- const keys = Object.keys(this.#matchResult[0][this.routeIndex][1]);
83954
+ const keys = Object.keys(this.#matchResult[0][this.routeIndex]?.[1] ?? {});
83431
83955
  for (const key of keys) {
83432
83956
  const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);
83433
83957
  if (value !== undefined) {
83434
- decoded[key] = /\%/.test(value) ? tryDecodeURIComponent(value) : value;
83958
+ decoded[key] = tryDecodeURIComponent(value);
83435
83959
  }
83436
83960
  }
83437
83961
  return decoded;
@@ -83464,8 +83988,7 @@ var HonoRequest = class {
83464
83988
  if (cachedBody) {
83465
83989
  return cachedBody;
83466
83990
  }
83467
- const anyCachedKey = Object.keys(bodyCache)[0];
83468
- if (anyCachedKey) {
83991
+ for (const anyCachedKey in bodyCache) {
83469
83992
  return bodyCache[anyCachedKey].then((body) => {
83470
83993
  if (anyCachedKey === "json") {
83471
83994
  body = JSON.stringify(body);
@@ -83494,10 +84017,10 @@ var HonoRequest = class {
83494
84017
  return this.#cachedBody("formData");
83495
84018
  }
83496
84019
  addValidatedData(target, data) {
83497
- this.#validatedData[target] = data;
84020
+ (this.#validatedData ??= {})[target] = data;
83498
84021
  }
83499
84022
  valid(target) {
83500
- return this.#validatedData[target];
84023
+ return this.#validatedData?.[target];
83501
84024
  }
83502
84025
  get url() {
83503
84026
  return this.raw.url;
@@ -83516,7 +84039,7 @@ var HonoRequest = class {
83516
84039
  }
83517
84040
  };
83518
84041
 
83519
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/utils/html.js
84042
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/html.js
83520
84043
  var HtmlEscapedCallbackPhase = {
83521
84044
  Stringify: 1,
83522
84045
  BeforeStream: 2,
@@ -83554,7 +84077,7 @@ var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) =>
83554
84077
  }
83555
84078
  };
83556
84079
 
83557
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/context.js
84080
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/context.js
83558
84081
  var TEXT_PLAIN = "text/plain; charset=UTF-8";
83559
84082
  var setDefaultContentType = (contentType, headers) => {
83560
84083
  return {
@@ -83672,11 +84195,11 @@ var Context = class {
83672
84195
  return Object.fromEntries(this.#var);
83673
84196
  }
83674
84197
  #newResponse(data, arg, headers) {
83675
- const responseHeaders = this.#res ? new Headers(this.#res.headers) : this.#preparedHeaders ?? new Headers;
83676
- if (typeof arg === "object" && "headers" in arg) {
83677
- const argHeaders = arg.headers instanceof Headers ? arg.headers : new Headers(arg.headers);
83678
- for (const [key, value] of argHeaders) {
83679
- if (key.toLowerCase() === "set-cookie") {
84198
+ let responseHeaders = this.#res ? new Headers(this.#res.headers) : this.#preparedHeaders;
84199
+ if (typeof arg === "object" && arg.headers) {
84200
+ responseHeaders ??= new Headers;
84201
+ for (const [key, value] of new Headers(arg.headers)) {
84202
+ if (key === "set-cookie") {
83680
84203
  responseHeaders.append(key, value);
83681
84204
  } else {
83682
84205
  responseHeaders.set(key, value);
@@ -83684,19 +84207,34 @@ var Context = class {
83684
84207
  }
83685
84208
  }
83686
84209
  if (headers) {
83687
- for (const [k, v] of Object.entries(headers)) {
83688
- if (typeof v === "string") {
83689
- responseHeaders.set(k, v);
83690
- } else {
83691
- responseHeaders.delete(k);
83692
- for (const v2 of v) {
83693
- responseHeaders.append(k, v2);
84210
+ if (!responseHeaders) {
84211
+ let count = 0;
84212
+ for (const k in headers) {
84213
+ if (++count > 1 || typeof headers[k] !== "string") {
84214
+ responseHeaders = new Headers;
84215
+ break;
84216
+ }
84217
+ }
84218
+ }
84219
+ if (responseHeaders) {
84220
+ for (const k in headers) {
84221
+ const v = headers[k];
84222
+ if (typeof v === "string") {
84223
+ responseHeaders.set(k, v);
84224
+ } else {
84225
+ responseHeaders.delete(k);
84226
+ for (const v2 of v) {
84227
+ responseHeaders.append(k, v2);
84228
+ }
83694
84229
  }
83695
84230
  }
83696
84231
  }
83697
84232
  }
83698
84233
  const status = typeof arg === "number" ? arg : arg?.status ?? this.#status;
83699
- return createResponseInstance(data, { status, headers: responseHeaders });
84234
+ return createResponseInstance(data, {
84235
+ status,
84236
+ headers: responseHeaders ?? headers
84237
+ });
83700
84238
  }
83701
84239
  newResponse = (...args) => this.#newResponse(...args);
83702
84240
  body = (data, arg, headers) => this.#newResponse(data, arg, headers);
@@ -83721,18 +84259,18 @@ var Context = class {
83721
84259
  };
83722
84260
  };
83723
84261
 
83724
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/router.js
84262
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router.js
83725
84263
  var METHOD_NAME_ALL = "ALL";
83726
84264
  var METHOD_NAME_ALL_LOWERCASE = "all";
83727
- var METHODS = ["get", "post", "put", "delete", "options", "patch"];
84265
+ var METHODS = ["get", "post", "put", "delete", "options", "patch", "query"];
83728
84266
  var MESSAGE_MATCHER_IS_ALREADY_BUILT = "Can not add a route since the matcher is already built.";
83729
84267
  var UnsupportedPathError = class extends Error {
83730
84268
  };
83731
84269
 
83732
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/utils/constants.js
84270
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/constants.js
83733
84271
  var COMPOSED_HANDLER = "__COMPOSED_HANDLER";
83734
84272
 
83735
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/hono-base.js
84273
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/hono-base.js
83736
84274
  var notFoundHandler = (c) => {
83737
84275
  return c.text("404 Not Found", 404);
83738
84276
  };
@@ -83751,6 +84289,7 @@ var Hono = class _Hono {
83751
84289
  delete;
83752
84290
  options;
83753
84291
  patch;
84292
+ query;
83754
84293
  all;
83755
84294
  on;
83756
84295
  use;
@@ -83763,13 +84302,14 @@ var Hono = class _Hono {
83763
84302
  const allMethods = [...METHODS, METHOD_NAME_ALL_LOWERCASE];
83764
84303
  allMethods.forEach((method) => {
83765
84304
  this[method] = (args1, ...args) => {
84305
+ const methodName = method.toUpperCase();
83766
84306
  if (typeof args1 === "string") {
83767
84307
  this.#path = args1;
83768
84308
  } else {
83769
- this.#addRoute(method, this.#path, args1);
84309
+ this.#addRoute(methodName, this.#path, args1);
83770
84310
  }
83771
84311
  args.forEach((handler) => {
83772
- this.#addRoute(method, this.#path, handler);
84312
+ this.#addRoute(methodName, this.#path, handler);
83773
84313
  });
83774
84314
  return this;
83775
84315
  };
@@ -83778,9 +84318,10 @@ var Hono = class _Hono {
83778
84318
  for (const p of [path].flat()) {
83779
84319
  this.#path = p;
83780
84320
  for (const m of [method].flat()) {
83781
- handlers.map((handler) => {
83782
- this.#addRoute(m.toUpperCase(), this.#path, handler);
83783
- });
84321
+ const methodName = m.toUpperCase();
84322
+ for (const handler of handlers) {
84323
+ this.#addRoute(methodName, this.#path, handler);
84324
+ }
83784
84325
  }
83785
84326
  }
83786
84327
  return this;
@@ -83885,7 +84426,6 @@ var Hono = class _Hono {
83885
84426
  return this;
83886
84427
  }
83887
84428
  #addRoute(method, path, handler, baseRoutePath) {
83888
- method = method.toUpperCase();
83889
84429
  path = mergePath(this._basePath, path);
83890
84430
  const r = {
83891
84431
  basePath: baseRoutePath !== undefined ? mergePath(this._basePath, baseRoutePath) : this._basePath,
@@ -83956,7 +84496,10 @@ var Hono = class _Hono {
83956
84496
  };
83957
84497
  };
83958
84498
 
83959
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/router/reg-exp-router/matcher.js
84499
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/utils.js
84500
+ var createNullObject = () => /* @__PURE__ */ Object.create(null);
84501
+
84502
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/matcher.js
83960
84503
  var emptyParam = [];
83961
84504
  function match(method, path) {
83962
84505
  const matchers = this.buildAllMatchers();
@@ -83977,7 +84520,7 @@ function match(method, path) {
83977
84520
  return match2(method, path);
83978
84521
  }
83979
84522
 
83980
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/router/reg-exp-router/node.js
84523
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/node.js
83981
84524
  var LABEL_REG_EXP_STR = "[^/]+";
83982
84525
  var ONLY_WILDCARD_REG_EXP_STR = ".*";
83983
84526
  var TAIL_WILDCARD_REG_EXP_STR = "(?:|/.*)";
@@ -83991,7 +84534,7 @@ function compareKey(a, b) {
83991
84534
  return 1;
83992
84535
  }
83993
84536
  if (a === ONLY_WILDCARD_REG_EXP_STR || a === TAIL_WILDCARD_REG_EXP_STR) {
83994
- return 1;
84537
+ return b === TAIL_WILDCARD_REG_EXP_STR ? -1 : 1;
83995
84538
  } else if (b === ONLY_WILDCARD_REG_EXP_STR || b === TAIL_WILDCARD_REG_EXP_STR) {
83996
84539
  return -1;
83997
84540
  }
@@ -84005,70 +84548,69 @@ function compareKey(a, b) {
84005
84548
  var Node = class _Node {
84006
84549
  #index;
84007
84550
  #varIndex;
84008
- #children = /* @__PURE__ */ Object.create(null);
84009
- insert(tokens, index, paramMap, context, pathErrorCheckOnly) {
84010
- if (tokens.length === 0) {
84011
- if (this.#index !== undefined) {
84012
- throw PATH_ERROR;
84013
- }
84014
- if (pathErrorCheckOnly) {
84015
- return;
84016
- }
84017
- this.#index = index;
84018
- return;
84019
- }
84020
- const [token, ...restTokens] = tokens;
84021
- const pattern = token === "*" ? restTokens.length === 0 ? ["", "", ONLY_WILDCARD_REG_EXP_STR] : ["", "", LABEL_REG_EXP_STR] : token === "/*" ? ["", "", TAIL_WILDCARD_REG_EXP_STR] : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
84022
- let node;
84023
- if (pattern) {
84024
- const name = pattern[1];
84025
- let regexpStr = pattern[2] || LABEL_REG_EXP_STR;
84026
- if (name && pattern[2]) {
84027
- if (regexpStr === ".*") {
84028
- throw PATH_ERROR;
84029
- }
84030
- regexpStr = regexpStr.replace(/^\((?!\?:)(?=[^)]+\)$)/, "(?:");
84031
- if (/\((?!\?:)/.test(regexpStr)) {
84032
- throw PATH_ERROR;
84033
- }
84034
- }
84035
- node = this.#children[regexpStr];
84036
- if (!node) {
84037
- if (Object.keys(this.#children).some((k) => k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR)) {
84038
- throw PATH_ERROR;
84039
- }
84040
- if (pathErrorCheckOnly) {
84041
- return;
84551
+ #children = createNullObject();
84552
+ insert(tokens, index, paramMap, context, isStatic) {
84553
+ let node = this;
84554
+ for (let i = 0, len = tokens.length;i < len; i++) {
84555
+ const token = tokens[i];
84556
+ const pattern = token.length === 1 ? token === "*" ? i === len - 1 ? ["", "", ONLY_WILDCARD_REG_EXP_STR] : ["", "", LABEL_REG_EXP_STR] : null : token === "/*" ? ["", "", TAIL_WILDCARD_REG_EXP_STR] : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
84557
+ let nextNode;
84558
+ if (pattern) {
84559
+ const name = pattern[1];
84560
+ let regexpStr = pattern[2] || LABEL_REG_EXP_STR;
84561
+ if (name && pattern[2]) {
84562
+ if (regexpStr === ".*") {
84563
+ throw PATH_ERROR;
84564
+ }
84565
+ regexpStr = regexpStr.replace(/^\((?!\?:)(?=[^)]+\)$)/, "(?:");
84566
+ if (/\((?!\?:)/.test(regexpStr)) {
84567
+ throw PATH_ERROR;
84568
+ }
84569
+ if (regexpStr.length === 1 && regExpMetaChars.has(regexpStr)) {
84570
+ throw PATH_ERROR;
84571
+ }
84572
+ }
84573
+ nextNode = node.#children[regexpStr];
84574
+ if (!nextNode) {
84575
+ if (regexpStr !== ONLY_WILDCARD_REG_EXP_STR && regexpStr !== TAIL_WILDCARD_REG_EXP_STR) {
84576
+ for (const k in node.#children) {
84577
+ if ((regexpStr.length > 1 || k.length > 1) && k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR) {
84578
+ throw PATH_ERROR;
84579
+ }
84580
+ }
84581
+ }
84582
+ nextNode = node.#children[regexpStr] = new _Node;
84042
84583
  }
84043
- node = this.#children[regexpStr] = new _Node;
84044
84584
  if (name !== "") {
84045
- node.#varIndex = context.varIndex++;
84046
- }
84047
- }
84048
- if (!pathErrorCheckOnly && name !== "") {
84049
- paramMap.push([name, node.#varIndex]);
84050
- }
84051
- } else {
84052
- node = this.#children[token];
84053
- if (!node) {
84054
- if (Object.keys(this.#children).some((k) => k.length > 1 && k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR)) {
84055
- throw PATH_ERROR;
84585
+ nextNode.#varIndex ??= context.varIndex++;
84586
+ paramMap.push([name, nextNode.#varIndex]);
84056
84587
  }
84057
- if (pathErrorCheckOnly) {
84058
- return;
84588
+ } else {
84589
+ nextNode = node.#children[token];
84590
+ if (!nextNode) {
84591
+ for (const k in node.#children) {
84592
+ if (k.length > 1 && k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR) {
84593
+ throw PATH_ERROR;
84594
+ }
84595
+ }
84596
+ nextNode = node.#children[token] = new _Node;
84059
84597
  }
84060
- node = this.#children[token] = new _Node;
84061
84598
  }
84599
+ node = nextNode;
84062
84600
  }
84063
- node.insert(restTokens, index, paramMap, context, pathErrorCheckOnly);
84601
+ if (node.#index !== undefined) {
84602
+ throw PATH_ERROR;
84603
+ }
84604
+ node.#index = isStatic ? -1 : index;
84064
84605
  }
84065
84606
  buildRegExpStr() {
84066
84607
  const childKeys = Object.keys(this.#children).sort(compareKey);
84067
84608
  const strList = childKeys.map((k) => {
84068
84609
  const c = this.#children[k];
84069
- return (typeof c.#varIndex === "number" ? `(${k})@${c.#varIndex}` : regExpMetaChars.has(k) ? `\\${k}` : k) + c.buildRegExpStr();
84070
- });
84071
- if (typeof this.#index === "number") {
84610
+ const childStr = c.buildRegExpStr();
84611
+ return childStr === "" ? "" : (typeof c.#varIndex === "number" ? `(${k})@${c.#varIndex}` : regExpMetaChars.has(k) ? `\\${k}` : k) + childStr;
84612
+ }).filter(Boolean);
84613
+ if (typeof this.#index === "number" && this.#index !== -1) {
84072
84614
  strList.unshift(`#${this.#index}`);
84073
84615
  }
84074
84616
  if (strList.length === 0) {
@@ -84081,16 +84623,23 @@ var Node = class _Node {
84081
84623
  }
84082
84624
  };
84083
84625
 
84084
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/router/reg-exp-router/trie.js
84626
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/trie.js
84085
84627
  var Trie = class {
84086
84628
  #context = { varIndex: 0 };
84087
84629
  #root = new Node;
84088
- insert(path, index, pathErrorCheckOnly) {
84630
+ #index = 0;
84631
+ paths = createNullObject();
84632
+ insert(path, isStatic) {
84633
+ if (isStatic) {
84634
+ this.#root.insert(path.split(""), 0, [], this.#context, true);
84635
+ return;
84636
+ }
84089
84637
  const paramAssoc = [];
84090
84638
  const groups = [];
84639
+ let markedPath = path;
84091
84640
  for (let i = 0;; ) {
84092
84641
  let replaced = false;
84093
- path = path.replace(/\{[^}]+\}/g, (m) => {
84642
+ markedPath = markedPath.replace(/\{[^}]+\}/g, (m) => {
84094
84643
  const mark = `@\\${i}`;
84095
84644
  groups[i] = [mark, m];
84096
84645
  i++;
@@ -84101,7 +84650,7 @@ var Trie = class {
84101
84650
  break;
84102
84651
  }
84103
84652
  }
84104
- const tokens = path.match(/(?::[^\/]+)|(?:\/\*$)|./g) || [];
84653
+ const tokens = markedPath.match(/(?::[^\/]+)|(?:\/\*$)|./g) || [];
84105
84654
  for (let i = groups.length - 1;i >= 0; i--) {
84106
84655
  const [mark] = groups[i];
84107
84656
  for (let j = tokens.length - 1;j >= 0; j--) {
@@ -84111,8 +84660,8 @@ var Trie = class {
84111
84660
  }
84112
84661
  }
84113
84662
  }
84114
- this.#root.insert(tokens, index, paramAssoc, this.#context, pathErrorCheckOnly);
84115
- return paramAssoc;
84663
+ this.#root.insert(tokens, this.#index, paramAssoc, this.#context, false);
84664
+ this.paths[path] = [this.#index++, paramAssoc];
84116
84665
  }
84117
84666
  buildRegExp() {
84118
84667
  let regexp = this.#root.buildRegExpStr();
@@ -84137,72 +84686,12 @@ var Trie = class {
84137
84686
  }
84138
84687
  };
84139
84688
 
84140
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/router/reg-exp-router/router.js
84141
- var nullMatcher = [/^$/, [], /* @__PURE__ */ Object.create(null)];
84142
- var wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
84689
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/router.js
84690
+ var wildcardRegExpCache = createNullObject();
84143
84691
  function buildWildcardRegExp(path) {
84144
- return wildcardRegExpCache[path] ??= new RegExp(path === "*" ? "" : `^${path.replace(/\/\*$|([.\\+*[^\]$()])/g, (_, metaChar) => metaChar ? `\\${metaChar}` : "(?:|/.*)")}$`);
84145
- }
84146
- function clearWildcardRegExpCache() {
84147
- wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
84148
- }
84149
- function buildMatcherFromPreprocessedRoutes(routes) {
84150
- const trie = new Trie;
84151
- const handlerData = [];
84152
- if (routes.length === 0) {
84153
- return nullMatcher;
84154
- }
84155
- const routesWithStaticPathFlag = routes.map((route) => [!/\*|\/:/.test(route[0]), ...route]).sort(([isStaticA, pathA], [isStaticB, pathB]) => isStaticA ? 1 : isStaticB ? -1 : pathA.length - pathB.length);
84156
- const staticMap = /* @__PURE__ */ Object.create(null);
84157
- for (let i = 0, j = -1, len = routesWithStaticPathFlag.length;i < len; i++) {
84158
- const [pathErrorCheckOnly, path, handlers] = routesWithStaticPathFlag[i];
84159
- if (pathErrorCheckOnly) {
84160
- staticMap[path] = [handlers.map(([h]) => [h, /* @__PURE__ */ Object.create(null)]), emptyParam];
84161
- } else {
84162
- j++;
84163
- }
84164
- let paramAssoc;
84165
- try {
84166
- paramAssoc = trie.insert(path, j, pathErrorCheckOnly);
84167
- } catch (e) {
84168
- throw e === PATH_ERROR ? new UnsupportedPathError(path) : e;
84169
- }
84170
- if (pathErrorCheckOnly) {
84171
- continue;
84172
- }
84173
- handlerData[j] = handlers.map(([h, paramCount]) => {
84174
- const paramIndexMap = /* @__PURE__ */ Object.create(null);
84175
- paramCount -= 1;
84176
- for (;paramCount >= 0; paramCount--) {
84177
- const [key, value] = paramAssoc[paramCount];
84178
- paramIndexMap[key] = value;
84179
- }
84180
- return [h, paramIndexMap];
84181
- });
84182
- }
84183
- const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
84184
- for (let i = 0, len = handlerData.length;i < len; i++) {
84185
- for (let j = 0, len2 = handlerData[i].length;j < len2; j++) {
84186
- const map = handlerData[i][j]?.[1];
84187
- if (!map) {
84188
- continue;
84189
- }
84190
- const keys = Object.keys(map);
84191
- for (let k = 0, len3 = keys.length;k < len3; k++) {
84192
- map[keys[k]] = paramReplacementMap[map[keys[k]]];
84193
- }
84194
- }
84195
- }
84196
- const handlerMap = [];
84197
- for (const i in indexReplacementMap) {
84198
- handlerMap[i] = handlerData[indexReplacementMap[i]];
84199
- }
84200
- return [regexp, handlerMap, staticMap];
84692
+ return wildcardRegExpCache[path] ??= new RegExp(`^${path.replace(/\/:[^/{}]+(?:\{\[\^\/]\+})?(?=[/{]|$)|\/?\*$|([.\\+*[^\]$()?{}|])/g, (match2, metaChar) => metaChar ? `\\${metaChar}` : match2 === "/*" ? TAIL_WILDCARD_REG_EXP_STR : match2 === "*" ? ONLY_WILDCARD_REG_EXP_STR : `/:${LABEL_REG_EXP_STR}`)}$`);
84201
84693
  }
84202
84694
  function findMiddleware(middleware, path) {
84203
- if (!middleware) {
84204
- return;
84205
- }
84206
84695
  for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) {
84207
84696
  if (buildWildcardRegExp(k).test(path)) {
84208
84697
  return [...middleware[k]];
@@ -84214,95 +84703,106 @@ var RegExpRouter = class {
84214
84703
  name = "RegExpRouter";
84215
84704
  #middleware;
84216
84705
  #routes;
84706
+ #tries;
84217
84707
  constructor() {
84218
- this.#middleware = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
84219
- this.#routes = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
84708
+ this.#middleware = { [METHOD_NAME_ALL]: createNullObject() };
84709
+ this.#routes = { [METHOD_NAME_ALL]: createNullObject() };
84710
+ this.#tries = { [METHOD_NAME_ALL]: new Trie };
84711
+ }
84712
+ #insertPath(method, path) {
84713
+ try {
84714
+ this.#tries[method].insert(path, !/\*|\/:/.test(path));
84715
+ } catch (e) {
84716
+ throw e === PATH_ERROR ? new UnsupportedPathError(path) : e;
84717
+ }
84220
84718
  }
84221
84719
  add(method, path, handler) {
84222
84720
  const middleware = this.#middleware;
84223
84721
  const routes = this.#routes;
84224
- if (!middleware || !routes) {
84722
+ if (!middleware) {
84225
84723
  throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
84226
84724
  }
84227
84725
  if (!middleware[method]) {
84228
- [middleware, routes].forEach((handlerMap) => {
84229
- handlerMap[method] = /* @__PURE__ */ Object.create(null);
84230
- Object.keys(handlerMap[METHOD_NAME_ALL]).forEach((p) => {
84726
+ this.#tries[method] = new Trie;
84727
+ for (const handlerMap of [middleware, routes]) {
84728
+ handlerMap[method] = createNullObject();
84729
+ for (const p in handlerMap[METHOD_NAME_ALL]) {
84231
84730
  handlerMap[method][p] = [...handlerMap[METHOD_NAME_ALL][p]];
84232
- });
84233
- });
84731
+ this.#insertPath(method, p);
84732
+ }
84733
+ }
84234
84734
  }
84235
84735
  if (path === "/*") {
84236
84736
  path = "*";
84237
84737
  }
84238
- const paramCount = (path.match(/\/:/g) || []).length;
84738
+ const methods = method === METHOD_NAME_ALL ? Object.keys(middleware) : [method];
84239
84739
  if (/\*$/.test(path)) {
84240
84740
  const re = buildWildcardRegExp(path);
84241
- if (method === METHOD_NAME_ALL) {
84242
- Object.keys(middleware).forEach((m) => {
84243
- middleware[m][path] ||= findMiddleware(middleware[m], path) || findMiddleware(middleware[METHOD_NAME_ALL], path) || [];
84244
- });
84245
- } else {
84246
- middleware[method][path] ||= findMiddleware(middleware[method], path) || findMiddleware(middleware[METHOD_NAME_ALL], path) || [];
84247
- }
84248
- Object.keys(middleware).forEach((m) => {
84249
- if (method === METHOD_NAME_ALL || method === m) {
84250
- Object.keys(middleware[m]).forEach((p) => {
84251
- re.test(p) && middleware[m][p].push([handler, paramCount]);
84252
- });
84741
+ for (const m of methods) {
84742
+ if (!middleware[m][path]) {
84743
+ this.#insertPath(m, path);
84744
+ middleware[m][path] = findMiddleware(middleware[m], path) || findMiddleware(middleware[METHOD_NAME_ALL], path) || [];
84253
84745
  }
84254
- });
84255
- Object.keys(routes).forEach((m) => {
84256
- if (method === METHOD_NAME_ALL || method === m) {
84257
- Object.keys(routes[m]).forEach((p) => re.test(p) && routes[m][p].push([handler, paramCount]));
84746
+ }
84747
+ for (const handlerMap of [middleware, routes]) {
84748
+ for (const m of methods) {
84749
+ for (const p in handlerMap[m]) {
84750
+ re.test(p) && handlerMap[m][p].push([handler, path]);
84751
+ }
84258
84752
  }
84259
- });
84753
+ }
84260
84754
  return;
84261
84755
  }
84262
84756
  const paths = checkOptionalParameter(path) || [path];
84263
- for (let i = 0, len = paths.length;i < len; i++) {
84264
- const path2 = paths[i];
84265
- Object.keys(routes).forEach((m) => {
84266
- if (method === METHOD_NAME_ALL || method === m) {
84267
- routes[m][path2] ||= [
84268
- ...findMiddleware(middleware[m], path2) || findMiddleware(middleware[METHOD_NAME_ALL], path2) || []
84269
- ];
84270
- routes[m][path2].push([handler, paramCount - len + i + 1]);
84757
+ for (const path2 of paths) {
84758
+ for (const m of methods) {
84759
+ if (!routes[m][path2]) {
84760
+ this.#insertPath(m, path2);
84761
+ routes[m][path2] = findMiddleware(middleware[m], path2) || findMiddleware(middleware[METHOD_NAME_ALL], path2) || [];
84271
84762
  }
84272
- });
84763
+ routes[m][path2].push([handler, path2]);
84764
+ }
84273
84765
  }
84274
84766
  }
84275
84767
  match = match;
84276
84768
  buildAllMatchers() {
84277
- const matchers = /* @__PURE__ */ Object.create(null);
84278
- Object.keys(this.#routes).concat(Object.keys(this.#middleware)).forEach((method) => {
84279
- matchers[method] ||= this.#buildMatcher(method);
84280
- });
84281
- this.#middleware = this.#routes = undefined;
84282
- clearWildcardRegExpCache();
84769
+ const matchers = createNullObject();
84770
+ for (const method of Object.keys(this.#routes)) {
84771
+ matchers[method] = this.#buildMatcher(method);
84772
+ }
84773
+ this.#middleware = this.#routes = this.#tries = undefined;
84774
+ wildcardRegExpCache = createNullObject();
84283
84775
  return matchers;
84284
84776
  }
84285
84777
  #buildMatcher(method) {
84286
- const routes = [];
84287
- let hasOwnRoute = method === METHOD_NAME_ALL;
84288
- [this.#middleware, this.#routes].forEach((r) => {
84289
- const ownRoute = r[method] ? Object.keys(r[method]).map((path) => [path, r[method][path]]) : [];
84290
- if (ownRoute.length !== 0) {
84291
- hasOwnRoute ||= true;
84292
- routes.push(...ownRoute);
84293
- } else if (method !== METHOD_NAME_ALL) {
84294
- routes.push(...Object.keys(r[METHOD_NAME_ALL]).map((path) => [path, r[METHOD_NAME_ALL][path]]));
84778
+ const middleware = this.#middleware[method];
84779
+ const routes = this.#routes[method];
84780
+ const trie = this.#tries[method];
84781
+ const staticMap = createNullObject();
84782
+ const handlerData = [];
84783
+ const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
84784
+ for (const r of [middleware, routes]) {
84785
+ for (const path in r) {
84786
+ const handlers = r[path];
84787
+ const pathData = trie.paths[path];
84788
+ if (!pathData) {
84789
+ staticMap[path] = [handlers.map(([h]) => [h, createNullObject()]), emptyParam];
84790
+ continue;
84791
+ }
84792
+ handlerData[pathData[0]] = handlers.map(([h, handlerPath]) => [
84793
+ h,
84794
+ trie.paths[handlerPath][1].reduceRight((map, [key], i) => {
84795
+ map[key] = paramReplacementMap[pathData[1][i][1]];
84796
+ return map;
84797
+ }, createNullObject())
84798
+ ]);
84295
84799
  }
84296
- });
84297
- if (!hasOwnRoute) {
84298
- return null;
84299
- } else {
84300
- return buildMatcherFromPreprocessedRoutes(routes);
84301
84800
  }
84801
+ return [regexp, indexReplacementMap.map((i) => handlerData[i]), staticMap];
84302
84802
  }
84303
84803
  };
84304
84804
 
84305
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/router/smart-router/router.js
84805
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/smart-router/router.js
84306
84806
  var SmartRouter = class {
84307
84807
  name = "SmartRouter";
84308
84808
  #routers = [];
@@ -84357,78 +84857,53 @@ var SmartRouter = class {
84357
84857
  }
84358
84858
  };
84359
84859
 
84360
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/router/trie-router/node.js
84361
- var emptyParams = /* @__PURE__ */ Object.create(null);
84362
- var hasChildren = (children) => {
84363
- for (const _ in children) {
84364
- return true;
84365
- }
84366
- return false;
84367
- };
84860
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/trie-router/node.js
84861
+ var emptyParams = createNullObject();
84862
+ var order = 0;
84368
84863
  var Node2 = class _Node {
84369
- #methods;
84370
- #children;
84371
- #patterns;
84372
- #order = 0;
84864
+ #methods = [];
84865
+ #children = createNullObject();
84866
+ #patterns = [];
84867
+ #pattern;
84373
84868
  #params = emptyParams;
84374
- constructor(method, handler, children) {
84375
- this.#children = children || /* @__PURE__ */ Object.create(null);
84376
- this.#methods = [];
84377
- if (method && handler) {
84378
- const m = /* @__PURE__ */ Object.create(null);
84379
- m[method] = { handler, possibleKeys: [], score: 0 };
84380
- this.#methods = [m];
84381
- }
84382
- this.#patterns = [];
84383
- }
84384
84869
  insert(method, path, handler) {
84385
- this.#order = ++this.#order;
84386
84870
  let curNode = this;
84387
84871
  const parts = splitRoutingPath(path);
84388
- const possibleKeys = [];
84389
- for (let i = 0, len = parts.length;i < len; i++) {
84390
- const p = parts[i];
84391
- const nextP = parts[i + 1];
84392
- const pattern = getPattern(p, nextP);
84393
- const key = Array.isArray(pattern) ? pattern[0] : p;
84394
- if (key in curNode.#children) {
84395
- curNode = curNode.#children[key];
84396
- if (pattern) {
84397
- possibleKeys.push(pattern[1]);
84398
- }
84399
- continue;
84872
+ const possibleKeys = /* @__PURE__ */ new Set;
84873
+ let i = 0;
84874
+ for (const p of parts) {
84875
+ const nextP = parts[++i];
84876
+ const pattern = getPattern(p, nextP) || (nextP === undefined && p && p.indexOf("*") === p.length - 1 ? p : null);
84877
+ const isParam = Array.isArray(pattern);
84878
+ const key = isParam ? pattern[0] : pattern || p;
84879
+ const child = curNode.#children[key] ||= new _Node;
84880
+ if (pattern && !child.#pattern) {
84881
+ child.#pattern = pattern;
84882
+ curNode.#patterns.push(child);
84400
84883
  }
84401
- curNode.#children[key] = new _Node;
84402
- if (pattern) {
84403
- curNode.#patterns.push(pattern);
84404
- possibleKeys.push(pattern[1]);
84884
+ curNode = child;
84885
+ if (isParam) {
84886
+ possibleKeys.add(pattern[1]);
84405
84887
  }
84406
- curNode = curNode.#children[key];
84407
84888
  }
84408
84889
  curNode.#methods.push({
84409
84890
  [method]: {
84410
84891
  handler,
84411
- possibleKeys: possibleKeys.filter((v, i, a) => a.indexOf(v) === i),
84412
- score: this.#order
84892
+ possibleKeys: [...possibleKeys],
84893
+ score: ++order
84413
84894
  }
84414
84895
  });
84415
- return curNode;
84416
84896
  }
84417
84897
  #pushHandlerSets(handlerSets, node, method, nodeParams, params) {
84418
84898
  for (let i = 0, len = node.#methods.length;i < len; i++) {
84419
84899
  const m = node.#methods[i];
84420
84900
  const handlerSet = m[method] || m[METHOD_NAME_ALL];
84421
- const processedSet = {};
84422
- if (handlerSet !== undefined) {
84423
- handlerSet.params = /* @__PURE__ */ Object.create(null);
84901
+ if (handlerSet) {
84902
+ handlerSet.params = createNullObject();
84424
84903
  handlerSets.push(handlerSet);
84425
- if (nodeParams !== emptyParams || params && params !== emptyParams) {
84426
- for (let i2 = 0, len2 = handlerSet.possibleKeys.length;i2 < len2; i2++) {
84427
- const key = handlerSet.possibleKeys[i2];
84428
- const processed = processedSet[handlerSet.score];
84429
- handlerSet.params[key] = params?.[key] && !processed ? params[key] : nodeParams[key] ?? params?.[key];
84430
- processedSet[handlerSet.score] = true;
84431
- }
84904
+ for (let i2 = 0, len2 = handlerSet.possibleKeys.length;i2 < len2; i2++) {
84905
+ const key = handlerSet.possibleKeys[i2];
84906
+ handlerSet.params[key] = params?.[key] && !i2 ? params[key] : nodeParams[key] ?? params?.[key];
84432
84907
  }
84433
84908
  }
84434
84909
  }
@@ -84460,33 +84935,33 @@ var Node2 = class _Node {
84460
84935
  tempNodes.push(nextNode);
84461
84936
  }
84462
84937
  }
84463
- for (let k = 0, len3 = node.#patterns.length;k < len3; k++) {
84464
- const pattern = node.#patterns[k];
84938
+ for (const child of node.#patterns) {
84939
+ const pattern = child.#pattern;
84465
84940
  const params = node.#params === emptyParams ? {} : { ...node.#params };
84466
- if (pattern === "*") {
84467
- const astNode = node.#children["*"];
84468
- if (astNode) {
84469
- this.#pushHandlerSets(handlerSets, astNode, method, node.#params);
84470
- astNode.#params = params;
84471
- tempNodes.push(astNode);
84941
+ if (typeof pattern === "string") {
84942
+ if (pattern === "*" || part.startsWith(pattern.slice(0, -1))) {
84943
+ this.#pushHandlerSets(handlerSets, child, method, node.#params);
84944
+ if (pattern === "*") {
84945
+ child.#params = params;
84946
+ tempNodes.push(child);
84947
+ }
84472
84948
  }
84473
84949
  continue;
84474
84950
  }
84475
- const [key, name, matcher] = pattern;
84476
- if (!part && !(matcher instanceof RegExp)) {
84951
+ const [, name, matcher] = pattern;
84952
+ if (!part && matcher === true) {
84477
84953
  continue;
84478
84954
  }
84479
- const child = node.#children[key];
84480
- if (matcher instanceof RegExp) {
84481
- if (partOffsets === null) {
84482
- partOffsets = new Array(len);
84955
+ if (matcher !== true) {
84956
+ if (!partOffsets) {
84957
+ partOffsets = [];
84483
84958
  let offset = path[0] === "/" ? 1 : 0;
84484
84959
  for (let p = 0;p < len; p++) {
84485
84960
  partOffsets[p] = offset;
84486
84961
  offset += parts[p].length + 1;
84487
84962
  }
84488
84963
  }
84489
- const restPathString = path.substring(partOffsets[i]);
84964
+ const restPathString = path.slice(partOffsets[i]);
84490
84965
  const m = matcher.exec(restPathString);
84491
84966
  if (m) {
84492
84967
  params[name] = m[0];
@@ -84494,11 +84969,12 @@ var Node2 = class _Node {
84494
84969
  if (m[0].length === restPathString.length && child.#children["*"]) {
84495
84970
  this.#pushHandlerSets(handlerSets, child.#children["*"], method, node.#params, params);
84496
84971
  }
84497
- if (hasChildren(child.#children)) {
84972
+ for (const _ in child.#children) {
84498
84973
  child.#params = params;
84499
- const componentCount = m[0].match(/\//)?.length ?? 0;
84974
+ const componentCount = m[0].match(/\//g)?.length ?? 0;
84500
84975
  const targetCurNodes = curNodesQueue[componentCount] ||= [];
84501
84976
  targetCurNodes.push(child);
84977
+ break;
84502
84978
  }
84503
84979
  continue;
84504
84980
  }
@@ -84520,7 +84996,7 @@ var Node2 = class _Node {
84520
84996
  const shifted = curNodesQueue.shift();
84521
84997
  curNodes = shifted ? tempNodes.concat(shifted) : tempNodes;
84522
84998
  }
84523
- if (handlerSets.length > 1) {
84999
+ if (handlerSets[1]) {
84524
85000
  handlerSets.sort((a, b) => {
84525
85001
  return a.score - b.score;
84526
85002
  });
@@ -84529,29 +85005,21 @@ var Node2 = class _Node {
84529
85005
  }
84530
85006
  };
84531
85007
 
84532
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/router/trie-router/router.js
85008
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/trie-router/router.js
84533
85009
  var TrieRouter = class {
84534
85010
  name = "TrieRouter";
84535
- #node;
84536
- constructor() {
84537
- this.#node = new Node2;
84538
- }
85011
+ #node = new Node2;
84539
85012
  add(method, path, handler) {
84540
- const results = checkOptionalParameter(path);
84541
- if (results) {
84542
- for (let i = 0, len = results.length;i < len; i++) {
84543
- this.#node.insert(method, results[i], handler);
84544
- }
84545
- return;
85013
+ for (const result of checkOptionalParameter(path) || [path]) {
85014
+ this.#node.insert(method, result, handler);
84546
85015
  }
84547
- this.#node.insert(method, path, handler);
84548
85016
  }
84549
85017
  match(method, path) {
84550
85018
  return this.#node.search(method, path);
84551
85019
  }
84552
85020
  };
84553
85021
 
84554
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/hono.js
85022
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/hono.js
84555
85023
  var Hono2 = class extends Hono {
84556
85024
  constructor(options = {}) {
84557
85025
  super(options);
@@ -84561,7 +85029,7 @@ var Hono2 = class extends Hono {
84561
85029
  }
84562
85030
  };
84563
85031
 
84564
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/utils/color.js
85032
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/color.js
84565
85033
  function getColorEnabled() {
84566
85034
  const { process: process2, Deno: Deno2 } = globalThis;
84567
85035
  const isNoColor = typeof Deno2?.noColor === "boolean" ? Deno2.noColor : process2 !== undefined ? "NO_COLOR" in process2?.env : false;
@@ -84580,7 +85048,7 @@ async function getColorEnabledAsync() {
84580
85048
  return !isNoColor;
84581
85049
  }
84582
85050
 
84583
- // ../../node_modules/.bun/hono@4.12.34/node_modules/hono/dist/middleware/logger/index.js
85051
+ // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/middleware/logger/index.js
84584
85052
  var humanize = (times) => {
84585
85053
  const [delimiter, separator] = [",", "."];
84586
85054
  const orderTimes = times.map((v) => v.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1" + delimiter));
@@ -84781,7 +85249,7 @@ function registerAuditUsageRoutes(app, ctx) {
84781
85249
  }
84782
85250
 
84783
85251
  // src/web/routes/config.ts
84784
- import { homedir as homedir9 } from "node:os";
85252
+ import { homedir as homedir10 } from "node:os";
84785
85253
  import { sep } from "node:path";
84786
85254
  init_config();
84787
85255
 
@@ -84902,7 +85370,7 @@ function registerConfigRoutes(app, ctx) {
84902
85370
  let configFile = null;
84903
85371
  try {
84904
85372
  const abs = resolveEnvFile();
84905
- const home = homedir9();
85373
+ const home = homedir10();
84906
85374
  configFile = abs === home || abs.startsWith(home + sep) ? `~${abs.slice(home.length)}` : abs;
84907
85375
  } catch {}
84908
85376
  return c.json({ keys: entries, config_file: configFile });
@@ -86234,12 +86702,12 @@ import { execFileSync } from "node:child_process";
86234
86702
 
86235
86703
  // src/web/docs.ts
86236
86704
  import {
86237
- existsSync as existsSync17,
86238
- readFileSync as readFileSync16,
86705
+ existsSync as existsSync18,
86706
+ readFileSync as readFileSync17,
86239
86707
  readdirSync as readdirSync6,
86240
86708
  statSync as statSync6
86241
86709
  } from "node:fs";
86242
- import { basename as basename5, dirname as dirname6, join as join14, resolve as resolve5 } from "node:path";
86710
+ import { basename as basename5, dirname as dirname6, join as join15, resolve as resolve5 } from "node:path";
86243
86711
  import { fileURLToPath as fileURLToPath3 } from "node:url";
86244
86712
  var TOP_LEVEL_DOCS = [
86245
86713
  { filename: "README.md", path: "README.md", category: "readme" },
@@ -86260,32 +86728,32 @@ function moduleDir2() {
86260
86728
  function resolveDocsRoots() {
86261
86729
  const here = moduleDir2();
86262
86730
  const pkgRootCandidates = [
86263
- join14(here, "..", ".."),
86264
- join14(here, "..", "..", "..", "..")
86731
+ join15(here, "..", ".."),
86732
+ join15(here, "..", "..", "..", "..")
86265
86733
  ];
86266
86734
  let pkgGuides = null;
86267
86735
  let pkgTopLevel = null;
86268
86736
  for (const pkg of pkgRootCandidates) {
86269
- const guides = join14(pkg, "docs", "guides");
86270
- if (existsSync17(guides) && statSync6(guides).isDirectory()) {
86737
+ const guides = join15(pkg, "docs", "guides");
86738
+ if (existsSync18(guides) && statSync6(guides).isDirectory()) {
86271
86739
  pkgGuides = guides;
86272
86740
  pkgTopLevel = pkg;
86273
86741
  break;
86274
86742
  }
86275
86743
  }
86276
- const repoCandidate = join14(here, "..", "..", "..", "..");
86277
- const repoGuides = join14(repoCandidate, "docs", "guides");
86744
+ const repoCandidate = join15(here, "..", "..", "..", "..");
86745
+ const repoGuides = join15(repoCandidate, "docs", "guides");
86278
86746
  const repoTopLevel = repoCandidate;
86279
86747
  return {
86280
86748
  pkgGuides,
86281
86749
  pkgTopLevel,
86282
- repoGuides: existsSync17(repoGuides) ? repoGuides : null,
86283
- repoTopLevel: existsSync17(join14(repoTopLevel, "README.md")) ? repoTopLevel : null
86750
+ repoGuides: existsSync18(repoGuides) ? repoGuides : null,
86751
+ repoTopLevel: existsSync18(join15(repoTopLevel, "README.md")) ? repoTopLevel : null
86284
86752
  };
86285
86753
  }
86286
86754
  function readH1(filePath) {
86287
86755
  try {
86288
- const content = readFileSync16(filePath, "utf8");
86756
+ const content = readFileSync17(filePath, "utf8");
86289
86757
  const match = content.match(/^#\s+(.+?)\s*$/m);
86290
86758
  return match ? match[1] : null;
86291
86759
  } catch {
@@ -86305,8 +86773,8 @@ function listBundledDocs2() {
86305
86773
  const topRoot = pkgTopLevel ?? repoTopLevel;
86306
86774
  if (topRoot) {
86307
86775
  for (const t of TOP_LEVEL_DOCS) {
86308
- const abs = join14(topRoot, t.filename);
86309
- if (existsSync17(abs)) {
86776
+ const abs = join15(topRoot, t.filename);
86777
+ if (existsSync18(abs)) {
86310
86778
  entries.push(entryForFile(abs, t.path, t.category));
86311
86779
  }
86312
86780
  }
@@ -86315,7 +86783,7 @@ function listBundledDocs2() {
86315
86783
  if (guidesRoot) {
86316
86784
  const names = readdirSync6(guidesRoot).filter((n) => n.endsWith(".md")).sort();
86317
86785
  for (const name of names) {
86318
- const abs = join14(guidesRoot, name);
86786
+ const abs = join15(guidesRoot, name);
86319
86787
  entries.push(entryForFile(abs, `guides/${name}`, "guide"));
86320
86788
  }
86321
86789
  }
@@ -86333,9 +86801,9 @@ function readDoc(docPath) {
86333
86801
  if (!candidate.startsWith(rootResolved + "/") && candidate !== rootResolved) {
86334
86802
  return null;
86335
86803
  }
86336
- if (existsSync17(candidate) && statSync6(candidate).isFile()) {
86804
+ if (existsSync18(candidate) && statSync6(candidate).isFile()) {
86337
86805
  try {
86338
- return readFileSync16(candidate, "utf8");
86806
+ return readFileSync17(candidate, "utf8");
86339
86807
  } catch {
86340
86808
  return null;
86341
86809
  }
@@ -86490,17 +86958,17 @@ function registerPostgrestProxy(app) {
86490
86958
 
86491
86959
  // src/web/routes/preferences.ts
86492
86960
  init_config();
86493
- import { existsSync as existsSync18, mkdirSync as mkdirSync5, readFileSync as readFileSync17, writeFileSync as writeFileSync6 } from "node:fs";
86494
- import { join as join15 } from "node:path";
86961
+ import { existsSync as existsSync19, mkdirSync as mkdirSync6, readFileSync as readFileSync18, writeFileSync as writeFileSync7 } from "node:fs";
86962
+ import { join as join16 } from "node:path";
86495
86963
  function isTheme(v) {
86496
86964
  return v === "auto" || v === "light" || v === "dark";
86497
86965
  }
86498
86966
  function prefsFile() {
86499
- return join15(userStateDir(), "web-prefs.json");
86967
+ return join16(userStateDir(), "web-prefs.json");
86500
86968
  }
86501
86969
  function readPrefs() {
86502
86970
  try {
86503
- const raw = JSON.parse(readFileSync17(prefsFile(), "utf8"));
86971
+ const raw = JSON.parse(readFileSync18(prefsFile(), "utf8"));
86504
86972
  if (isTheme(raw.theme))
86505
86973
  return { theme: raw.theme };
86506
86974
  } catch {}
@@ -86516,9 +86984,9 @@ function registerPreferencesRoutes(app) {
86516
86984
  const next = { ...readPrefs(), theme: body.theme };
86517
86985
  try {
86518
86986
  const dir = userStateDir();
86519
- if (!existsSync18(dir))
86520
- mkdirSync5(dir, { recursive: true });
86521
- writeFileSync6(prefsFile(), `${JSON.stringify(next, null, 2)}
86987
+ if (!existsSync19(dir))
86988
+ mkdirSync6(dir, { recursive: true });
86989
+ writeFileSync7(prefsFile(), `${JSON.stringify(next, null, 2)}
86522
86990
  `);
86523
86991
  } catch (err) {
86524
86992
  return c.json({ detail: err instanceof Error ? err.message : String(err) }, 500);
@@ -86653,21 +87121,21 @@ function registerProjectsRoutes(app, ctx) {
86653
87121
  }
86654
87122
 
86655
87123
  // src/web/static.ts
86656
- import { existsSync as existsSync19, statSync as statSync7 } from "node:fs";
86657
- import { dirname as dirname7, join as join16 } from "node:path";
87124
+ import { existsSync as existsSync20, statSync as statSync7 } from "node:fs";
87125
+ import { dirname as dirname7, join as join17 } from "node:path";
86658
87126
  import { fileURLToPath as fileURLToPath4 } from "node:url";
86659
87127
  function moduleDir3() {
86660
87128
  return dirname7(fileURLToPath4(import.meta.url));
86661
87129
  }
86662
87130
  function isUsableSpaDir(dir) {
86663
- return existsSync19(dir) && statSync7(dir).isDirectory() && existsSync19(join16(dir, "index.html"));
87131
+ return existsSync20(dir) && statSync7(dir).isDirectory() && existsSync20(join17(dir, "index.html"));
86664
87132
  }
86665
87133
  function resolveSpaDist() {
86666
87134
  const here = moduleDir3();
86667
87135
  const candidates = [
86668
- join16(here, "..", "frontend"),
86669
- join16(here, "..", "..", "..", "..", "frontend", "dist"),
86670
- join16(here, "..", "..", "dist", "frontend")
87136
+ join17(here, "..", "frontend"),
87137
+ join17(here, "..", "..", "..", "..", "frontend", "dist"),
87138
+ join17(here, "..", "..", "dist", "frontend")
86671
87139
  ];
86672
87140
  for (const c of candidates) {
86673
87141
  if (isUsableSpaDir(c))
@@ -86678,11 +87146,11 @@ function resolveSpaDist() {
86678
87146
  function resolveStaticDir() {
86679
87147
  const here = moduleDir3();
86680
87148
  const candidates = [
86681
- join16(here, "..", "static"),
86682
- join16(here, "..", "..", "..", "..", "web", "static")
87149
+ join17(here, "..", "static"),
87150
+ join17(here, "..", "..", "..", "..", "web", "static")
86683
87151
  ];
86684
87152
  for (const c of candidates) {
86685
- if (existsSync19(c) && statSync7(c).isDirectory())
87153
+ if (existsSync20(c) && statSync7(c).isDirectory())
86686
87154
  return c;
86687
87155
  }
86688
87156
  return null;
@@ -86772,10 +87240,27 @@ function buildApp(ctx = buildWebContext()) {
86772
87240
  root: spaDist,
86773
87241
  rewriteRequestPath: (path) => path.replace(/^\/app/, "") || "/"
86774
87242
  }));
86775
- const indexPath = join17(spaDist, "index.html");
86776
- if (existsSync20(indexPath)) {
86777
- const indexHtml = readFileSync18(indexPath, "utf8");
86778
- app.get("/app/*", (c) => c.html(indexHtml));
87243
+ app.get("/app/assets/*", (c) => c.text("Not found", 404));
87244
+ const indexPath = join18(spaDist, "index.html");
87245
+ if (existsSync21(indexPath)) {
87246
+ let cached = null;
87247
+ const readIndex = () => {
87248
+ try {
87249
+ const { mtimeMs, size } = statSync8(indexPath);
87250
+ if (!cached || cached.mtimeMs !== mtimeMs || cached.size !== size) {
87251
+ cached = { mtimeMs, size, html: readFileSync19(indexPath, "utf8") };
87252
+ }
87253
+ } catch {}
87254
+ return cached?.html ?? null;
87255
+ };
87256
+ readIndex();
87257
+ app.get("/app/*", (c) => {
87258
+ const html = readIndex();
87259
+ if (html === null) {
87260
+ return c.text("The web UI could not be read from disk. Restart the server.", 503);
87261
+ }
87262
+ return c.html(html);
87263
+ });
86779
87264
  }
86780
87265
  }
86781
87266
  app.get("/", (c) => c.html(ROOT_REDIRECT_HTML));
@@ -86840,160 +87325,8 @@ async function buildWebServer(options = {}) {
86840
87325
  };
86841
87326
  }
86842
87327
 
86843
- // src/web/daemon.ts
86844
- import { spawn } from "node:child_process";
86845
- import {
86846
- existsSync as existsSync21,
86847
- mkdirSync as mkdirSync6,
86848
- openSync,
86849
- readFileSync as readFileSync19,
86850
- rmSync,
86851
- writeFileSync as writeFileSync7
86852
- } from "node:fs";
86853
- import { homedir as homedir10 } from "node:os";
86854
- import { join as join18 } from "node:path";
86855
- function resolveStateDir(override = process.env.CEREFOX_CONFIG_DIR, home = homedir10()) {
86856
- override = (override ?? "").trim();
86857
- if (!override)
86858
- return join18(home, ".cerefox");
86859
- return override === "~" || override.startsWith("~/") ? join18(home, override.slice(2)) : override;
86860
- }
86861
- var STATE_DIR = resolveStateDir();
86862
- var PID_FILE = join18(STATE_DIR, "web.pid");
86863
- var LOG_FILE = join18(STATE_DIR, "web.log");
86864
- var daemonPaths = { stateDir: STATE_DIR, pidFile: PID_FILE, logFile: LOG_FILE };
86865
- function ensureStateDir() {
86866
- if (!existsSync21(STATE_DIR))
86867
- mkdirSync6(STATE_DIR, { recursive: true });
86868
- }
86869
- function readPidFile() {
86870
- if (!existsSync21(PID_FILE))
86871
- return null;
86872
- try {
86873
- const parsed = JSON.parse(readFileSync19(PID_FILE, "utf8"));
86874
- if (typeof parsed.pid !== "number")
86875
- return null;
86876
- return {
86877
- pid: parsed.pid,
86878
- port: typeof parsed.port === "number" ? parsed.port : 8000,
86879
- host: typeof parsed.host === "string" ? parsed.host : "127.0.0.1",
86880
- startedAt: typeof parsed.startedAt === "string" ? parsed.startedAt : "unknown"
86881
- };
86882
- } catch {
86883
- return null;
86884
- }
86885
- }
86886
- function writePidFile(info) {
86887
- ensureStateDir();
86888
- writeFileSync7(PID_FILE, JSON.stringify(info, null, 2) + `
86889
- `, "utf8");
86890
- }
86891
- function removePidFile() {
86892
- rmSync(PID_FILE, { force: true });
86893
- }
86894
- function isProcessAlive(pid) {
86895
- try {
86896
- process.kill(pid, 0);
86897
- return true;
86898
- } catch (err) {
86899
- return err.code === "EPERM";
86900
- }
86901
- }
86902
- async function isResponding(host, port, timeoutMs = 1500) {
86903
- const probeHost = host === "0.0.0.0" ? "127.0.0.1" : host;
86904
- try {
86905
- const ctrl = new AbortController;
86906
- const timer = setTimeout(() => ctrl.abort(), timeoutMs);
86907
- try {
86908
- const resp = await fetch(`http://${probeHost}:${port}/api/v1/version`, {
86909
- signal: ctrl.signal
86910
- });
86911
- return resp.ok;
86912
- } finally {
86913
- clearTimeout(timer);
86914
- }
86915
- } catch {
86916
- return false;
86917
- }
86918
- }
86919
- var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
86920
- function assertUnix() {
86921
- if (process.platform === "win32") {
86922
- throw new Error("Daemon mode (`cerefox web start/stop/status`) is not supported on Windows yet. " + "Run `cerefox web` in the foreground, or set up a Windows service manually.");
86923
- }
86924
- }
86925
- async function startDaemon(opts) {
86926
- assertUnix();
86927
- ensureStateDir();
86928
- const existing = readPidFile();
86929
- if (existing && isProcessAlive(existing.pid)) {
86930
- return existing.port === opts.port ? { kind: "already-running", info: existing } : { kind: "port-conflict", info: existing };
86931
- }
86932
- if (existing)
86933
- removePidFile();
86934
- const logFd = openSync(LOG_FILE, "a");
86935
- const child = spawn(opts.runtime, [opts.scriptPath, "web", "--host", opts.host, "--port", String(opts.port)], { detached: true, stdio: ["ignore", logFd, logFd] });
86936
- child.unref();
86937
- if (typeof child.pid !== "number") {
86938
- throw new Error("Failed to spawn the web server process (no pid).");
86939
- }
86940
- writePidFile({
86941
- pid: child.pid,
86942
- port: opts.port,
86943
- host: opts.host,
86944
- startedAt: new Date().toISOString()
86945
- });
86946
- let responding = false;
86947
- for (let i = 0;i < 20; i++) {
86948
- if (!isProcessAlive(child.pid))
86949
- break;
86950
- if (await isResponding(opts.host, opts.port)) {
86951
- responding = true;
86952
- break;
86953
- }
86954
- await sleep2(250);
86955
- }
86956
- return { kind: "started", pid: child.pid, responding };
86957
- }
86958
- async function stopDaemon() {
86959
- assertUnix();
86960
- const info = readPidFile();
86961
- if (!info || !isProcessAlive(info.pid)) {
86962
- removePidFile();
86963
- return { kind: "not-running" };
86964
- }
86965
- try {
86966
- process.kill(info.pid, "SIGTERM");
86967
- } catch {}
86968
- let forced = false;
86969
- let alive = true;
86970
- for (let i = 0;i < 12; i++) {
86971
- await sleep2(250);
86972
- if (!isProcessAlive(info.pid)) {
86973
- alive = false;
86974
- break;
86975
- }
86976
- }
86977
- if (alive) {
86978
- try {
86979
- process.kill(info.pid, "SIGKILL");
86980
- forced = true;
86981
- } catch {}
86982
- }
86983
- removePidFile();
86984
- return { kind: "stopped", pid: info.pid, forced };
86985
- }
86986
- async function statusDaemon() {
86987
- const info = readPidFile();
86988
- if (!info)
86989
- return { kind: "stopped" };
86990
- if (!isProcessAlive(info.pid))
86991
- return { kind: "stale", info };
86992
- const responding = await isResponding(info.host, info.port);
86993
- return { kind: "running", info, responding };
86994
- }
86995
-
86996
87328
  // src/cli/commands/web.ts
87329
+ init_meta();
86997
87330
  function parsePort(raw) {
86998
87331
  const port = Number.parseInt(raw, 10);
86999
87332
  if (!Number.isFinite(port) || port < 1 || port > 65535) {
@@ -87113,6 +87446,10 @@ Full log: ${daemonPaths.logFile}`);
87113
87446
  case "running":
87114
87447
  if (status.responding) {
87115
87448
  println(c.green(`Cerefox web: running on :${status.info.port} (pid ${status.info.pid}, since ${status.info.startedAt}).`));
87449
+ if (status.version && status.version !== PKG_VERSION) {
87450
+ println(c.yellow(` ⚠ It is serving v${status.version}; this CLI is v${PKG_VERSION}.`));
87451
+ println(c.dim(" Restart to pick up the new build: " + restartCommand(status.info.host, status.info.port)));
87452
+ }
87116
87453
  } else {
87117
87454
  println(c.yellow(`Cerefox web: process ${status.info.pid} alive but not responding on :${status.info.port}.`));
87118
87455
  println(c.dim(` Check the log: ${daemonPaths.logFile}`));