@inkeep/open-knowledge 0.0.0-dev-20260423025054 → 0.0.0-dev-20260423171826

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/cli.mjs CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import{a as e,i as t}from"./constants-CctQOxTv.mjs";import{O as n,c as r,k as i}from"./src-CQw-HNNM.mjs";import{n as a,t as o}from"./paths-DjCE6HsL.mjs";import{ct as s,lt as c,ot as l,p as u,st as d,tt as f}from"./src-y0OBZgkE.mjs";import{c as p,f as m,r as h}from"./server-lock-B4frNnOB.mjs";import{i as g}from"./init-CXgpPYnX.mjs";import{t as _}from"./is-object-DaHwJLOe.mjs";import{r as v}from"./init-B3ycweHs.mjs";import{i as y,n as ee,t as b}from"./loader-DC05jP_i.mjs";import{o as x,s as S}from"./start-B5aAHuCo.mjs";import"./src-C9R5iE4n.mjs";import{Command as C}from"commander";import{appendFileSync as w,closeSync as T,existsSync as E,mkdirSync as D,openSync as te,readFileSync as O,readdirSync as ne,realpathSync as re,statSync as ie,unlinkSync as ae,writeFileSync as oe}from"node:fs";import{homedir as se,hostname as ce}from"node:os";import{basename as le,dirname as ue,isAbsolute as de,join as fe,relative as pe,resolve as k}from"node:path";import{parse as me,stringify as he}from"yaml";import{createOAuthDeviceAuth as ge}from"@octokit/auth-oauth-device";import _e from"@inquirer/password";import{Octokit as ve}from"@octokit/rest";import{fileURLToPath as ye}from"node:url";import{randomUUID as be}from"node:crypto";import{execFileSync as xe,spawn as Se}from"node:child_process";import A from"simple-git";import{readFile as Ce,readdir as we,stat as Te}from"node:fs/promises";import{createServer as Ee,request as De}from"node:http";import Oe from"picomatch";import{z as j}from"zod";import{McpServer as ke}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as Ae}from"@modelcontextprotocol/sdk/server/stdio.js";import{RootsListChangedNotificationSchema as je}from"@modelcontextprotocol/sdk/types.js";import{AsyncLocalStorage as Me}from"node:async_hooks";import{Bash as Ne,ReadWriteFs as Pe}from"just-bash";import Fe from"shell-quote";const Ie=`open-knowledge`;var Le=class{backend=`keyring`;async get(e){let{Entry:t}=await import(`@napi-rs/keyring`);try{let n=new t(Ie,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(Ie,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(Ie,e).deletePassword()}catch{}}},Re=class{backend=`file`;authFile;constructor(e){this.authFile=e??fe(se(),`.open-knowledge`,`auth.yml`)}read(){if(!E(this.authFile))return{};try{return me(O(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=ue(this.authFile);E(t)||D(t,{recursive:!0,mode:448}),oe(this.authFile,he(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 ze(e){try{let{Entry:e}=await import(`@napi-rs/keyring`);return new e(Ie,`__probe__`),process.stderr.write(`[auth] token storage: OS keychain
3
- `),new Le}catch{return process.stderr.write(`[auth] token storage: file (~/.open-knowledge/auth.yml)
4
- `),new Re(e)}}async function Be(e,t,n){let r=Ve(await He(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 Ve(e){let t={};for(let n of e.split(`
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 He(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 Ue(e){let t=new C(`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 Be(process.stdin,process.stdout,t);process.exit(n)}),t}async function We(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=ge({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 Ge(e){return process.env.OPEN_KNOWLEDGE_GITHUB_CLIENT_ID??e?.github?.oauthAppClientId??`Ov23liqlSd0V1MwR6rhI`}const Ke=new Set([`gitlab.com`,`bitbucket.org`,`codeberg.org`,`gitea.com`,`sr.ht`,`sourcehut.org`]);function qe(e){let t=e.toLowerCase().replace(/:\d+$/,``);Ke.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 Je(e,t){e&&process.stdout.write(`${JSON.stringify(t)}\n`)}async function Ye(e,t,n,r=We){let i=Ge(n),{host:a,json:o}=e;qe(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?Je(!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?Je(!0,{type:`complete`,host:a,login:c}):process.stderr.write(`✓ Logged in as ${c} on ${a}\n`)}function Xe(e,t){return new C(`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 Ye(n,await t(),e())})}async function Ze(e,t,n){let{host:r,json:i}=e;qe(r);let a=await(n??(()=>_e({message:`Enter PAT:`})))();a||(process.stderr.write(`No token provided
2
+ import{a as e,i as t}from"./constants-Bu_4qpfA.mjs";import{O as n,c as r,k as i}from"./src-CQw-HNNM.mjs";import{n as a,t as o}from"./paths-BFWjaw5o.mjs";import{ct as s,lt as c,ot as l,p as u,st as d,tt as f}from"./src-4QBkn9Bx.mjs";import{c as p,f as m,r as h}from"./server-lock-B4frNnOB.mjs";import{i as g}from"./init-B9eYsbu4.mjs";import{t as _}from"./is-object-DaHwJLOe.mjs";import{r as v}from"./init-DfZ9mpCA.mjs";import{i as y,n as ee,t as b}from"./loader-BMNx8Mh2.mjs";import{o as x,s as S}from"./start-Ckhk73du.mjs";import"./src-DpF8sICX.mjs";import{Command as C}from"commander";import{appendFileSync as w,closeSync as T,existsSync as E,mkdirSync as D,openSync as te,readFileSync as O,readdirSync as ne,realpathSync as re,statSync as ie,unlinkSync as ae,writeFileSync as oe}from"node:fs";import{homedir as se,hostname as ce}from"node:os";import{basename as le,dirname as ue,isAbsolute as de,join as fe,relative as pe,resolve as k}from"node:path";import{parse as me,stringify as he}from"yaml";import{createOAuthDeviceAuth as ge}from"@octokit/auth-oauth-device";import _e from"@inquirer/password";import{Octokit as ve}from"@octokit/rest";import{fileURLToPath as ye}from"node:url";import{randomUUID as be}from"node:crypto";import{execFileSync as xe,spawn as Se}from"node:child_process";import Ce from"simple-git";import{readFile as we,readdir as Te,stat as Ee}from"node:fs/promises";import{createServer as De,request as Oe}from"node:http";import ke from"picomatch";import{z as A}from"zod";import{McpServer as Ae}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as je}from"@modelcontextprotocol/sdk/server/stdio.js";import{RootsListChangedNotificationSchema as Me}from"@modelcontextprotocol/sdk/types.js";import{AsyncLocalStorage as Ne}from"node:async_hooks";import{Bash as Pe,ReadWriteFs as Fe}from"just-bash";import Ie from"shell-quote";const Le=`open-knowledge`;var Re=class{backend=`keyring`;async get(e){let{Entry:t}=await import(`@napi-rs/keyring`);try{let n=new t(Le,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(Le,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(Le,e).deletePassword()}catch{}}},ze=class{backend=`file`;authFile;constructor(e){this.authFile=e??fe(se(),`.open-knowledge`,`auth.yml`)}read(){if(!E(this.authFile))return{};try{return me(O(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=ue(this.authFile);E(t)||D(t,{recursive:!0,mode:448}),oe(this.authFile,he(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 Be(e){try{let{Entry:e}=await import(`@napi-rs/keyring`);return new e(Le,`__probe__`),process.stderr.write(`[auth] token storage: OS keychain
3
+ `),new Re}catch{return process.stderr.write(`[auth] token storage: file (~/.open-knowledge/auth.yml)
4
+ `),new ze(e)}}async function Ve(e,t,n){let r=He(await Ue(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 He(e){let t={};for(let n of e.split(`
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 Ue(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 We(e){let t=new C(`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 Ve(process.stdin,process.stdout,t);process.exit(n)}),t}async function Ge(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=ge({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 Ke(e){return process.env.OPEN_KNOWLEDGE_GITHUB_CLIENT_ID??e?.github?.oauthAppClientId??`Ov23liqlSd0V1MwR6rhI`}const qe=new Set([`gitlab.com`,`bitbucket.org`,`codeberg.org`,`gitea.com`,`sr.ht`,`sourcehut.org`]);function Je(e){let t=e.toLowerCase().replace(/:\d+$/,``);qe.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 Ye(e,t){e&&process.stdout.write(`${JSON.stringify(t)}\n`)}async function Xe(e,t,n,r=Ge){let i=Ke(n),{host:a,json:o}=e;Je(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?Ye(!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?Ye(!0,{type:`complete`,host:a,login:c}):process.stderr.write(`✓ Logged in as ${c} on ${a}\n`)}function Ze(e,t){return new C(`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 Xe(n,await t(),e())})}async function Qe(e,t,n){let{host:r,json:i}=e;Je(r);let a=await(n??(()=>_e({message:`Enter PAT:`})))();a||(process.stderr.write(`No token provided
6
6
  `),process.exit(1));let o=r===`github.com`?void 0:`https://${r}/api/v3`,s=new ve({auth:a,...o?{baseUrl:o}:{}}),c=`unknown`,l,u;try{let{data:e}=await s.users.getAuthenticated();c=e.login,l=e.name??void 0,u=e.email??void 0}catch{process.stderr.write(`Token validation failed
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 Qe(e){return new C(`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 Ze(t,await e())})}async function $e(e,t){let{host:n,json:r}=e;qe(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 ve({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 et(e){return new C(`repos`).description(`List accessible repositories`).option(`--host <host>`,`GitHub or GitHub Enterprise hostname`,`github.com`).option(`--json`,`Output JSON`,!1).action(async t=>{await $e(t,await e())})}async function tt(e,t){let{host:n}=e;await t.clear(n),process.stderr.write(`✓ Signed out from ${n}\n`)}function nt(e){return new C(`signout`).description(`Remove stored credentials`).option(`--host <host>`,`GitHub hostname`,`github.com`).action(async t=>{await tt(t,await e())})}async function rt(e,t){let{host:n,json:r}=e;qe(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 ve({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
- `):process.stderr.write(`✗ Token invalid for ${n}\n`),process.exit(1)}}function it(e){return new C(`status`).description(`Show authentication status`).option(`--host <host>`,`GitHub or GitHub Enterprise hostname`,`github.com`).option(`--json`,`Output JSON`,!1).action(async t=>{await rt(t,await e())})}function at(e){let t=new C(`auth`);t.description(`GitHub authentication management`);let n=()=>ze(),r=e??(()=>({}));return t.addCommand(Xe(r,n)),t.addCommand(it(n)),t.addCommand(et(n)),t.addCommand(nt(n)),t.addCommand(Qe(n)),t.addCommand(Ue(n)),t}function ot(e,t,n={}){let r=p(e,t);if(!E(r))return{status:`missing`,lockPath:r};let i;try{i=JSON.parse(O(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??ce();return a.hostname===o?(n.isAlive??m)(a.pid)?{status:`alive`,lockPath:r,lock:a}:{status:`dead-pid`,lockPath:r,lock:a}:{status:`foreign-host`,lockPath:r,lock:a}}function st(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 ct(e){let t=e.inspect??(t=>ot(e.lockDir,t)),n=e.unlink??(e=>ae(e)),r=e.log??(e=>console.log(e)),i=e.error??(e=>console.error(e)),a=st(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 lt(e){return new C(`clean`).description(`Prune stale / corrupt open-knowledge lock files (never touches live locks)`).action(()=>{ct({lockDir:a(o(e(),process.cwd()))}).failed.length>0&&(process.exitCode=1)})}function ut(){try{let e=xe(`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 dt(e,t,n={},r=ut){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 ft(e){return e.replace(/:\d+$/,``)}function pt(e){let t=e.trim();if(!t)return null;{let e=/^https?:\/\/([^/?#]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`https`,hostname:ft(e[1]),owner:e[2],name:e[3]}}{let e=/^ssh:\/\/(?:[\w.-]+@)?([^/?#]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`ssh`,hostname:ft(e[1]),owner:e[2],name:e[3]}}{let e=/^git:\/\/([^/?#]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`git`,hostname:ft(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 mt=[[`count`,0,10],[`compress`,10,20],[`receiv`,20,60],[`resolv`,60,100]];function ht(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 mt)if(n.includes(e))return{stage:t[1],pct:Math.round(i+r/100*(a-i))};return null}function gt(e,t){e&&process.stdout.write(`${JSON.stringify(t)}\n`)}async function _t(e,t,n,r=process.cwd()){let i=pt(e);if(!i)throw Error(`Invalid git URL: ${e}`);let a=t.dir?k(r,t.dir):k(r,i.name);if(E(a)&&ne(a).length>0)throw Error(`Target directory is not empty: ${a}`);let o=await ze(),s=await dt(i.hostname,o,{}),c=A({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
- `)){let n=ht(e);n&&n.pct!==l&&(l=n.pct,gt(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
- `),!E(k(a,`.open-knowledge`)))try{let[{runInit:e},{ensureOkGitignoredAtRoot:t}]=await Promise.all([import(`./init-CfiW8yzl.mjs`),import(`./init-BBEtdFbt.mjs`)]);await e({cwd:a,mcp:!1});try{t(a)}catch{}}catch{}return a}function vt(e){return new C(`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 _t(t,{json:r.json,dir:n},i);if(r.json)gt(!0,{type:`complete`,dir:a});else{process.stderr.write(`✓ Cloned to ${a}\n`),process.chdir(a);let{startCommand:t}=await import(`./start-AX9SNvdk.mjs`);await t(e).parseAsync([],{from:`user`})}}catch(e){let t=e instanceof Error?e.message:String(e);r.json?gt(!0,{type:`error`,message:t}):process.stderr.write(`✗ ${t}\n`),process.exitCode=1}})}const yt=new Me;var bt=class e{sessionId;corrId;component;constructor(e=`mcp`,t){this.sessionId=t??be().slice(0,12),this.corrId=be().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{w(a,i)}catch(e){console.warn(`[mcp-logger] Failed to write to OK_LOG_FILE: ${e instanceof Error?e.message:e}`)}}};function xt(e=`mcp`){return new bt(e)}function St(e,t){return yt.run(e,t)}function Ct(){return yt.getStore()}const wt=new Set([`find`,`markdown`,`replace`]),Tt=[`backlinks`,`deadLinks`,`documents`,`enrichedPaths`,`entries`,`forwardLinks`,`hints`,`hubs`,`orphans`,`results`],Et=[`checkpointRef`,`cwd`,`fileCount`,`matchCount`,`ok`,`query`,`stdoutTruncated`,`truncated`];function M(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Dt(e){return M(e)&&`requestId`in e}function Ot(e,t){if(wt.has(e))return{redacted:!0,type:`string`,length:t.length,lines:t.length===0?0:t.split(`
11
- `).length};let n=e===`command`?240:120;return t.length<=n?t:{type:`string`,length:t.length,preview:`${t.slice(0,n)}...`}}function kt(e,t){if(t===null)return null;if(typeof t==`string`)return Ot(e,t);if(typeof t==`number`||typeof t==`boolean`)return t;if(Array.isArray(t))return{type:`array`,length:t.length};if(M(t)){let e=Object.keys(t).sort();return{type:`object`,keyCount:e.length,keys:e.slice(0,12)}}if(t!==void 0)return{type:typeof t}}function At(e,t){return M(t)?Object.fromEntries(Object.entries(t).map(([e,t])=>[e,kt(e,t)])):kt(e,t)}function jt(e){let t={structuredKeys:Object.keys(e).sort()};for(let n of Et)n in e&&(t[n]=e[n]);for(let n of Tt){let r=e[n];Array.isArray(r)&&(t[`${n}Count`]=r.length)}return`previewUrl`in e&&(t.previewUrl=e.previewUrl??null),`previewUrlSource`in e&&(t.previewUrlSource=e.previewUrlSource),typeof e.stdout==`string`&&(t.stdoutChars=e.stdout.length),Array.isArray(e.warnings)&&(t.warningsCount=e.warnings.length),M(e.warning)&&(t.warning=!0,`previewUrl`in e.warning&&(t.warningPreviewUrl=e.warning.previewUrl??null)),M(e.ui)&&(t.uiBaseUrl=typeof e.ui.baseUrl==`string`||e.ui.baseUrl===null?e.ui.baseUrl:void 0,t.uiPort=typeof e.ui.port==`number`||e.ui.port===null?e.ui.port:void 0),t}function Mt(e){if(!M(e))return{resultType:typeof e};let t={isError:e.isError===!0};return Array.isArray(e.content)&&(t.contentItems=e.content.length,t.contentTextChars=e.content.reduce((e,t)=>M(t)&&typeof t.text==`string`?e+t.text.length:e,0)),M(e.structuredContent)&&Object.assign(t,jt(e.structuredContent)),t}function Nt(e){return{connectionId:e.connectionId.slice(0,8),displayName:e.displayName,...e.clientInfo?.name?{clientName:e.clientInfo.name}:{},...e.label?{label:e.label}:{}}}function Pt(e){let t=e.at(-1);return Dt(t)?{toolArgs:e.length>1?e[0]:void 0,extra:t}:{toolArgs:e[0],extra:void 0}}function Ft(e,t,n){let r=n.logger;return r?async(...i)=>{let a=r.child(),o=Date.now(),{toolArgs:s,extra:c}=Pt(i);a.info(`tool start`,{tool:e,...c?.requestId===void 0?{}:{requestId:c.requestId},...c?.sessionId?{transportSessionId:c.sessionId}:{},...n.identityRef?.current?{agent:Nt(n.identityRef.current)}:{},...s===void 0?{}:{args:At(e,s)}});try{let n=await St(a,async()=>await t(...i));return a.info(`tool finish`,{tool:e,...c?.requestId===void 0?{}:{requestId:c.requestId},durationMs:Date.now()-o,result:Mt(n)}),n}catch(t){throw a.error(`tool error`,t,{tool:e,...c?.requestId===void 0?{}:{requestId:c.requestId},durationMs:Date.now()-o}),t}}:t}function It(e,t){if(!t.logger)return e;let n=e.tool.bind(e),r=Object.create(e);return r.tool=((...e)=>{let r=String(e[0]),i=e.at(-1);if(typeof i!=`function`)return n(...e);let a=[...e];return a[a.length-1]=Ft(r,i,t),n(...a)}),r}const N="Absolute host path to resolve the request against. Defaults only when the MCP client advertises exactly one root; otherwise pass `cwd` explicitly.";function P(e,t){return{content:[{type:`text`,text:e}],...t?{isError:!0}:{}}}function F(e,t,n){return{content:[{type:`text`,text:e}],structuredContent:t,...n?{isError:!0}:{}}}const I="Error: Hocuspocus server is not running. Start it with `open-knowledge start`, then retry.\nFor disk-only writes without real-time sync, use your native Edit tool directly.";async function Lt(e,t){return typeof e==`function`?await e(t):e}async function Rt(e,t){return typeof e==`function`?await e(t):e}async function L(e,t,n){let r;try{r=await e(n)}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}try{let e=await Rt(t,r);return{ok:!0,cwd:r,config:e}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}async function R(e,t,n,r){let i=await L(e,t,r);if(!i.ok)return i;let{cwd:a,config:o}=i;try{return{ok:!0,cwd:a,config:o,url:await Lt(n,a)}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}function z(e){let t=e.toLowerCase();return t.endsWith(`.md`)?{ok:!0,docName:e.slice(0,-3)}:t.endsWith(`.mdx`)?{ok:!0,docName:e.slice(0,-4)}:t.endsWith(`.markdown`)?{ok:!1,error:`Error: docName "${e}" ends in ".markdown", which is not a supported extension. Use ".md" or ".mdx", or strip the extension to let the server auto-detect.`}:{ok:!0,docName:e}}async function B(e,t){let n;try{n=await fetch(`${e}${t}`,{signal:AbortSignal.timeout(3e4)})}catch(e){return{ok:!1,error:`Server unreachable: ${e instanceof Error?e.message:e}`}}try{return await n.json()}catch{return{ok:!1,error:`Server returned HTTP ${n.status} with non-JSON body`}}}async function V(e,t,n){let r;try{r=await fetch(`${e}${t}`,{method:`POST`,headers:{"Content-Type":`application/json`},body:n?JSON.stringify(n):void 0,signal:AbortSignal.timeout(3e4)})}catch(e){return{ok:!1,error:`Server unreachable: ${e instanceof Error?e.message:e}`}}try{return await r.json()}catch{return{ok:!1,error:`Server returned HTTP ${r.status} with non-JSON body`}}}function zt(e,t){return`Promote existing research on this topic into a canonical article inside the project content directory. **Canonical, not provisional** — the output is the source of truth for future agents.
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 $e(e){return new C(`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 Qe(t,await e())})}async function et(e,t){let{host:n,json:r}=e;Je(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 ve({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 tt(e){return new C(`repos`).description(`List accessible repositories`).option(`--host <host>`,`GitHub or GitHub Enterprise hostname`,`github.com`).option(`--json`,`Output JSON`,!1).action(async t=>{await et(t,await e())})}async function nt(e,t){let{host:n}=e;await t.clear(n),process.stderr.write(`✓ Signed out from ${n}\n`)}function rt(e){return new C(`signout`).description(`Remove stored credentials`).option(`--host <host>`,`GitHub hostname`,`github.com`).action(async t=>{await nt(t,await e())})}async function it(e,t){let{host:n,json:r}=e;Je(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 ve({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
+ `):process.stderr.write(`✗ Token invalid for ${n}\n`),process.exit(1)}}function at(e){return new C(`status`).description(`Show authentication status`).option(`--host <host>`,`GitHub or GitHub Enterprise hostname`,`github.com`).option(`--json`,`Output JSON`,!1).action(async t=>{await it(t,await e())})}function ot(e){let t=new C(`auth`);t.description(`GitHub authentication management`);let n=()=>Be(),r=e??(()=>({}));return t.addCommand(Ze(r,n)),t.addCommand(at(n)),t.addCommand(tt(n)),t.addCommand(rt(n)),t.addCommand($e(n)),t.addCommand(We(n)),t}function st(e,t,n={}){let r=p(e,t);if(!E(r))return{status:`missing`,lockPath:r};let i;try{i=JSON.parse(O(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??ce();return a.hostname===o?(n.isAlive??m)(a.pid)?{status:`alive`,lockPath:r,lock:a}:{status:`dead-pid`,lockPath:r,lock:a}:{status:`foreign-host`,lockPath:r,lock:a}}function ct(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 lt(e){let t=e.inspect??(t=>st(e.lockDir,t)),n=e.unlink??(e=>ae(e)),r=e.log??(e=>console.log(e)),i=e.error??(e=>console.error(e)),a=ct(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 ut(e){return new C(`clean`).description(`Prune stale / corrupt open-knowledge lock files (never touches live locks)`).action(()=>{lt({lockDir:a(o(e(),process.cwd()))}).failed.length>0&&(process.exitCode=1)})}function dt(){try{let e=xe(`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 ft(e,t,n={},r=dt){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 pt(e){return e.replace(/:\d+$/,``)}function mt(e){let t=e.trim();if(!t)return null;{let e=/^https?:\/\/([^/?#]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`https`,hostname:pt(e[1]),owner:e[2],name:e[3]}}{let e=/^ssh:\/\/(?:[\w.-]+@)?([^/?#]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`ssh`,hostname:pt(e[1]),owner:e[2],name:e[3]}}{let e=/^git:\/\/([^/?#]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`git`,hostname:pt(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 ht=[[`count`,0,10],[`compress`,10,20],[`receiv`,20,60],[`resolv`,60,100]];function gt(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 ht)if(n.includes(e))return{stage:t[1],pct:Math.round(i+r/100*(a-i))};return null}function _t(e,t){e&&process.stdout.write(`${JSON.stringify(t)}\n`)}async function vt(e,t,n,r=process.cwd()){let i=mt(e);if(!i)throw Error(`Invalid git URL: ${e}`);let a=t.dir?k(r,t.dir):k(r,i.name);if(E(a)&&ne(a).length>0)throw Error(`Target directory is not empty: ${a}`);let o=await Be(),s=await ft(i.hostname,o,{}),c=Ce({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
+ `)){let n=gt(e);n&&n.pct!==l&&(l=n.pct,_t(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
+ `),!E(k(a,`.open-knowledge`)))try{let[{runInit:e},{ensureOkGitignoredAtRoot:t}]=await Promise.all([import(`./init-NCAR1ES1.mjs`),import(`./init-BItvTUr-.mjs`)]);await e({cwd:a,mcp:!1});try{t(a)}catch{}}catch{}return a}function yt(e){return new C(`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 vt(t,{json:r.json,dir:n},i);if(r.json)_t(!0,{type:`complete`,dir:a});else{process.stderr.write(`✓ Cloned to ${a}\n`),process.chdir(a);let{startCommand:t}=await import(`./start-Dl5MKdR9.mjs`);await t(e).parseAsync([],{from:`user`})}}catch(e){let t=e instanceof Error?e.message:String(e);r.json?_t(!0,{type:`error`,message:t}):process.stderr.write(`✗ ${t}\n`),process.exitCode=1}})}const bt=new Ne;var xt=class e{sessionId;corrId;component;constructor(e=`mcp`,t){this.sessionId=t??be().slice(0,12),this.corrId=be().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{w(a,i)}catch(e){console.warn(`[mcp-logger] Failed to write to OK_LOG_FILE: ${e instanceof Error?e.message:e}`)}}};function St(e=`mcp`){return new xt(e)}function Ct(e,t){return bt.run(e,t)}function wt(){return bt.getStore()}const Tt=new Set([`find`,`markdown`,`replace`]),Et=[`backlinks`,`deadLinks`,`documents`,`enrichedPaths`,`entries`,`forwardLinks`,`hints`,`hubs`,`orphans`,`results`],Dt=[`checkpointRef`,`cwd`,`fileCount`,`matchCount`,`ok`,`query`,`stdoutTruncated`,`truncated`];function j(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Ot(e){return j(e)&&`requestId`in e}function kt(e,t){if(Tt.has(e))return{redacted:!0,type:`string`,length:t.length,lines:t.length===0?0:t.split(`
11
+ `).length};let n=e===`command`?240:120;return t.length<=n?t:{type:`string`,length:t.length,preview:`${t.slice(0,n)}...`}}function At(e,t){if(t===null)return null;if(typeof t==`string`)return kt(e,t);if(typeof t==`number`||typeof t==`boolean`)return t;if(Array.isArray(t))return{type:`array`,length:t.length};if(j(t)){let e=Object.keys(t).sort();return{type:`object`,keyCount:e.length,keys:e.slice(0,12)}}if(t!==void 0)return{type:typeof t}}function jt(e,t){return j(t)?Object.fromEntries(Object.entries(t).map(([e,t])=>[e,At(e,t)])):At(e,t)}function Mt(e){let t={structuredKeys:Object.keys(e).sort()};for(let n of Dt)n in e&&(t[n]=e[n]);for(let n of Et){let r=e[n];Array.isArray(r)&&(t[`${n}Count`]=r.length)}return`previewUrl`in e&&(t.previewUrl=e.previewUrl??null),`previewUrlSource`in e&&(t.previewUrlSource=e.previewUrlSource),typeof e.stdout==`string`&&(t.stdoutChars=e.stdout.length),Array.isArray(e.warnings)&&(t.warningsCount=e.warnings.length),j(e.warning)&&(t.warning=!0,`previewUrl`in e.warning&&(t.warningPreviewUrl=e.warning.previewUrl??null)),j(e.ui)&&(t.uiBaseUrl=typeof e.ui.baseUrl==`string`||e.ui.baseUrl===null?e.ui.baseUrl:void 0,t.uiPort=typeof e.ui.port==`number`||e.ui.port===null?e.ui.port:void 0),t}function Nt(e){if(!j(e))return{resultType:typeof e};let t={isError:e.isError===!0};return Array.isArray(e.content)&&(t.contentItems=e.content.length,t.contentTextChars=e.content.reduce((e,t)=>j(t)&&typeof t.text==`string`?e+t.text.length:e,0)),j(e.structuredContent)&&Object.assign(t,Mt(e.structuredContent)),t}function Pt(e){return{connectionId:e.connectionId.slice(0,8),displayName:e.displayName,...e.clientInfo?.name?{clientName:e.clientInfo.name}:{},...e.label?{label:e.label}:{}}}function Ft(e){let t=e.at(-1);return Ot(t)?{toolArgs:e.length>1?e[0]:void 0,extra:t}:{toolArgs:e[0],extra:void 0}}function It(e,t,n){let r=n.logger;return r?async(...i)=>{let a=r.child(),o=Date.now(),{toolArgs:s,extra:c}=Ft(i);a.info(`tool start`,{tool:e,...c?.requestId===void 0?{}:{requestId:c.requestId},...c?.sessionId?{transportSessionId:c.sessionId}:{},...n.identityRef?.current?{agent:Pt(n.identityRef.current)}:{},...s===void 0?{}:{args:jt(e,s)}});try{let n=await Ct(a,async()=>await t(...i));return a.info(`tool finish`,{tool:e,...c?.requestId===void 0?{}:{requestId:c.requestId},durationMs:Date.now()-o,result:Nt(n)}),n}catch(t){throw a.error(`tool error`,t,{tool:e,...c?.requestId===void 0?{}:{requestId:c.requestId},durationMs:Date.now()-o}),t}}:t}function Lt(e,t){if(!t.logger)return e;let n=e.tool.bind(e),r=Object.create(e);return r.tool=((...e)=>{let r=String(e[0]),i=e.at(-1);if(typeof i!=`function`)return n(...e);let a=[...e];return a[a.length-1]=It(r,i,t),n(...a)}),r}const M="Absolute host path to resolve the request against. Defaults only when the MCP client advertises exactly one root; otherwise pass `cwd` explicitly.",Rt=A.string().max(200).optional().describe(`Optional one-line user-outcome description (≤80 chars). Appears as a bullet in the timeline.`);function N(e,t){return{content:[{type:`text`,text:e}],...t?{isError:!0}:{}}}function P(e,t,n){return{content:[{type:`text`,text:e}],structuredContent:t,...n?{isError:!0}:{}}}const F="Error: Hocuspocus server is not running. Start it with `open-knowledge start`, then retry.\nFor disk-only writes without real-time sync, use your native Edit tool directly.";async function zt(e,t){return typeof e==`function`?await e(t):e}async function Bt(e,t){return typeof e==`function`?await e(t):e}async function I(e,t,n){let r;try{r=await e(n)}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}try{let e=await Bt(t,r);return{ok:!0,cwd:r,config:e}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}async function L(e,t,n,r){let i=await I(e,t,r);if(!i.ok)return i;let{cwd:a,config:o}=i;try{return{ok:!0,cwd:a,config:o,url:await zt(n,a)}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}function R(e){let t=e.toLowerCase();return t.endsWith(`.md`)?{ok:!0,docName:e.slice(0,-3)}:t.endsWith(`.mdx`)?{ok:!0,docName:e.slice(0,-4)}:t.endsWith(`.markdown`)?{ok:!1,error:`Error: docName "${e}" ends in ".markdown", which is not a supported extension. Use ".md" or ".mdx", or strip the extension to let the server auto-detect.`}:{ok:!0,docName:e}}async function z(e,t){let n;try{n=await fetch(`${e}${t}`,{signal:AbortSignal.timeout(3e4)})}catch(e){return{ok:!1,error:`Server unreachable: ${e instanceof Error?e.message:e}`}}try{return await n.json()}catch{return{ok:!1,error:`Server returned HTTP ${n.status} with non-JSON body`}}}async function B(e,t,n){let r;try{r=await fetch(`${e}${t}`,{method:`POST`,headers:{"Content-Type":`application/json`},body:n?JSON.stringify(n):void 0,signal:AbortSignal.timeout(3e4)})}catch(e){return{ok:!1,error:`Server unreachable: ${e instanceof Error?e.message:e}`}}try{return await r.json()}catch{return{ok:!1,error:`Server returned HTTP ${r.status} with non-JSON body`}}}function Vt(e,t){return`Promote existing research on this topic into a canonical article inside the project content directory. **Canonical, not provisional** — the output is the source of truth for future agents.
12
12
 
13
13
  Topic: ${e}
14
14
 
@@ -150,29 +150,29 @@ superseded_by: <path-to-new-canonical-article>.md
150
150
  - **Don't rewrite research prose verbatim** — canonical articles have a different voice (direct, decided) than research (exploratory, provisional)
151
151
  - **Don't skip the supersedes / superseded_by links** — the audit trail matters for future readers
152
152
 
153
- Full convention: read \`${n}/AGENTS.md\`.`}const Bt=[`Promote research into a canonical article inside the project content directory. Canonical, not provisional — the output is the source of truth for future agents.`,``,`**Use when:**`,`- A team has made a decision after research and wants the outcome committed as canonical knowledge`,`- Compacting several provisional research notes into one authoritative article`,`- A developer asks to "consolidate" or "finalize" knowledge on a topic`,``,`**Triggers on:**`,`- "consolidate", "finalize", "promote to canonical", "make this official"`,`- User says the team has decided and wants the outcome written as canonical`,`- Research has stabilized and a destination article is needed`].join(`
154
- `);function Vt(e,t){e.tool(`consolidate`,Bt,{topic:j.string().describe(`The topic to consolidate into a canonical article`),cwd:j.string().optional().describe(N)},async e=>{let n=await L(t.resolveCwd,t.config,e.cwd);return n.ok?F(zt(e.topic,n.config.content.dir),{previewUrl:null}):P(`Error: ${n.error}`,!0)})}function Ht(e){return e.split(`/`).map(encodeURIComponent).join(`/`)}function Ut(e){return e.endsWith(`/`)?e.slice(0,-1):e}function Wt(e){try{return new URL(e),!0}catch{return!1}}async function H(e,t,n){let r=n??await t.resolveCwd(),i=await Rt(t.config,r),s=o(i,r);return W(e,{config:i,lockDir:a(s),contentDir:s})}function Gt(e){try{let t=f(e.lockDir);if(t&&t.port>0)return{baseUrl:`http://localhost:${t.port}`,port:t.port}}catch(t){process.stderr.write(`[preview-url] readUiLock failed at ${e.lockDir} while building ui block: ${t instanceof Error?t.message:String(t)}\n`)}return{baseUrl:null,port:null}}async function U(e,t){let n=t??await e.resolveCwd(),r=await Rt(e.config,n),i=o(r,n),s={config:r,lockDir:a(i),contentDir:i};return{resolve:e=>W(e,s),ui:Gt(s)}}function Kt(e){let t=e.toLowerCase();return t.endsWith(`.md`)?e.slice(0,-3):t.endsWith(`.mdx`)?e.slice(0,-4):e}function W(e,t){let n=`/#/${Ht(e)}`;if(process.env.OK_ELECTRON_PROTOCOL_HOST===`1`&&t.contentDir)try{let n=re(t.contentDir);return{url:`openknowledge://open?project=${encodeURIComponent(n)}&doc=${encodeURIComponent(e)}`,source:`electron-protocol`}}catch(e){process.stderr.write(`[preview-url] realpathSync failed for ${t.contentDir}, falling through to http sources: ${e instanceof Error?e.message:String(e)}\n`)}let r=process.env.OPEN_KNOWLEDGE_PREVIEW_BASE_URL;if(r&&Wt(r))return{url:`${Ut(r)}${n}`,source:`env`};try{let e=f(t.lockDir);if(e&&e.port>0)return{url:`http://localhost:${e.port}${n}`,source:`lock`}}catch(e){process.stderr.write(`[preview-url] readUiLock failed at ${t.lockDir}, falling through to config: ${e instanceof Error?e.message:String(e)}\n`)}let i=t.config.preview?.baseUrl;return i&&Wt(i)?{url:`${Ut(i)}${n}`,source:`config`}:null}const qt=["**IMPORTANT: Before calling this tool, you MUST first call `get_preview_url` and navigate to the returned URL in your preview browser. If `get_preview_url` returns null, start the server first (`open-knowledge start` or `preview_start`), then call `get_preview_url` again. Do NOT call this tool without the preview open. NEVER manually construct the URL.**",``,`[Requires: Hocuspocus server] Find-and-replace on a live document via the CRDT layer.`,`The patch is applied through Hocuspocus and propagated to all connected editors in real-time.`,"Use `offset` when you need to patch an exact occurrence; omit it to preserve first-match behavior.",``,"**When rewriting prose, add `[[wiki-links]]` aggressively.** If the replacement mentions other documents or entities that should have their own page, link them as `[[Page Name]]`. Over-linking is the goal; underlinked documents lose their value in backlink-driven navigation.",``,`**Parameters:**`,"- `docName` — Document name, typically without extension. A trailing `.md` or `.mdx` is stripped automatically.","- `find` — Text to find (exact match)","- `replace` — Replacement text","- `offset` (optional) — Exact occurrence to patch, as a JavaScript string offset in the current markdown. If the document changed and the text no longer matches there, the server returns a stale-target error; re-run `suggest_links` to get fresh offsets."].join(`
155
- `);function Jt(e,t){e.tool(`edit_document`,qt,{docName:j.string().describe(`Document name to edit`),find:j.string().describe(`Text to find (exact match)`),replace:j.string().describe(`Replacement text`),offset:j.number().int().min(0).optional().describe(`Exact occurrence to patch, as a JavaScript string offset in the current markdown`),cwd:j.string().optional().describe(N)},async e=>{let n=await R(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return P(`Error: ${n.error}`,!0);let{cwd:r,config:i,url:s}=n;if(!s)return P(I,!0);let c=z(e.docName);if(!c.ok)return P(c.error,!0);let l=t.identityRef?.current,u=await V(s,`/api/agent-patch`,{docName:c.docName,find:e.find,replace:e.replace,offset:e.offset,...l?{agentId:l.connectionId,agentName:l.displayName,clientName:l.clientInfo?.name,colorSeed:l.colorSeed}:{}});if(!u.ok)return P(`Error: ${u.error}`,!0);let d=a(o(i,r)),f=W(c.docName,{config:i,lockDir:d}),p=(typeof u.subscriberCount==`number`?u.subscriberCount:void 0)===0,m=[`Edit applied successfully.`];f&&m.push(`Preview: ${f.url}`),p&&m.push(f?`Warning: no preview is currently attached to "${c.docName}". Open ${f.url} to watch future edits live.`:`Warning: no preview is currently attached to "${c.docName}".`);let h=m.join(`
156
- `);if(!f&&!p)return P(h);let g={};return f&&(g.previewUrl=f.url,g.previewUrlSource=f.source),p&&(g.warning={message:`No preview attached to ${c.docName}.`,previewUrl:f?.url??null}),F(h,g)})}const Yt=new Set([`cat`,`ls`,`grep`,`find`]),Xt=/\b[\w./-]+\.(md|mdx)\b/g;function G(e){return/\.(md|mdx)$/.test(e)}function Zt(e){let t=e.trim();return t?(t=t.replace(/\/+/g,`/`),t.startsWith(`./`)&&(t=t.slice(2)),t.endsWith(`/`)&&(t=t.slice(0,-1)),t):``}function K(e){return e.args.slice(1)}function q(e){return e.filter(e=>!e.startsWith(`-`))}function Qt(e){return q(K(e)).filter(G)}function $t(e,t){let n=q(K(t)),r=n.length>0?n[n.length-1]:``,i=r&&r!==`.`?Zt(r):``,a=[];i&&a.push(i);for(let t of e.split(`
157
- `)){let e=t.trim();if(!e||/\.[a-z0-9]+$/i.test(e)&&!G(e))continue;let n=i?`${i}/${e}`:e;a.push(n)}return a}function en(e){let t=[];for(let n of e.split(`
158
- `)){if(!n)continue;let e=n.indexOf(`:`);if(e<0)continue;let r=Zt(n.slice(0,e));G(r)&&t.push(r)}return t}function tn(e){let t=[];for(let n of e.split(`
159
- `)){let e=Zt(n);e&&G(e)&&t.push(e)}return t}function nn(e){return q(K(e)).filter(G)}function rn(e){return q(K(e)).length>0}function an(e){let t=[],n=e.matchAll(Xt);for(let e of n)t.push(Zt(e[0]));return t}function on(e,t){let n=null;for(let e=t.length-1;e>=0;e--){let r=t[e];if(Yt.has(r.command)){n=r;break}if((r.command===`head`||r.command===`tail`)&&rn(r)){n=r;break}}let r;if(!n)r=an(e);else{switch(n.command){case`cat`:r=Qt(n);break;case`ls`:r=$t(e,n);break;case`grep`:r=en(e);break;case`find`:r=tn(e);break;case`head`:case`tail`:r=nn(n);break;default:r=an(e)}r.length===0&&(r=an(e))}let i=new Set,a=[];for(let e of r){let t=Zt(e);!t||i.has(t)||(i.add(t),a.push(t))}return a}function J(e){return e===``?`''`:/^[\w.\-/]+$/.test(e)?e:`'${e.replace(/'/g,`'\\''`)}'`}const sn=16*1024*1024;var cn=class extends Error{limitBytes;actualBytes;partial;constructor(e,t,n){super(`Output exceeded ${e} byte buffer (got ${t}); narrow the command`),this.name=`StdoutOverflowError`,this.limitBytes=e,this.actualBytes=t,this.partial=n}};function ln(e){if(!de(e))throw Error(`createBashInstance: cwd must be absolute (got: ${e})`);return new Ne({cwd:`/`,fs:new Pe({root:k(e),allowSymlinks:!1})})}async function un(e,t){let n=await e.exec(t);if(n.stdout.length>sn)throw new cn(sn,n.stdout.length,{stdout:n.stdout.slice(0,sn),stderr:n.stderr,exitCode:n.exitCode});return{stdout:n.stdout,stderr:n.stderr,exitCode:n.exitCode}}function dn(e){return e.startsWith(`**/`)?e.slice(3):e}async function fn(e,t,n={}){let r=ln(t),i=[`-rn`,`-F`];(n.caseInsensitive??!0)&&i.push(`-i`);for(let e of n.include??[])i.push(`--include=${J(dn(e))}`);for(let e of n.exclude??[])i.push(`--exclude=${J(dn(e))}`),i.push(`--exclude-dir=${J(dn(e))}`);let a=n.paths?.length?n.paths.map(J):[`.`],o=`grep ${i.join(` `)} ${J(e)} ${a.join(` `)}`,s;try{s=await un(r,o)}catch(e){if(e instanceof cn)s=e.partial;else throw e}if(s.exitCode===1&&!s.stdout)return[];if(s.exitCode!==0&&s.exitCode!==1&&!s.stdout)throw Error(`grep exited ${s.exitCode}: ${s.stderr}`);let c=[],l=n.maxResults??1/0;for(let e of s.stdout.split(`
160
- `)){if(!e)continue;if(c.length>=l)break;let t=e.indexOf(`:`);if(t===-1)continue;let n=e.indexOf(`:`,t+1);if(n===-1)continue;let r=e.slice(0,t),i=e.slice(t+1,n),a=e.slice(n+1),o=Number.parseInt(i,10);Number.isFinite(o)&&c.push({path:r,line:o,text:a})}return c}const pn=new Set([`.git`,n,`node_modules`,`.changeset`,`.claude`,`.agents`,`dist`,`build`]);async function mn(e){let t=k(e),n=new Map,r=0,i=!1;async function a(e){if(i)return;let o;try{o=await we(e,{withFileTypes:!0})}catch{return}for(let s of o){if(i)return;if(s.isDirectory()&&pn.has(s.name))continue;let o=k(e,s.name);if(s.isDirectory()){await a(o);continue}if(s.isFile()){if(r>=1e3){i=!0;return}try{let e=await Te(o);n.set(pe(t,o),e.mtimeMs),r++}catch{}}}}return await a(t),{snapshot:n,truncated:i}}function hn(e,t){let n=[];for(let[r,i]of t){let t=e.get(r);(t===void 0||t!==i)&&n.push(r)}for(let[r]of e)t.has(r)||n.push(r);return{changed:n}}const gn=[`node_modules`,`.git`,`dist`,`build`,`.next`,`.turbo`,`.nuxt`,`coverage`,`.cache`,`.parcel-cache`,`.vercel`,n,`.claude`];function _n(e){return e===`--recursive`||e===`--dereference-recursive`?!0:e.startsWith(`--`)||!e.startsWith(`-`)?!1:/[rR]/.test(e.slice(1))}const vn=[{command:`grep`,applies:e=>e.slice(1).some(_n),hasUserExcludes:e=>e.some(e=>e===`--exclude-dir`||e.startsWith(`--exclude-dir=`)),buildExcludeArgs:e=>e.map(e=>`--exclude-dir=${e}`),insertionIndex:()=>1},{command:`find`,applies:()=>!0,hasUserExcludes:e=>e.slice(1).some(e=>e===`-not`||e===`!`||e===`-prune`),buildExcludeArgs:e=>{let t=[];for(let n of e)t.push(`-not`,`-path`,`*/${n}/*`);return t},insertionIndex:e=>{for(let t=1;t<e.length;t++)if(e[t].startsWith(`-`))return t;return e.length}}];function yn(e){return e.map(e=>{let t=vn.find(t=>t.command===e.command);if(!t||!t.applies(e.args)||t.hasUserExcludes(e.args))return e;let n=t.buildExcludeArgs(gn),r=t.insertionIndex(e.args);return{command:e.command,args:[...e.args.slice(0,r),...n,...e.args.slice(r)]}})}function bn(e){return e.map(e=>e.args.map(J).join(` `)).join(` | `)}const xn=new Set([`cat`,`ls`,`grep`,`find`,`head`,`tail`,`wc`,`sort`,`uniq`,`cut`]),Sn=new Set([`>`,`>>`,`<`,`>&`,`<&`,`|&`]),Cn=new Set([`&`,`;`,`;;`,`&&`,`||`,`(`,`)`,`<(`,`>(`,`<<`,`<<-`]),wn=new Set([`-o`,`--output-file`,`--output`]),Tn=[`-o=`,`--output-file=`,`--output=`],En=new Set([`-exec`,`-execdir`,`-delete`,`-fprint`,`-fprintf`,`-fprint0`,`-ok`,`-okdir`]),Dn=/[`]|\$\(|\$\{|\$'/;function On(e){return typeof e==`object`&&!!e&&`op`in e}function kn(e){let t=typeof e.op==`string`?e.op:`(unknown)`;return Sn.has(t)?{category:`write_blocked`,message:`Write operation blocked: '${t}'. exec is read-only. For document changes, use write_document or edit_document.`}:Cn.has(t)?{category:`shell_construct_blocked`,message:`Shell construct '${t}' is not supported. Only pipes (|) are allowed between allowlisted stages.`}:{category:`shell_construct_blocked`,message:`Operator '${t}' is not supported.`}}function An(e){let t=[];for(let n of e){if(typeof n==`string`){if(Dn.test(n))return{error:{category:`shell_construct_blocked`,message:`Argument '${n}' contains a shell-injection pattern (backtick, $(), or \${}); not supported.`}};t.push(n);continue}if(!On(n))return{error:{category:`shell_construct_blocked`,message:`Unrecognized token shape.`}};if(n.op===`glob`&&typeof n.pattern==`string`){t.push(n.pattern);continue}return typeof n.comment==`string`?{error:{category:`shell_construct_blocked`,message:`Comments are not allowed in exec commands.`}}:{error:kn(n)}}return{args:t}}function jn(e){if(!xn.has(e.command))return{category:`unknown_command`,message:`Command '${e.command}' is not in the allowlist. For pattern matching try 'grep'; for file listing try 'ls' or 'find'. Allowlist: cat, ls, grep, find, head, tail, wc, sort, uniq, cut.`};for(let t of e.args.slice(1)){if(wn.has(t)||Tn.some(e=>t.startsWith(e)))return{category:`write_blocked`,message:`Write operation blocked: '${t}'. exec is read-only. For document changes, use write_document or edit_document.`};if(e.command===`find`&&En.has(t))return{category:`write_blocked`,message:`find flag '${t}' is blocked (executes commands or deletes files). Use exec for read-only discovery; chain with another allowlisted tool via '|' if you need to transform output.`}}return null}function Mn(e){let t=e.trim();if(!t)return{error:{category:`unknown_command`,message:`Empty command.`}};let n;try{n=Fe.parse(t)}catch{return{error:{category:`shell_construct_blocked`,message:`Failed to parse command — likely malformed quoting or an unsupported construct.`}}}let r=[],i=[];for(let e of n){if(On(e)&&e.op===`|`){r.push(i),i=[];continue}i.push(e)}r.push(i);let a=[];for(let e of r){let t=An(e);if(`error`in t)return t;if(t.args.length===0)return{error:{category:`shell_construct_blocked`,message:`Empty pipeline stage (trailing pipe or leading pipe).`}};let n={command:t.args[0],args:t.args},r=jn(n);if(r)return{error:r};a.push(n)}return{stages:a}}const Nn=/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;function Pn(e,t){let n=e.match(Nn);if(!n)return null;try{let e=me(n[1]);if(_(e)){if(t){let n=t.safeParse(e);return n.success?n.data:null}return e}}catch{}return null}const Fn=new WeakMap;function In(e){let t=Fn.get(e);if(t)return t;let n=e.map(e=>Oe(e.match,{dot:!0}));return Fn.set(e,n),n}function Ln(e,t){if(e.length===0)return{};let n=In(e),r={},i=[],a=!1;for(let o=0;o<e.length;o++){if(!n[o](t))continue;a=!0;let s=e[o].frontmatter;if(s.title!==void 0&&(r.title=s.title),s.description!==void 0&&(r.description=s.description),s.tags!==void 0)for(let e of s.tags)i.includes(e)||i.push(e)}return a?(i.length>0&&(r.tags=i),r):{}}function Rn(e){try{return ie(k(e,`.git`)).isDirectory()}catch{return!1}}function zn(e){return A({baseDir:k(e),timeout:{block:5e3}})}async function Bn(e,t,n=5){if(!Rn(e))return{commits:[],source:`git-absent`};let r=zn(e),i=``;try{i=await r.raw(`log`,`-${Math.max(1,n)}`,`--format=%H|%aI|%an|%s`,`--follow`,`--`,t)}catch{return{commits:[],source:`git`}}let a=[];for(let e of i.split(`
161
- `)){if(!e)continue;let t=e.indexOf(`|`);if(t<0)continue;let n=e.indexOf(`|`,t+1);if(n<0)continue;let r=e.indexOf(`|`,n+1);r<0||a.push({hash:e.slice(0,t),date:e.slice(t+1,n),authorName:e.slice(n+1,r),subject:e.slice(r+1)})}return{commits:a,source:`git`}}const Vn=5e3;async function Hn(e){try{let t=(await A({baseDir:e,timeout:{block:Vn}}).revparse([`--abbrev-ref`,`HEAD`])).trim();return t&&t!==`HEAD`?t:null}catch{return null}}function Un(e,t){return A({baseDir:t,timeout:{block:Vn}}).env({GIT_DIR:e,GIT_WORK_TREE:t})}function Wn(e,t){let n=d(t);return e.startsWith(n)?e.slice(n.length):e}async function Gn(e,t,n,r,i){let a=``;try{a=await e.raw(`log`,t,`-${Math.max(1,i*2)}`,`--format=%H%x00%aI%x00%an%x00%s%x00%B%x1e`,`--`,n)}catch{return[]}let o=Wn(t,r),l=c(o),u=[];for(let e of a.split(``)){let t=e.trimStart();if(!t)continue;let[n=``,i=``,a=``,c=``,d=``]=t.split(`\0`),f=n.trim();f.length===40&&u.push({hash:f,date:i,writerName:a,message:c,contributors:s(d),writerId:o,isAgent:l.isAgent,writerClassification:l.classification,branch:r})}return u}async function Kn(e,t,n=5){let r=l(e);if(!r)return{commits:[],source:`shadow-repo-absent`};let i=await Hn(e);if(!i)return{commits:[],source:`shadow-repo`};let a=Un(r,k(e)),o=``;try{o=await a.raw(`for-each-ref`,d(i),`--format=%(refname)`)}catch{return{commits:[],source:`shadow-repo`}}let s=o.split(`
162
- `).map(e=>e.trim()).filter(Boolean);return s.length===0?{commits:[],source:`shadow-repo`}:{commits:(await Promise.all(s.map(e=>Gn(a,e,t,i,n)))).flat().sort((e,t)=>t.date.localeCompare(e.date)).slice(0,n),source:`shadow-repo`}}const qn=1e3,Jn=new Set([`.git`,n,`node_modules`,`.changeset`,`.claude`,`.agents`,`dist`,`build`]),Yn=/\.(md|mdx)$/i,Xn=j.object({title:j.string().optional(),description:j.string().optional(),tags:j.array(j.string()).default([])});function Zn(e){return e.replace(/\.md$/,``).replace(/\.mdx$/,``)}async function Qn(e){try{let t=Pn(await Ce(e,`utf-8`),Xn);return t?{title:t.title,description:t.description,tags:t.tags??[]}:{tags:[]}}catch{return null}}async function $n(e,t){if(!e)return null;let n=await B(e,`/api/backlinks?docName=${encodeURIComponent(t)}`);if(!n.ok)return null;let r=n.backlinks??n.results??n.links;if(!Array.isArray(r))return[];let i=[];for(let e of r){if(typeof e!=`object`||!e)continue;let t=e,n=typeof t.docName==`string`?t.docName:typeof t.source==`string`?t.source:typeof t.page==`string`?t.page:void 0;n&&i.push({source:n,title:typeof t.title==`string`?t.title:void 0,snippet:typeof t.snippet==`string`?t.snippet:null})}return i}async function er(e,t){if(!e||t.length===0)return null;let n=[...new Set(t)],r=[];for(let e=0;e<n.length;e+=100)r.push(n.slice(e,e+100));let i=await Promise.all(r.map(async t=>{let n=await B(e,`/api/backlink-counts?docNames=${encodeURIComponent(t.join(`,`))}`);return n.ok?n.counts??{}:null})),a=new Map,o=!1;for(let e of i)if(e){o=!0;for(let[t,n]of Object.entries(e))typeof n==`number`&&Number.isFinite(n)&&a.set(t,n)}return o?a:null}async function tr(e,t){if(!e)return null;let n=await B(e,`/api/forward-links?docName=${encodeURIComponent(t)}`);if(!n.ok)return null;let r=n.forwardLinks??n.links??n.results;if(!Array.isArray(r))return[];let i=[];for(let e of r){if(typeof e!=`object`||!e)continue;let t=e;if(t.kind===`external`&&typeof t.url==`string`){i.push({kind:`external`,url:t.url,title:typeof t.title==`string`?t.title:void 0,snippet:typeof t.snippet==`string`?t.snippet:null});continue}let n=typeof t.docName==`string`?t.docName:void 0;n&&i.push({kind:`doc`,docName:n,title:typeof t.title==`string`?t.title:void 0,snippet:typeof t.snippet==`string`?t.snippet:null})}return i}function nr(e,t,n){let r=t??[],i=r.length===0?{}:Ln(r,n),a=e?.title??i.title,o=e?.description??i.description,s=e?.tags??[],c=i.tags??[],l;if(c.length===0)l=s;else{let e=new Set;l=[];for(let t of c)e.has(t)||(e.add(t),l.push(t));for(let t of s)e.has(t)||(e.add(t),l.push(t))}return{title:a,description:o,tags:l}}async function rr(e,t,n={}){let r=e.replace(/^\.\//,``).replace(/^\/+/,``),i=k(t.projectDir,r),a=t.historyDepth??5,o=n.includeRichFields===!0,s=Qn(i);if(!o){let e=nr(await s,t.folderRules,r);return{path:r,title:e.title,description:e.description,tags:e.tags,backlinkCount:null,backlinks:null,forwardLinkCount:null,forwardLinks:null,history:null,historySource:null,projectHistory:null,projectHistorySource:null}}let[c,l,u,d,f]=await Promise.all([s,$n(t.serverUrl,Zn(r)).catch(()=>null),tr(t.serverUrl,Zn(r)).catch(()=>null),Kn(t.projectDir,r,a).catch(()=>({commits:[],source:`shadow-repo`})),Bn(t.projectDir,r,a).catch(()=>({commits:[],source:`git`}))]),p=nr(c,t.folderRules,r);return{path:r,title:p.title,description:p.description,tags:p.tags,backlinkCount:l?.length??null,backlinks:l,forwardLinkCount:u?.length??null,forwardLinks:u,history:d.commits,historySource:d.source,projectHistory:f.commits,projectHistorySource:f.source}}async function ir(e,t){let n={directMdCount:0,recursiveMdCount:0,childDirCount:0,mostRecent:null,truncated:!1},r=0,i=[{path:e,depth:0}];for(;i.length>0;){let e=i.shift();if(!e)break;if(r>=qn){n.truncated=!0;break}let a;try{a=await we(e.path,{withFileTypes:!0})}catch{continue}for(let o of a){if(r>=qn){n.truncated=!0;break}r++;let a=o.name;if(o.isDirectory()){if(Jn.has(a)||a.startsWith(`.`))continue;e.depth===0&&n.childDirCount++,i.push({path:`${e.path}/${a}`,depth:e.depth+1})}else if(o.isFile()&&Yn.test(a)){n.recursiveMdCount++,e.depth===0&&n.directMdCount++;let r=`${e.path}/${a}`;try{let e=await Te(r);(!n.mostRecent||e.mtimeMs>n.mostRecent.mtimeMs)&&(n.mostRecent={absPath:r,relPath:pe(t,r).split(/[\\/]/).filter(Boolean).join(`/`),mtimeMs:e.mtimeMs})}catch{}}}}return n}async function ar(e,t){let n=e.replace(/^\.\//,``).replace(/^\/+/,``).replace(/\/+$/,``),r=await ir(k(t.projectDir,n),t.projectDir),i;if(r.mostRecent){let e=await Qn(r.mostRecent.absPath);i={path:r.mostRecent.relPath,title:e?.title??le(r.mostRecent.relPath),updatedAt:new Date(r.mostRecent.mtimeMs).toISOString()}}let a={path:n,type:`directory`,directMdCount:r.directMdCount,recursiveMdCount:r.recursiveMdCount,childDirCount:r.childDirCount,mostRecentMd:i,truncated:r.truncated},o=t.folderRules??[];if(o.length>0){let e=Ln(o,n);e.title!==void 0&&(a.title=e.title),e.description!==void 0&&(a.description=e.description),e.tags!==void 0&&e.tags.length>0&&(a.tags=e.tags)}return a}const or=50*1024,sr=/\.(png|jpe?g|gif|webp|svg|pdf|zip|tar|gz|tgz|mp4|mov|mp3|wav|ico|bmp)$/i,cr=[`Run a read-only bash-like command against the project content directory. Returns raw stdout plus enriched metadata for every wiki file referenced (frontmatter, backlink/forward-link counts, shadow-repo activity with agent/human attribution).`,``,`Allowlist: cat, ls, grep, find, head, tail, wc, sort, uniq, cut. Pipes (|) work between stages. Redirections, subshells, and writes are rejected.`,``,"cwd: the command runs in the explicit absolute `cwd` you pass, or in the MCP client's only advertised root when there is exactly one. If the client has zero or multiple roots, pass `cwd` explicitly. Paths inside the command resolve relative to that cwd; traversal above it is rejected.",``,"Stdout provenance headers (GNU-style): `ls <dir>/` prepends `<dir>/:`, single-file `cat`/`head`/`tail` prepends `==> <path> <==`, so the subject of the command is visible in raw output. Multi-file `cat a b` emits no header — the `enrichedPaths` array still lists every file. `head`/`tail` used as pipe trimmers (no file arg) defer to the upstream producer.",``,`Examples:`,'- `exec({ command: "cat articles/auth.md" })` — file contents + full enrichment','- `exec({ command: "ls articles/" })` — listing + per-file enrichment (slim)','- `exec({ command: "grep -rn oauth articles/ | head -5" })` — pipe with enrichment on matched files','- `exec({ command: "ls", cwd: "/abs/path/to/other-repo" })` — run in a different project'].join(`
163
- `);function lr(e){let t=e.split(`
164
- `),n=t[t.length-1]===``?t.length-1:t.length;if(n<=500&&e.length<=or)return{text:e,truncated:!1,omittedLines:0};let r=Math.min(n,500),i=0,a=0;for(let e=0;e<r;e++){let n=t[e];if(i+=n.length+1,i>or)break;a++}let o=t.slice(0,a).join(`
165
- `),s=n-a;return{text:`${o}\n<truncated: ${s} more lines — re-run with a more-specific query>`,truncated:!0,omittedLines:s}}function ur(e){let t=[];for(let n of e)if(n.command===`cat`)for(let e of n.args.slice(1))e.startsWith(`-`)||sr.test(e)&&t.push(e);return t}function dr(e){for(let t=1;t<e.length;t++){let n=e[t],r=n.match(/^--lines=(\d+)$/);if(r)return Number(r[1]);if(n===`--lines`&&t+1<e.length){let n=Number(e[t+1]);if(Number.isFinite(n))return n}if(n===`-n`&&t+1<e.length){let n=Number(e[t+1]);if(Number.isFinite(n))return n}let i=n.match(/^-n(\d+)$/);if(i)return Number(i[1]);let a=n.match(/^-(\d+)$/);if(a)return Number(a[1])}return 10}function fr(e,t){if(e.length<2)return null;let n=e[e.length-1];if(n.command!==`head`&&n.command!==`tail`)return null;let r=dr(n.args),i=t.split(`
166
- `),a=i[i.length-1]===``?i.length-1:i.length;if(a<r)return null;let o=i.slice(0,a),s=new Set(o.map(e=>{let t=e.indexOf(`:`);return t>0?e.slice(0,t):e})).size,c=e.slice(0,-1).map(e=>e.command).join(` | `);return{banner:`Output hit \`${n.command} -${r}\` cap (${a} lines, ${s} unique file${s===1?``:`s`}). The \`${c}\` stage may have had more matches that never reached stdout. For existence checks across many files, prefer \`grep -rl PATTERN <dir>\` (list files only, no head). For enumeration, drop the \`| ${n.command}\` or widen the cap.`}}function pr(e){return e.type===`directory`}function mr(e){let t=[e.title?`**${e.title}** (${e.path}/)`:`**${e.path}/** (directory)`];e.description&&t.push(e.description),e.tags&&e.tags.length>0&&t.push(`tags: ${e.tags.join(`, `)}`);let n=[];return n.push(`${e.recursiveMdCount} md file${e.recursiveMdCount===1?``:`s`}`),e.childDirCount>0&&n.push(`${e.childDirCount} subdir${e.childDirCount===1?``:`s`}`),t.push(n.join(`, `)),e.mostRecentMd&&t.push(`most recent: ${e.mostRecentMd.title??e.mostRecentMd.path} (${e.mostRecentMd.path})`),e.truncated&&t.push(`scan truncated`),`- ${t.join(` — `)}`}function hr(e){let t=[`**${e.title??e.path}** (${e.path})`];if(e.description&&t.push(e.description),e.tags.length>0&&t.push(`tags: ${e.tags.join(`, `)}`),e.backlinkCount!==null&&t.push(`backlinks: ${e.backlinkCount}`),e.forwardLinkCount!==null&&t.push(`forward links: ${e.forwardLinkCount}`),e.history&&e.history.length>0){let n=e.history.map(e=>{let t=e.writerClassification===`agent`?`agent: ${e.writerName}`:e.writerClassification===`principal`?`human: ${e.writerName}`:`${e.writerClassification}: ${e.writerName}`;return`${e.hash.slice(0,7)} [${t}] ${e.message}`});t.push(`OK edits: ${n.join(` · `)}`)}if(e.projectHistory&&e.projectHistory.length>0){let n=e.projectHistory.map(e=>`${e.hash.slice(0,7)} ${e.authorName}: ${e.subject}`);t.push(`commits: ${n.join(` · `)}`)}return`- ${t.join(` — `)}`}function gr(e,t,n){let r=null;for(let t=e.length-1;t>=0;t--){let n=e[t],i=n.command;if(i===`ls`||i===`cat`){r=n;break}if((i===`head`||i===`tail`)&&q(K(n)).length>0){r=n;break}}if(!r)return``;let i=q(K(r));if(r.command===`ls`){let e=i[i.length-1];if(!e||e===`.`)return``;let n=e.replace(/\/+/g,`/`);return n.startsWith(`./`)&&(n=n.slice(2)),n.endsWith(`/`)&&(n=n.slice(0,-1)),!n||!t.has(n)?``:`${n}/:\n`}let a=i.filter(e=>/\.(md|mdx)$/.test(e)&&n.has(e));return a.length===1?`==> ${a[0]} <==\n`:``}function _r(e){if(e.length===0)return``;let t=[``,`### Referenced files`,``];for(let n of e)t.push(pr(n)?mr(n):hr(n));return t.join(`
167
- `)}async function vr(e,t){let n=[],r=[];return await Promise.all(t.map(async t=>{try{let i=await Te(k(e,t));i.isDirectory()?r.push(t):i.isFile()&&n.push(t)}catch{/\.(md|mdx)$/i.test(t)&&n.push(t)}})),{files:n,dirs:r}}function Y(e,t){return F(t,{enrichedPaths:[],error:{category:e,message:t}},!0)}function yr(e,t){return e.map(e=>{let n=t(Kt(e.path));return{...e,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}})}async function br(e,t){let n=await R(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return Y(`shell_construct_blocked`,`exec failed: ${n.error}`);let{cwd:r,config:i,url:a}=n,o=Mn(e.command);if(`error`in o)return Y(o.error.category,o.error.message);let s=yn(o.stages),c=bn(s),l=await mn(r),u=ln(r),d=``,f=``;try{let e=await un(u,c);d=e.stdout,f=e.stderr}catch(e){return e instanceof cn?Y(`output_overflow`,`Output exceeded 16 MB buffer. Narrow the command (e.g., add a more specific grep pattern, use head, restrict the path).`):Y(`shell_construct_blocked`,`exec failed: ${e instanceof Error?e.message:String(e)}`)}let p=await mn(r),m=hn(l.snapshot,p.snapshot);if(m.changed.length>0)return Y(`security_invariant_violation`,`Security invariant violated: file(s) in the content directory were modified during a read-only exec call: ${m.changed.join(`, `)}. This indicates a parser bug; the command has been logged.`);let h=lr(d),g=on(d,s),{files:_,dirs:v}=await vr(r,g),y=s.length===1&&s[0].command===`cat`&&_.length===1,ee=i.folders,b=await Promise.all(_.map(e=>rr(e,{projectDir:r,serverUrl:a,folderRules:ee},{includeRichFields:y}).catch(()=>({path:e,tags:[],backlinkCount:null,backlinks:null,forwardLinkCount:null,forwardLinks:null,history:null,historySource:null,projectHistory:null,projectHistorySource:null})))),x=await Promise.all(v.map(e=>ar(e,{projectDir:r,folderRules:ee}).catch(()=>({path:e,type:`directory`,directMdCount:0,recursiveMdCount:0,childDirCount:0,truncated:!1}))));if(!y&&a&&b.length>0){let e=await er(a,b.map(e=>Zn(e.path))).catch(()=>null);if(e)for(let t of b){let n=e.get(Zn(t.path));typeof n==`number`&&(t.backlinkCount=n)}}let S=new Map(b.map(e=>[e.path,e])),C=new Map(x.map(e=>[e.path,e])),w=[];for(let e of g){let t=S.get(e);if(t){w.push(t);continue}let n=C.get(e);n&&w.push(n)}let T=ur(s),E=[];T.length>0&&E.push(`File${T.length>1?`s`:``} ${T.join(`, `)} appear${T.length===1?`s`:``} to be binary (image/PDF/etc.) — exec returns text only (NG8). For binary retrieval, use native Read.`);let D=fr(s,d);D&&E.push(D.banner),f&&E.push(`stderr: ${f.trim()}`);let te=E.length>0?`${E.join(`
168
- `)}\n\n`:``,O=gr(s,C,S)+h.text,ne=`${te}${O}${_r(w)}`,{resolve:re,ui:ie}=await U({config:i,resolveCwd:async()=>r}),ae=yr(w,re),oe=ie;return F(ne,{enrichedPaths:ae,stdout:O,stdoutTruncated:h.truncated,cwd:r,...oe?{ui:oe}:{},...E.length>0?{warnings:E}:{}})}function xr(e,t){e.tool(`exec`,cr,{command:j.string().describe(`Read-only bash command (allowlist: cat, ls, grep, find, head, tail, wc, sort, uniq, cut; pipes OK)`),cwd:j.string().optional().describe("Absolute host path to run the command from. Defaults only when the MCP client advertises exactly one root; otherwise pass `cwd` explicitly.")},async e=>{try{return await br(e,t)}catch(e){return Y(`shell_construct_blocked`,`exec handler error: ${e instanceof Error?e.message:String(e)}`)}})}const Sr=[`[Requires: Hocuspocus server] Find all pages that link to a given page.`,`Returns source page names, resolved titles, and context snippets as JSON.`,``,`**Parameters:**`,'- `docName` — Target page docName, typically without extension (for example, "articles/project-alpha"). A trailing `.md` or `.mdx` is stripped automatically.'].join(`
169
- `);function Cr(e,t){e.tool(`get_backlinks`,Sr,{docName:j.string().describe(`Target page docName`),cwd:j.string().optional().describe(N)},async e=>{let n=await R(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return P(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return P(I,!0);let a=z(e.docName);if(!a.ok)return P(a.error,!0);let o=await B(i,`/api/backlinks?docName=${encodeURIComponent(a.docName)}`);if(!o.ok)return P(`Error: ${o.error}`,!0);let{ok:s,...c}=o,l=c,{resolve:u,ui:d}=await U(t,r),f=(l.backlinks??[]).map(e=>{let t=typeof e.source==`string`?e.source:null,n=t?u(t):null;return{...e,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}}),p={...l,backlinks:f,ui:d,cwd:r};return F(JSON.stringify(p,null,2),p)})}const wr=[`[Requires: Hocuspocus server] Find missing internal page targets across the corpus.`,`Returns grouped dead links keyed by missing target with source-doc rows as JSON.`,``,`**Parameters:**`,"- `sourceDocNames` (optional) — Referring source docs to narrow the audit with OR semantics"].join(`
170
- `);function Tr(e,t){e.tool(`get_dead_links`,wr,{sourceDocNames:j.array(j.string()).optional().describe(`Referring source docs to narrow the audit with OR semantics`),cwd:j.string().optional().describe(N)},async e=>{let n=await R(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return P(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return P(I,!0);let a=new URLSearchParams;for(let t of e.sourceDocNames??[]){let e=z(t);if(!e.ok)return P(e.error,!0);a.append(`sourceDocName`,e.docName)}let o=a.toString(),s=await B(i,`/api/dead-links${o?`?${o}`:``}`);if(!s.ok)return P(`Error: ${s.error}`,!0);let{ok:c,...l}=s,u=l,{resolve:d,ui:f}=await U(t,r),p=(u.deadLinks??[]).map(e=>{let t=typeof e.target==`string`?e.target:null,n=t?d(t):null,r=(e.sources??[]).map(e=>{let t=typeof e.source==`string`?e.source:null,n=t?d(t):null;return{...e,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}});return{...e,sources:r,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}}),m={...u,deadLinks:p,ui:f,cwd:r};return F(JSON.stringify(m,null,2),m)})}const Er=[`[Requires: Hocuspocus server] Find all pages that a given page links to.`,`Returns forward links as JSON.`,``,`**Parameters:**`,"- `docName` — Source page docName, typically without extension. A trailing `.md` or `.mdx` is stripped automatically."].join(`
171
- `);function Dr(e,t){e.tool(`get_forward_links`,Er,{docName:j.string().describe(`Source page docName`),cwd:j.string().optional().describe(N)},async e=>{let n=await R(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return P(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return P(I,!0);let a=z(e.docName);if(!a.ok)return P(a.error,!0);let o=await B(i,`/api/forward-links?docName=${encodeURIComponent(a.docName)}`);if(!o.ok)return P(`Error: ${o.error}`,!0);let{ok:s,...c}=o,l=c,{resolve:u,ui:d}=await U(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 F(JSON.stringify(p,null,2),p)})}const Or=[`[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(`
172
- `);function kr(e,t){e.tool(`get_history`,Or,{docName:j.string().describe(`Document name to query history for`),branch:j.string().optional().describe(`Branch name (default: current branch)`),limit:j.number().int().min(1).max(200).optional().describe(`Maximum entries to return (default 50, max 200)`),offset:j.number().int().min(0).optional().describe(`Number of entries to skip for pagination (default 0)`),type:j.enum([`checkpoint`,`upstream`,`wip`]).optional().describe(`Filter by entry type`),author:j.string().optional().describe(`Filter to entries by this author name or email`),excludeAuthor:j.string().optional().describe(`Exclude entries by this author name or email`),cwd:j.string().optional().describe(N)},async e=>{let n=await R(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return P(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return P(I,!0);let a=z(e.docName);if(!a.ok)return P(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 B(i,`/api/history?${o.toString()}`);if(!s.ok)return P(`Error: ${s.error}`,!0);let{ok:c,...l}=s,u=await H(a.docName,{config:t.config,resolveCwd:t.resolveCwd},r);return F(JSON.stringify(l,null,2),{...l,previewUrl:u?.url??null,...u?{previewUrlSource:u.source}:{}})})}const Ar=[`[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(`
173
- `);function jr(e,t){e.tool(`get_hubs`,Ar,{limit:j.number().int().positive().optional().describe(`Maximum number of hubs to return`),cwd:j.string().optional().describe(N)},async e=>{let n=await R(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return P(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return P(I,!0);let a=await B(i,`/api/hubs${e.limit?`?limit=${encodeURIComponent(String(e.limit))}`:``}`);if(!a.ok)return P(`Error: ${a.error}`,!0);let{ok:o,...s}=a,c=s,{resolve:l,ui:u}=await U(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 F(JSON.stringify(f,null,2),f)})}const Mr=[`[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(`
174
- `);function Nr(e,t){e.tool(`get_orphans`,Mr,{mode:j.enum(i).optional().describe(`Filter which type of graph disconnection to surface`),cwd:j.string().optional().describe(N)},async e=>{let n=await R(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return P(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return P(I,!0);let a=await B(i,`/api/orphans${e.mode?`?mode=${encodeURIComponent(e.mode)}`:``}`);if(!a.ok)return P(`Error: ${a.error}`,!0);let{ok:o,...s}=a,c=s,{resolve:l,ui:u}=await U(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 F(JSON.stringify(f,null,2),f)})}const Pr=["Return a browser URL for the given wiki docName. Agents should call this IMMEDIATELY BEFORE `write_document` / `edit_document` so they can navigate the preview browser to the doc first and watch the CRDT edit land live.",``,`**Parameters:**`,"- `docName` — Wiki doc name, typically without extension.",``,"Returns `{ previewUrl, previewUrlSource }` (source: `env` / `lock` / `config`). When no source is configured, returns `{ previewUrl: null }` and the agent may proceed without navigation."].join(`
175
- `);async function Fr(e,t){let n=z(e.docName);if(!n.ok)return{ok:!1,error:n.error};let r=n.docName,i=await L(t.resolveCwd,t.config,e.cwd);if(!i.ok)return i;let{cwd:s,config:c}=i,l=o(c,s),d;try{d=u({projectDir:s,contentDir:l,includePatterns:c.content.include,excludePatterns:c.content.exclude})}catch(e){return{ok:!1,error:`Cannot evaluate content filter: ${e instanceof Error?e.message:String(e)}`}}if(![`${r}.md`,`${r}.mdx`].some(e=>!d.isExcluded(e)))return{ok:!1,error:`Error: docName "${r}" is not inside content.include globs (${c.content.include.join(`, `)}). This tool only returns URLs for docs that match those globs.`};let f=W(r,{config:c,lockDir:a(l),contentDir:l});return f?{ok:!0,result:{previewUrl:f.url,previewUrlSource:f.source},text:`Preview URL for "${r}" (source: ${f.source}):\n${f.url}`}:{ok:!0,result:{previewUrl:null},text:`No preview URL resolvable for "${r}". The server is likely not running yet. Start it with \`open-knowledge start\` (or \`preview_start\`), then **call \`get_preview_url\` again** — the server writes a lock file that this tool reads to resolve the URL. NEVER guess or manually construct the preview URL. Alternatively, set \`OPEN_KNOWLEDGE_PREVIEW_BASE_URL\` or add \`preview.baseUrl\` to .open-knowledge/config.yml.`}}function Ir(e,t){e.tool(`get_preview_url`,Pr,{docName:j.string().min(1),cwd:j.string().optional().describe(N)},async e=>{let n=await Fr(e,t);return n.ok?F(n.text,n.result):P(n.error,!0)})}function Lr(e,t){return`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.
153
+ Full convention: read \`${n}/AGENTS.md\`.`}const Ht=[`Promote research into a canonical article inside the project content directory. Canonical, not provisional — the output is the source of truth for future agents.`,``,`**Use when:**`,`- A team has made a decision after research and wants the outcome committed as canonical knowledge`,`- Compacting several provisional research notes into one authoritative article`,`- A developer asks to "consolidate" or "finalize" knowledge on a topic`,``,`**Triggers on:**`,`- "consolidate", "finalize", "promote to canonical", "make this official"`,`- User says the team has decided and wants the outcome written as canonical`,`- Research has stabilized and a destination article is needed`].join(`
154
+ `);function Ut(e,t){e.tool(`consolidate`,Ht,{topic:A.string().describe(`The topic to consolidate into a canonical article`),cwd:A.string().optional().describe(M)},async e=>{let n=await I(t.resolveCwd,t.config,e.cwd);return n.ok?P(Vt(e.topic,n.config.content.dir),{previewUrl:null}):N(`Error: ${n.error}`,!0)})}function Wt(e){return e.split(`/`).map(encodeURIComponent).join(`/`)}function Gt(e){return e.endsWith(`/`)?e.slice(0,-1):e}function Kt(e){try{return new URL(e),!0}catch{return!1}}async function V(e,t,n){let r=n??await t.resolveCwd(),i=await Bt(t.config,r),s=o(i,r);return U(e,{config:i,lockDir:a(s),contentDir:s})}function qt(e){try{let t=f(e.lockDir);if(t&&t.port>0)return{baseUrl:`http://localhost:${t.port}`,port:t.port}}catch(t){process.stderr.write(`[preview-url] readUiLock failed at ${e.lockDir} while building ui block: ${t instanceof Error?t.message:String(t)}\n`)}return{baseUrl:null,port:null}}async function H(e,t){let n=t??await e.resolveCwd(),r=await Bt(e.config,n),i=o(r,n),s={config:r,lockDir:a(i),contentDir:i};return{resolve:e=>U(e,s),ui:qt(s)}}function Jt(e){let t=e.toLowerCase();return t.endsWith(`.md`)?e.slice(0,-3):t.endsWith(`.mdx`)?e.slice(0,-4):e}function U(e,t){let n=`/#/${Wt(e)}`;if(process.env.OK_ELECTRON_PROTOCOL_HOST===`1`&&t.contentDir)try{let n=re(t.contentDir);return{url:`openknowledge://open?project=${encodeURIComponent(n)}&doc=${encodeURIComponent(e)}`,source:`electron-protocol`}}catch(e){process.stderr.write(`[preview-url] realpathSync failed for ${t.contentDir}, falling through to http sources: ${e instanceof Error?e.message:String(e)}\n`)}let r=process.env.OPEN_KNOWLEDGE_PREVIEW_BASE_URL;if(r&&Kt(r))return{url:`${Gt(r)}${n}`,source:`env`};try{let e=f(t.lockDir);if(e&&e.port>0)return{url:`http://localhost:${e.port}${n}`,source:`lock`}}catch(e){process.stderr.write(`[preview-url] readUiLock failed at ${t.lockDir}, falling through to config: ${e instanceof Error?e.message:String(e)}\n`)}let i=t.config.preview?.baseUrl;return i&&Kt(i)?{url:`${Gt(i)}${n}`,source:`config`}:null}const Yt=["**IMPORTANT: Before calling this tool, you MUST first call `get_preview_url` and navigate to the returned URL in your preview browser. If `get_preview_url` returns null, start the server first (`open-knowledge start` or `preview_start`), then call `get_preview_url` again. Do NOT call this tool without the preview open. NEVER manually construct the URL.**",``,`[Requires: Hocuspocus server] Find-and-replace on a live document via the CRDT layer.`,`The patch is applied through Hocuspocus and propagated to all connected editors in real-time.`,"Use `offset` when you need to patch an exact occurrence; omit it to preserve first-match behavior.",``,"**When rewriting prose, add `[[wiki-links]]` aggressively.** If the replacement mentions other documents or entities that should have their own page, link them as `[[Page Name]]`. Over-linking is the goal; underlinked documents lose their value in backlink-driven navigation.",``,`**Parameters:**`,"- `docName` — Document name, typically without extension. A trailing `.md` or `.mdx` is stripped automatically.","- `find` — Text to find (exact match)","- `replace` — Replacement text","- `offset` (optional) — Exact occurrence to patch, as a JavaScript string offset in the current markdown. If the document changed and the text no longer matches there, the server returns a stale-target error; re-run `suggest_links` to get fresh offsets.",'- `summary` — Optional one-line user-outcome description of this edit (≤80 chars). Appears as a bullet in the document timeline so readers can scan intent without opening every diff. Prefer outcome phrasing ("Fixed token-refresh race") over structural ("Changed 1 line"). Avoid including secrets or PII — summaries are persisted to git history.'].join(`
155
+ `);function Xt(e,t){e.tool(`edit_document`,Yt,{docName:A.string().describe(`Document name to edit`),find:A.string().describe(`Text to find (exact match)`),replace:A.string().describe(`Replacement text`),offset:A.number().int().min(0).optional().describe(`Exact occurrence to patch, as a JavaScript string offset in the current markdown`),summary:Rt,cwd:A.string().optional().describe(M)},async e=>{let n=await L(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return N(`Error: ${n.error}`,!0);let{cwd:r,config:i,url:s}=n;if(!s)return N(F,!0);let c=R(e.docName);if(!c.ok)return N(c.error,!0);let l=t.identityRef?.current,u=await B(s,`/api/agent-patch`,{docName:c.docName,find:e.find,replace:e.replace,offset:e.offset,...e.summary===void 0?{}:{summary:e.summary},...l?{agentId:l.connectionId,agentName:l.displayName,clientName:l.clientInfo?.name,colorSeed:l.colorSeed}:{}});if(!u.ok)return N(`Error: ${u.error}`,!0);let d=a(o(i,r)),f=U(c.docName,{config:i,lockDir:d}),p=(typeof u.subscriberCount==`number`?u.subscriberCount:void 0)===0,m=u.summary&&typeof u.summary==`object`?u.summary:void 0,h=typeof m?.hint==`string`?m.hint:void 0,g=[`Edit applied successfully.`];f&&g.push(`Preview: ${f.url}`),p&&g.push(f?`Warning: no preview is currently attached to "${c.docName}". Open ${f.url} to watch future edits live.`:`Warning: no preview is currently attached to "${c.docName}".`),h&&g.push(h);let _=g.join(`
156
+ `);if(!f&&!p&&!m)return N(_);let v={};return f&&(v.previewUrl=f.url,v.previewUrlSource=f.source),p&&(v.warning={message:`No preview attached to ${c.docName}.`,previewUrl:f?.url??null}),m&&(v.summary=m),P(_,v)})}const Zt=new Set([`cat`,`ls`,`grep`,`find`]),Qt=/\b[\w./-]+\.(md|mdx)\b/g;function W(e){return/\.(md|mdx)$/.test(e)}function G(e){let t=e.trim();return t?(t=t.replace(/\/+/g,`/`),t.startsWith(`./`)&&(t=t.slice(2)),t.endsWith(`/`)&&(t=t.slice(0,-1)),t):``}function K(e){return e.args.slice(1)}function q(e){return e.filter(e=>!e.startsWith(`-`))}function $t(e){return q(K(e)).filter(W)}function en(e,t){let n=q(K(t)),r=n.length>0?n[n.length-1]:``,i=r&&r!==`.`?G(r):``,a=[];i&&a.push(i);for(let t of e.split(`
157
+ `)){let e=t.trim();if(!e||/\.[a-z0-9]+$/i.test(e)&&!W(e))continue;let n=i?`${i}/${e}`:e;a.push(n)}return a}function tn(e){let t=[];for(let n of e.split(`
158
+ `)){if(!n)continue;let e=n.indexOf(`:`);if(e<0)continue;let r=G(n.slice(0,e));W(r)&&t.push(r)}return t}function nn(e){let t=[];for(let n of e.split(`
159
+ `)){let e=G(n);e&&W(e)&&t.push(e)}return t}function rn(e){return q(K(e)).filter(W)}function an(e){return q(K(e)).length>0}function on(e){let t=[],n=e.matchAll(Qt);for(let e of n)t.push(G(e[0]));return t}function sn(e,t){let n=null;for(let e=t.length-1;e>=0;e--){let r=t[e];if(Zt.has(r.command)){n=r;break}if((r.command===`head`||r.command===`tail`)&&an(r)){n=r;break}}let r;if(!n)r=on(e);else{switch(n.command){case`cat`:r=$t(n);break;case`ls`:r=en(e,n);break;case`grep`:r=tn(e);break;case`find`:r=nn(e);break;case`head`:case`tail`:r=rn(n);break;default:r=on(e)}r.length===0&&(r=on(e))}let i=new Set,a=[];for(let e of r){let t=G(e);!t||i.has(t)||(i.add(t),a.push(t))}return a}function J(e){return e===``?`''`:/^[\w.\-/]+$/.test(e)?e:`'${e.replace(/'/g,`'\\''`)}'`}const cn=16*1024*1024;var ln=class extends Error{limitBytes;actualBytes;partial;constructor(e,t,n){super(`Output exceeded ${e} byte buffer (got ${t}); narrow the command`),this.name=`StdoutOverflowError`,this.limitBytes=e,this.actualBytes=t,this.partial=n}};function un(e){if(!de(e))throw Error(`createBashInstance: cwd must be absolute (got: ${e})`);return new Pe({cwd:`/`,fs:new Fe({root:k(e),allowSymlinks:!1})})}async function dn(e,t){let n=await e.exec(t);if(n.stdout.length>cn)throw new ln(cn,n.stdout.length,{stdout:n.stdout.slice(0,cn),stderr:n.stderr,exitCode:n.exitCode});return{stdout:n.stdout,stderr:n.stderr,exitCode:n.exitCode}}function fn(e){return e.startsWith(`**/`)?e.slice(3):e}async function pn(e,t,n={}){let r=un(t),i=[`-rn`,`-F`];(n.caseInsensitive??!0)&&i.push(`-i`);for(let e of n.include??[])i.push(`--include=${J(fn(e))}`);for(let e of n.exclude??[])i.push(`--exclude=${J(fn(e))}`),i.push(`--exclude-dir=${J(fn(e))}`);let a=n.paths?.length?n.paths.map(J):[`.`],o=`grep ${i.join(` `)} ${J(e)} ${a.join(` `)}`,s;try{s=await dn(r,o)}catch(e){if(e instanceof ln)s=e.partial;else throw e}if(s.exitCode===1&&!s.stdout)return[];if(s.exitCode!==0&&s.exitCode!==1&&!s.stdout)throw Error(`grep exited ${s.exitCode}: ${s.stderr}`);let c=[],l=n.maxResults??1/0;for(let e of s.stdout.split(`
160
+ `)){if(!e)continue;if(c.length>=l)break;let t=e.indexOf(`:`);if(t===-1)continue;let n=e.indexOf(`:`,t+1);if(n===-1)continue;let r=e.slice(0,t),i=e.slice(t+1,n),a=e.slice(n+1),o=Number.parseInt(i,10);Number.isFinite(o)&&c.push({path:r,line:o,text:a})}return c}const mn=new Set([`.git`,n,`node_modules`,`.changeset`,`.claude`,`.agents`,`dist`,`build`]);async function hn(e){let t=k(e),n=new Map,r=0,i=!1;async function a(e){if(i)return;let o;try{o=await Te(e,{withFileTypes:!0})}catch{return}for(let s of o){if(i)return;if(s.isDirectory()&&mn.has(s.name))continue;let o=k(e,s.name);if(s.isDirectory()){await a(o);continue}if(s.isFile()){if(r>=1e3){i=!0;return}try{let e=await Ee(o);n.set(pe(t,o),e.mtimeMs),r++}catch{}}}}return await a(t),{snapshot:n,truncated:i}}function gn(e,t){let n=[];for(let[r,i]of t){let t=e.get(r);(t===void 0||t!==i)&&n.push(r)}for(let[r]of e)t.has(r)||n.push(r);return{changed:n}}const _n=[`node_modules`,`.git`,`dist`,`build`,`.next`,`.turbo`,`.nuxt`,`coverage`,`.cache`,`.parcel-cache`,`.vercel`,n,`.claude`];function vn(e){return e===`--recursive`||e===`--dereference-recursive`?!0:e.startsWith(`--`)||!e.startsWith(`-`)?!1:/[rR]/.test(e.slice(1))}const yn=[{command:`grep`,applies:e=>e.slice(1).some(vn),hasUserExcludes:e=>e.some(e=>e===`--exclude-dir`||e.startsWith(`--exclude-dir=`)),buildExcludeArgs:e=>e.map(e=>`--exclude-dir=${e}`),insertionIndex:()=>1},{command:`find`,applies:()=>!0,hasUserExcludes:e=>e.slice(1).some(e=>e===`-not`||e===`!`||e===`-prune`),buildExcludeArgs:e=>{let t=[];for(let n of e)t.push(`-not`,`-path`,`*/${n}/*`);return t},insertionIndex:e=>{for(let t=1;t<e.length;t++)if(e[t].startsWith(`-`))return t;return e.length}}];function bn(e){return e.map(e=>{let t=yn.find(t=>t.command===e.command);if(!t||!t.applies(e.args)||t.hasUserExcludes(e.args))return e;let n=t.buildExcludeArgs(_n),r=t.insertionIndex(e.args);return{command:e.command,args:[...e.args.slice(0,r),...n,...e.args.slice(r)]}})}function xn(e){return e.map(e=>e.args.map(J).join(` `)).join(` | `)}const Sn=new Set([`cat`,`ls`,`grep`,`find`,`head`,`tail`,`wc`,`sort`,`uniq`,`cut`]),Cn=new Set([`>`,`>>`,`<`,`>&`,`<&`,`|&`]),wn=new Set([`&`,`;`,`;;`,`&&`,`||`,`(`,`)`,`<(`,`>(`,`<<`,`<<-`]),Tn=new Set([`-o`,`--output-file`,`--output`]),En=[`-o=`,`--output-file=`,`--output=`],Dn=new Set([`-exec`,`-execdir`,`-delete`,`-fprint`,`-fprintf`,`-fprint0`,`-ok`,`-okdir`]),On=/[`]|\$\(|\$\{|\$'/;function kn(e){return typeof e==`object`&&!!e&&`op`in e}function An(e){let t=typeof e.op==`string`?e.op:`(unknown)`;return Cn.has(t)?{category:`write_blocked`,message:`Write operation blocked: '${t}'. exec is read-only. For document changes, use write_document or edit_document.`}:wn.has(t)?{category:`shell_construct_blocked`,message:`Shell construct '${t}' is not supported. Only pipes (|) are allowed between allowlisted stages.`}:{category:`shell_construct_blocked`,message:`Operator '${t}' is not supported.`}}function jn(e){let t=[];for(let n of e){if(typeof n==`string`){if(On.test(n))return{error:{category:`shell_construct_blocked`,message:`Argument '${n}' contains a shell-injection pattern (backtick, $(), or \${}); not supported.`}};t.push(n);continue}if(!kn(n))return{error:{category:`shell_construct_blocked`,message:`Unrecognized token shape.`}};if(n.op===`glob`&&typeof n.pattern==`string`){t.push(n.pattern);continue}return typeof n.comment==`string`?{error:{category:`shell_construct_blocked`,message:`Comments are not allowed in exec commands.`}}:{error:An(n)}}return{args:t}}function Mn(e){if(!Sn.has(e.command))return{category:`unknown_command`,message:`Command '${e.command}' is not in the allowlist. For pattern matching try 'grep'; for file listing try 'ls' or 'find'. Allowlist: cat, ls, grep, find, head, tail, wc, sort, uniq, cut.`};for(let t of e.args.slice(1)){if(Tn.has(t)||En.some(e=>t.startsWith(e)))return{category:`write_blocked`,message:`Write operation blocked: '${t}'. exec is read-only. For document changes, use write_document or edit_document.`};if(e.command===`find`&&Dn.has(t))return{category:`write_blocked`,message:`find flag '${t}' is blocked (executes commands or deletes files). Use exec for read-only discovery; chain with another allowlisted tool via '|' if you need to transform output.`}}return null}function Nn(e){let t=e.trim();if(!t)return{error:{category:`unknown_command`,message:`Empty command.`}};let n;try{n=Ie.parse(t)}catch{return{error:{category:`shell_construct_blocked`,message:`Failed to parse command — likely malformed quoting or an unsupported construct.`}}}let r=[],i=[];for(let e of n){if(kn(e)&&e.op===`|`){r.push(i),i=[];continue}i.push(e)}r.push(i);let a=[];for(let e of r){let t=jn(e);if(`error`in t)return t;if(t.args.length===0)return{error:{category:`shell_construct_blocked`,message:`Empty pipeline stage (trailing pipe or leading pipe).`}};let n={command:t.args[0],args:t.args},r=Mn(n);if(r)return{error:r};a.push(n)}return{stages:a}}const Pn=/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;function Fn(e,t){let n=e.match(Pn);if(!n)return null;try{let e=me(n[1]);if(_(e)){if(t){let n=t.safeParse(e);return n.success?n.data:null}return e}}catch{}return null}const In=new WeakMap;function Ln(e){let t=In.get(e);if(t)return t;let n=e.map(e=>ke(e.match,{dot:!0}));return In.set(e,n),n}function Rn(e,t){if(e.length===0)return{};let n=Ln(e),r={},i=[],a=!1;for(let o=0;o<e.length;o++){if(!n[o](t))continue;a=!0;let s=e[o].frontmatter;if(s.title!==void 0&&(r.title=s.title),s.description!==void 0&&(r.description=s.description),s.tags!==void 0)for(let e of s.tags)i.includes(e)||i.push(e)}return a?(i.length>0&&(r.tags=i),r):{}}function zn(e){try{return ie(k(e,`.git`)).isDirectory()}catch{return!1}}function Bn(e){return Ce({baseDir:k(e),timeout:{block:5e3}})}async function Vn(e,t,n=5){if(!zn(e))return{commits:[],source:`git-absent`};let r=Bn(e),i=``;try{i=await r.raw(`log`,`-${Math.max(1,n)}`,`--format=%H|%aI|%an|%s`,`--follow`,`--`,t)}catch{return{commits:[],source:`git`}}let a=[];for(let e of i.split(`
161
+ `)){if(!e)continue;let t=e.indexOf(`|`);if(t<0)continue;let n=e.indexOf(`|`,t+1);if(n<0)continue;let r=e.indexOf(`|`,n+1);r<0||a.push({hash:e.slice(0,t),date:e.slice(t+1,n),authorName:e.slice(n+1,r),subject:e.slice(r+1)})}return{commits:a,source:`git`}}const Hn=5e3;async function Un(e){try{let t=(await Ce({baseDir:e,timeout:{block:Hn}}).revparse([`--abbrev-ref`,`HEAD`])).trim();return t&&t!==`HEAD`?t:null}catch{return null}}function Wn(e,t){return Ce({baseDir:t,timeout:{block:Hn}}).env({GIT_DIR:e,GIT_WORK_TREE:t})}function Gn(e,t){let n=d(t);return e.startsWith(n)?e.slice(n.length):e}async function Kn(e,t,n,r,i){let a=``;try{a=await e.raw(`log`,t,`-${Math.max(1,i*2)}`,`--format=%H%x00%aI%x00%an%x00%s%x00%B%x1e`,`--`,n)}catch{return[]}let o=Gn(t,r),l=s(o),u=[];for(let e of a.split(``)){let t=e.trimStart();if(!t)continue;let[n=``,i=``,a=``,s=``,d=``]=t.split(`\0`),f=n.trim();f.length===40&&u.push({hash:f,date:i,writerName:a,message:s,contributors:c(d),writerId:o,isAgent:l.isAgent,writerClassification:l.classification,branch:r})}return u}async function qn(e,t,n=5){let r=l(e);if(!r)return{commits:[],source:`shadow-repo-absent`};let i=await Un(e);if(!i)return{commits:[],source:`shadow-repo`};let a=Wn(r,k(e)),o=``;try{o=await a.raw(`for-each-ref`,d(i),`--format=%(refname)`)}catch{return{commits:[],source:`shadow-repo`}}let s=o.split(`
162
+ `).map(e=>e.trim()).filter(Boolean);return s.length===0?{commits:[],source:`shadow-repo`}:{commits:(await Promise.all(s.map(e=>Kn(a,e,t,i,n)))).flat().sort((e,t)=>t.date.localeCompare(e.date)).slice(0,n),source:`shadow-repo`}}const Jn=1e3,Yn=new Set([`.git`,n,`node_modules`,`.changeset`,`.claude`,`.agents`,`dist`,`build`]),Xn=/\.(md|mdx)$/i,Zn=A.object({title:A.string().optional(),description:A.string().optional(),tags:A.array(A.string()).default([])});function Qn(e){return e.replace(/\.md$/,``).replace(/\.mdx$/,``)}async function $n(e){try{let t=Fn(await we(e,`utf-8`),Zn);return t?{title:t.title,description:t.description,tags:t.tags??[]}:{tags:[]}}catch{return null}}async function er(e,t){if(!e)return null;let n=await z(e,`/api/backlinks?docName=${encodeURIComponent(t)}`);if(!n.ok)return null;let r=n.backlinks??n.results??n.links;if(!Array.isArray(r))return[];let i=[];for(let e of r){if(typeof e!=`object`||!e)continue;let t=e,n=typeof t.docName==`string`?t.docName:typeof t.source==`string`?t.source:typeof t.page==`string`?t.page:void 0;n&&i.push({source:n,title:typeof t.title==`string`?t.title:void 0,snippet:typeof t.snippet==`string`?t.snippet:null})}return i}async function tr(e,t){if(!e||t.length===0)return null;let n=[...new Set(t)],r=[];for(let e=0;e<n.length;e+=100)r.push(n.slice(e,e+100));let i=await Promise.all(r.map(async t=>{let n=await z(e,`/api/backlink-counts?docNames=${encodeURIComponent(t.join(`,`))}`);return n.ok?n.counts??{}:null})),a=new Map,o=!1;for(let e of i)if(e){o=!0;for(let[t,n]of Object.entries(e))typeof n==`number`&&Number.isFinite(n)&&a.set(t,n)}return o?a:null}async function nr(e,t){if(!e)return null;let n=await z(e,`/api/forward-links?docName=${encodeURIComponent(t)}`);if(!n.ok)return null;let r=n.forwardLinks??n.links??n.results;if(!Array.isArray(r))return[];let i=[];for(let e of r){if(typeof e!=`object`||!e)continue;let t=e;if(t.kind===`external`&&typeof t.url==`string`){i.push({kind:`external`,url:t.url,title:typeof t.title==`string`?t.title:void 0,snippet:typeof t.snippet==`string`?t.snippet:null});continue}let n=typeof t.docName==`string`?t.docName:void 0;n&&i.push({kind:`doc`,docName:n,title:typeof t.title==`string`?t.title:void 0,snippet:typeof t.snippet==`string`?t.snippet:null})}return i}function rr(e,t,n){let r=t??[],i=r.length===0?{}:Rn(r,n),a=e?.title??i.title,o=e?.description??i.description,s=e?.tags??[],c=i.tags??[],l;if(c.length===0)l=s;else{let e=new Set;l=[];for(let t of c)e.has(t)||(e.add(t),l.push(t));for(let t of s)e.has(t)||(e.add(t),l.push(t))}return{title:a,description:o,tags:l}}async function ir(e,t,n={}){let r=e.replace(/^\.\//,``).replace(/^\/+/,``),i=k(t.projectDir,r),a=t.historyDepth??5,o=n.includeRichFields===!0,s=$n(i);if(!o){let e=rr(await s,t.folderRules,r);return{path:r,title:e.title,description:e.description,tags:e.tags,backlinkCount:null,backlinks:null,forwardLinkCount:null,forwardLinks:null,history:null,historySource:null,projectHistory:null,projectHistorySource:null}}let[c,l,u,d,f]=await Promise.all([s,er(t.serverUrl,Qn(r)).catch(()=>null),nr(t.serverUrl,Qn(r)).catch(()=>null),qn(t.projectDir,r,a).catch(()=>({commits:[],source:`shadow-repo`})),Vn(t.projectDir,r,a).catch(()=>({commits:[],source:`git`}))]),p=rr(c,t.folderRules,r);return{path:r,title:p.title,description:p.description,tags:p.tags,backlinkCount:l?.length??null,backlinks:l,forwardLinkCount:u?.length??null,forwardLinks:u,history:d.commits,historySource:d.source,projectHistory:f.commits,projectHistorySource:f.source}}async function ar(e,t){let n={directMdCount:0,recursiveMdCount:0,childDirCount:0,mostRecent:null,truncated:!1},r=0,i=[{path:e,depth:0}];for(;i.length>0;){let e=i.shift();if(!e)break;if(r>=Jn){n.truncated=!0;break}let a;try{a=await Te(e.path,{withFileTypes:!0})}catch{continue}for(let o of a){if(r>=Jn){n.truncated=!0;break}r++;let a=o.name;if(o.isDirectory()){if(Yn.has(a)||a.startsWith(`.`))continue;e.depth===0&&n.childDirCount++,i.push({path:`${e.path}/${a}`,depth:e.depth+1})}else if(o.isFile()&&Xn.test(a)){n.recursiveMdCount++,e.depth===0&&n.directMdCount++;let r=`${e.path}/${a}`;try{let e=await Ee(r);(!n.mostRecent||e.mtimeMs>n.mostRecent.mtimeMs)&&(n.mostRecent={absPath:r,relPath:pe(t,r).split(/[\\/]/).filter(Boolean).join(`/`),mtimeMs:e.mtimeMs})}catch{}}}}return n}async function or(e,t){let n=e.replace(/^\.\//,``).replace(/^\/+/,``).replace(/\/+$/,``),r=await ar(k(t.projectDir,n),t.projectDir),i;if(r.mostRecent){let e=await $n(r.mostRecent.absPath);i={path:r.mostRecent.relPath,title:e?.title??le(r.mostRecent.relPath),updatedAt:new Date(r.mostRecent.mtimeMs).toISOString()}}let a={path:n,type:`directory`,directMdCount:r.directMdCount,recursiveMdCount:r.recursiveMdCount,childDirCount:r.childDirCount,mostRecentMd:i,truncated:r.truncated},o=t.folderRules??[];if(o.length>0){let e=Rn(o,n);e.title!==void 0&&(a.title=e.title),e.description!==void 0&&(a.description=e.description),e.tags!==void 0&&e.tags.length>0&&(a.tags=e.tags)}return a}const sr=50*1024,cr=/\.(png|jpe?g|gif|webp|svg|pdf|zip|tar|gz|tgz|mp4|mov|mp3|wav|ico|bmp)$/i,lr=[`Run a read-only bash-like command against the project content directory. Returns raw stdout plus enriched metadata for every wiki file referenced (frontmatter, backlink/forward-link counts, shadow-repo activity with agent/human attribution).`,``,`Allowlist: cat, ls, grep, find, head, tail, wc, sort, uniq, cut. Pipes (|) work between stages. Redirections, subshells, and writes are rejected.`,``,"cwd: the command runs in the explicit absolute `cwd` you pass, or in the MCP client's only advertised root when there is exactly one. If the client has zero or multiple roots, pass `cwd` explicitly. Paths inside the command resolve relative to that cwd; traversal above it is rejected.",``,"Stdout provenance headers (GNU-style): `ls <dir>/` prepends `<dir>/:`, single-file `cat`/`head`/`tail` prepends `==> <path> <==`, so the subject of the command is visible in raw output. Multi-file `cat a b` emits no header — the `enrichedPaths` array still lists every file. `head`/`tail` used as pipe trimmers (no file arg) defer to the upstream producer.",``,`Examples:`,'- `exec({ command: "cat articles/auth.md" })` — file contents + full enrichment','- `exec({ command: "ls articles/" })` — listing + per-file enrichment (slim)','- `exec({ command: "grep -rn oauth articles/ | head -5" })` — pipe with enrichment on matched files','- `exec({ command: "ls", cwd: "/abs/path/to/other-repo" })` — run in a different project'].join(`
163
+ `);function ur(e){let t=e.split(`
164
+ `),n=t[t.length-1]===``?t.length-1:t.length;if(n<=500&&e.length<=sr)return{text:e,truncated:!1,omittedLines:0};let r=Math.min(n,500),i=0,a=0;for(let e=0;e<r;e++){let n=t[e];if(i+=n.length+1,i>sr)break;a++}let o=t.slice(0,a).join(`
165
+ `),s=n-a;return{text:`${o}\n<truncated: ${s} more lines — re-run with a more-specific query>`,truncated:!0,omittedLines:s}}function dr(e){let t=[];for(let n of e)if(n.command===`cat`)for(let e of n.args.slice(1))e.startsWith(`-`)||cr.test(e)&&t.push(e);return t}function fr(e){for(let t=1;t<e.length;t++){let n=e[t],r=n.match(/^--lines=(\d+)$/);if(r)return Number(r[1]);if(n===`--lines`&&t+1<e.length){let n=Number(e[t+1]);if(Number.isFinite(n))return n}if(n===`-n`&&t+1<e.length){let n=Number(e[t+1]);if(Number.isFinite(n))return n}let i=n.match(/^-n(\d+)$/);if(i)return Number(i[1]);let a=n.match(/^-(\d+)$/);if(a)return Number(a[1])}return 10}function pr(e,t){if(e.length<2)return null;let n=e[e.length-1];if(n.command!==`head`&&n.command!==`tail`)return null;let r=fr(n.args),i=t.split(`
166
+ `),a=i[i.length-1]===``?i.length-1:i.length;if(a<r)return null;let o=i.slice(0,a),s=new Set(o.map(e=>{let t=e.indexOf(`:`);return t>0?e.slice(0,t):e})).size,c=e.slice(0,-1).map(e=>e.command).join(` | `);return{banner:`Output hit \`${n.command} -${r}\` cap (${a} lines, ${s} unique file${s===1?``:`s`}). The \`${c}\` stage may have had more matches that never reached stdout. For existence checks across many files, prefer \`grep -rl PATTERN <dir>\` (list files only, no head). For enumeration, drop the \`| ${n.command}\` or widen the cap.`}}function mr(e){return e.type===`directory`}function hr(e){let t=[e.title?`**${e.title}** (${e.path}/)`:`**${e.path}/** (directory)`];e.description&&t.push(e.description),e.tags&&e.tags.length>0&&t.push(`tags: ${e.tags.join(`, `)}`);let n=[];return n.push(`${e.recursiveMdCount} md file${e.recursiveMdCount===1?``:`s`}`),e.childDirCount>0&&n.push(`${e.childDirCount} subdir${e.childDirCount===1?``:`s`}`),t.push(n.join(`, `)),e.mostRecentMd&&t.push(`most recent: ${e.mostRecentMd.title??e.mostRecentMd.path} (${e.mostRecentMd.path})`),e.truncated&&t.push(`scan truncated`),`- ${t.join(` — `)}`}function gr(e){let t=[`**${e.title??e.path}** (${e.path})`];if(e.description&&t.push(e.description),e.tags.length>0&&t.push(`tags: ${e.tags.join(`, `)}`),e.backlinkCount!==null&&t.push(`backlinks: ${e.backlinkCount}`),e.forwardLinkCount!==null&&t.push(`forward links: ${e.forwardLinkCount}`),e.history&&e.history.length>0){let n=e.history.map(e=>{let t=e.writerClassification===`agent`?`agent: ${e.writerName}`:e.writerClassification===`principal`?`human: ${e.writerName}`:`${e.writerClassification}: ${e.writerName}`;return`${e.hash.slice(0,7)} [${t}] ${e.message}`});t.push(`OK edits: ${n.join(` · `)}`)}if(e.projectHistory&&e.projectHistory.length>0){let n=e.projectHistory.map(e=>`${e.hash.slice(0,7)} ${e.authorName}: ${e.subject}`);t.push(`commits: ${n.join(` · `)}`)}return`- ${t.join(` — `)}`}function _r(e,t,n){let r=null;for(let t=e.length-1;t>=0;t--){let n=e[t],i=n.command;if(i===`ls`||i===`cat`){r=n;break}if((i===`head`||i===`tail`)&&q(K(n)).length>0){r=n;break}}if(!r)return``;let i=q(K(r));if(r.command===`ls`){let e=i[i.length-1];if(!e||e===`.`)return``;let n=e.replace(/\/+/g,`/`);return n.startsWith(`./`)&&(n=n.slice(2)),n.endsWith(`/`)&&(n=n.slice(0,-1)),!n||!t.has(n)?``:`${n}/:\n`}let a=i.filter(e=>/\.(md|mdx)$/.test(e)&&n.has(e));return a.length===1?`==> ${a[0]} <==\n`:``}function vr(e){if(e.length===0)return``;let t=[``,`### Referenced files`,``];for(let n of e)t.push(mr(n)?hr(n):gr(n));return t.join(`
167
+ `)}async function yr(e,t){let n=[],r=[];return await Promise.all(t.map(async t=>{try{let i=await Ee(k(e,t));i.isDirectory()?r.push(t):i.isFile()&&n.push(t)}catch{/\.(md|mdx)$/i.test(t)&&n.push(t)}})),{files:n,dirs:r}}function Y(e,t){return P(t,{enrichedPaths:[],error:{category:e,message:t}},!0)}function br(e,t){return e.map(e=>{let n=t(Jt(e.path));return{...e,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}})}async function xr(e,t){let n=await L(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return Y(`shell_construct_blocked`,`exec failed: ${n.error}`);let{cwd:r,config:i,url:a}=n,o=Nn(e.command);if(`error`in o)return Y(o.error.category,o.error.message);let s=bn(o.stages),c=xn(s),l=await hn(r),u=un(r),d=``,f=``;try{let e=await dn(u,c);d=e.stdout,f=e.stderr}catch(e){return e instanceof ln?Y(`output_overflow`,`Output exceeded 16 MB buffer. Narrow the command (e.g., add a more specific grep pattern, use head, restrict the path).`):Y(`shell_construct_blocked`,`exec failed: ${e instanceof Error?e.message:String(e)}`)}let p=await hn(r),m=gn(l.snapshot,p.snapshot);if(m.changed.length>0)return Y(`security_invariant_violation`,`Security invariant violated: file(s) in the content directory were modified during a read-only exec call: ${m.changed.join(`, `)}. This indicates a parser bug; the command has been logged.`);let h=ur(d),g=sn(d,s),{files:_,dirs:v}=await yr(r,g),y=s.length===1&&s[0].command===`cat`&&_.length===1,ee=i.folders,b=await Promise.all(_.map(e=>ir(e,{projectDir:r,serverUrl:a,folderRules:ee},{includeRichFields:y}).catch(()=>({path:e,tags:[],backlinkCount:null,backlinks:null,forwardLinkCount:null,forwardLinks:null,history:null,historySource:null,projectHistory:null,projectHistorySource:null})))),x=await Promise.all(v.map(e=>or(e,{projectDir:r,folderRules:ee}).catch(()=>({path:e,type:`directory`,directMdCount:0,recursiveMdCount:0,childDirCount:0,truncated:!1}))));if(!y&&a&&b.length>0){let e=await tr(a,b.map(e=>Qn(e.path))).catch(()=>null);if(e)for(let t of b){let n=e.get(Qn(t.path));typeof n==`number`&&(t.backlinkCount=n)}}let S=new Map(b.map(e=>[e.path,e])),C=new Map(x.map(e=>[e.path,e])),w=[];for(let e of g){let t=S.get(e);if(t){w.push(t);continue}let n=C.get(e);n&&w.push(n)}let T=dr(s),E=[];T.length>0&&E.push(`File${T.length>1?`s`:``} ${T.join(`, `)} appear${T.length===1?`s`:``} to be binary (image/PDF/etc.) — exec returns text only (NG8). For binary retrieval, use native Read.`);let D=pr(s,d);D&&E.push(D.banner),f&&E.push(`stderr: ${f.trim()}`);let te=E.length>0?`${E.join(`
168
+ `)}\n\n`:``,O=_r(s,C,S)+h.text,ne=`${te}${O}${vr(w)}`,{resolve:re,ui:ie}=await H({config:i,resolveCwd:async()=>r}),ae=br(w,re),oe=ie;return P(ne,{enrichedPaths:ae,stdout:O,stdoutTruncated:h.truncated,cwd:r,...oe?{ui:oe}:{},...E.length>0?{warnings:E}:{}})}function Sr(e,t){e.tool(`exec`,lr,{command:A.string().describe(`Read-only bash command (allowlist: cat, ls, grep, find, head, tail, wc, sort, uniq, cut; pipes OK)`),cwd:A.string().optional().describe("Absolute host path to run the command from. Defaults only when the MCP client advertises exactly one root; otherwise pass `cwd` explicitly.")},async e=>{try{return await xr(e,t)}catch(e){return Y(`shell_construct_blocked`,`exec handler error: ${e instanceof Error?e.message:String(e)}`)}})}const Cr=[`[Requires: Hocuspocus server] Find all pages that link to a given page.`,`Returns source page names, resolved titles, and context snippets as JSON.`,``,`**Parameters:**`,'- `docName` — Target page docName, typically without extension (for example, "articles/project-alpha"). A trailing `.md` or `.mdx` is stripped automatically.'].join(`
169
+ `);function wr(e,t){e.tool(`get_backlinks`,Cr,{docName:A.string().describe(`Target page docName`),cwd:A.string().optional().describe(M)},async e=>{let n=await L(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return N(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return N(F,!0);let a=R(e.docName);if(!a.ok)return N(a.error,!0);let o=await z(i,`/api/backlinks?docName=${encodeURIComponent(a.docName)}`);if(!o.ok)return N(`Error: ${o.error}`,!0);let{ok:s,...c}=o,l=c,{resolve:u,ui:d}=await H(t,r),f=(l.backlinks??[]).map(e=>{let t=typeof e.source==`string`?e.source:null,n=t?u(t):null;return{...e,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}}),p={...l,backlinks:f,ui:d,cwd:r};return P(JSON.stringify(p,null,2),p)})}const Tr=[`[Requires: Hocuspocus server] Find missing internal page targets across the corpus.`,`Returns grouped dead links keyed by missing target with source-doc rows as JSON.`,``,`**Parameters:**`,"- `sourceDocNames` (optional) — Referring source docs to narrow the audit with OR semantics"].join(`
170
+ `);function Er(e,t){e.tool(`get_dead_links`,Tr,{sourceDocNames:A.array(A.string()).optional().describe(`Referring source docs to narrow the audit with OR semantics`),cwd:A.string().optional().describe(M)},async e=>{let n=await L(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return N(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return N(F,!0);let a=new URLSearchParams;for(let t of e.sourceDocNames??[]){let e=R(t);if(!e.ok)return N(e.error,!0);a.append(`sourceDocName`,e.docName)}let o=a.toString(),s=await z(i,`/api/dead-links${o?`?${o}`:``}`);if(!s.ok)return N(`Error: ${s.error}`,!0);let{ok:c,...l}=s,u=l,{resolve:d,ui:f}=await H(t,r),p=(u.deadLinks??[]).map(e=>{let t=typeof e.target==`string`?e.target:null,n=t?d(t):null,r=(e.sources??[]).map(e=>{let t=typeof e.source==`string`?e.source:null,n=t?d(t):null;return{...e,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}});return{...e,sources:r,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}}),m={...u,deadLinks:p,ui:f,cwd:r};return P(JSON.stringify(m,null,2),m)})}const Dr=[`[Requires: Hocuspocus server] Find all pages that a given page links to.`,`Returns forward links as JSON.`,``,`**Parameters:**`,"- `docName` — Source page docName, typically without extension. A trailing `.md` or `.mdx` is stripped automatically."].join(`
171
+ `);function Or(e,t){e.tool(`get_forward_links`,Dr,{docName:A.string().describe(`Source page docName`),cwd:A.string().optional().describe(M)},async e=>{let n=await L(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return N(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return N(F,!0);let a=R(e.docName);if(!a.ok)return N(a.error,!0);let o=await z(i,`/api/forward-links?docName=${encodeURIComponent(a.docName)}`);if(!o.ok)return N(`Error: ${o.error}`,!0);let{ok:s,...c}=o,l=c,{resolve:u,ui:d}=await H(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 P(JSON.stringify(p,null,2),p)})}const kr=[`[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(`
172
+ `);function Ar(e,t){e.tool(`get_history`,kr,{docName:A.string().describe(`Document name to query history for`),branch:A.string().optional().describe(`Branch name (default: current branch)`),limit:A.number().int().min(1).max(200).optional().describe(`Maximum entries to return (default 50, max 200)`),offset:A.number().int().min(0).optional().describe(`Number of entries to skip for pagination (default 0)`),type:A.enum([`checkpoint`,`upstream`,`wip`]).optional().describe(`Filter by entry type`),author:A.string().optional().describe(`Filter to entries by this author name or email`),excludeAuthor:A.string().optional().describe(`Exclude entries by this author name or email`),cwd:A.string().optional().describe(M)},async e=>{let n=await L(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return N(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return N(F,!0);let a=R(e.docName);if(!a.ok)return N(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 z(i,`/api/history?${o.toString()}`);if(!s.ok)return N(`Error: ${s.error}`,!0);let{ok:c,...l}=s,u=await V(a.docName,{config:t.config,resolveCwd:t.resolveCwd},r);return P(JSON.stringify(l,null,2),{...l,previewUrl:u?.url??null,...u?{previewUrlSource:u.source}:{}})})}const jr=[`[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(`
173
+ `);function Mr(e,t){e.tool(`get_hubs`,jr,{limit:A.number().int().positive().optional().describe(`Maximum number of hubs to return`),cwd:A.string().optional().describe(M)},async e=>{let n=await L(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return N(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return N(F,!0);let a=await z(i,`/api/hubs${e.limit?`?limit=${encodeURIComponent(String(e.limit))}`:``}`);if(!a.ok)return N(`Error: ${a.error}`,!0);let{ok:o,...s}=a,c=s,{resolve:l,ui:u}=await H(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 P(JSON.stringify(f,null,2),f)})}const Nr=[`[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(`
174
+ `);function Pr(e,t){e.tool(`get_orphans`,Nr,{mode:A.enum(i).optional().describe(`Filter which type of graph disconnection to surface`),cwd:A.string().optional().describe(M)},async e=>{let n=await L(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return N(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return N(F,!0);let a=await z(i,`/api/orphans${e.mode?`?mode=${encodeURIComponent(e.mode)}`:``}`);if(!a.ok)return N(`Error: ${a.error}`,!0);let{ok:o,...s}=a,c=s,{resolve:l,ui:u}=await H(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 P(JSON.stringify(f,null,2),f)})}const Fr=["Return a browser URL for the given wiki docName. Agents should call this IMMEDIATELY BEFORE `write_document` / `edit_document` so they can navigate the preview browser to the doc first and watch the CRDT edit land live.",``,`**Parameters:**`,"- `docName` — Wiki doc name, typically without extension.",``,"Returns `{ previewUrl, previewUrlSource }` (source: `env` / `lock` / `config`). When no source is configured, returns `{ previewUrl: null }` and the agent may proceed without navigation."].join(`
175
+ `);async function Ir(e,t){let n=R(e.docName);if(!n.ok)return{ok:!1,error:n.error};let r=n.docName,i=await I(t.resolveCwd,t.config,e.cwd);if(!i.ok)return i;let{cwd:s,config:c}=i,l=o(c,s),d;try{d=u({projectDir:s,contentDir:l,includePatterns:c.content.include,excludePatterns:c.content.exclude})}catch(e){return{ok:!1,error:`Cannot evaluate content filter: ${e instanceof Error?e.message:String(e)}`}}if(![`${r}.md`,`${r}.mdx`].some(e=>!d.isExcluded(e)))return{ok:!1,error:`Error: docName "${r}" is not inside content.include globs (${c.content.include.join(`, `)}). This tool only returns URLs for docs that match those globs.`};let f=U(r,{config:c,lockDir:a(l),contentDir:l});return f?{ok:!0,result:{previewUrl:f.url,previewUrlSource:f.source},text:`Preview URL for "${r}" (source: ${f.source}):\n${f.url}`}:{ok:!0,result:{previewUrl:null},text:`No preview URL resolvable for "${r}". The server is likely not running yet. Start it with \`open-knowledge start\` (or \`preview_start\`), then **call \`get_preview_url\` again** — the server writes a lock file that this tool reads to resolve the URL. NEVER guess or manually construct the preview URL. Alternatively, set \`OPEN_KNOWLEDGE_PREVIEW_BASE_URL\` or add \`preview.baseUrl\` to .open-knowledge/config.yml.`}}function Lr(e,t){e.tool(`get_preview_url`,Fr,{docName:A.string().min(1),cwd:A.string().optional().describe(M)},async e=>{let n=await Ir(e,t);return n.ok?P(n.text,n.result):N(n.error,!0)})}function Rr(e,t){return`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.
176
176
 
177
177
  Source: ${e}
178
178
 
@@ -227,8 +227,8 @@ tags:
227
227
  - **No promotion to a canonical article** — that's the \`consolidate\` tool's job, later
228
228
  - **No deduplication** — if the same source is ingested twice, let it happen; cleanup is a separate concern
229
229
 
230
- Full convention: read \`${n}/AGENTS.md\`.`}const Rr=[`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`,``,`**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`,`- Research workflow needs raw sources before analysis`].join(`
231
- `);function zr(e,t){e.tool(`ingest`,Rr,{source:j.string().describe(`URL, file path, or identifier of the source to ingest`),cwd:j.string().optional().describe(N)},async e=>{let n=await L(t.resolveCwd,t.config,e.cwd);return n.ok?F(Lr(e.source,n.config.content.dir),{previewUrl:null}):P(`Error: ${n.error}`,!0)})}function Br(e){return`Initialize a project knowledge base at \`${e}\` for this repository.
230
+ Full convention: read \`${n}/AGENTS.md\`.`}const zr=[`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`,``,`**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`,`- Research workflow needs raw sources before analysis`].join(`
231
+ `);function Br(e,t){e.tool(`ingest`,zr,{source:A.string().describe(`URL, file path, or identifier of the source to ingest`),cwd:A.string().optional().describe(M)},async e=>{let n=await I(t.resolveCwd,t.config,e.cwd);return n.ok?P(Rr(e.source,n.config.content.dir),{previewUrl:null}):N(`Error: ${n.error}`,!0)})}function Vr(e){return`Initialize a project knowledge base at \`${e}\` for this repository.
232
232
 
233
233
  The content directory for this project is **\`${e}\`** (from \`${n}/config.yml\`).
234
234
 
@@ -323,16 +323,17 @@ Depending on the project, consider articles covering:
323
323
  - **Don't write articles for things that change often** (dependency versions, file counts); focus on stable understanding
324
324
  - **Don't create scaffolded subfolders you won't fill** — empty \`articles/\`/\`research/\`/\`external-sources/\` folders are clutter; organize as you actually need
325
325
 
326
- Full convention: read \`${n}/AGENTS.md\`.`}const Vr=[`Bootstrap the project knowledge base by reading the codebase and writing initial knowledge articles grouped by topic.`,``,`**Use when:**`,`- Setting up a knowledge base for the first time in a repo`,`- Onboarding to a new codebase and capturing initial understanding`,`- The content directory is empty or sparse`,``,`**Triggers on:**`,`- "init content", "bootstrap knowledge base", "populate articles", "set up project knowledge"`,`- User asks to document or catalog the codebase`].join(`
327
- `);function Hr(e,t){e.tool(`init-content`,Vr,{cwd:j.string().optional().describe(N)},async(e={})=>{let n=await L(t.resolveCwd,t.config,e.cwd);if(!n.ok)return P(`Error: ${n.error}`,!0);let{cwd:r,config:i}=n;return F(Br(i.content.dir),{ui:Gt({config:i,lockDir:a(o(i,r))})})})}const Ur=[`[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(`
328
- `);function Wr(e,t){e.tool(`list_documents`,Ur,{dir:j.string().optional().describe(`Optional directory to filter documents`),cwd:j.string().optional().describe(N)},async e=>{let n=await R(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return P(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return P(I,!0);let a=await B(i,`/api/documents${e.dir?`?dir=${encodeURIComponent(e.dir)}`:``}`);if(!a.ok)return P(`Error: ${a.error}`,!0);let{ok:o,...s}=a,c=s,{resolve:l,ui:u}=await U(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 F(JSON.stringify(f,null,2),f)})}const Gr=[`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(`
329
- `);function Kr(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(`
330
- `)}function qr(e){if(!e||e.length===0)return``;let t=[``,`### Commit history (project git)`,``];for(let n of e){let e=n.hash.slice(0,7);t.push(`- ${e} ${n.date} ${n.authorName} — ${n.subject}`)}return t.join(`
331
- `)}function Jr(e){if(!e||e.length===0)return``;let t=[``,`### Backlinks (${e.length})`,``];for(let n of e){let e=n.title?` — "${n.title}"`:``,r=n.snippet?` — "${n.snippet}"`:``;t.push(`- ${n.source}${e}${r}`)}return t.join(`
332
- `)}function Yr(e){if(!e||e.length===0)return``;let t=[``,`### Forward links (${e.length})`,``];for(let n of e){if(n.kind===`external`){let e=n.title?` — "${n.title}"`:``,r=n.snippet?` — "${n.snippet}"`:``;t.push(`- ${n.url}${e}${r}`);continue}let e=n.title?` — "${n.title}"`:``,r=n.snippet?` — "${n.snippet}"`:``;t.push(`- ${n.docName}${e}${r}`)}return t.join(`
333
- `)}function Xr(e){return e.replace(/^\.\//,``).replace(/^\/+/,``)}function Zr(e){return e.replace(/\.(md|mdx)$/i,``)}async function Qr(e,t){let n=await R(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)throw Error(n.error);let{cwd:r,config:i,url:a}=n,o=Xr(e.path),s=k(r,o),c=i.mcp.tools.read_document.historyDepth,[l,u]=await Promise.all([Ce(s,`utf-8`),rr(o,{projectDir:r,serverUrl:a,historyDepth:c,folderRules:i.folders},{includeRichFields:!0})]),d=o.split(`/`).pop()?.replace(/\.md$/,``).replace(/\.mdx$/,``)??o,f=u.title??d,p=u.description??``,m=u.tags,h=[];h.push(`## ${f}`),p&&h.push(`**Description:** ${p}`),m.length>0&&h.push(`**Tags:** ${m.join(`, `)}`),h.push(`**Path:** ${o}`);let g=Kr(u.history);g&&h.push(g);let _=qr(u.projectHistory);_&&h.push(_);let v=Jr(u.backlinks);v&&h.push(v);let y=Yr(u.forwardLinks);return y&&h.push(y),h.push(``,`### Content`,``,l),h.join(`
334
- `)}function $r(e,t){e.tool(`read_document`,Gr,{path:j.string().describe(`Project-root-relative path to the file`),since:j.string().optional().describe(`Reserved; currently unused (§15 Future Work)`),cwd:j.string().optional().describe("Absolute host path to resolve `path` against. Defaults only when the MCP client advertises exactly one root; otherwise pass `cwd` explicitly.")},async e=>{try{let n=await Qr(e,t),r=await H(Zr(Xr(e.path)),{config:t.config,resolveCwd:t.resolveCwd},await t.resolveCwd(e.cwd));return r?F(n,{previewUrl:r.url,previewUrlSource:r.source}):F(n,{previewUrl:null})}catch(e){return P(`Error: ${e instanceof Error?e.message:String(e)}`,!0)}})}const ei=["[Requires: Hocuspocus server] Rename a document through the managed rename flow at `POST /api/rename`.",`Renames the target document and rewrites inbound wiki-links plus supported internal inline Markdown links in affected docs.`,``,`**Parameters:**`,"- `docName` — Current document name, typically without extension. A trailing `.md` or `.mdx` is stripped automatically.","- `newDocName` — New document name, typically without extension. A trailing `.md` or `.mdx` is stripped automatically."].join(`
335
- `);function ti(e){return Array.isArray(e)?e.flatMap(e=>{if(!e||typeof e!=`object`)return[];let{fromDocName:t,toDocName:n}=e;return typeof t==`string`&&typeof n==`string`?[{fromDocName:t,toDocName:n}]:[]}):[]}function ni(e){return Array.isArray(e)?e.flatMap(e=>{if(!e||typeof e!=`object`)return[];let{docName:t,rewrites:n}=e;return typeof t==`string`&&typeof n==`number`?[{docName:t,rewrites:n}]:[]}):[]}function ri(e,t,n=`${t}s`){return e===1?t:n}function ii(e,t){e.tool(`rename_document`,ei,{docName:j.string().describe(`Current document name`),newDocName:j.string().describe(`New document name`),cwd:j.string().optional().describe(N)},async e=>{let n=await R(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return P(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return P(I,!0);let a=z(e.docName);if(!a.ok)return P(a.error,!0);let o=z(e.newDocName);if(!o.ok)return P(o.error,!0);let s=t.identityRef?.current,c=await V(i,`/api/rename`,{docName:a.docName,newDocName:o.docName,...s?{agentId:s.connectionId,agentName:s.displayName,clientName:s.clientInfo?.name,colorSeed:s.colorSeed}:{}});if(!c.ok){let e=typeof c.error==`string`?c.error:`Rename failed`,t={ok:!1,error:e};return F(`Error: ${e}`,t,!0)}let l=ti(c.renamed),u=ni(c.rewrittenDocs),d=l.map(({fromDocName:e,toDocName:t})=>`${e} -> ${t}`).join(`, `)||`${a.docName} -> ${o.docName}`,f=u.length===0?`No inbound links required updates.`:`Rewrote ${u.length} ${ri(u.length,`document`)}.`,p={config:t.config,resolveCwd:t.resolveCwd},m=await H(o.docName,p,r),h=await H(a.docName,p,r),g={ok:!0,renamed:l,rewrittenDocs:u,previewUrl:m?.url??null,...m?{previewUrlSource:m.source}:{},...h?{previousPreviewUrl:h.url}:{}};return F(`Renamed ${d}. ${f}`,g)})}function ai(e,t){return`Research this topic and write provisional findings inside the project content directory. Research is **provisional, not canonical** — it captures findings, trade-offs, and open questions at a point in time. Promoting to canonical articles is a deliberate later step (via the \`consolidate\` tool).
326
+ Full convention: read \`${n}/AGENTS.md\`.`}const Hr=[`Bootstrap the project knowledge base by reading the codebase and writing initial knowledge articles grouped by topic.`,``,`**Use when:**`,`- Setting up a knowledge base for the first time in a repo`,`- Onboarding to a new codebase and capturing initial understanding`,`- The content directory is empty or sparse`,``,`**Triggers on:**`,`- "init content", "bootstrap knowledge base", "populate articles", "set up project knowledge"`,`- User asks to document or catalog the codebase`].join(`
327
+ `);function Ur(e,t){e.tool(`init-content`,Hr,{cwd:A.string().optional().describe(M)},async(e={})=>{let n=await I(t.resolveCwd,t.config,e.cwd);if(!n.ok)return N(`Error: ${n.error}`,!0);let{cwd:r,config:i}=n;return P(Vr(i.content.dir),{ui:qt({config:i,lockDir:a(o(i,r))})})})}const Wr=[`[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(`
328
+ `);function Gr(e,t){e.tool(`list_documents`,Wr,{dir:A.string().optional().describe(`Optional directory to filter documents`),cwd:A.string().optional().describe(M)},async e=>{let n=await L(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return N(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return N(F,!0);let a=await z(i,`/api/documents${e.dir?`?dir=${encodeURIComponent(e.dir)}`:``}`);if(!a.ok)return N(`Error: ${a.error}`,!0);let{ok:o,...s}=a,c=s,{resolve:l,ui:u}=await H(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 P(JSON.stringify(f,null,2),f)})}const Kr=[`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(`
329
+ `);function qr(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(`
330
+ `)}function Jr(e){if(!e||e.length===0)return``;let t=[``,`### Commit history (project git)`,``];for(let n of e){let e=n.hash.slice(0,7);t.push(`- ${e} ${n.date} ${n.authorName} — ${n.subject}`)}return t.join(`
331
+ `)}function Yr(e){if(!e||e.length===0)return``;let t=[``,`### Backlinks (${e.length})`,``];for(let n of e){let e=n.title?` — "${n.title}"`:``,r=n.snippet?` — "${n.snippet}"`:``;t.push(`- ${n.source}${e}${r}`)}return t.join(`
332
+ `)}function Xr(e){if(!e||e.length===0)return``;let t=[``,`### Forward links (${e.length})`,``];for(let n of e){if(n.kind===`external`){let e=n.title?` — "${n.title}"`:``,r=n.snippet?` — "${n.snippet}"`:``;t.push(`- ${n.url}${e}${r}`);continue}let e=n.title?` — "${n.title}"`:``,r=n.snippet?` — "${n.snippet}"`:``;t.push(`- ${n.docName}${e}${r}`)}return t.join(`
333
+ `)}function Zr(e){return e.replace(/^\.\//,``).replace(/^\/+/,``)}function Qr(e){return e.replace(/\.(md|mdx)$/i,``)}async function $r(e,t){let n=await L(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)throw Error(n.error);let{cwd:r,config:i,url:a}=n,o=Zr(e.path),s=k(r,o),c=i.mcp.tools.read_document.historyDepth,[l,u]=await Promise.all([we(s,`utf-8`),ir(o,{projectDir:r,serverUrl:a,historyDepth:c,folderRules:i.folders},{includeRichFields:!0})]),d=o.split(`/`).pop()?.replace(/\.md$/,``).replace(/\.mdx$/,``)??o,f=u.title??d,p=u.description??``,m=u.tags,h=[];h.push(`## ${f}`),p&&h.push(`**Description:** ${p}`),m.length>0&&h.push(`**Tags:** ${m.join(`, `)}`),h.push(`**Path:** ${o}`);let g=qr(u.history);g&&h.push(g);let _=Jr(u.projectHistory);_&&h.push(_);let v=Yr(u.backlinks);v&&h.push(v);let y=Xr(u.forwardLinks);return y&&h.push(y),h.push(``,`### Content`,``,l),h.join(`
334
+ `)}function ei(e,t){e.tool(`read_document`,Kr,{path:A.string().describe(`Project-root-relative path to the file`),since:A.string().optional().describe(`Reserved; currently unused (§15 Future Work)`),cwd:A.string().optional().describe("Absolute host path to resolve `path` against. Defaults only when the MCP client advertises exactly one root; otherwise pass `cwd` explicitly.")},async e=>{try{let n=await $r(e,t),r=await V(Qr(Zr(e.path)),{config:t.config,resolveCwd:t.resolveCwd},await t.resolveCwd(e.cwd));return r?P(n,{previewUrl:r.url,previewUrlSource:r.source}):P(n,{previewUrl:null})}catch(e){return N(`Error: ${e instanceof Error?e.message:String(e)}`,!0)}})}const ti=["[Requires: Hocuspocus server] Rename a document through the managed rename flow at `POST /api/rename`.",`Renames the target document and rewrites inbound wiki-links plus supported internal inline Markdown links in affected docs.`,``,`**Parameters:**`,"- `docName` — Current document name, typically without extension. A trailing `.md` or `.mdx` is stripped automatically.","- `newDocName` — New document name, typically without extension. A trailing `.md` or `.mdx` is stripped automatically.",'- `summary` — Optional one-line user-outcome description (≤80 chars). Appears as a bullet in the timeline. If omitted, a default like "Renamed X → Y" is generated. Provide your own summary to explain the why. Avoid including secrets or PII — summaries are persisted to git history.'].join(`
335
+ `);function ni(e){return Array.isArray(e)?e.flatMap(e=>{if(!e||typeof e!=`object`)return[];let{fromDocName:t,toDocName:n}=e;return typeof t==`string`&&typeof n==`string`?[{fromDocName:t,toDocName:n}]:[]}):[]}function ri(e){return Array.isArray(e)?e.flatMap(e=>{if(!e||typeof e!=`object`)return[];let{docName:t,rewrites:n}=e;return typeof t==`string`&&typeof n==`number`?[{docName:t,rewrites:n}]:[]}):[]}function ii(e,t,n=`${t}s`){return e===1?t:n}function ai(e,t){e.tool(`rename_document`,ti,{docName:A.string().describe(`Current document name`),newDocName:A.string().describe(`New document name`),summary:A.string().max(200).optional().describe(`Optional one-line user-outcome description (≤80 chars). Defaults to "Renamed X → Y" when omitted.`),cwd:A.string().optional().describe(M)},async e=>{let n=await L(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return N(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return N(F,!0);let a=R(e.docName);if(!a.ok)return N(a.error,!0);let o=R(e.newDocName);if(!o.ok)return N(o.error,!0);let s=t.identityRef?.current,c=await B(i,`/api/rename`,{docName:a.docName,newDocName:o.docName,...e.summary===void 0?{}:{summary:e.summary},...s?{agentId:s.connectionId,agentName:s.displayName,clientName:s.clientInfo?.name,colorSeed:s.colorSeed}:{}});if(!c.ok){let e=typeof c.error==`string`?c.error:`Rename failed`,t={ok:!1,error:e};return P(`Error: ${e}`,t,!0)}let l=ni(c.renamed),u=ri(c.rewrittenDocs),d=l.map(({fromDocName:e,toDocName:t})=>`${e} -> ${t}`).join(`, `)||`${a.docName} -> ${o.docName}`,f=u.length===0?`No inbound links required updates.`:`Rewrote ${u.length} ${ii(u.length,`document`)}.`,p={config:t.config,resolveCwd:t.resolveCwd},m=await V(o.docName,p,r),h=await V(a.docName,p,r),g=c.summary&&typeof c.summary==`object`?c.summary:void 0,_=typeof g?.hint==`string`?g.hint:void 0,v={ok:!0,renamed:l,rewrittenDocs:u,previewUrl:m?.url??null,...m?{previewUrlSource:m.source}:{},...h?{previousPreviewUrl:h.url}:{},...g?{summary:g}:{}},y=[`Renamed ${d}. ${f}`];return _&&y.push(_),P(y.join(`
336
+ `),v)})}function oi(e,t){return`Research this topic and write provisional findings inside the project content directory. Research is **provisional, not canonical** — it captures findings, trade-offs, and open questions at a point in time. Promoting to canonical articles is a deliberate later step (via the \`consolidate\` tool).
336
337
 
337
338
  Topic: ${e}
338
339
 
@@ -482,15 +483,16 @@ Research articles are discovery surfaces — they should link out to **every** r
482
483
  - **Don't skip \`ingest\`** — always capture raw sources first, then analyze
483
484
  - **Don't overwrite existing research** — if the topic was researched before, either iterate on the existing file or create a clearly-named successor (e.g., \`crdt-alternatives-2.md\`) and mark the old one as superseded
484
485
 
485
- Full convention: read \`${n}/AGENTS.md\`.`}const oi=[`Analyze a topic by gathering sources via ingest and writing provisional findings into the project content directory.`,`Provisional, not canonical — findings live here until decisions solidify.`,``,`**Use when:**`,`- Researching a topic before committing to an approach`,`- Exploring a decision space or comparing alternatives`,`- Synthesizing multiple sources into structured analysis`,`- Spec conversations and exploratory work that is not yet canonical`,``,`**Triggers on:**`,`- "research", "investigate", "compare options for", "analyze alternatives"`,`- User asks to explore trade-offs, gather evidence, or evaluate approaches`,`- A decision needs structured analysis grounded in external sources`].join(`
486
- `);function si(e,t){e.tool(`research`,oi,{topic:j.string().describe(`The topic, question, or anchor URL to research`),cwd:j.string().optional().describe(N)},async e=>{let n=await L(t.resolveCwd,t.config,e.cwd);return n.ok?F(ai(e.topic,n.config.content.dir),{previewUrl:null}):P(`Error: ${n.error}`,!0)})}const ci=[`[Requires: Hocuspocus server] Restore a document to a historical version via the CRDT layer.`,`The restore is append-only — it creates a new version with the old content,`,`preserving all history. All connected editors see the change in real-time.`,``,`**Parameters:**`,"- `docName` — Document name to restore, typically without extension. A trailing `.md` or `.mdx` is stripped automatically.","- `commitSha` — The 40-character SHA of the shadow repo commit to restore to."," Use `get_history` to find available versions."].join(`
487
- `);function li(e,t){e.tool(`rollback_to_version`,ci,{docName:j.string().describe(`Document name to restore`),commitSha:j.string().length(40).regex(/^[0-9a-f]+$/i).describe(`40-character commit SHA from the shadow repo timeline`),cwd:j.string().optional().describe(N)},async e=>{let n=await R(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return P(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return P(I,!0);let a=z(e.docName);if(!a.ok)return P(a.error,!0);let o=a.docName,s=await B(i,`/api/history/${e.commitSha}?docName=${encodeURIComponent(o)}`);if(!s.ok)return P(`Error: ${s.error??`Version not found`}`,!0);let c=t.identityRef?.current,l=await V(i,`/api/rollback`,{docName:o,commitSha:e.commitSha,...c?{agentId:c.connectionId,agentName:c.displayName,clientName:c.clientInfo?.name,colorSeed:c.colorSeed}:{}});if(!l.ok)return P(`Error: ${l.error}`,!0);let u=`Restored "${o}" to version ${e.commitSha.slice(0,8)} (${s.author}, ${s.timestamp}). The change has been applied to all connected editors.`,d=await H(o,{config:t.config,resolveCwd:t.resolveCwd},r);return F(u,{previewUrl:d?.url??null,...d?{previewUrlSource:d.source}:{}})})}const ui=[`[Requires: Hocuspocus server] Save a version checkpoint of all documents.`,`Creates a checkpoint commit in the shadow repo and project repo,`,`preserving the current state of all documents. The checkpoint can later`,"be found via `get_history` and restored via `rollback_to_version`."].join(`
488
- `);function di(e,t,n,r,i){e.tool(`save_version`,ui,{cwd:j.string().optional().describe(N)},async(e={})=>{let a=await R(r,t,n,e.cwd);if(!a.ok)return P(`Error: ${a.error}`,!0);if(!a.url)return P(I,!0);let{url:o}=a,s=i?.current,c=await V(o,`/api/save-version`,{...s?{writers:[{id:`agent-${s.connectionId}`,name:s.displayName,email:`agent-${s.connectionId}@openknowledge.local`}]}:{}});return c.ok?F(`Checkpoint saved. Checkpoint ref: ${c.checkpointRef}`,{checkpointRef:c.checkpointRef,previewUrl:null}):P(`Error: ${c.error}`,!0)})}const fi=[`Search wiki content with metadata-enriched results. Matches are grouped by file; each file is annotated with its title, description, and tags so you can judge relevance without opening it first.`,``,`**Use when:**`,`- Finding all articles mentioning a topic`,`- Locating a specific term across the wiki before deciding which file to read`,``,"Prefer this over your native `Grep` for wiki search — results include article metadata so you can skip irrelevant matches without extra reads.",``,`**Parameters:**`,"- `query` Literal text to search for (fixed-string match, no regex)","- `case_sensitive` (optional, default false) — case-sensitive match"].join(`
489
- `);function pi(e){let t=new Map;for(let n of e){let e=t.get(n.path);e?e.push(n):t.set(n.path,[n])}return[...t.entries()].map(([e,t])=>({path:e,matches:t}))}async function mi(e,t){let r=await R(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!r.ok)throw Error(r.error);let{cwd:i,config:a,url:o}=r,s=a.mcp.tools.search.maxResults,c=a.content.include,l=a.content.exclude,u=await fn(e.query,i,{caseInsensitive:!(e.case_sensitive??!1),include:c,exclude:[...l,`node_modules`,`.git`,`.claude`,`.changeset`,n],maxResults:s+1}),d=u.length>s,f=d?u.slice(0,s):u,{resolve:p,ui:m}=await U({config:a,resolveCwd:async()=>i},i);if(f.length===0)return{text:`No matches for "${e.query}".`,structured:{query:e.query,matchCount:0,fileCount:0,truncated:!1,results:[],ui:m,cwd:i}};let h=pi(f),g=new Map,_=a.folders;await Promise.all(h.map(async e=>{try{let t=await rr(e.path,{projectDir:i,serverUrl:o,folderRules:_});g.set(e.path,t)}catch{}}));let v=[];v.push(`## Search results for "${e.query}" (${f.length} match${f.length===1?``:`es`} in ${h.length} file${h.length===1?``:`s`})`,``);let y=[];for(let e of h){let t=g.get(e.path),n=t?.title??e.path;v.push(`### ${n} (${e.path})`),t?.tags?.length&&v.push(`Tags: ${t.tags.join(`, `)}`),t?.description&&v.push(`${t.description}`);for(let t of e.matches)v.push(`- Line ${t.line}: ${t.text}`);v.push(``);let r=Kt(e.path),i=p(r);y.push({path:e.path,docName:r,title:t?.title??null,description:t?.description??null,tags:t?.tags??[],matches:e.matches.map(e=>({line:e.line,text:e.text})),previewUrl:i?.url??null,...i?{previewUrlSource:i.source}:{}})}return d&&v.push(`_${f.length} of ${u.length}+ matches shown. Raise \`mcp.tools.search.maxResults\` in config.yml to see more._`),{text:v.join(`
490
- `),structured:{query:e.query,matchCount:f.length,fileCount:h.length,truncated:d,results:y,ui:m,cwd:i}}}function hi(e,t){e.tool(`search`,fi,{query:j.string().describe(`Literal text to search for`),case_sensitive:j.boolean().optional().describe(`Case-sensitive search (default false)`),cwd:j.string().optional().describe("Absolute host path to search in. Defaults only when the MCP client advertises exactly one root; otherwise pass `cwd` explicitly.")},async e=>{try{let{text:n,structured:r}=await mi(e,t);return r?F(n,r):P(n)}catch(e){return P(`Error: ${e instanceof Error?e.message:String(e)}`,!0)}})}const gi=[`[Requires: Hocuspocus server] Find missing link candidates for a target page.`,"Returns JSON with structure: `{ target: { docName, title, aliases }, mentions: [{ source, excerpt, offset }], truncated }`.","Each mention includes an `offset` you can pass to `edit_document` for precision patching.","When `truncated` is true, the scan hit its time budget before reading every admitted document.",``,`**Parameters:**`,'- `docName` — Target page docName, typically without extension (for example, "articles/project-alpha"). A trailing `.md` or `.mdx` is stripped automatically.'].join(`
491
- `);function _i(e,t){e.tool(`suggest_links`,gi,{docName:j.string().describe(`Target page docName`),cwd:j.string().optional().describe(N)},async e=>{let n=await R(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return P(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return P(I,!0);let a=z(e.docName);if(!a.ok)return P(a.error,!0);let o=await B(i,`/api/suggest-links?docName=${encodeURIComponent(a.docName)}`);if(!o.ok)return P(`Error: ${o.error}`,!0);let{ok:s,...c}=o,l=await H(a.docName,{config:t.config,resolveCwd:t.resolveCwd},r);return F(JSON.stringify(c,null,2),{...c,previewUrl:l?.url??null,...l?{previewUrlSource:l.source}:{}})})}const vi=["**IMPORTANT: Before calling this tool, you MUST first call `get_preview_url` and navigate to the returned URL in your preview browser. If `get_preview_url` returns null, start the server first (`open-knowledge start` or `preview_start`), then call `get_preview_url` again. Do NOT call this tool without the preview open. NEVER manually construct the URL.**",``,`[Requires: Hocuspocus server] Write markdown content to a document via the CRDT layer.`,`Content is applied through Hocuspocus and propagated to all connected editors in real-time.`,``,'**Link liberally.** Every noun-phrase that names another document in this knowledge base should be a `[[wiki-link]]`, not plain prose. Backlinks are the primary navigation surface — underlinked documents become islands. Redlinks (links to pages that don\'t exist yet) are fine; they signal "this should exist." Prefer `[[Page Name]]` over Markdown `[text](./page.md)` only wiki-links participate in the backlinks index.',``,`**Parameters:**`,'- `docName` — Document name, typically without extension (e.g., "my-doc" or "notes/meeting"). A trailing `.md` or `.mdx` is stripped automatically. New documents are created as `.md` by default; to create a `.mdx` file, first place it on disk, then use this tool for edits.',"- `markdown` — Markdown content to write",'- `position` — Where to insert: "append", "prepend", or "replace"'].join(`
492
- `);function yi(e,t){e.tool(`write_document`,vi,{docName:j.string().describe(`Document name to write to`),markdown:j.string().describe(`Markdown content to write`),position:j.enum([`append`,`prepend`,`replace`]).describe(`Where to insert the content`),cwd:j.string().optional().describe(N)},async e=>{let n=await R(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return P(`Error: ${n.error}`,!0);let{cwd:r,config:i,url:s}=n;if(!s)return P(I,!0);let c=z(e.docName);if(!c.ok)return P(c.error,!0);let l=t.identityRef?.current,u=await V(s,`/api/agent-write-md`,{docName:c.docName,markdown:e.markdown,position:e.position,...l?{agentId:l.connectionId,agentName:l.displayName,clientName:l.clientInfo?.name,colorSeed:l.colorSeed}:{}});if(!u.ok)return P(`Error: ${u.error}`,!0);let d=a(o(i,r)),f=W(c.docName,{config:i,lockDir:d}),p=(typeof u.subscriberCount==`number`?u.subscriberCount:void 0)===0,m=Array.isArray(u.hints)?u.hints:void 0,h=[`Written successfully (${e.position}).`];if(f&&h.push(`Preview: ${f.url}`),p&&h.push(f?`Warning: no preview is currently attached to "${c.docName}". Open ${f.url} to watch future edits live.`:`Warning: no preview is currently attached to "${c.docName}".`),m)for(let e of m)e.message&&h.push(e.message);let g=h.join(`
493
- `);if(!f&&!p&&!m)return P(g);let _={};return f&&(_.previewUrl=f.url,_.previewUrlSource=f.source),p&&(_.warning={message:`No preview attached to ${c.docName}.`,previewUrl:f?.url??null}),m&&(_.hints=m),F(g,_)})}const bi={exec:cr,"init-content":Vr,ingest:Rr,research:oi,consolidate:Bt,read_document:Gr,rename_document:ei,search:fi,suggest_links:gi,write_document:vi,edit_document:qt,get_history:Or,save_version:ui,rollback_to_version:ci,list_documents:Ur,get_backlinks:Sr,get_forward_links:Er,get_orphans:Mr,get_hubs:Ar,get_dead_links:wr,get_preview_url:Pr};function xi(e,t){let n=t.logger,r=It(e,{logger:t.logger,identityRef:t.identityRef}),i=e=>async r=>{try{let i=await t.resolveCwd(r);return(Ct()??n)?.debug(`tool cwd resolved`,{tool:e,cwd:i,...r?{explicit:r}:{}}),i}catch(t){throw(Ct()??n)?.warn(`tool call failed`,{tool:e,error:t instanceof Error?t.message:String(t),...r?{explicit:r}:{}}),t}};xr(r,{resolveCwd:i(`exec`),serverUrl:t.serverUrl,config:t.config}),Hr(r,{config:t.config,resolveCwd:i(`init-content`)}),zr(r,{config:t.config,resolveCwd:i(`ingest`)}),si(r,{config:t.config,resolveCwd:i(`research`)}),Vt(r,{config:t.config,resolveCwd:i(`consolidate`)}),$r(r,{resolveCwd:i(`read_document`),config:t.config,serverUrl:t.serverUrl}),hi(r,{resolveCwd:i(`search`),config:t.config,serverUrl:t.serverUrl}),_i(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`suggest_links`)}),yi(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`write_document`),identityRef:t.identityRef}),Jt(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`edit_document`),identityRef:t.identityRef}),ii(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`rename_document`),identityRef:t.identityRef}),kr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_history`)}),di(r,t.config,t.serverUrl,i(`save_version`),t.identityRef),li(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`rollback_to_version`),identityRef:t.identityRef}),Wr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`list_documents`)}),Cr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_backlinks`)}),Dr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_forward_links`)}),Nr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_orphans`)}),jr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_hubs`)}),Tr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_dead_links`)}),Ir(r,{resolveCwd:i(`get_preview_url`),config:t.config})}function Si(e){return e&&typeof e==`object`&&`code`in e&&typeof e.code==`string`?e.code:e instanceof Error&&e.name?e.name:typeof e}var Ci=class extends Error{};function wi(e){let t=y(e.startupCwd),n=null,r=null,i=async()=>n===null?(r||=(async()=>{let t=await e.listRoots(),r=await Promise.all(t.roots.map(async e=>e.uri.startsWith(`file://`)?await y(ye(e.uri)):null)),i=[...new Set(r.filter(e=>e!==null))];return n=i,e.logger?.info(`roots resolved`,{roots:i,count:i.length}),i})().finally(()=>{r=null}),await r):n;return{async resolveCwd(n){if(n){let t=await y(n);return e.logger?.debug(`cwd resolved`,{cwd:t,routing:`explicit`}),t}if(e.bypassProjectSelection){let n=await t;return e.logger?.debug(`cwd resolved`,{cwd:n,routing:`bypass`}),n}let r;try{r=await i()}catch(t){throw e.logger?.warn(`roots/list unavailable`,{error:t instanceof Error?t.message:String(t),errorType:Si(t)}),new Ci(`Client roots unavailable; pass cwd explicitly.`)}if(r.length===0)throw new Ci(`No client roots available; pass cwd explicitly.`);if(r.length>1)throw new Ci(`Multiple roots available; pass cwd explicitly.`);return e.logger?.debug(`cwd resolved`,{cwd:r[0],routing:`single-root`}),r[0]},invalidateRoots(){n=null,r=null,e.logger?.info(`roots cache invalidated`)}}}function Ti(e){let t=y(e.startupCwd),n;return{async resolveCwdForTools(t){let r=await e.resolveCwd(t);return n=r,r},async getKeepaliveCwd(){return e.bypassProjectSelection?await t:n}}}let X;function Ei(e,t){let{dir:n,include:r,exclude:i}=e.content,a=i.length>0?i.map(e=>`\`${e}\``).join(`, `):`(none)`,o=t?.dynamicConfig?`## Startup Project Content Layout (Informational)`:`## This project's content layout (live config)`,s=t?.dynamicConfig?"**Multi-project note:** tool calls resolve `.open-knowledge/config.yml` from the **effective cwd** for that invocation (explicit tool `cwd` → exactly one client root → error). The values below describe the MCP process startup project only; they are not a routing fallback.":"**Path contract (`config.yml`):** `.open-knowledge/config.yml` (plus optional `~/.open-knowledge/config.yml`, with CLI/env overrides) owns the `content` keys. The table above is **this MCP session's resolved view** of that contract — same rules, no guessing from folder names. A file is an Open Knowledge document iff it lives under **Content directory**, matches at least one **Include glob**, and is not removed by **Exclude globs** or `.gitignore`.";return`# MCP Instructions v2 — exec-primary (2026-04-13)
486
+ Full convention: read \`${n}/AGENTS.md\`.`}const si=[`Analyze a topic by gathering sources via ingest and writing provisional findings into the project content directory.`,`Provisional, not canonical — findings live here until decisions solidify.`,``,`**Use when:**`,`- Researching a topic before committing to an approach`,`- Exploring a decision space or comparing alternatives`,`- Synthesizing multiple sources into structured analysis`,`- Spec conversations and exploratory work that is not yet canonical`,``,`**Triggers on:**`,`- "research", "investigate", "compare options for", "analyze alternatives"`,`- User asks to explore trade-offs, gather evidence, or evaluate approaches`,`- A decision needs structured analysis grounded in external sources`].join(`
487
+ `);function ci(e,t){e.tool(`research`,si,{topic:A.string().describe(`The topic, question, or anchor URL to research`),cwd:A.string().optional().describe(M)},async e=>{let n=await I(t.resolveCwd,t.config,e.cwd);return n.ok?P(oi(e.topic,n.config.content.dir),{previewUrl:null}):N(`Error: ${n.error}`,!0)})}const li=[`[Requires: Hocuspocus server] Restore a document to a historical version via the CRDT layer.`,`The restore is append-only — it creates a new version with the old content,`,`preserving all history. All connected editors see the change in real-time.`,``,`**Parameters:**`,"- `docName` — Document name to restore, typically without extension. A trailing `.md` or `.mdx` is stripped automatically.","- `commitSha` — The 40-character SHA of the shadow repo commit to restore to."," Use `get_history` to find available versions.",'- `summary` — Optional one-line user-outcome description (≤80 chars). Appears as a bullet in the timeline. If omitted, a default like "Restored to <sha-short>" is generated. Provide your own summary to explain the why. Avoid including secrets or PII — summaries are persisted to git history.'].join(`
488
+ `);function ui(e,t){e.tool(`rollback_to_version`,li,{docName:A.string().describe(`Document name to restore`),commitSha:A.string().length(40).regex(/^[0-9a-f]+$/i).describe(`40-character commit SHA from the shadow repo timeline`),summary:A.string().max(200).optional().describe(`Optional one-line user-outcome description (≤80 chars). Defaults to "Restored to <sha-short>" when omitted.`),cwd:A.string().optional().describe(M)},async e=>{let n=await L(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return N(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return N(F,!0);let a=R(e.docName);if(!a.ok)return N(a.error,!0);let o=a.docName,s=await z(i,`/api/history/${e.commitSha}?docName=${encodeURIComponent(o)}`);if(!s.ok)return N(`Error: ${s.error??`Version not found`}`,!0);let c=t.identityRef?.current,l=await B(i,`/api/rollback`,{docName:o,commitSha:e.commitSha,...e.summary===void 0?{}:{summary:e.summary},...c?{agentId:c.connectionId,agentName:c.displayName,clientName:c.clientInfo?.name,colorSeed:c.colorSeed}:{}});if(!l.ok)return N(`Error: ${l.error}`,!0);let u=l.summary&&typeof l.summary==`object`?l.summary:void 0,d=typeof u?.hint==`string`?u.hint:void 0,f=[`Restored "${o}" to version ${e.commitSha.slice(0,8)} (${s.author}, ${s.timestamp}). The change has been applied to all connected editors.`];d&&f.push(d);let p=await V(o,{config:t.config,resolveCwd:t.resolveCwd},r);return P(f.join(`
489
+ `),{previewUrl:p?.url??null,...p?{previewUrlSource:p.source}:{},...u?{summary:u}:{}})})}const di=[`[Requires: Hocuspocus server] Save a version checkpoint of all documents.`,`Creates a checkpoint commit in the shadow repo and project repo,`,`preserving the current state of all documents. The checkpoint can later`,"be found via `get_history` and restored via `rollback_to_version`."].join(`
490
+ `);function fi(e,t,n,r,i){e.tool(`save_version`,di,{cwd:A.string().optional().describe(M)},async(e={})=>{let a=await L(r,t,n,e.cwd);if(!a.ok)return N(`Error: ${a.error}`,!0);if(!a.url)return N(F,!0);let{url:o}=a,s=i?.current,c=await B(o,`/api/save-version`,{...s?{writers:[{id:`agent-${s.connectionId}`,name:s.displayName,email:`agent-${s.connectionId}@openknowledge.local`}]}:{}});return c.ok?P(`Checkpoint saved. Checkpoint ref: ${c.checkpointRef}`,{checkpointRef:c.checkpointRef,previewUrl:null}):N(`Error: ${c.error}`,!0)})}const pi=[`Search wiki content with metadata-enriched results. Matches are grouped by file; each file is annotated with its title, description, and tags so you can judge relevance without opening it first.`,``,`**Use when:**`,`- Finding all articles mentioning a topic`,`- Locating a specific term across the wiki before deciding which file to read`,``,"Prefer this over your native `Grep` for wiki search results include article metadata so you can skip irrelevant matches without extra reads.",``,`**Parameters:**`,"- `query` Literal text to search for (fixed-string match, no regex)","- `case_sensitive` (optional, default false) — case-sensitive match"].join(`
491
+ `);function mi(e){let t=new Map;for(let n of e){let e=t.get(n.path);e?e.push(n):t.set(n.path,[n])}return[...t.entries()].map(([e,t])=>({path:e,matches:t}))}async function hi(e,t){let r=await L(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!r.ok)throw Error(r.error);let{cwd:i,config:a,url:o}=r,s=a.mcp.tools.search.maxResults,c=a.content.include,l=a.content.exclude,u=await pn(e.query,i,{caseInsensitive:!(e.case_sensitive??!1),include:c,exclude:[...l,`node_modules`,`.git`,`.claude`,`.changeset`,n],maxResults:s+1}),d=u.length>s,f=d?u.slice(0,s):u,{resolve:p,ui:m}=await H({config:a,resolveCwd:async()=>i},i);if(f.length===0)return{text:`No matches for "${e.query}".`,structured:{query:e.query,matchCount:0,fileCount:0,truncated:!1,results:[],ui:m,cwd:i}};let h=mi(f),g=new Map,_=a.folders;await Promise.all(h.map(async e=>{try{let t=await ir(e.path,{projectDir:i,serverUrl:o,folderRules:_});g.set(e.path,t)}catch{}}));let v=[];v.push(`## Search results for "${e.query}" (${f.length} match${f.length===1?``:`es`} in ${h.length} file${h.length===1?``:`s`})`,``);let y=[];for(let e of h){let t=g.get(e.path),n=t?.title??e.path;v.push(`### ${n} (${e.path})`),t?.tags?.length&&v.push(`Tags: ${t.tags.join(`, `)}`),t?.description&&v.push(`${t.description}`);for(let t of e.matches)v.push(`- Line ${t.line}: ${t.text}`);v.push(``);let r=Jt(e.path),i=p(r);y.push({path:e.path,docName:r,title:t?.title??null,description:t?.description??null,tags:t?.tags??[],matches:e.matches.map(e=>({line:e.line,text:e.text})),previewUrl:i?.url??null,...i?{previewUrlSource:i.source}:{}})}return d&&v.push(`_${f.length} of ${u.length}+ matches shown. Raise \`mcp.tools.search.maxResults\` in config.yml to see more._`),{text:v.join(`
492
+ `),structured:{query:e.query,matchCount:f.length,fileCount:h.length,truncated:d,results:y,ui:m,cwd:i}}}function gi(e,t){e.tool(`search`,pi,{query:A.string().describe(`Literal text to search for`),case_sensitive:A.boolean().optional().describe(`Case-sensitive search (default false)`),cwd:A.string().optional().describe("Absolute host path to search in. Defaults only when the MCP client advertises exactly one root; otherwise pass `cwd` explicitly.")},async e=>{try{let{text:n,structured:r}=await hi(e,t);return r?P(n,r):N(n)}catch(e){return N(`Error: ${e instanceof Error?e.message:String(e)}`,!0)}})}const _i=[`[Requires: Hocuspocus server] Find missing link candidates for a target page.`,"Returns JSON with structure: `{ target: { docName, title, aliases }, mentions: [{ source, excerpt, offset }], truncated }`.","Each mention includes an `offset` you can pass to `edit_document` for precision patching.","When `truncated` is true, the scan hit its time budget before reading every admitted document.",``,`**Parameters:**`,'- `docName` — Target page docName, typically without extension (for example, "articles/project-alpha"). A trailing `.md` or `.mdx` is stripped automatically.'].join(`
493
+ `);function vi(e,t){e.tool(`suggest_links`,_i,{docName:A.string().describe(`Target page docName`),cwd:A.string().optional().describe(M)},async e=>{let n=await L(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return N(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return N(F,!0);let a=R(e.docName);if(!a.ok)return N(a.error,!0);let o=await z(i,`/api/suggest-links?docName=${encodeURIComponent(a.docName)}`);if(!o.ok)return N(`Error: ${o.error}`,!0);let{ok:s,...c}=o,l=await V(a.docName,{config:t.config,resolveCwd:t.resolveCwd},r);return P(JSON.stringify(c,null,2),{...c,previewUrl:l?.url??null,...l?{previewUrlSource:l.source}:{}})})}const yi=["**IMPORTANT: Before calling this tool, you MUST first call `get_preview_url` and navigate to the returned URL in your preview browser. If `get_preview_url` returns null, start the server first (`open-knowledge start` or `preview_start`), then call `get_preview_url` again. Do NOT call this tool without the preview open. NEVER manually construct the URL.**",``,`[Requires: Hocuspocus server] Write markdown content to a document via the CRDT layer.`,`Content is applied through Hocuspocus and propagated to all connected editors in real-time.`,``,'**Link liberally.** Every noun-phrase that names another document in this knowledge base should be a `[[wiki-link]]`, not plain prose. Backlinks are the primary navigation surface — underlinked documents become islands. Redlinks (links to pages that don\'t exist yet) are fine; they signal "this should exist." Prefer `[[Page Name]]` over Markdown `[text](./page.md)` — only wiki-links participate in the backlinks index.',``,`**Parameters:**`,'- `docName` — Document name, typically without extension (e.g., "my-doc" or "notes/meeting"). A trailing `.md` or `.mdx` is stripped automatically. New documents are created as `.md` by default; to create a `.mdx` file, first place it on disk, then use this tool for edits.',"- `markdown` Markdown content to write",'- `position` — Where to insert: "append", "prepend", or "replace"','- `summary` — Optional one-line user-outcome description of this edit (≤80 chars). Appears as a bullet in the document timeline so readers can scan intent without opening every diff. Prefer outcome phrasing ("Fixed token-refresh race") over structural ("Added 3 lines"). Avoid including secrets or PII — summaries are persisted to git history.'].join(`
494
+ `);function bi(e,t){e.tool(`write_document`,yi,{docName:A.string().describe(`Document name to write to`),markdown:A.string().describe(`Markdown content to write`),position:A.enum([`append`,`prepend`,`replace`]).describe(`Where to insert the content`),summary:Rt,cwd:A.string().optional().describe(M)},async e=>{let n=await L(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return N(`Error: ${n.error}`,!0);let{cwd:r,config:i,url:s}=n;if(!s)return N(F,!0);let c=R(e.docName);if(!c.ok)return N(c.error,!0);let l=t.identityRef?.current,u=await B(s,`/api/agent-write-md`,{docName:c.docName,markdown:e.markdown,position:e.position,...e.summary===void 0?{}:{summary:e.summary},...l?{agentId:l.connectionId,agentName:l.displayName,clientName:l.clientInfo?.name,colorSeed:l.colorSeed}:{}});if(!u.ok)return N(`Error: ${u.error}`,!0);let d=a(o(i,r)),f=U(c.docName,{config:i,lockDir:d}),p=(typeof u.subscriberCount==`number`?u.subscriberCount:void 0)===0,m=Array.isArray(u.hints)?u.hints:void 0,h=u.summary&&typeof u.summary==`object`?u.summary:void 0,g=typeof h?.hint==`string`?h.hint:void 0,_=[`Written successfully (${e.position}).`];if(f&&_.push(`Preview: ${f.url}`),p&&_.push(f?`Warning: no preview is currently attached to "${c.docName}". Open ${f.url} to watch future edits live.`:`Warning: no preview is currently attached to "${c.docName}".`),g&&_.push(g),m)for(let e of m)e.message&&_.push(e.message);let v=_.join(`
495
+ `);if(!f&&!p&&!m&&!h)return N(v);let y={};return f&&(y.previewUrl=f.url,y.previewUrlSource=f.source),p&&(y.warning={message:`No preview attached to ${c.docName}.`,previewUrl:f?.url??null}),m&&(y.hints=m),h&&(y.summary=h),P(v,y)})}const xi={exec:lr,"init-content":Hr,ingest:zr,research:si,consolidate:Ht,read_document:Kr,rename_document:ti,search:pi,suggest_links:_i,write_document:yi,edit_document:Yt,get_history:kr,save_version:di,rollback_to_version:li,list_documents:Wr,get_backlinks:Cr,get_forward_links:Dr,get_orphans:Nr,get_hubs:jr,get_dead_links:Tr,get_preview_url:Fr};function Si(e,t){let n=t.logger,r=Lt(e,{logger:t.logger,identityRef:t.identityRef}),i=e=>async r=>{try{let i=await t.resolveCwd(r);return(wt()??n)?.debug(`tool cwd resolved`,{tool:e,cwd:i,...r?{explicit:r}:{}}),i}catch(t){throw(wt()??n)?.warn(`tool call failed`,{tool:e,error:t instanceof Error?t.message:String(t),...r?{explicit:r}:{}}),t}};Sr(r,{resolveCwd:i(`exec`),serverUrl:t.serverUrl,config:t.config}),Ur(r,{config:t.config,resolveCwd:i(`init-content`)}),Br(r,{config:t.config,resolveCwd:i(`ingest`)}),ci(r,{config:t.config,resolveCwd:i(`research`)}),Ut(r,{config:t.config,resolveCwd:i(`consolidate`)}),ei(r,{resolveCwd:i(`read_document`),config:t.config,serverUrl:t.serverUrl}),gi(r,{resolveCwd:i(`search`),config:t.config,serverUrl:t.serverUrl}),vi(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`suggest_links`)}),bi(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`write_document`),identityRef:t.identityRef}),Xt(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`edit_document`),identityRef:t.identityRef}),ai(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`rename_document`),identityRef:t.identityRef}),Ar(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_history`)}),fi(r,t.config,t.serverUrl,i(`save_version`),t.identityRef),ui(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`rollback_to_version`),identityRef:t.identityRef}),Gr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`list_documents`)}),wr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_backlinks`)}),Or(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_forward_links`)}),Pr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_orphans`)}),Mr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_hubs`)}),Er(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_dead_links`)}),Lr(r,{resolveCwd:i(`get_preview_url`),config:t.config})}function Ci(e){return e&&typeof e==`object`&&`code`in e&&typeof e.code==`string`?e.code:e instanceof Error&&e.name?e.name:typeof e}var wi=class extends Error{};function Ti(e){let t=y(e.startupCwd),n=null,r=null,i=async()=>n===null?(r||=(async()=>{let t=await e.listRoots(),r=await Promise.all(t.roots.map(async e=>e.uri.startsWith(`file://`)?await y(ye(e.uri)):null)),i=[...new Set(r.filter(e=>e!==null))];return n=i,e.logger?.info(`roots resolved`,{roots:i,count:i.length}),i})().finally(()=>{r=null}),await r):n;return{async resolveCwd(n){if(n){let t=await y(n);return e.logger?.debug(`cwd resolved`,{cwd:t,routing:`explicit`}),t}if(e.bypassProjectSelection){let n=await t;return e.logger?.debug(`cwd resolved`,{cwd:n,routing:`bypass`}),n}let r;try{r=await i()}catch(t){throw e.logger?.warn(`roots/list unavailable`,{error:t instanceof Error?t.message:String(t),errorType:Ci(t)}),new wi(`Client roots unavailable; pass cwd explicitly.`)}if(r.length===0)throw new wi(`No client roots available; pass cwd explicitly.`);if(r.length>1)throw new wi(`Multiple roots available; pass cwd explicitly.`);return e.logger?.debug(`cwd resolved`,{cwd:r[0],routing:`single-root`}),r[0]},invalidateRoots(){n=null,r=null,e.logger?.info(`roots cache invalidated`)}}}function Ei(e){let t=y(e.startupCwd),n;return{async resolveCwdForTools(t){let r=await e.resolveCwd(t);return n=r,r},async getKeepaliveCwd(){return e.bypassProjectSelection?await t:n}}}let X;function Di(e,t){let{dir:n,include:r,exclude:i}=e.content,a=i.length>0?i.map(e=>`\`${e}\``).join(`, `):`(none)`,o=t?.dynamicConfig?`## Startup Project Content Layout (Informational)`:`## This project's content layout (live config)`,s=t?.dynamicConfig?"**Multi-project note:** tool calls resolve `.open-knowledge/config.yml` from the **effective cwd** for that invocation (explicit tool `cwd` → exactly one client root → error). The values below describe the MCP process startup project only; they are not a routing fallback.":"**Path contract (`config.yml`):** `.open-knowledge/config.yml` (plus optional `~/.open-knowledge/config.yml`, with CLI/env overrides) owns the `content` keys. The table above is **this MCP session's resolved view** of that contract — same rules, no guessing from folder names. A file is an Open Knowledge document iff it lives under **Content directory**, matches at least one **Include glob**, and is not removed by **Exclude globs** or `.gitignore`.";return`# MCP Instructions v2 — exec-primary (2026-04-13)
494
496
 
495
497
  ${o}
496
498
 
@@ -588,9 +590,9 @@ Folder metadata lives in \`config.yml\`, **not** in content files — this is in
588
590
  **Typed call sites (advanced) — prefer \`exec\` for common reads:**
589
591
  - \`read_document\`, \`search\`, \`list_documents\`, \`get_backlinks\`, \`get_forward_links\`, \`get_orphans\`, \`get_hubs\`.
590
592
 
591
- ${Object.entries(bi).map(([e,t])=>`### \`${e}\`\n${t}`).join(`
593
+ ${Object.entries(xi).map(([e,t])=>`### \`${e}\`\n${t}`).join(`
592
594
 
593
595
  `)}
594
- `}async function Di(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 Oi(n){let{projectDir:r,serverUrl:i,config:a,startupConfig:o,bypassProjectSelection:s=!1}=n;if(X=xt(),X.info(`MCP server starting`,{startupCwd:r,bypassProjectSelection:s,serverUrlType:typeof i==`string`?`explicit`:`lazy`}),typeof i==`string`){let e=await Di(i,X);X.info(`Hocuspocus detection complete`,{serverUrl:i,available:e})}else X.info(`server discovery is lazy per effective cwd`);let c=new ke({name:t,version:e},{instructions:Ei(o,{dynamicConfig:typeof a==`function`&&!s})}),l=wi({startupCwd:r,bypassProjectSelection:s,listRoots:()=>c.server.listRoots(),logger:X}),u=Ti({startupCwd:r,resolveCwd:l.resolveCwd,bypassProjectSelection:s}),d=u.resolveCwdForTools;c.server.setNotificationHandler(je,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=be(),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})},xi(c,{serverUrl:f,resolveCwd:d,config:a,identityRef:h,logger:X});let g=new Ae;await c.connect(g),X.info(`MCP server running on stdio`);let{startKeepalive:_}=await import(`./keepalive-UJIeMtkM.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 ki(e){if(e===void 0||e===``)return;let t=Number.parseInt(e,10);if(!(Number.isNaN(t)||t<=0))return t}function Ai(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 ji(e){let t=e.readLock??(()=>h(e.lockDir)),n=e.isAlive??m,r=e.sleep??(e=>new Promise(t=>setTimeout(t,e))),i=e.spawn??Se,a=e.readErrorLog??(e=>E(e)?O(e,`utf-8`).trim():``),o=e.openErrorLog??(e=>te(e,`w`)),s=e.closeFd??(e=>T(e)),c=e.timeoutMs??5e3,l=e.pollIntervalMs??100,u=Ai({host:e.host,portOverride:e.portOverride,envAutoStart:e.envAutoStart,configAutoStart:e.configAutoStart,readLock:t,isAlive:n});if(e.logger?.info(`auto-start decision`,{action:u.action,message:u.message,contentDir:e.contentDir}),u.action===`connect`)return{serverUrl:u.url,message:u.message};if(u.action===`disk-only`)return{serverUrl:void 0,message:u.message};E(e.lockDir)||D(e.lockDir,{recursive:!0});let d=fe(e.lockDir,`last-spawn-error.log`),f=o(d),p,g,_=S();e.logger?.info(`spawning server`,{command:_.command,cwd:e.contentDir,timeoutMs:c});try{try{p=i(_.command,[..._.prefixArgs,`start`],{detached:!0,stdio:[`ignore`,`ignore`,f],cwd:e.contentDir}),p.on(`error`,e=>{g=e instanceof Error?e.message:String(e)}),p.unref()}catch(e){g=e instanceof Error?e.message:String(e)}}finally{try{s(f)}catch{}}let v=Date.now()+c;for(;Date.now()<v;){if(g){let t=a(d);throw e.logger?.error(`spawn failed`,void 0,{error:g,stderr:t}),Error(`Error: spawn failed: ${g}${t?` stderr:\n${t}`:``}`)}await r(l);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(d);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(d),ee=(c/1e3).toFixed(c%1e3==0?0:2),b=p?.pid,x=``;throw typeof b==`number`&&(x=n(b)?` child pid=${b} is still running — raise OK_MCP_SPAWN_TIMEOUT_MS if this is a slow boot.`:` child pid=${b} exited — check last-spawn-error.log.`),e.logger?.error(`spawn poll timeout`,void 0,{timeoutMs:c,childPid:b,childAlive:typeof b==`number`?n(b):void 0,stderr:y||void 0}),Error(`Error: server did not start within ${ee}s.${x}${y?` stderr:\n${y}`:``}`)}function Mi(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??ji,n=e.cacheMs??1e3,r=new Map,i=new Map;return async s=>{let c=await y(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 Ni(e){return new C(`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=b({startupCwd:r,startupConfig:n}),a=ki(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=Mi({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 Oi({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 Pi(e){return new C(`preview`).description(`Show what content the watcher will track (read-only)`).action(async()=>{let{previewContent:t,formatPreviewBlock:n}=await import(`./preview-C9MefMSi.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 Fi(e,t,n=process.cwd()){let r=e.op??`sync`,i=h(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=A({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
595
- `)),Z(e.json,{type:`complete`,op:r}),e.json||process.stderr.write(`✓ ${r} complete\n`)}function Ii(e){return new C(`sync`).description(`Commit, pull, and push to the remote`).option(`--json`,`Output JSONL progress events`,!1).action(async t=>{try{await Fi({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 Li(e){return new C(`pull`).description(`Pull changes from the remote`).option(`--json`,`Output JSONL progress events`,!1).action(async t=>{try{await Fi({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 Ri(e){return new C(`push`).description(`Push commits to the remote`).option(`--json`,`Output JSONL progress events`,!1).action(async t=>{try{await Fi({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)}})}function zi(e,t){return{server:Bi(`server`,e),ui:Bi(`ui`,t)}}function Bi(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 Vi(e){return`${Hi(e.server)}\n${Hi(e.ui)}`}function Hi(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 Ui(e){let t=e.inspect??(t=>ot(e.lockDir,t)),n=e.log??(e=>console.log(e)),r=zi(t(`server`),t(`ui`));return e.json?n(JSON.stringify(r,null,2)):n(Vi(r)),r}function Wi(e){return new C(`status`).description(`Show live state of the server + ui lockfiles for this project`).option(`--json`,`Emit structured JSON instead of formatted text`).action(t=>{Ui({lockDir:a(o(e(),process.cwd())),json:t.json===!0})})}function Gi(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 Ki(e){let t=e.inspect??(t=>ot(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=Gi(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 qi(e){return new C(`stop`).description(`Stop the running open-knowledge server and UI (live only)`).action(()=>{Ki({lockDir:a(o(e(),process.cwd()))}).failed.length>0&&(process.exitCode=1)})}const Ji=1e4,Yi=[`connection`,`keep-alive`,`proxy-authenticate`,`proxy-authorization`,`te`,`trailer`,`transfer-encoding`,`upgrade`,`cookie`,`set-cookie`];async function Xi(e){let t=e.upstreamTimeoutMs??Ji,n=Ee((n,r)=>{Qi(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 Zi(e,t,n){Qi(e,t,n.upstreamHost,n.upstreamPort,n.upstreamTimeoutMs??Ji)}function Qi(e,t,n,r,i){let a={...e.headers};delete a.host;for(let e of Yi)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=De({host:n,port:r,method:e.method,path:e.url,headers:{...a,host:`${n}:${r}`}},e=>{let n={...e.headers};for(let e of Yi)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 $i(e){await Promise.all(e.map(e=>new Promise(t=>{e.close(()=>t())})))}async function ea(e){let{existsSync:t}=await import(`node:fs`),{createServer:n}=await import(`node:http`),{resolve:i}=await import(`node:path`),{acquireUiLock:a,readServerLock:o,releaseUiLock:s,updateUiLockPort:c}=await import(`./src-FL3Ydra9.mjs`),{default:l}=await import(`sirv`),{resolveContentDir:u,resolveLockDir:d}=await import(`./paths-DKYbHoVz.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,ee=(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}Zi(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):ta(t)});return}if(g){g(e,t);return}ta(t)},b=e.host===void 0?[`::1`,`127.0.0.1`]:[e.host],x=[],S=e.port;try{for(let e of b){let t=n(ee);x.push(t),await new Promise((n,r)=>{let i=e=>r(e);t.once(`error`,i),t.listen(S,e,()=>{t.off(`error`,i);let e=t.address();typeof e==`object`&&e&&(S=e.port),n()})})}}catch(e){await Promise.all(x.map(e=>new Promise(t=>{try{e.close(()=>t())}catch{t()}})));try{s(p)}catch{}throw e}let C=S;v=C,c(p,C);let w=e.scheduler??r,T=e.safetyNetMs??432e5,E=null,D=!1,te=!1,O=()=>{D||(D=!0,E!==null&&(w.clearTimeout(E),E=null))},ne=()=>{if(O(),!te){te=!0;try{s(p)}catch{}}},re=()=>{D||T<=0||(E!==null&&(w.clearTimeout(E),E=null),E=w.setTimeout(()=>{E=null,console.warn(`[ui] safety-net (${T}ms) reached — shutting down (D-025 backstop)`);try{e.onSafetyNet?.()}catch{}for(let e of x)try{e.close()}catch{}ne()},T))},ie=()=>{D||T<=0||re()};return y=ie,re(),{httpServers:x,port:C,release:ne,detachSafetyNet:O,nudgeSafetyNet:ie}}function ta(e){e.writeHead(404),e.end(`Not found`)}function na(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 ra(e){let t=e.readLock??(async()=>{let{readUiLock:t}=await import(`./src-FL3Ydra9.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 Xi({listenPort:e.requestedPort,host:e.host,upstreamHost:`localhost`,upstreamPort:r}),upstreamPort:r}}function ia(e){return new C(`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-xJqYhQcY.mjs`),{UiLockCollisionError:r}=await import(`./src-FL3Ydra9.mjs`),{resolveContentDir:i,resolveLockDir:a}=await import(`./paths-DKYbHoVz.mjs`),o=e(),s=t.host,c;try{c=na(t.port,process.env.PORT)}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exitCode=1;return}try{let e=await ea({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)}};$i(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 ra({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 C;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}=ee(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 aa=x(()=>$);Q.addCommand(aa,{isDefault:!0});const oa=Ni(()=>$);Q.addCommand(oa),Q.addCommand(v());const sa=Pi(()=>$);Q.addCommand(sa);const ca=ia(()=>$);Q.addCommand(ca),Q.addCommand(qi(()=>$)),Q.addCommand(lt(()=>$)),Q.addCommand(Wi(()=>$)),Q.addCommand(at(()=>$)),Q.addCommand(vt(()=>$)),Q.addCommand(Ii(()=>$)),Q.addCommand(Ri(()=>$)),Q.addCommand(Li(()=>$)),await Q.parseAsync();export{};
596
+ `}async function Oi(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 ki(n){let{projectDir:r,serverUrl:i,config:a,startupConfig:o,bypassProjectSelection:s=!1}=n;if(X=St(),X.info(`MCP server starting`,{startupCwd:r,bypassProjectSelection:s,serverUrlType:typeof i==`string`?`explicit`:`lazy`}),typeof i==`string`){let e=await Oi(i,X);X.info(`Hocuspocus detection complete`,{serverUrl:i,available:e})}else X.info(`server discovery is lazy per effective cwd`);let c=new Ae({name:t,version:e},{instructions:Di(o,{dynamicConfig:typeof a==`function`&&!s})}),l=Ti({startupCwd:r,bypassProjectSelection:s,listRoots:()=>c.server.listRoots(),logger:X}),u=Ei({startupCwd:r,resolveCwd:l.resolveCwd,bypassProjectSelection:s}),d=u.resolveCwdForTools;c.server.setNotificationHandler(Me,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=be(),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})},Si(c,{serverUrl:f,resolveCwd:d,config:a,identityRef:h,logger:X});let g=new je;await c.connect(g),X.info(`MCP server running on stdio`);let{startKeepalive:_}=await import(`./keepalive-UJIeMtkM.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 Ai(e){if(e===void 0||e===``)return;let t=Number.parseInt(e,10);if(!(Number.isNaN(t)||t<=0))return t}function ji(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 Mi(e){let t=e.readLock??(()=>h(e.lockDir)),n=e.isAlive??m,r=e.sleep??(e=>new Promise(t=>setTimeout(t,e))),i=e.spawn??Se,a=e.readErrorLog??(e=>E(e)?O(e,`utf-8`).trim():``),o=e.openErrorLog??(e=>te(e,`w`)),s=e.closeFd??(e=>T(e)),c=e.timeoutMs??5e3,l=e.pollIntervalMs??100,u=ji({host:e.host,portOverride:e.portOverride,envAutoStart:e.envAutoStart,configAutoStart:e.configAutoStart,readLock:t,isAlive:n});if(e.logger?.info(`auto-start decision`,{action:u.action,message:u.message,contentDir:e.contentDir}),u.action===`connect`)return{serverUrl:u.url,message:u.message};if(u.action===`disk-only`)return{serverUrl:void 0,message:u.message};E(e.lockDir)||D(e.lockDir,{recursive:!0});let d=fe(e.lockDir,`last-spawn-error.log`),f=o(d),p,g,_=S();e.logger?.info(`spawning server`,{command:_.command,cwd:e.contentDir,timeoutMs:c});try{try{p=i(_.command,[..._.prefixArgs,`start`],{detached:!0,stdio:[`ignore`,`ignore`,f],cwd:e.contentDir}),p.on(`error`,e=>{g=e instanceof Error?e.message:String(e)}),p.unref()}catch(e){g=e instanceof Error?e.message:String(e)}}finally{try{s(f)}catch{}}let v=Date.now()+c;for(;Date.now()<v;){if(g){let t=a(d);throw e.logger?.error(`spawn failed`,void 0,{error:g,stderr:t}),Error(`Error: spawn failed: ${g}${t?` stderr:\n${t}`:``}`)}await r(l);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(d);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(d),ee=(c/1e3).toFixed(c%1e3==0?0:2),b=p?.pid,x=``;throw typeof b==`number`&&(x=n(b)?` child pid=${b} is still running — raise OK_MCP_SPAWN_TIMEOUT_MS if this is a slow boot.`:` child pid=${b} exited — check last-spawn-error.log.`),e.logger?.error(`spawn poll timeout`,void 0,{timeoutMs:c,childPid:b,childAlive:typeof b==`number`?n(b):void 0,stderr:y||void 0}),Error(`Error: server did not start within ${ee}s.${x}${y?` stderr:\n${y}`:``}`)}function Ni(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??Mi,n=e.cacheMs??1e3,r=new Map,i=new Map;return async s=>{let c=await y(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 Pi(e){return new C(`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=b({startupCwd:r,startupConfig:n}),a=Ai(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=Ni({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 ki({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 Fi(e){return new C(`preview`).description(`Show what content the watcher will track (read-only)`).action(async()=>{let{previewContent:t,formatPreviewBlock:n}=await import(`./preview-DMG2O5V6.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 Ii(e,t,n=process.cwd()){let r=e.op??`sync`,i=h(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=Ce({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
597
+ `)),Z(e.json,{type:`complete`,op:r}),e.json||process.stderr.write(`✓ ${r} complete\n`)}function Li(e){return new C(`sync`).description(`Commit, pull, and push to the remote`).option(`--json`,`Output JSONL progress events`,!1).action(async t=>{try{await Ii({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 Ri(e){return new C(`pull`).description(`Pull changes from the remote`).option(`--json`,`Output JSONL progress events`,!1).action(async t=>{try{await Ii({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 C(`push`).description(`Push commits to the remote`).option(`--json`,`Output JSONL progress events`,!1).action(async t=>{try{await Ii({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)}})}function Bi(e,t){return{server:Vi(`server`,e),ui:Vi(`ui`,t)}}function Vi(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 Hi(e){return`${Ui(e.server)}\n${Ui(e.ui)}`}function Ui(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 Wi(e){let t=e.inspect??(t=>st(e.lockDir,t)),n=e.log??(e=>console.log(e)),r=Bi(t(`server`),t(`ui`));return e.json?n(JSON.stringify(r,null,2)):n(Hi(r)),r}function Gi(e){return new C(`status`).description(`Show live state of the server + ui lockfiles for this project`).option(`--json`,`Emit structured JSON instead of formatted text`).action(t=>{Wi({lockDir:a(o(e(),process.cwd())),json:t.json===!0})})}function Ki(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 qi(e){let t=e.inspect??(t=>st(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=Ki(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 Ji(e){return new C(`stop`).description(`Stop the running open-knowledge server and UI (live only)`).action(()=>{qi({lockDir:a(o(e(),process.cwd()))}).failed.length>0&&(process.exitCode=1)})}const Yi=1e4,Xi=[`connection`,`keep-alive`,`proxy-authenticate`,`proxy-authorization`,`te`,`trailer`,`transfer-encoding`,`upgrade`,`cookie`,`set-cookie`];async function Zi(e){let t=e.upstreamTimeoutMs??Yi,n=De((n,r)=>{$i(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 Qi(e,t,n){$i(e,t,n.upstreamHost,n.upstreamPort,n.upstreamTimeoutMs??Yi)}function $i(e,t,n,r,i){let a={...e.headers};delete a.host;for(let e of Xi)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=Oe({host:n,port:r,method:e.method,path:e.url,headers:{...a,host:`${n}:${r}`}},e=>{let n={...e.headers};for(let e of Xi)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 ea(e){await Promise.all(e.map(e=>new Promise(t=>{e.close(()=>t())})))}async function ta(e){let{existsSync:t}=await import(`node:fs`),{createServer:n}=await import(`node:http`),{resolve:i}=await import(`node:path`),{acquireUiLock:a,readServerLock:o,releaseUiLock:s,updateUiLockPort:c}=await import(`./src-DaFsir-C.mjs`),{default:l}=await import(`sirv`),{resolveContentDir:u,resolveLockDir:d}=await import(`./paths-CwaesO_J.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,ee=(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}Qi(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):na(t)});return}if(g){g(e,t);return}na(t)},b=e.host===void 0?[`::1`,`127.0.0.1`]:[e.host],x=[],S=e.port;try{for(let e of b){let t=n(ee);x.push(t),await new Promise((n,r)=>{let i=e=>r(e);t.once(`error`,i),t.listen(S,e,()=>{t.off(`error`,i);let e=t.address();typeof e==`object`&&e&&(S=e.port),n()})})}}catch(e){await Promise.all(x.map(e=>new Promise(t=>{try{e.close(()=>t())}catch{t()}})));try{s(p)}catch{}throw e}let C=S;v=C,c(p,C);let w=e.scheduler??r,T=e.safetyNetMs??432e5,E=null,D=!1,te=!1,O=()=>{D||(D=!0,E!==null&&(w.clearTimeout(E),E=null))},ne=()=>{if(O(),!te){te=!0;try{s(p)}catch{}}},re=()=>{D||T<=0||(E!==null&&(w.clearTimeout(E),E=null),E=w.setTimeout(()=>{E=null,console.warn(`[ui] safety-net (${T}ms) reached — shutting down (D-025 backstop)`);try{e.onSafetyNet?.()}catch{}for(let e of x)try{e.close()}catch{}ne()},T))},ie=()=>{D||T<=0||re()};return y=ie,re(),{httpServers:x,port:C,release:ne,detachSafetyNet:O,nudgeSafetyNet:ie}}function na(e){e.writeHead(404),e.end(`Not found`)}function ra(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 ia(e){let t=e.readLock??(async()=>{let{readUiLock:t}=await import(`./src-DaFsir-C.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 Zi({listenPort:e.requestedPort,host:e.host,upstreamHost:`localhost`,upstreamPort:r}),upstreamPort:r}}function aa(e){return new C(`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-xJqYhQcY.mjs`),{UiLockCollisionError:r}=await import(`./src-DaFsir-C.mjs`),{resolveContentDir:i,resolveLockDir:a}=await import(`./paths-CwaesO_J.mjs`),o=e(),s=t.host,c;try{c=ra(t.port,process.env.PORT)}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exitCode=1;return}try{let e=await ta({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)}};ea(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 ia({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 C;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}=ee(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 oa=x(()=>$);Q.addCommand(oa,{isDefault:!0});const sa=Pi(()=>$);Q.addCommand(sa),Q.addCommand(v());const ca=Fi(()=>$);Q.addCommand(ca);const la=aa(()=>$);Q.addCommand(la),Q.addCommand(Ji(()=>$)),Q.addCommand(ut(()=>$)),Q.addCommand(Gi(()=>$)),Q.addCommand(ot(()=>$)),Q.addCommand(yt(()=>$)),Q.addCommand(Li(()=>$)),Q.addCommand(zi(()=>$)),Q.addCommand(Ri(()=>$)),await Q.parseAsync();export{};
596
598
  //# sourceMappingURL=cli.mjs.map