@inkeep/open-knowledge 0.0.0-dev-20260424205527 → 0.0.0-dev-20260424235440

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.
Files changed (70) hide show
  1. package/dist/assets/skills/open-knowledge/SKILL.md +5 -0
  2. package/dist/cli.mjs +64 -61
  3. package/dist/constants-Bp2eH26E.mjs +2 -0
  4. package/dist/index.mjs +1 -1
  5. package/dist/{init-i7vPAWv4.mjs → init-0LxWJ-6l.mjs} +2 -2
  6. package/dist/init-CQtHg31y.mjs +1 -0
  7. package/dist/init-CUmfZczR.mjs +5 -0
  8. package/dist/init-EmyViDSm.mjs +1 -0
  9. package/dist/{loader-CEfLKIxb.mjs → loader-5fUYhvT7.mjs} +2 -2
  10. package/dist/loader-CLKYIpRM.mjs +1 -0
  11. package/dist/paths-Bmv6OTgc.mjs +1 -0
  12. package/dist/{paths-BN0gQKmX.mjs → paths-D7eyE9c-.mjs} +2 -2
  13. package/dist/preview-Bwvil0xG.mjs +1 -0
  14. package/dist/{preview-BmSvc-WZ.mjs → preview-DW48ZUVb.mjs} +2 -2
  15. package/dist/public/assets/ActivityModeContent-DER5Nogg.js +2 -0
  16. package/dist/public/assets/ActivityPanelDiffView-DVyTW-J7.js +13 -0
  17. package/dist/public/assets/ActivityPanelDiffView-legcy4jI.css +1 -0
  18. package/dist/public/assets/DocumentContext--jj-O3yz.js +5 -0
  19. package/dist/public/assets/GraphPanel-CBqnoz3m.js +46 -0
  20. package/dist/public/assets/McpConsentDialogBody-D_7y-PBs.js +1 -0
  21. package/dist/public/assets/{panel-jSlO39XJ.js → PageListContext-DdGQSVEJ.js} +76 -76
  22. package/dist/public/assets/SourceEditor-C2oyeI25.js +3 -0
  23. package/dist/public/assets/agent-presence-BQu9C0ej.js +1 -0
  24. package/dist/public/assets/button-B1XQHQcX.js +1 -0
  25. package/dist/public/assets/clipboard-DRhtxu43.js +32 -0
  26. package/dist/public/assets/compiler-runtime-Y11fyRVL.js +1 -0
  27. package/dist/public/assets/dialog-xzAlDsHy.js +45 -0
  28. package/dist/public/assets/{dist-Crc5NE1l.js → dist-BqO6NfD-.js} +1 -1
  29. package/dist/public/assets/{dist-DRn8lLJe.js → dist-CgLbXgrr.js} +1 -1
  30. package/dist/public/assets/{dist-BCB7v76S.js → dist-D2wwoXzm.js} +1 -1
  31. package/dist/public/assets/{dist-DF16X4DB.js → dist-DR1MrLJC.js} +1 -1
  32. package/dist/public/assets/{dist-Dx3W_u4i.js → dist-DeDgmJv2.js} +1 -1
  33. package/dist/public/assets/{dist-CjujjdB3.js → dist-DlS42TIP.js} +1 -1
  34. package/dist/public/assets/{dist-CDhLtkvx.js → dist-GlH2KPqY.js} +1 -1
  35. package/dist/public/assets/index-CIIADYsi.css +1 -0
  36. package/dist/public/assets/index-D_7iKKj-.js +27 -0
  37. package/dist/public/assets/panel-rtdrPcLu.js +1 -0
  38. package/dist/public/assets/{telemetry-impl-D_lLX0fS.js → telemetry-impl-BTqkcpTj.js} +2 -2
  39. package/dist/public/assets/toggle-group-B86VAmbm.js +1 -0
  40. package/dist/public/assets/tooltip-C2q723j6.js +1 -0
  41. package/dist/public/index.html +16 -11
  42. package/dist/src-CO-0JWV1.mjs +1 -0
  43. package/dist/src-DNH9XQcP.mjs +1 -0
  44. package/dist/{src-DBQiBMMB.mjs → src-H34RCSr9.mjs} +28 -18
  45. package/dist/start-D22sG9zf.mjs +1 -0
  46. package/dist/{start-J6AfuX7h.mjs → start-DbI7KgzR.mjs} +2 -2
  47. package/package.json +1 -1
  48. package/dist/constants-Dqmeg2oi.mjs +0 -2
  49. package/dist/init-3UxRGVAs.mjs +0 -1
  50. package/dist/init-AmgsVReV.mjs +0 -1
  51. package/dist/init-DIbZ0Udv.mjs +0 -5
  52. package/dist/loader-CQUrJ6T-.mjs +0 -1
  53. package/dist/paths-HWNY8fZl.mjs +0 -1
  54. package/dist/preview-DzHGOZjd.mjs +0 -1
  55. package/dist/public/assets/GraphPanel-CMj_CKRD.js +0 -46
  56. package/dist/public/assets/McpConsentDialogBody-BpxsNx0A.js +0 -1
  57. package/dist/public/assets/SourceEditor-BSUZdSkZ.js +0 -3
  58. package/dist/public/assets/button-D7O9FEkM.js +0 -1
  59. package/dist/public/assets/clipboard-B0g7jEdf.js +0 -36
  60. package/dist/public/assets/dialog-DzmWgC4p.js +0 -45
  61. package/dist/public/assets/index-Bnp3jcJM.js +0 -27
  62. package/dist/public/assets/index-tR5M38z1.css +0 -1
  63. package/dist/public/assets/toggle-group-D0c1wSh_.js +0 -1
  64. package/dist/src-DjVVQCyp.mjs +0 -1
  65. package/dist/src-zcKwR9RH.mjs +0 -1
  66. package/dist/start-Dta5BSHL.mjs +0 -1
  67. /package/dist/public/assets/{__vite-browser-external-BFtnoaxY.js → __vite-browser-external-Dz2JgTKm.js} +0 -0
  68. /package/dist/public/assets/{go-BL7jA4zR.js → go-DatQmDtN.js} +0 -0
  69. /package/dist/public/assets/{shell-DKDblpGA.js → shell-7yok6dsz.js} +0 -0
  70. /package/dist/public/assets/{w3c-keyname-B56rf6AD.js → w3c-keyname-CSlwavrd.js} +0 -0
package/dist/cli.mjs CHANGED
@@ -1,14 +1,17 @@
1
1
  #!/usr/bin/env node
