@inkeep/open-knowledge 0.0.0-dev-20260422183611 → 0.0.0-dev-20260422215402
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +14 -14
- package/dist/constants-Z_elUeaU.mjs +2 -0
- package/dist/index.mjs +1 -1
- package/dist/init-B18k0eWn.mjs +1 -0
- package/dist/{init-CbtQtRR6.mjs → init-Bell0i8d.mjs} +3 -3
- package/dist/init-CY-1sO72.mjs +1 -0
- package/dist/{init-BVkwhSV7.mjs → init-Dp5IRYFc.mjs} +2 -2
- package/dist/keepalive-BtwYrChs.mjs +2 -0
- package/dist/{loader-EY80f87l.mjs → loader-DFXyUwo3.mjs} +2 -2
- package/dist/loader-DqlTfvZz.mjs +1 -0
- package/dist/paths-CK0cj4J4.mjs +2 -0
- package/dist/paths-uks91rcD.mjs +1 -0
- package/dist/preview-0B7pnKaj.mjs +1 -0
- package/dist/{preview-CYIJfQoO.mjs → preview-D2Qyb0bk.mjs} +2 -2
- package/dist/public/assets/{GraphPanel-DqKpB1Tz.js → GraphPanel-DW_Dc6tS.js} +2 -2
- package/dist/public/assets/SourceEditor-BKyWQ7mW.js +3 -0
- package/dist/public/assets/clipboard-CdKkyvy1.js +80 -0
- package/dist/public/assets/{dist-CtX4SukE.js → dist-B2YW_ZWG.js} +1 -1
- package/dist/public/assets/{dist-CYCPSCCQ.js → dist-Bx72hMUu.js} +1 -1
- package/dist/public/assets/{dist-ztaVC3x4.js → dist-CQbn9Fxc.js} +1 -1
- package/dist/public/assets/{dist-BZpbe8U5.js → dist-DVEMi7bH.js} +1 -1
- package/dist/public/assets/{dist-C0ssNH5f.js → dist-Dm75HY2n.js} +1 -1
- package/dist/public/assets/{dist-BXy0xauE.js → dist-X189qk_S.js} +1 -1
- package/dist/public/assets/{dist-C5cYj6hk.js → dist-k7VFO_3K.js} +1 -1
- package/dist/public/assets/index-CK9ftSXy.js +21 -0
- package/dist/public/assets/index-Cb6FKPFW.css +1 -0
- package/dist/public/assets/{panel-DFRQJTBG.js → panel-DTlG8EiB.js} +31 -31
- package/dist/public/assets/{toggle-group-D4a_dmpa.js → toggle-group-kvG9skOM.js} +1 -1
- package/dist/public/index.html +8 -8
- package/dist/src-6Cuk2tbm.mjs +1 -0
- package/dist/src-BmCYyMng.mjs +91 -0
- package/dist/{src-CdcLi3q2.mjs → src-CQw-HNNM.mjs} +2 -2
- package/dist/src-sdceGyDy.mjs +1 -0
- package/dist/{start-CmfUuJ8y.mjs → start-BEl3Vts5.mjs} +2 -2
- package/dist/start-CmW0OJ8S.mjs +1 -0
- package/package.json +1 -1
- package/dist/constants-Cqh2k1vs.mjs +0 -2
- package/dist/init-CjD3Y8vy.mjs +0 -1
- package/dist/init-DCdj_qYX.mjs +0 -1
- package/dist/keepalive-BI3oBFIq.mjs +0 -2
- package/dist/loader-D4WqC1BR.mjs +0 -1
- package/dist/paths-ClIaGARi.mjs +0 -2
- package/dist/paths-De38nMxk.mjs +0 -1
- package/dist/preview-DCtsNigW.mjs +0 -1
- package/dist/public/assets/SourceEditor-CV7AqTwb.js +0 -3
- package/dist/public/assets/clipboard-D_fdmuyh.js +0 -80
- package/dist/public/assets/index-DGQHaqKr.css +0 -1
- package/dist/public/assets/index-l0fisju1.js +0 -21
- package/dist/src-B-CjXHOr.mjs +0 -1
- package/dist/src-B2ovo79d.mjs +0 -1
- package/dist/src-CHPbwD29.mjs +0 -90
- package/dist/start-CwtyENWS.mjs +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as e,i as t}from"./constants-
|
|
2
|
+
import{a as e,i as t}from"./constants-Z_elUeaU.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-CK0cj4J4.mjs";import{ct as s,lt as c,ot as l,p as u,st as d,tt as f}from"./src-BmCYyMng.mjs";import{c as p,f as m,r as h}from"./server-lock-B4frNnOB.mjs";import{i as g}from"./init-Dp5IRYFc.mjs";import{t as _}from"./is-object-B4GcZahG.mjs";import{r as v}from"./init-Bell0i8d.mjs";import{i as y,n as b,t as x}from"./loader-DFXyUwo3.mjs";import{o as S,s as C}from"./start-BEl3Vts5.mjs";import"./src-sdceGyDy.mjs";import{Command as w}from"commander";import{appendFileSync as T,closeSync as E,existsSync as D,mkdirSync as O,openSync as ee,readFileSync as k,readdirSync as te,realpathSync as ne,statSync as re,unlinkSync as ie,writeFileSync as ae}from"node:fs";import{homedir as oe,hostname as se}from"node:os";import{basename as ce,dirname as le,isAbsolute as ue,join as de,relative as fe,resolve as A}from"node:path";import{parse as pe,stringify as me}from"yaml";import{createOAuthDeviceAuth as he}from"@octokit/auth-oauth-device";import ge from"@inquirer/password";import{Octokit as _e}from"@octokit/rest";import{fileURLToPath as ve}from"node:url";import{randomUUID as ye}from"node:crypto";import{execFileSync as be,spawn as xe}from"node:child_process";import j from"simple-git";import{readFile as Se,readdir as Ce,stat as we}from"node:fs/promises";import{createServer as Te,request as Ee}from"node:http";import De from"picomatch";import{z as M}from"zod";import{McpServer as Oe}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as ke}from"@modelcontextprotocol/sdk/server/stdio.js";import{RootsListChangedNotificationSchema as Ae}from"@modelcontextprotocol/sdk/types.js";import{Bash as je,ReadWriteFs as Me}from"just-bash";import Ne from"shell-quote";const Pe=`open-knowledge`;var Fe=class{backend=`keyring`;async get(e){let{Entry:t}=await import(`@napi-rs/keyring`);try{let n=new t(Pe,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(Pe,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(Pe,e).deletePassword()}catch{}}},Ie=class{backend=`file`;authFile;constructor(e){this.authFile=e??de(oe(),`.open-knowledge`,`auth.yml`)}read(){if(!D(this.authFile))return{};try{return pe(k(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=le(this.authFile);D(t)||O(t,{recursive:!0,mode:448}),ae(this.authFile,me(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 Le(e){try{let{Entry:e}=await import(`@napi-rs/keyring`);return new e(Pe,`__probe__`),process.stderr.write(`[auth] token storage: OS keychain
|
|
3
3
|
`),new Fe}catch{return process.stderr.write(`[auth] token storage: file (~/.open-knowledge/auth.yml)
|
|
4
4
|
`),new Ie(e)}}async function Re(e,t,n){let r=ze(await Be(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 ze(e){let t={};for(let n of e.split(`
|
|
5
5
|
`)){let e=n.trim();if(e===``)continue;let r=e.indexOf(`=`);r!==-1&&(t[e.slice(0,r)]=e.slice(r+1))}return t}function Be(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 Ve(e){let t=new w(`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 Re(process.stdin,process.stdout,t);process.exit(n)}),t}async function He(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=he({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 Ue(e){return process.env.OPEN_KNOWLEDGE_GITHUB_CLIENT_ID??e?.github?.oauthAppClientId??`Ov23liqlSd0V1MwR6rhI`}const We=new Set([`gitlab.com`,`bitbucket.org`,`codeberg.org`,`gitea.com`,`sr.ht`,`sourcehut.org`]);function Ge(e){let t=e.toLowerCase().replace(/:\d+$/,``);We.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 Ke(e,t){e&&process.stdout.write(`${JSON.stringify(t)}\n`)}async function qe(e,t,n,r=He){let i=Ue(n),{host:a,json:o}=e;Ge(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?Ke(!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?Ke(!0,{type:`complete`,host:a,login:c}):process.stderr.write(`✓ Logged in as ${c} on ${a}\n`)}function Je(e,t){return new w(`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 qe(n,await t(),e())})}async function Ye(e,t,n){let{host:r,json:i}=e;Ge(r);let a=await(n??(()=>ge({message:`Enter PAT:`})))();a||(process.stderr.write(`No token provided
|
|
@@ -7,7 +7,7 @@ import{a as e,i as t}from"./constants-Cqh2k1vs.mjs";import{D as n,O as r,s as i}
|
|
|
7
7
|
`),process.exit(1)}await t.set(r,c,a,{gitProtocol:`https`,name:l,email:u}),i?process.stdout.write(`${JSON.stringify({type:`complete`,host:r,login:c})}\n`):process.stderr.write(`✓ PAT stored for ${c} on ${r}\n`)}function Xe(e){return new w(`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 Ye(t,await e())})}async function Ze(e,t){let{host:n,json:r}=e;Ge(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 _e({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 Qe(e){return new w(`repos`).description(`List accessible repositories`).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}=e;await t.clear(n),process.stderr.write(`✓ Signed out from ${n}\n`)}function et(e){return new w(`signout`).description(`Remove stored credentials`).option(`--host <host>`,`GitHub hostname`,`github.com`).action(async t=>{await $e(t,await e())})}async function tt(e,t){let{host:n,json:r}=e;Ge(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 _e({auth:i.token,...a?{baseUrl:a}:{}});try{let{data:e}=await o.users.getAuthenticated();r?process.stdout.write(`${JSON.stringify({type:`status`,host:n,authenticated:!0,login:e.login,name:e.name,email:e.email})}\n`):process.stderr.write(`✓ Logged in as ${e.login} on ${n}\n`)}catch{r?process.stdout.write(JSON.stringify({type:`status`,host:n,authenticated:!1,error:`token invalid`})+`
|
|
8
8
|
`):process.stderr.write(`✗ Token invalid for ${n}\n`),process.exit(1)}}function nt(e){return new w(`status`).description(`Show authentication status`).option(`--host <host>`,`GitHub or GitHub Enterprise hostname`,`github.com`).option(`--json`,`Output JSON`,!1).action(async t=>{await tt(t,await e())})}function rt(e){let t=new w(`auth`);t.description(`GitHub authentication management`);let n=()=>Le(),r=e??(()=>({}));return t.addCommand(Je(r,n)),t.addCommand(nt(n)),t.addCommand(Qe(n)),t.addCommand(et(n)),t.addCommand(Xe(n)),t.addCommand(Ve(n)),t}function it(e,t,n={}){let r=p(e,t);if(!D(r))return{status:`missing`,lockPath:r};let i;try{i=JSON.parse(k(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??se();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 at(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 ot(e){let t=e.inspect??(t=>it(e.lockDir,t)),n=e.unlink??(e=>ie(e)),r=e.log??(e=>console.log(e)),i=e.error??(e=>console.error(e)),a=at(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 st(e){return new w(`clean`).description(`Prune stale / corrupt open-knowledge lock files (never touches live locks)`).action(()=>{ot({lockDir:a(o(e(),process.cwd()))}).failed.length>0&&(process.exitCode=1)})}function ct(){try{let e=be(`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 lt(e,t,n={},r=ct){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 ut(e){return e.replace(/:\d+$/,``)}function dt(e){let t=e.trim();if(!t)return null;{let e=/^https?:\/\/([^/?#]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`https`,hostname:ut(e[1]),owner:e[2],name:e[3]}}{let e=/^ssh:\/\/(?:[\w.-]+@)?([^/?#]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`ssh`,hostname:ut(e[1]),owner:e[2],name:e[3]}}{let e=/^git:\/\/([^/?#]+)\/([\w.\-~%]+)\/([\w.\-~%]+?)(?:\.git)?\/?$/.exec(t);if(e)return{protocol:`git`,hostname:ut(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 ft=[[`count`,0,10],[`compress`,10,20],[`receiv`,20,60],[`resolv`,60,100]];function pt(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 ft)if(n.includes(e))return{stage:t[1],pct:Math.round(i+r/100*(a-i))};return null}function mt(e,t){e&&process.stdout.write(`${JSON.stringify(t)}\n`)}async function ht(e,t,n,r=process.cwd()){let i=dt(e);if(!i)throw Error(`Invalid git URL: ${e}`);let a=t.dir?A(r,t.dir):A(r,i.name);if(D(a)&&te(a).length>0)throw Error(`Target directory is not empty: ${a}`);let o=await Le(),s=await lt(i.hostname,o,{}),c=j({baseDir:r,config:s.credentialArgs.length>=2?[s.credentialArgs[1]]:[],unsafe:{allowUnsafeCredentialHelper:!0}}).env({GIT_TERMINAL_PROMPT:`0`}),l=-1;if(c.outputHandler((e,n,r)=>{r.on(`data`,e=>{let n=e.toString(`utf-8`);for(let e of n.split(`
|
|
9
9
|
`)){let n=pt(e);n&&n.pct!==l&&(l=n.pct,mt(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
|
-
`),!D(A(a,`.open-knowledge`)))try{let[{runInit:e},{ensureOkGitignoredAtRoot:t}]=await Promise.all([import(`./init-
|
|
10
|
+
`),!D(A(a,`.open-knowledge`)))try{let[{runInit:e},{ensureOkGitignoredAtRoot:t}]=await Promise.all([import(`./init-CY-1sO72.mjs`),import(`./init-B18k0eWn.mjs`)]);await e({cwd:a,mcp:!1});try{t(a)}catch{}}catch{}return a}function gt(e){return new w(`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 ht(t,{json:r.json,dir:n},i);if(r.json)mt(!0,{type:`complete`,dir:a});else{process.stderr.write(`✓ Cloned to ${a}\n`),process.chdir(a);let{startCommand:t}=await import(`./start-CmW0OJ8S.mjs`);await t(e).parseAsync([],{from:`user`})}}catch(e){let t=e instanceof Error?e.message:String(e);r.json?mt(!0,{type:`error`,message:t}):process.stderr.write(`✗ ${t}\n`),process.exitCode=1}})}var _t=class e{sessionId;corrId;component;constructor(e=`mcp`,t){this.sessionId=t??ye().slice(0,12),this.corrId=ye().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{T(a,i)}catch(e){console.warn(`[mcp-logger] Failed to write to OK_LOG_FILE: ${e instanceof Error?e.message:e}`)}}};function vt(e=`mcp`){return new _t(e)}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 yt(e,t){return typeof e==`function`?await e(t):e}async function bt(e,t){return typeof e==`function`?await e(t):e}async function 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 bt(t,r);return{ok:!0,cwd:r,config:e}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}async function 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 yt(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 xt(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
11
|
|
|
12
12
|
Topic: ${e}
|
|
13
13
|
|
|
@@ -150,19 +150,19 @@ superseded_by: <path-to-new-canonical-article>.md
|
|
|
150
150
|
- **Don't skip the supersedes / superseded_by links** — the audit trail matters for future readers
|
|
151
151
|
|
|
152
152
|
Full convention: read \`${n}/AGENTS.md\`.`}const St=[`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(`
|
|
153
|
-
`);function Ct(e,t){e.tool(`consolidate`,St,{topic:M.string().describe(`The topic to consolidate into a canonical article`),cwd:M.string().optional().describe(N)},async e=>{let n=await L(t.resolveCwd,t.config,e.cwd);return n.ok?F(xt(e.topic,n.config.content.dir),{previewUrl:null}):P(`Error: ${n.error}`,!0)})}function wt(e){return e.split(`/`).map(encodeURIComponent).join(`/`)}function Tt(e){return e.endsWith(`/`)?e.slice(0,-1):e}function Et(e){try{return new URL(e),!0}catch{return!1}}async function H(e,t,n){let r=n??await t.resolveCwd(),i=await bt(t.config,r),s=o(i,r);return W(e,{config:i,lockDir:a(s),contentDir:s})}function Dt(e){try{let t=
|
|
153
|
+
`);function Ct(e,t){e.tool(`consolidate`,St,{topic:M.string().describe(`The topic to consolidate into a canonical article`),cwd:M.string().optional().describe(N)},async e=>{let n=await L(t.resolveCwd,t.config,e.cwd);return n.ok?F(xt(e.topic,n.config.content.dir),{previewUrl:null}):P(`Error: ${n.error}`,!0)})}function wt(e){return e.split(`/`).map(encodeURIComponent).join(`/`)}function Tt(e){return e.endsWith(`/`)?e.slice(0,-1):e}function Et(e){try{return new URL(e),!0}catch{return!1}}async function H(e,t,n){let r=n??await t.resolveCwd(),i=await bt(t.config,r),s=o(i,r);return W(e,{config:i,lockDir:a(s),contentDir:s})}function Dt(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 bt(e.config,n),i=o(r,n),s={config:r,lockDir:a(i),contentDir:i};return{resolve:e=>W(e,s),ui:Dt(s)}}function Ot(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=`/#/${wt(e)}`;if(process.env.OK_ELECTRON_PROTOCOL_HOST===`1`&&t.contentDir)try{let n=ne(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&&Et(r))return{url:`${Tt(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&&Et(i)?{url:`${Tt(i)}${n}`,source:`config`}:null}const kt=["**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(`
|
|
154
154
|
`);function At(e,t){e.tool(`edit_document`,kt,{docName:M.string().describe(`Document name to edit`),find:M.string().describe(`Text to find (exact match)`),replace:M.string().describe(`Replacement text`),offset:M.number().int().min(0).optional().describe(`Exact occurrence to patch, as a JavaScript string offset in the current markdown`),cwd:M.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(`
|
|
155
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 jt=new Set([`cat`,`ls`,`grep`,`find`]),Mt=/\b[\w./-]+\.(md|mdx)\b/g;function G(e){return/\.(md|mdx)$/.test(e)}function Nt(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 Pt(e){return q(K(e)).filter(G)}function Ft(e,t){let n=q(K(t)),r=n.length>0?n[n.length-1]:``,i=r&&r!==`.`?Nt(r):``,a=[];i&&a.push(i);for(let t of e.split(`
|
|
156
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 It(e){let t=[];for(let n of e.split(`
|
|
157
157
|
`)){if(!n)continue;let e=n.indexOf(`:`);if(e<0)continue;let r=Nt(n.slice(0,e));G(r)&&t.push(r)}return t}function Lt(e){let t=[];for(let n of e.split(`
|
|
158
158
|
`)){let e=Nt(n);e&&G(e)&&t.push(e)}return t}function Rt(e){return q(K(e)).filter(G)}function zt(e){return q(K(e)).length>0}function Bt(e){let t=[],n=e.matchAll(Mt);for(let e of n)t.push(Nt(e[0]));return t}function Vt(e,t){let n=null;for(let e=t.length-1;e>=0;e--){let r=t[e];if(jt.has(r.command)){n=r;break}if((r.command===`head`||r.command===`tail`)&&zt(r)){n=r;break}}let r;if(!n)r=Bt(e);else{switch(n.command){case`cat`:r=Pt(n);break;case`ls`:r=Ft(e,n);break;case`grep`:r=It(e);break;case`find`:r=Lt(e);break;case`head`:case`tail`:r=Rt(n);break;default:r=Bt(e)}r.length===0&&(r=Bt(e))}let i=new Set,a=[];for(let e of r){let t=Nt(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 Ht=16*1024*1024;var Ut=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 Wt(e){if(!ue(e))throw Error(`createBashInstance: cwd must be absolute (got: ${e})`);return new je({cwd:`/`,fs:new Me({root:A(e),allowSymlinks:!1})})}async function Gt(e,t){let n=await e.exec(t);if(n.stdout.length>Ht)throw new Ut(Ht,n.stdout.length,{stdout:n.stdout.slice(0,Ht),stderr:n.stderr,exitCode:n.exitCode});return{stdout:n.stdout,stderr:n.stderr,exitCode:n.exitCode}}function Kt(e){return e.startsWith(`**/`)?e.slice(3):e}async function qt(e,t,n={}){let r=Wt(t),i=[`-rn`,`-F`];(n.caseInsensitive??!0)&&i.push(`-i`);for(let e of n.include??[])i.push(`--include=${J(Kt(e))}`);for(let e of n.exclude??[])i.push(`--exclude=${J(Kt(e))}`),i.push(`--exclude-dir=${J(Kt(e))}`);let a=n.paths?.length?n.paths.map(J):[`.`],o=`grep ${i.join(` `)} ${J(e)} ${a.join(` `)}`,s;try{s=await Gt(r,o)}catch(e){if(e instanceof Ut)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(`
|
|
159
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 Jt=new Set([`.git`,n,`node_modules`,`.changeset`,`.claude`,`.agents`,`dist`,`build`]);async function Yt(e){let t=A(e),n=new Map,r=0,i=!1;async function a(e){if(i)return;let o;try{o=await Ce(e,{withFileTypes:!0})}catch{return}for(let s of o){if(i)return;if(s.isDirectory()&&Jt.has(s.name))continue;let o=A(e,s.name);if(s.isDirectory()){await a(o);continue}if(s.isFile()){if(r>=1e3){i=!0;return}try{let e=await we(o);n.set(fe(t,o),e.mtimeMs),r++}catch{}}}}return await a(t),{snapshot:n,truncated:i}}function Xt(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 Zt=[`node_modules`,`.git`,`dist`,`build`,`.next`,`.turbo`,`.nuxt`,`coverage`,`.cache`,`.parcel-cache`,`.vercel`,n,`.claude`];function Qt(e){return e===`--recursive`||e===`--dereference-recursive`?!0:e.startsWith(`--`)||!e.startsWith(`-`)?!1:/[rR]/.test(e.slice(1))}const $t=[{command:`grep`,applies:e=>e.slice(1).some(Qt),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 en(e){return e.map(e=>{let t=$t.find(t=>t.command===e.command);if(!t||!t.applies(e.args)||t.hasUserExcludes(e.args))return e;let n=t.buildExcludeArgs(Zt),r=t.insertionIndex(e.args);return{command:e.command,args:[...e.args.slice(0,r),...n,...e.args.slice(r)]}})}function tn(e){return e.map(e=>e.args.map(J).join(` `)).join(` | `)}const nn=new Set([`cat`,`ls`,`grep`,`find`,`head`,`tail`,`wc`,`sort`,`uniq`,`cut`]),rn=new Set([`>`,`>>`,`<`,`>&`,`<&`,`|&`]),an=new Set([`&`,`;`,`;;`,`&&`,`||`,`(`,`)`,`<(`,`>(`,`<<`,`<<-`]),on=new Set([`-o`,`--output-file`,`--output`]),sn=[`-o=`,`--output-file=`,`--output=`],cn=new Set([`-exec`,`-execdir`,`-delete`,`-fprint`,`-fprintf`,`-fprint0`,`-ok`,`-okdir`]),ln=/[`]|\$\(|\$\{|\$'/;function un(e){return typeof e==`object`&&!!e&&`op`in e}function dn(e){let t=typeof e.op==`string`?e.op:`(unknown)`;return rn.has(t)?{category:`write_blocked`,message:`Write operation blocked: '${t}'. exec is read-only. For document changes, use write_document or edit_document.`}:an.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 fn(e){let t=[];for(let n of e){if(typeof n==`string`){if(ln.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(!un(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:dn(n)}}return{args:t}}function pn(e){if(!nn.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(on.has(t)||sn.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`&&cn.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=Ne.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(un(e)&&e.op===`|`){r.push(i),i=[];continue}i.push(e)}r.push(i);let a=[];for(let e of r){let t=fn(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=pn(n);if(r)return{error:r};a.push(n)}return{stages:a}}const hn=/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;function gn(e,t){let n=e.match(hn);if(!n)return null;try{let e=pe(n[1]);if(_(e)){if(t){let n=t.safeParse(e);return n.success?n.data:null}return e}}catch{}return null}const _n=new WeakMap;function vn(e){let t=_n.get(e);if(t)return t;let n=e.map(e=>De(e.match,{dot:!0}));return _n.set(e,n),n}function yn(e,t){if(e.length===0)return{};let n=vn(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 bn(e){try{return re(A(e,`.git`)).isDirectory()}catch{return!1}}function xn(e){return j({baseDir:A(e),timeout:{block:5e3}})}async function Sn(e,t,n=5){if(!bn(e))return{commits:[],source:`git-absent`};let r=xn(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(`
|
|
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 Cn=5e3;async function wn(e){try{let t=(await j({baseDir:e,timeout:{block:Cn}}).revparse([`--abbrev-ref`,`HEAD`])).trim();return t&&t!==`HEAD`?t:null}catch{return null}}function Tn(e,t){return j({baseDir:t,timeout:{block:Cn}}).env({GIT_DIR:e,GIT_WORK_TREE:t})}function En(e,t){let n=
|
|
161
|
-
`).map(e=>e.trim()).filter(Boolean);return
|
|
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 Cn=5e3;async function wn(e){try{let t=(await j({baseDir:e,timeout:{block:Cn}}).revparse([`--abbrev-ref`,`HEAD`])).trim();return t&&t!==`HEAD`?t:null}catch{return null}}function Tn(e,t){return j({baseDir:t,timeout:{block:Cn}}).env({GIT_DIR:e,GIT_WORK_TREE:t})}function En(e,t){let n=d(t);return e.startsWith(n)?e.slice(n.length):e}async function Dn(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=En(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 On(e,t,n=5){let r=l(e);if(!r)return{commits:[],source:`shadow-repo-absent`};let i=await wn(e);if(!i)return{commits:[],source:`shadow-repo`};let a=Tn(r,A(e)),o=``;try{o=await a.raw(`for-each-ref`,d(i),`--format=%(refname)`)}catch{return{commits:[],source:`shadow-repo`}}let s=o.split(`
|
|
161
|
+
`).map(e=>e.trim()).filter(Boolean);return s.length===0?{commits:[],source:`shadow-repo`}:{commits:(await Promise.all(s.map(e=>Dn(a,e,t,i,n)))).flat().sort((e,t)=>t.date.localeCompare(e.date)).slice(0,n),source:`shadow-repo`}}const kn=1e3,An=new Set([`.git`,n,`node_modules`,`.changeset`,`.claude`,`.agents`,`dist`,`build`]),jn=/\.(md|mdx)$/i,Mn=M.object({title:M.string().optional(),description:M.string().optional(),tags:M.array(M.string()).default([])});function Nn(e){return e.replace(/\.md$/,``).replace(/\.mdx$/,``)}async function Pn(e){try{let t=gn(await Se(e,`utf-8`),Mn);return t?{title:t.title,description:t.description,tags:t.tags??[]}:{tags:[]}}catch{return null}}async function Fn(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 In(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 Ln(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 Rn(e,t,n){let r=t??[],i=r.length===0?{}:yn(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 zn(e,t,n={}){let r=e.replace(/^\.\//,``).replace(/^\/+/,``),i=A(t.projectDir,r),a=t.historyDepth??5,o=n.includeRichFields===!0,s=Pn(i);if(!o){let e=Rn(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,Fn(t.serverUrl,Nn(r)).catch(()=>null),Ln(t.serverUrl,Nn(r)).catch(()=>null),On(t.projectDir,r,a).catch(()=>({commits:[],source:`shadow-repo`})),Sn(t.projectDir,r,a).catch(()=>({commits:[],source:`git`}))]),p=Rn(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 Bn(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>=kn){n.truncated=!0;break}let a;try{a=await Ce(e.path,{withFileTypes:!0})}catch{continue}for(let o of a){if(r>=kn){n.truncated=!0;break}r++;let a=o.name;if(o.isDirectory()){if(An.has(a)||a.startsWith(`.`))continue;e.depth===0&&n.childDirCount++,i.push({path:`${e.path}/${a}`,depth:e.depth+1})}else if(o.isFile()&&jn.test(a)){n.recursiveMdCount++,e.depth===0&&n.directMdCount++;let r=`${e.path}/${a}`;try{let e=await we(r);(!n.mostRecent||e.mtimeMs>n.mostRecent.mtimeMs)&&(n.mostRecent={absPath:r,relPath:fe(t,r).split(/[\\/]/).filter(Boolean).join(`/`),mtimeMs:e.mtimeMs})}catch{}}}}return n}async function Vn(e,t){let n=e.replace(/^\.\//,``).replace(/^\/+/,``).replace(/\/+$/,``),r=await Bn(A(t.projectDir,n),t.projectDir),i;if(r.mostRecent){let e=await Pn(r.mostRecent.absPath);i={path:r.mostRecent.relPath,title:e?.title??ce(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=yn(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 Hn=50*1024,Un=/\.(png|jpe?g|gif|webp|svg|pdf|zip|tar|gz|tgz|mp4|mov|mp3|wav|ico|bmp)$/i,Wn=[`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(`
|
|
162
162
|
`);function Gn(e){let t=e.split(`
|
|
163
163
|
`),n=t[t.length-1]===``?t.length-1:t.length;if(n<=500&&e.length<=Hn)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>Hn)break;a++}let o=t.slice(0,a).join(`
|
|
164
164
|
`),s=n-a;return{text:`${o}\n<truncated: ${s} more lines — re-run with a more-specific query>`,truncated:!0,omittedLines:s}}function Kn(e){let t=[];for(let n of e)if(n.command===`cat`)for(let e of n.args.slice(1))e.startsWith(`-`)||Un.test(e)&&t.push(e);return t}function qn(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 Jn(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=qn(n.args),i=t.split(`
|
|
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 Yn(e){return e.type===`directory`}function Xn(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 Zn(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===`
|
|
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 Yn(e){return e.type===`directory`}function Xn(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 Zn(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 Qn(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 $n(e){if(e.length===0)return``;let t=[``,`### Referenced files`,``];for(let n of e)t.push(Yn(n)?Xn(n):Zn(n));return t.join(`
|
|
166
166
|
`)}async function er(e,t){let n=[],r=[];return await Promise.all(t.map(async t=>{try{let i=await we(A(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 tr(e,t){return e.map(e=>{let n=t(Ot(e.path));return{...e,previewUrl:n?.url??null,...n?{previewUrlSource:n.source}:{}}})}async function nr(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=en(o.stages),c=tn(s),l=await Yt(r),u=Wt(r),d=``,f=``;try{let e=await Gt(u,c);d=e.stdout,f=e.stderr}catch(e){return e instanceof Ut?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 Yt(r),m=Xt(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=Gn(d),g=Vt(d,s),{files:_,dirs:v}=await er(r,g),y=s.length===1&&s[0].command===`cat`&&_.length===1,b=i.folders,x=await Promise.all(_.map(e=>zn(e,{projectDir:r,serverUrl:a,folderRules:b},{includeRichFields:y}).catch(()=>({path:e,tags:[],backlinkCount:null,backlinks:null,forwardLinkCount:null,forwardLinks:null,history:null,historySource:null,projectHistory:null,projectHistorySource:null})))),S=await Promise.all(v.map(e=>Vn(e,{projectDir:r,folderRules:b}).catch(()=>({path:e,type:`directory`,directMdCount:0,recursiveMdCount:0,childDirCount:0,truncated:!1}))));if(!y&&a&&x.length>0){let e=await In(a,x.map(e=>Nn(e.path))).catch(()=>null);if(e)for(let t of x){let n=e.get(Nn(t.path));typeof n==`number`&&(t.backlinkCount=n)}}let C=new Map(x.map(e=>[e.path,e])),w=new Map(S.map(e=>[e.path,e])),T=[];for(let e of g){let t=C.get(e);if(t){T.push(t);continue}let n=w.get(e);n&&T.push(n)}let E=Kn(s),D=[];E.length>0&&D.push(`File${E.length>1?`s`:``} ${E.join(`, `)} appear${E.length===1?`s`:``} to be binary (image/PDF/etc.) — exec returns text only (NG8). For binary retrieval, use native Read.`);let O=Jn(s,d);O&&D.push(O.banner),f&&D.push(`stderr: ${f.trim()}`);let ee=D.length>0?`${D.join(`
|
|
167
167
|
`)}\n\n`:``,k=Qn(s,w,C)+h.text,te=`${ee}${k}${$n(T)}`,{resolve:ne,ui:re}=await U({config:i,resolveCwd:async()=>r}),ie=tr(T,ne),ae=re;return F(te,{enrichedPaths:ie,stdout:k,stdoutTruncated:h.truncated,cwd:r,...ae?{ui:ae}:{},...D.length>0?{warnings:D}:{}})}function rr(e,t){e.tool(`exec`,Wn,{command:M.string().describe(`Read-only bash command (allowlist: cat, ls, grep, find, head, tail, wc, sort, uniq, cut; pipes OK)`),cwd:M.string().optional().describe("Absolute host path to run the command from. Defaults only when the MCP client advertises exactly one root; otherwise pass `cwd` explicitly.")},async e=>{try{return await nr(e,t)}catch(e){return Y(`shell_construct_blocked`,`exec handler error: ${e instanceof Error?e.message:String(e)}`)}})}const ir=[`[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(`
|
|
168
168
|
`);function ar(e,t){e.tool(`get_backlinks`,ir,{docName:M.string().describe(`Target page docName`),cwd:M.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 or=[`[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,8 +170,8 @@ Full convention: read \`${n}/AGENTS.md\`.`}const St=[`Promote research into a ca
|
|
|
170
170
|
`);function lr(e,t){e.tool(`get_forward_links`,cr,{docName:M.string().describe(`Source page docName`),cwd:M.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 ur=[`[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(`
|
|
171
171
|
`);function dr(e,t){e.tool(`get_history`,ur,{docName:M.string().describe(`Document name to query history for`),branch:M.string().optional().describe(`Branch name (default: current branch)`),limit:M.number().int().min(1).max(200).optional().describe(`Maximum entries to return (default 50, max 200)`),offset:M.number().int().min(0).optional().describe(`Number of entries to skip for pagination (default 0)`),type:M.enum([`checkpoint`,`upstream`,`wip`]).optional().describe(`Filter by entry type`),author:M.string().optional().describe(`Filter to entries by this author name or email`),excludeAuthor:M.string().optional().describe(`Exclude entries by this author name or email`),cwd:M.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 fr=[`[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(`
|
|
172
172
|
`);function pr(e,t){e.tool(`get_hubs`,fr,{limit:M.number().int().positive().optional().describe(`Maximum number of hubs to return`),cwd:M.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(`
|
|
173
|
-
`);function hr(e,t){e.tool(`get_orphans`,mr,{mode:M.enum(
|
|
174
|
-
`);async function _r(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),
|
|
173
|
+
`);function hr(e,t){e.tool(`get_orphans`,mr,{mode:M.enum(i).optional().describe(`Filter which type of graph disconnection to surface`),cwd:M.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 gr=["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(`
|
|
174
|
+
`);async function _r(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 vr(e,t){e.tool(`get_preview_url`,gr,{docName:M.string().min(1),cwd:M.string().optional().describe(N)},async e=>{let n=await _r(e,t);return n.ok?F(n.text,n.result):P(n.error,!0)})}function yr(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
175
|
|
|
176
176
|
Source: ${e}
|
|
177
177
|
|
|
@@ -325,13 +325,13 @@ Depending on the project, consider articles covering:
|
|
|
325
325
|
Full convention: read \`${n}/AGENTS.md\`.`}const Cr=[`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(`
|
|
326
326
|
`);function wr(e,t){e.tool(`init-content`,Cr,{cwd:M.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(Sr(i.content.dir),{ui:Dt({config:i,lockDir:a(o(i,r))})})})}const Tr=[`[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(`
|
|
327
327
|
`);function Er(e,t){e.tool(`list_documents`,Tr,{dir:M.string().optional().describe(`Optional directory to filter documents`),cwd:M.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 Dr=[`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(`
|
|
328
|
-
`);function Or(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===`
|
|
328
|
+
`);function Or(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(`
|
|
329
329
|
`)}function kr(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(`
|
|
330
330
|
`)}function Ar(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(`
|
|
331
331
|
`)}function jr(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(`
|
|
332
332
|
`)}function Mr(e){return e.replace(/^\.\//,``).replace(/^\/+/,``)}function Nr(e){return e.replace(/\.(md|mdx)$/i,``)}async function Pr(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=Mr(e.path),s=A(r,o),c=i.mcp.tools.read_document.historyDepth,[l,u]=await Promise.all([Se(s,`utf-8`),zn(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=Or(u.history);g&&h.push(g);let _=kr(u.projectHistory);_&&h.push(_);let v=Ar(u.backlinks);v&&h.push(v);let y=jr(u.forwardLinks);return y&&h.push(y),h.push(``,`### Content`,``,l),h.join(`
|
|
333
333
|
`)}function Fr(e,t){e.tool(`read_document`,Dr,{path:M.string().describe(`Project-root-relative path to the file`),since:M.string().optional().describe(`Reserved; currently unused (§15 Future Work)`),cwd:M.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 Pr(e,t),r=await H(Nr(Mr(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 Ir=["[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(`
|
|
334
|
-
`);function Lr(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 Rr(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 zr(e,t,n=`${t}s`){return e===1?t:n}function Br(e,t){e.tool(`rename_document`,Ir,{docName:M.string().describe(`Current document name`),newDocName:M.string().describe(`New document name`),cwd:M.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=await V(i,`/api/rename`,{docName:a.docName,newDocName:o.docName});if(!
|
|
334
|
+
`);function Lr(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 Rr(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 zr(e,t,n=`${t}s`){return e===1?t:n}function Br(e,t){e.tool(`rename_document`,Ir,{docName:M.string().describe(`Current document name`),newDocName:M.string().describe(`New document name`),cwd:M.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=Lr(c.renamed),u=Rr(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} ${zr(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 Vr(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
335
|
|
|
336
336
|
Topic: ${e}
|
|
337
337
|
|
|
@@ -483,13 +483,13 @@ Research articles are discovery surfaces — they should link out to **every** r
|
|
|
483
483
|
|
|
484
484
|
Full convention: read \`${n}/AGENTS.md\`.`}const Hr=[`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(`
|
|
485
485
|
`);function Ur(e,t){e.tool(`research`,Hr,{topic:M.string().describe(`The topic, question, or anchor URL to research`),cwd:M.string().optional().describe(N)},async e=>{let n=await L(t.resolveCwd,t.config,e.cwd);return n.ok?F(Vr(e.topic,n.config.content.dir),{previewUrl:null}):P(`Error: ${n.error}`,!0)})}const Wr=[`[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(`
|
|
486
|
-
`);function Gr(e,t){e.tool(`rollback_to_version`,Wr,{docName:M.string().describe(`Document name to restore`),commitSha:M.string().length(40).regex(/^[0-9a-f]+$/i).describe(`40-character commit SHA from the shadow repo timeline`),cwd:M.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=await V(i,`/api/rollback`,{docName:o,commitSha:e.commitSha});if(!
|
|
486
|
+
`);function Gr(e,t){e.tool(`rollback_to_version`,Wr,{docName:M.string().describe(`Document name to restore`),commitSha:M.string().length(40).regex(/^[0-9a-f]+$/i).describe(`40-character commit SHA from the shadow repo timeline`),cwd:M.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 Kr=[`[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(`
|
|
487
487
|
`);function qr(e,t,n,r,i){e.tool(`save_version`,Kr,{cwd:M.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 Jr=[`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(`
|
|
488
488
|
`);function Yr(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 Xr(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 qt(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=Yr(f),g=new Map,_=a.folders;await Promise.all(h.map(async e=>{try{let t=await zn(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=Ot(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(`
|
|
489
489
|
`),structured:{query:e.query,matchCount:f.length,fileCount:h.length,truncated:d,results:y,ui:m,cwd:i}}}function Zr(e,t){e.tool(`search`,Jr,{query:M.string().describe(`Literal text to search for`),case_sensitive:M.boolean().optional().describe(`Case-sensitive search (default false)`),cwd:M.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 Xr(e,t);return r?F(n,r):P(n)}catch(e){return P(`Error: ${e instanceof Error?e.message:String(e)}`,!0)}})}const Qr=[`[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(`
|
|
490
490
|
`);function $r(e,t){e.tool(`suggest_links`,Qr,{docName:M.string().describe(`Target page docName`),cwd:M.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 ei=["**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(`
|
|
491
491
|
`);function ti(e,t){e.tool(`write_document`,ei,{docName:M.string().describe(`Document name to write to`),markdown:M.string().describe(`Markdown content to write`),position:M.enum([`append`,`prepend`,`replace`]).describe(`Where to insert the content`),cwd:M.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(`
|
|
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 ni={exec:Wn,"init-content":Cr,ingest:br,research:Hr,consolidate:St,read_document:Dr,rename_document:Ir,search:Jr,suggest_links:Qr,write_document:ei,edit_document:kt,get_history:ur,save_version:Kr,rollback_to_version:Wr,list_documents:Tr,get_backlinks:ir,get_forward_links:cr,get_orphans:mr,get_hubs:fr,get_dead_links:or,get_preview_url:gr};function ri(e,t){let n=t.logger,r=e=>async r=>{try{let i=await t.resolveCwd(r);return n?.info(`tool call`,{tool:e,cwd:i,...r?{explicit:r}:{}}),i}catch(t){throw n?.warn(`tool call failed`,{tool:e,error:t instanceof Error?t.message:String(t),...r?{explicit:r}:{}}),t}};rr(e,{resolveCwd:r(`exec`),serverUrl:t.serverUrl,config:t.config}),wr(e,{config:t.config,resolveCwd:r(`init-content`)}),xr(e,{config:t.config,resolveCwd:r(`ingest`)}),Ur(e,{config:t.config,resolveCwd:r(`research`)}),Ct(e,{config:t.config,resolveCwd:r(`consolidate`)}),Fr(e,{resolveCwd:r(`read_document`),config:t.config,serverUrl:t.serverUrl}),Zr(e,{resolveCwd:r(`search`),config:t.config,serverUrl:t.serverUrl}),$r(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`suggest_links`)}),ti(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`write_document`),identityRef:t.identityRef}),At(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`edit_document`),identityRef:t.identityRef}),Br(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`rename_document`)}),dr(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`get_history`)}),qr(e,t.config,t.serverUrl,r(`save_version`),t.identityRef),Gr(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`rollback_to_version`)}),Er(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`list_documents`)}),ar(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`get_backlinks`)}),lr(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`get_forward_links`)}),hr(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`get_orphans`)}),pr(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`get_hubs`)}),sr(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`get_dead_links`)}),vr(e,{resolveCwd:r(`get_preview_url`),config:t.config})}function ii(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 ai=class extends Error{};function oi(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(ve(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:ii(t)}),new ai(`Client roots unavailable; pass cwd explicitly.`)}if(r.length===0)throw new ai(`No client roots available; pass cwd explicitly.`);if(r.length>1)throw new ai(`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 si(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 ci(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)
|
|
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 ni={exec:Wn,"init-content":Cr,ingest:br,research:Hr,consolidate:St,read_document:Dr,rename_document:Ir,search:Jr,suggest_links:Qr,write_document:ei,edit_document:kt,get_history:ur,save_version:Kr,rollback_to_version:Wr,list_documents:Tr,get_backlinks:ir,get_forward_links:cr,get_orphans:mr,get_hubs:fr,get_dead_links:or,get_preview_url:gr};function ri(e,t){let n=t.logger,r=e=>async r=>{try{let i=await t.resolveCwd(r);return n?.info(`tool call`,{tool:e,cwd:i,...r?{explicit:r}:{}}),i}catch(t){throw n?.warn(`tool call failed`,{tool:e,error:t instanceof Error?t.message:String(t),...r?{explicit:r}:{}}),t}};rr(e,{resolveCwd:r(`exec`),serverUrl:t.serverUrl,config:t.config}),wr(e,{config:t.config,resolveCwd:r(`init-content`)}),xr(e,{config:t.config,resolveCwd:r(`ingest`)}),Ur(e,{config:t.config,resolveCwd:r(`research`)}),Ct(e,{config:t.config,resolveCwd:r(`consolidate`)}),Fr(e,{resolveCwd:r(`read_document`),config:t.config,serverUrl:t.serverUrl}),Zr(e,{resolveCwd:r(`search`),config:t.config,serverUrl:t.serverUrl}),$r(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`suggest_links`)}),ti(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`write_document`),identityRef:t.identityRef}),At(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`edit_document`),identityRef:t.identityRef}),Br(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`rename_document`),identityRef:t.identityRef}),dr(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`get_history`)}),qr(e,t.config,t.serverUrl,r(`save_version`),t.identityRef),Gr(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`rollback_to_version`),identityRef:t.identityRef}),Er(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`list_documents`)}),ar(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`get_backlinks`)}),lr(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`get_forward_links`)}),hr(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`get_orphans`)}),pr(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`get_hubs`)}),sr(e,{serverUrl:t.serverUrl,config:t.config,resolveCwd:r(`get_dead_links`)}),vr(e,{resolveCwd:r(`get_preview_url`),config:t.config})}function ii(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 ai=class extends Error{};function oi(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(ve(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:ii(t)}),new ai(`Client roots unavailable; pass cwd explicitly.`)}if(r.length===0)throw new ai(`No client roots available; pass cwd explicitly.`);if(r.length>1)throw new ai(`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 si(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 ci(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
493
|
|
|
494
494
|
${o}
|
|
495
495
|
|
|
@@ -590,6 +590,6 @@ Folder metadata lives in \`config.yml\`, **not** in content files — this is in
|
|
|
590
590
|
${Object.entries(ni).map(([e,t])=>`### \`${e}\`\n${t}`).join(`
|
|
591
591
|
|
|
592
592
|
`)}
|
|
593
|
-
`}async function li(e,t){try{let t=e.replace(`ws://`,`http://`).replace(`wss://`,`https://`);return(await fetch(`${t}/api/
|
|
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:r}=await import(`node:path`),{acquireUiLock:a,readServerLock:o,releaseUiLock:s,updateUiLockPort:c}=await import(`./src-B-CjXHOr.mjs`),{default:l}=await import(`sirv`),{resolveContentDir:u,resolveLockDir:d}=await import(`./paths-De38nMxk.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=[r(m,`public`),r(m,`../../app/dist`),r(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??i,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-B-CjXHOr.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-B-CjXHOr.mjs`),{resolveContentDir:i,resolveLockDir:a}=await import(`./paths-De38nMxk.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{};
|
|
593
|
+
`}async function li(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 ui(n){let{projectDir:r,serverUrl:i,config:a,startupConfig:o,bypassProjectSelection:s=!1}=n;if(X=vt(),X.info(`MCP server starting`,{startupCwd:r,bypassProjectSelection:s,serverUrlType:typeof i==`string`?`explicit`:`lazy`}),typeof i==`string`){let e=await li(i,X);X.info(`Hocuspocus detection complete`,{serverUrl:i,available:e})}else X.info(`server discovery is lazy per effective cwd`);let c=new Oe({name:t,version:e},{instructions:ci(o,{dynamicConfig:typeof a==`function`&&!s})}),l=oi({startupCwd:r,bypassProjectSelection:s,listRoots:()=>c.server.listRoots(),logger:X}),u=si({startupCwd:r,resolveCwd:l.resolveCwd,bypassProjectSelection:s}),d=u.resolveCwdForTools;c.server.setNotificationHandler(Ae,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=ye(),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})},ri(c,{serverUrl:f,resolveCwd:d,config:a,identityRef:h,logger:X});let g=new ke;await c.connect(g),X.info(`MCP server running on stdio`);let{startKeepalive:_}=await import(`./keepalive-BtwYrChs.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 di(e){if(e===void 0||e===``)return;let t=Number.parseInt(e,10);if(!(Number.isNaN(t)||t<=0))return t}function fi(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 pi(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??xe,a=e.readErrorLog??(e=>D(e)?k(e,`utf-8`).trim():``),o=e.openErrorLog??(e=>ee(e,`w`)),s=e.closeFd??(e=>E(e)),c=e.timeoutMs??5e3,l=e.pollIntervalMs??100,u=fi({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};D(e.lockDir)||O(e.lockDir,{recursive:!0});let d=de(e.lockDir,`last-spawn-error.log`),f=o(d),p,g,_=C();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),b=(c/1e3).toFixed(c%1e3==0?0:2),x=p?.pid,S=``;throw typeof x==`number`&&(S=n(x)?` child pid=${x} is still running — raise OK_MCP_SPAWN_TIMEOUT_MS if this is a slow boot.`:` child pid=${x} exited — check last-spawn-error.log.`),e.logger?.error(`spawn poll timeout`,void 0,{timeoutMs:c,childPid:x,childAlive:typeof x==`number`?n(x):void 0,stderr:y||void 0}),Error(`Error: server did not start within ${b}s.${S}${y?` stderr:\n${y}`:``}`)}function 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??pi,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 hi(e){return new w(`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=x({startupCwd:r,startupConfig:n}),a=di(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 ui({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 gi(e){return new w(`preview`).description(`Show what content the watcher will track (read-only)`).action(async()=>{let{previewContent:t,formatPreviewBlock:n}=await import(`./preview-0B7pnKaj.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 _i(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=j({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
|
|
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-uks91rcD.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-uks91rcD.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{};
|
|
595
595
|
//# sourceMappingURL=cli.mjs.map
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e,r as t}from"./loader-
|
|
1
|
+
import{n as e,r as t}from"./loader-DFXyUwo3.mjs";import"./src-sdceGyDy.mjs";export{t as ConfigSchema,e as loadConfig};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as e,i as t,n,o as r,r as i,s as a,t as o}from"./init-Dp5IRYFc.mjs";export{o as CLAUDE_MD_SECTION,n as OK_MARKER_BEGIN,i as OK_MARKER_END,t as PREVIEW_GUIDANCE,e as ensureOkGitignoredAtRoot,r as initContent,a as upsertRootInstructions};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{i as e}from"./constants-
|
|
1
|
+
import{i as e}from"./constants-Z_elUeaU.mjs";import{O as t}from"./src-CQw-HNNM.mjs";import{n,t as r}from"./src-BmCYyMng.mjs";import{o as i,s as a}from"./init-Dp5IRYFc.mjs";import{t as o}from"./preview-D2Qyb0bk.mjs";import{o as s}from"./colors-CoV_NlQL.mjs";import{t as c}from"./is-object-B4GcZahG.mjs";import{Command as l}from"commander";import{existsSync as u,mkdirSync as d,readFileSync as f,writeFileSync as p}from"node:fs";import{homedir as m}from"node:os";import{basename as h,dirname as g,isAbsolute as ee,join as _,posix as v,relative as y,resolve as b,sep as x,win32 as S}from"node:path";import{parse as C,stringify as w}from"smol-toml";const T=[`claude`,`claude-desktop`,`cursor`,`vscode`,`windsurf`,`codex`],E=[`@inkeep/open-knowledge`,`mcp`],D={MCP_DEBUG:`1`,OK_LOG_FILE:`/tmp/ok-mcp.log`};function O(e=process.argv[1]){if(!e)throw Error(`Cannot infer the local CLI entry for --dev-mcp because process.argv[1] is empty.`);let t=b(e);if(h(t)===`cli.mjs`&&h(g(t))===`dist`)return t;let n=t.split(x),r=n.lastIndexOf(`packages`);if(r===-1||n[r+1]!==`cli`)throw Error(`Cannot infer the repo root for --dev-mcp from ${t}. Run the local CLI from this repo so the built dist path can be derived.`);let i=n.slice(0,r);return _(i.length===0?x:i.join(x),`packages`,`cli`,`dist`,`cli.mjs`)}function k(e={}){return e.mode===`dev`?{command:`node`,args:[O(e.cliEntryPath),`mcp`],env:{...D}}:{command:`npx`,args:[...E]}}function A(e){return e===`win32`?S:v}function te(e={}){let t=e.platformName??process.platform,n=e.home??m(),r=e.env??process.env,i=A(t);return t===`darwin`?i.join(n,`Library`,`Application Support`):t===`win32`?r.APPDATA??i.join(n,`AppData`,`Roaming`):r.XDG_CONFIG_HOME??i.join(n,`.config`)}function j(e={}){let t=e.platformName??process.platform,n=e.home??m();return A(t).join(n,`.claude.json`)}function M(e={}){let t=e.platformName??process.platform,n=e.home??m(),r=e.env??process.env;if(t===`darwin`)return v.join(n,`Library`,`Application Support`,`Claude`,`claude_desktop_config.json`);if(t===`win32`){let e=r.APPDATA??S.join(n,`AppData`,`Roaming`);return S.join(e,`Claude`,`claude_desktop_config.json`)}throw Error(`Claude Desktop is not available on ${t}. Supported: macOS, Windows.`)}function N(e={}){let t=e.platformName??process.platform,n=e.home??m();return A(t).join(n,`.cursor`,`mcp.json`)}function P(e={}){return A(e.platformName??process.platform).join(te(e),`Code`,`User`,`mcp.json`)}function F(e={}){let t=e.platformName??process.platform,n=e.home??m();return A(t).join(n,`.codeium`,`windsurf`,`mcp_config.json`)}function I(e={}){let t=e.platformName??process.platform,n=e.home??m();return(e.env??process.env).CODEX_HOME??A(t).join(n,`.codex`)}function L(e={}){return A(e.platformName??process.platform).join(I(e),`config.toml`)}function R(e,t){if(e===t)return!0;if(Array.isArray(e)&&Array.isArray(t))return e.length===t.length&&e.every((e,n)=>R(e,t[n]));if(c(e)&&c(t)){let n=Object.keys(e).sort(),r=Object.keys(t).sort();return n.length===r.length&&n.every((e,t)=>e===r[t])&&n.every(n=>R(e[n],t[n]))}return!1}function ne(e,t){return Object.entries(t).every(([t,n])=>R(e[t],n))}function re(e,t){return{...e,...t}}function z(e){return{...e,isCompatible(t,n,r){return ne(t,e.buildEntry(n,r))},mergeManagedFields(t,n,r){return re(t,e.buildEntry(n,r))}}}const B={claude:z({id:`claude`,label:`Claude Code`,configPath:(e,t)=>j({home:t}),format:`json`,topLevelKey:`mcpServers`,serverName:()=>e,buildEntry:(e,t)=>k(t),scope:`global`,detectPath:(e,t)=>_(t??m(),`.claude`),legacyProjectConfigPath:e=>_(e,`.mcp.json`),instructionsPath:e=>_(e,`CLAUDE.md`)}),"claude-desktop":z({id:`claude-desktop`,label:`Claude Desktop`,configPath:(e,t)=>M({home:t}),format:`json`,topLevelKey:`mcpServers`,serverName:()=>e,buildEntry:(e,t)=>k(t),scope:`global`,detectPath:(e,t)=>g(M({home:t}))}),cursor:z({id:`cursor`,label:`Cursor`,configPath:(e,t)=>N({home:t}),format:`json`,topLevelKey:`mcpServers`,serverName:()=>e,buildEntry:(e,t)=>k(t),scope:`global`,detectPath:(e,t)=>g(N({home:t})),legacyProjectConfigPath:e=>_(e,`.cursor`,`mcp.json`)}),vscode:z({id:`vscode`,label:`VS Code`,configPath:(e,t)=>P({home:t}),format:`json`,topLevelKey:`servers`,serverName:()=>e,buildEntry:(e,t)=>({type:`stdio`,...k(t)}),scope:`global`,detectPath:(e,t)=>g(P({home:t})),legacyProjectConfigPath:e=>_(e,`.vscode`,`mcp.json`)}),windsurf:z({id:`windsurf`,label:`Windsurf`,configPath:(e,t)=>F({home:t}),format:`json`,topLevelKey:`mcpServers`,serverName:()=>e,buildEntry:(e,t)=>k(t),scope:`global`,detectPath:(e,t)=>g(F({home:t}))}),codex:z({id:`codex`,label:`Codex`,configPath:(e,t)=>L({home:t}),format:`toml`,topLevelKey:`mcp_servers`,serverName:()=>e,buildEntry:(e,t)=>k(t),scope:`global`,detectPath:(e,t)=>g(L({home:t})),legacyProjectConfigPath:e=>_(e,`.codex`,`config.toml`)})};function V(e){let t=e.filter(e=>!(e in B));if(t.length>0)throw Error(`Unknown editor(s): ${t.join(`, `)}. Valid options: ${T.join(`, `)}`);return e.map(e=>B[e])}function H(e){if(!u(e))return{};let t=f(e,`utf-8`).trim();if(t===``)return{};try{let n=JSON.parse(t);if(c(n))return n;throw Error(`${e} root must be a JSON object`)}catch(t){throw t instanceof SyntaxError?Error(`${e} contains invalid JSON: ${t.message}`):t}}function U(e){if(!u(e))return{};let t=f(e,`utf-8`).trim();if(t===``)return{};try{let n=C(t);if(c(n))return n;throw Error(`${e} root must be a TOML table`)}catch(t){throw Error(`${e} contains invalid TOML: ${t instanceof Error?t.message:String(t)}`)}}function W(e,t){d(g(e),{recursive:!0}),p(e,`${JSON.stringify(t,null,2)}\n`,`utf-8`)}function G(e,t){d(g(e),{recursive:!0});let n=w(t);p(e,n.endsWith(`
|
|
2
2
|
`)?n:`${n}\n`,`utf-8`)}const K=`0.0.1`,q=`open-knowledge-ui`;function J(e,t){let n=[];e.runtimeExecutable!==t.runtimeExecutable&&n.push(`runtimeExecutable`);let r=e.runtimeArgs;return Array.isArray(r)&&r.length===t.runtimeArgs.length&&r.every((e,n)=>e===t.runtimeArgs[n])||n.push(`runtimeArgs`),e.port!==t.port&&n.push(`port`),n}function Y(e,t,n={}){let r=_(e,`.claude`,`launch.json`),i=n.mode===`dev`,a=n.mode===`dev`?{name:q,runtimeExecutable:`node`,runtimeArgs:[O(n.cliEntryPath),`ui`],port:3e3}:{name:q,runtimeExecutable:`npx`,runtimeArgs:[`@inkeep/open-knowledge`,`ui`],port:3e3};try{if(!u(r))return d(g(r),{recursive:!0}),p(r,`${JSON.stringify({version:K,configurations:[a]},null,2)}\n`,`utf-8`),{action:`created`,configPath:r};let e=f(r,`utf-8`).trim(),n=e?JSON.parse(e):{};if(!c(n))return{action:`failed`,configPath:r,error:`launch.json root is not an object`};let o=Array.isArray(n.configurations)?n.configurations:[],s=o.findIndex(e=>c(e)&&e.name===q);if(s>=0&&!t){let e=o[s],t=J(e,a);if(t.length>0){if(!i)return{action:`skipped-stale`,configPath:r,staleFields:t}}else return{action:`skipped-existing`,configPath:r}}s>=0?o[s]=a:o.push(a);let l={...n,version:n.version??K,configurations:o};return p(r,`${JSON.stringify(l,null,2)}\n`,`utf-8`),{action:`merged`,configPath:r}}catch(e){return{action:`failed`,configPath:r,error:e instanceof Error?e.message:String(e)}}}function ie(e,t,n,r,i){let a=e.serverName(t),o;try{o=e.configPath(t,i)}catch(t){return{editorId:e.id,label:e.label,action:`failed`,configPath:``,serverName:a,error:t instanceof Error?t.message:String(t)}}let s;try{s=e.format===`toml`?U(o):H(o)}catch(t){return{editorId:e.id,label:e.label,action:`failed`,configPath:o,serverName:a,error:t instanceof Error?t.message:String(t)}}let l=s[e.topLevelKey]??{},u=l[a],d=r.mode===`dev`,f;try{if(u!==void 0&&!n){if(c(u)&&e.isCompatible(u,t,r))return{editorId:e.id,label:e.label,action:`skipped-existing`,configPath:o,serverName:a};if(!d)return{editorId:e.id,label:e.label,action:`skipped-conflict`,configPath:o,serverName:a}}f=c(u)&&(n||d)?e.mergeManagedFields(u,t,r):e.buildEntry(t,r)}catch(t){return{editorId:e.id,label:e.label,action:`failed`,configPath:o,serverName:a,error:t instanceof Error?t.message:String(t)}}let p={...s,[e.topLevelKey]:{...l,[a]:f}};try{e.format===`toml`?G(o,p):W(o,p)}catch(t){return{editorId:e.id,label:e.label,action:`failed`,configPath:o,serverName:a,error:t instanceof Error?t.message:String(t)}}return{editorId:e.id,label:e.label,action:u===void 0?`written`:`overwritten`,configPath:o,serverName:a}}function ae(e,t){let n=e.legacyProjectConfigPath?.(t);if(!(!n||!u(n)))return{editorId:e.id,label:e.label,path:n}}async function X(e={}){let t=b(e.cwd??process.cwd()),r={mode:e.devMcp?`dev`:`published`,cliEntryPath:e.cliEntryPath},o=await n(t),s;try{s=i(t)}catch(n){let r=B.claude.configPath(t,e.home);return{contentCreated:[],contentSkipped:[],editors:[],legacyProjectConfigs:[],rootInstructions:[],didGitInit:o.didInit,mcpAction:`failed`,mcpPath:r,mcpError:`Content scaffolding failed: ${n instanceof Error?n.message:String(n)}`}}let c=e.editors??[`claude`],l=V(c),u=[];for(let n of l){if(e.mcp===!1){let r=``;try{r=n.configPath(t,e.home)}catch{}u.push({editorId:n.id,label:n.label,action:`skipped-flag`,configPath:r,serverName:n.serverName(t)});continue}u.push(ie(n,t,e.force??!1,r,e.home))}let d=e.mcp===!1?[]:l.map(e=>ae(e,t)).filter(e=>e!==void 0),f=c.includes(`claude`)&&e.mcp!==!1?Y(t,e.force??!1,r):void 0,p=l.map(e=>e.instructionsPath?.(t)).filter(e=>e!==void 0).map(e=>ee(e)?y(t,e):e),m=e.rootInstructions===!1?[]:a(t,e.force??!1,p),h=u.find(e=>e.editorId===`claude`)??u[0]??{action:`skipped-flag`,configPath:B.claude.configPath(t,e.home)};return{contentCreated:s.created,contentSkipped:s.skipped,editors:u,legacyProjectConfigs:d,rootInstructions:m,launchJson:f,didGitInit:o.didInit,mcpAction:h.action,mcpPath:h.configPath,mcpError:`error`in h?h.error:void 0}}function Z(e,n){let r=[];e.didGitInit&&r.push(`Initialized git repo at ${n}/.git/ (default branch: main)`);let i=_(n,t);if(e.contentCreated.length>0?(r.push(`Content scaffolded at ${i}/`),r.push(` Created: ${e.contentCreated.join(`, `)}`)):r.push(`Content already present at ${i}/`),e.contentSkipped.length>0&&r.push(` Skipped (already exist): ${e.contentSkipped.join(`, `)}`),r.push(``),e.editors.length===0)e.mcpError&&r.push(`Warning: ${e.mcpError}`);else{let t=e.editors.some(e=>e.action===`written`||e.action===`overwritten`),i=e.editors.some(e=>e.action===`failed`);if(e.editors.every(e=>e.action===`skipped-flag`))r.push(`MCP config not written — use without --no-mcp to configure editors`);else{r.push(`MCP server configuration:`);for(let t of e.editors){let e=t.configPath.startsWith(n)?y(n,t.configPath):t.configPath.replace(/^\/Users\/[^/]+/,`~`),i=t.serverName===`open-knowledge`?``:` (${t.serverName})`,a=` `.repeat(Math.max(1,14-t.label.length)),o=t.editorId===`claude-desktop`&&(t.action===`written`||t.action===`overwritten`)?` — quit and relaunch Claude Desktop to activate`:``;switch(t.action){case`written`:r.push(` ${t.label}${a}${e} registered${i}${o}`);break;case`overwritten`:r.push(` ${t.label}${a}${e} updated${i}${o}`);break;case`skipped-existing`:r.push(` ${t.label}${a}${e} already configured${i}`);break;case`skipped-conflict`:r.push(` ${t.label}${a}${e} managed MCP fields differ from current defaults${i}; re-run with --force to update`);break;case`failed`:r.push(` ${t.label}${a}${e} FAILED: ${t.error}`);break;case`skipped-flag`:break}}}if(i&&(r.push(``),r.push(`For failed editors, add the MCP server entry manually. See:`),r.push(` https://github.com/inkeep/open-knowledge#mcp-setup`)),e.legacyProjectConfigs.length>0){r.push(``),r.push(`Legacy project MCP configs detected:`);for(let t of e.legacyProjectConfigs)r.push(` ${t.label} ${y(n,t.path)}`);r.push(` These project-local files may override the new global config. Remove them if you want fully user-scoped MCP setup in this project.`)}if(e.launchJson){let t=e.launchJson,i=t.configPath.startsWith(n)?y(n,t.configPath):t.configPath;switch(t.action){case`created`:r.push(` launch.json ${i} created (preview_start("${q}") ready)`);break;case`merged`:r.push(` launch.json ${i} merged open-knowledge entry`);break;case`skipped-existing`:r.push(` launch.json ${i} already has open-knowledge entry`);break;case`skipped-stale`:r.push(` launch.json ${i} ${s(`⚠ existing open-knowledge entry is out of date`)}`),t.staleFields&&t.staleFields.length>0&&r.push(` ${s(`${t.staleFields.join(`, `)} differ from current defaults`)}`),r.push(` ${s(`re-run with --force to update`)}`);break;case`failed`:r.push(` launch.json ${i} FAILED: ${t.error}`);break}}if(e.rootInstructions.length>0){let t=e.rootInstructions.filter(e=>e.action!==`skipped-symlink`);if(t.length>0){r.push(``),r.push(`Root instructions:`);for(let e of t){let t=e.path.startsWith(n)?y(n,e.path):e.path,i=` `.repeat(Math.max(1,14-e.file.length));switch(e.action){case`created`:r.push(` ${e.file}${i}${t} created`);break;case`appended`:r.push(` ${e.file}${i}${t} appended Open Knowledge section`);break;case`replaced`:r.push(` ${e.file}${i}${t} replaced Open Knowledge section (--force)`);break;case`skipped-existing`:r.push(` ${e.file}${i}${t} already has Open Knowledge section`);break}}}}if(e.preview?(r.push(``),r.push(o(e.preview,n))):e.previewWarning&&(r.push(``),r.push(`Content preview unavailable: ${e.previewWarning}`)),t){let t=e.editors.filter(e=>e.action===`written`||e.action===`overwritten`).map(e=>e.label);r.push(``),r.push(`Next steps:`),r.push(` 1. Open your editor (${t.join(` / `)})`),r.push(` 2. Approve the MCP server when prompted`),r.push(` 3. The knowledge base is ready — use the three workflow tools:`),r.push(` - mcp__open-knowledge__init-content — bootstrap articles from the codebase`),r.push(` - mcp__open-knowledge__ingest — capture an external source`),r.push(` - mcp__open-knowledge__research — gather sources and write findings`)}}return r.join(`
|
|
3
3
|
`)}function oe(e){switch(e){case`claude_desktop`:return`claude-desktop`;default:return e}}function Q(e){if(e===`all`)return[...T];let t=e.split(`,`).map(e=>e.trim()).map(oe);return V(t),t}function $(e,t){let n=[];for(let r of T){let i=B[r],a;try{a=i.detectPath?.(e,t)??g(i.configPath(e,t))}catch{continue}u(a)&&n.push(r)}return n}function se(){return new l(`init`).description(`Scaffold ${t}/ in the current directory and register the MCP server for your editor(s)`).option(`--mcp`,`Register the MCP server for selected editors (default: true)`,!0).option(`--no-mcp`,`Scaffold the ${t}/ directory but do not touch MCP config`).option(`--force`,`Overwrite existing open-knowledge MCP entries (default: skip)`).option(`--dev-mcp`,`Register a local dev MCP entry using node + packages/cli/dist/cli.mjs with debug logging`).option(`--editor <editors>`,`Target editor(s): ${T.join(`, `)}, all (comma-separated) — default: all detected editors (non-TTY) / preselects detected editors (TTY)`).action(async e=>{let t=process.cwd(),n;if(e.editor)try{n=Q(e.editor)}catch(e){process.stderr.write(`${e instanceof Error?e.message:String(e)}\n`),process.exitCode=1;return}else if(e.mcp!==!1&&process.stdin.isTTY){let{multiselect:e,isCancel:r}=await import(`@clack/prompts`),i=new Set($(t));i.size===0&&process.stdout.write(`No MCP-capable editors detected — select manually, or cancel and use --editor <all|${T.join(`|`)}>.\n`);let a=await e({message:`Which tools do you use? (space to toggle, enter to confirm)`,options:T.flatMap(e=>{let n=B[e];try{let r=n.configPath(t),a=n.scope===`global`?r.replace(/^\/Users\/[^/]+/,`~`):y(t,r);return[{value:e,label:n.label,hint:a,initialValue:i.has(e)}]}catch{return[]}}),required:!0});if(r(a)){process.stdout.write(`Init cancelled.
|
|
4
4
|
`);return}n=a}else if(n=$(t),n.length===0){process.stderr.write(`No MCP-capable editors detected. Use --editor <all|${T.join(`|`)}> to force.\n`),process.exitCode=1;return}let i;try{i=await X({cwd:t,mcp:e.mcp,force:e.force,devMcp:e.devMcp,editors:n})}catch(e){if(e instanceof r){process.stderr.write(`open-knowledge requires git to initialize a parent repo. Install git or run 'git init' yourself, then re-run.
|
|
5
|
-
`),e.stderr&&process.stderr.write(`${e.stderr.trim()}\n`),process.exitCode=1;return}throw e}try{let{previewContent:e}=await import(`./preview-
|
|
6
|
-
//# sourceMappingURL=init-
|
|
5
|
+
`),e.stderr&&process.stderr.write(`${e.stderr.trim()}\n`),process.exitCode=1;return}throw e}try{let{previewContent:e}=await import(`./preview-0B7pnKaj.mjs`),{loadConfig:n}=await import(`./loader-DqlTfvZz.mjs`),{resolveContentDir:r}=await import(`./paths-uks91rcD.mjs`),{config:a}=n(t),o=r(a,t);i.preview=e({projectDir:t,contentDir:o,include:a.content.include,exclude:a.content.exclude})}catch(e){i.previewWarning=e instanceof Error?e.message:String(e)}process.stdout.write(`${Z(i,t)}\n`),(i.editors.some(e=>e.action===`failed`)||i.mcpAction===`failed`)&&(process.exitCode=1)})}export{X as a,Q as i,Z as n,se as r,$ as t};
|
|
6
|
+
//# sourceMappingURL=init-Bell0i8d.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as e,i as t,n,r,t as i}from"./init-Bell0i8d.mjs";export{i as detectInstalledEditors,n as formatInitResult,r as initCommand,t as parseEditorFlag,e as runInit};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{n as e,r as t,t as n}from"./constants-
|
|
1
|
+
import{n as e,r as t,t as n}from"./constants-Z_elUeaU.mjs";import{O as r}from"./src-CQw-HNNM.mjs";import{existsSync as i,mkdirSync as a,readFileSync as o,realpathSync as s,writeFileSync as c}from"node:fs";import{join as l,resolve as u}from"node:path";const d=`<!-- open-knowledge:begin -->`,f=`<!-- open-knowledge:end -->`,p=/<!-- open-knowledge:begin -->[\s\S]*?<!-- open-knowledge:end -->/,m=`# ${r}/ — Open Knowledge config
|
|
2
2
|
|
|
3
3
|
This directory holds Open Knowledge's configuration for this project. It's **not** where content lives — content lives wherever \`content.dir\` + \`content.include\` in \`config.yml\` point. The default is the repo root with \`**/*.md\`, so any markdown file in the project is fair game. Inspect \`config.yml\` for the actual setting.
|
|
4
4
|
|
|
@@ -227,4 +227,4 @@ ${f}`;function v(e,t,r){let a=[n,...r??[]],u=new Set,d=[];for(let n of a){let r=
|
|
|
227
227
|
|
|
228
228
|
`)?`
|
|
229
229
|
`:``}${n}`,`utf-8`),`appended`)}const x=[{name:n,content:m},{name:`.gitignore`,content:`${e}/\nserver.lock\nui.lock\nsync-state.json\n`},{name:t,content:h}];function S(t){let n=u(t,r),i=[],o=[];a(n,{recursive:!0}),a(l(n,e),{recursive:!0});for(let e of x)y(l(n,e.name),e.content)?i.push(e.name):o.push(e.name);return{created:i,skipped:o}}export{b as a,g as i,d as n,S as o,f as r,v as s,_ as t};
|
|
230
|
-
//# sourceMappingURL=init-
|
|
230
|
+
//# sourceMappingURL=init-Dp5IRYFc.mjs.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function e(e){let t=e.scheduler??{setTimeout:(e,t)=>globalThis.setTimeout(e,t),clearTimeout:e=>globalThis.clearTimeout(e)},n=e.initialBackoffMs??1e3,r=e.maxBackoffMs??3e4,i=e.createWebSocket??(e=>new WebSocket(e)),a=e.logger??null,o=e.log,s=null,c=null,l=!1,u=n;function d(e,t,n){try{a?a[e](t,n):o?.(t)}catch{}}function f(){if(l)return;c!==null&&t.clearTimeout(c);let e=u;u=Math.min(u*2,r),d(`debug`,`scheduling reconnect`,{backoffMs:e}),c=t.setTimeout(()=>{c=null,p().catch(e=>d(`warn`,`reconnect failed`,{error:String(e)}))},e)}async function p(){if(l)return;let t;try{t=await e.resolveWsUrl()}catch(e){d(`warn`,`resolveWsUrl threw`,{error:String(e)}),f();return}if(!t){f();return}let r=e.connectionId?`&connectionId=${encodeURIComponent(e.connectionId)}`:``,a=`${t}/collab/keepalive?pid=${process.pid}${r}`;try{s=i(a)}catch(e){d(`warn`,`WebSocket constructor failed`,{url:a,error:String(e)}),s=null,f();return}s.addEventListener(`open`,()=>{d(`info`,`connected`,{url:t}),u=n}),s.addEventListener(`close`,()=>{l||(d(`info`,`disconnected`,{url:t}),s=null,f())}),s.addEventListener(`error`,()=>{d(`debug`,`websocket error observed`,{url:t,readyState:s?.readyState,reason:`error-event`})})}return queueMicrotask(()=>{p().catch(e=>d(`warn`,`initial connect failed`,{error:String(e)}))}),{close:()=>{if(!l&&(l=!0,c!==null&&(t.clearTimeout(c),c=null),s)){try{s.close()}catch{}s=null}},isConnected:()=>s!==null&&s.readyState===1}}export{e as startKeepalive};
|
|
2
|
+
//# sourceMappingURL=keepalive-BtwYrChs.mjs.map
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{r as e}from"./constants-
|
|
1
|
+
import{r as e}from"./constants-Z_elUeaU.mjs";import{O as t}from"./src-CQw-HNNM.mjs";import{t as n}from"./is-object-B4GcZahG.mjs";import{existsSync as r,readFileSync as i}from"node:fs";import{homedir as a}from"node:os";import{resolve as o}from"node:path";import{parse as s}from"yaml";import{realpath as c}from"node:fs/promises";import{z as l}from"zod";async function u(e){let t=o(e);try{return await c(t)}catch(e){if(e.code!==`ENOENT`){let n=e instanceof Error?e.message:String(e);console.warn(`[normalize-cwd] realpath failed for ${t}: ${n}`)}return t}}const d=l.object({title:l.string().optional(),description:l.string().optional(),tags:l.array(l.string()).optional()}).strict(),f=l.object({match:l.string().min(1,"`match` must be a non-empty glob pattern (e.g. 'specs/**' or 'reports/*/**')"),frontmatter:d}).strict(),p=l.object({content:l.object({dir:l.string().default(`.`),include:l.array(l.string()).min(1).default([`**/*.md`,`**/*.mdx`]),exclude:l.array(l.string()).default([])}).default({dir:`.`,include:[`**/*.md`,`**/*.mdx`],exclude:[]}),github:l.object({oauthAppClientId:l.string().default(`Ov23liqlSd0V1MwR6rhI`)}).default({oauthAppClientId:`Ov23liqlSd0V1MwR6rhI`}),sync:l.object({enabled:l.boolean().optional(),pushIntervalSeconds:l.number().int().min(1).default(60),pullIntervalSeconds:l.number().int().min(1).default(30),autoCommit:l.boolean().default(!0),autoPush:l.boolean().default(!0),autoPull:l.boolean().default(!0),commitMessage:l.string().default(`auto`)}).default({pushIntervalSeconds:60,pullIntervalSeconds:30,autoCommit:!0,autoPush:!0,autoPull:!0,commitMessage:`auto`}),server:l.object({port:l.number().int().min(0).max(65535).default(0),host:l.string().regex(/^[\w.\-:]+$/,`Invalid hostname`).default(`localhost`),openOnAgentEdit:l.boolean().default(!1)}).default({port:0,host:`localhost`,openOnAgentEdit:!1}),persistence:l.object({debounceMs:l.number().int().min(0).default(2e3),maxDebounceMs:l.number().int().min(0).default(1e4)}).default({debounceMs:2e3,maxDebounceMs:1e4}),preview:l.object({baseUrl:l.url().optional()}).default({}),folders:l.array(f).default([]),mcp:l.object({autoStart:l.boolean().default(!0),tools:l.object({read_document:l.object({historyDepth:l.number().int().min(0).default(5)}).default({historyDepth:5}),search:l.object({maxResults:l.number().int().min(1).default(50)}).default({maxResults:50})}).default({read_document:{historyDepth:5},search:{maxResults:50}})}).default({autoStart:!0,tools:{read_document:{historyDepth:5},search:{maxResults:50}}})});function m(e,t){let r={...e};for(let i of Object.keys(t)){let a=e[i],o=t[i];n(o)&&n(a)?r[i]=m(a,o):o!==void 0&&(r[i]=o)}return r}function h(e){if(!r(e))return null;try{let t=s(i(e,`utf-8`));return n(t)?t:null}catch(t){return console.warn(`[config] Failed to parse ${e}: ${t instanceof Error?t.message:t}`),null}}function g(n){let r=n??process.cwd(),i=[],s=o(a(),t,e),c={},l=h(s);l&&(c=m(c,l),i.push(s));let u=o(r,t,e),d=h(u);d&&(c=m(c,d),i.push(u));let f=p.safeParse(c);if(!f.success){let e=f.error.issues.map(e=>` ${e.path.join(`.`)}: ${e.message}`);throw Error(`Invalid configuration:\n${e.join(`
|
|
2
2
|
`)}`)}return{config:f.data,sources:i}}function _(e,t=process.env){let n=e;return t.PORT&&(n={...n,server:{...n.server,port:Number(t.PORT)}}),t.HOST&&(n={...n,server:{...n.server,host:t.HOST}}),n}function v(e){let t=e.env??process.env,n=e.cacheMs??1e3,r=e.loadConfigFn??g,i=new Map,a=new Map,o=u(e.startupCwd);return async s=>{let c=await u(s??e.startupCwd),l=Date.now(),d=i.get(c);if(d&&d.expiresAt>l)return d.config;let f=a.get(c);if(f)return await f;let p=(async()=>{if(c===await o){let r=_(e.startupConfig,t);return i.set(c,{config:r,expiresAt:Date.now()+n}),r}let a=_(r(c).config,t);return i.set(c,{config:a,expiresAt:Date.now()+n}),a})();a.set(c,p);try{return await p}finally{a.delete(c)}}}export{u as i,g as n,p as r,v as t};
|
|
3
|
-
//# sourceMappingURL=loader-
|
|
3
|
+
//# sourceMappingURL=loader-DFXyUwo3.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as e}from"./loader-DFXyUwo3.mjs";export{e as loadConfig};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as e,t}from"./paths-CK0cj4J4.mjs";export{t as resolveContentDir,e as resolveLockDir};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as e,t}from"./preview-D2Qyb0bk.mjs";export{t as formatPreviewBlock,e as previewContent};
|