@mrdrmccoy/xerj-opencode-plugin 0.4.3 → 0.4.5
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/tui-jsx.js +2 -0
- package/dist/tui.js +1 -1
- package/package.json +2 -2
- package/src/index/watcher.ts +20 -1
- package/src/index.ts +13 -4
- package/src/paths.ts +16 -1
- package/src/rpc.ts +1 -1
- package/src/tui-jsx.ts +32 -0
package/dist/tui-jsx.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var t="opentui:runtime-module:"+encodeURIComponent("@opentui/solid/jsx-runtime");async function e(){try{return await import(t)}catch{return await import("@opentui/solid/jsx-runtime")}}var n=await e(),{jsx:o,jsxs:s,jsxDEV:r,Fragment:a}=n;export{a as Fragment,o as jsx,r as jsxDEV,s as jsxs};
|
package/dist/tui.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import{Plugin as W}from"@opencode/plugin/tui";import{Rpc as O}from"@opencode/plugin/rpc";function c(e){return{type:"object",properties:e,required:Object.keys(e),additionalProperties:!1}}function y(e){return c(e)}function V(e){return c(e)}var H={id:"xerj",methods:{status:{input:y({}),output:y({pluginVersion:{type:"string"},updatedAt:{type:"number"},node:c({phase:{type:"string",enum:["starting","ready","error","disabled"]},baseUrl:{type:["string","null"]},serverVersion:{type:["string","null"]},spawned:{type:"boolean"},error:{type:["string","null"]}}),binary:c({version:{type:["string","null"]},path:{type:["string","null"]},upgraded:{type:"boolean"},target:{type:["string","null"]}}),index:c({prefix:{type:"string"},docs:{type:"number"},bytes:{type:"number"},indices:{type:"number"},state:{type:"string",enum:["disabled","starting","watching","idle","error"]},lastRunAt:{type:["number","null"]},lastGeneration:{type:["number","null"]},lastRecords:{type:["number","null"]},restarts:{type:"number"},lastError:{type:["string","null"]},datasets:{type:"array",items:c({name:{type:"string"},docs:{type:"number"},bytes:{type:"number"}})}}),config:c({indexRoot:{type:"string"},brain:{type:"array",items:{type:"string"}},watch:{type:"boolean"},contextEnabled:{type:"boolean"}}),usage:c({queries:{type:"number"},hits:{type:"number"},hitRate:{type:"number"},bytesReturned:{type:"number"},bytesRead:{type:"number"},baselineBytes:{type:"number"},savedBytes:{type:"number"},tokensSaved:{type:"number"},topFiles:{type:"array",items:c({path:{type:"string"},citations:{type:"number"},bytes:{type:"number"}})}}),memory:c({namespace:{type:"string"},count:{type:"number"}}),brains:{type:"array",items:c({name:{type:"string"},prefix:{type:"string"},docs:{type:"number"},edges:{type:"number"},lastRunAt:{type:["number","null"]},error:{type:["string","null"]}})},counters:c({rpcCalls:{type:"number"},injectedTurns:{type:"number"},nodeSpawns:{type:"number"},http429:{type:"number"},indexRuns:{type:"number"}}),recent:{type:"array",items:{type:"string"}},warnings:{type:"array",items:{type:"string"}},errors:{type:"array",items:{type:"string"}}})},reindex:{input:y({}),output:y({queued:{type:"boolean"}})},remember:{input:y({text:{type:"string"}}),output:y({id:{type:"string"},created:{type:"boolean"}})},search:{input:y({query:{type:"string"}}),output:y({text:{type:"string"},hits:{type:"number"},bytes:{type:"number"}})}},events:{updated:{schema:V({updatedAt:{type:"number"}})}}},B=O.define(H);var N=["B","KB","MB","GB","TB"];function p(e){if(!Number.isFinite(e)||e<=0)return"0 B";let n=e,t=0;while(n>=1024&&t<N.length-1)n/=1024,t+=1;let l=t===0||n>=100?0:1;return`${n.toFixed(l)} ${N[t]}`}function d(e){if(!Number.isFinite(e))return"0";return Math.round(e).toLocaleString("en-US")}function k(e){if(!Number.isFinite(e)||e<=0)return"~0";if(e<1000)return`~${Math.round(e)}`;if(e<1e6)return`~${(e/1000).toFixed(1)}K`;return`~${(e/1e6).toFixed(1)}M`}function T(e){if(!Number.isFinite(e)||e<=0)return"0%";return`${Math.round(e*100)}%`}function v(e,n){if(n===null||!Number.isFinite(n))return"never";let t=Math.max(0,Math.round((e-n)/1000));if(t<10)return"just now";if(t<60)return`${t}s ago`;let l=Math.floor(t/60);if(l<60)return`${l}m ago`;let i=Math.floor(l/60);if(i<24)return`${i}h ago`;return`${Math.floor(i/24)}d ago`}function C(e,n){let{index:t,usage:l}=e,i=t.lastRecords===null?"no runs":`${d(t.lastRecords)} records`;return[`XERJ ${_(e)} ${G(e)}`,[`q ${d(l.queries)}`,`hit ${T(l.hitRate)}`,`${k(l.tokensSaved)} tok saved`].join(" "),`idx ${v(n,t.lastRunAt)} \xB7 ${i}`,"\u25B8 /xerj for details"]}function E(e,n){let{node:t,binary:l,index:i,memory:b,usage:u}=e;return[{title:"Node",lines:[[t.phase,t.spawned?"spawned":"attached",t.baseUrl??"no address"].join(" \xB7 "),t.serverVersion===null?"server unknown":`server ${t.serverVersion}`]},{title:"Binary",lines:[`${l.version??"not installed"}${K(l.target)}`,l.path??"managed download"]},{title:"Index",lines:[`${i.state} \xB7 ${i.prefix}`,[`${d(i.docs)} docs`,p(i.bytes),`${i.indices} ${w(i.indices,"index","indices")}`].join(" \xB7 "),[`last run ${v(n,i.lastRunAt)}`,`generation ${i.lastGeneration??"\u2014"}`,`${i.lastRecords??0} records`].join(" \xB7 ")]},{title:"Datasets",lines:i.datasets.length===0?["none yet"]:i.datasets.map((o)=>`${o.name} ${d(o.docs)} docs \xB7 `+`${p(o.bytes)}`)},...e.brains.length===0?[]:[{title:"Brains",lines:e.brains.map((o)=>[`${o.name} ${d(o.docs)} docs`,`${o.edges} ${w(o.edges,"edge","edges")}`,v(n,o.lastRunAt),o.error??""].filter((g)=>g.length>0).join(" \xB7 "))}],{title:"Memory",lines:[[`${d(b.count)} ${w(b.count,"entry","entries")}`,b.namespace].join(" \xB7 ")]},{title:"Usage",lines:[[`${d(u.queries)} queries`,`${T(u.hitRate)} hit`,`${d(u.hits)} hits`].join(" \xB7 "),[`${k(u.tokensSaved)} tokens saved`,`${p(u.savedBytes)} of ${p(u.baselineBytes)} baseline`].join(" \xB7 "),`${p(u.bytesReturned)} returned \xB7 ${p(u.bytesRead)} read`]},{title:"Counters",lines:[`${d(e.counters.indexRuns)} index runs \xB7 `+`${d(e.counters.injectedTurns)} injections`,`${d(e.counters.nodeSpawns)} spawns \xB7 `+`${d(e.counters.http429)} rate-limited \xB7 `+`${d(e.counters.rpcCalls)} rpc calls`]},{title:"Top cited",lines:u.topFiles.length===0?["none yet"]:u.topFiles.map((o)=>`${o.path} \xD7${o.citations} \xB7 ${p(o.bytes)}`)},{title:"Health",lines:I(e)}]}function I(e){let n=[...e.warnings.map((t)=>`warn: ${t}`),...e.recent,...e.errors.map((t)=>`error: ${t}`)];return n.length===0?["no warnings"]:n.slice(-6)}function _(e){return e.node.phase==="ready"?"\u25CF":"\u25CB"}function G(e){let n=e.index;if(e.node.phase!=="ready")return e.node.phase==="starting"?"starting":e.node.phase;if(n.docs===0)return"ready \xB7 not indexed yet";return`ready \xB7 ${d(n.docs)} docs \xB7 ${p(n.bytes)}`}function K(e){return e===null?"":` \xB7 ${e}`}function w(e,n,t){return e===1?n:t}import{jsx as m,jsxs as R}from"@opentui/solid/jsx-runtime";var S="xerj.stats",Q=30000,P=6000,oe=W.define({id:"xerj.tui",setup(e){let n=()=>e.client.rpc(B),[t,l]=e.storage.memory("status",{initial:{lines:["XERJ \u25CB starting"],ready:!1,snapshot:null,reason:null}}),i=()=>e.location?{location:e.location}:void 0,b=new AbortController,u=!1,o=!1,g=null,U=()=>{if(o||g)return;g=Y(n(),()=>void h(),b.signal)},j=()=>{g?.(),g=null},h=async()=>{if(o||u)return;u=!0;try{let r=await n().status({},i());l((s)=>{s.snapshot=r,s.lines=C(r,Date.now()),s.ready=r.node.phase==="ready",s.reason=null}),U()}catch(r){let s=Z(r);j(),l((a)=>{a.snapshot=null,a.reason=s,a.lines=["XERJ \u25CB offline",s],a.ready=!1})}finally{u=!1}},F=async()=>{if(await h(),e.ui.panel.current()?.name===S){e.ui.panel.close();return}if(e.ui.panel.open(S))return;await e.ui.dialog.alert({title:"XERJ",message:z(t.snapshot,t.lines)})},A=async()=>{try{let r=await n().reindex({},i());e.ui.toast.show({message:r.queued?"XERJ reindex queued":"XERJ indexing is disabled",variant:r.queued?"success":"info"})}catch{e.ui.toast.show({message:"XERJ is not available",variant:"error"})}},X=async(r,s)=>(await e.ui.dialog.prompt({title:r,description:s}))?.trim()??"",J=async(r)=>{let s=r?.trim()||await X("Remember in XERJ","Durable fact or decision");if(!s)return;try{let a=await n().remember({text:s},i());e.ui.toast.show({message:a.created?`Remembered as ${a.id}`:`Already remembered as ${a.id}`,variant:a.created?"success":"info"})}catch{e.ui.toast.show({message:"XERJ memory is not available",variant:"error"})}},D=async(r)=>{let s=r?.trim()||await X("Search XERJ","What to look for");if(!s)return;try{let a=await n().search({query:s},i());await e.ui.dialog.alert({title:["XERJ search",`${a.hits} ${a.hits===1?"hit":"hits"}`].join(" \xB7 "),message:a.hits===0?"No passages matched.":ee(a.text)})}catch{e.ui.toast.show({message:"XERJ search is not available",variant:"error"})}};h();let M=setInterval(()=>void h(),Q);e.keymap.layer(()=>({mode:"global",commands:[{id:"xerj.status",title:"XERJ status",description:"Open the XERJ details panel",group:"XERJ",palette:!0,slash:{name:"xerj",arguments:!0},run:async(r)=>{let s=(r??"").trim();if(s.length===0||s==="status")return F();let[a,...f]=s.split(/\s+/),x=f.join(" ").trim();if(a==="reindex")return A();if(a==="remember")return J(x);if(a==="search")return D(x);e.ui.toast.show({message:`Unknown XERJ command: ${a}`,variant:"warning"})}},{id:"xerj.close",title:"Close XERJ panel",description:"Close the XERJ details panel",group:"XERJ",bind:"escape",enabled:()=>e.ui.panel.current()?.name===S,run:()=>e.ui.panel.close()},{id:"xerj.reindex",title:"XERJ reindex",description:"Queue an incremental reindex of this project",group:"XERJ",palette:!0,slash:{name:"xerj-reindex"},enabled:()=>t.ready,run:()=>A()},{id:"xerj.remember",title:"XERJ remember",description:"Store a durable project fact in XERJ memory",group:"XERJ",palette:!0,slash:{name:"xerj-remember",arguments:!0},enabled:()=>t.ready,run:(r)=>J(r)},{id:"xerj.search",title:"XERJ search",description:"Search the project index and show cited passages",group:"XERJ",palette:!0,slash:{name:"xerj-search",arguments:!0},enabled:()=>t.ready,run:(r)=>D(r)}]}));let q=e.ui.slot({append:"sidebar.content",render:()=>m("box",{flexDirection:"column",children:t.lines.map((r)=>{let s=r.startsWith("XERJ")?e.theme.text.base:e.theme.text.muted;return m("text",{fg:s,children:r})})})}),L=e.ui.slot({append:"session.panel",render:(r)=>{if(r.name!==S)return null;let s=t.snapshot,a=s?E(s,Date.now()):[];return R("box",{flexDirection:"column",children:[R("scrollbox",{flexDirection:"column",children:[a.map((f)=>R("box",{flexDirection:"column",marginBottom:1,children:[m("text",{fg:e.theme.text.base,children:f.title}),f.lines.map((x)=>m("text",{fg:e.theme.text.muted,children:x}))]})),a.length===0?m("text",{fg:e.theme.text.faint,children:t.reason??"waiting for the XERJ server part"}):null]}),R("box",{flexDirection:"row",justifyContent:"space-between",marginTop:1,children:[m("text",{fg:e.theme.text.faint,children:"esc or /xerj closes"}),m("box",{border:!0,borderColor:e.theme.border.base,paddingLeft:1,paddingRight:1,onMouseDown:()=>r.close(),children:m("text",{fg:e.theme.text.base,children:"\u2715 close"})})]})]})}});return()=>{o=!0,b.abort(),clearInterval(M),j(),L(),q()}}});function Y(e,n,t){try{return e.events?.on?.("updated",n,{signal:t})??(()=>{})}catch{return()=>{}}}function z(e,n){if(!e)return n.join(`
|
|
2
|
+
import{Plugin as W}from"@opencode/plugin/tui";import{Rpc as O}from"@opencode/plugin/rpc";function c(e){return{type:"object",properties:e,required:Object.keys(e),additionalProperties:!1}}function y(e){return c(e)}function V(e){return c(e)}var H={id:"xerj",methods:{status:{input:y({}),output:y({pluginVersion:{type:"string"},updatedAt:{type:"number"},node:c({phase:{type:"string",enum:["starting","ready","error","disabled"]},baseUrl:{type:["string","null"]},serverVersion:{type:["string","null"]},spawned:{type:"boolean"},error:{type:["string","null"]}}),binary:c({version:{type:["string","null"]},path:{type:["string","null"]},upgraded:{type:"boolean"},target:{type:["string","null"]}}),index:c({prefix:{type:"string"},docs:{type:"number"},bytes:{type:"number"},indices:{type:"number"},state:{type:"string",enum:["disabled","starting","watching","idle","error"]},lastRunAt:{type:["number","null"]},lastGeneration:{type:["number","null"]},lastRecords:{type:["number","null"]},restarts:{type:"number"},lastError:{type:["string","null"]},datasets:{type:"array",items:c({name:{type:"string"},docs:{type:"number"},bytes:{type:"number"}})}}),config:c({indexRoot:{type:"string"},brain:{type:"array",items:{type:"string"}},watch:{type:"boolean"},contextEnabled:{type:"boolean"}}),usage:c({queries:{type:"number"},hits:{type:"number"},hitRate:{type:"number"},bytesReturned:{type:"number"},bytesRead:{type:"number"},baselineBytes:{type:"number"},savedBytes:{type:"number"},tokensSaved:{type:"number"},topFiles:{type:"array",items:c({path:{type:"string"},citations:{type:"number"},bytes:{type:"number"}})}}),memory:c({namespace:{type:"string"},count:{type:"number"}}),brains:{type:"array",items:c({name:{type:"string"},prefix:{type:"string"},docs:{type:"number"},edges:{type:"number"},lastRunAt:{type:["number","null"]},error:{type:["string","null"]}})},counters:c({rpcCalls:{type:"number"},injectedTurns:{type:"number"},nodeSpawns:{type:"number"},http429:{type:"number"},indexRuns:{type:"number"}}),recent:{type:"array",items:{type:"string"}},warnings:{type:"array",items:{type:"string"}},errors:{type:"array",items:{type:"string"}}})},reindex:{input:y({}),output:y({queued:{type:"boolean"}})},remember:{input:y({text:{type:"string"}}),output:y({id:{type:"string"},created:{type:"boolean"}})},search:{input:y({query:{type:"string"}}),output:y({text:{type:"string"},hits:{type:"number"},bytes:{type:"number"}})}},events:{updated:{schema:V({updatedAt:{type:"number"}})}}},B=O.define(H);var N=["B","KB","MB","GB","TB"];function p(e){if(!Number.isFinite(e)||e<=0)return"0 B";let n=e,t=0;while(n>=1024&&t<N.length-1)n/=1024,t+=1;let l=t===0||n>=100?0:1;return`${n.toFixed(l)} ${N[t]}`}function d(e){if(!Number.isFinite(e))return"0";return Math.round(e).toLocaleString("en-US")}function k(e){if(!Number.isFinite(e)||e<=0)return"~0";if(e<1000)return`~${Math.round(e)}`;if(e<1e6)return`~${(e/1000).toFixed(1)}K`;return`~${(e/1e6).toFixed(1)}M`}function T(e){if(!Number.isFinite(e)||e<=0)return"0%";return`${Math.round(e*100)}%`}function v(e,n){if(n===null||!Number.isFinite(n))return"never";let t=Math.max(0,Math.round((e-n)/1000));if(t<10)return"just now";if(t<60)return`${t}s ago`;let l=Math.floor(t/60);if(l<60)return`${l}m ago`;let i=Math.floor(l/60);if(i<24)return`${i}h ago`;return`${Math.floor(i/24)}d ago`}function C(e,n){let{index:t,usage:l}=e,i=t.lastRecords===null?"no runs":`${d(t.lastRecords)} records`;return[`XERJ ${_(e)} ${G(e)}`,[`q ${d(l.queries)}`,`hit ${T(l.hitRate)}`,`${k(l.tokensSaved)} tok saved`].join(" "),`idx ${v(n,t.lastRunAt)} \xB7 ${i}`,"\u25B8 /xerj for details"]}function E(e,n){let{node:t,binary:l,index:i,memory:b,usage:u}=e;return[{title:"Node",lines:[[t.phase,t.spawned?"spawned":"attached",t.baseUrl??"no address"].join(" \xB7 "),t.serverVersion===null?"server unknown":`server ${t.serverVersion}`]},{title:"Binary",lines:[`${l.version??"not installed"}${K(l.target)}`,l.path??"managed download"]},{title:"Index",lines:[`${i.state} \xB7 ${i.prefix}`,[`${d(i.docs)} docs`,p(i.bytes),`${i.indices} ${w(i.indices,"index","indices")}`].join(" \xB7 "),[`last run ${v(n,i.lastRunAt)}`,`generation ${i.lastGeneration??"\u2014"}`,`${i.lastRecords??0} records`].join(" \xB7 ")]},{title:"Datasets",lines:i.datasets.length===0?["none yet"]:i.datasets.map((o)=>`${o.name} ${d(o.docs)} docs \xB7 `+`${p(o.bytes)}`)},...e.brains.length===0?[]:[{title:"Brains",lines:e.brains.map((o)=>[`${o.name} ${d(o.docs)} docs`,`${o.edges} ${w(o.edges,"edge","edges")}`,v(n,o.lastRunAt),o.error??""].filter((g)=>g.length>0).join(" \xB7 "))}],{title:"Memory",lines:[[`${d(b.count)} ${w(b.count,"entry","entries")}`,b.namespace].join(" \xB7 ")]},{title:"Usage",lines:[[`${d(u.queries)} queries`,`${T(u.hitRate)} hit`,`${d(u.hits)} hits`].join(" \xB7 "),[`${k(u.tokensSaved)} tokens saved`,`${p(u.savedBytes)} of ${p(u.baselineBytes)} baseline`].join(" \xB7 "),`${p(u.bytesReturned)} returned \xB7 ${p(u.bytesRead)} read`]},{title:"Counters",lines:[`${d(e.counters.indexRuns)} index runs \xB7 `+`${d(e.counters.injectedTurns)} injections`,`${d(e.counters.nodeSpawns)} spawns \xB7 `+`${d(e.counters.http429)} rate-limited \xB7 `+`${d(e.counters.rpcCalls)} rpc calls`]},{title:"Top cited",lines:u.topFiles.length===0?["none yet"]:u.topFiles.map((o)=>`${o.path} \xD7${o.citations} \xB7 ${p(o.bytes)}`)},{title:"Health",lines:I(e)}]}function I(e){let n=[...e.warnings.map((t)=>`warn: ${t}`),...e.recent,...e.errors.map((t)=>`error: ${t}`)];return n.length===0?["no warnings"]:n.slice(-6)}function _(e){return e.node.phase==="ready"?"\u25CF":"\u25CB"}function G(e){let n=e.index;if(e.node.phase!=="ready")return e.node.phase==="starting"?"starting":e.node.phase;if(n.docs===0)return"ready \xB7 not indexed yet";return`ready \xB7 ${d(n.docs)} docs \xB7 ${p(n.bytes)}`}function K(e){return e===null?"":` \xB7 ${e}`}function w(e,n,t){return e===1?n:t}import{jsx as m,jsxs as R}from"./tui-jsx.js";var S="xerj.stats",Q=30000,P=6000,oe=W.define({id:"xerj.tui",setup(e){let n=()=>e.client.rpc(B),[t,l]=e.storage.memory("status",{initial:{lines:["XERJ \u25CB starting"],ready:!1,snapshot:null,reason:null}}),i=()=>e.location?{location:e.location}:void 0,b=new AbortController,u=!1,o=!1,g=null,U=()=>{if(o||g)return;g=Y(n(),()=>void h(),b.signal)},j=()=>{g?.(),g=null},h=async()=>{if(o||u)return;u=!0;try{let r=await n().status({},i());l((s)=>{s.snapshot=r,s.lines=C(r,Date.now()),s.ready=r.node.phase==="ready",s.reason=null}),U()}catch(r){let s=Z(r);j(),l((a)=>{a.snapshot=null,a.reason=s,a.lines=["XERJ \u25CB offline",s],a.ready=!1})}finally{u=!1}},F=async()=>{if(await h(),e.ui.panel.current()?.name===S){e.ui.panel.close();return}if(e.ui.panel.open(S))return;await e.ui.dialog.alert({title:"XERJ",message:z(t.snapshot,t.lines)})},A=async()=>{try{let r=await n().reindex({},i());e.ui.toast.show({message:r.queued?"XERJ reindex queued":"XERJ indexing is disabled",variant:r.queued?"success":"info"})}catch{e.ui.toast.show({message:"XERJ is not available",variant:"error"})}},X=async(r,s)=>(await e.ui.dialog.prompt({title:r,description:s}))?.trim()??"",J=async(r)=>{let s=r?.trim()||await X("Remember in XERJ","Durable fact or decision");if(!s)return;try{let a=await n().remember({text:s},i());e.ui.toast.show({message:a.created?`Remembered as ${a.id}`:`Already remembered as ${a.id}`,variant:a.created?"success":"info"})}catch{e.ui.toast.show({message:"XERJ memory is not available",variant:"error"})}},D=async(r)=>{let s=r?.trim()||await X("Search XERJ","What to look for");if(!s)return;try{let a=await n().search({query:s},i());await e.ui.dialog.alert({title:["XERJ search",`${a.hits} ${a.hits===1?"hit":"hits"}`].join(" \xB7 "),message:a.hits===0?"No passages matched.":ee(a.text)})}catch{e.ui.toast.show({message:"XERJ search is not available",variant:"error"})}};h();let M=setInterval(()=>void h(),Q);e.keymap.layer(()=>({mode:"global",commands:[{id:"xerj.status",title:"XERJ status",description:"Open the XERJ details panel",group:"XERJ",palette:!0,slash:{name:"xerj",arguments:!0},run:async(r)=>{let s=(r??"").trim();if(s.length===0||s==="status")return F();let[a,...f]=s.split(/\s+/),x=f.join(" ").trim();if(a==="reindex")return A();if(a==="remember")return J(x);if(a==="search")return D(x);e.ui.toast.show({message:`Unknown XERJ command: ${a}`,variant:"warning"})}},{id:"xerj.close",title:"Close XERJ panel",description:"Close the XERJ details panel",group:"XERJ",bind:"escape",enabled:()=>e.ui.panel.current()?.name===S,run:()=>e.ui.panel.close()},{id:"xerj.reindex",title:"XERJ reindex",description:"Queue an incremental reindex of this project",group:"XERJ",palette:!0,slash:{name:"xerj-reindex"},enabled:()=>t.ready,run:()=>A()},{id:"xerj.remember",title:"XERJ remember",description:"Store a durable project fact in XERJ memory",group:"XERJ",palette:!0,slash:{name:"xerj-remember",arguments:!0},enabled:()=>t.ready,run:(r)=>J(r)},{id:"xerj.search",title:"XERJ search",description:"Search the project index and show cited passages",group:"XERJ",palette:!0,slash:{name:"xerj-search",arguments:!0},enabled:()=>t.ready,run:(r)=>D(r)}]}));let q=e.ui.slot({append:"sidebar.content",render:()=>m("box",{flexDirection:"column",children:t.lines.map((r)=>{let s=r.startsWith("XERJ")?e.theme.text.base:e.theme.text.muted;return m("text",{fg:s,children:r})})})}),L=e.ui.slot({append:"session.panel",render:(r)=>{if(r.name!==S)return null;let s=t.snapshot,a=s?E(s,Date.now()):[];return R("box",{flexDirection:"column",children:[R("scrollbox",{flexDirection:"column",children:[a.map((f)=>R("box",{flexDirection:"column",marginBottom:1,children:[m("text",{fg:e.theme.text.base,children:f.title}),f.lines.map((x)=>m("text",{fg:e.theme.text.muted,children:x}))]})),a.length===0?m("text",{fg:e.theme.text.faint,children:t.reason??"waiting for the XERJ server part"}):null]}),R("box",{flexDirection:"row",justifyContent:"space-between",marginTop:1,children:[m("text",{fg:e.theme.text.faint,children:"esc or /xerj closes"}),m("box",{border:!0,borderColor:e.theme.border.base,paddingLeft:1,paddingRight:1,onMouseDown:()=>r.close(),children:m("text",{fg:e.theme.text.base,children:"\u2715 close"})})]})]})}});return()=>{o=!0,b.abort(),clearInterval(M),j(),L(),q()}}});function Y(e,n,t){try{return e.events?.on?.("updated",n,{signal:t})??(()=>{})}catch{return()=>{}}}function z(e,n){if(!e)return n.join(`
|
|
3
3
|
`);return E(e,Date.now()).flatMap((t)=>[t.title,...t.lines]).join(`
|
|
4
4
|
`)}function Z(e){let t=(e instanceof Error?e.message:String(e)).split(`
|
|
5
5
|
`)[0]?.trim();if(!t)return"the XERJ server part did not answer";return t.length>72?`${t.slice(0,71)}\u2026`:t}function ee(e){if(e.length<=P)return e;return`${e.slice(0,P)}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrdrmccoy/xerj-opencode-plugin",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "XERJ integration for OpenCode: local code and docs search, agent memory, and retrieval accounting.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"test": "bun test test/unit",
|
|
36
36
|
"test:unit": "bun test test/unit",
|
|
37
37
|
"test:integration": "bun test test/integration",
|
|
38
|
-
"build:tui": "bun build
|
|
38
|
+
"build:tui": "bun scripts/build-tui.ts",
|
|
39
39
|
"prepack": "bun run build:tui"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
package/src/index/watcher.ts
CHANGED
|
@@ -47,6 +47,9 @@ const BACKOFF_MS = [1_000, 2_000, 5_000, 15_000, 30_000] as const;
|
|
|
47
47
|
const MAX_RESTARTS = 5;
|
|
48
48
|
const STOP_GRACE_MS = 3_000;
|
|
49
49
|
const STDERR_TAIL_LINES = 20;
|
|
50
|
+
// The engine refuses a second watcher for the same journal and says so.
|
|
51
|
+
const CONTENTION_MARKER = "already owns";
|
|
52
|
+
const CONTENTION_RETRY_MS = 5_000;
|
|
50
53
|
|
|
51
54
|
export class CorpusService {
|
|
52
55
|
readonly layout: CorpusLayout;
|
|
@@ -62,6 +65,7 @@ export class CorpusService {
|
|
|
62
65
|
private timer: ReturnType<typeof setInterval> | null = null;
|
|
63
66
|
private stopped = false;
|
|
64
67
|
private spawning = false;
|
|
68
|
+
private lastStderr = "";
|
|
65
69
|
|
|
66
70
|
constructor(private readonly options: CorpusServiceOptions) {
|
|
67
71
|
this.layout = corpusLayout(options.projectRoot, options.prefix, options.dataRoot);
|
|
@@ -160,6 +164,7 @@ export class CorpusService {
|
|
|
160
164
|
|
|
161
165
|
this.child = child;
|
|
162
166
|
this.spawning = false;
|
|
167
|
+
this.lastStderr = "";
|
|
163
168
|
if (watch) this.state = "starting";
|
|
164
169
|
|
|
165
170
|
this.attachOutput(child);
|
|
@@ -204,8 +209,9 @@ export class CorpusService {
|
|
|
204
209
|
});
|
|
205
210
|
|
|
206
211
|
child.once("close", () => {
|
|
212
|
+
this.lastStderr = stderr.join("").slice(-2000);
|
|
207
213
|
if (stderr.length > 0) {
|
|
208
|
-
this.options.logger.debug("autoindex stderr",
|
|
214
|
+
this.options.logger.debug("autoindex stderr", this.lastStderr);
|
|
209
215
|
}
|
|
210
216
|
});
|
|
211
217
|
}
|
|
@@ -213,6 +219,19 @@ export class CorpusService {
|
|
|
213
219
|
private handleWatchExit(code: number | null, signal: NodeJS.Signals | null): void {
|
|
214
220
|
const meaning = exitMeaning(code);
|
|
215
221
|
|
|
222
|
+
// Another process owns this journal, typically the plugin instance of a
|
|
223
|
+
// server that is being replaced. Ownership frees when that process exits,
|
|
224
|
+
// so wait it out instead of counting a failure or burning the restart
|
|
225
|
+
// budget.
|
|
226
|
+
if (this.lastStderr.includes(CONTENTION_MARKER)) {
|
|
227
|
+
this.state = "starting";
|
|
228
|
+
this.lastError = null;
|
|
229
|
+
this.options.logger.debug("autoindex watcher journal is owned elsewhere; retrying");
|
|
230
|
+
this.options.onChange?.();
|
|
231
|
+
setTimeout(() => this.spawn(true), CONTENTION_RETRY_MS);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
216
235
|
// A clean exit from `--watch` is unexpected; it should stay resident.
|
|
217
236
|
if (meaning === "decision") {
|
|
218
237
|
this.state = "idle";
|
package/src/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
environmentFrom,
|
|
28
28
|
externalPrefix,
|
|
29
29
|
indexPrefix,
|
|
30
|
+
isOverbroadRoot,
|
|
30
31
|
memoryNamespace,
|
|
31
32
|
projectStateDir,
|
|
32
33
|
webPrefix,
|
|
@@ -81,6 +82,14 @@ export default Plugin.define({
|
|
|
81
82
|
// The location may be a subdirectory of the project; index the project root
|
|
82
83
|
// so a session opened in `docs/` does not create a second corpus.
|
|
83
84
|
const projectRoot = ctx.location.project.canonical;
|
|
85
|
+
const overbroad = isOverbroadRoot(projectRoot);
|
|
86
|
+
const indexWarnings = [...warnings];
|
|
87
|
+
if (overbroad) {
|
|
88
|
+
const refusal =
|
|
89
|
+
`refusing to index ${projectRoot}: the home directory and filesystem roots are too broad. Open OpenCode inside a project.`;
|
|
90
|
+
indexWarnings.push(refusal);
|
|
91
|
+
logger.warn(refusal);
|
|
92
|
+
}
|
|
84
93
|
const prefix = indexPrefix(projectRoot);
|
|
85
94
|
const brains = brainSpecs(projectRoot, config.index.brain);
|
|
86
95
|
const hiddenRoots = documentRoots(projectRoot, config.index.hidden);
|
|
@@ -99,7 +108,7 @@ export default Plugin.define({
|
|
|
99
108
|
hiddenRoots,
|
|
100
109
|
anomaly: null,
|
|
101
110
|
error: null,
|
|
102
|
-
warnings:
|
|
111
|
+
warnings: indexWarnings,
|
|
103
112
|
};
|
|
104
113
|
|
|
105
114
|
const ledger = new Ledger({
|
|
@@ -502,7 +511,7 @@ export default Plugin.define({
|
|
|
502
511
|
.catch(() => {});
|
|
503
512
|
},
|
|
504
513
|
});
|
|
505
|
-
await runtime.corpus.start();
|
|
514
|
+
if (!overbroad) await runtime.corpus.start();
|
|
506
515
|
|
|
507
516
|
if (runtime.hiddenRoots.length > 0) {
|
|
508
517
|
runtime.hidden = new HiddenIndex({
|
|
@@ -521,7 +530,7 @@ export default Plugin.define({
|
|
|
521
530
|
},
|
|
522
531
|
});
|
|
523
532
|
|
|
524
|
-
if (config.index.auto) await runtime.hidden.start();
|
|
533
|
+
if (config.index.auto && !overbroad) await runtime.hidden.start();
|
|
525
534
|
}
|
|
526
535
|
|
|
527
536
|
if (runtime.brains.length > 0) {
|
|
@@ -544,7 +553,7 @@ export default Plugin.define({
|
|
|
544
553
|
|
|
545
554
|
// One-shot: graph indexing cannot run in watch mode. `index.auto`
|
|
546
555
|
// gates startup runs only; an explicit reindex still works.
|
|
547
|
-
if (config.index.auto) void runtime.brain.runAll();
|
|
556
|
+
if (config.index.auto && !overbroad) void runtime.brain.runAll();
|
|
548
557
|
}
|
|
549
558
|
|
|
550
559
|
await registration.events.emit("updated", { updatedAt: Date.now() });
|
package/src/paths.ts
CHANGED
|
@@ -12,7 +12,6 @@ export interface Environment {
|
|
|
12
12
|
readonly HOME?: string;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
/** Picks the variables that matter out of a wider process environment. */
|
|
16
15
|
export function environmentFrom(env: Readonly<Record<string, string | undefined>>): Environment {
|
|
17
16
|
return {
|
|
18
17
|
XDG_DATA_HOME: env.XDG_DATA_HOME,
|
|
@@ -21,6 +20,22 @@ export function environmentFrom(env: Readonly<Record<string, string | undefined>
|
|
|
21
20
|
};
|
|
22
21
|
}
|
|
23
22
|
|
|
23
|
+
/**
|
|
24
|
+
* True when a directory is too broad to index: the home directory itself or a
|
|
25
|
+
* filesystem root. Indexing it walks every cache, download and trash folder and
|
|
26
|
+
* costs minutes of I/O for results nobody asked for.
|
|
27
|
+
*/
|
|
28
|
+
export function isOverbroadRoot(
|
|
29
|
+
directory: string,
|
|
30
|
+
home: string = homedir(),
|
|
31
|
+
platform: NodeJS.Platform = process.platform,
|
|
32
|
+
): boolean {
|
|
33
|
+
const resolved = resolve(directory);
|
|
34
|
+
const root = platform === "win32" ? resolve(resolved, sep) : "/";
|
|
35
|
+
return resolved === root || resolved === resolve(home);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
24
39
|
const APP_DIR = "opencode/xerj";
|
|
25
40
|
const PREFIX_ROOT = "xop";
|
|
26
41
|
const MAX_SLUG_LENGTH = 24;
|
package/src/rpc.ts
CHANGED
package/src/tui-jsx.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// JSX runtime shim for the TUI entry.
|
|
2
|
+
//
|
|
3
|
+
// The host publishes its single OpenTUI/Solid runtime as a virtual module. A
|
|
4
|
+
// plugin that imports the bare package instead loads a second copy whose JSX
|
|
5
|
+
// factory has no renderer, which throws "No renderer found" on first render.
|
|
6
|
+
// Prefer the host runtime; fall back to the local package for a bare Bun run
|
|
7
|
+
// where the virtual registry does not exist.
|
|
8
|
+
|
|
9
|
+
const VIRTUAL_JSX_RUNTIME =
|
|
10
|
+
"opentui:runtime-module:" + encodeURIComponent("@opentui/solid/jsx-runtime");
|
|
11
|
+
|
|
12
|
+
interface JsxRuntime {
|
|
13
|
+
readonly jsx: unknown;
|
|
14
|
+
readonly jsxs: unknown;
|
|
15
|
+
readonly jsxDEV?: unknown;
|
|
16
|
+
readonly Fragment: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function loadRuntime(): Promise<JsxRuntime> {
|
|
20
|
+
try {
|
|
21
|
+
return (await import(VIRTUAL_JSX_RUNTIME)) as JsxRuntime;
|
|
22
|
+
} catch {
|
|
23
|
+
return (await import("@opentui/solid/jsx-runtime")) as JsxRuntime;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const runtime = await loadRuntime();
|
|
28
|
+
|
|
29
|
+
export const jsx = runtime.jsx;
|
|
30
|
+
export const jsxs = runtime.jsxs;
|
|
31
|
+
export const jsxDEV = runtime.jsxDEV;
|
|
32
|
+
export const Fragment = runtime.Fragment;
|