2
- import{i as e,r as t}from"./constants-Dqmeg2oi.mjs";import{O as n,c as r,k as i}from"./src-CB-sXsMW.mjs";import{n as a,t as o}from"./paths-BN0gQKmX.mjs";import{C as s,_t as c,gt as l,ht as u,ut as d,vt as f,w as p,x as m}from"./src-DBQiBMMB.mjs";import{c as h,f as g,r as _}from"./server-lock-4vgF5yho.mjs";import{i as v,n as y,o as b,r as x,s as S,t as C}from"./colors-Cmha5EZJ.mjs";import{t as w}from"./is-object-7k010cEq.mjs";import{r as T}from"./init-DIbZ0Udv.mjs";import{i as E,n as D,t as O}from"./loader-CEfLKIxb.mjs";import{o as ee,s as k}from"./start-J6AfuX7h.mjs";import"./src-DjVVQCyp.mjs";import{Command as A}from"commander";import{appendFileSync as te,closeSync as ne,existsSync as j,mkdirSync as re,openSync as ie,readFileSync as ae,readdirSync as oe,realpathSync as se,statSync as ce,unlinkSync as le,writeFileSync as ue}from"node:fs";import{homedir as de,hostname as fe}from"node:os";import{basename as pe,dirname as me,isAbsolute as he,join as ge,relative as _e,resolve as M}from"node:path";import{parse as ve,stringify as ye}from"yaml";import{createOAuthDeviceAuth as be}from"@octokit/auth-oauth-device";import xe from"@inquirer/password";import{Octokit as Se}from"@octokit/rest";import{fileURLToPath as Ce}from"node:url";import{randomUUID as we}from"node:crypto";import{execFileSync as Te,spawn as Ee}from"node:child_process";import De from"simple-git";import{readFile as Oe,readdir as ke,stat as Ae}from"node:fs/promises";import{createServer as je,request as Me}from"node:http";import Ne from"picomatch";import{z as N}from"zod";import{McpServer as Pe}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as Fe}from"@modelcontextprotocol/sdk/server/stdio.js";import{RootsListChangedNotificationSchema as Ie}from"@modelcontextprotocol/sdk/types.js";import{AsyncLocalStorage as Le}from"node:async_hooks";import{Bash as Re,ReadWriteFs as ze}from"just-bash";import Be from"shell-quote";import{createInterface as Ve}from"node:readline/promises";const He=`open-knowledge`;var Ue=class{backend=`keyring`;async get(e){let{Entry:t}=await import(`@napi-rs/keyring`);try{let n=new t(He,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(He,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(He,e).deletePassword()}catch{}}},We=class{backend=`file`;authFile;constructor(e){this.authFile=e??ge(de(),`.open-knowledge`,`auth.yml`)}read(){if(!j(this.authFile))return{};try{return ve(ae(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=me(this.authFile);j(t)||re(t,{recursive:!0,mode:448}),ue(this.authFile,ye(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 Ge(e){try{let{Entry:e}=await import(`@napi-rs/keyring`);return new e(He,`__probe__`),process.stderr.write(`[auth] token storage: OS keychain
3
- `),new Ue}catch{return process.stderr.write(`[auth] token storage: file (~/.open-knowledge/auth.yml)
4
- `),new We(e)}}async function Ke(e,t,n){let r=qe(await Je(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 qe(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 Je(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 Ye(e){let t=new A(`git-credential`);return t.description(`Git credential helper (git credential-helper protocol)`),t.command(`get`).description(`Lookup credentials from TokenStore (called by git)`).action(async()=>{let t=await e(),n=await Ke(process.stdin,process.stdout,t);process.exit(n)}),t}async function Xe(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=be({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 Ze(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 $e(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 et(e,t){e&&process.stdout.write(`${JSON.stringify(t)}\n`)}async function tt(e,t,n,r=Xe){let i=Ze(n),{host:a,json:o}=e;$e(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?et(!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?et(!0,{type:`complete`,host:a,login:c}):process.stderr.write(`✓ Logged in as ${c} on ${a}\n`)}function nt(e,t){return new A(`login`).description(`Authenticate with GitHub via Device Flow`).option(`--host <host>`,`GitHub or GitHub Enterprise hostname`,`github.com`).option(`--json`,`Output JSONL progress events`,!1).action(async n=>{await tt(n,await t(),e())})}async function rt(e,t,n){let{host:r,json:i}=e;$e(r);let a=await(n??(()=>xe({message:`Enter PAT:`})))();a||(process.stderr.write(`No token provided
6
- `),process.exit(1));let o=r===`github.com`?void 0:`https://${r}/api/v3`,s=new Se({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 it(e){return new A(`pat`).description(`Store a Personal Access Token`).option(`--host <host>`,`GitHub or GitHub Enterprise hostname`,`github.com`).option(`--json`,`Output JSON`,!1).action(async t=>{await rt(t,await e())})}async function at(e,t){let{host:n,json:r}=e;$e(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 Se({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 ot(e){return new A(`repos`).description(`List accessible repositories`).option(`--host <host>`,`GitHub or GitHub Enterprise hostname`,`github.com`).option(`--json`,`Output JSON`,!1).action(async t=>{await at(t,await e())})}async function st(e,t){let{host:n}=e;await t.clear(n),process.stderr.write(`✓ Signed out from ${n}\n`)}function ct(e){return new A(`signout`).description(`Remove stored credentials`).option(`--host <host>`,`GitHub hostname`,`github.com`).action(async t=>{await st(t,await e())})}async function lt(e,t){let{host:n,json:r}=e;$e(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 Se({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 ut(e){return new A(`status`).description(`Show authentication status`).option(`--host <host>`,`GitHub or GitHub Enterprise hostname`,`github.com`).option(`--json`,`Output JSON`,!1).action(async t=>{await lt(t,await e())})}function dt(e){let t=new A(`auth`);t.description(`GitHub authentication management`);let n=()=>Ge(),r=e??(()=>({}));return t.addCommand(nt(r,n)),t.addCommand(ut(n)),t.addCommand(ot(n)),t.addCommand(ct(n)),t.addCommand(it(n)),t.addCommand(Ye(n)),t}function ft(e,t,n={}){let r=h(e,t);if(!j(r))return{status:`missing`,lockPath:r};let i;try{i=JSON.parse(ae(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??fe();return a.hostname===o?(n.isAlive??g)(a.pid)?{status:`alive`,lockPath:r,lock:a}:{status:`dead-pid`,lockPath:r,lock:a}:{status:`foreign-host`,lockPath:r,lock:a}}function pt(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 mt(e){let t=e.inspect??(t=>ft(e.lockDir,t)),n=e.unlink??(e=>le(e)),r=e.log??(e=>console.log(e)),i=e.error??(e=>console.error(e)),a=pt(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 ht(e){return new A(`clean`).description(`Prune stale / corrupt open-knowledge lock files (never touches live locks)`).action(()=>{mt({lockDir:a(o(e(),process.cwd()))}).failed.length>0&&(process.exitCode=1)})}function gt(){try{let e=Te(`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 _t(e,t,n={},r=gt){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 vt(e){return e.replace(/:\d+$/,``)}function yt(e){let t=e.trim();if(!t)return null;{let e=/^https?:\/\/([^/?#]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`https`,hostname:vt(e[1]),owner:e[2],name:e[3]}}{let e=/^ssh:\/\/(?:[\w.-]+@)?([^/?#]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`ssh`,hostname:vt(e[1]),owner:e[2],name:e[3]}}{let e=/^git:\/\/([^/?#]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`git`,hostname:vt(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 bt=[[`count`,0,10],[`compress`,10,20],[`receiv`,20,60],[`resolv`,60,100]];function xt(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 bt)if(n.includes(e))return{stage:t[1],pct:Math.round(i+r/100*(a-i))};return null}function St(e,t){e&&process.stdout.write(`${JSON.stringify(t)}\n`)}async function Ct(e,t,n,r=process.cwd()){let i=yt(e);if(!i)throw Error(`Invalid git URL: ${e}`);let a=t.dir?M(r,t.dir):M(r,i.name);if(j(a)&&oe(a).length>0)throw Error(`Target directory is not empty: ${a}`);let o=await Ge(),s=await _t(i.hostname,o,{}),c=De({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=xt(e);n&&n.pct!==l&&(l=n.pct,St(t.json,{type:`progress`,pct:n.pct,stage:n.stage}),t.json||process.stderr.write(`\r Cloning… ${n.pct}%`))}})}),await c.clone(e,a,[`--progress`]),t.json||process.stderr.write(`
10
- `),!j(M(a,`.open-knowledge`)))try{let[{runInit:e},{ensureOkGitignoredAtRoot:t}]=await Promise.all([import(`./init-3UxRGVAs.mjs`),import(`./init-AmgsVReV.mjs`)]);await e({cwd:a,mcp:!1});try{t(a)}catch{}}catch{}return a}function wt(e){return new A(`clone`).description(`Clone a git repository and open it`).argument(`<url>`,`Repository URL or owner/repo shorthand`).argument(`[dir]`,`Target directory (default: ./<repo-name>)`).option(`--json`,`Output JSONL progress events`,!1).action(async(t,n,r)=>{let i=e();try{let a=await Ct(t,{json:r.json,dir:n},i);if(r.json)St(!0,{type:`complete`,dir:a});else{process.stderr.write(`✓ Cloned to ${a}\n`),process.chdir(a);let{startCommand:t}=await import(`./start-Dta5BSHL.mjs`);await t(e).parseAsync([],{from:`user`})}}catch(e){let t=e instanceof Error?e.message:String(e);r.json?St(!0,{type:`error`,message:t}):process.stderr.write(`✗ ${t}\n`),process.exitCode=1}})}const Tt=new Le;var Et=class e{sessionId;corrId;component;constructor(e=`mcp`,t){this.sessionId=t??we().slice(0,12),this.corrId=we().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{te(a,i)}catch(e){console.warn(`[mcp-logger] Failed to write to OK_LOG_FILE: ${e instanceof Error?e.message:e}`)}}};function Dt(e=`mcp`){return new Et(e)}function Ot(e,t){return Tt.run(e,t)}function kt(){return Tt.getStore()}const At=new Set([`find`,`markdown`,`replace`]),jt=[`backlinks`,`deadLinks`,`documents`,`enrichedPaths`,`entries`,`forwardLinks`,`hints`,`hubs`,`orphans`,`results`],Mt=[`checkpointRef`,`cwd`,`fileCount`,`matchCount`,`ok`,`query`,`stdoutTruncated`,`truncated`];function P(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Nt(e){return P(e)&&`requestId`in e}function Pt(e,t){if(At.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 Ft(e,t){if(t===null)return null;if(typeof t==`string`)return Pt(e,t);if(typeof t==`number`||typeof t==`boolean`)return t;if(Array.isArray(t))return{type:`array`,length:t.length};if(P(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 It(e,t){return P(t)?Object.fromEntries(Object.entries(t).map(([e,t])=>[e,Ft(e,t)])):Ft(e,t)}function Lt(e){let t={structuredKeys:Object.keys(e).sort()};for(let n of Mt)n in e&&(t[n]=e[n]);for(let n of jt){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),P(e.warning)&&(t.warning=!0,`previewUrl`in e.warning&&(t.warningPreviewUrl=e.warning.previewUrl??null)),P(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 Rt(e){if(!P(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)=>P(t)&&typeof t.text==`string`?e+t.text.length:e,0)),P(e.structuredContent)&&Object.assign(t,Lt(e.structuredContent)),t}function zt(e){return{connectionId:e.connectionId.slice(0,8),displayName:e.displayName,...e.clientInfo?.name?{clientName:e.clientInfo.name}:{},...e.label?{label:e.label}:{}}}function Bt(e){let t=e.at(-1);return Nt(t)?{toolArgs:e.length>1?e[0]:void 0,extra:t}:{toolArgs:e[0],extra:void 0}}function Vt(e,t,n){let r=n.logger;return r?async(...i)=>{let a=r.child(),o=Date.now(),{toolArgs:s,extra:c}=Bt(i);a.info(`tool start`,{tool:e,...c?.requestId===void 0?{}:{requestId:c.requestId},...c?.sessionId?{transportSessionId:c.sessionId}:{},...n.identityRef?.current?{agent:zt(n.identityRef.current)}:{},...s===void 0?{}:{args:It(e,s)}});try{let n=await Ot(a,async()=>await t(...i));return a.info(`tool finish`,{tool:e,...c?.requestId===void 0?{}:{requestId:c.requestId},durationMs:Date.now()-o,result:Rt(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 Ht(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]=Vt(r,i,t),n(...a)}),r}const F="Absolute host path to resolve the request against. Defaults only when the MCP client advertises exactly one root; otherwise pass `cwd` explicitly.",Ut=N.string().max(200).optional().describe(`Optional one-line user-outcome description (≤80 chars). Appears as a bullet in the timeline.`);function I(e,t){return{content:[{type:`text`,text:e}],...t?{isError:!0}:{}}}function L(e,t,n){return{content:[{type:`text`,text:e}],structuredContent:t,...n?{isError:!0}:{}}}const R="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.",Wt={ingest:`raw-sources layer (preserve external material, no analysis)`,research:`wiki layer, provisional (synthesize findings that can still change)`,consolidate:`wiki layer, canonical (promote stabilized research to source-of-truth)`},Gt={ingest:"user shares a URL or file they want preserved, or `research` needs raw sources",research:"`ingest` has captured the relevant sources (or the user points at one)",consolidate:"`research` has produced a provisional article AND a decision has actually been made"},Kt={ingest:"often `research` on the same topic — or just stop; raw preservation is frequently enough on its own",research:"usually stop (research lives as provisional indefinitely) or `consolidate` once a decision lands",consolidate:"update 2–3 neighbor docs to link the new canonical article; research articles it supersedes gain a `superseded_by` pointer"};function qt(e){return`## Where this fits
2
+ import{i as e,r as t}from"./constants-Bp2eH26E.mjs";import{O as n,c as r,k as i}from"./src-CB-sXsMW.mjs";import{n as a,t as o}from"./paths-D7eyE9c-.mjs";import{D as s,O as c,St as l,T as u,a as d,bt as f,mt as p,xt as m,yt as h}from"./src-H34RCSr9.mjs";import{c as g,f as _,r as v}from"./server-lock-4vgF5yho.mjs";import{i as y,n as b,o as x,r as S,s as C,t as w}from"./colors-Cmha5EZJ.mjs";import{t as T}from"./is-object-7k010cEq.mjs";import{r as E}from"./init-CUmfZczR.mjs";import{i as D,n as O,t as ee}from"./loader-5fUYhvT7.mjs";import{o as k,s as te}from"./start-DbI7KgzR.mjs";import"./src-DNH9XQcP.mjs";import{Command as A}from"commander";import{appendFileSync as ne,closeSync as re,existsSync as j,mkdirSync as ie,openSync as ae,readFileSync as oe,readdirSync as se,realpathSync as ce,statSync as le,unlinkSync as ue,writeFileSync as de}from"node:fs";import{homedir as fe,hostname as pe,platform as me}from"node:os";import{basename as he,dirname as ge,isAbsolute as _e,join as ve,relative as ye,resolve as M}from"node:path";import{parse as be,stringify as xe}from"yaml";import{createOAuthDeviceAuth as Se}from"@octokit/auth-oauth-device";import Ce from"@inquirer/password";import{Octokit as we}from"@octokit/rest";import{fileURLToPath as Te}from"node:url";import{randomUUID as Ee}from"node:crypto";import{execFileSync as De,spawn as Oe}from"node:child_process";import N from"simple-git";import{mkdir as ke,readFile as Ae,readdir as je,stat as Me}from"node:fs/promises";import{createServer as Ne,request as Pe}from"node:http";import Fe from"picomatch";import{z as P}from"zod";import{McpServer as Ie}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as Le}from"@modelcontextprotocol/sdk/server/stdio.js";import{RootsListChangedNotificationSchema as Re}from"@modelcontextprotocol/sdk/types.js";import{AsyncLocalStorage as ze}from"node:async_hooks";import{Bash as Be,ReadWriteFs as Ve}from"just-bash";import He from"shell-quote";import{createInterface as Ue}from"node:readline/promises";const We=`open-knowledge`;var Ge=class{backend=`keyring`;async get(e){let{Entry:t}=await import(`@napi-rs/keyring`);try{let n=new t(We,e).getPassword();return n==null?null:JSON.parse(n)}catch{return null}}async set(e,t,n,r){let{Entry:i}=await import(`@napi-rs/keyring`),a=new i(We,e),o={login:t,token:n,...r};a.setPassword(JSON.stringify(o))}async clear(e){let{Entry:t}=await import(`@napi-rs/keyring`);try{new t(We,e).deletePassword()}catch{}}},Ke=class{backend=`file`;authFile;constructor(e){this.authFile=e??ve(fe(),`.open-knowledge`,`auth.yml`)}read(){if(!j(this.authFile))return{};try{return be(oe(this.authFile,`utf-8`))??{}}catch(e){let t=e instanceof Error?e.message:`unknown error`;return process.stderr.write(`[auth] Failed to parse ${this.authFile}: ${t}. Starting with empty credentials.\n`),{}}}write(e){let t=ge(this.authFile);j(t)||ie(t,{recursive:!0,mode:448}),de(this.authFile,xe(e),{mode:384})}async get(e){return this.read()[e]??null}async set(e,t,n,r){let i=this.read();i[e]={login:t,token:n,...r},this.write(i)}async clear(e){let t=this.read();delete t[e],this.write(t)}};async function qe(e){try{let{Entry:e}=await import(`@napi-rs/keyring`);return new e(We,`__probe__`),process.stderr.write(`[auth] token storage: OS keychain
3
+ `),new Ge}catch{return process.stderr.write(`[auth] token storage: file (~/.open-knowledge/auth.yml)
4
+ `),new Ke(e)}}async function Je(e,t,n){let r=Ye(await Xe(e)).host??``;if(!r)return 1;let i=await n.get(r);if(i==null)return 1;let a=e=>e.replace(/[\r\n]/g,``);return t.write(`username=${a(i.login)}\npassword=${a(i.token)}\n`),0}function Ye(e){let t={};for(let n of e.split(`
5
+ `)){let e=n.trim();if(e===``)continue;let r=e.indexOf(`=`);r!==-1&&(t[e.slice(0,r)]=e.slice(r+1))}return t}function Xe(e){return new Promise((t,n)=>{let r=[];e.on(`data`,e=>r.push(e)),e.on(`end`,()=>t(Buffer.concat(r).toString(`utf-8`))),e.on(`error`,n)})}function Ze(e){let t=new A(`git-credential`);return t.description(`Git credential helper (git credential-helper protocol)`),t.command(`get`).description(`Lookup credentials from TokenStore (called by git)`).action(async()=>{let t=await e(),n=await Je(process.stdin,process.stdout,t);process.exit(n)}),t}async function Qe(e){let{clientId:t,scopes:n=[`repo`,`read:user`,`user:email`],onVerification:r,host:i}=e,a=i&&i!==`github.com`?`https://${i}/api/v3`:`https://api.github.com`,o=Se({clientType:`oauth-app`,clientId:t,scopes:n,onVerification:async e=>{await r({verificationUri:e.verification_uri,userCode:e.user_code,expiresIn:e.expires_in,interval:e.interval})},request:a===`https://api.github.com`?void 0:(await import(`@octokit/request`)).request.defaults({baseUrl:a})}),s;try{s=await o({type:`oauth`})}catch(e){if(e instanceof Error){let t=e.message.toLowerCase();throw t.includes(`access_denied`)?Error(`Device-flow authorization was denied.`):t.includes(`expired_token`)||t.includes(`timeout`)||t.includes(`timed out`)?Error(`Device-flow code expired before authorization — please try again.`):Error(`GitHub sign-in failed: ${e.message}`)}throw e}return{token:s.token,tokenType:s.tokenType,scopes:s.scopes??[]}}function $e(e){return process.env.OPEN_KNOWLEDGE_GITHUB_CLIENT_ID??e?.github?.oauthAppClientId??`Ov23liqlSd0V1MwR6rhI`}const et=new Set([`gitlab.com`,`bitbucket.org`,`codeberg.org`,`gitea.com`,`sr.ht`,`sourcehut.org`]);function tt(e){let t=e.toLowerCase().replace(/:\d+$/,``);et.has(t)&&(process.stderr.write(`Error: ${e} is not a GitHub host. Only GitHub and GitHub Enterprise Server are supported.\n`),process.exit(1))}function nt(e,t){e&&process.stdout.write(`${JSON.stringify(t)}\n`)}async function rt(e,t,n,r=Qe){let i=$e(n),{host:a,json:o}=e;tt(a),o||process.stderr.write(`Logging in to ${a}…\n`);let s=await r({clientId:i,host:a===`github.com`?void 0:a,onVerification:e=>{e.userCode,e.verificationUri,o?nt(!0,{type:`verification`,user_code:e.userCode,verification_uri:e.verificationUri,expires_in:e.expiresIn}):process.stderr.write(`Open: ${e.verificationUri}\nEnter code: ${e.userCode}\n`)}}),c=`unknown`,l,u;try{let e=a===`github.com`?`https://api.github.com`:`https://${a}/api/v3`,t=await fetch(`${e}/user`,{headers:{Authorization:`Bearer ${s.token}`,"User-Agent":`open-knowledge-cli`,Accept:`application/vnd.github+json`}});if(t.ok){let e=await t.json();c=e.login??c,l=e.name??void 0,u=e.email??void 0}}catch{}await t.set(a,c,s.token,{gitProtocol:`https`,name:l,email:u}),o?nt(!0,{type:`complete`,host:a,login:c}):process.stderr.write(`✓ Logged in as ${c} on ${a}\n`)}function it(e,t){return new A(`login`).description(`Authenticate with GitHub via Device Flow`).option(`--host <host>`,`GitHub or GitHub Enterprise hostname`,`github.com`).option(`--json`,`Output JSONL progress events`,!1).action(async n=>{await rt(n,await t(),e())})}async function at(e,t,n){let{host:r,json:i}=e;tt(r);let a=await(n??(()=>Ce({message:`Enter PAT:`})))();a||(process.stderr.write(`No token provided
6
+ `),process.exit(1));let o=r===`github.com`?void 0:`https://${r}/api/v3`,s=new we({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 ot(e){return new A(`pat`).description(`Store a Personal Access Token`).option(`--host <host>`,`GitHub or GitHub Enterprise hostname`,`github.com`).option(`--json`,`Output JSON`,!1).action(async t=>{await at(t,await e())})}async function st(e,t){let{host:n,json:r}=e;tt(n);let i=await t.get(n);i??(process.stderr.write(`Not logged in to ${n}\n`),process.exit(1));let a=n===`github.com`?void 0:`https://${n}/api/v3`,o=new we({auth:i.token,...a?{baseUrl:a}:{}}),s=[];for await(let e of o.paginate.iterator(o.repos.listForAuthenticatedUser,{per_page:100,sort:`updated`}))for(let t of e.data)s.push({full_name:t.full_name,clone_url:t.clone_url,private:t.private});if(r)process.stdout.write(`${JSON.stringify({type:`repos`,host:n,repos:s})}\n`);else for(let e of s)process.stdout.write(`${e.full_name} ${e.clone_url}\n`)}function ct(e){return new A(`repos`).description(`List accessible repositories`).option(`--host <host>`,`GitHub or GitHub Enterprise hostname`,`github.com`).option(`--json`,`Output JSON`,!1).action(async t=>{await st(t,await e())})}async function lt(e,t){let{host:n}=e;await t.clear(n),process.stderr.write(`✓ Signed out from ${n}\n`)}function ut(e){return new A(`signout`).description(`Remove stored credentials`).option(`--host <host>`,`GitHub hostname`,`github.com`).action(async t=>{await lt(t,await e())})}async function dt(e,t){let{host:n,json:r}=e;tt(n);let i=await t.get(n);i??(r?process.stdout.write(`${JSON.stringify({type:`status`,host:n,authenticated:!1})}\n`):process.stderr.write(`Not logged in to ${n}\n`),process.exit(1));let a=n===`github.com`?void 0:`https://${n}/api/v3`,o=new we({auth:i.token,...a?{baseUrl:a}:{}});try{let{data:e}=await o.users.getAuthenticated();r?process.stdout.write(`${JSON.stringify({type:`status`,host:n,authenticated:!0,login:e.login,name:e.name,email:e.email})}\n`):process.stderr.write(`✓ Logged in as ${e.login} on ${n}\n`)}catch{r?process.stdout.write(JSON.stringify({type:`status`,host:n,authenticated:!1,error:`token invalid`})+`
8
+ `):process.stderr.write(`✗ Token invalid for ${n}\n`),process.exit(1)}}function ft(e){return new A(`status`).description(`Show authentication status`).option(`--host <host>`,`GitHub or GitHub Enterprise hostname`,`github.com`).option(`--json`,`Output JSON`,!1).action(async t=>{await dt(t,await e())})}function pt(e){let t=new A(`auth`);t.description(`GitHub authentication management`);let n=()=>qe(),r=e??(()=>({}));return t.addCommand(it(r,n)),t.addCommand(ft(n)),t.addCommand(ct(n)),t.addCommand(ut(n)),t.addCommand(ot(n)),t.addCommand(Ze(n)),t}function mt(e,t,n={}){let r=g(e,t);if(!j(r))return{status:`missing`,lockPath:r};let i;try{i=JSON.parse(oe(r,`utf-8`))}catch{return{status:`corrupt`,lockPath:r}}if(!i||typeof i!=`object`||typeof i.pid!=`number`)return{status:`corrupt`,lockPath:r};let a=i,o=n.host??pe();return a.hostname===o?(n.isAlive??_)(a.pid)?{status:`alive`,lockPath:r,lock:a}:{status:`dead-pid`,lockPath:r,lock:a}:{status:`foreign-host`,lockPath:r,lock:a}}function ht(e,t){let n=[];for(let[r,i]of[[`server`,e],[`ui`,t]])(i.status===`dead-pid`||i.status===`corrupt`)&&n.push({name:r,lockPath:i.lockPath,reason:i.status});return{prune:n}}function gt(e){let t=e.inspect??(t=>mt(e.lockDir,t)),n=e.unlink??(e=>ue(e)),r=e.log??(e=>console.log(e)),i=e.error??(e=>console.error(e)),a=ht(t(`server`),t(`ui`));if(a.prune.length===0)return r(`No stale locks.`),{pruned:[],failed:[]};let o=[],s=[];for(let e of a.prune)try{n(e.lockPath),o.push(e)}catch(t){s.push({target:e,error:t instanceof Error?t.message:String(t)})}if(o.length>0){let e=o.map(e=>`${e.name} (${e.reason})`).join(`, `);r(`Pruned ${o.length} stale lock${o.length===1?``:`s`}: ${e}`)}return s.length>0&&i(`Failed to prune: ${s.map(({target:e,error:t})=>`${e.name} (${e.lockPath}): ${t}`).join(`; `)}`),{pruned:o,failed:s}}function _t(e){return new A(`clean`).description(`Prune stale / corrupt open-knowledge lock files (never touches live locks)`).action(()=>{gt({lockDir:a(o(e(),process.cwd()))}).failed.length>0&&(process.exitCode=1)})}function vt(){try{let e=De(`gh`,[`auth`,`token`],{encoding:`utf-8`,stdio:[`ignore`,`pipe`,`pipe`],timeout:5e3}).trim();return e.length===0?{available:!1}:{available:!0,token:e}}catch{return{available:!1}}}async function yt(e,t,n={},r=vt){if(!n.skipGhDetect&&r().available)return{tier:`A`,credentialArgs:[`-c`,`credential.helper=!gh auth git-credential`]};let i=await t.get(e);return i==null?{tier:`none`,credentialArgs:[]}:{tier:i.gitProtocol===`ssh`?`C`:`B`,credentialArgs:[`-c`,`credential.helper=!open-knowledge auth git-credential`]}}function bt(e){return e.replace(/:\d+$/,``)}function xt(e){let t=e.trim();if(!t)return null;{let e=/^https?:\/\/([^/?#]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`https`,hostname:bt(e[1]),owner:e[2],name:e[3]}}{let e=/^ssh:\/\/(?:[\w.-]+@)?([^/?#]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`ssh`,hostname:bt(e[1]),owner:e[2],name:e[3]}}{let e=/^git:\/\/([^/?#]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`git`,hostname:bt(e[1]),owner:e[2],name:e[3]}}{let e=/^(?:[\w.-]+@)?([\w.-]+):([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?$/.exec(t);if(e?.[1].includes(`.`)||e&&t.startsWith(`git@`))return{protocol:`ssh`,hostname:e[1],owner:e[2],name:e[3]}}{let e=/^git:([\w.-]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`git`,hostname:e[1],owner:e[2],name:e[3]}}if(!t.includes(`://`)&&!t.includes(`@`)&&!t.startsWith(`/`)){let e=/^([\w.-]+)\/([\w.\-~%]+?)(?:\.git)?$/.exec(t);if(e)return{protocol:`https`,hostname:`github.com`,owner:e[1],name:e[2]}}return null}const St=[[`count`,0,10],[`compress`,10,20],[`receiv`,20,60],[`resolv`,60,100]];function Ct(e){let t=/^([\w ]+):\s+(\d+)%/.exec(e.trim());if(!t)return null;let n=t[1].toLowerCase(),r=Number(t[2]);for(let[e,i,a]of St)if(n.includes(e))return{stage:t[1],pct:Math.round(i+r/100*(a-i))};return null}function wt(e,t){e&&process.stdout.write(`${JSON.stringify(t)}\n`)}async function Tt(e,t,n,r=process.cwd()){let i=xt(e);if(!i)throw Error(`Invalid git URL: ${e}`);let a=t.dir?M(r,t.dir):M(r,i.name);if(j(a)&&se(a).length>0)throw Error(`Target directory is not empty: ${a}`);let o=await qe(),s=await yt(i.hostname,o,{}),c=N({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=Ct(e);n&&n.pct!==l&&(l=n.pct,wt(t.json,{type:`progress`,pct:n.pct,stage:n.stage}),t.json||process.stderr.write(`\r Cloning… ${n.pct}%`))}})}),await c.clone(e,a,[`--progress`]),t.json||process.stderr.write(`
10
+ `),!j(M(a,`.open-knowledge`)))try{let[{runInit:e},{ensureOkGitignoredAtRoot:t}]=await Promise.all([import(`./init-CQtHg31y.mjs`),import(`./init-EmyViDSm.mjs`)]);await e({cwd:a,mcp:!1});try{t(a)}catch{}}catch{}return a}function Et(e){return new A(`clone`).description(`Clone a git repository and open it`).argument(`<url>`,`Repository URL or owner/repo shorthand`).argument(`[dir]`,`Target directory (default: ./<repo-name>)`).option(`--json`,`Output JSONL progress events`,!1).action(async(t,n,r)=>{let i=e();try{let a=await Tt(t,{json:r.json,dir:n},i);if(r.json)wt(!0,{type:`complete`,dir:a});else{process.stderr.write(`✓ Cloned to ${a}\n`),process.chdir(a);let{startCommand:t}=await import(`./start-D22sG9zf.mjs`);await t(e).parseAsync([],{from:`user`})}}catch(e){let t=e instanceof Error?e.message:String(e);r.json?wt(!0,{type:`error`,message:t}):process.stderr.write(`✗ ${t}\n`),process.exitCode=1}})}function Dt(e=fe()){return ve(e,`Downloads`,`openknowledge.skill`)}function Ot(e,t,n){try{return t===`darwin`?(n(`open`,[e],{detached:!0,stdio:`ignore`}).unref(),{ok:!0}):t===`win32`?(n(`cmd`,[`/c`,`start`,`""`,e],{detached:!0,stdio:`ignore`}).unref(),{ok:!0}):t===`linux`?(n(`xdg-open`,[e],{detached:!0,stdio:`ignore`}).unref(),{ok:!0}):{ok:!1,reason:`unsupported-platform`,message:`Platform '${t}' has no file-association invocation wired. Use --no-open and open the file manually.`}}catch(e){return{ok:!1,reason:`spawn-error`,message:e instanceof Error?e.message:String(e)}}}async function kt(e={}){let t=M(e.out??Dt()),n=e.platformName??me(),r=e.spawnFn??Oe;try{await ke(ge(t),{recursive:!0})}catch(e){return{status:`failed`,message:`${S(`Error:`)} could not create output directory: ${e instanceof Error?e.message:String(e)}`,exitCode:1}}let i;try{i=await d({outputPath:t,skipVersionCheck:!0})}catch(e){return{status:`failed`,message:`${S(`Error:`)} build failed — ${e instanceof Error?e.message:String(e)}`,exitCode:1}}if(e.noOpen)return{status:`built`,outputPath:i.outputPath,size:i.size,sha256:i.sha256,cliVersion:i.cliVersion,skillVersion:i.skillVersion,message:[x(`Built ${i.outputPath}`),b(` ${i.size} bytes • sha256 ${i.sha256.slice(0,12)}…`),y(` Open the Claude Desktop App, then: ${w(`Customize → Skills → + → Create skill → Upload skill`)} → pick the file.`)].join(`
11
+ `),exitCode:0};let a=Ot(i.outputPath,n,r);return a.ok?{status:`installed`,outputPath:i.outputPath,size:i.size,sha256:i.sha256,cliVersion:i.cliVersion,skillVersion:i.skillVersion,message:[x(`Built ${i.outputPath}`),b(` ${i.size} bytes • sha256 ${i.sha256.slice(0,12)}… • CLI v${i.cliVersion}`),y(` Claude Desktop App opened. Now upload the file manually:`),` 1. ${w(`Customize`)} (sidebar) ${w(`Skills`)}`,` 2. Click the ${w(`+`)} button`,` 3. Click ${w(`Create skill`)}`,` 4. Click ${w(`Upload skill`)}`,` 5. Pick ${w(`openknowledge.skill`)} from Downloads`,b(` If Claude Desktop didn't open, open it and start at step 1. The file is at ${i.outputPath}`)].join(`
12
+ `),exitCode:0}:{status:`built`,outputPath:i.outputPath,size:i.size,sha256:i.sha256,cliVersion:i.cliVersion,skillVersion:i.skillVersion,message:[x(`Built ${i.outputPath}`),C(` Handoff failed: ${a.message}`),y(` Open the Claude Desktop App, then: ${w(`Customize → Skills → + → Create skill → Upload skill`)} → pick the file.`)].join(`
13
+ `),exitCode:0}}function At(){return new A(`install-skill`).description("Build openknowledge.skill and open the Claude Desktop App so you can upload it for Claude Chat & Cowork. Not needed for Claude Code — `ok init` covers that separately.").option(`--out <path>`,`Custom output path (default: ~/Downloads/openknowledge.skill)`).option(`--no-open`,`Build the file but skip the OS file-association handoff`).action(async e=>{let t=await kt({out:e.out,noOpen:!e.open});process.stdout.write(`${t.message}\n`),t.exitCode!==0&&process.exit(t.exitCode)})}const jt=new ze;var Mt=class e{sessionId;corrId;component;constructor(e=`mcp`,t){this.sessionId=t??Ee().slice(0,12),this.corrId=Ee().slice(0,8),this.component=e}info(e,t={}){this.emit(`info`,e,t)}warn(e,t={}){this.emit(`warn`,e,t)}error(e,t,n={}){let r=t?{error:t instanceof Error?t.message:String(t),...n}:n;this.emit(`error`,e,r)}debug(e,t={}){(process.env.MCP_DEBUG===`1`||process.env.DEBUG?.includes(`mcp`))&&this.emit(`debug`,e,t)}child(t){return new e(t??this.component,this.sessionId)}asCallback(){return e=>this.info(e)}emit(e,t,n){let r={ts:new Date().toISOString(),level:e,sessionId:this.sessionId,corrId:this.corrId,component:this.component,msg:t,...n},i=`${JSON.stringify(r)}\n`;process.stderr.write(i);let a=process.env.OK_LOG_FILE;if(a)try{ne(a,i)}catch(e){console.warn(`[mcp-logger] Failed to write to OK_LOG_FILE: ${e instanceof Error?e.message:e}`)}}};function Nt(e=`mcp`){return new Mt(e)}function Pt(e,t){return jt.run(e,t)}function Ft(){return jt.getStore()}const It=new Set([`find`,`markdown`,`replace`]),Lt=[`backlinks`,`deadLinks`,`documents`,`enrichedPaths`,`entries`,`forwardLinks`,`hints`,`hubs`,`orphans`,`results`],Rt=[`checkpointRef`,`cwd`,`fileCount`,`matchCount`,`ok`,`query`,`stdoutTruncated`,`truncated`];function F(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function zt(e){return F(e)&&`requestId`in e}function Bt(e,t){if(It.has(e))return{redacted:!0,type:`string`,length:t.length,lines:t.length===0?0:t.split(`
14
+ `).length};let n=e===`command`?240:120;return t.length<=n?t:{type:`string`,length:t.length,preview:`${t.slice(0,n)}...`}}function Vt(e,t){if(t===null)return null;if(typeof t==`string`)return Bt(e,t);if(typeof t==`number`||typeof t==`boolean`)return t;if(Array.isArray(t))return{type:`array`,length:t.length};if(F(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 Ht(e,t){return F(t)?Object.fromEntries(Object.entries(t).map(([e,t])=>[e,Vt(e,t)])):Vt(e,t)}function Ut(e){let t={structuredKeys:Object.keys(e).sort()};for(let n of Rt)n in e&&(t[n]=e[n]);for(let n of Lt){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),F(e.warning)&&(t.warning=!0,`previewUrl`in e.warning&&(t.warningPreviewUrl=e.warning.previewUrl??null)),F(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 Wt(e){if(!F(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)=>F(t)&&typeof t.text==`string`?e+t.text.length:e,0)),F(e.structuredContent)&&Object.assign(t,Ut(e.structuredContent)),t}function Gt(e){return{connectionId:e.connectionId.slice(0,8),displayName:e.displayName,...e.clientInfo?.name?{clientName:e.clientInfo.name}:{},...e.label?{label:e.label}:{}}}function Kt(e){let t=e.at(-1);return zt(t)?{toolArgs:e.length>1?e[0]:void 0,extra:t}:{toolArgs:e[0],extra:void 0}}function qt(e,t,n){let r=n.logger;return r?async(...i)=>{let a=r.child(),o=Date.now(),{toolArgs:s,extra:c}=Kt(i);a.info(`tool start`,{tool:e,...c?.requestId===void 0?{}:{requestId:c.requestId},...c?.sessionId?{transportSessionId:c.sessionId}:{},...n.identityRef?.current?{agent:Gt(n.identityRef.current)}:{},...s===void 0?{}:{args:Ht(e,s)}});try{let n=await Pt(a,async()=>await t(...i));return a.info(`tool finish`,{tool:e,...c?.requestId===void 0?{}:{requestId:c.requestId},durationMs:Date.now()-o,result:Wt(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 Jt(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]=qt(r,i,t),n(...a)}),r}const I="Absolute host path to resolve the request against. Defaults only when the MCP client advertises exactly one root; otherwise pass `cwd` explicitly.",Yt=P.string().max(200).optional().describe(`Optional one-line user-outcome description (≤80 chars). Appears as a bullet in the timeline.`);function L(e,t){return{content:[{type:`text`,text:e}],...t?{isError:!0}:{}}}function R(e,t,n){return{content:[{type:`text`,text:e}],structuredContent:t,...n?{isError:!0}:{}}}const z="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.",Xt={ingest:`raw-sources layer (preserve external material, no analysis)`,research:`wiki layer, provisional (synthesize findings that can still change)`,consolidate:`wiki layer, canonical (promote stabilized research to source-of-truth)`},Zt={ingest:"user shares a URL or file they want preserved, or `research` needs raw sources",research:"`ingest` has captured the relevant sources (or the user points at one)",consolidate:"`research` has produced a provisional article AND a decision has actually been made"},Qt={ingest:"often `research` on the same topic — or just stop; raw preservation is frequently enough on its own",research:"usually stop (research lives as provisional indefinitely) or `consolidate` once a decision lands",consolidate:"update 2–3 neighbor docs to link the new canonical article; research articles it supersedes gain a `superseded_by` pointer"};function $t(e){return`## Where this fits
12
15
 
13
16
  Open Knowledge accretes a persistent wiki through three workflow tools, mapped to [Karpathy's three-layer knowledge-base pattern](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f):
14
17
 
@@ -18,14 +21,14 @@ Open Knowledge accretes a persistent wiki through three workflow tools, mapped t
18
21
 
19
22
  (Project-level folder structure + \`config.yml\` scaffolding is handled by the \`ok seed\` CLI, not by an MCP tool.)
20
23
 
21
- **This tool operates in the ${Wt[e]}.**
24
+ **This tool operates in the ${Xt[e]}.**
22
25
 
23
- - **Before this:** ${Gt[e]}
24
- - **After this:** ${Kt[e]}
26
+ - **Before this:** ${Zt[e]}
27
+ - **After this:** ${Qt[e]}
25
28
 
26
29
  Karpathy's insight: "The tedious part of maintaining a knowledge base is not the reading or the thinking — it's the bookkeeping." Humans abandon wikis because maintenance costs exceed perceived value. These tools exist so an agent can do the bookkeeping (fetching, summarizing, cross-linking, superseding) without fatigue. Follow the steps below faithfully — skipping the cross-linking, supersedes chains, or raw-source preservation is what turns a useful wiki back into an abandoned one.
27
30
 
28
- `}async function Jt(e,t){return typeof e==`function`?await e(t):e}async function Yt(e,t){return typeof e==`function`?await e(t):e}async function Xt(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 Yt(t,r);return{ok:!0,cwd:r,config:e}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}async function z(e,t,n,r){let i=await Xt(e,t,r);if(!i.ok)return i;let{cwd:a,config:o}=i;try{return{ok:!0,cwd:a,config:o,url:await Jt(n,a)}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}function B(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 V(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 H(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`${qt(`consolidate`)}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.
31
+ `}async function en(e,t){return typeof e==`function`?await e(t):e}async function tn(e,t){return typeof e==`function`?await e(t):e}async function nn(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 tn(t,r);return{ok:!0,cwd:r,config:e}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}async function B(e,t,n,r){let i=await nn(e,t,r);if(!i.ok)return i;let{cwd:a,config:o}=i;try{return{ok:!0,cwd:a,config:o,url:await en(n,a)}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}function V(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 H(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 U(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 rn(e,t){return`${$t(`consolidate`)}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.
29
32
 
30
33
  Topic: ${e}
31
34
 
@@ -172,28 +175,28 @@ superseded_by: <path-to-new-canonical-article>.md
172
175
  - **Don't delete research articles** — they are the trail; keep them with a \`superseded_by\` marker
173
176
  - **Don't rewrite research prose verbatim** — canonical articles have a different voice (direct, decided) than research (exploratory, provisional)
174
177
  - **Don't skip the supersedes / superseded_by links** — the audit trail matters for future readers
175
- `}const Qt=[`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(`
176
- `);function $t(e,t){e.tool(`consolidate`,Qt,{topic:N.string().describe(`The topic to consolidate into a canonical article`),cwd:N.string().optional().describe(F)},async e=>{let n=await Xt(t.resolveCwd,t.config,e.cwd);return n.ok?L(Zt(e.topic,n.config.content.dir),{previewUrl:null}):I(`Error: ${n.error}`,!0)})}function en(e){return e.split(`/`).map(encodeURIComponent).join(`/`)}function tn(e){return e.endsWith(`/`)?e.slice(0,-1):e}function nn(e){try{return new URL(e),!0}catch{return!1}}async function U(e,t,n){let r=n??await t.resolveCwd(),i=await Yt(t.config,r),s=o(i,r);return on(e,{config:i,lockDir:a(s),contentDir:s})}function rn(e){try{let t=d(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 W(e,t){let n=t??await e.resolveCwd(),r=await Yt(e.config,n),i=o(r,n),s={config:r,lockDir:a(i),contentDir:i};return{resolve:e=>on(e,s),ui:rn(s)}}function an(e){let t=e.toLowerCase();return t.endsWith(`.md`)?e.slice(0,-3):t.endsWith(`.mdx`)?e.slice(0,-4):e}function on(e,t){let n=`/#/${en(e)}`;if(process.env.OK_ELECTRON_PROTOCOL_HOST===`1`&&t.contentDir)try{let n=se(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&&nn(r))return{url:`${tn(r)}${n}`,source:`env`};try{let e=d(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&&nn(i)?{url:`${tn(i)}${n}`,source:`config`}:null}const sn=[`[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(`
177
- `);function cn(e,t){e.tool(`edit_document`,sn,{docName:N.string().describe(`Document name to edit`),find:N.string().describe(`Text to find (exact match)`),replace:N.string().describe(`Replacement text`),offset:N.number().int().min(0).optional().describe(`Exact occurrence to patch, as a JavaScript string offset in the current markdown`),summary:Ut,cwd:N.string().optional().describe(F)},async e=>{let n=await z(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return I(`Error: ${n.error}`,!0);let{cwd:r,config:i,url:s}=n;if(!s)return I(R,!0);let c=B(e.docName);if(!c.ok)return I(c.error,!0);let l=t.identityRef?.current,u=await H(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 I(`Error: ${u.error}`,!0);let d=a(o(i,r)),f=on(c.docName,{config:i,lockDir:d}),p=typeof u.subscriberCount==`number`?u.subscriberCount:void 0,m=(typeof u.systemSubscriberCount==`number`?u.systemSubscriberCount:void 0)===0,h=p===0,g=u.summary&&typeof u.summary==`object`?u.summary:void 0,_=typeof g?.hint==`string`?g.hint:void 0,v=[`Edit applied successfully.`];f&&v.push(`Preview: ${f.url}`),m&&v.push(f?`Open ${f.url} in your preview browser.`:`No preview attached. Start the UI.`),_&&v.push(_);let y=v.join(`
178
- `);if(!f&&!m&&!h&&!g)return I(y);let b={};return f&&(b.previewUrl=f.url,b.previewUrlSource=f.source),m&&(b.warning={message:`Open the previewUrl in your preview browser.`,action:`attach-preview-once`,previewUrl:f?.url??null}),g&&(b.summary=g),L(y,b)})}const ln=new Set([`cat`,`ls`,`grep`,`find`]),un=/\b[\w./-]+\.(md|mdx)\b/g;function G(e){return/\.(md|mdx)$/.test(e)}function dn(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 fn(e){return q(K(e)).filter(G)}function pn(e,t){let n=q(K(t)),r=n.length>0?n[n.length-1]:``,i=r&&r!==`.`?dn(r):``,a=[];i&&a.push(i);for(let t of e.split(`
179
- `)){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 mn(e){let t=[];for(let n of e.split(`
180
- `)){if(!n)continue;let e=n.indexOf(`:`);if(e<0)continue;let r=dn(n.slice(0,e));G(r)&&t.push(r)}return t}function hn(e){let t=[];for(let n of e.split(`
181
- `)){let e=dn(n);e&&G(e)&&t.push(e)}return t}function gn(e){return q(K(e)).filter(G)}function _n(e){return q(K(e)).length>0}function vn(e){let t=[],n=e.matchAll(un);for(let e of n)t.push(dn(e[0]));return t}function yn(e,t){let n=null;for(let e=t.length-1;e>=0;e--){let r=t[e];if(ln.has(r.command)){n=r;break}if((r.command===`head`||r.command===`tail`)&&_n(r)){n=r;break}}let r;if(!n)r=vn(e);else{switch(n.command){case`cat`:r=fn(n);break;case`ls`:r=pn(e,n);break;case`grep`:r=mn(e);break;case`find`:r=hn(e);break;case`head`:case`tail`:r=gn(n);break;default:r=vn(e)}r.length===0&&(r=vn(e))}let i=new Set,a=[];for(let e of r){let t=dn(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 bn=16*1024*1024;var xn=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 Sn(e){if(!he(e))throw Error(`createBashInstance: cwd must be absolute (got: ${e})`);return new Re({cwd:`/`,fs:new ze({root:M(e),allowSymlinks:!1})})}async function Cn(e,t){let n=await e.exec(t);if(n.stdout.length>bn)throw new xn(bn,n.stdout.length,{stdout:n.stdout.slice(0,bn),stderr:n.stderr,exitCode:n.exitCode});return{stdout:n.stdout,stderr:n.stderr,exitCode:n.exitCode}}function wn(e){return e.startsWith(`**/`)?e.slice(3):e}async function Tn(e,t,n={}){let r=Sn(t),i=[`-rn`,`-F`];(n.caseInsensitive??!0)&&i.push(`-i`);for(let e of n.include??[])i.push(`--include=${J(wn(e))}`);for(let e of n.exclude??[])i.push(`--exclude=${J(wn(e))}`),i.push(`--exclude-dir=${J(wn(e))}`);let a=n.paths?.length?n.paths.map(J):[`.`],o=`grep ${i.join(` `)} ${J(e)} ${a.join(` `)}`,s;try{s=await Cn(r,o)}catch(e){if(e instanceof xn)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(`
182
- `)){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 En=new Set([`.git`,n,`node_modules`,`.changeset`,`.claude`,`.agents`,`dist`,`build`]);async function Dn(e){let t=M(e),n=new Map,r=0,i=!1;async function a(e){if(i)return;let o;try{o=await ke(e,{withFileTypes:!0})}catch{return}for(let s of o){if(i)return;if(s.isDirectory()&&En.has(s.name))continue;let o=M(e,s.name);if(s.isDirectory()){await a(o);continue}if(s.isFile()){if(r>=1e3){i=!0;return}try{let e=await Ae(o);n.set(_e(t,o),e.mtimeMs),r++}catch{}}}}return await a(t),{snapshot:n,truncated:i}}function On(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 kn=[`node_modules`,`.git`,`dist`,`build`,`.next`,`.turbo`,`.nuxt`,`coverage`,`.cache`,`.parcel-cache`,`.vercel`,n,`.claude`];function An(e){return e===`--recursive`||e===`--dereference-recursive`?!0:e.startsWith(`--`)||!e.startsWith(`-`)?!1:/[rR]/.test(e.slice(1))}const jn=[{command:`grep`,applies:e=>e.slice(1).some(An),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 Mn(e){return e.map(e=>{let t=jn.find(t=>t.command===e.command);if(!t||!t.applies(e.args)||t.hasUserExcludes(e.args))return e;let n=t.buildExcludeArgs(kn),r=t.insertionIndex(e.args);return{command:e.command,args:[...e.args.slice(0,r),...n,...e.args.slice(r)]}})}function Nn(e){return e.map(e=>e.args.map(J).join(` `)).join(` | `)}const Pn=new Set([`cat`,`ls`,`grep`,`find`,`head`,`tail`,`wc`,`sort`,`uniq`,`cut`]),Fn=new Set([`>`,`>>`,`<`,`>&`,`<&`,`|&`]),In=new Set([`&`,`;`,`;;`,`&&`,`||`,`(`,`)`,`<(`,`>(`,`<<`,`<<-`]),Ln=new Set([`-o`,`--output-file`,`--output`]),Rn=[`-o=`,`--output-file=`,`--output=`],zn=new Set([`-exec`,`-execdir`,`-delete`,`-fprint`,`-fprintf`,`-fprint0`,`-ok`,`-okdir`]),Bn=/[`]|\$\(|\$\{|\$'/;function Vn(e){return typeof e==`object`&&!!e&&`op`in e}function Hn(e){let t=typeof e.op==`string`?e.op:`(unknown)`;return Fn.has(t)?{category:`write_blocked`,message:`Write operation blocked: '${t}'. exec is read-only. For document changes, use write_document or edit_document.`}:In.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 Un(e){let t=[];for(let n of e){if(typeof n==`string`){if(Bn.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(!Vn(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:Hn(n)}}return{args:t}}function Wn(e){if(!Pn.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(Ln.has(t)||Rn.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`&&zn.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 Gn(e){let t=e.trim();if(!t)return{error:{category:`unknown_command`,message:`Empty command.`}};let n;try{n=Be.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(Vn(e)&&e.op===`|`){r.push(i),i=[];continue}i.push(e)}r.push(i);let a=[];for(let e of r){let t=Un(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=Wn(n);if(r)return{error:r};a.push(n)}return{stages:a}}const Kn=/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;function qn(e,t){let n=e.match(Kn);if(!n)return null;try{let e=ve(n[1]);if(w(e)){if(t){let n=t.safeParse(e);return n.success?n.data:null}return e}}catch{}return null}const Jn=new WeakMap;function Yn(e){let t=Jn.get(e);if(t)return t;let n=e.map(e=>Ne(e.match,{dot:!0}));return Jn.set(e,n),n}function Xn(e,t){if(e.length===0)return{};let n=Yn(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 ce(M(e,`.git`)).isDirectory()}catch{return!1}}function Qn(e){return De({baseDir:M(e),timeout:{block:5e3}})}async function $n(e,t,n=5){if(!Zn(e))return{commits:[],source:`git-absent`};let r=Qn(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(`
183
- `)){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 er=5e3;async function tr(e){try{let t=(await De({baseDir:e,timeout:{block:er}}).revparse([`--abbrev-ref`,`HEAD`])).trim();return t&&t!==`HEAD`?t:null}catch{return null}}function nr(e,t){return De({baseDir:t,timeout:{block:er}}).env({GIT_DIR:e,GIT_WORK_TREE:t})}function rr(e,t){let n=l(t);return e.startsWith(n)?e.slice(n.length):e}async function ir(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=rr(t,r),s=c(o),l=[];for(let e of a.split(``)){let t=e.trimStart();if(!t)continue;let[n=``,i=``,a=``,c=``,u=``]=t.split(`\0`),d=n.trim();d.length===40&&l.push({hash:d,date:i,writerName:a,message:c,contributors:f(u),writerId:o,isAgent:s.isAgent,writerClassification:s.classification,branch:r})}return l}async function ar(e,t,n=5){let r=u(e);if(!r)return{commits:[],source:`shadow-repo-absent`};let i=await tr(e);if(!i)return{commits:[],source:`shadow-repo`};let a=nr(r,M(e)),o=``;try{o=await a.raw(`for-each-ref`,l(i),`--format=%(refname)`)}catch{return{commits:[],source:`shadow-repo`}}let s=o.split(`
184
- `).map(e=>e.trim()).filter(Boolean);return s.length===0?{commits:[],source:`shadow-repo`}:{commits:(await Promise.all(s.map(e=>ir(a,e,t,i,n)))).flat().sort((e,t)=>t.date.localeCompare(e.date)).slice(0,n),source:`shadow-repo`}}const or=1e3,sr=new Set([`.git`,n,`node_modules`,`.changeset`,`.claude`,`.agents`,`dist`,`build`]),cr=/\.(md|mdx)$/i,lr=N.object({title:N.string().optional(),description:N.string().optional(),tags:N.array(N.string()).default([])});function ur(e){return e.replace(/\.md$/,``).replace(/\.mdx$/,``)}async function dr(e){try{let t=qn(await Oe(e,`utf-8`),lr);return t?{title:t.title,description:t.description,tags:t.tags??[]}:{tags:[]}}catch{return null}}async function fr(e,t){if(!e)return null;let n=await V(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 pr(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 V(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 mr(e,t){if(!e)return null;let n=await V(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 hr(e,t,n){let r=t??[],i=r.length===0?{}:Xn(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 gr(e,t,n={}){let r=e.replace(/^\.\//,``).replace(/^\/+/,``),i=M(t.projectDir,r),a=t.historyDepth??5,o=n.includeRichFields===!0,s=dr(i);if(!o){let e=hr(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,fr(t.serverUrl,ur(r)).catch(()=>null),mr(t.serverUrl,ur(r)).catch(()=>null),ar(t.projectDir,r,a).catch(()=>({commits:[],source:`shadow-repo`})),$n(t.projectDir,r,a).catch(()=>({commits:[],source:`git`}))]),p=hr(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 _r(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>=or){n.truncated=!0;break}let a;try{a=await ke(e.path,{withFileTypes:!0})}catch{continue}for(let o of a){if(r>=or){n.truncated=!0;break}r++;let a=o.name;if(o.isDirectory()){if(sr.has(a)||a.startsWith(`.`))continue;e.depth===0&&n.childDirCount++,i.push({path:`${e.path}/${a}`,depth:e.depth+1})}else if(o.isFile()&&cr.test(a)){n.recursiveMdCount++,e.depth===0&&n.directMdCount++;let r=`${e.path}/${a}`;try{let e=await Ae(r);(!n.mostRecent||e.mtimeMs>n.mostRecent.mtimeMs)&&(n.mostRecent={absPath:r,relPath:_e(t,r).split(/[\\/]/).filter(Boolean).join(`/`),mtimeMs:e.mtimeMs})}catch{}}}}return n}async function vr(e,t){let n=e.replace(/^\.\//,``).replace(/^\/+/,``).replace(/\/+$/,``),r=await _r(M(t.projectDir,n),t.projectDir),i;if(r.mostRecent){let e=await dr(r.mostRecent.absPath);i={path:r.mostRecent.relPath,title:e?.title??pe(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=Xn(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 yr=50*1024,br=/\.(png|jpe?g|gif|webp|svg|pdf|zip|tar|gz|tgz|mp4|mov|mp3|wav|ico|bmp)$/i,xr=["**STOP — native tools on in-scope markdown.** Do NOT use your host's native `Read`, `Grep`, or `Glob` on `.md` / `.mdx` paths inside OK's content directory — use `exec` (this tool) instead. Native file tools skip frontmatter, backlinks, shadow-repo activity, and project git history that `exec` returns for every matched wiki file. Reserve native `Read`/`Grep`/`Glob` for source code and non-markdown paths only.",``,`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(`
185
- `);function Sr(e){let t=e.split(`
186
- `),n=t[t.length-1]===``?t.length-1:t.length;if(n<=500&&e.length<=yr)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>yr)break;a++}let o=t.slice(0,a).join(`
187
- `),s=n-a;return{text:`${o}\n<truncated: ${s} more lines — re-run with a more-specific query>`,truncated:!0,omittedLines:s}}function Cr(e){let t=[];for(let n of e)if(n.command===`cat`)for(let e of n.args.slice(1))e.startsWith(`-`)||br.test(e)&&t.push(e);return t}function wr(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 Tr(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=wr(n.args),i=t.split(`
188
- `),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 Er(e){return e.type===`directory`}function Dr(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 Or(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 kr(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 Ar(e){if(e.length===0)return``;let t=[``,`### Referenced files`,``];for(let n of e)t.push(Er(n)?Dr(n):Or(n));return t.join(`
189
- `)}async function jr(e,t){let n=[],r=[];return await Promise.all(t.map(async t=>{try{let i=await Ae(M(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 L(t,{enrichedPaths:[],error:{category:e,message:t}},!0)}function Mr(e,t){return e.map(e=>{let n=t(an(e.path));return{...e,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}})}async function Nr(e,t){let n=await z(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=Gn(e.command);if(`error`in o)return Y(o.error.category,o.error.message);let s=Mn(o.stages),c=Nn(s),l=await Dn(r),u=Sn(r),d=``,f=``;try{let e=await Cn(u,c);d=e.stdout,f=e.stderr}catch(e){return e instanceof xn?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 Dn(r),m=On(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=Sr(d),g=yn(d,s),{files:_,dirs:v}=await jr(r,g),y=s.length===1&&s[0].command===`cat`&&_.length===1,b=i.folders,x=await Promise.all(_.map(e=>gr(e,{projectDir:r,serverUrl:a,folderRules:b},{includeRichFields:y}).catch(()=>({path:e,tags:[],backlinkCount:null,backlinks:null,forwardLinkCount:null,forwardLinks:null,history:null,historySource:null,projectHistory:null,projectHistorySource:null})))),S=await Promise.all(v.map(e=>vr(e,{projectDir:r,folderRules:b}).catch(()=>({path:e,type:`directory`,directMdCount:0,recursiveMdCount:0,childDirCount:0,truncated:!1}))));if(!y&&a&&x.length>0){let e=await pr(a,x.map(e=>ur(e.path))).catch(()=>null);if(e)for(let t of x){let n=e.get(ur(t.path));typeof n==`number`&&(t.backlinkCount=n)}}let C=new Map(x.map(e=>[e.path,e])),w=new Map(S.map(e=>[e.path,e])),T=[];for(let e of g){let t=C.get(e);if(t){T.push(t);continue}let n=w.get(e);n&&T.push(n)}let E=Cr(s),D=[];E.length>0&&D.push(`File${E.length>1?`s`:``} ${E.join(`, `)} appear${E.length===1?`s`:``} to be binary (image/PDF/etc.) — exec returns text only (NG8). For binary retrieval, use native Read.`);let O=Tr(s,d);O&&D.push(O.banner),f&&D.push(`stderr: ${f.trim()}`);let ee=D.length>0?`${D.join(`
190
- `)}\n\n`:``,k=kr(s,w,C)+h.text,A=`${ee}${k}${Ar(T)}`,{resolve:te,ui:ne}=await W({config:i,resolveCwd:async()=>r}),j=Mr(T,te),re=ne;return L(A,{enrichedPaths:j,stdout:k,stdoutTruncated:h.truncated,cwd:r,...re?{ui:re}:{},...D.length>0?{warnings:D}:{}})}function Pr(e,t){e.tool(`exec`,xr,{command:N.string().describe(`Read-only bash command (allowlist: cat, ls, grep, find, head, tail, wc, sort, uniq, cut; pipes OK)`),cwd:N.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 Nr(e,t)}catch(e){return Y(`shell_construct_blocked`,`exec handler error: ${e instanceof Error?e.message:String(e)}`)}})}const Fr=[`[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(`
191
- `);function Ir(e,t){e.tool(`get_backlinks`,Fr,{docName:N.string().describe(`Target page docName`),cwd:N.string().optional().describe(F)},async e=>{let n=await z(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return I(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return I(R,!0);let a=B(e.docName);if(!a.ok)return I(a.error,!0);let o=await V(i,`/api/backlinks?docName=${encodeURIComponent(a.docName)}`);if(!o.ok)return I(`Error: ${o.error}`,!0);let{ok:s,...c}=o,l=c,{resolve:u,ui:d}=await W(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 L(JSON.stringify(p,null,2),p)})}const Lr=[`[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(`
192
- `);function Rr(e,t){e.tool(`get_dead_links`,Lr,{sourceDocNames:N.array(N.string()).optional().describe(`Referring source docs to narrow the audit with OR semantics`),cwd:N.string().optional().describe(F)},async e=>{let n=await z(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return I(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return I(R,!0);let a=new URLSearchParams;for(let t of e.sourceDocNames??[]){let e=B(t);if(!e.ok)return I(e.error,!0);a.append(`sourceDocName`,e.docName)}let o=a.toString(),s=await V(i,`/api/dead-links${o?`?${o}`:``}`);if(!s.ok)return I(`Error: ${s.error}`,!0);let{ok:c,...l}=s,u=l,{resolve:d,ui:f}=await W(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 L(JSON.stringify(m,null,2),m)})}const zr=[`[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(`
193
- `);function Br(e,t){e.tool(`get_forward_links`,zr,{docName:N.string().describe(`Source page docName`),cwd:N.string().optional().describe(F)},async e=>{let n=await z(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return I(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return I(R,!0);let a=B(e.docName);if(!a.ok)return I(a.error,!0);let o=await V(i,`/api/forward-links?docName=${encodeURIComponent(a.docName)}`);if(!o.ok)return I(`Error: ${o.error}`,!0);let{ok:s,...c}=o,l=c,{resolve:u,ui:d}=await W(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 L(JSON.stringify(p,null,2),p)})}const Vr=[`[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(`
194
- `);function Hr(e,t){e.tool(`get_history`,Vr,{docName:N.string().describe(`Document name to query history for`),branch:N.string().optional().describe(`Branch name (default: current branch)`),limit:N.number().int().min(1).max(200).optional().describe(`Maximum entries to return (default 50, max 200)`),offset:N.number().int().min(0).optional().describe(`Number of entries to skip for pagination (default 0)`),type:N.enum([`checkpoint`,`upstream`,`wip`]).optional().describe(`Filter by entry type`),author:N.string().optional().describe(`Filter to entries by this author name or email`),excludeAuthor:N.string().optional().describe(`Exclude entries by this author name or email`),cwd:N.string().optional().describe(F)},async e=>{let n=await z(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return I(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return I(R,!0);let a=B(e.docName);if(!a.ok)return I(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 V(i,`/api/history?${o.toString()}`);if(!s.ok)return I(`Error: ${s.error}`,!0);let{ok:c,...l}=s,u=await U(a.docName,{config:t.config,resolveCwd:t.resolveCwd},r);return L(JSON.stringify(l,null,2),{...l,previewUrl:u?.url??null,...u?{previewUrlSource:u.source}:{}})})}const Ur=[`[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(`
195
- `);function Wr(e,t){e.tool(`get_hubs`,Ur,{limit:N.number().int().positive().optional().describe(`Maximum number of hubs to return`),cwd:N.string().optional().describe(F)},async e=>{let n=await z(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return I(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return I(R,!0);let a=await V(i,`/api/hubs${e.limit?`?limit=${encodeURIComponent(String(e.limit))}`:``}`);if(!a.ok)return I(`Error: ${a.error}`,!0);let{ok:o,...s}=a,c=s,{resolve:l,ui:u}=await W(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 L(JSON.stringify(f,null,2),f)})}const Gr=[`[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(`
196
- `);function Kr(e,t){e.tool(`get_orphans`,Gr,{mode:N.enum(i).optional().describe(`Filter which type of graph disconnection to surface`),cwd:N.string().optional().describe(F)},async e=>{let n=await z(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return I(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return I(R,!0);let a=await V(i,`/api/orphans${e.mode?`?mode=${encodeURIComponent(e.mode)}`:``}`);if(!a.ok)return I(`Error: ${a.error}`,!0);let{ok:o,...s}=a,c=s,{resolve:l,ui:u}=await W(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 L(JSON.stringify(f,null,2),f)})}function qr(e,t){return`${qt(`ingest`)}Capture this external source into the project knowledge base as raw reference material. **Raw preservation only** — no summary, no analysis, no interpretation. Summarizing is the job of the \`research\` tool later.
178
+ `}const an=[`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(`
179
+ `);function on(e,t){e.tool(`consolidate`,an,{topic:P.string().describe(`The topic to consolidate into a canonical article`),cwd:P.string().optional().describe(I)},async e=>{let n=await nn(t.resolveCwd,t.config,e.cwd);return n.ok?R(rn(e.topic,n.config.content.dir),{previewUrl:null}):L(`Error: ${n.error}`,!0)})}function sn(e){return e.split(`/`).map(encodeURIComponent).join(`/`)}function cn(e){return e.endsWith(`/`)?e.slice(0,-1):e}function ln(e){try{return new URL(e),!0}catch{return!1}}async function W(e,t,n){let r=n??await t.resolveCwd(),i=await tn(t.config,r),s=o(i,r);return fn(e,{config:i,lockDir:a(s),contentDir:s})}function un(e){try{let t=p(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 G(e,t){let n=t??await e.resolveCwd(),r=await tn(e.config,n),i=o(r,n),s={config:r,lockDir:a(i),contentDir:i};return{resolve:e=>fn(e,s),ui:un(s)}}function dn(e){let t=e.toLowerCase();return t.endsWith(`.md`)?e.slice(0,-3):t.endsWith(`.mdx`)?e.slice(0,-4):e}function fn(e,t){let n=`/#/${sn(e)}`;if(process.env.OK_ELECTRON_PROTOCOL_HOST===`1`&&t.contentDir)try{let n=ce(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&&ln(r))return{url:`${cn(r)}${n}`,source:`env`};try{let e=p(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&&ln(i)?{url:`${cn(i)}${n}`,source:`config`}:null}const pn=[`[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(`
180
+ `);function mn(e,t){e.tool(`edit_document`,pn,{docName:P.string().describe(`Document name to edit`),find:P.string().describe(`Text to find (exact match)`),replace:P.string().describe(`Replacement text`),offset:P.number().int().min(0).optional().describe(`Exact occurrence to patch, as a JavaScript string offset in the current markdown`),summary:Yt,cwd:P.string().optional().describe(I)},async e=>{let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return L(`Error: ${n.error}`,!0);let{cwd:r,config:i,url:s}=n;if(!s)return L(z,!0);let c=V(e.docName);if(!c.ok)return L(c.error,!0);let l=t.identityRef?.current,u=await U(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 L(`Error: ${u.error}`,!0);let d=a(o(i,r)),f=fn(c.docName,{config:i,lockDir:d}),p=typeof u.subscriberCount==`number`?u.subscriberCount:void 0,m=(typeof u.systemSubscriberCount==`number`?u.systemSubscriberCount:void 0)===0,h=p===0,g=u.summary&&typeof u.summary==`object`?u.summary:void 0,_=typeof g?.hint==`string`?g.hint:void 0,v=[`Edit applied successfully.`];f&&v.push(`Preview: ${f.url}`),m&&v.push(f?`Open ${f.url} in your preview browser.`:`No preview attached. Start the UI.`),_&&v.push(_);let y=v.join(`
181
+ `);if(!f&&!m&&!h&&!g)return L(y);let b={};return f&&(b.previewUrl=f.url,b.previewUrlSource=f.source),m&&(b.warning={message:`Open the previewUrl in your preview browser.`,action:`attach-preview-once`,previewUrl:f?.url??null}),g&&(b.summary=g),R(y,b)})}const hn=new Set([`cat`,`ls`,`grep`,`find`]),gn=/\b[\w./-]+\.(md|mdx)\b/g;function _n(e){return/\.(md|mdx)$/.test(e)}function vn(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 yn(e){return q(K(e)).filter(_n)}function bn(e,t){let n=q(K(t)),r=n.length>0?n[n.length-1]:``,i=r&&r!==`.`?vn(r):``,a=[];i&&a.push(i);for(let t of e.split(`
182
+ `)){let e=t.trim();if(!e||/\.[a-z0-9]+$/i.test(e)&&!_n(e))continue;let n=i?`${i}/${e}`:e;a.push(n)}return a}function xn(e){let t=[];for(let n of e.split(`
183
+ `)){if(!n)continue;let e=n.indexOf(`:`);if(e<0)continue;let r=vn(n.slice(0,e));_n(r)&&t.push(r)}return t}function Sn(e){let t=[];for(let n of e.split(`
184
+ `)){let e=vn(n);e&&_n(e)&&t.push(e)}return t}function Cn(e){return q(K(e)).filter(_n)}function wn(e){return q(K(e)).length>0}function Tn(e){let t=[],n=e.matchAll(gn);for(let e of n)t.push(vn(e[0]));return t}function En(e,t){let n=null;for(let e=t.length-1;e>=0;e--){let r=t[e];if(hn.has(r.command)){n=r;break}if((r.command===`head`||r.command===`tail`)&&wn(r)){n=r;break}}let r;if(!n)r=Tn(e);else{switch(n.command){case`cat`:r=yn(n);break;case`ls`:r=bn(e,n);break;case`grep`:r=xn(e);break;case`find`:r=Sn(e);break;case`head`:case`tail`:r=Cn(n);break;default:r=Tn(e)}r.length===0&&(r=Tn(e))}let i=new Set,a=[];for(let e of r){let t=vn(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 Dn=16*1024*1024;var On=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 kn(e){if(!_e(e))throw Error(`createBashInstance: cwd must be absolute (got: ${e})`);return new Be({cwd:`/`,fs:new Ve({root:M(e),allowSymlinks:!1})})}async function An(e,t){let n=await e.exec(t);if(n.stdout.length>Dn)throw new On(Dn,n.stdout.length,{stdout:n.stdout.slice(0,Dn),stderr:n.stderr,exitCode:n.exitCode});return{stdout:n.stdout,stderr:n.stderr,exitCode:n.exitCode}}function jn(e){return e.startsWith(`**/`)?e.slice(3):e}async function Mn(e,t,n={}){let r=kn(t),i=[`-rn`,`-F`];(n.caseInsensitive??!0)&&i.push(`-i`);for(let e of n.include??[])i.push(`--include=${J(jn(e))}`);for(let e of n.exclude??[])i.push(`--exclude=${J(jn(e))}`),i.push(`--exclude-dir=${J(jn(e))}`);let a=n.paths?.length?n.paths.map(J):[`.`],o=`grep ${i.join(` `)} ${J(e)} ${a.join(` `)}`,s;try{s=await An(r,o)}catch(e){if(e instanceof On)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(`
185
+ `)){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 Nn=new Set([`.git`,n,`node_modules`,`.changeset`,`.claude`,`.agents`,`dist`,`build`]);async function Pn(e){let t=M(e),n=new Map,r=0,i=!1;async function a(e){if(i)return;let o;try{o=await je(e,{withFileTypes:!0})}catch{return}for(let s of o){if(i)return;if(s.isDirectory()&&Nn.has(s.name))continue;let o=M(e,s.name);if(s.isDirectory()){await a(o);continue}if(s.isFile()){if(r>=1e3){i=!0;return}try{let e=await Me(o);n.set(ye(t,o),e.mtimeMs),r++}catch{}}}}return await a(t),{snapshot:n,truncated:i}}function Fn(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 In=[`node_modules`,`.git`,`dist`,`build`,`.next`,`.turbo`,`.nuxt`,`coverage`,`.cache`,`.parcel-cache`,`.vercel`,n,`.claude`];function Ln(e){return e===`--recursive`||e===`--dereference-recursive`?!0:e.startsWith(`--`)||!e.startsWith(`-`)?!1:/[rR]/.test(e.slice(1))}const Rn=[{command:`grep`,applies:e=>e.slice(1).some(Ln),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 zn(e){return e.map(e=>{let t=Rn.find(t=>t.command===e.command);if(!t||!t.applies(e.args)||t.hasUserExcludes(e.args))return e;let n=t.buildExcludeArgs(In),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 Vn=new Set([`cat`,`ls`,`grep`,`find`,`head`,`tail`,`wc`,`sort`,`uniq`,`cut`]),Hn=new Set([`>`,`>>`,`<`,`>&`,`<&`,`|&`]),Un=new Set([`&`,`;`,`;;`,`&&`,`||`,`(`,`)`,`<(`,`>(`,`<<`,`<<-`]),Wn=new Set([`-o`,`--output-file`,`--output`]),Gn=[`-o=`,`--output-file=`,`--output=`],Kn=new Set([`-exec`,`-execdir`,`-delete`,`-fprint`,`-fprintf`,`-fprint0`,`-ok`,`-okdir`]),qn=/[`]|\$\(|\$\{|\$'/;function Jn(e){return typeof e==`object`&&!!e&&`op`in e}function Yn(e){let t=typeof e.op==`string`?e.op:`(unknown)`;return Hn.has(t)?{category:`write_blocked`,message:`Write operation blocked: '${t}'. exec is read-only. For document changes, use write_document or edit_document.`}:Un.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 Xn(e){let t=[];for(let n of e){if(typeof n==`string`){if(qn.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(!Jn(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:Yn(n)}}return{args:t}}function Zn(e){if(!Vn.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)||Gn.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`&&Kn.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 Qn(e){let t=e.trim();if(!t)return{error:{category:`unknown_command`,message:`Empty command.`}};let n;try{n=He.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(Jn(e)&&e.op===`|`){r.push(i),i=[];continue}i.push(e)}r.push(i);let a=[];for(let e of r){let t=Xn(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=Zn(n);if(r)return{error:r};a.push(n)}return{stages:a}}const $n=/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;function er(e,t){let n=e.match($n);if(!n)return null;try{let e=be(n[1]);if(T(e)){if(t){let n=t.safeParse(e);return n.success?n.data:null}return e}}catch{}return null}const tr=new WeakMap;function nr(e){let t=tr.get(e);if(t)return t;let n=e.map(e=>Fe(e.match,{dot:!0}));return tr.set(e,n),n}function rr(e,t){if(e.length===0)return{};let n=nr(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 ir(e){try{return le(M(e,`.git`)).isDirectory()}catch{return!1}}function ar(e){return N({baseDir:M(e),timeout:{block:5e3}})}async function or(e,t,n=5){if(!ir(e))return{commits:[],source:`git-absent`};let r=ar(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(`
186
+ `)){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 sr=5e3;async function cr(e){try{let t=(await N({baseDir:e,timeout:{block:sr}}).revparse([`--abbrev-ref`,`HEAD`])).trim();return t&&t!==`HEAD`?t:null}catch{return null}}function lr(e,t){return N({baseDir:t,timeout:{block:sr}}).env({GIT_DIR:e,GIT_WORK_TREE:t})}function ur(e,t){let n=f(t);return e.startsWith(n)?e.slice(n.length):e}async function dr(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=ur(t,r),s=m(o),c=[];for(let e of a.split(``)){let t=e.trimStart();if(!t)continue;let[n=``,i=``,a=``,u=``,d=``]=t.split(`\0`),f=n.trim();f.length===40&&c.push({hash:f,date:i,writerName:a,message:u,contributors:l(d),writerId:o,isAgent:s.isAgent,writerClassification:s.classification,branch:r})}return c}async function fr(e,t,n=5){let r=h(e);if(!r)return{commits:[],source:`shadow-repo-absent`};let i=await cr(e);if(!i)return{commits:[],source:`shadow-repo`};let a=lr(r,M(e)),o=``;try{o=await a.raw(`for-each-ref`,f(i),`--format=%(refname)`)}catch{return{commits:[],source:`shadow-repo`}}let s=o.split(`
187
+ `).map(e=>e.trim()).filter(Boolean);return s.length===0?{commits:[],source:`shadow-repo`}:{commits:(await Promise.all(s.map(e=>dr(a,e,t,i,n)))).flat().sort((e,t)=>t.date.localeCompare(e.date)).slice(0,n),source:`shadow-repo`}}const pr=1e3,mr=new Set([`.git`,n,`node_modules`,`.changeset`,`.claude`,`.agents`,`dist`,`build`]),hr=/\.(md|mdx)$/i,gr=P.object({title:P.string().optional(),description:P.string().optional(),tags:P.array(P.string()).default([])});function _r(e){return e.replace(/\.md$/,``).replace(/\.mdx$/,``)}async function vr(e){try{let t=er(await Ae(e,`utf-8`),gr);return t?{title:t.title,description:t.description,tags:t.tags??[]}:{tags:[]}}catch{return null}}async function yr(e,t){if(!e)return null;let n=await H(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 br(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 H(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 xr(e,t){if(!e)return null;let n=await H(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 Sr(e,t,n){let r=t??[],i=r.length===0?{}:rr(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 Cr(e,t,n={}){let r=e.replace(/^\.\//,``).replace(/^\/+/,``),i=M(t.projectDir,r),a=t.historyDepth??5,o=n.includeRichFields===!0,s=vr(i);if(!o){let e=Sr(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,yr(t.serverUrl,_r(r)).catch(()=>null),xr(t.serverUrl,_r(r)).catch(()=>null),fr(t.projectDir,r,a).catch(()=>({commits:[],source:`shadow-repo`})),or(t.projectDir,r,a).catch(()=>({commits:[],source:`git`}))]),p=Sr(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 wr(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>=pr){n.truncated=!0;break}let a;try{a=await je(e.path,{withFileTypes:!0})}catch{continue}for(let o of a){if(r>=pr){n.truncated=!0;break}r++;let a=o.name;if(o.isDirectory()){if(mr.has(a)||a.startsWith(`.`))continue;e.depth===0&&n.childDirCount++,i.push({path:`${e.path}/${a}`,depth:e.depth+1})}else if(o.isFile()&&hr.test(a)){n.recursiveMdCount++,e.depth===0&&n.directMdCount++;let r=`${e.path}/${a}`;try{let e=await Me(r);(!n.mostRecent||e.mtimeMs>n.mostRecent.mtimeMs)&&(n.mostRecent={absPath:r,relPath:ye(t,r).split(/[\\/]/).filter(Boolean).join(`/`),mtimeMs:e.mtimeMs})}catch{}}}}return n}async function Tr(e,t){let n=e.replace(/^\.\//,``).replace(/^\/+/,``).replace(/\/+$/,``),r=await wr(M(t.projectDir,n),t.projectDir),i;if(r.mostRecent){let e=await vr(r.mostRecent.absPath);i={path:r.mostRecent.relPath,title:e?.title??he(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=rr(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 Er=50*1024,Dr=/\.(png|jpe?g|gif|webp|svg|pdf|zip|tar|gz|tgz|mp4|mov|mp3|wav|ico|bmp)$/i,Or=["**STOP — native tools on in-scope markdown.** Do NOT use your host's native `Read`, `Grep`, or `Glob` on `.md` / `.mdx` paths inside OK's content directory — use `exec` (this tool) instead. Native file tools skip frontmatter, backlinks, shadow-repo activity, and project git history that `exec` returns for every matched wiki file. Reserve native `Read`/`Grep`/`Glob` for source code and non-markdown paths only.",``,`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(`
188
+ `);function kr(e){let t=e.split(`
189
+ `),n=t[t.length-1]===``?t.length-1:t.length;if(n<=500&&e.length<=Er)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>Er)break;a++}let o=t.slice(0,a).join(`
190
+ `),s=n-a;return{text:`${o}\n<truncated: ${s} more lines — re-run with a more-specific query>`,truncated:!0,omittedLines:s}}function Ar(e){let t=[];for(let n of e)if(n.command===`cat`)for(let e of n.args.slice(1))e.startsWith(`-`)||Dr.test(e)&&t.push(e);return t}function jr(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 Mr(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=jr(n.args),i=t.split(`
191
+ `),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 Nr(e){return e.type===`directory`}function Pr(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 Fr(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 Ir(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 Lr(e){if(e.length===0)return``;let t=[``,`### Referenced files`,``];for(let n of e)t.push(Nr(n)?Pr(n):Fr(n));return t.join(`
192
+ `)}async function Rr(e,t){let n=[],r=[];return await Promise.all(t.map(async t=>{try{let i=await Me(M(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 R(t,{enrichedPaths:[],error:{category:e,message:t}},!0)}function zr(e,t){return e.map(e=>{let n=t(dn(e.path));return{...e,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}})}async function Br(e,t){let n=await B(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=Qn(e.command);if(`error`in o)return Y(o.error.category,o.error.message);let s=zn(o.stages),c=Bn(s),l=await Pn(r),u=kn(r),d=``,f=``;try{let e=await An(u,c);d=e.stdout,f=e.stderr}catch(e){return e instanceof On?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 Pn(r),m=Fn(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=kr(d),g=En(d,s),{files:_,dirs:v}=await Rr(r,g),y=s.length===1&&s[0].command===`cat`&&_.length===1,b=i.folders,x=await Promise.all(_.map(e=>Cr(e,{projectDir:r,serverUrl:a,folderRules:b},{includeRichFields:y}).catch(()=>({path:e,tags:[],backlinkCount:null,backlinks:null,forwardLinkCount:null,forwardLinks:null,history:null,historySource:null,projectHistory:null,projectHistorySource:null})))),S=await Promise.all(v.map(e=>Tr(e,{projectDir:r,folderRules:b}).catch(()=>({path:e,type:`directory`,directMdCount:0,recursiveMdCount:0,childDirCount:0,truncated:!1}))));if(!y&&a&&x.length>0){let e=await br(a,x.map(e=>_r(e.path))).catch(()=>null);if(e)for(let t of x){let n=e.get(_r(t.path));typeof n==`number`&&(t.backlinkCount=n)}}let C=new Map(x.map(e=>[e.path,e])),w=new Map(S.map(e=>[e.path,e])),T=[];for(let e of g){let t=C.get(e);if(t){T.push(t);continue}let n=w.get(e);n&&T.push(n)}let E=Ar(s),D=[];E.length>0&&D.push(`File${E.length>1?`s`:``} ${E.join(`, `)} appear${E.length===1?`s`:``} to be binary (image/PDF/etc.) — exec returns text only (NG8). For binary retrieval, use native Read.`);let O=Mr(s,d);O&&D.push(O.banner),f&&D.push(`stderr: ${f.trim()}`);let ee=D.length>0?`${D.join(`
193
+ `)}\n\n`:``,k=Ir(s,w,C)+h.text,te=`${ee}${k}${Lr(T)}`,{resolve:A,ui:ne}=await G({config:i,resolveCwd:async()=>r}),re=zr(T,A),j=ne;return R(te,{enrichedPaths:re,stdout:k,stdoutTruncated:h.truncated,cwd:r,...j?{ui:j}:{},...D.length>0?{warnings:D}:{}})}function Vr(e,t){e.tool(`exec`,Or,{command:P.string().describe(`Read-only bash command (allowlist: cat, ls, grep, find, head, tail, wc, sort, uniq, cut; pipes OK)`),cwd:P.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 Hr=[`[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(`
194
+ `);function Ur(e,t){e.tool(`get_backlinks`,Hr,{docName:P.string().describe(`Target page docName`),cwd:P.string().optional().describe(I)},async e=>{let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return L(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return L(z,!0);let a=V(e.docName);if(!a.ok)return L(a.error,!0);let o=await H(i,`/api/backlinks?docName=${encodeURIComponent(a.docName)}`);if(!o.ok)return L(`Error: ${o.error}`,!0);let{ok:s,...c}=o,l=c,{resolve:u,ui:d}=await G(t,r),f=(l.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 R(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(`
195
+ `);function Gr(e,t){e.tool(`get_dead_links`,Wr,{sourceDocNames:P.array(P.string()).optional().describe(`Referring source docs to narrow the audit with OR semantics`),cwd:P.string().optional().describe(I)},async e=>{let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return L(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return L(z,!0);let a=new URLSearchParams;for(let t of e.sourceDocNames??[]){let e=V(t);if(!e.ok)return L(e.error,!0);a.append(`sourceDocName`,e.docName)}let o=a.toString(),s=await H(i,`/api/dead-links${o?`?${o}`:``}`);if(!s.ok)return L(`Error: ${s.error}`,!0);let{ok:c,...l}=s,u=l,{resolve:d,ui:f}=await G(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 R(JSON.stringify(m,null,2),m)})}const Kr=[`[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(`
196
+ `);function qr(e,t){e.tool(`get_forward_links`,Kr,{docName:P.string().describe(`Source page docName`),cwd:P.string().optional().describe(I)},async e=>{let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return L(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return L(z,!0);let a=V(e.docName);if(!a.ok)return L(a.error,!0);let o=await H(i,`/api/forward-links?docName=${encodeURIComponent(a.docName)}`);if(!o.ok)return L(`Error: ${o.error}`,!0);let{ok:s,...c}=o,l=c,{resolve:u,ui:d}=await G(t,r),f=(l.forwardLinks??[]).map(e=>{let t=e.kind===`doc`&&typeof e.docName==`string`?e.docName:null,n=t?u(t):null;return{...e,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}}),p={...l,forwardLinks:f,ui:d,cwd:r};return R(JSON.stringify(p,null,2),p)})}const Jr=[`[Requires: Hocuspocus server] List version history for a document.`,`Returns timeline entries from the shadow repo, sorted by timestamp descending.`,"Each entry includes a commit SHA that can be passed to `rollback_to_version`.",``,`**Parameters:**`,"- `docName` — Document name to query history for, typically without extension. A trailing `.md` or `.mdx` is stripped automatically.","- `branch` (optional) — Branch name (default: current branch)","- `limit` (optional) — Maximum entries to return (default 50, max 200)","- `offset` (optional) — Number of entries to skip for pagination (default 0)",'- `type` (optional) — Filter by entry type: "checkpoint", "upstream", or "wip"',"- `author` (optional) — Filter to entries by this author name or email","- `excludeAuthor` (optional) — Exclude entries by this author name or email"].join(`
197
+ `);function Yr(e,t){e.tool(`get_history`,Jr,{docName:P.string().describe(`Document name to query history for`),branch:P.string().optional().describe(`Branch name (default: current branch)`),limit:P.number().int().min(1).max(200).optional().describe(`Maximum entries to return (default 50, max 200)`),offset:P.number().int().min(0).optional().describe(`Number of entries to skip for pagination (default 0)`),type:P.enum([`checkpoint`,`upstream`,`wip`]).optional().describe(`Filter by entry type`),author:P.string().optional().describe(`Filter to entries by this author name or email`),excludeAuthor:P.string().optional().describe(`Exclude entries by this author name or email`),cwd:P.string().optional().describe(I)},async e=>{let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return L(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return L(z,!0);let a=V(e.docName);if(!a.ok)return L(a.error,!0);let o=new URLSearchParams;o.set(`docName`,a.docName),e.branch&&o.set(`branch`,e.branch),e.limit!=null&&o.set(`limit`,String(e.limit)),e.offset!=null&&o.set(`offset`,String(e.offset)),e.type&&o.set(`type`,e.type),e.author&&o.set(`author`,e.author),e.excludeAuthor&&o.set(`excludeAuthor`,e.excludeAuthor);let s=await H(i,`/api/history?${o.toString()}`);if(!s.ok)return L(`Error: ${s.error}`,!0);let{ok:c,...l}=s,u=await W(a.docName,{config:t.config,resolveCwd:t.resolveCwd},r);return R(JSON.stringify(l,null,2),{...l,previewUrl:u?.url??null,...u?{previewUrlSource:u.source}:{}})})}const Xr=[`[Requires: Hocuspocus server] Find the most-linked pages in the knowledge graph.`,`Returns hub pages ordered by inbound link count as JSON.`,``,`**Parameters:**`,"- `limit` (optional) — Maximum number of hubs to return (default 20)"].join(`
198
+ `);function Zr(e,t){e.tool(`get_hubs`,Xr,{limit:P.number().int().positive().optional().describe(`Maximum number of hubs to return`),cwd:P.string().optional().describe(I)},async e=>{let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return L(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return L(z,!0);let a=await H(i,`/api/hubs${e.limit?`?limit=${encodeURIComponent(String(e.limit))}`:``}`);if(!a.ok)return L(`Error: ${a.error}`,!0);let{ok:o,...s}=a,c=s,{resolve:l,ui:u}=await G(t,r),d=(c.hubs??[]).map(e=>{let t=typeof e.docName==`string`?e.docName:null,n=t?l(t):null;return{...e,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}}),f={...c,hubs:d,ui:u,cwd:r};return R(JSON.stringify(f,null,2),f)})}const Qr=[`[Requires: Hocuspocus server] Find disconnected pages in the knowledge graph.`,`Returns orphaned pages as JSON.`,``,`**Parameters:**`,"- `mode` (optional) — Orphan lens: `incoming`, `outgoing`, or `both` (default `both`)"].join(`
199
+ `);function $r(e,t){e.tool(`get_orphans`,Qr,{mode:P.enum(i).optional().describe(`Filter which type of graph disconnection to surface`),cwd:P.string().optional().describe(I)},async e=>{let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return L(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return L(z,!0);let a=await H(i,`/api/orphans${e.mode?`?mode=${encodeURIComponent(e.mode)}`:``}`);if(!a.ok)return L(`Error: ${a.error}`,!0);let{ok:o,...s}=a,c=s,{resolve:l,ui:u}=await G(t,r),d=(c.orphans??[]).map(e=>{let t=typeof e.docName==`string`?e.docName:null,n=t?l(t):null;return{...e,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}}),f={...c,orphans:d,ui:u,cwd:r};return R(JSON.stringify(f,null,2),f)})}function ei(e,t){return`${$t(`ingest`)}Capture this external source into the project knowledge base as raw reference material. **Raw preservation only** — no summary, no analysis, no interpretation. Summarizing is the job of the \`research\` tool later.
197
200
 
198
201
  Source: ${e}
199
202
 
@@ -274,17 +277,17 @@ If the source is directly relevant to an existing article or research doc, updat
274
277
  - **No promotion to a canonical article** — that's the \`consolidate\` tool's job, later
275
278
  - **No silent chaining into research** — ingest completes on its own; the user explicitly opts into \`research\`
276
279
  - **No synthesis inside the raw file** — the takeaways live in chat or a separate summary doc, never mixed into the preserved source
277
- `}const Jr=[`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(`
278
- `);function Yr(e,t){e.tool(`ingest`,Jr,{source:N.string().describe(`URL, file path, or identifier of the source to ingest`),cwd:N.string().optional().describe(F)},async e=>{let n=await Xt(t.resolveCwd,t.config,e.cwd);return n.ok?L(qr(e.source,n.config.content.dir),{previewUrl:null}):I(`Error: ${n.error}`,!0)})}const Xr=[`[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(`
279
- `);function Zr(e,t){e.tool(`list_documents`,Xr,{dir:N.string().optional().describe(`Optional directory to filter documents`),cwd:N.string().optional().describe(F)},async e=>{let n=await z(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return I(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return I(R,!0);let a=await V(i,`/api/documents${e.dir?`?dir=${encodeURIComponent(e.dir)}`:``}`);if(!a.ok)return I(`Error: ${a.error}`,!0);let{ok:o,...s}=a,c=s,{resolve:l,ui:u}=await W(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 L(JSON.stringify(f,null,2),f)})}const Qr=[`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(`
280
- `);function $r(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(`
281
- `)}function ei(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(`
282
- `)}function ti(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(`
283
- `)}function ni(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(`
284
- `)}function ri(e){return e.replace(/^\.\//,``).replace(/^\/+/,``)}function ii(e){return e.replace(/\.(md|mdx)$/i,``)}async function ai(e,t){let n=await z(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)throw Error(n.error);let{cwd:r,config:i,url:a}=n,o=ri(e.path),s=M(r,o),c=i.mcp.tools.read_document.historyDepth,[l,u]=await Promise.all([Oe(s,`utf-8`),gr(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=$r(u.history);g&&h.push(g);let _=ei(u.projectHistory);_&&h.push(_);let v=ti(u.backlinks);v&&h.push(v);let y=ni(u.forwardLinks);return y&&h.push(y),h.push(``,`### Content`,``,l),h.join(`
285
- `)}function oi(e,t){e.tool(`read_document`,Qr,{path:N.string().describe(`Project-root-relative path to the file`),since:N.string().optional().describe(`Reserved; currently unused (§15 Future Work)`),cwd:N.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 ai(e,t),r=await U(ii(ri(e.path)),{config:t.config,resolveCwd:t.resolveCwd},await t.resolveCwd(e.cwd));return r?L(n,{previewUrl:r.url,previewUrlSource:r.source}):L(n,{previewUrl:null})}catch(e){return I(`Error: ${e instanceof Error?e.message:String(e)}`,!0)}})}const si=["[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(`
286
- `);function ci(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 li(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 ui(e,t,n=`${t}s`){return e===1?t:n}function di(e,t){e.tool(`rename_document`,si,{docName:N.string().describe(`Current document name`),newDocName:N.string().describe(`New document name`),summary:N.string().max(200).optional().describe(`Optional one-line user-outcome description (≤80 chars). Defaults to "Renamed X → Y" when omitted.`),cwd:N.string().optional().describe(F)},async e=>{let n=await z(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return I(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return I(R,!0);let a=B(e.docName);if(!a.ok)return I(a.error,!0);let o=B(e.newDocName);if(!o.ok)return I(o.error,!0);let s=t.identityRef?.current,c=await H(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 L(`Error: ${e}`,t,!0)}let l=ci(c.renamed),u=li(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} ${ui(u.length,`document`)}.`,p={config:t.config,resolveCwd:t.resolveCwd},m=await U(o.docName,p,r),h=await U(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(_),L(y.join(`
287
- `),v)})}function fi(e,t){return`${qt(`research`)}Conduct **evidence-driven research** on this topic and produce a provisional research article in the Open Knowledge content directory. This workflow mirrors the discipline of the \`eng:research\` skill, scoped to Open Knowledge's wiki-provisional layer.
280
+ `}const ti=[`Fetch an external source (URL or local file) and save raw content as reference material in the project content directory.`,`Raw preservation only — no analysis or interpretation.`,``,`**Use when:**`,`- Capturing reference material for the project knowledge base`,`- Saving a URL or document for later research`,`- Archiving an external source alongside the codebase`,`- The user shares a URL or document they want preserved`,``,`**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(`
281
+ `);function ni(e,t){e.tool(`ingest`,ti,{source:P.string().describe(`URL, file path, or identifier of the source to ingest`),cwd:P.string().optional().describe(I)},async e=>{let n=await nn(t.resolveCwd,t.config,e.cwd);return n.ok?R(ei(e.source,n.config.content.dir),{previewUrl:null}):L(`Error: ${n.error}`,!0)})}const ri=[`[Requires: Hocuspocus server] List available documents from the Hocuspocus server.`,`Returns document names, optionally filtered by directory.`,``,`**Parameters:**`,"- `dir` (optional) — Filter to documents in this directory"].join(`
282
+ `);function ii(e,t){e.tool(`list_documents`,ri,{dir:P.string().optional().describe(`Optional directory to filter documents`),cwd:P.string().optional().describe(I)},async e=>{let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return L(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return L(z,!0);let a=await H(i,`/api/documents${e.dir?`?dir=${encodeURIComponent(e.dir)}`:``}`);if(!a.ok)return L(`Error: ${a.error}`,!0);let{ok:o,...s}=a,c=s,{resolve:l,ui:u}=await G(t,r),d=(c.documents??[]).map(e=>{let t=typeof e.docName==`string`?e.docName:null,n=t?l(t):null;return{...e,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}}),f={...c,documents:d,ui:u,cwd:r};return R(JSON.stringify(f,null,2),f)})}const ai=[`Read a wiki file with enriched context: contents + frontmatter metadata + recent shadow-repo activity (agent vs human attribution) + backlink/forward-link context.`,``,`**Use when:**`,`- Loading an article for context`,`- Understanding who changed a file recently and whether it was an agent or human`,`- Seeing how this page links out and what links back to it`,``,"Prefer this over your native `Read` for wiki files — one call returns what otherwise takes 3-4.",``,`**Parameters:**`,"- `path` — Project-root-relative path to the file, including extension (e.g. `articles/auth/sso.md`). To pass this document to `edit_document` / `write_document` / `get_backlinks`, strip the extension (they take extension-less `docName`).","- `since` (reserved) — Reserved for shadow-log since-filter; currently unused."].join(`
283
+ `);function oi(e){if(!e||e.length===0)return``;let t=[``,`### Recent activity (OK edits)`,``];for(let n of e){let e=n.writerClassification===`agent`?`agent: ${n.writerName}`:n.writerClassification===`principal`?`human: ${n.writerName}`:`${n.writerClassification}: ${n.writerName}`,r=n.hash.slice(0,7);t.push(`- ${r} ${n.date} [${e}] ${n.message}`)}return t.join(`
284
+ `)}function si(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(`
285
+ `)}function ci(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(`
286
+ `)}function li(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(`
287
+ `)}function ui(e){return e.replace(/^\.\//,``).replace(/^\/+/,``)}function di(e){return e.replace(/\.(md|mdx)$/i,``)}async function fi(e,t){let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)throw Error(n.error);let{cwd:r,config:i,url:a}=n,o=ui(e.path),s=M(r,o),c=i.mcp.tools.read_document.historyDepth,[l,u]=await Promise.all([Ae(s,`utf-8`),Cr(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=oi(u.history);g&&h.push(g);let _=si(u.projectHistory);_&&h.push(_);let v=ci(u.backlinks);v&&h.push(v);let y=li(u.forwardLinks);return y&&h.push(y),h.push(``,`### Content`,``,l),h.join(`
288
+ `)}function pi(e,t){e.tool(`read_document`,ai,{path:P.string().describe(`Project-root-relative path to the file`),since:P.string().optional().describe(`Reserved; currently unused (§15 Future Work)`),cwd:P.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 fi(e,t),r=await W(di(ui(e.path)),{config:t.config,resolveCwd:t.resolveCwd},await t.resolveCwd(e.cwd));return r?R(n,{previewUrl:r.url,previewUrlSource:r.source}):R(n,{previewUrl:null})}catch(e){return L(`Error: ${e instanceof Error?e.message:String(e)}`,!0)}})}const mi=["[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(`
289
+ `);function hi(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 gi(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 _i(e,t,n=`${t}s`){return e===1?t:n}function vi(e,t){e.tool(`rename_document`,mi,{docName:P.string().describe(`Current document name`),newDocName:P.string().describe(`New document name`),summary:P.string().max(200).optional().describe(`Optional one-line user-outcome description (≤80 chars). Defaults to "Renamed X → Y" when omitted.`),cwd:P.string().optional().describe(I)},async e=>{let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return L(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return L(z,!0);let a=V(e.docName);if(!a.ok)return L(a.error,!0);let o=V(e.newDocName);if(!o.ok)return L(o.error,!0);let s=t.identityRef?.current,c=await U(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 R(`Error: ${e}`,t,!0)}let l=hi(c.renamed),u=gi(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} ${_i(u.length,`document`)}.`,p={config:t.config,resolveCwd:t.resolveCwd},m=await W(o.docName,p,r),h=await W(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(_),R(y.join(`
290
+ `),v)})}function yi(e,t){return`${$t(`research`)}Conduct **evidence-driven research** on this topic and produce a provisional research article in the Open Knowledge content directory. This workflow mirrors the discipline of the \`eng:research\` skill, scoped to Open Knowledge's wiki-provisional layer.
288
291
 
289
292
  Topic: ${e}
290
293
  Content directory: \`${t}\` (from \`${n}/config.yml\`)
@@ -634,16 +637,16 @@ In headless mode, write the recap into the research article's "Further reading"
634
637
  - **Don't skip the scoping gate in Supervised mode.** The user's rubric shapes everything downstream; you cannot recover a wrong-scope article cheaply.
635
638
  - **Don't mix 1P codebase analysis into the article unless asked.** Findings drift from factual synthesis to opinion when you do.
636
639
  - **Don't overwrite existing research silently.** If the topic was researched before, either iterate (Path C) or create a clearly-named successor (\`crdt-alternatives-2.md\`) and mark the old one as superseded.
637
- `}const pi=[`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(`
638
- `);function mi(e,t){e.tool(`research`,pi,{topic:N.string().describe(`The topic, question, or anchor URL to research`),cwd:N.string().optional().describe(F)},async e=>{let n=await Xt(t.resolveCwd,t.config,e.cwd);return n.ok?L(fi(e.topic,n.config.content.dir),{previewUrl:null}):I(`Error: ${n.error}`,!0)})}const hi=[`[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(`
639
- `);function gi(e,t){e.tool(`rollback_to_version`,hi,{docName:N.string().describe(`Document name to restore`),commitSha:N.string().length(40).regex(/^[0-9a-f]+$/i).describe(`40-character commit SHA from the shadow repo timeline`),summary:N.string().max(200).optional().describe(`Optional one-line user-outcome description (≤80 chars). Defaults to "Restored to <sha-short>" when omitted.`),cwd:N.string().optional().describe(F)},async e=>{let n=await z(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return I(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return I(R,!0);let a=B(e.docName);if(!a.ok)return I(a.error,!0);let o=a.docName,s=await V(i,`/api/history/${e.commitSha}?docName=${encodeURIComponent(o)}`);if(!s.ok)return I(`Error: ${s.error??`Version not found`}`,!0);let c=t.identityRef?.current,l=await H(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 I(`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 U(o,{config:t.config,resolveCwd:t.resolveCwd},r);return L(f.join(`
640
- `),{previewUrl:p?.url??null,...p?{previewUrlSource:p.source}:{},...u?{summary:u}:{}})})}const _i=[`[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(`
641
- `);function vi(e,t,n,r,i){e.tool(`save_version`,_i,{cwd:N.string().optional().describe(F)},async(e={})=>{let a=await z(r,t,n,e.cwd);if(!a.ok)return I(`Error: ${a.error}`,!0);if(!a.url)return I(R,!0);let{url:o}=a,s=i?.current,c=await H(o,`/api/save-version`,{...s?{writers:[{id:`agent-${s.connectionId}`,name:s.displayName,email:`agent-${s.connectionId}@openknowledge.local`}]}:{}});return c.ok?L(`Checkpoint saved. Checkpoint ref: ${c.checkpointRef}`,{checkpointRef:c.checkpointRef,previewUrl:null}):I(`Error: ${c.error}`,!0)})}const yi=[`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(`
642
- `);function bi(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 xi(e,t){let r=await z(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 Tn(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 W({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=bi(f),g=new Map,_=a.folders;await Promise.all(h.map(async e=>{try{let t=await gr(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=an(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(`
643
- `),structured:{query:e.query,matchCount:f.length,fileCount:h.length,truncated:d,results:y,ui:m,cwd:i}}}function Si(e,t){e.tool(`search`,yi,{query:N.string().describe(`Literal text to search for`),case_sensitive:N.boolean().optional().describe(`Case-sensitive search (default false)`),cwd:N.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 xi(e,t);return r?L(n,r):I(n)}catch(e){return I(`Error: ${e instanceof Error?e.message:String(e)}`,!0)}})}const Ci=[`[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(`
644
- `);function wi(e,t){e.tool(`suggest_links`,Ci,{docName:N.string().describe(`Target page docName`),cwd:N.string().optional().describe(F)},async e=>{let n=await z(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return I(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return I(R,!0);let a=B(e.docName);if(!a.ok)return I(a.error,!0);let o=await V(i,`/api/suggest-links?docName=${encodeURIComponent(a.docName)}`);if(!o.ok)return I(`Error: ${o.error}`,!0);let{ok:s,...c}=o,l=await U(a.docName,{config:t.config,resolveCwd:t.resolveCwd},r);return L(JSON.stringify(c,null,2),{...c,previewUrl:l?.url??null,...l?{previewUrlSource:l.source}:{}})})}const Ti=[`[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(`
645
- `);function Ei(e,t){e.tool(`write_document`,Ti,{docName:N.string().describe(`Document name to write to`),markdown:N.string().describe(`Markdown content to write`),position:N.enum([`append`,`prepend`,`replace`]).describe(`Where to insert the content`),summary:Ut,cwd:N.string().optional().describe(F)},async e=>{let n=await z(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return I(`Error: ${n.error}`,!0);let{cwd:r,config:i,url:s}=n;if(!s)return I(R,!0);let c=B(e.docName);if(!c.ok)return I(c.error,!0);let l=t.identityRef?.current,u=await H(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 I(`Error: ${u.error}`,!0);let d=a(o(i,r)),f=on(c.docName,{config:i,lockDir:d}),p=typeof u.subscriberCount==`number`?u.subscriberCount:void 0,m=(typeof u.systemSubscriberCount==`number`?u.systemSubscriberCount:void 0)===0,h=p===0,g=Array.isArray(u.hints)?u.hints:void 0,_=u.summary&&typeof u.summary==`object`?u.summary:void 0,v=typeof _?.hint==`string`?_.hint:void 0,y=[`Written successfully (${e.position}).`];if(f&&y.push(`Preview: ${f.url}`),m&&y.push(f?`Open ${f.url} in your preview browser.`:`No preview attached. Start the UI.`),v&&y.push(v),g)for(let e of g)e.message&&y.push(e.message);let b=y.join(`
646
- `);if(!f&&!m&&!h&&!g&&!_)return I(b);let x={};return f&&(x.previewUrl=f.url,x.previewUrlSource=f.source),m&&(x.warning={message:`Open the previewUrl in your preview browser.`,action:`attach-preview-once`,previewUrl:f?.url??null}),g&&(x.hints=g),_&&(x.summary=_),L(b,x)})}function Di(e,t){let n=t.logger,r=Ht(e,{logger:t.logger,identityRef:t.identityRef}),i=e=>async r=>{try{let i=await t.resolveCwd(r);return(kt()??n)?.debug(`tool cwd resolved`,{tool:e,cwd:i,...r?{explicit:r}:{}}),i}catch(t){throw(kt()??n)?.warn(`tool call failed`,{tool:e,error:t instanceof Error?t.message:String(t),...r?{explicit:r}:{}}),t}};Pr(r,{resolveCwd:i(`exec`),serverUrl:t.serverUrl,config:t.config}),Yr(r,{config:t.config,resolveCwd:i(`ingest`)}),mi(r,{config:t.config,resolveCwd:i(`research`)}),$t(r,{config:t.config,resolveCwd:i(`consolidate`)}),oi(r,{resolveCwd:i(`read_document`),config:t.config,serverUrl:t.serverUrl}),Si(r,{resolveCwd:i(`search`),config:t.config,serverUrl:t.serverUrl}),wi(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`suggest_links`)}),Ei(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`write_document`),identityRef:t.identityRef}),cn(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`edit_document`),identityRef:t.identityRef}),di(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`rename_document`),identityRef:t.identityRef}),Hr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_history`)}),vi(r,t.config,t.serverUrl,i(`save_version`),t.identityRef),gi(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`rollback_to_version`),identityRef:t.identityRef}),Zr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`list_documents`)}),Ir(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_backlinks`)}),Br(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_forward_links`)}),Kr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_orphans`)}),Wr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_hubs`)}),Rr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_dead_links`)})}function Oi(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 ki=class extends Error{};function Ai(e){let t=E(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 E(Ce(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 E(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:Oi(t)}),new ki(`Client roots unavailable; pass cwd explicitly.`)}if(r.length===0)throw new ki(`No client roots available; pass cwd explicitly.`);if(r.length>1)throw new ki(`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 ji(e){let t=E(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 Mi(e,t){let{dir:n,include:r,exclude:i}=e.content,a=i.length>0?i.map(e=>`\`${e}\``).join(`, `):`(none)`;return`# Open Knowledge (OK) — collaborative markdown via MCP
640
+ `}const bi=[`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(`
641
+ `);function xi(e,t){e.tool(`research`,bi,{topic:P.string().describe(`The topic, question, or anchor URL to research`),cwd:P.string().optional().describe(I)},async e=>{let n=await nn(t.resolveCwd,t.config,e.cwd);return n.ok?R(yi(e.topic,n.config.content.dir),{previewUrl:null}):L(`Error: ${n.error}`,!0)})}const Si=[`[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(`
642
+ `);function Ci(e,t){e.tool(`rollback_to_version`,Si,{docName:P.string().describe(`Document name to restore`),commitSha:P.string().length(40).regex(/^[0-9a-f]+$/i).describe(`40-character commit SHA from the shadow repo timeline`),summary:P.string().max(200).optional().describe(`Optional one-line user-outcome description (≤80 chars). Defaults to "Restored to <sha-short>" when omitted.`),cwd:P.string().optional().describe(I)},async e=>{let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return L(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return L(z,!0);let a=V(e.docName);if(!a.ok)return L(a.error,!0);let o=a.docName,s=await H(i,`/api/history/${e.commitSha}?docName=${encodeURIComponent(o)}`);if(!s.ok)return L(`Error: ${s.error??`Version not found`}`,!0);let c=t.identityRef?.current,l=await U(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 L(`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 W(o,{config:t.config,resolveCwd:t.resolveCwd},r);return R(f.join(`
643
+ `),{previewUrl:p?.url??null,...p?{previewUrlSource:p.source}:{},...u?{summary:u}:{}})})}const wi=[`[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(`
644
+ `);function Ti(e,t,n,r,i){e.tool(`save_version`,wi,{cwd:P.string().optional().describe(I)},async(e={})=>{let a=await B(r,t,n,e.cwd);if(!a.ok)return L(`Error: ${a.error}`,!0);if(!a.url)return L(z,!0);let{url:o}=a,s=i?.current,c=await U(o,`/api/save-version`,{...s?{writers:[{id:`agent-${s.connectionId}`,name:s.displayName,email:`agent-${s.connectionId}@openknowledge.local`}]}:{}});return c.ok?R(`Checkpoint saved. Checkpoint ref: ${c.checkpointRef}`,{checkpointRef:c.checkpointRef,previewUrl:null}):L(`Error: ${c.error}`,!0)})}const Ei=[`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(`
645
+ `);function Di(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 Oi(e,t){let r=await B(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 Mn(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 G({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=Di(f),g=new Map,_=a.folders;await Promise.all(h.map(async e=>{try{let t=await Cr(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=dn(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(`
646
+ `),structured:{query:e.query,matchCount:f.length,fileCount:h.length,truncated:d,results:y,ui:m,cwd:i}}}function ki(e,t){e.tool(`search`,Ei,{query:P.string().describe(`Literal text to search for`),case_sensitive:P.boolean().optional().describe(`Case-sensitive search (default false)`),cwd:P.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 Oi(e,t);return r?R(n,r):L(n)}catch(e){return L(`Error: ${e instanceof Error?e.message:String(e)}`,!0)}})}const Ai=[`[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(`
647
+ `);function ji(e,t){e.tool(`suggest_links`,Ai,{docName:P.string().describe(`Target page docName`),cwd:P.string().optional().describe(I)},async e=>{let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return L(`Error: ${n.error}`,!0);let{cwd:r,url:i}=n;if(!i)return L(z,!0);let a=V(e.docName);if(!a.ok)return L(a.error,!0);let o=await H(i,`/api/suggest-links?docName=${encodeURIComponent(a.docName)}`);if(!o.ok)return L(`Error: ${o.error}`,!0);let{ok:s,...c}=o,l=await W(a.docName,{config:t.config,resolveCwd:t.resolveCwd},r);return R(JSON.stringify(c,null,2),{...c,previewUrl:l?.url??null,...l?{previewUrlSource:l.source}:{}})})}const Mi=[`[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(`
648
+ `);function Ni(e,t){e.tool(`write_document`,Mi,{docName:P.string().describe(`Document name to write to`),markdown:P.string().describe(`Markdown content to write`),position:P.enum([`append`,`prepend`,`replace`]).describe(`Where to insert the content`),summary:Yt,cwd:P.string().optional().describe(I)},async e=>{let n=await B(t.resolveCwd,t.config,t.serverUrl,e.cwd);if(!n.ok)return L(`Error: ${n.error}`,!0);let{cwd:r,config:i,url:s}=n;if(!s)return L(z,!0);let c=V(e.docName);if(!c.ok)return L(c.error,!0);let l=t.identityRef?.current,u=await U(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 L(`Error: ${u.error}`,!0);let d=a(o(i,r)),f=fn(c.docName,{config:i,lockDir:d}),p=typeof u.subscriberCount==`number`?u.subscriberCount:void 0,m=(typeof u.systemSubscriberCount==`number`?u.systemSubscriberCount:void 0)===0,h=p===0,g=Array.isArray(u.hints)?u.hints:void 0,_=u.summary&&typeof u.summary==`object`?u.summary:void 0,v=typeof _?.hint==`string`?_.hint:void 0,y=[`Written successfully (${e.position}).`];if(f&&y.push(`Preview: ${f.url}`),m&&y.push(f?`Open ${f.url} in your preview browser.`:`No preview attached. Start the UI.`),v&&y.push(v),g)for(let e of g)e.message&&y.push(e.message);let b=y.join(`
649
+ `);if(!f&&!m&&!h&&!g&&!_)return L(b);let x={};return f&&(x.previewUrl=f.url,x.previewUrlSource=f.source),m&&(x.warning={message:`Open the previewUrl in your preview browser.`,action:`attach-preview-once`,previewUrl:f?.url??null}),g&&(x.hints=g),_&&(x.summary=_),R(b,x)})}function Pi(e,t){let n=t.logger,r=Jt(e,{logger:t.logger,identityRef:t.identityRef}),i=e=>async r=>{try{let i=await t.resolveCwd(r);return(Ft()??n)?.debug(`tool cwd resolved`,{tool:e,cwd:i,...r?{explicit:r}:{}}),i}catch(t){throw(Ft()??n)?.warn(`tool call failed`,{tool:e,error:t instanceof Error?t.message:String(t),...r?{explicit:r}:{}}),t}};Vr(r,{resolveCwd:i(`exec`),serverUrl:t.serverUrl,config:t.config}),ni(r,{config:t.config,resolveCwd:i(`ingest`)}),xi(r,{config:t.config,resolveCwd:i(`research`)}),on(r,{config:t.config,resolveCwd:i(`consolidate`)}),pi(r,{resolveCwd:i(`read_document`),config:t.config,serverUrl:t.serverUrl}),ki(r,{resolveCwd:i(`search`),config:t.config,serverUrl:t.serverUrl}),ji(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`suggest_links`)}),Ni(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`write_document`),identityRef:t.identityRef}),mn(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`edit_document`),identityRef:t.identityRef}),vi(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`rename_document`),identityRef:t.identityRef}),Yr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_history`)}),Ti(r,t.config,t.serverUrl,i(`save_version`),t.identityRef),Ci(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`rollback_to_version`),identityRef:t.identityRef}),ii(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`list_documents`)}),Ur(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_backlinks`)}),qr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_forward_links`)}),$r(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_orphans`)}),Zr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_hubs`)}),Gr(r,{serverUrl:t.serverUrl,config:t.config,resolveCwd:i(`get_dead_links`)})}function Fi(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 Ii=class extends Error{};function Li(e){let t=D(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 D(Te(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 D(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:Fi(t)}),new Ii(`Client roots unavailable; pass cwd explicitly.`)}if(r.length===0)throw new Ii(`No client roots available; pass cwd explicitly.`);if(r.length>1)throw new Ii(`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 Ri(e){let t=D(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 zi(e,t){let{dir:n,include:r,exclude:i}=e.content,a=i.length>0?i.map(e=>`\`${e}\``).join(`, `):`(none)`;return`# Open Knowledge (OK) — collaborative markdown via MCP
647
650
 
648
651
  **STOP — native tools on in-scope \`.md\` / \`.mdx\`.** Do NOT use host-native \`Read\`, \`Grep\`, \`Glob\`, \`Edit\`, \`Write\` on markdown inside the content dir. Reads: \`exec\` / \`read_document\` / \`search\`. Writes: \`write_document\` / \`edit_document\` ONLY.
649
652
 
@@ -664,8 +667,8 @@ Claude Code Desktop: \`preview_start("open-knowledge-ui")\`. Other hosts: open-U
664
667
  Detailed conventions (wiki-link authoring, frontmatter, anti-patterns) live in the installed \`open-knowledge\` Agent Skill. If missing, run \`npx @inkeep/open-knowledge init\`.
665
668
 
666
669
  **Escape hatch.** Native \`Read\`/\`Grep\`/\`Glob\` on \`.md\` is allowed ONLY when no OK MCP is registered, or immediately after an OK MCP call failed — then begin your sentence with \`Open Knowledge MCP unavailable:\`. Non-markdown: native tools always.
667
- `}async function Ni(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 Pi(n){let{projectDir:r,serverUrl:i,config:a,startupConfig:o,bypassProjectSelection:s=!1}=n;if(X=Dt(),X.info(`MCP server starting`,{startupCwd:r,bypassProjectSelection:s,serverUrlType:typeof i==`string`?`explicit`:`lazy`}),typeof i==`string`){let e=await Ni(i,X);X.info(`Hocuspocus detection complete`,{serverUrl:i,available:e})}else X.info(`server discovery is lazy per effective cwd`);let c=new Pe({name:t,version:e},{instructions:Mi(o,{dynamicConfig:typeof a==`function`&&!s})}),l=Ai({startupCwd:r,bypassProjectSelection:s,listRoots:()=>c.server.listRoots(),logger:X}),u=ji({startupCwd:r,resolveCwd:l.resolveCwd,bypassProjectSelection:s}),d=u.resolveCwdForTools;c.server.setNotificationHandler(Ie,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=we(),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})},Di(c,{serverUrl:f,resolveCwd:d,config:a,identityRef:h,logger:X});let g=new Fe;await c.connect(g),X.info(`MCP server running on stdio`);let{startKeepalive:_}=await import(`./keepalive-CfaIYv9f.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 Fi(e){if(e===void 0||e===``)return;let t=Number.parseInt(e,10);if(!(Number.isNaN(t)||t<=0))return t}function Ii(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 Li(e){let t=e.readLock??(()=>_(e.lockDir)),n=e.isAlive??g,r=e.sleep??(e=>new Promise(t=>setTimeout(t,e))),i=e.spawn??Ee,a=e.readErrorLog??(e=>j(e)?ae(e,`utf-8`).trim():``),o=e.openErrorLog??(e=>ie(e,`w`)),s=e.closeFd??(e=>ne(e)),c=e.timeoutMs??5e3,l=e.pollIntervalMs??100,u=Ii({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};j(e.lockDir)||re(e.lockDir,{recursive:!0});let d=ge(e.lockDir,`last-spawn-error.log`),f=o(d),p,m,h=k();e.logger?.info(`spawning server`,{command:h.command,cwd:e.contentDir,timeoutMs:c});try{try{p=i(h.command,[...h.prefixArgs,`start`],{detached:!0,stdio:[`ignore`,`ignore`,f],cwd:e.contentDir}),p.on(`error`,e=>{m=e instanceof Error?e.message:String(e)}),p.unref()}catch(e){m=e instanceof Error?e.message:String(e)}}finally{try{s(f)}catch{}}let v=Date.now()+c;for(;Date.now()<v;){if(m){let t=a(d);throw e.logger?.error(`spawn failed`,void 0,{error:m,stderr:t}),Error(`Error: spawn failed: ${m}${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(m){let t=a(d);throw e.logger?.error(`spawn failed (post-deadline)`,void 0,{error:m,stderr:t}),Error(`Error: spawn failed: ${m}${t?` stderr:\n${t}`:``}`)}let y=a(d),b=(c/1e3).toFixed(c%1e3==0?0:2),x=p?.pid,S=``;throw typeof x==`number`&&(S=n(x)?` child pid=${x} is still running — raise OK_MCP_SPAWN_TIMEOUT_MS if this is a slow boot.`:` child pid=${x} exited — check last-spawn-error.log.`),e.logger?.error(`spawn poll timeout`,void 0,{timeoutMs:c,childPid:x,childAlive:typeof x==`number`?n(x):void 0,stderr:y||void 0}),Error(`Error: server did not start within ${b}s.${S}${y?` stderr:\n${y}`:``}`)}function Ri(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??Li,n=e.cacheMs??1e3,r=new Map,i=new Map;return async s=>{let c=await E(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 zi(e){return new A(`mcp`).description(`Start MCP stdio server for project knowledge base`).option(`-p, --port <port>`,`Override port discovery and connect to this port (0 = disk-only)`,void 0).action(async t=>{try{let n=e(),r=process.cwd(),i=O({startupCwd:r,startupConfig:n}),a=Fi(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=Ri({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 Pi({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 Bi(e){return new A(`preview`).description(`Show what content the watcher will track (read-only)`).action(async()=>{let{previewContent:t,formatPreviewBlock:n}=await import(`./preview-DzHGOZjd.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 Vi(e,t,n=process.cwd()){let r=e.op??`sync`,i=_(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=De({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
668
- `)),Z(e.json,{type:`complete`,op:r}),e.json||process.stderr.write(`✓ ${r} complete\n`)}function Hi(e){return new A(`sync`).description(`Commit, pull, and push to the remote`).option(`--json`,`Output JSONL progress events`,!1).action(async t=>{try{await Vi({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 Ui(e){return new A(`pull`).description(`Pull changes from the remote`).option(`--json`,`Output JSONL progress events`,!1).action(async t=>{try{await Vi({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 Wi(e){return new A(`push`).description(`Push commits to the remote`).option(`--json`,`Output JSONL progress events`,!1).action(async t=>{try{await Vi({json:t.json,op:`push`},e())}catch(e){let n=e instanceof Error?e.message:String(e);t.json?process.stdout.write(`${JSON.stringify({type:`error`,message:n})}\n`):process.stderr.write(`✗ push failed: ${n}\n`),process.exit(1)}})}async function Gi(e={}){let t=M(e.cwd??process.cwd()),n;try{n=await m({projectDir:t})}catch(e){return e instanceof s?{status:`prerequisite-missing`,message:`${x(`Error:`)} ${e.message}`,exitCode:1}:{status:`failed`,message:`${x(`Error:`)} ${e instanceof Error?e.message:String(e)}`,exitCode:1}}if(n.created.length===0&&n.configEdits.length===0)return{status:`no-op`,message:`${b(`Your knowledge base is already seeded.`)}\n${y(`Nothing to do.`)}`,plan:n,exitCode:0};if(e.dryRun)return{status:`dry-run`,message:`${C(`Plan (dry-run — no changes made):`)}\n\n${Ki(n,t)}`,plan:n,exitCode:0};if(!e.yes&&!await qi(`${C(`Plan:`)}\n\n${Ki(n,t)}\n\n${C(`Apply?`)} ${y(`[Y/n] `)}`,e.confirmStream))return{status:`cancelled`,message:y(`Cancelled.`),plan:n,exitCode:0};let r=await p(n,{projectDir:t});if(r.errors.length>0){let e=r.errors.map(e=>` ${x(`✗`)} ${e.path}: ${e.error}`);return{status:`failed`,message:[`${S(`Applied`)} ${r.applied} entries, ${S(String(r.errors.length))} error(s):`,...e].join(`
669
- `),plan:n,exitCode:1}}return{status:`applied`,message:`${b(`✓ Seeded knowledge base`)} ${y(`(${r.applied} entries, ${r.durationMs}ms)`)}`,plan:n,exitCode:0}}function Ki(e,t){let n=[],r=e.created.filter(e=>e.kind===`folder`),i=e.created.filter(e=>e.kind===`file`);if(r.length>0){n.push(C(`Folders to create:`));for(let e of r)n.push(` ${b(`+`)} ${v(_e(t,M(t,e.path))||e.path)}${y(`/`)}`)}if(i.length>0){n.length>0&&n.push(``),n.push(C(`Files to create:`));for(let e of i)n.push(` ${b(`+`)} ${v(_e(t,M(t,e.path))||e.path)}`)}if(e.configEdits.length>0){n.length>0&&n.push(``),n.push(C(`config.yml folders: entries to add:`));for(let t of e.configEdits)n.push(` ${b(`+`)} ${v(t.folderMatch)} ${y(`—`)} ${t.entry.frontmatter.title??``}`)}if(e.skipped.length>0){n.length>0&&n.push(``),n.push(y(`Already present (skipped):`));for(let t of e.skipped)n.push(` ${y(`· ${t.path} (${t.reason})`)}`)}if(e.warnings.length>0){n.length>0&&n.push(``),n.push(S(`Warnings:`));for(let t of e.warnings)n.push(` ${S(`!`)} ${t}`)}return n.join(`
670
- `)}async function qi(e,t){let n=Ve({input:t??process.stdin,output:process.stdout});try{let t=(await n.question(e)).trim().toLowerCase();return t===``||t===`y`||t===`yes`}finally{n.close()}}function Ji(){return new A(`seed`).description(`Scaffold the Karpathy three-layer knowledge-base structure (external-sources/, research/, articles/) + log.md + config.yml folders: entries`).argument(`[path]`,`Project directory (defaults to cwd)`).option(`-y, --yes`,`Skip confirmation prompt`).option(`--dry-run`,`Print the plan and exit without writing`).action(async(e,t)=>{let n=await Gi({cwd:e??process.cwd(),yes:t.yes,dryRun:t.dryRun});process.stdout.write(`${n.message}\n`),n.exitCode!==0&&(process.exitCode=n.exitCode)})}function Yi(e,t){return{server:Xi(`server`,e),ui:Xi(`ui`,t)}}function Xi(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 Zi(e){return`${Qi(e.server)}\n${Qi(e.ui)}`}function Qi(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 $i(e){let t=e.inspect??(t=>ft(e.lockDir,t)),n=e.log??(e=>console.log(e)),r=Yi(t(`server`),t(`ui`));return e.json?n(JSON.stringify(r,null,2)):n(Zi(r)),r}function ea(e){return new A(`status`).description(`Show live state of the server + ui lockfiles for this project`).option(`--json`,`Emit structured JSON instead of formatted text`).action(t=>{$i({lockDir:a(o(e(),process.cwd())),json:t.json===!0})})}function ta(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 na(e){let t=e.inspect??(t=>ft(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=ta(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 ra(e){return new A(`stop`).description(`Stop the running open-knowledge server and UI (live only)`).action(()=>{na({lockDir:a(o(e(),process.cwd()))}).failed.length>0&&(process.exitCode=1)})}const ia=1e4,aa=[`connection`,`keep-alive`,`proxy-authenticate`,`proxy-authorization`,`te`,`trailer`,`transfer-encoding`,`upgrade`,`cookie`,`set-cookie`];async function oa(e){let t=e.upstreamTimeoutMs??ia,n=je((n,r)=>{ca(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 sa(e,t,n){ca(e,t,n.upstreamHost,n.upstreamPort,n.upstreamTimeoutMs??ia)}function ca(e,t,n,r,i){let a={...e.headers};delete a.host;for(let e of aa)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=Me({host:n,port:r,method:e.method,path:e.url,headers:{...a,host:`${n}:${r}`}},e=>{let n={...e.headers};for(let e of aa)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 la(e){await Promise.all(e.map(e=>new Promise(t=>{e.close(()=>t())})))}async function ua(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-zcKwR9RH.mjs`),{default:l}=await import(`sirv`),{resolveContentDir:u,resolveLockDir:d}=await import(`./paths-HWNY8fZl.mjs`),f=u(e.config,e.cwd),p=d(f);a(p,{port:0,worktreeRoot:e.cwd});let m=import.meta.dirname??new URL(`.`,import.meta.url).pathname,h=[i(m,`public`),i(m,`../../app/dist`),i(m,`../../../app/dist`)].find(e=>t(e)),g=h?l(h,{single:!0,gzip:!0,immutable:!0}):null,_=t(f)?l(f,{dotfiles:!1,dev:!0}):null,v=e.port,y=null,b=(e,t)=>{let n=e.url?.split(`?`)[0];if(n===`/api/config`&&(e.method===`GET`||e.method===`HEAD`)){y?.();let n=o(p),r=n&&n.port>0?`ws://localhost:${n.port}/collab`:null,i=JSON.stringify({collabUrl:r,previewUrl:null,port:v});t.setHeader(`Content-Type`,`application/json`),t.setHeader(`Cache-Control`,`no-store`),t.setHeader(`X-Content-Type-Options`,`nosniff`),t.statusCode=200,e.method===`HEAD`?t.end():t.end(i);return}if(n?.startsWith(`/api/`)){y?.();let r=o(p);if(!r||r.port<=0){t.writeHead(503,{"Content-Type":`application/json`,"Cache-Control":`no-store`}),t.end(JSON.stringify({error:"Collab server not running. Start `ok start` or run `ok status`.",path:n}));return}sa(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):da(t)});return}if(g){g(e,t);return}da(t)},x=e.host===void 0?[`::1`,`127.0.0.1`]:[e.host],S=[],C=e.port;try{for(let e of x){let t=n(b);S.push(t),await new Promise((n,r)=>{let i=e=>r(e);t.once(`error`,i),t.listen(C,e,()=>{t.off(`error`,i);let e=t.address();typeof e==`object`&&e&&(C=e.port),n()})})}}catch(e){await Promise.all(S.map(e=>new Promise(t=>{try{e.close(()=>t())}catch{t()}})));try{s(p)}catch{}throw e}let w=C;v=w,c(p,w);let T=e.scheduler??r,E=e.safetyNetMs??432e5,D=null,O=!1,ee=!1,k=()=>{O||(O=!0,D!==null&&(T.clearTimeout(D),D=null))},A=()=>{if(k(),!ee){ee=!0;try{s(p)}catch{}}},te=()=>{O||E<=0||(D!==null&&(T.clearTimeout(D),D=null),D=T.setTimeout(()=>{D=null,console.warn(`[ui] safety-net (${E}ms) reached — shutting down (D-025 backstop)`);try{e.onSafetyNet?.()}catch{}for(let e of S)try{e.close()}catch{}A()},E))},ne=()=>{O||E<=0||te()};return y=ne,te(),{httpServers:S,port:w,release:A,detachSafetyNet:k,nudgeSafetyNet:ne}}function da(e){e.writeHead(404),e.end(`Not found`)}function fa(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 pa(e){let t=e.readLock??(async()=>{let{readUiLock:t}=await import(`./src-zcKwR9RH.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 oa({listenPort:e.requestedPort,host:e.host,upstreamHost:`localhost`,upstreamPort:r}),upstreamPort:r}}function ma(e){return new A(`ui`).description(`Serve the Open Knowledge React editor UI`).option(`-p, --port <port>`,`UI port (default: $PORT env or 0 / kernel-allocated)`).option(`-H, --host <host>`,"UI host. Default: two-socket loopback bind (`[::1]` + `127.0.0.1`) so cross-family collisions fail loud (D-033). Pass an explicit host (e.g. `127.0.0.1`, `0.0.0.0`) to bind a single socket on that host.").action(async t=>{let{dim:n}=await import(`./colors-CxLEz90E.mjs`),{UiLockCollisionError:r}=await import(`./src-zcKwR9RH.mjs`),{resolveContentDir:i,resolveLockDir:a}=await import(`./paths-HWNY8fZl.mjs`),o=e(),s=t.host,c;try{c=fa(t.port,process.env.PORT)}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exitCode=1;return}try{let e=await ua({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)}};la(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 pa({requestedPort:c,host:l,lockDir:t})}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exit(1)}u.mode===`already-running`&&(console.log(`UI already running at http://${l}:${u.port}`),process.exit(0)),console.log(`UI running at http://${l}:${u.upstreamPort}; acting as HTTP proxy on port ${u.handle.port}`);let d=!1,f=e=>{d||(d=!0,console.log(n(`\n[ui-proxy] Shutting down (${e})...`)),u.handle.close().finally(()=>process.exit(process.exitCode??0)),setTimeout(()=>process.exit(process.exitCode??0),2e3).unref())};process.once(`SIGINT`,()=>f(`SIGINT`)),process.once(`SIGTERM`,()=>f(`SIGTERM`))}})}process.argv.includes(`--no-color`)?(process.env.NO_COLOR=`1`,delete process.env.FORCE_COLOR):process.argv.includes(`--color`)&&(process.env.FORCE_COLOR=`1`,delete process.env.NO_COLOR);const Q=new A;let $;Q.name(`open-knowledge`).description(`Local-first knowledge base with CRDT collaboration`).version(e).option(`--cwd <path>`,`Working directory`).option(`--log-level <level>`,`Log level`,`info`).option(`--no-color`,`Disable color output`).option(`--color`,`Force color output`).hook(`preAction`,e=>{let t=e.opts(),n=t.cwd;n!==void 0&&process.chdir(n);let{config:r}=D(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 ha=ee(()=>$);Q.addCommand(ha,{isDefault:!0});const ga=zi(()=>$);Q.addCommand(ga),Q.addCommand(T()),Q.addCommand(Ji());const _a=Bi(()=>$);Q.addCommand(_a);const va=ma(()=>$);Q.addCommand(va),Q.addCommand(ra(()=>$)),Q.addCommand(ht(()=>$)),Q.addCommand(ea(()=>$)),Q.addCommand(dt(()=>$)),Q.addCommand(wt(()=>$)),Q.addCommand(Hi(()=>$)),Q.addCommand(Wi(()=>$)),Q.addCommand(Ui(()=>$)),await Q.parseAsync();export{};
670
+ `}async function Bi(e,t){try{let t=e.replace(`ws://`,`http://`).replace(`wss://`,`https://`);return(await fetch(`${t}/api/document`,{signal:AbortSignal.timeout(2e3)})).ok}catch(n){return t.warn(`Hocuspocus probe failed`,{serverUrl:e,error:n instanceof Error?n.message:String(n)}),!1}}async function Vi(n){let{projectDir:r,serverUrl:i,config:a,startupConfig:o,bypassProjectSelection:s=!1}=n;if(X=Nt(),X.info(`MCP server starting`,{startupCwd:r,bypassProjectSelection:s,serverUrlType:typeof i==`string`?`explicit`:`lazy`}),typeof i==`string`){let e=await Bi(i,X);X.info(`Hocuspocus detection complete`,{serverUrl:i,available:e})}else X.info(`server discovery is lazy per effective cwd`);let c=new Ie({name:t,version:e},{instructions:zi(o,{dynamicConfig:typeof a==`function`&&!s})}),l=Li({startupCwd:r,bypassProjectSelection:s,listRoots:()=>c.server.listRoots(),logger:X}),u=Ri({startupCwd:r,resolveCwd:l.resolveCwd,bypassProjectSelection:s}),d=u.resolveCwdForTools;c.server.setNotificationHandler(Re,async()=>{l.invalidateRoots()});let f=async e=>{if(typeof i==`string`)return i.replace(`ws://`,`http://`).replace(`wss://`,`https://`);let t=e??await d();return(typeof i==`function`?await i(t):i)?.replace(`ws://`,`http://`).replace(`wss://`,`https://`)},p=Ee(),m=process.env.AGENT_LABEL||void 0,h={current:{connectionId:p,label:m,displayName:m??`Agent`,colorSeed:m??p}};c.server.oninitialized=()=>{let e=c.server.getClientVersion();h.current={connectionId:p,clientInfo:e?{name:e.name,version:e.version}:void 0,label:m,displayName:m??e?.name??`Agent`,colorSeed:m??e?.name??p},X?.info(`agent identity established`,{displayName:h.current.displayName,connectionId:p.slice(0,8),clientName:e?.name})},Pi(c,{serverUrl:f,resolveCwd:d,config:a,identityRef:h,logger:X});let g=new Le;await c.connect(g),X.info(`MCP server running on stdio`);let{startKeepalive:_}=await import(`./keepalive-CfaIYv9f.mjs`),v=_({resolveWsUrl:async()=>{let e=await u.getKeepaliveCwd();if(!e)return;let t=await f(e);if(t)return t.replace(/^http:/,`ws:`).replace(/^https:/,`wss:`)},connectionId:`agent-${p}`,logger:X.child(`keepalive`)}),y=e=>{X?.info(`MCP server shutting down`,{signal:e});try{v.close()}catch{}process.exit(0)};process.on(`SIGINT`,()=>y(`SIGINT`)),process.on(`SIGTERM`,()=>y(`SIGTERM`))}function Hi(e){if(e===void 0||e===``)return;let t=Number.parseInt(e,10);if(!(Number.isNaN(t)||t<=0))return t}function Ui(e){if(e.portOverride!==void 0){let t=Number.parseInt(e.portOverride,10);if(Number.isNaN(t))return{action:`disk-only`,message:`invalid --port value '${e.portOverride}' — disk-only mode`};if(t>0){let n=`ws://${e.host}:${t}`;return{action:`connect`,url:n,message:`using --port override, connecting to ${n}`}}return{action:`disk-only`,message:`--port=0 — disk-only mode`}}let t=e.readLock();if(t&&t.port>0&&e.isAlive(t.pid)){let e=`ws://localhost:${t.port}`;return{action:`connect`,url:e,message:`connected to running instance at ${e} (pid ${t.pid})`}}return e.envAutoStart===`0`?{action:`disk-only`,message:`auto-spawn disabled via OK_MCP_AUTOSTART=0 — disk-only mode`}:e.configAutoStart?t?{action:`spawn`,message:`existing lock is not usable (port=${t.port}, pid=${t.pid}) — spawning ok start`}:{action:`spawn`,message:`no running instance — spawning ok start`}:{action:`disk-only`,message:`auto-spawn disabled via config.mcp.autoStart=false — disk-only mode`}}async function Wi(e){let t=e.readLock??(()=>v(e.lockDir)),n=e.isAlive??_,r=e.sleep??(e=>new Promise(t=>setTimeout(t,e))),i=e.spawn??Oe,a=e.readErrorLog??(e=>j(e)?oe(e,`utf-8`).trim():``),o=e.openErrorLog??(e=>ae(e,`w`)),s=e.closeFd??(e=>re(e)),c=e.timeoutMs??5e3,l=e.pollIntervalMs??100,u=Ui({host:e.host,portOverride:e.portOverride,envAutoStart:e.envAutoStart,configAutoStart:e.configAutoStart,readLock:t,isAlive:n});if(e.logger?.info(`auto-start decision`,{action: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};j(e.lockDir)||ie(e.lockDir,{recursive:!0});let d=ve(e.lockDir,`last-spawn-error.log`),f=o(d),p,m,h=te();e.logger?.info(`spawning server`,{command:h.command,cwd:e.contentDir,timeoutMs:c});try{try{p=i(h.command,[...h.prefixArgs,`start`],{detached:!0,stdio:[`ignore`,`ignore`,f],cwd:e.contentDir}),p.on(`error`,e=>{m=e instanceof Error?e.message:String(e)}),p.unref()}catch(e){m=e instanceof Error?e.message:String(e)}}finally{try{s(f)}catch{}}let g=Date.now()+c;for(;Date.now()<g;){if(m){let t=a(d);throw e.logger?.error(`spawn failed`,void 0,{error:m,stderr:t}),Error(`Error: spawn failed: ${m}${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(m){let t=a(d);throw e.logger?.error(`spawn failed (post-deadline)`,void 0,{error:m,stderr:t}),Error(`Error: spawn failed: ${m}${t?` stderr:\n${t}`:``}`)}let y=a(d),b=(c/1e3).toFixed(c%1e3==0?0:2),x=p?.pid,S=``;throw typeof x==`number`&&(S=n(x)?` child pid=${x} is still running — raise OK_MCP_SPAWN_TIMEOUT_MS if this is a slow boot.`:` child pid=${x} exited — check last-spawn-error.log.`),e.logger?.error(`spawn poll timeout`,void 0,{timeoutMs:c,childPid:x,childAlive:typeof x==`number`?n(x):void 0,stderr:y||void 0}),Error(`Error: server did not start within ${b}s.${S}${y?` stderr:\n${y}`:``}`)}function Gi(e){if(e.portOverride!==void 0){let t=Number.parseInt(e.portOverride,10);if(Number.isNaN(t)||t<=0)return async()=>void 0;let n=`ws://${e.host}:${t}`;return async()=>n}let t=e.ensureServerRunningFn??Wi,n=e.cacheMs??1e3,r=new Map,i=new Map;return async s=>{let c=await D(s??e.startupCwd),l=Date.now(),u=r.get(c);if(u&&u.expiresAt>l)return e.logger?.debug(`server url cache hit`,{cwd:c,url:u.url}),u.url;let d=i.get(c);if(d)return e.logger?.debug(`server url resolution pending`,{cwd:c}),await d;e.logger?.debug(`server url cache miss`,{cwd:c});let f=(async()=>{let i=await e.resolveConfig(c),s=o(i,c),l=a(s),u=e.readLock,d=u?()=>u(l):void 0,f=await t({lockDir:l,contentDir:s,host:i.server.host,portOverride:void 0,envAutoStart:e.envAutoStart,configAutoStart:i.mcp.autoStart,logger:e.logger,timeoutMs:e.timeoutMs,pollIntervalMs:e.pollIntervalMs,spawn:e.spawn,readLock:d,isAlive:e.isAlive,sleep:e.sleep,readErrorLog:e.readErrorLog,openErrorLog:e.openErrorLog,closeFd:e.closeFd});return r.set(c,{url:f.serverUrl,expiresAt:Date.now()+n}),f.serverUrl})();i.set(c,f);try{return await f}finally{i.delete(c)}}}function Ki(e){return new A(`mcp`).description(`Start MCP stdio server for project knowledge base`).option(`-p, --port <port>`,`Override port discovery and connect to this port (0 = disk-only)`,void 0).action(async t=>{try{let n=e(),r=process.cwd(),i=ee({startupCwd:r,startupConfig:n}),a=Hi(process.env.OK_MCP_SPAWN_TIMEOUT_MS),o,s;if(t.port!==void 0){let e=Number.parseInt(t.port,10);Number.isNaN(e)?(o=void 0,s=`invalid --port value '${t.port}' — disk-only mode`):e>0?(o=`ws://${n.server.host}:${e}`,s=`using --port override, connecting to ${o}`):(o=void 0,s=`--port=0 — disk-only mode`)}else o=Gi({startupCwd:r,resolveConfig:i,host:n.server.host,portOverride:void 0,envAutoStart:process.env.OK_MCP_AUTOSTART,timeoutMs:a}),s=`project server discovery/autostart is lazy per effective cwd`;process.stderr.write(`[mcp] ${s}\n`),await Vi({projectDir:r,serverUrl:o,config:i,startupConfig:n,bypassProjectSelection:t.port!==void 0})}catch(e){process.stderr.write(`MCP server failed to start: ${e instanceof Error?e.message:String(e)}\n`),process.exitCode=1}})}function qi(e){return new A(`preview`).description(`Show what content the watcher will track (read-only)`).action(async()=>{let{previewContent:t,formatPreviewBlock:n}=await import(`./preview-Bwvil0xG.mjs`),r=e(),i=process.cwd(),a=o(r,i),s;try{s=t({projectDir:i,contentDir:a,include:r.content.include,exclude:r.content.exclude})}catch(e){console.error(`Content preview failed: ${e instanceof Error?e.message:String(e)}`),process.exitCode=1;return}process.stdout.write(`${n(s,i)}\n`),s.totalCount===0&&s.warnings.length>0&&(process.exitCode=1)})}function Z(e,t){e&&process.stdout.write(`${JSON.stringify(t)}\n`)}async function Ji(e,t,n=process.cwd()){let r=e.op??`sync`,i=v(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=N({baseDir:n});if(r===`sync`||r===`pull`){Z(e.json,{type:`step`,step:`pull`});let t=await s.pull();Z(e.json,{type:`pull`,summary:t.summary}),e.json||process.stderr.write(` pull: ${t.summary.changes} changes\n`)}(r===`sync`||r===`push`)&&(Z(e.json,{type:`step`,step:`push`}),await s.push(),Z(e.json,{type:`push`,ok:!0}),e.json||process.stderr.write(` push: ok
671
+ `)),Z(e.json,{type:`complete`,op:r}),e.json||process.stderr.write(`✓ ${r} complete\n`)}function Yi(e){return new A(`sync`).description(`Commit, pull, and push to the remote`).option(`--json`,`Output JSONL progress events`,!1).action(async t=>{try{await Ji({json:t.json,op:`sync`},e())}catch(e){let n=e instanceof Error?e.message:String(e);t.json?process.stdout.write(`${JSON.stringify({type:`error`,message:n})}\n`):process.stderr.write(`✗ sync failed: ${n}\n`),process.exit(1)}})}function Xi(e){return new A(`pull`).description(`Pull changes from the remote`).option(`--json`,`Output JSONL progress events`,!1).action(async t=>{try{await Ji({json:t.json,op:`pull`},e())}catch(e){let n=e instanceof Error?e.message:String(e);t.json?process.stdout.write(`${JSON.stringify({type:`error`,message:n})}\n`):process.stderr.write(`✗ pull failed: ${n}\n`),process.exit(1)}})}function Zi(e){return new A(`push`).description(`Push commits to the remote`).option(`--json`,`Output JSONL progress events`,!1).action(async t=>{try{await Ji({json:t.json,op:`push`},e())}catch(e){let n=e instanceof Error?e.message:String(e);t.json?process.stdout.write(`${JSON.stringify({type:`error`,message:n})}\n`):process.stderr.write(`✗ push failed: ${n}\n`),process.exit(1)}})}async function Qi(e={}){let t=M(e.cwd??process.cwd()),n;try{n=await u({projectDir:t})}catch(e){return e instanceof s?{status:`prerequisite-missing`,message:`${S(`Error:`)} ${e.message}`,exitCode:1}:{status:`failed`,message:`${S(`Error:`)} ${e instanceof Error?e.message:String(e)}`,exitCode:1}}if(n.created.length===0&&n.configEdits.length===0)return{status:`no-op`,message:`${x(`Your knowledge base is already seeded.`)}\n${b(`Nothing to do.`)}`,plan:n,exitCode:0};if(e.dryRun)return{status:`dry-run`,message:`${w(`Plan (dry-run — no changes made):`)}\n\n${$i(n,t)}`,plan:n,exitCode:0};if(!e.yes&&!await ea(`${w(`Plan:`)}\n\n${$i(n,t)}\n\n${w(`Apply?`)} ${b(`[Y/n] `)}`,e.confirmStream))return{status:`cancelled`,message:b(`Cancelled.`),plan:n,exitCode:0};let r=await c(n,{projectDir:t});if(r.errors.length>0){let e=r.errors.map(e=>` ${S(`✗`)} ${e.path}: ${e.error}`);return{status:`failed`,message:[`${C(`Applied`)} ${r.applied} entries, ${C(String(r.errors.length))} error(s):`,...e].join(`
672
+ `),plan:n,exitCode:1}}return{status:`applied`,message:`${x(`✓ Seeded knowledge base`)} ${b(`(${r.applied} entries, ${r.durationMs}ms)`)}`,plan:n,exitCode:0}}function $i(e,t){let n=[],r=e.created.filter(e=>e.kind===`folder`),i=e.created.filter(e=>e.kind===`file`);if(r.length>0){n.push(w(`Folders to create:`));for(let e of r)n.push(` ${x(`+`)} ${y(ye(t,M(t,e.path))||e.path)}${b(`/`)}`)}if(i.length>0){n.length>0&&n.push(``),n.push(w(`Files to create:`));for(let e of i)n.push(` ${x(`+`)} ${y(ye(t,M(t,e.path))||e.path)}`)}if(e.configEdits.length>0){n.length>0&&n.push(``),n.push(w(`config.yml folders: entries to add:`));for(let t of e.configEdits)n.push(` ${x(`+`)} ${y(t.folderMatch)} ${b(`—`)} ${t.entry.frontmatter.title??``}`)}if(e.skipped.length>0){n.length>0&&n.push(``),n.push(b(`Already present (skipped):`));for(let t of e.skipped)n.push(` ${b(`· ${t.path} (${t.reason})`)}`)}if(e.warnings.length>0){n.length>0&&n.push(``),n.push(C(`Warnings:`));for(let t of e.warnings)n.push(` ${C(`!`)} ${t}`)}return n.join(`
673
+ `)}async function ea(e,t){let n=Ue({input:t??process.stdin,output:process.stdout});try{let t=(await n.question(e)).trim().toLowerCase();return t===``||t===`y`||t===`yes`}finally{n.close()}}function ta(){return new A(`seed`).description(`Scaffold the Karpathy three-layer knowledge-base structure (external-sources/, research/, articles/) + log.md + config.yml folders: entries`).argument(`[path]`,`Project directory (defaults to cwd)`).option(`-y, --yes`,`Skip confirmation prompt`).option(`--dry-run`,`Print the plan and exit without writing`).action(async(e,t)=>{let n=await Qi({cwd:e??process.cwd(),yes:t.yes,dryRun:t.dryRun});process.stdout.write(`${n.message}\n`),n.exitCode!==0&&(process.exitCode=n.exitCode)})}function na(e,t){return{server:ra(`server`,e),ui:ra(`ui`,t)}}function ra(e,t){switch(t.status){case`missing`:return{name:e,state:`missing`,alive:!1};case`corrupt`:return{name:e,state:`corrupt`,alive:!1};case`foreign-host`:return{name:e,state:`foreign-host`,pid:t.lock.pid,port:t.lock.port,startedAt:t.lock.startedAt,host:t.lock.hostname,alive:`unknown`};case`dead-pid`:return{name:e,state:`dead-pid`,pid:t.lock.pid,port:t.lock.port,startedAt:t.lock.startedAt,host:t.lock.hostname,alive:!1};case`alive`:return{name:e,state:`alive`,pid:t.lock.pid,port:t.lock.port,startedAt:t.lock.startedAt,host:t.lock.hostname,alive:!0}}}function ia(e){return`${aa(e.server)}\n${aa(e.ui)}`}function aa(e){let t=e.name===`server`?`server`:`ui `;return e.state===`missing`?`${t} not running`:e.state===`corrupt`?`${t} lock file corrupt — run \`ok clean\``:e.state===`foreign-host`?`${t} foreign host (${e.host}) pid=${e.pid} port=${e.port}`:e.state===`dead-pid`?`${t} stale (dead pid=${e.pid}) — run \`ok clean\``:`${t} alive pid=${e.pid} port=${e.port} started=${e.startedAt}`}function oa(e){let t=e.inspect??(t=>mt(e.lockDir,t)),n=e.log??(e=>console.log(e)),r=na(t(`server`),t(`ui`));return e.json?n(JSON.stringify(r,null,2)):n(ia(r)),r}function sa(e){return new A(`status`).description(`Show live state of the server + ui lockfiles for this project`).option(`--json`,`Emit structured JSON instead of formatted text`).action(t=>{oa({lockDir:a(o(e(),process.cwd())),json:t.json===!0})})}function ca(e,t){let n=[];return e.status===`alive`&&n.push({name:`server`,pid:e.lock.pid,port:e.lock.port}),t.status===`alive`&&n.push({name:`ui`,pid:t.lock.pid,port:t.lock.port}),{targets:n}}function la(e){let t=e.inspect??(t=>mt(e.lockDir,t)),n=e.kill??((e,t)=>process.kill(e,t)),r=e.log??(e=>console.log(e)),i=e.error??(e=>console.error(e)),a=ca(t(`server`),t(`ui`));if(a.targets.length===0)return r(`No running open-knowledge processes.`),{stopped:[],failed:[],hadTargets:!1};let o=[],s=[];for(let e of a.targets)try{n(e.pid,`SIGTERM`),o.push(e)}catch(t){s.push({target:e,error:t instanceof Error?t.message:String(t)})}return o.length>0&&r(`Stopped: ${o.map(e=>`${e.name} (pid=${e.pid}, port=${e.port})`).join(`, `)}`),s.length>0&&i(`Failed to stop: ${s.map(({target:e,error:t})=>`${e.name} (pid=${e.pid}): ${t}`).join(`; `)}`),{stopped:o,failed:s,hadTargets:!0}}function ua(e){return new A(`stop`).description(`Stop the running open-knowledge server and UI (live only)`).action(()=>{la({lockDir:a(o(e(),process.cwd()))}).failed.length>0&&(process.exitCode=1)})}const da=1e4,fa=[`connection`,`keep-alive`,`proxy-authenticate`,`proxy-authorization`,`te`,`trailer`,`transfer-encoding`,`upgrade`,`cookie`,`set-cookie`];async function pa(e){let t=e.upstreamTimeoutMs??da,n=Ne((n,r)=>{ha(n,r,e.upstreamHost,e.upstreamPort,t)});await new Promise((t,r)=>{let i=e=>r(e);n.once(`error`,i),n.listen(e.listenPort,e.host,()=>{n.off(`error`,i),t()})});let r=n.address();return{httpServer:n,port:typeof r==`object`&&r?r.port:e.listenPort,close:()=>new Promise(e=>{n.close(()=>e())})}}function ma(e,t,n){ha(e,t,n.upstreamHost,n.upstreamPort,n.upstreamTimeoutMs??da)}function ha(e,t,n,r,i){let a={...e.headers};delete a.host;for(let e of fa)delete a[e];e.setTimeout(3e4,()=>{if(t.headersSent)try{t.end()}catch{}else try{t.writeHead(408,{"Content-Type":`text/plain`}),t.end(`Request Timeout`)}catch{}try{e.socket?.destroy()}catch{}});let o=Pe({host:n,port:r,method:e.method,path:e.url,headers:{...a,host:`${n}:${r}`}},e=>{let n={...e.headers};for(let e of fa)delete n[e];t.writeHead(e.statusCode??502,n),e.pipe(t),e.once(`error`,()=>{try{t.end()}catch{}})});i>0&&o.setTimeout(i,()=>{if(!t.headersSent)t.writeHead(504,{"Content-Type":`text/plain`}),t.end(`Gateway Timeout`);else try{t.end()}catch{}o.destroy()}),o.on(`error`,()=>{if(!t.headersSent)t.writeHead(502,{"Content-Type":`text/plain`}),t.end(`Bad Gateway`);else try{t.end()}catch{}}),e.on(`error`,()=>{o.destroy()}),e.pipe(o)}async function ga(e){await Promise.all(e.map(e=>new Promise(t=>{e.close(()=>t())})))}async function _a(e){let{existsSync:t}=await import(`node:fs`),{createServer:n}=await import(`node:http`),{resolve:i}=await import(`node:path`),{acquireUiLock:a,readServerLock:o,releaseUiLock:s,updateUiLockPort:c}=await import(`./src-CO-0JWV1.mjs`),{default:l}=await import(`sirv`),{resolveContentDir:u,resolveLockDir:d}=await import(`./paths-Bmv6OTgc.mjs`),f=u(e.config,e.cwd),p=d(f);a(p,{port:0,worktreeRoot:e.cwd});let m=import.meta.dirname??new URL(`.`,import.meta.url).pathname,h=[i(m,`public`),i(m,`../../app/dist`),i(m,`../../../app/dist`)].find(e=>t(e)),g=h?l(h,{single:!0,gzip:!0,immutable:!0}):null,_=t(f)?l(f,{dotfiles:!1,dev:!0}):null,v=e.port,y=null,b=(e,t)=>{let n=e.url?.split(`?`)[0];if(n===`/api/config`&&(e.method===`GET`||e.method===`HEAD`)){y?.();let n=o(p),r=n&&n.port>0?`ws://localhost:${n.port}/collab`:null,i=JSON.stringify({collabUrl:r,previewUrl:null,port:v});t.setHeader(`Content-Type`,`application/json`),t.setHeader(`Cache-Control`,`no-store`),t.setHeader(`X-Content-Type-Options`,`nosniff`),t.statusCode=200,e.method===`HEAD`?t.end():t.end(i);return}if(n?.startsWith(`/api/`)){y?.();let r=o(p);if(!r||r.port<=0){t.writeHead(503,{"Content-Type":`application/json`,"Cache-Control":`no-store`}),t.end(JSON.stringify({error:"Collab server not running. Start `ok start` or run `ok status`.",path:n}));return}ma(e,t,{upstreamHost:`localhost`,upstreamPort:r.port});return}if(decodeURIComponent(n?.replace(/^\//,``)??``)&&_){t.setHeader(`X-Content-Type-Options`,`nosniff`),_(e,t,()=>{g?g(e,t):va(t)});return}if(g){g(e,t);return}va(t)},x=e.host===void 0?[`::1`,`127.0.0.1`]:[e.host],S=[],C=e.port;try{for(let e of x){let t=n(b);S.push(t),await new Promise((n,r)=>{let i=e=>r(e);t.once(`error`,i),t.listen(C,e,()=>{t.off(`error`,i);let e=t.address();typeof e==`object`&&e&&(C=e.port),n()})})}}catch(e){await Promise.all(S.map(e=>new Promise(t=>{try{e.close(()=>t())}catch{t()}})));try{s(p)}catch{}throw e}let w=C;v=w,c(p,w);let T=e.scheduler??r,E=e.safetyNetMs??432e5,D=null,O=!1,ee=!1,k=()=>{O||(O=!0,D!==null&&(T.clearTimeout(D),D=null))},te=()=>{if(k(),!ee){ee=!0;try{s(p)}catch{}}},A=()=>{O||E<=0||(D!==null&&(T.clearTimeout(D),D=null),D=T.setTimeout(()=>{D=null,console.warn(`[ui] safety-net (${E}ms) reached — shutting down (D-025 backstop)`);try{e.onSafetyNet?.()}catch{}for(let e of S)try{e.close()}catch{}te()},E))},ne=()=>{O||E<=0||A()};return y=ne,A(),{httpServers:S,port:w,release:te,detachSafetyNet:k,nudgeSafetyNet:ne}}function va(e){e.writeHead(404),e.end(`Not found`)}function ya(e,t){if(e!==void 0){let t=Number.parseInt(e,10);if(Number.isNaN(t)||t<0||t>65535)throw Error(`Invalid --port value '${e}'`);return t}if(t!==void 0&&t!==``){let e=Number.parseInt(t,10);if(Number.isNaN(e)||e<0||e>65535)throw Error(`Invalid PORT env value '${t}'`);return e}return 0}async function ba(e){let t=e.readLock??(async()=>{let{readUiLock:t}=await import(`./src-CO-0JWV1.mjs`);return t(e.lockDir)}),n=await t();if(!n)throw Error(`UI lock collision reported but the lock disappeared before handling — retry acquiring.`);if(n.port===e.requestedPort&&n.port>0)return{mode:`already-running`,port:n.port};let r=n.port;if(r===0){let n=Date.now()+(e.pollDeadlineMs??2e3),i=e.pollIntervalMs??100;for(;Date.now()<n;){await new Promise(e=>{setTimeout(e,i)});let e=await t();if(e&&e.port>0){r=e.port;break}}if(r===0)throw Error("UI did not bind within 2s; run `ok clean`");if(r===e.requestedPort)return{mode:`already-running`,port:r}}return{mode:`proxy`,handle:await pa({listenPort:e.requestedPort,host:e.host,upstreamHost:`localhost`,upstreamPort:r}),upstreamPort:r}}function xa(e){return new A(`ui`).description(`Serve the Open Knowledge React editor UI`).option(`-p, --port <port>`,`UI port (default: $PORT env or 0 / kernel-allocated)`).option(`-H, --host <host>`,"UI host. Default: two-socket loopback bind (`[::1]` + `127.0.0.1`) so cross-family collisions fail loud (D-033). Pass an explicit host (e.g. `127.0.0.1`, `0.0.0.0`) to bind a single socket on that host.").action(async t=>{let{dim:n}=await import(`./colors-CxLEz90E.mjs`),{UiLockCollisionError:r}=await import(`./src-CO-0JWV1.mjs`),{resolveContentDir:i,resolveLockDir:a}=await import(`./paths-Bmv6OTgc.mjs`),o=e(),s=t.host,c;try{c=ya(t.port,process.env.PORT)}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exitCode=1;return}try{let e=await _a({config:o,cwd:process.cwd(),port:c,host:s}),t=s===void 0||s===`::`||s===`0.0.0.0`?`localhost`:s;console.log(`${n(`[ui]`)} listening on http://${t}:${e.port}`);let r=!1,i=t=>{if(r)return;r=!0,console.log(n(`\n[ui] Shutting down (${t})...`)),e.detachSafetyNet();let i=()=>{try{e.release()}finally{process.exit(process.exitCode??0)}};ga(e.httpServers).then(i,i),setTimeout(i,2e3).unref()};process.once(`SIGINT`,()=>i(`SIGINT`)),process.once(`SIGTERM`,()=>i(`SIGTERM`));return}catch(e){if(!(e instanceof r))throw e;let t=a(i(o,process.cwd())),l=s??`localhost`,u;try{u=await ba({requestedPort:c,host:l,lockDir:t})}catch(e){console.error(e instanceof Error?e.message:String(e)),process.exit(1)}u.mode===`already-running`&&(console.log(`UI already running at http://${l}:${u.port}`),process.exit(0)),console.log(`UI running at http://${l}:${u.upstreamPort}; acting as HTTP proxy on port ${u.handle.port}`);let d=!1,f=e=>{d||(d=!0,console.log(n(`\n[ui-proxy] Shutting down (${e})...`)),u.handle.close().finally(()=>process.exit(process.exitCode??0)),setTimeout(()=>process.exit(process.exitCode??0),2e3).unref())};process.once(`SIGINT`,()=>f(`SIGINT`)),process.once(`SIGTERM`,()=>f(`SIGTERM`))}})}process.argv.includes(`--no-color`)?(process.env.NO_COLOR=`1`,delete process.env.FORCE_COLOR):process.argv.includes(`--color`)&&(process.env.FORCE_COLOR=`1`,delete process.env.NO_COLOR);const Q=new A;let $;Q.name(`open-knowledge`).description(`Local-first knowledge base with CRDT collaboration`).version(e).option(`--cwd <path>`,`Working directory`).option(`--log-level <level>`,`Log level`,`info`).option(`--no-color`,`Disable color output`).option(`--color`,`Force color output`).hook(`preAction`,e=>{let t=e.opts(),n=t.cwd;n!==void 0&&process.chdir(n);let{config:r}=O(n),i=e.args.length===0?t:e.commands[0]?.opts()??{};i.port!==void 0&&(r.server.port=Number(i.port)),i.host!==void 0&&(r.server.host=i.host),process.env.PORT&&(r.server.port=Number(process.env.PORT)),process.env.HOST&&(r.server.host=process.env.HOST),$=r});const Sa=k(()=>$);Q.addCommand(Sa,{isDefault:!0});const Ca=Ki(()=>$);Q.addCommand(Ca),Q.addCommand(E()),Q.addCommand(ta()),Q.addCommand(At());const wa=qi(()=>$);Q.addCommand(wa);const Ta=xa(()=>$);Q.addCommand(Ta),Q.addCommand(ua(()=>$)),Q.addCommand(_t(()=>$)),Q.addCommand(sa(()=>$)),Q.addCommand(pt(()=>$)),Q.addCommand(Et(()=>$)),Q.addCommand(Yi(()=>$)),Q.addCommand(Zi(()=>$)),Q.addCommand(Xi(()=>$)),await Q.parseAsync();export{};
671
674
  //# sourceMappingURL=cli.mjs.map