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