@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.
- package/AGENT_GUIDE.md +12 -1
- package/AGENT_QUICK_REFERENCE.md +9 -8
- package/dist/bin/cerefox.js +1004 -667
- package/dist/frontend/assets/{index-BC_iqEHZ.js → index-DUY9wimN.js} +29 -29
- package/dist/frontend/assets/index-DUY9wimN.js.map +1 -0
- package/dist/frontend/index.html +1 -1
- package/dist/server-assets/_shared/ef-meta/index.ts +3 -3
- package/dist/server-assets/_shared/mcp-tools/_utils.ts +8 -3
- package/dist/server-assets/_shared/mcp-tools/get-help-content.ts +2 -2
- package/dist/server-assets/_shared/mcp-tools/metadata-search.ts +90 -19
- package/dist/server-assets/_shared/mcp-tools/search.ts +306 -50
- package/dist/server-assets/supabase/functions/cerefox-search/index.ts +75 -35
- package/docs/guides/connect-agents.md +15 -4
- package/docs/guides/response-limits.md +25 -3
- package/package.json +3 -3
- package/dist/frontend/assets/index-BC_iqEHZ.js.map +0 -1
package/dist/bin/cerefox.js
CHANGED
|
@@ -7400,7 +7400,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
7400
7400
|
});
|
|
7401
7401
|
|
|
7402
7402
|
// src/meta.ts
|
|
7403
|
-
var PKG_VERSION = "1.14.
|
|
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.
|
|
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
|
|
25927
|
-
import { homedir as
|
|
25928
|
-
import { join as
|
|
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
|
|
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 (!
|
|
25949
|
-
|
|
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").\
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
56600
|
-
|
|
56601
|
-
|
|
56602
|
-
|
|
56603
|
-
|
|
56604
|
-
|
|
56605
|
-
|
|
56606
|
-
|
|
56607
|
-
|
|
56608
|
-
|
|
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
|
-
|
|
56611
|
-
const
|
|
56612
|
-
const
|
|
56613
|
-
|
|
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
|
-
|
|
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
|
-
` +
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 (
|
|
70929
|
-
const m =
|
|
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
|
|
77478
|
-
import { homedir as
|
|
77479
|
-
import { join as
|
|
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 (!
|
|
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 (!
|
|
78044
|
+
if (!existsSync11(assets.schemaFile))
|
|
77743
78045
|
return null;
|
|
77744
|
-
const m =
|
|
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 (!
|
|
78289
|
+
if (!existsSync11(path))
|
|
77988
78290
|
return false;
|
|
77989
78291
|
try {
|
|
77990
|
-
const parsed = JSON.parse(
|
|
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 =
|
|
77999
|
-
const claudeCodeUser =
|
|
78000
|
-
const claudeCodeProj =
|
|
78001
|
-
const claudeDesktop = process.platform === "darwin" ?
|
|
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 =
|
|
78026
|
-
if (!
|
|
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
|
|
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
|
|
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,
|
|
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 =
|
|
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
|
|
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
|
|
79763
|
+
return readFileSync11(0, "utf8");
|
|
79413
79764
|
if (value.startsWith("@"))
|
|
79414
|
-
return
|
|
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
|
|
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 =
|
|
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
|
|
79589
|
-
mkdirSync as
|
|
79590
|
-
readFileSync as
|
|
79591
|
-
writeFileSync as
|
|
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
|
|
79594
|
-
import { dirname as dirname4, join as
|
|
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 (!
|
|
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(
|
|
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(
|
|
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
|
-
|
|
79899
|
-
|
|
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 =
|
|
79910
|
-
const cwdEnv =
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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
|
|
80558
|
-
import { homedir as
|
|
80559
|
-
import { join as
|
|
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
|
|
80913
|
+
return homedir9();
|
|
80563
80914
|
if (path.startsWith("~/"))
|
|
80564
|
-
return
|
|
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 (!
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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 (!
|
|
81064
|
-
|
|
81420
|
+
if (!existsSync15(path)) {
|
|
81421
|
+
writeFileSync6(path, `${header}${line}
|
|
81065
81422
|
`, { mode: 384 });
|
|
81066
81423
|
return { path, action: "created" };
|
|
81067
81424
|
}
|
|
81068
|
-
const original =
|
|
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
|
-
|
|
81446
|
+
writeFileSync6(path, next);
|
|
81090
81447
|
return { path, action, backupPath };
|
|
81091
81448
|
}
|
|
81092
81449
|
function readEnvVar(path, key) {
|
|
81093
|
-
if (!
|
|
81450
|
+
if (!existsSync15(path))
|
|
81094
81451
|
return null;
|
|
81095
|
-
const 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
|
|
81473
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
81117
81474
|
|
|
81118
|
-
// ../../node_modules/.bun/@hono+node-server@2.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
82905
|
-
import { createReadStream, existsSync as
|
|
82906
|
-
import { join as
|
|
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 !== "" && !
|
|
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 =
|
|
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 =
|
|
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
|
|
83063
|
-
import { readFileSync as
|
|
83064
|
-
import { join as
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 (
|
|
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.
|
|
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
|
|
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 &&
|
|
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.
|
|
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 &&
|
|
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] =
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
83676
|
-
if (typeof arg === "object" &&
|
|
83677
|
-
|
|
83678
|
-
for (const [key, value] of
|
|
83679
|
-
if (key
|
|
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
|
-
|
|
83688
|
-
|
|
83689
|
-
|
|
83690
|
-
|
|
83691
|
-
|
|
83692
|
-
|
|
83693
|
-
|
|
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, {
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
84309
|
+
this.#addRoute(methodName, this.#path, args1);
|
|
83770
84310
|
}
|
|
83771
84311
|
args.forEach((handler) => {
|
|
83772
|
-
this.#addRoute(
|
|
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
|
-
|
|
83782
|
-
|
|
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.
|
|
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.
|
|
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 =
|
|
84009
|
-
insert(tokens, index, paramMap, context,
|
|
84010
|
-
|
|
84011
|
-
|
|
84012
|
-
|
|
84013
|
-
}
|
|
84014
|
-
|
|
84015
|
-
|
|
84016
|
-
|
|
84017
|
-
|
|
84018
|
-
|
|
84019
|
-
|
|
84020
|
-
|
|
84021
|
-
|
|
84022
|
-
|
|
84023
|
-
|
|
84024
|
-
|
|
84025
|
-
|
|
84026
|
-
|
|
84027
|
-
|
|
84028
|
-
|
|
84029
|
-
}
|
|
84030
|
-
|
|
84031
|
-
if (
|
|
84032
|
-
|
|
84033
|
-
|
|
84034
|
-
|
|
84035
|
-
|
|
84036
|
-
|
|
84037
|
-
|
|
84038
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84058
|
-
|
|
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
|
|
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
|
-
|
|
84070
|
-
|
|
84071
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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,
|
|
84115
|
-
|
|
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.
|
|
84141
|
-
var
|
|
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(
|
|
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]:
|
|
84219
|
-
this.#routes = { [METHOD_NAME_ALL]:
|
|
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
|
|
84722
|
+
if (!middleware) {
|
|
84225
84723
|
throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
|
|
84226
84724
|
}
|
|
84227
84725
|
if (!middleware[method]) {
|
|
84228
|
-
[
|
|
84229
|
-
|
|
84230
|
-
|
|
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
|
|
84738
|
+
const methods = method === METHOD_NAME_ALL ? Object.keys(middleware) : [method];
|
|
84239
84739
|
if (/\*$/.test(path)) {
|
|
84240
84740
|
const re = buildWildcardRegExp(path);
|
|
84241
|
-
|
|
84242
|
-
|
|
84243
|
-
|
|
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
|
-
|
|
84256
|
-
|
|
84257
|
-
|
|
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 (
|
|
84264
|
-
const
|
|
84265
|
-
|
|
84266
|
-
|
|
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 =
|
|
84278
|
-
Object.keys(this.#routes)
|
|
84279
|
-
matchers[method]
|
|
84280
|
-
}
|
|
84281
|
-
this.#middleware = this.#routes = undefined;
|
|
84282
|
-
|
|
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
|
|
84287
|
-
|
|
84288
|
-
|
|
84289
|
-
|
|
84290
|
-
|
|
84291
|
-
|
|
84292
|
-
|
|
84293
|
-
|
|
84294
|
-
|
|
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.
|
|
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.
|
|
84361
|
-
var emptyParams =
|
|
84362
|
-
var
|
|
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
|
-
#
|
|
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
|
-
|
|
84390
|
-
|
|
84391
|
-
const nextP = parts[i
|
|
84392
|
-
const pattern = getPattern(p, nextP);
|
|
84393
|
-
const
|
|
84394
|
-
|
|
84395
|
-
|
|
84396
|
-
|
|
84397
|
-
|
|
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
|
|
84402
|
-
if (
|
|
84403
|
-
|
|
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
|
|
84412
|
-
score:
|
|
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
|
-
|
|
84422
|
-
|
|
84423
|
-
handlerSet.params = /* @__PURE__ */ Object.create(null);
|
|
84901
|
+
if (handlerSet) {
|
|
84902
|
+
handlerSet.params = createNullObject();
|
|
84424
84903
|
handlerSets.push(handlerSet);
|
|
84425
|
-
|
|
84426
|
-
|
|
84427
|
-
|
|
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 (
|
|
84464
|
-
const pattern =
|
|
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
|
-
|
|
84468
|
-
|
|
84469
|
-
|
|
84470
|
-
|
|
84471
|
-
|
|
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 [
|
|
84476
|
-
if (!part &&
|
|
84951
|
+
const [, name, matcher] = pattern;
|
|
84952
|
+
if (!part && matcher === true) {
|
|
84477
84953
|
continue;
|
|
84478
84954
|
}
|
|
84479
|
-
|
|
84480
|
-
|
|
84481
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
84541
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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 =
|
|
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
|
|
86238
|
-
readFileSync as
|
|
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
|
|
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
|
-
|
|
86264
|
-
|
|
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 =
|
|
86270
|
-
if (
|
|
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 =
|
|
86277
|
-
const repoGuides =
|
|
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:
|
|
86283
|
-
repoTopLevel:
|
|
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 =
|
|
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 =
|
|
86309
|
-
if (
|
|
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 =
|
|
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 (
|
|
86804
|
+
if (existsSync18(candidate) && statSync6(candidate).isFile()) {
|
|
86337
86805
|
try {
|
|
86338
|
-
return
|
|
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
|
|
86494
|
-
import { join as
|
|
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
|
|
86967
|
+
return join16(userStateDir(), "web-prefs.json");
|
|
86500
86968
|
}
|
|
86501
86969
|
function readPrefs() {
|
|
86502
86970
|
try {
|
|
86503
|
-
const raw = JSON.parse(
|
|
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 (!
|
|
86520
|
-
|
|
86521
|
-
|
|
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
|
|
86657
|
-
import { dirname as dirname7, join as
|
|
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
|
|
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
|
-
|
|
86669
|
-
|
|
86670
|
-
|
|
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
|
-
|
|
86682
|
-
|
|
87149
|
+
join17(here, "..", "static"),
|
|
87150
|
+
join17(here, "..", "..", "..", "..", "web", "static")
|
|
86683
87151
|
];
|
|
86684
87152
|
for (const c of candidates) {
|
|
86685
|
-
if (
|
|
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
|
-
|
|
86776
|
-
|
|
86777
|
-
|
|
86778
|
-
|
|
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}`));
|