@inkeep/open-knowledge 0.0.0-dev-20260427163338 → 0.0.0-dev-20260427194153
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/skills/open-knowledge/SKILL.md +25 -15
- package/dist/cli.mjs +6 -6
- package/dist/constants-CXtb6S9e.mjs +2 -0
- package/dist/{dist-C410Bipn.mjs → dist-B8mg-f6q.mjs} +1 -1
- package/dist/{dist-e1ryjJJb.mjs → dist-mmLEboji.mjs} +16 -4
- package/dist/index.mjs +1 -1
- package/dist/init-BCnlpWMq.mjs +1 -0
- package/dist/{init-BH4lmTY2.mjs → init-Bc8AaeuH.mjs} +3 -3
- package/dist/{init-C-DJ6Bcb.mjs → init-Dn8zvSe9.mjs} +1 -1
- package/dist/{init-CV-fiK86.mjs → init-aygOL5mI.mjs} +2 -2
- package/dist/{loader-BM3mbEZf.mjs → loader-CkLCJZ_a.mjs} +2 -2
- package/dist/loader-VgnU35KK.mjs +1 -0
- package/dist/paths-L4UjjxH7.mjs +1 -0
- package/dist/{paths-pxE-5qU3.mjs → paths-uWxFvwPB.mjs} +2 -2
- package/dist/preview-BRKICSmX.mjs +1 -0
- package/dist/{preview-DRhAyj_V.mjs → preview-DMPV6CsE.mjs} +2 -2
- package/dist/public/assets/ActivityModeContent-7CjHJQA-.js +2 -0
- package/dist/public/assets/{ActivityPanelDiffView-DVyTW-J7.js → ActivityPanelDiffView-D-EUyEh6.js} +1 -1
- package/dist/public/assets/{DocumentContext-D125gYAt.js → DocumentContext-yJTVxTi8.js} +2 -2
- package/dist/public/assets/McpConsentDialogBody-rv8E7452.js +1 -0
- package/dist/public/assets/{SourceEditor-M-p0lLYp.js → SourceEditor-g0En9fI8.js} +1 -1
- package/dist/public/assets/agent-presence-DBKMYj1x.js +1 -0
- package/dist/public/assets/{clipboard-BmiNdq3p.js → clipboard-tp9ju8rD.js} +1 -1
- package/dist/public/assets/dialog-DeibR6sn.js +45 -0
- package/dist/public/assets/index-DsWkmB5S.css +1 -0
- package/dist/public/assets/index-e-6eeSOy.js +1843 -0
- package/dist/public/index.html +6 -6
- package/dist/src-B0_BgXoG.mjs +1 -0
- package/dist/{start-BBTXc3GL.mjs → start-BrAnBRSa.mjs} +2 -2
- package/dist/start-CZ3afhmt.mjs +1 -0
- package/package.json +1 -1
- package/dist/constants-BXmH5dNC.mjs +0 -2
- package/dist/init-BX_jwB_V.mjs +0 -1
- package/dist/loader-v0m1NTka.mjs +0 -1
- package/dist/paths-CYEVSOoI.mjs +0 -1
- package/dist/preview-qf6rsBxN.mjs +0 -1
- package/dist/public/assets/ActivityModeContent-Bh4C07uL.js +0 -2
- package/dist/public/assets/McpConsentDialogBody-QKrs5-z6.js +0 -1
- package/dist/public/assets/agent-presence-BQu9C0ej.js +0 -1
- package/dist/public/assets/dialog-xzAlDsHy.js +0 -45
- package/dist/public/assets/index-DLXD_AE1.js +0 -1838
- package/dist/public/assets/index-DPJk5E6v.css +0 -1
- package/dist/src-CIwfojI2.mjs +0 -1
- package/dist/start-CosrPkb0.mjs +0 -1
|
@@ -21,9 +21,9 @@ When this workspace has Open Knowledge MCP configured, do **not** use your host'
|
|
|
21
21
|
- **`Bash ls` / `Bash find` / `Bash cat` on dirs containing in-scope markdown** — use `exec("ls …")` / `exec("find … -name '*.md'")` / `exec("cat …")` instead. Native returns bare names; `exec` returns frontmatter, backlink counts, and recent activity per child.
|
|
22
22
|
- **Glob patterns that target markdown** (`**/*.md`, any dir known to be markdown-heavy like `specs/**`, `reports/**`, `docs/**`) — use `exec` with `find`, or `list_documents({ dir })`.
|
|
23
23
|
- **Dispatching the Explore / general-purpose subagent for markdown-heavy exploration** — subagents use native `Read` / `Grep` / `Glob` internally and bypass Open Knowledge entirely. Do markdown exploration yourself via `exec` / `search`. Subagents remain appropriate for **source-code** exploration.
|
|
24
|
-
- **Reading `.open-knowledge/AGENTS.md` via native `Read`** — observed failure mode during M1 testing. The `.open-knowledge/` directory is in-scope; treat its contents the same as any other
|
|
24
|
+
- **Reading `.open-knowledge/AGENTS.md` via native `Read`** — observed failure mode during M1 testing. The `.open-knowledge/` directory is in-scope; treat its contents the same as any other knowledge-base file.
|
|
25
25
|
|
|
26
|
-
Why: native tools skip frontmatter, backlinks, shadow-repo activity, and project git history that OK's tools return for every matched
|
|
26
|
+
Why: native tools skip frontmatter, backlinks, shadow-repo activity, and project git history that OK's tools return for every matched knowledge-base file. `exec` is the primary read surface; it runs read-only bash (`cat`, `ls`, `grep`, `find`, `head`, `tail`, `wc`, `sort`, `uniq`, `cut` — pipes OK) and returns raw stdout plus enriched metadata per file.
|
|
27
27
|
|
|
28
28
|
**MCP tool visibility — not seeing `exec` is NOT the escape hatch.** MCP wiring varies by client. Claude Code, Cursor, Codex, Windsurf, VS Code — each surfaces MCP differently. Server labels are user-defined; tools may not appear as top-level symbols named `exec` in your specific UI. If Open Knowledge is registered as an MCP server in this workspace, route markdown reads through its `exec` / `search` / `read_document` via your client's documented MCP invocation (including any generic "call MCP tool" flow). Registration is the test, not top-level-symbol visibility.
|
|
29
29
|
|
|
@@ -62,22 +62,24 @@ Call `write_document` / `edit_document` as soon as you have content. Native `Edi
|
|
|
62
62
|
|
|
63
63
|
## Grounding — every factual claim needs a source (MUST)
|
|
64
64
|
|
|
65
|
-
Knowledge-base docs are factual artifacts. Every claim must be traceable to
|
|
65
|
+
Knowledge-base docs are factual artifacts — whether the project is a wiki, an LLM brain, a spec collection, a research log, or anything else markdown-shaped. Every claim must be traceable, and **the source has to live inside the knowledge base**, not float on the public web.
|
|
66
66
|
|
|
67
|
+
- **The knowledge base is source-of-truth — closed loop.** External sources don't get *cited out* to the live web; they get *pulled in* via `ingest`, then cited locally. A bare `[source](https://...)` URL inside a knowledge-base doc is **not** a finished citation — it's a TODO that says "this source still needs to be ingested." The chain only works if every leaf is a local doc.
|
|
67
68
|
- **Every factual claim MUST cite its source at the point of claim.** No unsourced speculation.
|
|
68
|
-
- **Web sources** →
|
|
69
|
-
- **
|
|
69
|
+
- **Web sources for knowledge-base docs** → fetch the page (your host's `WebFetch` / `WebSearch` / equivalent), then `ingest` it as a local doc, then cite the local path: `[source name](./path/to/source.md)`. The local doc carries the original URL in its frontmatter `source_url:`. **Inline `[source](URL)` is a chat affordance, not a knowledge-base one.**
|
|
70
|
+
- **Self-fetched counts.** When YOU fetched a URL to ground a claim that's about to land in the knowledge base, that fetch triggers `ingest` exactly like a user share does. Don't downgrade to inline-URL citation because the fetch was agent-initiated — same KB, same closed-loop contract.
|
|
71
|
+
- **Internal cross-refs** → standard markdown link to the OK doc that contains the authoritative claim: `[text](./path/to/doc.md)`. The linked doc itself must cite its sources — chains should terminate in preserved local docs. Where ingested sources live is project-specific (an `external-sources/` folder if the project uses Karpathy's layout; wherever the project's existing layout puts raw references otherwise).
|
|
70
72
|
- **If you don't have evidence:**
|
|
71
|
-
1. Run a web search and
|
|
73
|
+
1. Run a web search and `ingest` the result, OR
|
|
72
74
|
2. Mark inline `(TODO: needs source)` so a human can verify, OR
|
|
73
75
|
3. Don't write the claim. Do NOT fabricate.
|
|
74
76
|
- Unsourced speculation looks authoritative but rots into tribal knowledge that can't be audited. The knowledge base loses its value if readers can't trust it.
|
|
75
|
-
- If a fact is in the knowledge base, a reader must be able to trace it to its origin
|
|
77
|
+
- If a fact is in the knowledge base, a reader must be able to trace it to its origin via local docs only — no dead-link-on-the-public-web exposure.
|
|
76
78
|
|
|
77
79
|
## Linking — use standard markdown links
|
|
78
80
|
|
|
79
81
|
- **Every noun-phrase that names another document should be linked** using standard markdown link syntax: `[text](./relative/path.md)` or `[text](/absolute/from/content-root.md)`.
|
|
80
|
-
- **External web sources
|
|
82
|
+
- **External web sources are NOT inline body links.** Per the Grounding rule above, web URLs live in the `source_url:` frontmatter of an ingested doc under `external-sources/` (or the project's equivalent raw-sources folder); the body cites the local path: `[source name](./external-sources/source-slug.md)`. A raw `[source](https://...)` inline in the body is a TODO, not a citation — see Grounding for the closed-loop contract.
|
|
81
83
|
- **Internal cross-refs between OK docs** → `[text](./other-doc.md)` — link liberally to aid navigation.
|
|
82
84
|
- **Never wrap a link in backticks.** `` `[text](./foo.md)` `` is a bug — the backticks make it render as literal code rather than a link.
|
|
83
85
|
- **Never use HTML anchors** (`<a href="...">`). Markdown link syntax only.
|
|
@@ -147,7 +149,7 @@ tags:
|
|
|
147
149
|
|
|
148
150
|
- **Folder structure intent** — the `folders:` block tells you which folders exist, what each one contains, and what tags its files should carry. Every `exec("ls <folder>")` / `read_document` / `search` call merges these defaults with per-file frontmatter automatically, but you should also read config.yml directly when orienting so you can *place new docs in the right folder* and *write them in the voice + shape the project expects*.
|
|
149
151
|
- **Per-folder instructions** — each `folders:` entry's `description:` field is the canonical place for "what does this folder contain + how should agents work inside it." Treat the description as a binding instruction, not flavor text. If a folder's description says "preserve verbatim, no analysis" (e.g. `external-sources/`), don't synthesize into those files; takeaways belong elsewhere.
|
|
150
|
-
- **Content scope** — `content.dir` / `content.include` / `content.exclude` define which files count as knowledge-base documents. Anything outside those globs is regular source code, not a
|
|
152
|
+
- **Content scope** — `content.dir` / `content.include` / `content.exclude` define which files count as knowledge-base documents. Anything outside those globs is regular source code, not a knowledge-base doc.
|
|
151
153
|
|
|
152
154
|
If a project uses `ok seed` to scaffold the Karpathy three-layer layout (`external-sources/` → `research/` → `articles/`), each folder's description in `config.yml` encodes the layer's rules. Projects with custom layouts put their own discipline in their own descriptions. Either way: **follow what config.yml says.**
|
|
153
155
|
|
|
@@ -195,6 +197,12 @@ If a hub doc exists in a folder, update it as you change children. Don't batch f
|
|
|
195
197
|
|
|
196
198
|
This is primarily a human-watchability concern — the user watches edits land in the preview; interleaved cadence makes the narrative legible.
|
|
197
199
|
|
|
200
|
+
## Log discipline — check for a project log when KB content changes
|
|
201
|
+
|
|
202
|
+
Some projects keep an append-only project log to make agent activity auditable. **After any turn that creates, edits, or restructures docs in the knowledge base, check for a project log:** look for a `log.md` at the project root (or at the seed `rootDir` if `ok seed --root <dir>` was used). If one exists, follow whatever its frontmatter `description:` and in-file comment say — they carry the project-specific contract (entry shape, cadence, categories). Different projects log differently — some treat the log as a wiki audit trail, others as an LLM-brain history, others as a spec changelog. If no `log.md` exists, no log discipline applies; don't fabricate one.
|
|
203
|
+
|
|
204
|
+
The skill carries the trigger ("KB content changed this turn — go look"). The file owns the policy.
|
|
205
|
+
|
|
198
206
|
## Anti-patterns — at a glance
|
|
199
207
|
|
|
200
208
|
| Task | Don't | Do |
|
|
@@ -208,7 +216,9 @@ This is primarily a human-watchability concern — the user watches edits land i
|
|
|
208
216
|
| Ignore the attach hint | Skip the `warning: { action: "attach-preview-once" }` hint in write-tool responses | Open the `previewUrl` when the hint fires; otherwise do nothing |
|
|
209
217
|
| Reference another doc | `` `[text](./page.md)` `` (backticked) or HTML `<a>` | `[text](./page.md)` (raw markdown) |
|
|
210
218
|
| Embed an image | `<img src="...">` (HTML) or hot-linked external URL | Fetch + save locally + `` |
|
|
211
|
-
| Write a factual claim
|
|
219
|
+
| Write a factual claim in a KB doc | plausible prose without citation, OR inline `[source](https://URL)` | `ingest` the source first, then cite the local path per Grounding |
|
|
220
|
+
| Cite a web source you just fetched | inline `[source](https://...)` because YOU did the fetch (not the user) | `ingest` it — agent-initiated fetches are not exempt from the closed-loop rule |
|
|
221
|
+
| Finish a turn that changed KB content | move on without checking for a log | check for a `log.md` and follow its contract per Log discipline |
|
|
212
222
|
| Add an image | empty alt `` or generic alt `` | meaningful alt + source caption below |
|
|
213
223
|
| Catalog folder contents | create `INDEX.md` hub file | add `folders:` entry in `.open-knowledge/config.yml` |
|
|
214
224
|
| Fork a skill and expect no stomp | Edit installed SKILL.md | `npx skills remove` before CLI upgrade |
|
|
@@ -217,11 +227,11 @@ This is primarily a human-watchability concern — the user watches edits land i
|
|
|
217
227
|
|
|
218
228
|
Three MCP tools build on the primitives above and correspond to [Karpathy's three-layer knowledge-base pattern](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f):
|
|
219
229
|
|
|
220
|
-
| Tool | Layer | When to invoke
|
|
221
|
-
| ------------- | ----------------------- |
|
|
222
|
-
| `ingest` | Raw sources (immutable) | User shares a URL
|
|
223
|
-
| `research` |
|
|
224
|
-
| `consolidate` |
|
|
230
|
+
| Tool | Layer | When to invoke |
|
|
231
|
+
| ------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
232
|
+
| `ingest` | Raw sources (immutable) | User shares a URL/PDF/file to preserve verbatim, **OR you fetched a URL** (`WebFetch` / `WebSearch` / equivalent) to ground a claim that's about to land in the knowledge base. The KB is closed-loop — agent-initiated fetches are not exempt. No analysis in the file itself — takeaways go back to the user in chat. |
|
|
233
|
+
| `research` | KB, provisional | User asks you to investigate, compare alternatives, or synthesize multiple sources. Produces a `status: provisional` article with a `sources:` list. Follows scan-first routing, a STOP scoping gate, 3P-external framing, and a validate checklist — the tool body enforces each step. |
|
|
234
|
+
| `consolidate` | KB, canonical | Team has actually decided after research and wants the outcome committed as source-of-truth. Starts with a STOP gate confirming the decision exists; writes a `status: canonical` article with a `supersedes:` chain. |
|
|
225
235
|
|
|
226
236
|
Each tool returns a multi-step instructional body when invoked. The bodies enforce their own gates — follow the numbered steps in order, don't skip the STOP gates.
|
|
227
237
|
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{i as e,r as t}from"./constants-
|
|
2
|
+
import{i as e,r as t}from"./constants-CXtb6S9e.mjs";import{S as n,m as r,p as i}from"./dist-D4iyaPjq.mjs";import{n as a,t as o}from"./paths-uWxFvwPB.mjs";import{a as s,c,o as l}from"./server-lock-CH0GCP_4-DCYOtKMW.mjs";import{Jt as u,Kt as d,O as f,S as p,Yt as m,gt as h,j as g,qt as _,vt as v}from"./dist-mmLEboji.mjs";import{i as y,n as b,o as x,r as S,s as C,t as w}from"./colors-Jf5QUiX2.mjs";import{t as T}from"./is-object-CEU0BgQ5.mjs";import{r as E}from"./init-Bc8AaeuH.mjs";import{i as D,n as O,t as ee}from"./loader-CkLCJZ_a.mjs";import{o as k,s as te}from"./start-BrAnBRSa.mjs";import"./src-B0_BgXoG.mjs";import{Command as A}from"commander";import{appendFileSync as ne,closeSync as re,existsSync as j,mkdirSync as ie,openSync as ae,readFileSync as oe,readdirSync as se,realpathSync as ce,statSync as le,unlinkSync as ue,writeFileSync as de}from"node:fs";import{homedir as fe,hostname as pe,platform as me}from"node:os";import{basename as he,dirname as ge,isAbsolute as _e,join as ve,relative as ye,resolve as M}from"node:path";import{parse as be,stringify as xe}from"yaml";import{createOAuthDeviceAuth as Se}from"@octokit/auth-oauth-device";import Ce from"@inquirer/password";import{Octokit as we}from"@octokit/rest";import{fileURLToPath as Te}from"node:url";import{z as N}from"zod";import P from"simple-git";import{randomUUID as Ee}from"node:crypto";import{execFileSync as De,spawn as Oe}from"node:child_process";import{mkdir as ke,readFile as Ae,readdir as je,stat as Me}from"node:fs/promises";import{createServer as Ne,request as Pe}from"node:http";import Fe from"picomatch";import{McpServer as Ie}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as Le}from"@modelcontextprotocol/sdk/server/stdio.js";import{RootsListChangedNotificationSchema as Re}from"@modelcontextprotocol/sdk/types.js";import{AsyncLocalStorage as ze}from"node:async_hooks";import{Bash as Be,ReadWriteFs as Ve}from"just-bash";import He from"shell-quote";import{createInterface as Ue}from"node:readline/promises";const We=`open-knowledge`;var Ge=class{backend=`keyring`;async get(e){let{Entry:t}=await import(`@napi-rs/keyring`);try{let n=new t(We,e).getPassword();return n==null?null:JSON.parse(n)}catch{return null}}async set(e,t,n,r){let{Entry:i}=await import(`@napi-rs/keyring`),a=new i(We,e),o={login:t,token:n,...r};a.setPassword(JSON.stringify(o))}async clear(e){let{Entry:t}=await import(`@napi-rs/keyring`);try{new t(We,e).deletePassword()}catch{}}},Ke=class{backend=`file`;authFile;constructor(e){this.authFile=e??ve(fe(),`.open-knowledge`,`auth.yml`)}read(){if(!j(this.authFile))return{};try{return be(oe(this.authFile,`utf-8`))??{}}catch(e){let t=e instanceof Error?e.message:`unknown error`;return process.stderr.write(`[auth] Failed to parse ${this.authFile}: ${t}. Starting with empty credentials.\n`),{}}}write(e){let t=ge(this.authFile);j(t)||ie(t,{recursive:!0,mode:448}),de(this.authFile,xe(e),{mode:384})}async get(e){return this.read()[e]??null}async set(e,t,n,r){let i=this.read();i[e]={login:t,token:n,...r},this.write(i)}async clear(e){let t=this.read();delete t[e],this.write(t)}};async function qe(e){try{let{Entry:e}=await import(`@napi-rs/keyring`);return new e(We,`__probe__`),process.stderr.write(`[auth] token storage: OS keychain
|
|
3
3
|
`),new Ge}catch{return process.stderr.write(`[auth] token storage: file (~/.open-knowledge/auth.yml)
|
|
4
4
|
`),new Ke(e)}}async function Je(e,t,n){let r=Ye(await Xe(e)).host??``;if(!r)return 1;let i=await n.get(r);if(i==null)return 1;let a=e=>e.replace(/[\r\n]/g,``);return t.write(`username=${a(i.login)}\npassword=${a(i.token)}\n`),0}function Ye(e){let t={};for(let n of e.split(`
|
|
5
5
|
`)){let e=n.trim();if(e===``)continue;let r=e.indexOf(`=`);r!==-1&&(t[e.slice(0,r)]=e.slice(r+1))}return t}function Xe(e){return new Promise((t,n)=>{let r=[];e.on(`data`,e=>r.push(e)),e.on(`end`,()=>t(Buffer.concat(r).toString(`utf-8`))),e.on(`error`,n)})}function Ze(e){let t=new A(`git-credential`);return t.description(`Git credential helper (git credential-helper protocol)`),t.command(`get`).description(`Lookup credentials from TokenStore (called by git)`).action(async()=>{let t=await e(),n=await Je(process.stdin,process.stdout,t);process.exit(n)}),t}async function Qe(e){let{clientId:t,scopes:n=[`repo`,`read:user`,`user:email`],onVerification:r,host:i}=e,a=i&&i!==`github.com`?`https://${i}/api/v3`:`https://api.github.com`,o=Se({clientType:`oauth-app`,clientId:t,scopes:n,onVerification:async e=>{await r({verificationUri:e.verification_uri,userCode:e.user_code,expiresIn:e.expires_in,interval:e.interval})},request:a===`https://api.github.com`?void 0:(await import(`@octokit/request`)).request.defaults({baseUrl:a})}),s;try{s=await o({type:`oauth`})}catch(e){if(e instanceof Error){let t=e.message.toLowerCase();throw t.includes(`access_denied`)?Error(`Device-flow authorization was denied.`):t.includes(`expired_token`)||t.includes(`timeout`)||t.includes(`timed out`)?Error(`Device-flow code expired before authorization — please try again.`):Error(`GitHub sign-in failed: ${e.message}`)}throw e}return{token:s.token,tokenType:s.tokenType,scopes:s.scopes??[]}}function $e(e){return process.env.OPEN_KNOWLEDGE_GITHUB_CLIENT_ID??e?.github?.oauthAppClientId??`Ov23liqlSd0V1MwR6rhI`}const et=new Set([`gitlab.com`,`bitbucket.org`,`codeberg.org`,`gitea.com`,`sr.ht`,`sourcehut.org`]);function tt(e){let t=e.toLowerCase().replace(/:\d+$/,``);et.has(t)&&(process.stderr.write(`Error: ${e} is not a GitHub host. Only GitHub and GitHub Enterprise Server are supported.\n`),process.exit(1))}function nt(e,t){e&&process.stdout.write(`${JSON.stringify(t)}\n`)}async function rt(e,t,n,r=Qe){let i=$e(n),{host:a,json:o}=e;tt(a),o||process.stderr.write(`Logging in to ${a}…\n`);let s=await r({clientId:i,host:a===`github.com`?void 0:a,onVerification:e=>{e.userCode,e.verificationUri,o?nt(!0,{type:`verification`,user_code:e.userCode,verification_uri:e.verificationUri,expires_in:e.expiresIn}):process.stderr.write(`Open: ${e.verificationUri}\nEnter code: ${e.userCode}\n`)}}),c=`unknown`,l,u;try{let e=a===`github.com`?`https://api.github.com`:`https://${a}/api/v3`,t=await fetch(`${e}/user`,{headers:{Authorization:`Bearer ${s.token}`,"User-Agent":`open-knowledge-cli`,Accept:`application/vnd.github+json`}});if(t.ok){let e=await t.json();c=e.login??c,l=e.name??void 0,u=e.email??void 0}}catch{}await t.set(a,c,s.token,{gitProtocol:`https`,name:l,email:u}),o?nt(!0,{type:`complete`,host:a,login:c}):process.stderr.write(`✓ Logged in as ${c} on ${a}\n`)}function it(e,t){return new A(`login`).description(`Authenticate with GitHub via Device Flow`).option(`--host <host>`,`GitHub or GitHub Enterprise hostname`,`github.com`).option(`--json`,`Output JSONL progress events`,!1).action(async n=>{await rt(n,await t(),e())})}async function at(e,t,n){let{host:r,json:i}=e;tt(r);let a=await(n??(()=>Ce({message:`Enter PAT:`})))();a||(process.stderr.write(`No token provided
|
|
@@ -7,7 +7,7 @@ import{i as e,r as t}from"./constants-BXmH5dNC.mjs";import{S as n,m as r,p as i}
|
|
|
7
7
|
`),process.exit(1)}await t.set(r,c,a,{gitProtocol:`https`,name:l,email:u}),i?process.stdout.write(`${JSON.stringify({type:`complete`,host:r,login:c})}\n`):process.stderr.write(`✓ PAT stored for ${c} on ${r}\n`)}function ot(e){return new A(`pat`).description(`Store a Personal Access Token`).option(`--host <host>`,`GitHub or GitHub Enterprise hostname`,`github.com`).option(`--json`,`Output JSON`,!1).action(async t=>{await at(t,await e())})}async function st(e,t){let{host:n,json:r}=e;tt(n);let i=await t.get(n);i??(process.stderr.write(`Not logged in to ${n}\n`),process.exit(1));let a=n===`github.com`?void 0:`https://${n}/api/v3`,o=new we({auth:i.token,...a?{baseUrl:a}:{}}),s=[];for await(let e of o.paginate.iterator(o.repos.listForAuthenticatedUser,{per_page:100,sort:`updated`}))for(let t of e.data)s.push({full_name:t.full_name,clone_url:t.clone_url,private:t.private});if(r)process.stdout.write(`${JSON.stringify({type:`repos`,host:n,repos:s})}\n`);else for(let e of s)process.stdout.write(`${e.full_name} ${e.clone_url}\n`)}function ct(e){return new A(`repos`).description(`List accessible repositories`).option(`--host <host>`,`GitHub or GitHub Enterprise hostname`,`github.com`).option(`--json`,`Output JSON`,!1).action(async t=>{await st(t,await e())})}async function lt(e,t){let{host:n}=e;await t.clear(n),process.stderr.write(`✓ Signed out from ${n}\n`)}function ut(e){return new A(`signout`).description(`Remove stored credentials`).option(`--host <host>`,`GitHub hostname`,`github.com`).action(async t=>{await lt(t,await e())})}async function dt(e,t){let{host:n,json:r}=e;tt(n);let i=await t.get(n);i??(r?process.stdout.write(`${JSON.stringify({type:`status`,host:n,authenticated:!1})}\n`):process.stderr.write(`Not logged in to ${n}\n`),process.exit(1));let a=n===`github.com`?void 0:`https://${n}/api/v3`,o=new we({auth:i.token,...a?{baseUrl:a}:{}});try{let{data:e}=await o.users.getAuthenticated();r?process.stdout.write(`${JSON.stringify({type:`status`,host:n,authenticated:!0,login:e.login,name:e.name,email:e.email})}\n`):process.stderr.write(`✓ Logged in as ${e.login} on ${n}\n`)}catch{r?process.stdout.write(JSON.stringify({type:`status`,host:n,authenticated:!1,error:`token invalid`})+`
|
|
8
8
|
`):process.stderr.write(`✗ Token invalid for ${n}\n`),process.exit(1)}}function ft(e){return new A(`status`).description(`Show authentication status`).option(`--host <host>`,`GitHub or GitHub Enterprise hostname`,`github.com`).option(`--json`,`Output JSON`,!1).action(async t=>{await dt(t,await e())})}function pt(e){let t=new A(`auth`);t.description(`GitHub authentication management`);let n=()=>qe(),r=e??(()=>({}));return t.addCommand(it(r,n)),t.addCommand(ft(n)),t.addCommand(ct(n)),t.addCommand(ut(n)),t.addCommand(ot(n)),t.addCommand(Ze(n)),t}function mt(e,t,n={}){let r=l(e,t);if(!j(r))return{status:`missing`,lockPath:r};let i;try{i=JSON.parse(oe(r,`utf-8`))}catch{return{status:`corrupt`,lockPath:r}}if(!i||typeof i!=`object`||typeof i.pid!=`number`)return{status:`corrupt`,lockPath:r};let a=i,o=n.host??pe();return a.hostname===o?(n.isAlive??s)(a.pid)?{status:`alive`,lockPath:r,lock:a}:{status:`dead-pid`,lockPath:r,lock:a}:{status:`foreign-host`,lockPath:r,lock:a}}function ht(e,t){let n=[];for(let[r,i]of[[`server`,e],[`ui`,t]])(i.status===`dead-pid`||i.status===`corrupt`)&&n.push({name:r,lockPath:i.lockPath,reason:i.status});return{prune:n}}function gt(e){let t=e.inspect??(t=>mt(e.lockDir,t)),n=e.unlink??(e=>ue(e)),r=e.log??(e=>console.log(e)),i=e.error??(e=>console.error(e)),a=ht(t(`server`),t(`ui`));if(a.prune.length===0)return r(`No stale locks.`),{pruned:[],failed:[]};let o=[],s=[];for(let e of a.prune)try{n(e.lockPath),o.push(e)}catch(t){s.push({target:e,error:t instanceof Error?t.message:String(t)})}if(o.length>0){let e=o.map(e=>`${e.name} (${e.reason})`).join(`, `);r(`Pruned ${o.length} stale lock${o.length===1?``:`s`}: ${e}`)}return s.length>0&&i(`Failed to prune: ${s.map(({target:e,error:t})=>`${e.name} (${e.lockPath}): ${t}`).join(`; `)}`),{pruned:o,failed:s}}function _t(e){return new A(`clean`).description(`Prune stale / corrupt open-knowledge lock files (never touches live locks)`).action(()=>{gt({lockDir:a(o(e(),process.cwd()))}).failed.length>0&&(process.exitCode=1)})}function vt(){try{let e=De(`gh`,[`auth`,`token`],{encoding:`utf-8`,stdio:[`ignore`,`pipe`,`pipe`],timeout:5e3}).trim();return e.length===0?{available:!1}:{available:!0,token:e}}catch{return{available:!1}}}async function yt(e,t,n={},r=vt){if(!n.skipGhDetect&&r().available)return{tier:`A`,credentialArgs:[`-c`,`credential.helper=!gh auth git-credential`]};let i=await t.get(e);return i==null?{tier:`none`,credentialArgs:[]}:{tier:i.gitProtocol===`ssh`?`C`:`B`,credentialArgs:[`-c`,`credential.helper=!open-knowledge auth git-credential`]}}function bt(e){return e.replace(/:\d+$/,``)}function xt(e){let t=e.trim();if(!t)return null;{let e=/^https?:\/\/([^/?#]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`https`,hostname:bt(e[1]),owner:e[2],name:e[3]}}{let e=/^ssh:\/\/(?:[\w.-]+@)?([^/?#]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`ssh`,hostname:bt(e[1]),owner:e[2],name:e[3]}}{let e=/^git:\/\/([^/?#]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`git`,hostname:bt(e[1]),owner:e[2],name:e[3]}}{let e=/^(?:[\w.-]+@)?([\w.-]+):([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?$/.exec(t);if(e?.[1].includes(`.`)||e&&t.startsWith(`git@`))return{protocol:`ssh`,hostname:e[1],owner:e[2],name:e[3]}}{let e=/^git:([\w.-]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`git`,hostname:e[1],owner:e[2],name:e[3]}}if(!t.includes(`://`)&&!t.includes(`@`)&&!t.startsWith(`/`)){let e=/^([\w.-]+)\/([\w.\-~%]+?)(?:\.git)?$/.exec(t);if(e)return{protocol:`https`,hostname:`github.com`,owner:e[1],name:e[2]}}return null}const St=[[`count`,0,10],[`compress`,10,20],[`receiv`,20,60],[`resolv`,60,100]];function Ct(e){let t=/^([\w ]+):\s+(\d+)%/.exec(e.trim());if(!t)return null;let n=t[1].toLowerCase(),r=Number(t[2]);for(let[e,i,a]of St)if(n.includes(e))return{stage:t[1],pct:Math.round(i+r/100*(a-i))};return null}function wt(e,t){e&&process.stdout.write(`${JSON.stringify(t)}\n`)}async function Tt(e,t,n,r=process.cwd()){let i=xt(e);if(!i)throw Error(`Invalid git URL: ${e}`);let a=t.dir?M(r,t.dir):M(r,i.name);if(j(a)&&se(a).length>0)throw Error(`Target directory is not empty: ${a}`);let o=await qe(),s=await yt(i.hostname,o,{}),c=P({baseDir:r,config:s.credentialArgs.length>=2?[s.credentialArgs[1]]:[],unsafe:{allowUnsafeCredentialHelper:!0}}).env({GIT_TERMINAL_PROMPT:`0`}),l=-1;if(c.outputHandler((e,n,r)=>{r.on(`data`,e=>{let n=e.toString(`utf-8`);for(let e of n.split(`
|
|
9
9
|
`)){let n=Ct(e);n&&n.pct!==l&&(l=n.pct,wt(t.json,{type:`progress`,pct:n.pct,stage:n.stage}),t.json||process.stderr.write(`\r Cloning… ${n.pct}%`))}})}),await c.clone(e,a,[`--progress`]),t.json||process.stderr.write(`
|
|
10
|
-
`),!j(M(a,`.open-knowledge`)))try{let[{runInit:e},{ensureOkGitignoredAtRoot:t}]=await Promise.all([import(`./init-
|
|
10
|
+
`),!j(M(a,`.open-knowledge`)))try{let[{runInit:e},{ensureOkGitignoredAtRoot:t}]=await Promise.all([import(`./init-Dn8zvSe9.mjs`),import(`./init-BCnlpWMq.mjs`)]);await e({cwd:a,mcp:!1});try{t(a)}catch{}}catch{}return a}function Et(e){return new A(`clone`).description(`Clone a git repository and open it`).argument(`<url>`,`Repository URL or owner/repo shorthand`).argument(`[dir]`,`Target directory (default: ./<repo-name>)`).option(`--json`,`Output JSONL progress events`,!1).action(async(t,n,r)=>{let i=e();try{let a=await Tt(t,{json:r.json,dir:n},i);if(r.json)wt(!0,{type:`complete`,dir:a});else{process.stderr.write(`✓ Cloned to ${a}\n`),process.chdir(a);let{startCommand:t}=await import(`./start-CZ3afhmt.mjs`);await t(e).parseAsync([],{from:`user`})}}catch(e){let t=e instanceof Error?e.message:String(e);r.json?wt(!0,{type:`error`,message:t}):process.stderr.write(`✗ ${t}\n`),process.exitCode=1}})}function Dt(e=fe()){return ve(e,`Downloads`,`openknowledge.skill`)}function Ot(e,t,n){try{return t===`darwin`?(n(`open`,[e],{detached:!0,stdio:`ignore`}).unref(),{ok:!0}):t===`win32`?(n(`cmd`,[`/c`,`start`,`""`,e],{detached:!0,stdio:`ignore`}).unref(),{ok:!0}):t===`linux`?(n(`xdg-open`,[e],{detached:!0,stdio:`ignore`}).unref(),{ok:!0}):{ok:!1,reason:`unsupported-platform`,message:`Platform '${t}' has no file-association invocation wired. Use --no-open and open the file manually.`}}catch(e){return{ok:!1,reason:`spawn-error`,message:e instanceof Error?e.message:String(e)}}}async function kt(e={}){let t=M(e.out??Dt()),n=e.platformName??me(),r=e.spawnFn??Oe;try{await ke(ge(t),{recursive:!0})}catch(e){return{status:`failed`,message:`${S(`Error:`)} could not create output directory: ${e instanceof Error?e.message:String(e)}`,exitCode:1}}let i;try{i=await g({outputPath:t,skipVersionCheck:!0})}catch(e){return{status:`failed`,message:`${S(`Error:`)} build failed — ${e instanceof Error?e.message:String(e)}`,exitCode:1}}if(e.noOpen)return{status:`built`,outputPath:i.outputPath,size:i.size,sha256:i.sha256,cliVersion:i.cliVersion,skillVersion:i.skillVersion,message:[x(`Built ${i.outputPath}`),b(` ${i.size} bytes • sha256 ${i.sha256.slice(0,12)}…`),y(` Open the Claude Desktop App, then: ${w(`Customize → Skills → + → Create skill → Upload skill`)} → pick the file.`)].join(`
|
|
11
11
|
`),exitCode:0};let a=Ot(i.outputPath,n,r);return a.ok?{status:`installed`,outputPath:i.outputPath,size:i.size,sha256:i.sha256,cliVersion:i.cliVersion,skillVersion:i.skillVersion,message:[x(`Built ${i.outputPath}`),b(` ${i.size} bytes • sha256 ${i.sha256.slice(0,12)}… • CLI v${i.cliVersion}`),y(` Claude Desktop App opened. Now upload the file manually:`),` 1. ${w(`Customize`)} (sidebar) → ${w(`Skills`)}`,` 2. Click the ${w(`+`)} button`,` 3. Click ${w(`Create skill`)}`,` 4. Click ${w(`Upload skill`)}`,` 5. Pick ${w(`openknowledge.skill`)} from Downloads`,b(` If Claude Desktop didn't open, open it and start at step 1. The file is at ${i.outputPath}`)].join(`
|
|
12
12
|
`),exitCode:0}:{status:`built`,outputPath:i.outputPath,size:i.size,sha256:i.sha256,cliVersion:i.cliVersion,skillVersion:i.skillVersion,message:[x(`Built ${i.outputPath}`),C(` Handoff failed: ${a.message}`),y(` Open the Claude Desktop App, then: ${w(`Customize → Skills → + → Create skill → Upload skill`)} → pick the file.`)].join(`
|
|
13
13
|
`),exitCode:0}}function At(){return new A(`install-skill`).description("Build openknowledge.skill and open the Claude Desktop App so you can upload it for Claude Chat & Cowork. Not needed for Claude Code — `ok init` covers that separately.").option(`--out <path>`,`Custom output path (default: ~/Downloads/openknowledge.skill)`).option(`--no-open`,`Build the file but skip the OS file-association handoff`).action(async e=>{let t=await kt({out:e.out,noOpen:!e.open});process.stdout.write(`${t.message}\n`),t.exitCode!==0&&process.exit(t.exitCode)})}const jt=new ze;var Mt=class e{sessionId;corrId;component;constructor(e=`mcp`,t){this.sessionId=t??Ee().slice(0,12),this.corrId=Ee().slice(0,8),this.component=e}info(e,t={}){this.emit(`info`,e,t)}warn(e,t={}){this.emit(`warn`,e,t)}error(e,t,n={}){let r=t?{error:t instanceof Error?t.message:String(t),...n}:n;this.emit(`error`,e,r)}debug(e,t={}){(process.env.MCP_DEBUG===`1`||process.env.DEBUG?.includes(`mcp`))&&this.emit(`debug`,e,t)}child(t){return new e(t??this.component,this.sessionId)}asCallback(){return e=>this.info(e)}emit(e,t,n){let r={ts:new Date().toISOString(),level:e,sessionId:this.sessionId,corrId:this.corrId,component:this.component,msg:t,...n},i=`${JSON.stringify(r)}\n`;process.stderr.write(i);let a=process.env.OK_LOG_FILE;if(a)try{ne(a,i)}catch(e){console.warn(`[mcp-logger] Failed to write to OK_LOG_FILE: ${e instanceof Error?e.message:e}`)}}};function Nt(e=`mcp`){return new Mt(e)}function Pt(e,t){return jt.run(e,t)}function Ft(){return jt.getStore()}const It=new Set([`find`,`markdown`,`replace`]),Lt=[`backlinks`,`deadLinks`,`documents`,`enrichedPaths`,`entries`,`forwardLinks`,`hints`,`hubs`,`orphans`,`results`],Rt=[`checkpointRef`,`cwd`,`fileCount`,`matchCount`,`ok`,`query`,`stdoutTruncated`,`truncated`];function F(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function zt(e){return F(e)&&`requestId`in e}function Bt(e,t){if(It.has(e))return{redacted:!0,type:`string`,length:t.length,lines:t.length===0?0:t.split(`
|
|
@@ -196,7 +196,7 @@ superseded_by: <path-to-new-canonical-article>.md
|
|
|
196
196
|
`);function qr(e,t){e.tool(`get_forward_links`,Kr,{docName:N.string().describe(`Source page docName`),cwd:N.string().optional().describe(I)},async e=>{let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return L(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return L(z,!0);let a=V(e.docName);if(!a.ok)return L(a.error,!0);let o=await H(i,`/api/forward-links?docName=${encodeURIComponent(a.docName)}`);if(!o.ok)return L(`Error: ${o.error}`,!0);let{ok:s,...c}=o,l=c,{resolve:u,ui:d}=await G(t,r),f=(l.forwardLinks??[]).map(e=>{let t=e.kind===`doc`&&typeof e.docName==`string`?e.docName:null,n=t?u(t):null;return{...e,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}}),p={...l,forwardLinks:f,ui:d,cwd:r};return R(JSON.stringify(p,null,2),p)})}const Jr=[`[Requires: Hocuspocus server] List version history for a document.`,`Returns timeline entries from the shadow repo, sorted by timestamp descending.`,"Each entry includes a commit SHA that can be passed to `rollback_to_version`.",``,`**Parameters:**`,"- `docName` — Document name to query history for, typically without extension. A trailing `.md` or `.mdx` is stripped automatically.","- `branch` (optional) — Branch name (default: current branch)","- `limit` (optional) — Maximum entries to return (default 50, max 200)","- `offset` (optional) — Number of entries to skip for pagination (default 0)",'- `type` (optional) — Filter by entry type: "checkpoint", "upstream", or "wip"',"- `author` (optional) — Filter to entries by this author name or email","- `excludeAuthor` (optional) — Exclude entries by this author name or email"].join(`
|
|
197
197
|
`);function Yr(e,t){e.tool(`get_history`,Jr,{docName:N.string().describe(`Document name to query history for`),branch:N.string().optional().describe(`Branch name (default: current branch)`),limit:N.number().int().min(1).max(200).optional().describe(`Maximum entries to return (default 50, max 200)`),offset:N.number().int().min(0).optional().describe(`Number of entries to skip for pagination (default 0)`),type:N.enum([`checkpoint`,`upstream`,`wip`]).optional().describe(`Filter by entry type`),author:N.string().optional().describe(`Filter to entries by this author name or email`),excludeAuthor:N.string().optional().describe(`Exclude entries by this author name or email`),cwd:N.string().optional().describe(I)},async e=>{let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return L(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return L(z,!0);let a=V(e.docName);if(!a.ok)return L(a.error,!0);let o=new URLSearchParams;o.set(`docName`,a.docName),e.branch&&o.set(`branch`,e.branch),e.limit!=null&&o.set(`limit`,String(e.limit)),e.offset!=null&&o.set(`offset`,String(e.offset)),e.type&&o.set(`type`,e.type),e.author&&o.set(`author`,e.author),e.excludeAuthor&&o.set(`excludeAuthor`,e.excludeAuthor);let s=await H(i,`/api/history?${o.toString()}`);if(!s.ok)return L(`Error: ${s.error}`,!0);let{ok:c,...l}=s,u=await W(a.docName,{config:t.config,resolveCwd:t.resolveCwd},r);return R(JSON.stringify(l,null,2),{...l,previewUrl:u?.url??null,...u?{previewUrlSource:u.source}:{}})})}const Xr=[`[Requires: Hocuspocus server] Find the most-linked pages in the knowledge graph.`,`Returns hub pages ordered by inbound link count as JSON.`,``,`**Parameters:**`,"- `limit` (optional) — Maximum number of hubs to return (default 20)"].join(`
|
|
198
198
|
`);function Zr(e,t){e.tool(`get_hubs`,Xr,{limit:N.number().int().positive().optional().describe(`Maximum number of hubs to return`),cwd:N.string().optional().describe(I)},async e=>{let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return L(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return L(z,!0);let a=await H(i,`/api/hubs${e.limit?`?limit=${encodeURIComponent(String(e.limit))}`:``}`);if(!a.ok)return L(`Error: ${a.error}`,!0);let{ok:o,...s}=a,c=s,{resolve:l,ui:u}=await G(t,r),d=(c.hubs??[]).map(e=>{let t=typeof e.docName==`string`?e.docName:null,n=t?l(t):null;return{...e,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}}),f={...c,hubs:d,ui:u,cwd:r};return R(JSON.stringify(f,null,2),f)})}const Qr=[`[Requires: Hocuspocus server] Find disconnected pages in the knowledge graph.`,`Returns orphaned pages as JSON.`,``,`**Parameters:**`,"- `mode` (optional) — Orphan lens: `incoming`, `outgoing`, or `both` (default `both`)"].join(`
|
|
199
|
-
`);function $r(e,t){e.tool(`get_orphans`,Qr,{mode:N.enum(r).optional().describe(`Filter which type of graph disconnection to surface`),cwd:N.string().optional().describe(I)},async e=>{let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return L(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return L(z,!0);let a=await H(i,`/api/orphans${e.mode?`?mode=${encodeURIComponent(e.mode)}`:``}`);if(!a.ok)return L(`Error: ${a.error}`,!0);let{ok:o,...s}=a,c=s,{resolve:l,ui:u}=await G(t,r),d=(c.orphans??[]).map(e=>{let t=typeof e.docName==`string`?e.docName:null,n=t?l(t):null;return{...e,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}}),f={...c,orphans:d,ui:u,cwd:r};return R(JSON.stringify(f,null,2),f)})}function ei(e,t){return`${$t(`ingest`)}Capture this external source into the project knowledge base as raw reference material. **Raw preservation only** — no summary, no analysis, no interpretation. Summarizing is the job of the \`research\` tool later.
|
|
199
|
+
`);function $r(e,t){e.tool(`get_orphans`,Qr,{mode:N.enum(r).optional().describe(`Filter which type of graph disconnection to surface`),cwd:N.string().optional().describe(I)},async e=>{let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return L(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return L(z,!0);let a=await H(i,`/api/orphans${e.mode?`?mode=${encodeURIComponent(e.mode)}`:``}`);if(!a.ok)return L(`Error: ${a.error}`,!0);let{ok:o,...s}=a,c=s,{resolve:l,ui:u}=await G(t,r),d=(c.orphans??[]).map(e=>{let t=typeof e.docName==`string`?e.docName:null,n=t?l(t):null;return{...e,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}}),f={...c,orphans:d,ui:u,cwd:r};return R(JSON.stringify(f,null,2),f)})}function ei(e,t){return`${$t(`ingest`)}Capture this external source into the project knowledge base as raw reference material. The KB is **closed-loop**: external sources are pulled INTO the knowledge base here so downstream docs cite local paths, never bare web URLs. This applies whether a user shared the source OR you fetched it yourself to ground a knowledge-base claim — agent-initiated fetches are not exempt. **Raw preservation only** — no summary, no analysis, no interpretation. Summarizing is the job of the \`research\` tool later.
|
|
200
200
|
|
|
201
201
|
Source: ${e}
|
|
202
202
|
|
|
@@ -277,7 +277,7 @@ If the source is directly relevant to an existing article or research doc, updat
|
|
|
277
277
|
- **No promotion to a canonical article** — that's the \`consolidate\` tool's job, later
|
|
278
278
|
- **No silent chaining into research** — ingest completes on its own; the user explicitly opts into \`research\`
|
|
279
279
|
- **No synthesis inside the raw file** — the takeaways live in chat or a separate summary doc, never mixed into the preserved source
|
|
280
|
-
`}const ti=[`Fetch an external source (URL or local file) and save raw content as reference material in the project content directory.`,`Raw preservation only — no analysis or interpretation.`,``,`**Use when:**`,`- Capturing reference material for the project knowledge base`,`- Saving a URL or document for later research`,`- Archiving an external source alongside the codebase`,`- The user shares a URL or document they want preserved
|
|
280
|
+
`}const ti=[`Fetch an external source (URL or local file) and save raw content as reference material in the project content directory.`,`Raw preservation only — no analysis or interpretation.`,`The knowledge base is closed-loop: web sources cited by KB docs MUST resolve to a local doc captured here, not bare URLs.`,``,`**Use when:**`,`- Capturing reference material for the project knowledge base`,`- Saving a URL or document for later research`,`- Archiving an external source alongside the codebase`,`- The user shares a URL or document they want preserved`,"- **You yourself fetched a URL (`WebFetch` / `WebSearch` / equivalent) to ground a claim that is about to land in a knowledge-base doc** — agent-initiated fetches are not exempt from the closed-loop rule",``,`**Triggers on:**`,`- "ingest", "save this source", "capture this URL", "add to external sources"`,`- User shares a URL, article, or document to preserve in the knowledge base`,`- Agent fetches a URL via WebFetch/WebSearch to support a knowledge-base claim — preserve the source before citing it`,`- Research workflow needs raw sources before analysis`].join(`
|
|
281
281
|
`);function ni(e,t){e.tool(`ingest`,ti,{source:N.string().describe(`URL, file path, or identifier of the source to ingest`),cwd:N.string().optional().describe(I)},async e=>{let n=await nn(t.resolveCwd,t.config,e.cwd);return n.ok?R(ei(e.source,n.config.content.dir),{previewUrl:null}):L(`Error: ${n.error}`,!0)})}const ri=[`[Requires: Hocuspocus server] List available documents from the Hocuspocus server.`,`Returns document names, optionally filtered by directory.`,``,`**Parameters:**`,"- `dir` (optional) — Filter to documents in this directory"].join(`
|
|
282
282
|
`);function ii(e,t){e.tool(`list_documents`,ri,{dir:N.string().optional().describe(`Optional directory to filter documents`),cwd:N.string().optional().describe(I)},async e=>{let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return L(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return L(z,!0);let a=await H(i,`/api/documents${e.dir?`?dir=${encodeURIComponent(e.dir)}`:``}`);if(!a.ok)return L(`Error: ${a.error}`,!0);let{ok:o,...s}=a,c=s,{resolve:l,ui:u}=await G(t,r),d=(c.documents??[]).map(e=>{let t=typeof e.docName==`string`?e.docName:null,n=t?l(t):null;return{...e,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}}),f={...c,documents:d,ui:u,cwd:r};return R(JSON.stringify(f,null,2),f)})}const ai=[`Read a wiki file with enriched context: contents + frontmatter metadata + recent shadow-repo activity (agent vs human attribution) + backlink/forward-link context.`,``,`**Use when:**`,`- Loading an article for context`,`- Understanding who changed a file recently and whether it was an agent or human`,`- Seeing how this page links out and what links back to it`,``,"Prefer this over your native `Read` for wiki files — one call returns what otherwise takes 3-4.",``,`**Parameters:**`,"- `path` — Project-root-relative path to the file, including extension (e.g. `articles/auth/sso.md`). To pass this document to `edit_document` / `write_document` / `get_backlinks`, strip the extension (they take extension-less `docName`).","- `since` (reserved) — Reserved for shadow-log since-filter; currently unused."].join(`
|
|
283
283
|
`);function oi(e){if(!e||e.length===0)return``;let t=[``,`### Recent activity (OK edits)`,``];for(let n of e){let e=n.writerClassification===`agent`?`agent: ${n.writerName}`:n.writerClassification===`principal`?`human: ${n.writerName}`:`${n.writerClassification}: ${n.writerName}`,r=n.hash.slice(0,7);t.push(`- ${r} ${n.date} [${e}] ${n.message}`)}return t.join(`
|
|
@@ -667,8 +667,8 @@ Claude Code Desktop: \`preview_start("open-knowledge-ui")\`. Other hosts: open-U
|
|
|
667
667
|
Detailed conventions (wiki-link authoring, frontmatter, anti-patterns) live in the installed \`open-knowledge\` Agent Skill. If missing, run \`npx @inkeep/open-knowledge init\`.
|
|
668
668
|
|
|
669
669
|
**Escape hatch.** Native \`Read\`/\`Grep\`/\`Glob\` on \`.md\` is allowed ONLY when no OK MCP is registered, or immediately after an OK MCP call failed — then begin your sentence with \`Open Knowledge MCP unavailable:\`. Non-markdown: native tools always.
|
|
670
|
-
`}async function Bi(e,t){try{let t=e.replace(`ws://`,`http://`).replace(`wss://`,`https://`);return(await fetch(`${t}/api/document`,{signal:AbortSignal.timeout(2e3)})).ok}catch(n){return t.warn(`Hocuspocus probe failed`,{serverUrl:e,error:n instanceof Error?n.message:String(n)}),!1}}async function Vi(n){let{projectDir:r,serverUrl:i,config:a,startupConfig:o,bypassProjectSelection:s=!1}=n;if(X=Nt(),X.info(`MCP server starting`,{startupCwd:r,bypassProjectSelection:s,serverUrlType:typeof i==`string`?`explicit`:`lazy`}),typeof i==`string`){let e=await Bi(i,X);X.info(`Hocuspocus detection complete`,{serverUrl:i,available:e})}else X.info(`server discovery is lazy per effective cwd`);let c=new Ie({name:t,version:e},{instructions:zi(o,{dynamicConfig:typeof a==`function`&&!s})}),l=Li({startupCwd:r,bypassProjectSelection:s,listRoots:()=>c.server.listRoots(),logger:X}),u=Ri({startupCwd:r,resolveCwd:l.resolveCwd,bypassProjectSelection:s}),d=u.resolveCwdForTools;c.server.setNotificationHandler(Re,async()=>{l.invalidateRoots()});let f=async e=>{if(typeof i==`string`)return i.replace(`ws://`,`http://`).replace(`wss://`,`https://`);let t=e??await d();return(typeof i==`function`?await i(t):i)?.replace(`ws://`,`http://`).replace(`wss://`,`https://`)},p=Ee(),m=process.env.AGENT_LABEL||void 0,h={current:{connectionId:p,label:m,displayName:m??`Agent`,colorSeed:m??p}};c.server.oninitialized=()=>{let e=c.server.getClientVersion();h.current={connectionId:p,clientInfo:e?{name:e.name,version:e.version}:void 0,label:m,displayName:m??e?.name??`Agent`,colorSeed:m??e?.name??p},X?.info(`agent identity established`,{displayName:h.current.displayName,connectionId:p.slice(0,8),clientName:e?.name})},Pi(c,{serverUrl:f,resolveCwd:d,config:a,identityRef:h,logger:X});let g=new Le;await c.connect(g),X.info(`MCP server running on stdio`);let{startKeepalive:_}=await import(`./keepalive-DCcA7pmC.mjs`),v=_({resolveWsUrl:async()=>{let e=await u.getKeepaliveCwd();if(!e)return;let t=await f(e);if(t)return t.replace(/^http:/,`ws:`).replace(/^https:/,`wss:`)},connectionId:`agent-${p}`,logger:X.child(`keepalive`)}),y=e=>{X?.info(`MCP server shutting down`,{signal:e});try{v.close()}catch{}process.exit(0)};process.on(`SIGINT`,()=>y(`SIGINT`)),process.on(`SIGTERM`,()=>y(`SIGTERM`))}function Hi(e){if(e===void 0||e===``)return;let t=Number.parseInt(e,10);if(!(Number.isNaN(t)||t<=0))return t}function Ui(e){if(e.portOverride!==void 0){let t=Number.parseInt(e.portOverride,10);if(Number.isNaN(t))return{action:`disk-only`,message:`invalid --port value '${e.portOverride}' — disk-only mode`};if(t>0){let n=`ws://${e.host}:${t}`;return{action:`connect`,url:n,message:`using --port override, connecting to ${n}`}}return{action:`disk-only`,message:`--port=0 — disk-only mode`}}let t=e.readLock();if(t&&t.port>0&&e.isAlive(t.pid)){let e=`ws://localhost:${t.port}`;return{action:`connect`,url:e,message:`connected to running instance at ${e} (pid ${t.pid})`}}return e.envAutoStart===`0`?{action:`disk-only`,message:`auto-spawn disabled via OK_MCP_AUTOSTART=0 — disk-only mode`}:e.configAutoStart?t?{action:`spawn`,message:`existing lock is not usable (port=${t.port}, pid=${t.pid}) — spawning ok start`}:{action:`spawn`,message:`no running instance — spawning ok start`}:{action:`disk-only`,message:`auto-spawn disabled via config.mcp.autoStart=false — disk-only mode`}}async function Wi(e){let t=e.readLock??(()=>c(e.lockDir)),n=e.isAlive??s,r=e.sleep??(e=>new Promise(t=>setTimeout(t,e))),i=e.spawn??Oe,a=e.readErrorLog??(e=>j(e)?oe(e,`utf-8`).trim():``),o=e.openErrorLog??(e=>ae(e,`w`)),l=e.closeFd??(e=>re(e)),u=e.timeoutMs??5e3,d=e.pollIntervalMs??100,f=Ui({host:e.host,portOverride:e.portOverride,envAutoStart:e.envAutoStart,configAutoStart:e.configAutoStart,readLock:t,isAlive:n});if(e.logger?.info(`auto-start decision`,{action:f.action,message:f.message,contentDir:e.contentDir}),f.action===`connect`)return{serverUrl:f.url,message:f.message};if(f.action===`disk-only`)return{serverUrl:void 0,message:f.message};j(e.lockDir)||ie(e.lockDir,{recursive:!0});let p=ve(e.lockDir,`last-spawn-error.log`),m=o(p),h,g,_=te();e.logger?.info(`spawning server`,{command:_.command,cwd:e.contentDir,timeoutMs:u});try{try{h=i(_.command,[..._.prefixArgs,`start`],{detached:!0,stdio:[`ignore`,`ignore`,m],cwd:e.contentDir}),h.on(`error`,e=>{g=e instanceof Error?e.message:String(e)}),h.unref()}catch(e){g=e instanceof Error?e.message:String(e)}}finally{try{l(m)}catch{}}let v=Date.now()+u;for(;Date.now()<v;){if(g){let t=a(p);throw e.logger?.error(`spawn failed`,void 0,{error:g,stderr:t}),Error(`Error: spawn failed: ${g}${t?` stderr:\n${t}`:``}`)}await r(d);let i=t();if(i&&i.port>0&&n(i.pid)){let t=`ws://localhost:${i.port}`;return e.logger?.info(`server ready after spawn`,{url:t,pid:i.pid}),{serverUrl:t,message:`spawned ok start; connected at ${t} (pid ${i.pid})`}}}if(g){let t=a(p);throw e.logger?.error(`spawn failed (post-deadline)`,void 0,{error:g,stderr:t}),Error(`Error: spawn failed: ${g}${t?` stderr:\n${t}`:``}`)}let y=a(p),b=(u/1e3).toFixed(u%1e3==0?0:2),x=h?.pid,S=``;throw typeof x==`number`&&(S=n(x)?` child pid=${x} is still running — raise OK_MCP_SPAWN_TIMEOUT_MS if this is a slow boot.`:` child pid=${x} exited — check last-spawn-error.log.`),e.logger?.error(`spawn poll timeout`,void 0,{timeoutMs:u,childPid:x,childAlive:typeof x==`number`?n(x):void 0,stderr:y||void 0}),Error(`Error: server did not start within ${b}s.${S}${y?` stderr:\n${y}`:``}`)}function Gi(e){if(e.portOverride!==void 0){let t=Number.parseInt(e.portOverride,10);if(Number.isNaN(t)||t<=0)return async()=>void 0;let n=`ws://${e.host}:${t}`;return async()=>n}let t=e.ensureServerRunningFn??Wi,n=e.cacheMs??1e3,r=new Map,i=new Map;return async s=>{let c=await D(s??e.startupCwd),l=Date.now(),u=r.get(c);if(u&&u.expiresAt>l)return e.logger?.debug(`server url cache hit`,{cwd:c,url:u.url}),u.url;let d=i.get(c);if(d)return e.logger?.debug(`server url resolution pending`,{cwd:c}),await d;e.logger?.debug(`server url cache miss`,{cwd:c});let f=(async()=>{let i=await e.resolveConfig(c),s=o(i,c),l=a(s),u=e.readLock,d=u?()=>u(l):void 0,f=await t({lockDir:l,contentDir:s,host:i.server.host,portOverride:void 0,envAutoStart:e.envAutoStart,configAutoStart:i.mcp.autoStart,logger:e.logger,timeoutMs:e.timeoutMs,pollIntervalMs:e.pollIntervalMs,spawn:e.spawn,readLock:d,isAlive:e.isAlive,sleep:e.sleep,readErrorLog:e.readErrorLog,openErrorLog:e.openErrorLog,closeFd:e.closeFd});return r.set(c,{url:f.serverUrl,expiresAt:Date.now()+n}),f.serverUrl})();i.set(c,f);try{return await f}finally{i.delete(c)}}}function Ki(e){return new A(`mcp`).description(`Start MCP stdio server for project knowledge base`).option(`-p, --port <port>`,`Override port discovery and connect to this port (0 = disk-only)`,void 0).action(async t=>{try{let n=e(),r=process.cwd(),i=ee({startupCwd:r,startupConfig:n}),a=Hi(process.env.OK_MCP_SPAWN_TIMEOUT_MS),o,s;if(t.port!==void 0){let e=Number.parseInt(t.port,10);Number.isNaN(e)?(o=void 0,s=`invalid --port value '${t.port}' — disk-only mode`):e>0?(o=`ws://${n.server.host}:${e}`,s=`using --port override, connecting to ${o}`):(o=void 0,s=`--port=0 — disk-only mode`)}else o=Gi({startupCwd:r,resolveConfig:i,host:n.server.host,portOverride:void 0,envAutoStart:process.env.OK_MCP_AUTOSTART,timeoutMs:a}),s=`project server discovery/autostart is lazy per effective cwd`;process.stderr.write(`[mcp] ${s}\n`),await Vi({projectDir:r,serverUrl:o,config:i,startupConfig:n,bypassProjectSelection:t.port!==void 0})}catch(e){process.stderr.write(`MCP server failed to start: ${e instanceof Error?e.message:String(e)}\n`),process.exitCode=1}})}function qi(e){return new A(`preview`).description(`Show what content the watcher will track (read-only)`).action(async()=>{let{previewContent:t,formatPreviewBlock:n}=await import(`./preview-
|
|
670
|
+
`}async function Bi(e,t){try{let t=e.replace(`ws://`,`http://`).replace(`wss://`,`https://`);return(await fetch(`${t}/api/document`,{signal:AbortSignal.timeout(2e3)})).ok}catch(n){return t.warn(`Hocuspocus probe failed`,{serverUrl:e,error:n instanceof Error?n.message:String(n)}),!1}}async function Vi(n){let{projectDir:r,serverUrl:i,config:a,startupConfig:o,bypassProjectSelection:s=!1}=n;if(X=Nt(),X.info(`MCP server starting`,{startupCwd:r,bypassProjectSelection:s,serverUrlType:typeof i==`string`?`explicit`:`lazy`}),typeof i==`string`){let e=await Bi(i,X);X.info(`Hocuspocus detection complete`,{serverUrl:i,available:e})}else X.info(`server discovery is lazy per effective cwd`);let c=new Ie({name:t,version:e},{instructions:zi(o,{dynamicConfig:typeof a==`function`&&!s})}),l=Li({startupCwd:r,bypassProjectSelection:s,listRoots:()=>c.server.listRoots(),logger:X}),u=Ri({startupCwd:r,resolveCwd:l.resolveCwd,bypassProjectSelection:s}),d=u.resolveCwdForTools;c.server.setNotificationHandler(Re,async()=>{l.invalidateRoots()});let f=async e=>{if(typeof i==`string`)return i.replace(`ws://`,`http://`).replace(`wss://`,`https://`);let t=e??await d();return(typeof i==`function`?await i(t):i)?.replace(`ws://`,`http://`).replace(`wss://`,`https://`)},p=Ee(),m=process.env.AGENT_LABEL||void 0,h={current:{connectionId:p,label:m,displayName:m??`Agent`,colorSeed:m??p}};c.server.oninitialized=()=>{let e=c.server.getClientVersion();h.current={connectionId:p,clientInfo:e?{name:e.name,version:e.version}:void 0,label:m,displayName:m??e?.name??`Agent`,colorSeed:m??e?.name??p},X?.info(`agent identity established`,{displayName:h.current.displayName,connectionId:p.slice(0,8),clientName:e?.name})},Pi(c,{serverUrl:f,resolveCwd:d,config:a,identityRef:h,logger:X});let g=new Le;await c.connect(g),X.info(`MCP server running on stdio`);let{startKeepalive:_}=await import(`./keepalive-DCcA7pmC.mjs`),v=_({resolveWsUrl:async()=>{let e=await u.getKeepaliveCwd();if(!e)return;let t=await f(e);if(t)return t.replace(/^http:/,`ws:`).replace(/^https:/,`wss:`)},connectionId:`agent-${p}`,logger:X.child(`keepalive`)}),y=e=>{X?.info(`MCP server shutting down`,{signal:e});try{v.close()}catch{}process.exit(0)};process.on(`SIGINT`,()=>y(`SIGINT`)),process.on(`SIGTERM`,()=>y(`SIGTERM`))}function Hi(e){if(e===void 0||e===``)return;let t=Number.parseInt(e,10);if(!(Number.isNaN(t)||t<=0))return t}function Ui(e){if(e.portOverride!==void 0){let t=Number.parseInt(e.portOverride,10);if(Number.isNaN(t))return{action:`disk-only`,message:`invalid --port value '${e.portOverride}' — disk-only mode`};if(t>0){let n=`ws://${e.host}:${t}`;return{action:`connect`,url:n,message:`using --port override, connecting to ${n}`}}return{action:`disk-only`,message:`--port=0 — disk-only mode`}}let t=e.readLock();if(t&&t.port>0&&e.isAlive(t.pid)){let e=`ws://localhost:${t.port}`;return{action:`connect`,url:e,message:`connected to running instance at ${e} (pid ${t.pid})`}}return e.envAutoStart===`0`?{action:`disk-only`,message:`auto-spawn disabled via OK_MCP_AUTOSTART=0 — disk-only mode`}:e.configAutoStart?t?{action:`spawn`,message:`existing lock is not usable (port=${t.port}, pid=${t.pid}) — spawning ok start`}:{action:`spawn`,message:`no running instance — spawning ok start`}:{action:`disk-only`,message:`auto-spawn disabled via config.mcp.autoStart=false — disk-only mode`}}async function Wi(e){let t=e.readLock??(()=>c(e.lockDir)),n=e.isAlive??s,r=e.sleep??(e=>new Promise(t=>setTimeout(t,e))),i=e.spawn??Oe,a=e.readErrorLog??(e=>j(e)?oe(e,`utf-8`).trim():``),o=e.openErrorLog??(e=>ae(e,`w`)),l=e.closeFd??(e=>re(e)),u=e.timeoutMs??5e3,d=e.pollIntervalMs??100,f=Ui({host:e.host,portOverride:e.portOverride,envAutoStart:e.envAutoStart,configAutoStart:e.configAutoStart,readLock:t,isAlive:n});if(e.logger?.info(`auto-start decision`,{action:f.action,message:f.message,contentDir:e.contentDir}),f.action===`connect`)return{serverUrl:f.url,message:f.message};if(f.action===`disk-only`)return{serverUrl:void 0,message:f.message};j(e.lockDir)||ie(e.lockDir,{recursive:!0});let p=ve(e.lockDir,`last-spawn-error.log`),m=o(p),h,g,_=te();e.logger?.info(`spawning server`,{command:_.command,cwd:e.contentDir,timeoutMs:u});try{try{h=i(_.command,[..._.prefixArgs,`start`],{detached:!0,stdio:[`ignore`,`ignore`,m],cwd:e.contentDir}),h.on(`error`,e=>{g=e instanceof Error?e.message:String(e)}),h.unref()}catch(e){g=e instanceof Error?e.message:String(e)}}finally{try{l(m)}catch{}}let v=Date.now()+u;for(;Date.now()<v;){if(g){let t=a(p);throw e.logger?.error(`spawn failed`,void 0,{error:g,stderr:t}),Error(`Error: spawn failed: ${g}${t?` stderr:\n${t}`:``}`)}await r(d);let i=t();if(i&&i.port>0&&n(i.pid)){let t=`ws://localhost:${i.port}`;return e.logger?.info(`server ready after spawn`,{url:t,pid:i.pid}),{serverUrl:t,message:`spawned ok start; connected at ${t} (pid ${i.pid})`}}}if(g){let t=a(p);throw e.logger?.error(`spawn failed (post-deadline)`,void 0,{error:g,stderr:t}),Error(`Error: spawn failed: ${g}${t?` stderr:\n${t}`:``}`)}let y=a(p),b=(u/1e3).toFixed(u%1e3==0?0:2),x=h?.pid,S=``;throw typeof x==`number`&&(S=n(x)?` child pid=${x} is still running — raise OK_MCP_SPAWN_TIMEOUT_MS if this is a slow boot.`:` child pid=${x} exited — check last-spawn-error.log.`),e.logger?.error(`spawn poll timeout`,void 0,{timeoutMs:u,childPid:x,childAlive:typeof x==`number`?n(x):void 0,stderr:y||void 0}),Error(`Error: server did not start within ${b}s.${S}${y?` stderr:\n${y}`:``}`)}function Gi(e){if(e.portOverride!==void 0){let t=Number.parseInt(e.portOverride,10);if(Number.isNaN(t)||t<=0)return async()=>void 0;let n=`ws://${e.host}:${t}`;return async()=>n}let t=e.ensureServerRunningFn??Wi,n=e.cacheMs??1e3,r=new Map,i=new Map;return async s=>{let c=await D(s??e.startupCwd),l=Date.now(),u=r.get(c);if(u&&u.expiresAt>l)return e.logger?.debug(`server url cache hit`,{cwd:c,url:u.url}),u.url;let d=i.get(c);if(d)return e.logger?.debug(`server url resolution pending`,{cwd:c}),await d;e.logger?.debug(`server url cache miss`,{cwd:c});let f=(async()=>{let i=await e.resolveConfig(c),s=o(i,c),l=a(s),u=e.readLock,d=u?()=>u(l):void 0,f=await t({lockDir:l,contentDir:s,host:i.server.host,portOverride:void 0,envAutoStart:e.envAutoStart,configAutoStart:i.mcp.autoStart,logger:e.logger,timeoutMs:e.timeoutMs,pollIntervalMs:e.pollIntervalMs,spawn:e.spawn,readLock:d,isAlive:e.isAlive,sleep:e.sleep,readErrorLog:e.readErrorLog,openErrorLog:e.openErrorLog,closeFd:e.closeFd});return r.set(c,{url:f.serverUrl,expiresAt:Date.now()+n}),f.serverUrl})();i.set(c,f);try{return await f}finally{i.delete(c)}}}function Ki(e){return new A(`mcp`).description(`Start MCP stdio server for project knowledge base`).option(`-p, --port <port>`,`Override port discovery and connect to this port (0 = disk-only)`,void 0).action(async t=>{try{let n=e(),r=process.cwd(),i=ee({startupCwd:r,startupConfig:n}),a=Hi(process.env.OK_MCP_SPAWN_TIMEOUT_MS),o,s;if(t.port!==void 0){let e=Number.parseInt(t.port,10);Number.isNaN(e)?(o=void 0,s=`invalid --port value '${t.port}' — disk-only mode`):e>0?(o=`ws://${n.server.host}:${e}`,s=`using --port override, connecting to ${o}`):(o=void 0,s=`--port=0 — disk-only mode`)}else o=Gi({startupCwd:r,resolveConfig:i,host:n.server.host,portOverride:void 0,envAutoStart:process.env.OK_MCP_AUTOSTART,timeoutMs:a}),s=`project server discovery/autostart is lazy per effective cwd`;process.stderr.write(`[mcp] ${s}\n`),await Vi({projectDir:r,serverUrl:o,config:i,startupConfig:n,bypassProjectSelection:t.port!==void 0})}catch(e){process.stderr.write(`MCP server failed to start: ${e instanceof Error?e.message:String(e)}\n`),process.exitCode=1}})}function qi(e){return new A(`preview`).description(`Show what content the watcher will track (read-only)`).action(async()=>{let{previewContent:t,formatPreviewBlock:n}=await import(`./preview-BRKICSmX.mjs`),r=e(),i=process.cwd(),a=o(r,i),s;try{s=t({projectDir:i,contentDir:a,include:r.content.include,exclude:r.content.exclude})}catch(e){console.error(`Content preview failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}process.stdout.write(`${n(s,i)}\n`),s.totalCount===0&&s.warnings.length>0&&(process.exitCode=1)})}function Z(e,t){e&&process.stdout.write(`${JSON.stringify(t)}\n`)}async function Ji(e,t,n=process.cwd()){let r=e.op??`sync`,i=c(a(o(t,n)));if(i&&i.port>0){let t=`http://127.0.0.1:${i.port}/api/sync/trigger`;e.json||process.stderr.write(`Triggering ${r} via running server (port ${i.port})…\n`);try{let n=await fetch(t,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({op:r})});if(!n.ok){let e=await n.json().catch(()=>({}));throw Error(e.error??`Server responded with ${n.status}`)}Z(e.json,{type:`triggered`,op:r,port:i.port}),e.json||process.stderr.write(`✓ ${r} triggered\n`);return}catch(t){let n=t instanceof Error?t.message:String(t);e.json||process.stderr.write(`Server trigger failed (${n}), running directly…\n`)}}e.json||process.stderr.write(`Running ${r} directly (no live server)…\n`);let s=P({baseDir:n});if(r===`sync`||r===`pull`){Z(e.json,{type:`step`,step:`pull`});let t=await s.pull();Z(e.json,{type:`pull`,summary:t.summary}),e.json||process.stderr.write(` pull: ${t.summary.changes} changes\n`)}(r===`sync`||r===`push`)&&(Z(e.json,{type:`step`,step:`push`}),await s.push(),Z(e.json,{type:`push`,ok:!0}),e.json||process.stderr.write(` push: ok
|
|
671
671
|
`)),Z(e.json,{type:`complete`,op:r}),e.json||process.stderr.write(`✓ ${r} complete\n`)}function Yi(e){return new A(`sync`).description(`Commit, pull, and push to the remote`).option(`--json`,`Output JSONL progress events`,!1).action(async t=>{try{await Ji({json:t.json,op:`sync`},e())}catch(e){let n=e instanceof Error?e.message:String(e);t.json?process.stdout.write(`${JSON.stringify({type:`error`,message:n})}\n`):process.stderr.write(`✗ sync failed: ${n}\n`),process.exit(1)}})}function Xi(e){return new A(`pull`).description(`Pull changes from the remote`).option(`--json`,`Output JSONL progress events`,!1).action(async t=>{try{await Ji({json:t.json,op:`pull`},e())}catch(e){let n=e instanceof Error?e.message:String(e);t.json?process.stdout.write(`${JSON.stringify({type:`error`,message:n})}\n`):process.stderr.write(`✗ pull failed: ${n}\n`),process.exit(1)}})}function Zi(e){return new A(`push`).description(`Push commits to the remote`).option(`--json`,`Output JSONL progress events`,!1).action(async t=>{try{await Ji({json:t.json,op:`push`},e())}catch(e){let n=e instanceof Error?e.message:String(e);t.json?process.stdout.write(`${JSON.stringify({type:`error`,message:n})}\n`):process.stderr.write(`✗ push failed: ${n}\n`),process.exit(1)}})}async function Qi(e={}){let t=M(e.cwd??process.cwd()),n;try{n=await h({projectDir:t,rootDir:e.root})}catch(e){return e instanceof p?{status:`prerequisite-missing`,message:`${S(`Error:`)} ${e.message}`,exitCode:1}:{status:`failed`,message:`${S(`Error:`)} ${e instanceof Error?e.message:String(e)}`,exitCode:1}}if(n.created.length===0&&n.configEdits.length===0)return{status:`no-op`,message:`${x(`Your knowledge base is already seeded.`)}\n${b(`Nothing to do.`)}`,plan:n,exitCode:0};if(e.dryRun)return{status:`dry-run`,message:`${w(`Plan (dry-run — no changes made):`)}\n\n${$i(n,t)}`,plan:n,exitCode:0};if(!e.yes&&!await ea(`${w(`Plan:`)}\n\n${$i(n,t)}\n\n${w(`Apply?`)} ${b(`[Y/n] `)}`,e.confirmStream))return{status:`cancelled`,message:b(`Cancelled.`),plan:n,exitCode:0};let r=await f(n,{projectDir:t});if(r.errors.length>0){let e=r.errors.map(e=>` ${S(`✗`)} ${e.path}: ${e.error}`);return{status:`failed`,message:[`${C(`Applied`)} ${r.applied} entries, ${C(String(r.errors.length))} error(s):`,...e].join(`
|
|
672
672
|
`),plan:n,exitCode:1}}return{status:`applied`,message:`${x(`✓ Seeded knowledge base`)} ${b(`(${r.applied} entries, ${r.durationMs}ms)`)}`,plan:n,exitCode:0}}function $i(e,t){let n=[],r=e.created.filter(e=>e.kind===`folder`),i=e.created.filter(e=>e.kind===`file`);if(r.length>0){n.push(w(`Folders to create:`));for(let e of r)n.push(` ${x(`+`)} ${y(ye(t,M(t,e.path))||e.path)}${b(`/`)}`)}if(i.length>0){n.length>0&&n.push(``),n.push(w(`Files to create:`));for(let e of i)n.push(` ${x(`+`)} ${y(ye(t,M(t,e.path))||e.path)}`)}if(e.configEdits.length>0){n.length>0&&n.push(``),n.push(w(`config.yml folders: entries to add:`));for(let t of e.configEdits)n.push(` ${x(`+`)} ${y(t.folderMatch)} ${b(`—`)} ${t.entry.frontmatter.title??``}`)}if(e.skipped.length>0){n.length>0&&n.push(``),n.push(b(`Already present (skipped):`));for(let t of e.skipped)n.push(` ${b(`· ${t.path} (${t.reason})`)}`)}if(e.warnings.length>0){n.length>0&&n.push(``),n.push(C(`Warnings:`));for(let t of e.warnings)n.push(` ${C(`!`)} ${t}`)}return n.join(`
|
|
673
|
-
`)}async function ea(e,t){let n=Ue({input:t??process.stdin,output:process.stdout});try{let t=(await n.question(e)).trim().toLowerCase();return t===``||t===`y`||t===`yes`}finally{n.close()}}function ta(){return new A(`seed`).description(`Scaffold the Karpathy three-layer knowledge-base structure (external-sources/, research/, articles/) + log.md + config.yml folders: entries. Use --root to place them inside a subfolder instead of the project root.`).argument(`[path]`,`Project directory (defaults to cwd)`).option(`-r, --root <path>`,`Subfolder (relative to the project dir) to scaffold into — created if missing. Defaults to the project root when omitted in non-interactive runs; prompts on a TTY.`).option(`-y, --yes`,`Skip confirmation prompt`).option(`--dry-run`,`Print the plan and exit without writing`).action(async(e,t)=>{let n=await Qi({cwd:e??process.cwd(),root:t.root,yes:t.yes,dryRun:t.dryRun});process.stdout.write(`${n.message}\n`),n.exitCode!==0&&(process.exitCode=n.exitCode)})}function na(e,t){return{server:ra(`server`,e),ui:ra(`ui`,t)}}function ra(e,t){switch(t.status){case`missing`:return{name:e,state:`missing`,alive:!1};case`corrupt`:return{name:e,state:`corrupt`,alive:!1};case`foreign-host`:return{name:e,state:`foreign-host`,pid:t.lock.pid,port:t.lock.port,startedAt:t.lock.startedAt,host:t.lock.hostname,alive:`unknown`};case`dead-pid`:return{name:e,state:`dead-pid`,pid:t.lock.pid,port:t.lock.port,startedAt:t.lock.startedAt,host:t.lock.hostname,alive:!1};case`alive`:return{name:e,state:`alive`,pid:t.lock.pid,port:t.lock.port,startedAt:t.lock.startedAt,host:t.lock.hostname,alive:!0}}}function ia(e){return`${aa(e.server)}\n${aa(e.ui)}`}function aa(e){let t=e.name===`server`?`server`:`ui `;return e.state===`missing`?`${t} not running`:e.state===`corrupt`?`${t} lock file corrupt — run \`ok clean\``:e.state===`foreign-host`?`${t} foreign host (${e.host}) pid=${e.pid} port=${e.port}`:e.state===`dead-pid`?`${t} stale (dead pid=${e.pid}) — run \`ok clean\``:`${t} alive pid=${e.pid} port=${e.port} started=${e.startedAt}`}function oa(e){let t=e.inspect??(t=>mt(e.lockDir,t)),n=e.log??(e=>console.log(e)),r=na(t(`server`),t(`ui`));return e.json?n(JSON.stringify(r,null,2)):n(ia(r)),r}function sa(e){return new A(`status`).description(`Show live state of the server + ui lockfiles for this project`).option(`--json`,`Emit structured JSON instead of formatted text`).action(t=>{oa({lockDir:a(o(e(),process.cwd())),json:t.json===!0})})}function ca(e,t){let n=[];return e.status===`alive`&&n.push({name:`server`,pid:e.lock.pid,port:e.lock.port}),t.status===`alive`&&n.push({name:`ui`,pid:t.lock.pid,port:t.lock.port}),{targets:n}}function la(e){let t=e.inspect??(t=>mt(e.lockDir,t)),n=e.kill??((e,t)=>process.kill(e,t)),r=e.log??(e=>console.log(e)),i=e.error??(e=>console.error(e)),a=ca(t(`server`),t(`ui`));if(a.targets.length===0)return r(`No running open-knowledge processes.`),{stopped:[],failed:[],hadTargets:!1};let o=[],s=[];for(let e of a.targets)try{n(e.pid,`SIGTERM`),o.push(e)}catch(t){s.push({target:e,error:t instanceof Error?t.message:String(t)})}return o.length>0&&r(`Stopped: ${o.map(e=>`${e.name} (pid=${e.pid}, port=${e.port})`).join(`, `)}`),s.length>0&&i(`Failed to stop: ${s.map(({target:e,error:t})=>`${e.name} (pid=${e.pid}): ${t}`).join(`; `)}`),{stopped:o,failed:s,hadTargets:!0}}function ua(e){return new A(`stop`).description(`Stop the running open-knowledge server and UI (live only)`).action(()=>{la({lockDir:a(o(e(),process.cwd()))}).failed.length>0&&(process.exitCode=1)})}const da=1e4,fa=[`connection`,`keep-alive`,`proxy-authenticate`,`proxy-authorization`,`te`,`trailer`,`transfer-encoding`,`upgrade`,`cookie`,`set-cookie`];async function pa(e){let t=e.upstreamTimeoutMs??da,n=Ne((n,r)=>{ha(n,r,e.upstreamHost,e.upstreamPort,t)});await new Promise((t,r)=>{let i=e=>r(e);n.once(`error`,i),n.listen(e.listenPort,e.host,()=>{n.off(`error`,i),t()})});let r=n.address();return{httpServer:n,port:typeof r==`object`&&r?r.port:e.listenPort,close:()=>new Promise(e=>{n.close(()=>e())})}}function ma(e,t,n){ha(e,t,n.upstreamHost,n.upstreamPort,n.upstreamTimeoutMs??da)}function ha(e,t,n,r,i){let a={...e.headers};delete a.host;for(let e of fa)delete a[e];e.setTimeout(3e4,()=>{if(t.headersSent)try{t.end()}catch{}else try{t.writeHead(408,{"Content-Type":`text/plain`}),t.end(`Request Timeout`)}catch{}try{e.socket?.destroy()}catch{}});let o=Pe({host:n,port:r,method:e.method,path:e.url,headers:{...a,host:`${n}:${r}`}},e=>{let n={...e.headers};for(let e of fa)delete n[e];t.writeHead(e.statusCode??502,n),e.pipe(t),e.once(`error`,()=>{try{t.end()}catch{}})});i>0&&o.setTimeout(i,()=>{if(!t.headersSent)t.writeHead(504,{"Content-Type":`text/plain`}),t.end(`Gateway Timeout`);else try{t.end()}catch{}o.destroy()}),o.on(`error`,()=>{if(!t.headersSent)t.writeHead(502,{"Content-Type":`text/plain`}),t.end(`Bad Gateway`);else try{t.end()}catch{}}),e.on(`error`,()=>{o.destroy()}),e.pipe(o)}async function ga(e){await Promise.all(e.map(e=>new Promise(t=>{e.close(()=>t())})))}async function _a(e){let{existsSync:t}=await import(`node:fs`),{createServer:r}=await import(`node:http`),{resolve:i}=await import(`node:path`),{acquireUiLock:a,readServerLock:o,releaseUiLock:s,updateUiLockPort:c}=await import(`./dist-C410Bipn.mjs`),{default:l}=await import(`sirv`),{resolveContentDir:u,resolveLockDir:d}=await import(`./paths-CYEVSOoI.mjs`),f=u(e.config,e.cwd),p=d(f);a(p,{port:0,worktreeRoot:e.cwd});let m=import.meta.dirname??new URL(`.`,import.meta.url).pathname,h=[i(m,`public`),i(m,`../../app/dist`),i(m,`../../../app/dist`)].find(e=>t(e)),g=h?l(h,{single:!0,gzip:!0,immutable:!0}):null,_=t(f)?l(f,{dotfiles:!1,dev:!0}):null,v=e.port,y=null,b=(e,t)=>{let n=e.url?.split(`?`)[0];if(n===`/api/config`&&(e.method===`GET`||e.method===`HEAD`)){y?.();let n=o(p),r=n&&n.port>0?`ws://localhost:${n.port}/collab`:null,i=JSON.stringify({collabUrl:r,previewUrl:null,port:v});t.setHeader(`Content-Type`,`application/json`),t.setHeader(`Cache-Control`,`no-store`),t.setHeader(`X-Content-Type-Options`,`nosniff`),t.statusCode=200,e.method===`HEAD`?t.end():t.end(i);return}if(n?.startsWith(`/api/`)){y?.();let r=o(p);if(!r||r.port<=0){t.writeHead(503,{"Content-Type":`application/json`,"Cache-Control":`no-store`}),t.end(JSON.stringify({error:"Collab server not running. Start `ok start` or run `ok status`.",path:n}));return}ma(e,t,{upstreamHost:`localhost`,upstreamPort:r.port});return}if(decodeURIComponent(n?.replace(/^\//,``)??``)&&_){t.setHeader(`X-Content-Type-Options`,`nosniff`),_(e,t,()=>{g?g(e,t):va(t)});return}if(g){g(e,t);return}va(t)},x=e.host===void 0?[`::1`,`127.0.0.1`]:[e.host],S=[],C=e.port;try{for(let e of x){let t=r(b);S.push(t),await new Promise((n,r)=>{let i=e=>r(e);t.once(`error`,i),t.listen(C,e,()=>{t.off(`error`,i);let e=t.address();typeof e==`object`&&e&&(C=e.port),n()})})}}catch(e){await Promise.all(S.map(e=>new Promise(t=>{try{e.close(()=>t())}catch{t()}})));try{s(p)}catch{}throw e}let w=C;v=w,c(p,w);let T=e.scheduler??n,E=e.safetyNetMs??432e5,D=null,O=!1,ee=!1,k=()=>{O||(O=!0,D!==null&&(T.clearTimeout(D),D=null))},te=()=>{if(k(),!ee){ee=!0;try{s(p)}catch{}}},A=()=>{O||E<=0||(D!==null&&(T.clearTimeout(D),D=null),D=T.setTimeout(()=>{D=null,console.warn(`[ui] safety-net (${E}ms) reached — shutting down (D-025 backstop)`);try{e.onSafetyNet?.()}catch{}for(let e of S)try{e.close()}catch{}te()},E))},ne=()=>{O||E<=0||A()};return y=ne,A(),{httpServers:S,port:w,release:te,detachSafetyNet:k,nudgeSafetyNet:ne}}function va(e){e.writeHead(404),e.end(`Not found`)}function ya(e,t){if(e!==void 0){let t=Number.parseInt(e,10);if(Number.isNaN(t)||t<0||t>65535)throw Error(`Invalid --port value '${e}'`);return t}if(t!==void 0&&t!==``){let e=Number.parseInt(t,10);if(Number.isNaN(e)||e<0||e>65535)throw Error(`Invalid PORT env value '${t}'`);return e}return 0}async function ba(e){let t=e.readLock??(async()=>{let{readUiLock:t}=await import(`./dist-C410Bipn.mjs`);return t(e.lockDir)}),n=await t();if(!n)throw Error(`UI lock collision reported but the lock disappeared before handling — retry acquiring.`);if(n.port===e.requestedPort&&n.port>0)return{mode:`already-running`,port:n.port};let r=n.port;if(r===0){let n=Date.now()+(e.pollDeadlineMs??2e3),i=e.pollIntervalMs??100;for(;Date.now()<n;){await new Promise(e=>{setTimeout(e,i)});let e=await t();if(e&&e.port>0){r=e.port;break}}if(r===0)throw Error("UI did not bind within 2s; run `ok clean`");if(r===e.requestedPort)return{mode:`already-running`,port:r}}return{mode:`proxy`,handle:await pa({listenPort:e.requestedPort,host:e.host,upstreamHost:`localhost`,upstreamPort:r}),upstreamPort:r}}function xa(e){return new A(`ui`).description(`Serve the Open Knowledge React editor UI`).option(`-p, --port <port>`,`UI port (default: $PORT env or 0 / kernel-allocated)`).option(`-H, --host <host>`,"UI host. Default: two-socket loopback bind (`[::1]` + `127.0.0.1`) so cross-family collisions fail loud (D-033). Pass an explicit host (e.g. `127.0.0.1`, `0.0.0.0`) to bind a single socket on that host.").action(async t=>{let{dim:n}=await import(`./colors-CMt7h9Vn.mjs`),{UiLockCollisionError:r}=await import(`./dist-C410Bipn.mjs`),{resolveContentDir:i,resolveLockDir:a}=await import(`./paths-CYEVSOoI.mjs`),o=e(),s=t.host,c;try{c=ya(t.port,process.env.PORT)}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exitCode=1;return}try{let e=await _a({config:o,cwd:process.cwd(),port:c,host:s}),t=s===void 0||s===`::`||s===`0.0.0.0`?`localhost`:s;console.log(`${n(`[ui]`)} listening on http://${t}:${e.port}`);let r=!1,i=t=>{if(r)return;r=!0,console.log(n(`\n[ui] Shutting down (${t})...`)),e.detachSafetyNet();let i=()=>{try{e.release()}finally{process.exit(process.exitCode??0)}};ga(e.httpServers).then(i,i),setTimeout(i,2e3).unref()};process.once(`SIGINT`,()=>i(`SIGINT`)),process.once(`SIGTERM`,()=>i(`SIGTERM`));return}catch(e){if(!(e instanceof r))throw e;let t=a(i(o,process.cwd())),l=s??`localhost`,u;try{u=await ba({requestedPort:c,host:l,lockDir:t})}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exit(1)}u.mode===`already-running`&&(console.log(`UI already running at http://${l}:${u.port}`),process.exit(0)),console.log(`UI running at http://${l}:${u.upstreamPort}; acting as HTTP proxy on port ${u.handle.port}`);let d=!1,f=e=>{d||(d=!0,console.log(n(`\n[ui-proxy] Shutting down (${e})...`)),u.handle.close().finally(()=>process.exit(process.exitCode??0)),setTimeout(()=>process.exit(process.exitCode??0),2e3).unref())};process.once(`SIGINT`,()=>f(`SIGINT`)),process.once(`SIGTERM`,()=>f(`SIGTERM`))}})}process.argv.includes(`--no-color`)?(process.env.NO_COLOR=`1`,delete process.env.FORCE_COLOR):process.argv.includes(`--color`)&&(process.env.FORCE_COLOR=`1`,delete process.env.NO_COLOR);const Q=new A;let $;Q.name(`open-knowledge`).description(`Local-first knowledge base with CRDT collaboration`).version(e).option(`--cwd <path>`,`Working directory`).option(`--log-level <level>`,`Log level`,`info`).option(`--no-color`,`Disable color output`).option(`--color`,`Force color output`).hook(`preAction`,e=>{let t=e.opts(),n=t.cwd;n!==void 0&&process.chdir(n);let{config:r}=O(n),i=e.args.length===0?t:e.commands[0]?.opts()??{};i.port!==void 0&&(r.server.port=Number(i.port)),i.host!==void 0&&(r.server.host=i.host),process.env.PORT&&(r.server.port=Number(process.env.PORT)),process.env.HOST&&(r.server.host=process.env.HOST),$=r});const Sa=k(()=>$);Q.addCommand(Sa,{isDefault:!0});const Ca=Ki(()=>$);Q.addCommand(Ca),Q.addCommand(E()),Q.addCommand(ta()),Q.addCommand(At());const wa=qi(()=>$);Q.addCommand(wa);const Ta=xa(()=>$);Q.addCommand(Ta),Q.addCommand(ua(()=>$)),Q.addCommand(_t(()=>$)),Q.addCommand(sa(()=>$)),Q.addCommand(pt(()=>$)),Q.addCommand(Et(()=>$)),Q.addCommand(Yi(()=>$)),Q.addCommand(Zi(()=>$)),Q.addCommand(Xi(()=>$)),await Q.parseAsync();export{};
|
|
673
|
+
`)}async function ea(e,t){let n=Ue({input:t??process.stdin,output:process.stdout});try{let t=(await n.question(e)).trim().toLowerCase();return t===``||t===`y`||t===`yes`}finally{n.close()}}function ta(){return new A(`seed`).description(`Scaffold the Karpathy three-layer knowledge-base structure (external-sources/, research/, articles/) + log.md + config.yml folders: entries. Use --root to place them inside a subfolder instead of the project root.`).argument(`[path]`,`Project directory (defaults to cwd)`).option(`-r, --root <path>`,`Subfolder (relative to the project dir) to scaffold into — created if missing. Defaults to the project root when omitted in non-interactive runs; prompts on a TTY.`).option(`-y, --yes`,`Skip confirmation prompt`).option(`--dry-run`,`Print the plan and exit without writing`).action(async(e,t)=>{let n=await Qi({cwd:e??process.cwd(),root:t.root,yes:t.yes,dryRun:t.dryRun});process.stdout.write(`${n.message}\n`),n.exitCode!==0&&(process.exitCode=n.exitCode)})}function na(e,t){return{server:ra(`server`,e),ui:ra(`ui`,t)}}function ra(e,t){switch(t.status){case`missing`:return{name:e,state:`missing`,alive:!1};case`corrupt`:return{name:e,state:`corrupt`,alive:!1};case`foreign-host`:return{name:e,state:`foreign-host`,pid:t.lock.pid,port:t.lock.port,startedAt:t.lock.startedAt,host:t.lock.hostname,alive:`unknown`};case`dead-pid`:return{name:e,state:`dead-pid`,pid:t.lock.pid,port:t.lock.port,startedAt:t.lock.startedAt,host:t.lock.hostname,alive:!1};case`alive`:return{name:e,state:`alive`,pid:t.lock.pid,port:t.lock.port,startedAt:t.lock.startedAt,host:t.lock.hostname,alive:!0}}}function ia(e){return`${aa(e.server)}\n${aa(e.ui)}`}function aa(e){let t=e.name===`server`?`server`:`ui `;return e.state===`missing`?`${t} not running`:e.state===`corrupt`?`${t} lock file corrupt — run \`ok clean\``:e.state===`foreign-host`?`${t} foreign host (${e.host}) pid=${e.pid} port=${e.port}`:e.state===`dead-pid`?`${t} stale (dead pid=${e.pid}) — run \`ok clean\``:`${t} alive pid=${e.pid} port=${e.port} started=${e.startedAt}`}function oa(e){let t=e.inspect??(t=>mt(e.lockDir,t)),n=e.log??(e=>console.log(e)),r=na(t(`server`),t(`ui`));return e.json?n(JSON.stringify(r,null,2)):n(ia(r)),r}function sa(e){return new A(`status`).description(`Show live state of the server + ui lockfiles for this project`).option(`--json`,`Emit structured JSON instead of formatted text`).action(t=>{oa({lockDir:a(o(e(),process.cwd())),json:t.json===!0})})}function ca(e,t){let n=[];return e.status===`alive`&&n.push({name:`server`,pid:e.lock.pid,port:e.lock.port}),t.status===`alive`&&n.push({name:`ui`,pid:t.lock.pid,port:t.lock.port}),{targets:n}}function la(e){let t=e.inspect??(t=>mt(e.lockDir,t)),n=e.kill??((e,t)=>process.kill(e,t)),r=e.log??(e=>console.log(e)),i=e.error??(e=>console.error(e)),a=ca(t(`server`),t(`ui`));if(a.targets.length===0)return r(`No running open-knowledge processes.`),{stopped:[],failed:[],hadTargets:!1};let o=[],s=[];for(let e of a.targets)try{n(e.pid,`SIGTERM`),o.push(e)}catch(t){s.push({target:e,error:t instanceof Error?t.message:String(t)})}return o.length>0&&r(`Stopped: ${o.map(e=>`${e.name} (pid=${e.pid}, port=${e.port})`).join(`, `)}`),s.length>0&&i(`Failed to stop: ${s.map(({target:e,error:t})=>`${e.name} (pid=${e.pid}): ${t}`).join(`; `)}`),{stopped:o,failed:s,hadTargets:!0}}function ua(e){return new A(`stop`).description(`Stop the running open-knowledge server and UI (live only)`).action(()=>{la({lockDir:a(o(e(),process.cwd()))}).failed.length>0&&(process.exitCode=1)})}const da=1e4,fa=[`connection`,`keep-alive`,`proxy-authenticate`,`proxy-authorization`,`te`,`trailer`,`transfer-encoding`,`upgrade`,`cookie`,`set-cookie`];async function pa(e){let t=e.upstreamTimeoutMs??da,n=Ne((n,r)=>{ha(n,r,e.upstreamHost,e.upstreamPort,t)});await new Promise((t,r)=>{let i=e=>r(e);n.once(`error`,i),n.listen(e.listenPort,e.host,()=>{n.off(`error`,i),t()})});let r=n.address();return{httpServer:n,port:typeof r==`object`&&r?r.port:e.listenPort,close:()=>new Promise(e=>{n.close(()=>e())})}}function ma(e,t,n){ha(e,t,n.upstreamHost,n.upstreamPort,n.upstreamTimeoutMs??da)}function ha(e,t,n,r,i){let a={...e.headers};delete a.host;for(let e of fa)delete a[e];e.setTimeout(3e4,()=>{if(t.headersSent)try{t.end()}catch{}else try{t.writeHead(408,{"Content-Type":`text/plain`}),t.end(`Request Timeout`)}catch{}try{e.socket?.destroy()}catch{}});let o=Pe({host:n,port:r,method:e.method,path:e.url,headers:{...a,host:`${n}:${r}`}},e=>{let n={...e.headers};for(let e of fa)delete n[e];t.writeHead(e.statusCode??502,n),e.pipe(t),e.once(`error`,()=>{try{t.end()}catch{}})});i>0&&o.setTimeout(i,()=>{if(!t.headersSent)t.writeHead(504,{"Content-Type":`text/plain`}),t.end(`Gateway Timeout`);else try{t.end()}catch{}o.destroy()}),o.on(`error`,()=>{if(!t.headersSent)t.writeHead(502,{"Content-Type":`text/plain`}),t.end(`Bad Gateway`);else try{t.end()}catch{}}),e.on(`error`,()=>{o.destroy()}),e.pipe(o)}async function ga(e){await Promise.all(e.map(e=>new Promise(t=>{e.close(()=>t())})))}async function _a(e){let{existsSync:t}=await import(`node:fs`),{createServer:r}=await import(`node:http`),{resolve:i}=await import(`node:path`),{acquireUiLock:a,readServerLock:o,releaseUiLock:s,updateUiLockPort:c}=await import(`./dist-B8mg-f6q.mjs`),{default:l}=await import(`sirv`),{resolveContentDir:u,resolveLockDir:d}=await import(`./paths-L4UjjxH7.mjs`),f=u(e.config,e.cwd),p=d(f);a(p,{port:0,worktreeRoot:e.cwd});let m=import.meta.dirname??new URL(`.`,import.meta.url).pathname,h=[i(m,`public`),i(m,`../../app/dist`),i(m,`../../../app/dist`)].find(e=>t(e)),g=h?l(h,{single:!0,gzip:!0,immutable:!0}):null,_=t(f)?l(f,{dotfiles:!1,dev:!0}):null,v=e.port,y=null,b=(e,t)=>{let n=e.url?.split(`?`)[0];if(n===`/api/config`&&(e.method===`GET`||e.method===`HEAD`)){y?.();let n=o(p),r=n&&n.port>0?`ws://localhost:${n.port}/collab`:null,i=JSON.stringify({collabUrl:r,previewUrl:null,port:v});t.setHeader(`Content-Type`,`application/json`),t.setHeader(`Cache-Control`,`no-store`),t.setHeader(`X-Content-Type-Options`,`nosniff`),t.statusCode=200,e.method===`HEAD`?t.end():t.end(i);return}if(n?.startsWith(`/api/`)){y?.();let r=o(p);if(!r||r.port<=0){t.writeHead(503,{"Content-Type":`application/json`,"Cache-Control":`no-store`}),t.end(JSON.stringify({error:"Collab server not running. Start `ok start` or run `ok status`.",path:n}));return}ma(e,t,{upstreamHost:`localhost`,upstreamPort:r.port});return}if(decodeURIComponent(n?.replace(/^\//,``)??``)&&_){t.setHeader(`X-Content-Type-Options`,`nosniff`),_(e,t,()=>{g?g(e,t):va(t)});return}if(g){g(e,t);return}va(t)},x=e.host===void 0?[`::1`,`127.0.0.1`]:[e.host],S=[],C=e.port;try{for(let e of x){let t=r(b);S.push(t),await new Promise((n,r)=>{let i=e=>r(e);t.once(`error`,i),t.listen(C,e,()=>{t.off(`error`,i);let e=t.address();typeof e==`object`&&e&&(C=e.port),n()})})}}catch(e){await Promise.all(S.map(e=>new Promise(t=>{try{e.close(()=>t())}catch{t()}})));try{s(p)}catch{}throw e}let w=C;v=w,c(p,w);let T=e.scheduler??n,E=e.safetyNetMs??432e5,D=null,O=!1,ee=!1,k=()=>{O||(O=!0,D!==null&&(T.clearTimeout(D),D=null))},te=()=>{if(k(),!ee){ee=!0;try{s(p)}catch{}}},A=()=>{O||E<=0||(D!==null&&(T.clearTimeout(D),D=null),D=T.setTimeout(()=>{D=null,console.warn(`[ui] safety-net (${E}ms) reached — shutting down (D-025 backstop)`);try{e.onSafetyNet?.()}catch{}for(let e of S)try{e.close()}catch{}te()},E))},ne=()=>{O||E<=0||A()};return y=ne,A(),{httpServers:S,port:w,release:te,detachSafetyNet:k,nudgeSafetyNet:ne}}function va(e){e.writeHead(404),e.end(`Not found`)}function ya(e,t){if(e!==void 0){let t=Number.parseInt(e,10);if(Number.isNaN(t)||t<0||t>65535)throw Error(`Invalid --port value '${e}'`);return t}if(t!==void 0&&t!==``){let e=Number.parseInt(t,10);if(Number.isNaN(e)||e<0||e>65535)throw Error(`Invalid PORT env value '${t}'`);return e}return 0}async function ba(e){let t=e.readLock??(async()=>{let{readUiLock:t}=await import(`./dist-B8mg-f6q.mjs`);return t(e.lockDir)}),n=await t();if(!n)throw Error(`UI lock collision reported but the lock disappeared before handling — retry acquiring.`);if(n.port===e.requestedPort&&n.port>0)return{mode:`already-running`,port:n.port};let r=n.port;if(r===0){let n=Date.now()+(e.pollDeadlineMs??2e3),i=e.pollIntervalMs??100;for(;Date.now()<n;){await new Promise(e=>{setTimeout(e,i)});let e=await t();if(e&&e.port>0){r=e.port;break}}if(r===0)throw Error("UI did not bind within 2s; run `ok clean`");if(r===e.requestedPort)return{mode:`already-running`,port:r}}return{mode:`proxy`,handle:await pa({listenPort:e.requestedPort,host:e.host,upstreamHost:`localhost`,upstreamPort:r}),upstreamPort:r}}function xa(e){return new A(`ui`).description(`Serve the Open Knowledge React editor UI`).option(`-p, --port <port>`,`UI port (default: $PORT env or 0 / kernel-allocated)`).option(`-H, --host <host>`,"UI host. Default: two-socket loopback bind (`[::1]` + `127.0.0.1`) so cross-family collisions fail loud (D-033). Pass an explicit host (e.g. `127.0.0.1`, `0.0.0.0`) to bind a single socket on that host.").action(async t=>{let{dim:n}=await import(`./colors-CMt7h9Vn.mjs`),{UiLockCollisionError:r}=await import(`./dist-B8mg-f6q.mjs`),{resolveContentDir:i,resolveLockDir:a}=await import(`./paths-L4UjjxH7.mjs`),o=e(),s=t.host,c;try{c=ya(t.port,process.env.PORT)}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exitCode=1;return}try{let e=await _a({config:o,cwd:process.cwd(),port:c,host:s}),t=s===void 0||s===`::`||s===`0.0.0.0`?`localhost`:s;console.log(`${n(`[ui]`)} listening on http://${t}:${e.port}`);let r=!1,i=t=>{if(r)return;r=!0,console.log(n(`\n[ui] Shutting down (${t})...`)),e.detachSafetyNet();let i=()=>{try{e.release()}finally{process.exit(process.exitCode??0)}};ga(e.httpServers).then(i,i),setTimeout(i,2e3).unref()};process.once(`SIGINT`,()=>i(`SIGINT`)),process.once(`SIGTERM`,()=>i(`SIGTERM`));return}catch(e){if(!(e instanceof r))throw e;let t=a(i(o,process.cwd())),l=s??`localhost`,u;try{u=await ba({requestedPort:c,host:l,lockDir:t})}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exit(1)}u.mode===`already-running`&&(console.log(`UI already running at http://${l}:${u.port}`),process.exit(0)),console.log(`UI running at http://${l}:${u.upstreamPort}; acting as HTTP proxy on port ${u.handle.port}`);let d=!1,f=e=>{d||(d=!0,console.log(n(`\n[ui-proxy] Shutting down (${e})...`)),u.handle.close().finally(()=>process.exit(process.exitCode??0)),setTimeout(()=>process.exit(process.exitCode??0),2e3).unref())};process.once(`SIGINT`,()=>f(`SIGINT`)),process.once(`SIGTERM`,()=>f(`SIGTERM`))}})}process.argv.includes(`--no-color`)?(process.env.NO_COLOR=`1`,delete process.env.FORCE_COLOR):process.argv.includes(`--color`)&&(process.env.FORCE_COLOR=`1`,delete process.env.NO_COLOR);const Q=new A;let $;Q.name(`open-knowledge`).description(`Local-first knowledge base with CRDT collaboration`).version(e).option(`--cwd <path>`,`Working directory`).option(`--log-level <level>`,`Log level`,`info`).option(`--no-color`,`Disable color output`).option(`--color`,`Force color output`).hook(`preAction`,e=>{let t=e.opts(),n=t.cwd;n!==void 0&&process.chdir(n);let{config:r}=O(n),i=e.args.length===0?t:e.commands[0]?.opts()??{};i.port!==void 0&&(r.server.port=Number(i.port)),i.host!==void 0&&(r.server.host=i.host),process.env.PORT&&(r.server.port=Number(process.env.PORT)),process.env.HOST&&(r.server.host=process.env.HOST),$=r});const Sa=k(()=>$);Q.addCommand(Sa,{isDefault:!0});const Ca=Ki(()=>$);Q.addCommand(Ca),Q.addCommand(E()),Q.addCommand(ta()),Q.addCommand(At());const wa=qi(()=>$);Q.addCommand(wa);const Ta=xa(()=>$);Q.addCommand(Ta),Q.addCommand(ua(()=>$)),Q.addCommand(_t(()=>$)),Q.addCommand(sa(()=>$)),Q.addCommand(pt(()=>$)),Q.addCommand(Et(()=>$)),Q.addCommand(Yi(()=>$)),Q.addCommand(Zi(()=>$)),Q.addCommand(Xi(()=>$)),await Q.parseAsync();export{};
|
|
674
674
|
//# sourceMappingURL=cli.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./dist-D4iyaPjq.mjs";import{a as e,c as t}from"./server-lock-CH0GCP_4-DCYOtKMW.mjs";import{A as n,Bt as r,St as i,T as a,X as o,_ as s,q as c,vt as l,w as u}from"./dist-
|
|
1
|
+
import"./dist-D4iyaPjq.mjs";import{a as e,c as t}from"./server-lock-CH0GCP_4-DCYOtKMW.mjs";import{A as n,Bt as r,St as i,T as a,X as o,_ as s,q as c,vt as l,w as u}from"./dist-mmLEboji.mjs";export{s as ProjectGitInitError,u as UiLockCollisionError,a as acquireUiLock,n as bootServer,c as ensureProjectGit,o as getLogger,e as isProcessAlive,t as readServerLock,l as readUiLock,i as releaseUiLock,r as updateUiLockPort};
|
|
@@ -90,20 +90,32 @@ To resolve the conflict:`,(0,r.getConflictResolutionRecipe)(i,e))),a=o):n.diag.w
|
|
|
90
90
|
`).trim()),i=[]):i.push(e)}if(i.length>0){let e=i.join(`
|
|
91
91
|
`).trim();e&&r.push(e)}return r}function reconcile(e){if(isSystemDoc(e.docName))return{kind:`noop`};let{base:t,ours:n,theirs:r}=e;return containsConflictMarkers(r)?{kind:`refused`,reason:`conflict-markers`}:r===t?{kind:`noop`}:n===t?{kind:`clean`,newContent:r}:mergeBlocks(splitMarkdownBlocks(t),splitMarkdownBlocks(n),splitMarkdownBlocks(r))}function mergeBlocks(e,t,n){let r=computeEditOps(e,t),i=computeEditOps(e,n),a=[],o=[];for(let t=0;t<e.length;t++){let n=e[t],s=r.get(t),c=i.get(t),l=s?.insertsBefore??[],u=c?.insertsBefore??[];a.push(...l,...u);let d=s?.action??`keep`,f=c?.action??`keep`;if(d===`keep`&&f===`keep`)a.push(n);else if(d===`keep`&&f!==`keep`)f===`modify`&&c?.newContent!==void 0&&a.push(c.newContent);else if(d!==`keep`&&f===`keep`)d===`modify`&&s?.newContent!==void 0&&a.push(s.newContent);else{let e=d===`modify`?s?.newContent:null,r=f===`modify`?c?.newContent:null;e===r||o.push({blockIndex:t,base:n,ours:e??``,theirs:r??``}),e!=null&&a.push(e)}}let s=r.get(e.length),c=i.get(e.length);s?.insertsBefore&&a.push(...s.insertsBefore),c?.insertsBefore&&a.push(...c.insertsBefore);let l=a.length>0?`${a.join(`
|
|
92
92
|
|
|
93
|
-
`)}\n`:``;return o.length>0?{kind:`conflicts`,newContent:l,conflicts:o}:{kind:`merged`,newContent:l,mergedBlocks:a.length}}function computeEditOps(e,t){let n=new Map,r=longestCommonSubsequence(e,t);for(let t=0;t<=e.length;t++)n.set(t,{action:`keep`,insertsBefore:[]});let i=new Set,a=new Set;for(let[e,t]of r)i.add(e),a.add(t);let o=-1;for(let s=0;s<e.length;s++)if(i.has(s)){let e=r.find(e=>e[0]===s)?.[1]??-1,i=[];for(let n=o+1;n<e;n++)a.has(n)||i.push(t[n]);let c=n.get(s);c&&(c.insertsBefore=i),o=e}else{let e=r.find(e=>e[0]>s),i=e?e[1]:t.length,c=[];for(let e=o+1;e<i;e++)a.has(e)||c.push(e);if(c.length>0){let e=c[0];a.add(e);let r=n.get(s);r&&(r.action=`modify`,r.newContent=t[e])}else{let e=n.get(s);e&&(e.action=`delete`)}}let s=[];for(let e=o+1;e<t.length;e++)a.has(e)||s.push(t[e]);let c=n.get(e.length);return c&&(c.insertsBefore=s),n}function longestCommonSubsequence(e,t){let n=e.length,r=t.length,i=Array.from({length:n+1},()=>Array(r+1).fill(0));for(let a=1;a<=n;a++)for(let n=1;n<=r;n++)e[a-1]===t[n-1]?i[a][n]=i[a-1][n-1]+1:i[a][n]=Math.max(i[a-1][n],i[a][n-1]);let a=[],o=n,s=r;for(;o>0&&s>0;)e[o-1]===t[s-1]?(a.push([o-1,s-1]),o--,s--):i[o-1][s]>=i[o][s-1]?o--:s--;return a.reverse()}const writeTracker=new Map,WRITE_TRACKER_TTL_MS=1e4;function registerWrite(e,t){let n=writeTracker.get(e)??[];n.push({hash:t,timestamp:Date.now()}),writeTracker.set(e,n)}function evictStaleTrackerEntries(){let e=Date.now();for(let[t,n]of writeTracker){let r=n.filter(t=>e-t.timestamp<=WRITE_TRACKER_TTL_MS);r.length===0?writeTracker.delete(t):r.length!==n.length&&writeTracker.set(t,r)}}function contentHash(e){return createHash(`sha256`).update(e).digest(`hex`)}function pathToDocName(e,t){return stripDocExtension(relative(t,e))}function extractDocExtension(e){let t=e.toLowerCase();return t.endsWith(`.mdx`)?`.mdx`:t.endsWith(`.md`)?`.md`:null}const lastKnownHash=new Map;function updateLastKnownHash(e,t){lastKnownHash.set(e,t)}function removeLastKnownHash(e){let t=lastKnownHash.get(e);return lastKnownHash.delete(e),t}async function classifyEvents(e,t,n,r){let i=[],a=[],o=[];for(let r of e)if(isSupportedDocFile(r.path)){if(n){let e=relative(t,r.path);if(n.isExcluded(e))continue}switch(r.type){case`delete`:i.push(r);break;case`create`:lastKnownHash.has(r.path)?o.push(r):a.push(r);break;case`update`:o.push(r);break}}let s=new Map,c=new Map;for(let e of a)try{s.set(e.path,await readFile(e.path,`utf-8`))}catch(t){t.code!==`ENOENT`&&console.warn(`[file-watcher] Failed to read ${e.path}:`,t)}for(let e of o)try{c.set(e.path,await readFile(e.path,`utf-8`))}catch(t){t.code!==`ENOENT`&&console.warn(`[file-watcher] Failed to read ${e.path}:`,t)}function l(e){let n=pathToDocName(e,t);if(!r)return n;let i=null;try{i=lstatSync(e)}catch(t){return t.code!==`ENOENT`&&console.warn(`[file-watcher] resolveDocName lstat failed for ${e}:`,t),r.has(n)&&r.delete(n),n}if(!i.isSymbolicLink())return r.has(n)&&r.delete(n),n;let a;try{a=realpathSync(e)}catch(t){let i=t.code;return i!==`ENOENT`&&i!==`ELOOP`&&console.warn(`[file-watcher] resolveDocName realpath failed for ${e}:`,t),r.delete(n),n}if(!isWithinContentDir(a,t))return r.delete(n),n;let o=pathToDocName(a,t);return o===n?n:(r.set(n,o),o)}let u=[],d=new Set,f=new Set;for(let e of i){let t=removeLastKnownHash(e.path);if(t)for(let n of a){if(d.has(n.path))continue;let r=s.get(n.path);if(!r)continue;let i=contentHash(r);if(i===t){d.add(n.path),f.add(e.path),updateLastKnownHash(n.path,i),u.push({kind:`rename`,oldPath:e.path,newPath:n.path,oldDocName:l(e.path),newDocName:l(n.path),content:r});break}}}for(let e of i)f.has(e.path)||(removeLastKnownHash(e.path),u.push({kind:`delete`,path:e.path,docName:l(e.path)}));for(let e of a){if(d.has(e.path))continue;let t=s.get(e.path);if(!t)continue;let n=contentHash(t);updateLastKnownHash(e.path,n),containsConflictMarkers(t)?u.push({kind:`conflict`,path:e.path,docName:l(e.path),content:t}):u.push({kind:`create`,path:e.path,docName:l(e.path),content:t})}for(let e of o){let t=c.get(e.path);if(!t)continue;let n=contentHash(t);updateLastKnownHash(e.path,n),containsConflictMarkers(t)?u.push({kind:`conflict`,path:e.path,docName:l(e.path),content:t}):u.push({kind:`update`,path:e.path,docName:l(e.path),content:t})}return u}function isSelfWrite(e,t){let n=writeTracker.get(e);if(!n)return!1;let r=n.findIndex(e=>e.hash===t);return r<0?!1:(n.splice(r,1),n.length===0&&writeTracker.delete(e),!0)}function seedLastKnownHashes(e,t,n,r,i,a){let o=a??new Set;try{let a=readdirSync(e,{withFileTypes:!0});for(let s of a){let a=join(e,s.name),c;try{c=lstatSync(a)}catch(e){e.code!==`ENOENT`&&console.warn(`[file-watcher] Failed to lstat ${a}, skipping:`,e);continue}if(c.isSymbolicLink()){let e;try{e=realpathSync(a)}catch(e){let t=e.code;t===`ENOENT`||t===`ELOOP`?console.warn(`[file-watcher] Broken/cyclic symlink at ${a}, skipping`):console.warn(`[file-watcher] Failed to resolve symlink ${a}:`,e);continue}if(!isWithinContentDir(e,t)){console.warn(`[file-watcher] Symlink escape: ${a} → ${e}, skipping`);continue}try{let c=statSync(e);if(o.has(c.ino)){if(c.isFile()&&isSupportedDocFile(s.name)){let n=pathToDocName(a,t),o=pathToDocName(e,t);i.set(n,o);let s=r.get(o);s&&!s.aliases.includes(n)&&s.aliases.push(n)}continue}if(o.add(c.ino),c.isDirectory()){if(n){let r=relative(t,e);if(n.isDirExcluded(r))continue}seedLastKnownHashes(e,t,n,r,i,o)}else if(c.isFile()&&isSupportedDocFile(s.name)){if(n){let r=relative(t,e);if(n.isExcluded(r))continue}let o=pathToDocName(a,t),s=pathToDocName(e,t);i.set(o,s);try{let t=contentHash(readFileSync(e,`utf-8`));lastKnownHash.set(e,t);let n=extractDocExtension(e);if(n){let e=registerDocExtension(s,n);if(e.shadowed&&(console.warn(`[file-watcher] docName "${s}" has both "${e.effective}" and "${e.shadowed}" on disk; "${e.effective}" wins (industry convention). Rename or delete one to disambiguate.`),!e.changed))continue}r.set(s,{size:c.size,modified:c.mtime.toISOString(),canonicalPath:e,inode:c.ino,aliases:[o]})}catch(t){t.code!==`ENOENT`&&console.warn(`[file-watcher] Failed to seed hash for ${e}:`,t)}}}catch(t){console.warn(`[file-watcher] Failed to stat symlink target ${e}:`,t)}}else if(c.isDirectory()){if(n){let e=relative(t,a);if(n.isDirExcluded(e))continue}seedLastKnownHashes(a,t,n,r,i,o)}else if(c.isFile()&&isSupportedDocFile(s.name)){if(o.has(c.ino))continue;if(o.add(c.ino),n){let e=relative(t,a);if(n.isExcluded(e))continue}try{let e=readFileSync(a,`utf-8`);lastKnownHash.set(a,contentHash(e));let n=pathToDocName(a,t),i=extractDocExtension(a);if(i){let e=registerDocExtension(n,i);if(e.shadowed&&(console.warn(`[file-watcher] docName "${n}" has both "${e.effective}" and "${e.shadowed}" on disk; "${e.effective}" wins (industry convention). Rename or delete one to disambiguate.`),!e.changed))continue}r.set(n,{size:c.size,modified:c.mtime.toISOString(),canonicalPath:a,inode:c.ino,aliases:[]})}catch(e){let t=e.code;t===`EACCES`?console.warn(`[file-watcher] Permission denied reading ${a}, file excluded from index`):t!==`ENOENT`&&console.warn(`[file-watcher] Failed to seed hash for ${a}:`,e)}}}}catch(t){t.code!==`ENOENT`&&console.warn(`[file-watcher] Failed to read directory ${e}:`,t)}}function updateFileIndex(e,t){if(!isSystemDoc(e.kind===`rename`?e.newDocName:e.docName))switch(e.kind){case`create`:case`update`:case`conflict`:{let n=e.docName,r=t.get(n),i=extractDocExtension(e.path);i&®isterDocExtension(n,i),t.set(n,{size:Buffer.byteLength(e.content,`utf-8`),modified:new Date().toISOString(),canonicalPath:r?.canonicalPath??e.path,inode:r?.inode??0,aliases:r?.aliases??[]});break}case`delete`:if(t.has(e.docName))t.delete(e.docName),forgetDocExtension(e.docName);else for(let[,n]of t){let t=n.aliases.indexOf(e.docName);if(t!==-1){n.aliases.splice(t,1);break}}break;case`rename`:{let n=t.get(e.oldDocName);t.delete(e.oldDocName),forgetDocExtension(e.oldDocName);let r=extractDocExtension(e.newPath);r&®isterDocExtension(e.newDocName,r),t.set(e.newDocName,{size:Buffer.byteLength(e.content,`utf-8`),modified:new Date().toISOString(),canonicalPath:n?.canonicalPath??e.newPath,inode:n?.inode??0,aliases:n?.aliases??[]});break}}}async function handleRawEvents(e,t,n,r,i,a){let o=e.filter(e=>isSupportedDocFile(e.path));if(o.length===0)return;let s=await classifyEvents(o,t,n,a);for(let e of s){let t=!1;if(e.kind!==`delete`&&e.kind!==`rename`){let n=contentHash(e.content),r=e.path;try{r=realpathSync(e.path)}catch(t){let n=t.code;n!==`ENOENT`&&console.warn(`[file-watcher] realpathSync failed for self-write check on ${e.path} (${n})`)}t=isSelfWrite(r,n)}else if(e.kind===`rename`){let n=contentHash(e.content),r=e.newPath;try{r=realpathSync(e.newPath)}catch(t){let n=t.code;n!==`ENOENT`&&console.warn(`[file-watcher] realpathSync failed for self-write check on ${e.newPath} (${n})`)}t=isSelfWrite(r,n)}if(updateFileIndex(e,r),n)switch(e.kind){case`create`:n.incrementMdDir(dirname(e.docName));break;case`delete`:n.decrementMdDir(dirname(e.docName));break;case`rename`:n.decrementMdDir(dirname(e.oldDocName)),n.incrementMdDir(dirname(e.newDocName));break}if(t){getLogger(`file-watcher`).debug({kind:e.kind,path:e.kind===`rename`?e.newPath:e.path,self:!0},`[file-watcher] Skipped self-write: ${e.kind}`),_fileWatcherEventsCounter().add(1,{"disk.kind":e.kind,self:!0});continue}getLogger(`file-watcher`).debug({kind:e.kind,path:e.kind===`rename`?e.newPath:e.path},`[file-watcher] Dispatching: ${e.kind}`),_fileWatcherEventsCounter().add(1,{"disk.kind":e.kind,self:!1});let a=e.kind===`rename`?e.newPath:e.path;await withSpan(`file_watcher.process_event`,{attributes:{"disk.kind":e.kind,"disk.path":normalizeFsPath(a),"disk.path.role":classifyFsPath(a)}},async()=>i(e))}}let _fwEventsCounterCache=null;function _fileWatcherEventsCounter(){return _fwEventsCounterCache||=getMeter().createCounter(`ok.file_watcher.events`,{description:`Number of file-watcher events classified by kind`}),_fwEventsCounterCache}async function startParcelWatcher(e,t,n,r,i){let a;try{a=await import(`@parcel/watcher`)}catch(e){return console.warn(`[file-watcher] @parcel/watcher import failed:`,e instanceof Error?e.message:e),null}try{let o=t?{ignore:t.getWatcherIgnoreGlobs()}:void 0;return await a.subscribe(e,async(a,o)=>{if(a){console.error(`[file-watcher]`,a);return}try{await handleRawEvents(o.map(e=>({type:e.type,path:e.path})),e,t,n,r,i)}catch(e){console.error(`[file-watcher] parcel batch error:`,e)}},o)}catch(e){return console.warn(`[file-watcher] @parcel/watcher subscribe failed, falling back to chokidar:`,e),null}}async function startChokidarWatcher(e,t,n,r,i){let{watch:a}=await import(`./chokidar-DhL3M6Oj.mjs`);console.warn(`[file-watcher] @parcel/watcher unavailable, using chokidar fallback`);let o=a(e,{ignoreInitial:!0,ignored:t?(n,r)=>{let i=relative(e,n);return i===``||i===`.`?!1:r?.isDirectory()?t.isDirExcluded(i):t.isExcluded(i)}:void 0});o.on(`error`,e=>console.error(`[file-watcher] chokidar error:`,e));let s=50,c=[],l=null;function u(a,o){c.push({type:a,path:o}),l||=setTimeout(()=>{let a=c;c=[],l=null,handleRawEvents(a,e,t,n,r,i).catch(e=>console.error(`[file-watcher] chokidar batch error:`,e))},50)}return o.on(`add`,e=>u(`create`,e)),o.on(`change`,e=>u(`update`,e)),o.on(`unlink`,e=>u(`delete`,e)),{unsubscribe:()=>(l&&(clearTimeout(l),l=null,c=[]),o.close())}}async function startWatcher(e,t,n){let r;try{r=realpathSync(e)}catch{r=e}let i=new Map,a=new Map;seedLastKnownHashes(r,r,n,i,a);let o=setInterval(evictStaleTrackerEntries,WRITE_TRACKER_TTL_MS),s,c;try{let e=await startParcelWatcher(r,n,i,t,a);e?(s=e,c=`parcel`):(s=await startChokidarWatcher(r,n,i,t,a),c=`chokidar`)}catch(e){throw clearInterval(o),e}let l=s.unsubscribe.bind(s);return console.log(`[file-watcher] Watching ${r} for external .md changes (backend: ${c})`),{async unsubscribe(){return clearInterval(o),writeTracker.clear(),lastKnownHash.clear(),l()},getFileIndex(){return i},getAliasMap(){return a}}}const defaultGitConfigReader=(e,t,n)=>{let r=spawnSync(`git`,[`config`,n===`local`?`--local`:`--global`,t],{cwd:e,encoding:`utf-8`,timeout:5e3});return r.status!==0||!r.stdout?null:r.stdout.trim()||null};async function resolveGitIdentity(e,t,n,r=defaultGitConfigReader){let i=r(e,`user.name`,`local`),a=r(e,`user.email`,`local`);if(i&&a)return{name:i,email:a};let o=r(e,`user.name`,`global`),s=r(e,`user.email`,`global`);if(o&&s)return{name:o,email:s};if(t&&n){let e=await t.get(n);if(e){let t=e.name??e.login,n=e.email??`${e.login}@users.noreply.github.com`;if(t)return{name:t,email:n}}}return null}function writeGitIdentity(e,t,n){let r=(t,n)=>{let r=spawnSync(`git`,[`config`,`--local`,t,n],{cwd:e,encoding:`utf-8`,timeout:5e3});if(r.status!==0){let e=r.stderr?.trim()??``;throw Error(`git config --local ${t} failed: ${e}`)}};r(`user.name`,t),r(`user.email`,n)}const MAX_LEN=128;function sanitizeGitIdentity(e){return e.replace(/[<>\r\n]/g,``).trim().slice(0,MAX_LEN)}const ALLOWED_URL_PATTERNS=[/^https?:\/\//i,/^ssh:\/\//i,/^git:\/\//i,/^git@[^:]+:/],BLOCKED_URL_PATTERNS=[/^file:\/\//i,/^javascript:/i,/^ext::/i,/^data:/i,/^vbscript:/i];function isAllowedGitUrl(e){return!e||typeof e!=`string`||BLOCKED_URL_PATTERNS.some(t=>t.test(e))?!1:ALLOWED_URL_PATTERNS.some(t=>t.test(e))}function expandTilde(e){return e===`~`?homedir():e.startsWith(`~/`)?join(homedir(),e.slice(2)):e}function isSafeLocalPath(e){if(!e||typeof e!=`string`||e.includes(`\0`))return!1;let t=homedir(),n=resolve(expandTilde(e));return n===t||n.startsWith(`${t}/`)}function isLoopbackRequest(e){let t=e.socket.remoteAddress;return t===`127.0.0.1`||t===`::1`||t===`::ffff:127.0.0.1`}function hasValidLocalOpOrigin(e){let t=e.headers.origin;if(!t)return!0;try{let{hostname:e}=new URL(t);return e===`127.0.0.1`||e===`localhost`||e===`[::1]`||e===`::1`}catch{return!1}}function checkLocalOpSecurity(e,t,n){return isLoopbackRequest(e)?hasValidLocalOpOrigin(e)?!0:(n(t,403,{ok:!1,error:`Forbidden: invalid origin for local-op endpoint`}),!1):(n(t,403,{ok:!1,error:`Forbidden: local-op endpoints require loopback connection`}),!1)}function createConcurrencyGuard(){let e=new Set;return{tryAcquire(t){return e.has(t)?!1:(e.add(t),!0)},release(t){e.delete(t)}}}function isLoopbackAddress(e){return e?!!(e===`::1`||e.startsWith(`::ffff:127.`)||e.startsWith(`127.`)):!1}function isAllowedWorkspaceHostHeader(e){if(!e)return!1;if(e.startsWith(`[`)){let t=e.indexOf(`]`);if(t<0)return!1;let n=e.slice(1,t),r=e.slice(t+1);return r!==``&&!/^:\d+$/.test(r)?!1:n===`::1`}let t=e.lastIndexOf(`:`),n=t>=0?e.slice(0,t):e,r=t>=0?e.slice(t+1):null;return r!==null&&!/^\d+$/.test(r)?!1:!!(n===`localhost`||/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(n))}const MANAGED_RENAME_JOURNAL_VERSION=1,MANAGED_RENAME_JOURNAL_FILENAME=`managed-rename.json`;function journalDir(e){return resolve(e,`.open-knowledge`)}function managedRenameJournalPath(e){return resolve(journalDir(e),MANAGED_RENAME_JOURNAL_FILENAME)}function createManagedRenameRecoveryJournal(e){return{version:MANAGED_RENAME_JOURNAL_VERSION,sourceDocName:e.sourceDocName,destinationDocName:e.destinationDocName,createdAt:e.createdAt??new Date().toISOString(),snapshots:e.snapshots}}function isManagedRenameSnapshot(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.docName==`string`&&typeof t.content==`string`}function parseManagedRenameRecoveryJournal(e){if(!e||typeof e!=`object`)throw Error(`Managed rename journal must be an object`);let t=e;if(t.version!==MANAGED_RENAME_JOURNAL_VERSION)throw Error(`Unsupported managed rename journal version: ${String(t.version)}`);if(typeof t.sourceDocName!=`string`||t.sourceDocName.length===0)throw Error(`Managed rename journal is missing sourceDocName`);if(typeof t.destinationDocName!=`string`||t.destinationDocName.length===0)throw Error(`Managed rename journal is missing destinationDocName`);if(typeof t.createdAt!=`string`||t.createdAt.length===0)throw Error(`Managed rename journal is missing createdAt`);if(!Array.isArray(t.snapshots)||t.snapshots.length===0)throw Error(`Managed rename journal is missing snapshots`);if(!t.snapshots.every(isManagedRenameSnapshot))throw Error(`Managed rename journal has invalid snapshots`);if(!t.snapshots.some(e=>e.docName===t.sourceDocName))throw Error(`Managed rename journal must include the source document snapshot`);return{version:t.version,sourceDocName:t.sourceDocName,destinationDocName:t.destinationDocName,createdAt:t.createdAt,snapshots:t.snapshots}}function readManagedRenameJournal(e){let t=managedRenameJournalPath(e);if(!existsSync(t))return null;let n=readFileSync(t,`utf-8`);try{return parseManagedRenameRecoveryJournal(JSON.parse(n))}catch(e){throw Error(`Managed rename journal at ${t} is corrupt: ${e instanceof Error?e.message:String(e)}`)}}function writeManagedRenameJournal(e,t){let n=managedRenameJournalPath(e);mkdirSync(dirname(n),{recursive:!0});let r=`${n}.tmp`;writeFileSync(r,JSON.stringify(t,null,2),`utf-8`),renameSync(r,n)}function clearManagedRenameJournal(e){rmSync(managedRenameJournalPath(e),{force:!0})}async function withManagedRenameRecovery(e,t,n){writeManagedRenameJournal(e,t);let r=await n();return clearManagedRenameJournal(e),r}function recoverPendingManagedRename(e){let t=readManagedRenameJournal(e);if(!t)return{recovered:!1,journal:null,restoredDocNames:[]};let n=new Set,r=[];for(let i of t.snapshots)try{let t=safeContentPath(i.docName,e);mkdirSync(dirname(t),{recursive:!0}),writeFileSync(t,i.content,`utf-8`),n.add(i.docName)}catch(e){r.push(i.docName),console.warn(`[managed-rename] Failed to restore ${i.docName}:`,e)}if(r.length>0)throw console.warn(`[managed-rename] Recovery incomplete; keeping journal for retry (${r.join(`, `)})`),Error(`Managed rename recovery incomplete; failed to restore: ${r.join(`, `)}`);if(!n.has(t.destinationDocName)){let n=safeContentPath(t.destinationDocName,e);try{rmSync(n,{force:!0})}catch(e){throw existsSync(n)&&console.warn(`[managed-rename] Both source and destination files exist after partial recovery for ${t.destinationDocName}`),console.warn(`[managed-rename] Recovery incomplete; failed to clean destination ${t.destinationDocName}:`,e),Error(`Managed rename recovery incomplete; failed to clean destination: ${t.destinationDocName}`)}}return clearManagedRenameJournal(e),{recovered:!0,journal:t,restoredDocNames:[...n].sort((e,t)=>e.localeCompare(t))}}function matchFence$1(e){let t=/^\s{0,3}([`~]{3,})/.exec(e);if(!t)return null;let n=t[1],r=n[0];return r!=="`"&&r!==`~`?null:{char:r,length:n.length}}function isFenceClose$1(e,t){return RegExp(`^\\s{0,3}\\${t.char}{${t.length},}\\s*$`).test(e)}function leadingMarkdownPrefixLength$1(e){let t=/^\s{0,3}(?:#{1,6}\s+|>\s+|(?:[-+*]|\d+[.)])\s+)/.exec(e);return t?t[0].length:0}function readInlineCode$1(e,t){let n=0;for(;e[t+n]==="`";)n++;if(n===0)return null;let r=t+n;for(;r<e.length;){if(e[r]!=="`"){r++;continue}let t=0;for(;e[r+t]==="`";)t++;if(t===n)return{nextIndex:r+n};r+=t}return null}function readWikiLink$1(e,t){let n=/^\[\[([^\n#[\]|]+)(?:#([^\n[\]|]+))?(?:\|([^\n[\]]+))?\]\]/.exec(e.slice(t));if(!n)return null;let r=n[1]?.trim(),i=n[2]?.trim()||null,a=n[3]?.trim()||null;return r?{target:r,alias:a,anchor:i,nextIndex:t+n[0].length}:null}function readMarkdownLink$1(e,t){let n=/^\[([^\]\n]*)\]\((<[^>\n]+>|[^)\s\n]+)((?:\s+(?:"[^"\n]*"|'[^'\n]*'|\([^)\n]*\)))?)\)/.exec(e.slice(t));if(!n)return null;let r=n[2]??``;return{text:n[1]??``,hrefRaw:r,href:r.startsWith(`<`)&&r.endsWith(`>`)?r.slice(1,-1):r,titleSuffix:n[3]??``,nextIndex:t+n[0].length}}function splitLines(e){let t=e.split(/(\r\n|\r|\n)/),n=[];for(let e=0;e<t.length;e+=2)n.push({line:t[e]??``,ending:t[e+1]??``});return n}function rewriteWikiLinksInLine(e,t,n){let r=``,i=0,a=0,o=leadingMarkdownPrefixLength$1(e);for(o>0&&(r+=e.slice(0,o),a=o);a<e.length;){if(e[a]===`\\`&&a+1<e.length){r+=e.slice(a,a+2),a+=2;continue}if(e[a]==="`"){let t=readInlineCode$1(e,a);if(t){r+=e.slice(a,t.nextIndex),a=t.nextIndex;continue}}if(e[a]===`[`&&e[a+1]===`[`){let o=readWikiLink$1(e,a);if(o){o.target===t?(r+=`[[${n}${o.anchor?`#${o.anchor}`:``}${o.alias?`|${o.alias}`:``}]]`,i++):r+=e.slice(a,o.nextIndex),a=o.nextIndex;continue}}r+=e[a],a++}return{markdown:r,rewrites:i}}function buildRelativeMarkdownHref(e,t,n){let r=e.indexOf(`#`),i=r>=0?e.slice(r):``,a=r>=0?e.slice(0,r):e,o=a.indexOf(`?`),s=o>=0?a.slice(o):``,c=o>=0?a.slice(0,o):a,l=posix.dirname(t),u=posix.relative(l===`.`?``:l,n);return u||=posix.basename(n),c.endsWith(`.md`)&&(u+=`.md`),c.startsWith(`./`)&&!u.startsWith(`./`)&&!u.startsWith(`../`)&&(u=`./${u}`),`${u}${s}${i}`}function rewriteMarkdownLinksInLine(e,t,n,r){let i=``,a=0,o=0,s=leadingMarkdownPrefixLength$1(e);for(s>0&&(i+=e.slice(0,s),o=s);o<e.length;){if(e[o]===`\\`&&o+1<e.length){i+=e.slice(o,o+2),o+=2;continue}if(e[o]==="`"){let t=readInlineCode$1(e,o);if(t){i+=e.slice(o,t.nextIndex),o=t.nextIndex;continue}}if(e[o]===`[`&&e[o+1]===`[`){let t=readWikiLink$1(e,o);if(t){i+=e.slice(o,t.nextIndex),o=t.nextIndex;continue}}if(e[o]===`[`&&e[o-1]!==`!`){let s=readMarkdownLink$1(e,o);if(s){if(resolveInternalHref(s.href,t)?.docName===n){let e=buildRelativeMarkdownHref(s.href,t,r),n=s.hrefRaw.startsWith(`<`)&&s.hrefRaw.endsWith(`>`)?`<${e}>`:e;i+=`[${s.text}](${n}${s.titleSuffix})`,a++}else i+=e.slice(o,s.nextIndex);o=s.nextIndex;continue}}i+=e[o],o++}return{markdown:i,rewrites:a}}function rewriteWikiLinksForDocumentRename(e,t,n){let r=null,i=0;return{markdown:splitLines(e).map(({line:e,ending:a})=>{if(r)return isFenceClose$1(e,r)&&(r=null),`${e}${a}`;let o=matchFence$1(e);if(o)return r=o,`${e}${a}`;let s=rewriteWikiLinksInLine(e,t,n);return i+=s.rewrites,`${s.markdown}${a}`}).join(``),rewrites:i}}function rewriteMarkdownLinksForDocumentRename(e,t,n,r){let i=null,a=0;return{markdown:splitLines(e).map(({line:e,ending:o})=>{if(i)return isFenceClose$1(e,i)&&(i=null),`${e}${o}`;let s=matchFence$1(e);if(s)return i=s,`${e}${o}`;let c=rewriteMarkdownLinksInLine(e,t,n,r);return a+=c.rewrites,`${c.markdown}${o}`}).join(``),rewrites:a}}const STARTER_FOLDERS=[{path:`external-sources`,match:`external-sources/**`,title:`External Sources`,description:"Raw sources SAVED verbatim — not just cited. The actual fetched text of URLs, extracted text of PDFs, and copies of any referenced files live as .md files here, each with frontmatter carrying the original URL, access date, and any publisher / author metadata. Produced by `ingest`. Immutable after capture (update only to refresh a stale fetch). No analysis in these files — that belongs in `research/`. Downstream articles cite specific docs here by path so every claim is traceable to preserved evidence rather than a dead link.",tags:[`source`,`immutable`,`layer-ingest`]},{path:`research`,match:`research/**`,title:`Research`,description:"Provisional analysis synthesizing external sources. Produced by the `research` tool. Every factual claim cites a specific doc in `external-sources/` (or an inline URL if ingest was skipped) — no unsourced assertions. Each article has `status: provisional` and a `sources:` frontmatter list of cited paths. Promoted to `articles/` via `consolidate` once the team decides the findings are stable.",tags:[`research`,`provisional`,`layer-research`]},{path:`articles`,match:`articles/**`,title:`Articles`,description:"Canonical knowledge committed after a team decision. Produced by `consolidate`. Carries `status: canonical` plus a `supersedes:` chain tying back to the `research/` docs it replaces, which in turn cite `external-sources/` — the full evidence chain is traceable without leaving the repo. Source-of-truth for the domain; update only when a new decision supersedes it.",tags:[`article`,`canonical`,`layer-consolidate`]}],LOG_MD_TEMPLATE=`---
|
|
93
|
+
`)}\n`:``;return o.length>0?{kind:`conflicts`,newContent:l,conflicts:o}:{kind:`merged`,newContent:l,mergedBlocks:a.length}}function computeEditOps(e,t){let n=new Map,r=longestCommonSubsequence(e,t);for(let t=0;t<=e.length;t++)n.set(t,{action:`keep`,insertsBefore:[]});let i=new Set,a=new Set;for(let[e,t]of r)i.add(e),a.add(t);let o=-1;for(let s=0;s<e.length;s++)if(i.has(s)){let e=r.find(e=>e[0]===s)?.[1]??-1,i=[];for(let n=o+1;n<e;n++)a.has(n)||i.push(t[n]);let c=n.get(s);c&&(c.insertsBefore=i),o=e}else{let e=r.find(e=>e[0]>s),i=e?e[1]:t.length,c=[];for(let e=o+1;e<i;e++)a.has(e)||c.push(e);if(c.length>0){let e=c[0];a.add(e);let r=n.get(s);r&&(r.action=`modify`,r.newContent=t[e])}else{let e=n.get(s);e&&(e.action=`delete`)}}let s=[];for(let e=o+1;e<t.length;e++)a.has(e)||s.push(t[e]);let c=n.get(e.length);return c&&(c.insertsBefore=s),n}function longestCommonSubsequence(e,t){let n=e.length,r=t.length,i=Array.from({length:n+1},()=>Array(r+1).fill(0));for(let a=1;a<=n;a++)for(let n=1;n<=r;n++)e[a-1]===t[n-1]?i[a][n]=i[a-1][n-1]+1:i[a][n]=Math.max(i[a-1][n],i[a][n-1]);let a=[],o=n,s=r;for(;o>0&&s>0;)e[o-1]===t[s-1]?(a.push([o-1,s-1]),o--,s--):i[o-1][s]>=i[o][s-1]?o--:s--;return a.reverse()}const writeTracker=new Map,WRITE_TRACKER_TTL_MS=1e4;function registerWrite(e,t){let n=writeTracker.get(e)??[];n.push({hash:t,timestamp:Date.now()}),writeTracker.set(e,n)}function evictStaleTrackerEntries(){let e=Date.now();for(let[t,n]of writeTracker){let r=n.filter(t=>e-t.timestamp<=WRITE_TRACKER_TTL_MS);r.length===0?writeTracker.delete(t):r.length!==n.length&&writeTracker.set(t,r)}}function contentHash(e){return createHash(`sha256`).update(e).digest(`hex`)}function pathToDocName(e,t){return stripDocExtension(relative(t,e))}function extractDocExtension(e){let t=e.toLowerCase();return t.endsWith(`.mdx`)?`.mdx`:t.endsWith(`.md`)?`.md`:null}const lastKnownHash=new Map;function updateLastKnownHash(e,t){lastKnownHash.set(e,t)}function removeLastKnownHash(e){let t=lastKnownHash.get(e);return lastKnownHash.delete(e),t}async function classifyEvents(e,t,n,r){let i=[],a=[],o=[];for(let r of e)if(isSupportedDocFile(r.path)){if(n){let e=relative(t,r.path);if(n.isExcluded(e))continue}switch(r.type){case`delete`:i.push(r);break;case`create`:lastKnownHash.has(r.path)?o.push(r):a.push(r);break;case`update`:o.push(r);break}}let s=new Map,c=new Map;for(let e of a)try{s.set(e.path,await readFile(e.path,`utf-8`))}catch(t){t.code!==`ENOENT`&&console.warn(`[file-watcher] Failed to read ${e.path}:`,t)}for(let e of o)try{c.set(e.path,await readFile(e.path,`utf-8`))}catch(t){t.code!==`ENOENT`&&console.warn(`[file-watcher] Failed to read ${e.path}:`,t)}function l(e){let n=pathToDocName(e,t);if(!r)return n;let i=null;try{i=lstatSync(e)}catch(t){return t.code!==`ENOENT`&&console.warn(`[file-watcher] resolveDocName lstat failed for ${e}:`,t),r.has(n)&&r.delete(n),n}if(!i.isSymbolicLink())return r.has(n)&&r.delete(n),n;let a;try{a=realpathSync(e)}catch(t){let i=t.code;return i!==`ENOENT`&&i!==`ELOOP`&&console.warn(`[file-watcher] resolveDocName realpath failed for ${e}:`,t),r.delete(n),n}if(!isWithinContentDir(a,t))return r.delete(n),n;let o=pathToDocName(a,t);return o===n?n:(r.set(n,o),o)}let u=[],d=new Set,f=new Set;for(let e of i){let t=removeLastKnownHash(e.path);if(t)for(let n of a){if(d.has(n.path))continue;let r=s.get(n.path);if(!r)continue;let i=contentHash(r);if(i===t){d.add(n.path),f.add(e.path),updateLastKnownHash(n.path,i),u.push({kind:`rename`,oldPath:e.path,newPath:n.path,oldDocName:l(e.path),newDocName:l(n.path),content:r});break}}}for(let e of i)f.has(e.path)||(removeLastKnownHash(e.path),u.push({kind:`delete`,path:e.path,docName:l(e.path)}));for(let e of a){if(d.has(e.path))continue;let t=s.get(e.path);if(!t)continue;let n=contentHash(t);updateLastKnownHash(e.path,n),containsConflictMarkers(t)?u.push({kind:`conflict`,path:e.path,docName:l(e.path),content:t}):u.push({kind:`create`,path:e.path,docName:l(e.path),content:t})}for(let e of o){let t=c.get(e.path);if(!t)continue;let n=contentHash(t);updateLastKnownHash(e.path,n),containsConflictMarkers(t)?u.push({kind:`conflict`,path:e.path,docName:l(e.path),content:t}):u.push({kind:`update`,path:e.path,docName:l(e.path),content:t})}return u}function isSelfWrite(e,t){let n=writeTracker.get(e);if(!n)return!1;let r=n.findIndex(e=>e.hash===t);return r<0?!1:(n.splice(r,1),n.length===0&&writeTracker.delete(e),!0)}function seedLastKnownHashes(e,t,n,r,i,a){let o=a??new Set;try{let a=readdirSync(e,{withFileTypes:!0});for(let s of a){let a=join(e,s.name),c;try{c=lstatSync(a)}catch(e){e.code!==`ENOENT`&&console.warn(`[file-watcher] Failed to lstat ${a}, skipping:`,e);continue}if(c.isSymbolicLink()){let e;try{e=realpathSync(a)}catch(e){let t=e.code;t===`ENOENT`||t===`ELOOP`?console.warn(`[file-watcher] Broken/cyclic symlink at ${a}, skipping`):console.warn(`[file-watcher] Failed to resolve symlink ${a}:`,e);continue}if(!isWithinContentDir(e,t)){console.warn(`[file-watcher] Symlink escape: ${a} → ${e}, skipping`);continue}try{let c=statSync(e);if(o.has(c.ino)){if(c.isFile()&&isSupportedDocFile(s.name)){let n=pathToDocName(a,t),o=pathToDocName(e,t);i.set(n,o);let s=r.get(o);s&&!s.aliases.includes(n)&&s.aliases.push(n)}continue}if(o.add(c.ino),c.isDirectory()){if(n){let r=relative(t,e);if(n.isDirExcluded(r))continue}seedLastKnownHashes(e,t,n,r,i,o)}else if(c.isFile()&&isSupportedDocFile(s.name)){if(n){let r=relative(t,e);if(n.isExcluded(r))continue}let o=pathToDocName(a,t),s=pathToDocName(e,t);i.set(o,s);try{let t=contentHash(readFileSync(e,`utf-8`));lastKnownHash.set(e,t);let n=extractDocExtension(e);if(n){let e=registerDocExtension(s,n);if(e.shadowed&&(console.warn(`[file-watcher] docName "${s}" has both "${e.effective}" and "${e.shadowed}" on disk; "${e.effective}" wins (industry convention). Rename or delete one to disambiguate.`),!e.changed))continue}r.set(s,{size:c.size,modified:c.mtime.toISOString(),canonicalPath:e,inode:c.ino,aliases:[o]})}catch(t){t.code!==`ENOENT`&&console.warn(`[file-watcher] Failed to seed hash for ${e}:`,t)}}}catch(t){console.warn(`[file-watcher] Failed to stat symlink target ${e}:`,t)}}else if(c.isDirectory()){if(n){let e=relative(t,a);if(n.isDirExcluded(e))continue}seedLastKnownHashes(a,t,n,r,i,o)}else if(c.isFile()&&isSupportedDocFile(s.name)){if(o.has(c.ino))continue;if(o.add(c.ino),n){let e=relative(t,a);if(n.isExcluded(e))continue}try{let e=readFileSync(a,`utf-8`);lastKnownHash.set(a,contentHash(e));let n=pathToDocName(a,t),i=extractDocExtension(a);if(i){let e=registerDocExtension(n,i);if(e.shadowed&&(console.warn(`[file-watcher] docName "${n}" has both "${e.effective}" and "${e.shadowed}" on disk; "${e.effective}" wins (industry convention). Rename or delete one to disambiguate.`),!e.changed))continue}r.set(n,{size:c.size,modified:c.mtime.toISOString(),canonicalPath:a,inode:c.ino,aliases:[]})}catch(e){let t=e.code;t===`EACCES`?console.warn(`[file-watcher] Permission denied reading ${a}, file excluded from index`):t!==`ENOENT`&&console.warn(`[file-watcher] Failed to seed hash for ${a}:`,e)}}}}catch(t){t.code!==`ENOENT`&&console.warn(`[file-watcher] Failed to read directory ${e}:`,t)}}function updateFileIndex(e,t){if(!isSystemDoc(e.kind===`rename`?e.newDocName:e.docName))switch(e.kind){case`create`:case`update`:case`conflict`:{let n=e.docName,r=t.get(n),i=extractDocExtension(e.path);i&®isterDocExtension(n,i),t.set(n,{size:Buffer.byteLength(e.content,`utf-8`),modified:new Date().toISOString(),canonicalPath:r?.canonicalPath??e.path,inode:r?.inode??0,aliases:r?.aliases??[]});break}case`delete`:if(t.has(e.docName))t.delete(e.docName),forgetDocExtension(e.docName);else for(let[,n]of t){let t=n.aliases.indexOf(e.docName);if(t!==-1){n.aliases.splice(t,1);break}}break;case`rename`:{let n=t.get(e.oldDocName);t.delete(e.oldDocName),forgetDocExtension(e.oldDocName);let r=extractDocExtension(e.newPath);r&®isterDocExtension(e.newDocName,r),t.set(e.newDocName,{size:Buffer.byteLength(e.content,`utf-8`),modified:new Date().toISOString(),canonicalPath:n?.canonicalPath??e.newPath,inode:n?.inode??0,aliases:n?.aliases??[]});break}}}async function handleRawEvents(e,t,n,r,i,a){let o=e.filter(e=>isSupportedDocFile(e.path));if(o.length===0)return;let s=await classifyEvents(o,t,n,a);for(let e of s){let t=!1;if(e.kind!==`delete`&&e.kind!==`rename`){let n=contentHash(e.content),r=e.path;try{r=realpathSync(e.path)}catch(t){let n=t.code;n!==`ENOENT`&&console.warn(`[file-watcher] realpathSync failed for self-write check on ${e.path} (${n})`)}t=isSelfWrite(r,n)}else if(e.kind===`rename`){let n=contentHash(e.content),r=e.newPath;try{r=realpathSync(e.newPath)}catch(t){let n=t.code;n!==`ENOENT`&&console.warn(`[file-watcher] realpathSync failed for self-write check on ${e.newPath} (${n})`)}t=isSelfWrite(r,n)}if(updateFileIndex(e,r),n)switch(e.kind){case`create`:n.incrementMdDir(dirname(e.docName));break;case`delete`:n.decrementMdDir(dirname(e.docName));break;case`rename`:n.decrementMdDir(dirname(e.oldDocName)),n.incrementMdDir(dirname(e.newDocName));break}if(t){getLogger(`file-watcher`).debug({kind:e.kind,path:e.kind===`rename`?e.newPath:e.path,self:!0},`[file-watcher] Skipped self-write: ${e.kind}`),_fileWatcherEventsCounter().add(1,{"disk.kind":e.kind,self:!0});continue}getLogger(`file-watcher`).debug({kind:e.kind,path:e.kind===`rename`?e.newPath:e.path},`[file-watcher] Dispatching: ${e.kind}`),_fileWatcherEventsCounter().add(1,{"disk.kind":e.kind,self:!1});let a=e.kind===`rename`?e.newPath:e.path;await withSpan(`file_watcher.process_event`,{attributes:{"disk.kind":e.kind,"disk.path":normalizeFsPath(a),"disk.path.role":classifyFsPath(a)}},async()=>i(e))}}let _fwEventsCounterCache=null;function _fileWatcherEventsCounter(){return _fwEventsCounterCache||=getMeter().createCounter(`ok.file_watcher.events`,{description:`Number of file-watcher events classified by kind`}),_fwEventsCounterCache}async function startParcelWatcher(e,t,n,r,i){let a;try{a=await import(`@parcel/watcher`)}catch(e){return console.warn(`[file-watcher] @parcel/watcher import failed:`,e instanceof Error?e.message:e),null}try{let o=t?{ignore:t.getWatcherIgnoreGlobs()}:void 0;return await a.subscribe(e,async(a,o)=>{if(a){console.error(`[file-watcher]`,a);return}try{await handleRawEvents(o.map(e=>({type:e.type,path:e.path})),e,t,n,r,i)}catch(e){console.error(`[file-watcher] parcel batch error:`,e)}},o)}catch(e){return console.warn(`[file-watcher] @parcel/watcher subscribe failed, falling back to chokidar:`,e),null}}async function startChokidarWatcher(e,t,n,r,i){let{watch:a}=await import(`./chokidar-DhL3M6Oj.mjs`);console.warn(`[file-watcher] @parcel/watcher unavailable, using chokidar fallback`);let o=a(e,{ignoreInitial:!0,ignored:t?(n,r)=>{let i=relative(e,n);return i===``||i===`.`?!1:r?.isDirectory()?t.isDirExcluded(i):t.isExcluded(i)}:void 0});o.on(`error`,e=>console.error(`[file-watcher] chokidar error:`,e));let s=50,c=[],l=null;function u(a,o){c.push({type:a,path:o}),l||=setTimeout(()=>{let a=c;c=[],l=null,handleRawEvents(a,e,t,n,r,i).catch(e=>console.error(`[file-watcher] chokidar batch error:`,e))},50)}return o.on(`add`,e=>u(`create`,e)),o.on(`change`,e=>u(`update`,e)),o.on(`unlink`,e=>u(`delete`,e)),{unsubscribe:()=>(l&&(clearTimeout(l),l=null,c=[]),o.close())}}async function startWatcher(e,t,n){let r;try{r=realpathSync(e)}catch{r=e}let i=new Map,a=new Map;seedLastKnownHashes(r,r,n,i,a);let o=setInterval(evictStaleTrackerEntries,WRITE_TRACKER_TTL_MS),s,c;try{let e=await startParcelWatcher(r,n,i,t,a);e?(s=e,c=`parcel`):(s=await startChokidarWatcher(r,n,i,t,a),c=`chokidar`)}catch(e){throw clearInterval(o),e}let l=s.unsubscribe.bind(s);return console.log(`[file-watcher] Watching ${r} for external .md changes (backend: ${c})`),{async unsubscribe(){return clearInterval(o),writeTracker.clear(),lastKnownHash.clear(),l()},getFileIndex(){return i},getAliasMap(){return a}}}const defaultGitConfigReader=(e,t,n)=>{let r=spawnSync(`git`,[`config`,n===`local`?`--local`:`--global`,t],{cwd:e,encoding:`utf-8`,timeout:5e3});return r.status!==0||!r.stdout?null:r.stdout.trim()||null};async function resolveGitIdentity(e,t,n,r=defaultGitConfigReader){let i=r(e,`user.name`,`local`),a=r(e,`user.email`,`local`);if(i&&a)return{name:i,email:a};let o=r(e,`user.name`,`global`),s=r(e,`user.email`,`global`);if(o&&s)return{name:o,email:s};if(t&&n){let e=await t.get(n);if(e){let t=e.name??e.login,n=e.email??`${e.login}@users.noreply.github.com`;if(t)return{name:t,email:n}}}return null}function writeGitIdentity(e,t,n){let r=(t,n)=>{let r=spawnSync(`git`,[`config`,`--local`,t,n],{cwd:e,encoding:`utf-8`,timeout:5e3});if(r.status!==0){let e=r.stderr?.trim()??``;throw Error(`git config --local ${t} failed: ${e}`)}};r(`user.name`,t),r(`user.email`,n)}const MAX_LEN=128;function sanitizeGitIdentity(e){return e.replace(/[<>\r\n]/g,``).trim().slice(0,MAX_LEN)}const ALLOWED_URL_PATTERNS=[/^https?:\/\//i,/^ssh:\/\//i,/^git:\/\//i,/^git@[^:]+:/],BLOCKED_URL_PATTERNS=[/^file:\/\//i,/^javascript:/i,/^ext::/i,/^data:/i,/^vbscript:/i];function isAllowedGitUrl(e){return!e||typeof e!=`string`||BLOCKED_URL_PATTERNS.some(t=>t.test(e))?!1:ALLOWED_URL_PATTERNS.some(t=>t.test(e))}function expandTilde(e){return e===`~`?homedir():e.startsWith(`~/`)?join(homedir(),e.slice(2)):e}function isSafeLocalPath(e){if(!e||typeof e!=`string`||e.includes(`\0`))return!1;let t=homedir(),n=resolve(expandTilde(e));return n===t||n.startsWith(`${t}/`)}function isLoopbackRequest(e){let t=e.socket.remoteAddress;return t===`127.0.0.1`||t===`::1`||t===`::ffff:127.0.0.1`}function hasValidLocalOpOrigin(e){let t=e.headers.origin;if(!t)return!0;try{let{hostname:e}=new URL(t);return e===`127.0.0.1`||e===`localhost`||e===`[::1]`||e===`::1`}catch{return!1}}function checkLocalOpSecurity(e,t,n){return isLoopbackRequest(e)?hasValidLocalOpOrigin(e)?!0:(n(t,403,{ok:!1,error:`Forbidden: invalid origin for local-op endpoint`}),!1):(n(t,403,{ok:!1,error:`Forbidden: local-op endpoints require loopback connection`}),!1)}function createConcurrencyGuard(){let e=new Set;return{tryAcquire(t){return e.has(t)?!1:(e.add(t),!0)},release(t){e.delete(t)}}}function isLoopbackAddress(e){return e?!!(e===`::1`||e.startsWith(`::ffff:127.`)||e.startsWith(`127.`)):!1}function isAllowedWorkspaceHostHeader(e){if(!e)return!1;if(e.startsWith(`[`)){let t=e.indexOf(`]`);if(t<0)return!1;let n=e.slice(1,t),r=e.slice(t+1);return r!==``&&!/^:\d+$/.test(r)?!1:n===`::1`}let t=e.lastIndexOf(`:`),n=t>=0?e.slice(0,t):e,r=t>=0?e.slice(t+1):null;return r!==null&&!/^\d+$/.test(r)?!1:!!(n===`localhost`||/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(n))}const MANAGED_RENAME_JOURNAL_VERSION=1,MANAGED_RENAME_JOURNAL_FILENAME=`managed-rename.json`;function journalDir(e){return resolve(e,`.open-knowledge`)}function managedRenameJournalPath(e){return resolve(journalDir(e),MANAGED_RENAME_JOURNAL_FILENAME)}function createManagedRenameRecoveryJournal(e){return{version:MANAGED_RENAME_JOURNAL_VERSION,sourceDocName:e.sourceDocName,destinationDocName:e.destinationDocName,createdAt:e.createdAt??new Date().toISOString(),snapshots:e.snapshots}}function isManagedRenameSnapshot(e){if(!e||typeof e!=`object`)return!1;let t=e;return typeof t.docName==`string`&&typeof t.content==`string`}function parseManagedRenameRecoveryJournal(e){if(!e||typeof e!=`object`)throw Error(`Managed rename journal must be an object`);let t=e;if(t.version!==MANAGED_RENAME_JOURNAL_VERSION)throw Error(`Unsupported managed rename journal version: ${String(t.version)}`);if(typeof t.sourceDocName!=`string`||t.sourceDocName.length===0)throw Error(`Managed rename journal is missing sourceDocName`);if(typeof t.destinationDocName!=`string`||t.destinationDocName.length===0)throw Error(`Managed rename journal is missing destinationDocName`);if(typeof t.createdAt!=`string`||t.createdAt.length===0)throw Error(`Managed rename journal is missing createdAt`);if(!Array.isArray(t.snapshots)||t.snapshots.length===0)throw Error(`Managed rename journal is missing snapshots`);if(!t.snapshots.every(isManagedRenameSnapshot))throw Error(`Managed rename journal has invalid snapshots`);if(!t.snapshots.some(e=>e.docName===t.sourceDocName))throw Error(`Managed rename journal must include the source document snapshot`);return{version:t.version,sourceDocName:t.sourceDocName,destinationDocName:t.destinationDocName,createdAt:t.createdAt,snapshots:t.snapshots}}function readManagedRenameJournal(e){let t=managedRenameJournalPath(e);if(!existsSync(t))return null;let n=readFileSync(t,`utf-8`);try{return parseManagedRenameRecoveryJournal(JSON.parse(n))}catch(e){throw Error(`Managed rename journal at ${t} is corrupt: ${e instanceof Error?e.message:String(e)}`)}}function writeManagedRenameJournal(e,t){let n=managedRenameJournalPath(e);mkdirSync(dirname(n),{recursive:!0});let r=`${n}.tmp`;writeFileSync(r,JSON.stringify(t,null,2),`utf-8`),renameSync(r,n)}function clearManagedRenameJournal(e){rmSync(managedRenameJournalPath(e),{force:!0})}async function withManagedRenameRecovery(e,t,n){writeManagedRenameJournal(e,t);let r=await n();return clearManagedRenameJournal(e),r}function recoverPendingManagedRename(e){let t=readManagedRenameJournal(e);if(!t)return{recovered:!1,journal:null,restoredDocNames:[]};let n=new Set,r=[];for(let i of t.snapshots)try{let t=safeContentPath(i.docName,e);mkdirSync(dirname(t),{recursive:!0}),writeFileSync(t,i.content,`utf-8`),n.add(i.docName)}catch(e){r.push(i.docName),console.warn(`[managed-rename] Failed to restore ${i.docName}:`,e)}if(r.length>0)throw console.warn(`[managed-rename] Recovery incomplete; keeping journal for retry (${r.join(`, `)})`),Error(`Managed rename recovery incomplete; failed to restore: ${r.join(`, `)}`);if(!n.has(t.destinationDocName)){let n=safeContentPath(t.destinationDocName,e);try{rmSync(n,{force:!0})}catch(e){throw existsSync(n)&&console.warn(`[managed-rename] Both source and destination files exist after partial recovery for ${t.destinationDocName}`),console.warn(`[managed-rename] Recovery incomplete; failed to clean destination ${t.destinationDocName}:`,e),Error(`Managed rename recovery incomplete; failed to clean destination: ${t.destinationDocName}`)}}return clearManagedRenameJournal(e),{recovered:!0,journal:t,restoredDocNames:[...n].sort((e,t)=>e.localeCompare(t))}}function matchFence$1(e){let t=/^\s{0,3}([`~]{3,})/.exec(e);if(!t)return null;let n=t[1],r=n[0];return r!=="`"&&r!==`~`?null:{char:r,length:n.length}}function isFenceClose$1(e,t){return RegExp(`^\\s{0,3}\\${t.char}{${t.length},}\\s*$`).test(e)}function leadingMarkdownPrefixLength$1(e){let t=/^\s{0,3}(?:#{1,6}\s+|>\s+|(?:[-+*]|\d+[.)])\s+)/.exec(e);return t?t[0].length:0}function readInlineCode$1(e,t){let n=0;for(;e[t+n]==="`";)n++;if(n===0)return null;let r=t+n;for(;r<e.length;){if(e[r]!=="`"){r++;continue}let t=0;for(;e[r+t]==="`";)t++;if(t===n)return{nextIndex:r+n};r+=t}return null}function readWikiLink$1(e,t){let n=/^\[\[([^\n#[\]|]+)(?:#([^\n[\]|]+))?(?:\|([^\n[\]]+))?\]\]/.exec(e.slice(t));if(!n)return null;let r=n[1]?.trim(),i=n[2]?.trim()||null,a=n[3]?.trim()||null;return r?{target:r,alias:a,anchor:i,nextIndex:t+n[0].length}:null}function readMarkdownLink$1(e,t){let n=/^\[([^\]\n]*)\]\((<[^>\n]+>|[^)\s\n]+)((?:\s+(?:"[^"\n]*"|'[^'\n]*'|\([^)\n]*\)))?)\)/.exec(e.slice(t));if(!n)return null;let r=n[2]??``;return{text:n[1]??``,hrefRaw:r,href:r.startsWith(`<`)&&r.endsWith(`>`)?r.slice(1,-1):r,titleSuffix:n[3]??``,nextIndex:t+n[0].length}}function splitLines(e){let t=e.split(/(\r\n|\r|\n)/),n=[];for(let e=0;e<t.length;e+=2)n.push({line:t[e]??``,ending:t[e+1]??``});return n}function rewriteWikiLinksInLine(e,t,n){let r=``,i=0,a=0,o=leadingMarkdownPrefixLength$1(e);for(o>0&&(r+=e.slice(0,o),a=o);a<e.length;){if(e[a]===`\\`&&a+1<e.length){r+=e.slice(a,a+2),a+=2;continue}if(e[a]==="`"){let t=readInlineCode$1(e,a);if(t){r+=e.slice(a,t.nextIndex),a=t.nextIndex;continue}}if(e[a]===`[`&&e[a+1]===`[`){let o=readWikiLink$1(e,a);if(o){o.target===t?(r+=`[[${n}${o.anchor?`#${o.anchor}`:``}${o.alias?`|${o.alias}`:``}]]`,i++):r+=e.slice(a,o.nextIndex),a=o.nextIndex;continue}}r+=e[a],a++}return{markdown:r,rewrites:i}}function buildRelativeMarkdownHref(e,t,n){let r=e.indexOf(`#`),i=r>=0?e.slice(r):``,a=r>=0?e.slice(0,r):e,o=a.indexOf(`?`),s=o>=0?a.slice(o):``,c=o>=0?a.slice(0,o):a,l=posix.dirname(t),u=posix.relative(l===`.`?``:l,n);return u||=posix.basename(n),c.endsWith(`.md`)&&(u+=`.md`),c.startsWith(`./`)&&!u.startsWith(`./`)&&!u.startsWith(`../`)&&(u=`./${u}`),`${u}${s}${i}`}function rewriteMarkdownLinksInLine(e,t,n,r){let i=``,a=0,o=0,s=leadingMarkdownPrefixLength$1(e);for(s>0&&(i+=e.slice(0,s),o=s);o<e.length;){if(e[o]===`\\`&&o+1<e.length){i+=e.slice(o,o+2),o+=2;continue}if(e[o]==="`"){let t=readInlineCode$1(e,o);if(t){i+=e.slice(o,t.nextIndex),o=t.nextIndex;continue}}if(e[o]===`[`&&e[o+1]===`[`){let t=readWikiLink$1(e,o);if(t){i+=e.slice(o,t.nextIndex),o=t.nextIndex;continue}}if(e[o]===`[`&&e[o-1]!==`!`){let s=readMarkdownLink$1(e,o);if(s){if(resolveInternalHref(s.href,t)?.docName===n){let e=buildRelativeMarkdownHref(s.href,t,r),n=s.hrefRaw.startsWith(`<`)&&s.hrefRaw.endsWith(`>`)?`<${e}>`:e;i+=`[${s.text}](${n}${s.titleSuffix})`,a++}else i+=e.slice(o,s.nextIndex);o=s.nextIndex;continue}}i+=e[o],o++}return{markdown:i,rewrites:a}}function rewriteWikiLinksForDocumentRename(e,t,n){let r=null,i=0;return{markdown:splitLines(e).map(({line:e,ending:a})=>{if(r)return isFenceClose$1(e,r)&&(r=null),`${e}${a}`;let o=matchFence$1(e);if(o)return r=o,`${e}${a}`;let s=rewriteWikiLinksInLine(e,t,n);return i+=s.rewrites,`${s.markdown}${a}`}).join(``),rewrites:i}}function rewriteMarkdownLinksForDocumentRename(e,t,n,r){let i=null,a=0;return{markdown:splitLines(e).map(({line:e,ending:o})=>{if(i)return isFenceClose$1(e,i)&&(i=null),`${e}${o}`;let s=matchFence$1(e);if(s)return i=s,`${e}${o}`;let c=rewriteMarkdownLinksInLine(e,t,n,r);return a+=c.rewrites,`${c.markdown}${o}`}).join(``),rewrites:a}}const STARTER_FOLDERS=[{path:`external-sources`,match:`external-sources/**`,title:`External Sources`,description:"Raw sources SAVED verbatim — not just cited. The actual fetched text of URLs, extracted text of PDFs, and copies of any referenced files live as .md files here, each with frontmatter carrying the original URL, access date, and any publisher / author metadata. Produced by `ingest` — applies whether the user shared the URL OR the agent fetched it itself to ground a knowledge-base claim. The KB is closed-loop: downstream docs cite local paths in this folder, never bare web URLs. Immutable after capture (update only to refresh a stale fetch). No analysis in these files — that belongs in `research/`. Downstream articles cite specific docs here by path so every claim is traceable to preserved evidence rather than a dead link.",tags:[`source`,`immutable`,`layer-ingest`]},{path:`research`,match:`research/**`,title:`Research`,description:"Provisional analysis synthesizing external sources. Produced by the `research` tool. Every factual claim cites a specific doc in `external-sources/` (or an inline URL if ingest was skipped) — no unsourced assertions. Each article has `status: provisional` and a `sources:` frontmatter list of cited paths. Promoted to `articles/` via `consolidate` once the team decides the findings are stable.",tags:[`research`,`provisional`,`layer-research`]},{path:`articles`,match:`articles/**`,title:`Articles`,description:"Canonical knowledge committed after a team decision. Produced by `consolidate`. Carries `status: canonical` plus a `supersedes:` chain tying back to the `research/` docs it replaces, which in turn cite `external-sources/` — the full evidence chain is traceable without leaving the repo. Source-of-truth for the domain; update only when a new decision supersedes it.",tags:[`article`,`canonical`,`layer-consolidate`]}],LOG_MD_TEMPLATE=`---
|
|
94
94
|
title: Work Log
|
|
95
|
-
description:
|
|
95
|
+
description: Append-only audit trail. After each turn that creates, edits, or restructures content in the knowledge base, append one dated entry here (one per turn, not per file). Silent edits break the audit trail.
|
|
96
96
|
---
|
|
97
97
|
|
|
98
98
|
# Work Log
|
|
99
99
|
|
|
100
|
-
Append-only
|
|
100
|
+
Append-only audit trail. **Append a dated entry after any turn that creates, edits, or restructures content in the knowledge base** — one entry per turn, not per file. Silent edits break the chain that makes knowledge-base changes auditable.
|
|
101
|
+
|
|
102
|
+
What to log:
|
|
103
|
+
|
|
104
|
+
- \`ingest\` runs (new external sources captured)
|
|
105
|
+
- \`research\` / \`consolidate\` runs (provisional or canonical articles produced)
|
|
106
|
+
- Direct \`write_document\` / \`edit_document\` / renames / deletions outside the three workflow tools
|
|
107
|
+
- Folder restructures (\`ok seed\`, manual reorganization)
|
|
108
|
+
- \`.open-knowledge/config.yml\` changes
|
|
109
|
+
|
|
110
|
+
**Reference docs as markdown links, not bare paths.** Every doc you touched should appear as \`[path/to/doc](./path/to/doc.md)\` so the log shows up in \`get_backlinks\` for those docs. A bare path string (\`Files touched: foo/bar.md\`) does not register in the doc graph — the audit trail compounds only when the log is a real linker.
|
|
101
111
|
|
|
102
112
|
<!-- Example entry shape:
|
|
103
113
|
|
|
104
114
|
## YYYY-MM-DD — <short title>
|
|
105
115
|
|
|
106
116
|
- <what was done>
|
|
117
|
+
- Files touched: [path/to/doc-a](./path/to/doc-a.md), [path/to/doc-b](./path/to/doc-b.md)
|
|
118
|
+
- Sources ingested: [source-slug](./external-sources/source-slug.md)
|
|
107
119
|
- Open follow-ups: <topic-1>, <topic-2>
|
|
108
120
|
|
|
109
121
|
-->
|
|
@@ -143,4 +155,4 @@ Append-only record of ingests, research, consolidations, and maintenance. Each e
|
|
|
143
155
|
---`,4);if(!e.startsWith(`---
|
|
144
156
|
`)||t<0)return;let n=e.slice(4,t),r=n.search(/^metadata:/m);if(r<0)return;let i=n.slice(r).split(`
|
|
145
157
|
`).slice(1);for(let e of i){if(/^[^\s]/.test(e))break;let t=e.match(/^\s+version:\s*["']?([^"'\s]+)["']?$/);if(t)return t[1]}}async function validateSkillZip(e,t,n={}){let r=statSync(e).size;if(r>MAX_ZIP_BYTES)throw Error(`Built ${e} is ${r} bytes, exceeds ${MAX_ZIP_BYTES}-byte ceiling`);let i=await sha256OfFile(e),a=await readFile(join(resolveBundledSkillDir(),`SKILL.md`),`utf-8`);if(!/^name:\s+open-knowledge$/m.test(a.slice(0,1e3)))throw Error("SKILL.md frontmatter `name:` does not match 'open-knowledge'. Check packages/server/assets/skills/open-knowledge/SKILL.md frontmatter.");let o=extractMetadataVersion(a);if(!n.skipVersionCheck){if(!o)throw Error("SKILL.md metadata.version missing. Add it to packages/server/assets/skills/open-knowledge/SKILL.md or run `bash scripts/sync-skill-version.sh`.");if(o!==t)throw Error(`SKILL.md metadata.version (${o}) does not match CLI version (${t}). Run \`bash scripts/sync-skill-version.sh\` after bumping package versions.`)}return{size:r,sha256:i,skillVersion:o}}async function buildSkillZip(e={}){let t=e.sourceDir??resolveBundledSkillDir(),n=e.outputPath??join(process.cwd(),`openknowledge.skill`),r=await readCliVersion();await zipDirectory(t,n);let{size:i,sha256:a,skillVersion:o}=await validateSkillZip(n,r,{skipVersionCheck:e.skipVersionCheck});return{outputPath:n,size:i,sha256:a,cliVersion:r,skillVersion:o}}function detectClaudeDesktopPresence(e={}){let t=e.home??homedir(),n=e.platformName??process.platform,r=e.env??process.env;return n===`darwin`?existsSync(join(t,`Library`,`Application Support`,`Claude`)):n===`win32`?existsSync(join(r.APPDATA??join(t,`AppData`,`Roaming`),`Claude`)):!1}const execFileAsync=promisify(execFile);var ProjectGitInitError=class extends Error{stderr;constructor(e,t=``,n){super(e,n),this.name=`ProjectGitInitError`,this.stderr=t}};async function ensureProjectGit(e){let t=resolve(e),n=resolve(t,`.git`);if(existsSync(n))return{didInit:!1};let r=``;try{r=(await execFileAsync(`git`,[`init`,`--initial-branch=main`,t])).stderr??``}catch(e){let n=typeof e==`object`&&e&&`stderr`in e?String(e.stderr??``):``;throw new ProjectGitInitError(`git init failed at ${t}: ${e instanceof Error?e.message:String(e)}`,n,{cause:e})}if(!existsSync(resolve(n,`HEAD`)))throw new ProjectGitInitError(`git init reported success but ${n}/HEAD is missing (partial init detected)`,r);return console.log(`[project-git] initialized .git/ at ${t} (branch: main)`),{didInit:!0}}const SIDECAR_FILENAME=`skill-installed-version`,SKILLS_CLI_SPEC=`skills@~1.5.0`,DEFAULT_TIMEOUT_MS=6e4,VERSION_RE=/^\d+\.\d+\.\d+(?:[-+][\w.-]+)?$/;async function readServerPackageVersion(){let e=await readFile(fileURLToPath(new URL(`../package.json`,import.meta.url)),`utf-8`),t=JSON.parse(e);if(typeof t.version!=`string`||t.version.length===0)throw Error(`@inkeep/open-knowledge-server/package.json missing version field`);return t.version}function sidecarPath(e){return join(e,`.open-knowledge`,SIDECAR_FILENAME)}async function readSidecarVersion(e){try{let t=(await readFile(sidecarPath(e),`utf-8`)).trim();return t.length===0||!VERSION_RE.test(t)?null:t}catch(e){if(e.code===`ENOENT`)return null;throw e}}async function writeSidecarVersion(e,t){let n=sidecarPath(e);await mkdir(dirname(n),{recursive:!0}),await writeFile(n,`${t}\n`,`utf-8`)}function runSpawn(e,t,n,r,i){return new Promise(a=>{let o;try{o=e(t,n,{env:r,stdio:[`ignore`,`pipe`,`pipe`]})}catch(e){a({kind:`spawn-error`,stderr:``,error:e});return}let s=``,c=!1,l=e=>{c||(c=!0,clearTimeout(u),a(e))};o.stderr?.on(`data`,e=>{s+=typeof e==`string`?e:e.toString(`utf-8`)}),o.on(`error`,e=>{l({kind:`spawn-error`,stderr:s,error:e})}),o.on(`exit`,e=>{l(e===0?{kind:`ok`,exitCode:e,stderr:s}:{kind:`nonzero`,exitCode:e,stderr:s})});let u=setTimeout(()=>{try{o.kill(`SIGTERM`)}catch{}l({kind:`timeout`,stderr:s})},i)})}async function installUserSkill(e={}){let t=e.home??homedir(),n=e.logger??{warn:(e,t)=>console.warn(t,e),info:(e,t)=>console.info(t,e)},r=e.spawn??spawn,i=e.timeoutMs??DEFAULT_TIMEOUT_MS,a;try{a=await readServerPackageVersion()}catch(e){return n.warn({event:`skill-install.failed`,reason:`version-read-failed`,error:String(e)},`Skill install aborted — could not read @inkeep/open-knowledge-server version.`),`failed`}let o=await readSidecarVersion(t).catch(()=>null);if(o!==null&&o===a)return n.info?.({event:`skill-install.skip-current`,version:a},`Open Knowledge skill already installed at current version; skipping.`),`skip-current`;let s;try{s=resolveBundledSkillDir()}catch(e){return n.warn({event:`skill-install.failed`,reason:`bundled-asset-missing`,error:String(e)},`Skill install aborted — bundled SKILL.md asset not found.`),`failed`}let c=await runSpawn(r,`npx`,[`-y`,SKILLS_CLI_SPEC,`add`,s,`--agent`,`*`,`-g`,`-y`,`--copy`],{...process.env,HOME:t},i);if(c.kind===`ok`){try{await writeSidecarVersion(t,a)}catch(e){return n.warn({event:`skill-install.failed`,reason:`sidecar-write-failed`,error:String(e)},`Skill install succeeded but sidecar write failed.`),`failed`}return n.info?.({event:`skill-install.installed`,version:a},`Open Knowledge skill installed to detected agent hosts.`),`installed`}return c.kind===`timeout`?(n.warn({event:`skill-install.failed`,reason:`timeout`,timeoutMs:i,stderr:c.stderr},`Skill install subprocess timed out. Run manually: npx ${SKILLS_CLI_SPEC} add ${s} --agent '*' -g -y --copy`),`failed`):c.kind===`spawn-error`?(n.warn({event:`skill-install.failed`,reason:`spawn-error`,error:String(c.error),stderr:c.stderr},`Skill install failed — \`npx\` unavailable or spawn errored. Run manually: npx ${SKILLS_CLI_SPEC} add ${s} --agent '*' -g -y --copy`),`failed`):(n.warn({event:`skill-install.failed`,reason:`nonzero-exit`,exitCode:c.exitCode,stderr:c.stderr},`Skill install subprocess exited non-zero. Run manually: npx ${SKILLS_CLI_SPEC} add ${s} --agent '*' -g -y --copy`),`failed`)}export{getTracer as $,bootServer as A,saveInMemoryCheckpoint as At,createApiExtension as B,updateUiLockPort as Bt,SeedRootDirError as C,removeLastKnownHash as Ct,applyExternalChange as D,safeContentPath as Dt,applyAgentMarkdownWrite as E,rewriteWikiLinksForDocumentRename as Et,commitWip as F,splitMarkdownBlocks as Ft,createServerObserverExtension as G,writeTracker as Gt,createLiveDerivedIndexExtension as H,validateSkillZip as Ht,commitWipFromTree as I,startWatcher as It,evictStaleTrackerEntries as J,parseWriterId as Jt,detectClaudeDesktopPresence as K,getShadowRepoPath as Kt,containsConflictMarkers as L,starterFolderRule as Lt,buildWipTree as M,setActiveSpanAttributes as Mt,classifyEvents as N,shadowGit as Nt,applySeed as O,safeSubdir as Ot,commitUpstreamImport as P,shutdownTelemetry as Pt,getMetrics as Q,contentHash as R,toBroadcasterKey as Rt,SeedPrerequisiteError as S,releaseUiLock as St,acquireUiLock as T,rewriteMarkdownLinksForDocumentRename as Tt,createPersistenceExtension as U,withSpan as Ut,createContentFilter as V,validateAgentId as Vt,createServer$1 as W,withSpanSync as Wt,getLogger as X,extractWikiLinksFromMarkdown as Y,readContributors as Yt,getMeter as Z,ProjectGitInitError as _,readBranchFromHead as _t,BacklinkIndex as a,installUserSkill as at,SERVICE_WRITER as b,recordContributor as bt,FILE_SYSTEM_WRITER as c,isSystemDoc as ct,HocuspocusAuthRejection as d,loadPrincipal as dt,handleCollabSocketError as et,HocuspocusAuthTokenSchema as f,loggerFactory as ft,PinoLogger as g,planSeed as gt,OBSERVER_SYNC_ORIGIN as h,pathToDocName as ht,AgentSessionManager as i,initTelemetry as it,buildSkillZip as j,saveVersion as jt,attachIdleShutdown as k,safetyCheckpoint as kt,FILE_WATCHER_ORIGIN as l,lastKnownHash as lt,MANAGED_RENAME_ORIGIN as m,parseKeepaliveConnectionId as mt,AgentFocusBroadcaster as n,incrementServerObserverFire as nt,CC1Broadcaster as o,isPairedWriteOrigin as ot,LOG_MD_TEMPLATE as p,parseHocuspocusAuthToken as pt,ensureProjectGit as q,getWipRefPattern as qt,AgentPresenceBroadcaster as r,initShadowRepo as rt,CONFLICT_MARKER_RE as s,isSelfWrite as st,AGENT_ID_RE as t,incrementCollabSocketFilteredError as tt,GIT_UPSTREAM_WRITER as u,listRescueCheckpoints as ut,ROLLBACK_ORIGIN as v,readUiLock as vt,UiLockCollisionError as w,resolveBundledSkillDir as wt,STARTER_FOLDERS as x,registerWrite as xt,SEED_CONFIG_FILENAME as y,reconcile as yt,contributorCount as z,updateLastKnownHash as zt};
|
|
146
|
-
//# sourceMappingURL=dist-
|
|
158
|
+
//# sourceMappingURL=dist-mmLEboji.mjs.map
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c as e,i as t,l as n,o as r,s as i,t as a}from"./init-
|
|
1
|
+
import{c as e,i as t,l as n,o as r,s as i,t as a}from"./init-Bc8AaeuH.mjs";import{n as o,r as s}from"./loader-CkLCJZ_a.mjs";import"./src-B0_BgXoG.mjs";export{e as ALL_EDITOR_IDS,s as ConfigSchema,n as EDITOR_TARGETS,a as detectInstalledEditors,o as loadConfig,t as readExistingMcpEntry,r as writeEditorMcpConfig,i as writeUserMcpConfigs};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as e,t}from"./init-aygOL5mI.mjs";export{t as ensureOkGitignoredAtRoot,e as initContent};
|