oioxo-mcp 0.5.3 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +46 -14
  2. package/bundle/cli.js +5 -4
  3. package/package.json +8 -3
package/README.md CHANGED
@@ -6,7 +6,7 @@ OIOXO does two things from one CLI:
6
6
  1. **Context engine** — sits between your codebase and your AI agents (GitHub Copilot, Claude Code, Cursor, Windsurf, Gemini CLI, Codex) and hands them the *minimal relevant slice* of your project instead of letting them read whole files.
7
7
  2. **Coding agent** — `oioxo code "<task>"` plans, edits, and verifies in your repo, using your own key **or a local Ollama model** — nothing leaves your machine. See [Code in your terminal](#code-in-your-terminal--oioxo-code).
8
8
 
9
- **Measured on a real production codebase: 90–92% fewer context tokens per question.** A question that costs an agent ~50,000 tokens of file reading comes back as a ~5,000-token capsule — same answer, a fraction of the cost.
9
+ **90–92% fewer context tokens per question, measured on a real production codebase.** A question that costs an agent ~50,000 tokens of file reading comes back as a ~5,000-token capsule — same answer, a fraction of the cost.
10
10
 
11
11
  ## Quick start
12
12
 
@@ -34,16 +34,24 @@ ANTHROPIC_API_KEY=… oioxo code "fix the failing test" --verify "npm test"
34
34
  OIOXO_PROVIDER=ollama OIOXO_MODEL=qwen2.5-coder oioxo code "add input validation to the signup form"
35
35
  ```
36
36
 
37
- - **Verified, not vibes** — every change is run through your tests/typecheck on a shadow copy; a red result is fed back and repaired. Your real files are only touched after you approve the diff.
37
+ - **Verified, not vibes** — every change runs through your tests/typecheck on a shadow copy; a red result is fed back and repaired. Your real files are touched only after you approve the diff.
38
38
  - **Bring any model** — OpenAI, Anthropic, Groq, Mistral, Together, local Ollama, or any OpenAI-compatible base (`OIOXO_PROVIDER` / `OIOXO_BASE_URL` / `OIOXO_MODEL` / `OIOXO_API_KEY`).
39
- - **Or fully local, no key** — point it at [Ollama](https://ollama.com) (`OIOXO_PROVIDER=ollama OIOXO_MODEL=qwen2.5-coder`) and nothing leaves your machine. A built-in `--local` on-device coder (open model via llama.cpp, auto-downloaded once to `~/.oioxo/models`) is **coming next**.
39
+ - **Or fully local, no key** — point it at [Ollama](https://ollama.com) (`OIOXO_PROVIDER=ollama OIOXO_MODEL=qwen2.5-coder`) and nothing leaves your machine. Or run the built-in on-device coder with **`oioxo code --local`** (open model via llama.cpp; one-time `npm i -g node-llama-cpp`) no key, nothing uploaded.
40
40
 
41
- Flags: `--verify "<cmd>"` (override the test command) · `--yes` (apply without the prompt) · `--max-iters N`. *(`--local` is reserved for the built-in on-device coder, coming nextuse `OIOXO_PROVIDER=ollama` for local today.)*
41
+ Flags: `--verify "<cmd>"` (override the test command) · `--yes` (apply without the prompt) · `--max-iters N` · `--local` (built-in on-device coder, no keyone-time `npm i -g node-llama-cpp`).
42
+
43
+ ## Compute Mesh — lend a hand, or borrow one
44
+
45
+ On the same Wi-Fi, your devices work as one engine — and the CLI is a lend-only peer. Add a
46
+ device with `oioxo invite` (or `oioxo join`) and this machine lends its coder to another
47
+ device's build, so your phone or a thin laptop builds on *this* machine's GPU. The handshake
48
+ is a short code — a QR or a string — with no signaling server and nothing relayed through
49
+ OIOXO. Lend compute and you earn free coding time for it.
42
50
 
43
51
  ## Why developers use it
44
52
 
45
- - 💸 **Subscriptions go further** — Copilot premium requests, Claude limits, API keys all burn down slower.
46
- - ⚡ **Better answers** — focused context beats 75k tokens of noise; your agent gets the exact code in play and the parts of the project it actually depends on.
53
+ - 💸 **Subscriptions go further** — Copilot premium requests, Claude limits, and API keys all last longer.
54
+ - ⚡ **Better answers** — focused context beats 75k tokens of noise. Your agent gets the exact code in play, plus the parts of the project it actually depends on.
47
55
  - 🔒 **100% on-device** — your code is indexed and queried locally, never uploaded. Only the saved-token *count* is metered.
48
56
  - 🪄 **One command** — `init` detects Claude Code, VS Code/Copilot, Cursor, Windsurf, Gemini CLI and Codex, and merges their configs without touching your other MCP servers.
49
57
 
@@ -65,16 +73,40 @@ Flags: `--verify "<cmd>"` (override the test command) · `--yes` (apply without
65
73
  ## Commands
66
74
 
67
75
  ```
68
- oioxo-mcp code "<task>" code in this repo: capsule-grounded, verified, then asks
69
- before writing (--verify "<cmd>" · --yes · --max-iters)
70
- oioxo-mcp login connect your OIOXO account
71
- oioxo-mcp init [--all] write agent MCP configs + instruction files
72
- oioxo-mcp serve run the MCP server (agent configs call this)
73
- oioxo-mcp status plan, savings and index stats
74
- oioxo-mcp logout remove the stored credential
76
+ # Code
77
+ oioxo code "<task>" code in this repo: capsule-grounded, verified, then asks
78
+ before writing (--verify "<cmd>" · --yes · --max-iters · --local)
79
+ --local runs the built-in on-device coder, no key
80
+
81
+ # Account
82
+ oioxo login | logout connect / disconnect your OIOXO account
83
+ oioxo whoami | usage who you're signed in as · your saved-token allowance
84
+ oioxo status plan, savings and on-device index stats
85
+
86
+ # Devices (Compute Mesh) — build together on the same Wi-Fi, one account
87
+ oioxo invite show a code; the device that HAS the project scans it,
88
+ and this machine lends its coder to that build
89
+ oioxo join scan/paste the code from the device that has the project,
90
+ and lend this machine's compute to it
91
+ oioxo devices find your other same-account devices on this Wi-Fi
92
+ oioxo stop-helping how to stop lending (Ctrl+C in the helper's terminal)
93
+
94
+ # AI / model
95
+ oioxo model list | use <p> [m] show providers / pin one (and a model)
96
+ oioxo config set|get|list|unset persist provider/model/baseUrl/apiKey
97
+
98
+ # Agents (MCP)
99
+ oioxo init [--all] wire OIOXO into the AI agents in this project
100
+ oioxo mcp list show which agents OIOXO is wired into
101
+ oioxo serve run the MCP server over stdio (agents call this)
102
+
103
+ # Maintenance
104
+ oioxo doctor check creds, connection, coder config, agent wiring
105
+ oioxo update how to update the CLI
106
+ oioxo --version | --help
75
107
  ```
76
108
 
77
- > The binary is published as `oioxo-mcp`; `oioxo code …` and `oioxo-mcp code …` are the same.
109
+ > The binary is published as `oioxo-mcp`; `oioxo …` and `oioxo-mcp …` are the same command.
78
110
 
79
111
  ## Pricing
80
112
 
package/bundle/cli.js CHANGED
@@ -258,7 +258,7 @@ ${a.ok?"\u2713 verified green":"\u26A0 best attempt (did not fully verify)"} \u2
258
258
  OIOXO saved ~${a.savedTokens.toLocaleString()} tokens of context on this task.`),!r.yes&&!await x$(`
259
259
  Apply these changes to your files?`)){console.log("Discarded. Nothing was written.");return}await Mne(t,a.edits),console.log(`
260
260
  \u2713 applied ${a.edits.length} file(s).${a.ok?"":" (unverified \u2014 run your tests)"}`),await ms("report",a.savedTokens).catch(()=>{})}async function Mne(t,e){let r=Zi.resolve(t),n=Zi.join(r,".oioxo","backups"),s=Zi.join(n,jne()),i=0;await dk(n,{recursive:!0}).then(()=>lk(Zi.join(n,".gitignore"),`*
261
- `)).catch(()=>{});for(let o of e){if(!Vc(o.path)){console.error(` \u26A0 skipped unsafe path: ${o.path}`);continue}let a=Zi.resolve(r,o.path);if(a!==r&&!a.startsWith(r+Zi.sep)){console.error(` \u26A0 skipped path escaping repo: ${o.path}`);continue}let c=await b$(a,"utf8").catch(()=>null);if(c!==null){let u=Zi.join(s,o.path);try{await dk(Zi.dirname(u),{recursive:!0}),await lk(u,c,"utf8"),i++}catch{}}await dk(Zi.dirname(a),{recursive:!0}),await lk(a,o.content,"utf8")}if(i){let o=Zi.relative(r,s)||s;console.log(` \u21B6 backed up ${i} overwritten file(s) \u2192 ${o} (restore: copy them back)`)}}function jne(){return new Date().toISOString().replace(/[-:]/g,"").replace(/\.\d+Z$/,"Z")}async function x$(t){if(!process.stdin.isTTY)return console.log(`${t} (no TTY \u2014 pass --yes to apply non-interactively)`),!1;let e=_$({input:process.stdin,output:process.stdout});try{let r=(await e.question(`${t} [y/N] `)).trim().toLowerCase();return r==="y"||r==="yes"}finally{e.close()}}import{createInterface as _ge}from"node:readline";P_();function E$(t,e,r){return JSON.stringify(["hello",t,e,r])}var R_=16*1024,Dne=3e4,C$=1500,Une=4e3;async function hk(t,e){let r=e.now??(()=>Date.now()),n=e.setTimer??((S,C)=>setTimeout(S,C)),s=e.clearTimer??(S=>clearTimeout(S)),i=e.rand??(()=>Math.random().toString(36).slice(2)+r().toString(36)),o=fk(e.keyLookup),a=r(),c=!1,u=new Map,l=new Map,d={},f=null;function p(S){if(c)return;let C=JSON.stringify(S);if(C.length<=R_){t.send(C);return}let E=i(),B=Math.ceil(C.length/R_);for(let G=0;G<B;G++)t.send(JSON.stringify({t:"chunk",id:E,i:G,total:B,part:C.slice(G*R_,(G+1)*R_)}))}let m=i(),y=await e.identity.sign(E$(e.identity.deviceId,t.channelId,m)),_={t:"hello",v:1,profile:e.profile,publicKeyJwk:e.identity.publicKeyJwk,nonce:m,sig:y};return await new Promise((S,C)=>{let E=null,B=null,G=new Set,se=!1;function _e(){if(!se){se=!0,c=!0,B&&s(B);for(let ut of u.values())ut.reject(new Error("link lost"));u.clear();try{t.close()}catch{}for(let ut of G)try{ut()}catch{}}}function he(ut){if(!c){c=!0,B&&s(B);for(let ze of u.values())ze.reject(new Error("link closed"));u.clear(),t.close(),C(new Error(ut))}}function Y(ut,ze){return{peerId:ut,profile:ze,idleMs:()=>r()-a,sendProject:$=>p({t:"project",v:1,code:$}),onProject:$=>{f=$},serve:$=>{d=$},onLost:$=>{se?$():G.add($)},call:(($,U)=>new Promise((be,ke)=>{let Le=i(),k=n(()=>{u.delete(Le),ke(new Error(`rpc ${$} timed out`))},Dne);u.set(Le,{resolve:P=>{s(k),be(P)},reject:P=>{s(k),ke(P)}}),p({t:"rpc",v:1,id:Le,method:$,arg:U})})),close:()=>{_e()}}}async function ct(ut){a=r();let ze;try{ze=JSON.parse(ut)}catch{return}if(ze.t==="chunk"){let $=ze,U=l.get($.id)??{total:$.total,parts:new Array($.total).fill("")};U.parts[$.i]=$.part,l.set($.id,U),U.parts.every(be=>be!=="")&&(l.delete($.id),await ct(U.parts.join("")));return}let z=ze;switch(z.t){case"hello":{if(E)return;let $=await nh(z.publicKeyJwk).catch(()=>null);if(!$)return he("hello: bad key");if(!await o(E$($,t.channelId,z.nonce),z.sig,$))return he("hello: signature/identity rejected");E={peerId:$,profile:z.profile};let be=Y($,z.profile);B=n(function ke(){if(!c){if(r()-a>Une){_e();return}p({t:"ping",v:1,ts:r()}),B=n(ke,C$)}},C$),S(be);return}case"ping":return p({t:"pong",v:1,ts:z.ts});case"pong":return;case"project":{f?.(z.code);return}case"rpc":{try{if(z.method==="generate"){if(!d.generate)throw new Error("generate not served here");let $=await d.generate(z.arg);p({t:"rpc-result",v:1,id:z.id,result:$.result,receipt:$.receipt})}else{if(!d.verify)throw new Error("verify not served here");let $=await d.verify(z.arg);p({t:"rpc-result",v:1,id:z.id,result:$.result})}}catch($){p({t:"rpc-error",v:1,id:z.id,error:String($?.message??$)})}return}case"rpc-result":{let $=u.get(z.id);if(!$)return;u.delete(z.id),$.resolve({result:z.result,receipt:z.receipt});return}case"rpc-error":{let $=u.get(z.id);if(!$)return;u.delete(z.id),$.reject(new Error(z.error));return}}}t.onClose(()=>{E?_e():he("channel closed before handshake")}),t.onMessage(ut=>{ct(ut)}),p(_)})}I_();I_();import{gzipSync as Fne,gunzipSync as Vne}from"zlib";var qne=1;function Hne(t){try{let e=JSON.parse(w$(t.trim()));if(!Array.isArray(e)||e[0]!==qne)return null;let[r,n,s,i,o,a]=e;return n!=="offer"&&n!=="answer"||typeof s!="string"||!s||typeof i!="string"||typeof o!="string"||!o?null:{v:r,role:n,sdp:s,token:i,deviceId:o,label:a||void 0}}catch{return null}}function k$(t,e){let r=t.token,n=e;if(r.length!==n.length)return!1;let s=0;for(let i=0;i<r.length;i++)s|=r.charCodeAt(i)^n.charCodeAt(i);return s===0}var pk="oiop1";function mk(t){let e=new TextEncoder().encode(JSON.stringify([t.v,t.role,t.sdp,t.token,t.deviceId,t.label??""]));return pk+T_(Fne(Buffer.from(e)))}function T$(t){let e=t.trim();if(!e.startsWith(pk))return Hne(e);try{let r=Vne(Buffer.from(Bm(e.slice(pk.length)))).toString("utf8"),n=JSON.parse(r);if(!Array.isArray(n)||n[0]!==1)return null;let[s,i,o,a,c,u]=n;return i!=="offer"&&i!=="answer"||typeof o!="string"||!o||typeof a!="string"||typeof c!="string"||!c?null:{v:s,role:i,sdp:o,token:a,deviceId:c,label:u||void 0}}catch{return null}}var l1;async function uge(){if(l1!==void 0)return l1;try{l1=await Promise.resolve().then(()=>(RW(),PW))}catch{l1=null}return l1}function OW(t,e=3e3){return t.iceGatheringState==="complete"?Promise.resolve():new Promise(r=>{let n=setTimeout(r,e);t.iceGatheringStateChange.subscribe(()=>{t.iceGatheringState==="complete"&&(clearTimeout(n),r())})})}function NW(t){let e=null,r=[];return t.onMessage.subscribe(n=>{let s=typeof n=="string"?n:n.toString();e?e(s):r.push(s)}),{channelId:t.label||"oioxo-mesh",send:n=>t.send(n),onMessage:n=>{e=n;let s=r.splice(0);for(let i of s)n(i)},onClose:n=>t.stateChanged.subscribe(s=>{s==="closed"&&n()}),close:()=>{try{t.close()}catch{}}}}var YA=class{deps;constructor(e){this.deps=e}async newPc(){let e=await uge();if(!e)throw new Error("mesh transport unavailable (werift not installed)");return new e.RTCPeerConnection({iceServers:[]})}async hostOffer(){let e=await this.newPc(),r=e.createDataChannel("oioxo-mesh"),n=this.linkWhenOpen(r);return await e.setLocalDescription(await e.createOffer()),await OW(e),{offerCode:mk({v:1,role:"offer",sdp:e.localDescription.sdp,token:this.deps.token,deviceId:this.deps.identity.deviceId}),pending:{pc:e,link:n}}}async joinFromOffer(e){let r=this.accept(e,"offer"),n=await this.newPc(),s=new Promise((o,a)=>{n.onDataChannel.subscribe(c=>{hk(NW(c),this.linkDeps()).then(o,a)})});return await n.setRemoteDescription({sdp:r.sdp,type:"offer"}),await n.setLocalDescription(await n.createAnswer()),await OW(n),{answerCode:mk({v:1,role:"answer",sdp:n.localDescription.sdp,token:this.deps.token,deviceId:this.deps.identity.deviceId}),link:s}}async completeWithAnswer(e,r){let n=this.accept(r,"answer");return await e.pc.setRemoteDescription({sdp:n.sdp,type:"answer"}),e.link}accept(e,r){let n=T$(e);if(!n||n.role!==r)throw new Error("invalid pairing code");if(!k$(n,this.deps.token))throw new Error("this code is for a different account");return n}linkWhenOpen(e){return new Promise((r,n)=>{let s=()=>hk(NW(e),this.linkDeps()).then(r,n);e.readyState==="open"?s():e.stateChanged.subscribe(i=>{i==="open"&&s()})})}linkDeps(){return{identity:this.deps.identity,profile:this.deps.profile,keyLookup:this.deps.keyLookup}}};import lge from"node:os";import jW from"node:path";import{readFile as dge,writeFile as fge,mkdir as hge}from"node:fs/promises";var zW=process.env.OIOXO_HOME||jW.join(lge.homedir(),".oioxo"),BW=jW.join(zW,"mesh-identity.json"),L5={name:"ECDSA",namedCurve:"P-256"},pge={name:"ECDSA",hash:"SHA-256"},mge=new TextEncoder;function $W(t){return Buffer.from(new Uint8Array(t)).toString("base64url")}async function MW(t,e){let r=await crypto.subtle.importKey("jwk",t,L5,!0,["verify"]),n=await crypto.subtle.importKey("jwk",e,L5,!1,["sign"]);return{deviceId:$W(await crypto.subtle.digest("SHA-256",await crypto.subtle.exportKey("raw",r))),publicKeyJwk:t,sign:async i=>$W(await crypto.subtle.sign(pge,n,mge.encode(i)))}}async function D5(){try{let n=JSON.parse(await dge(BW,"utf8"));if(n.pub&&n.priv)return MW(n.pub,n.priv)}catch{}let t=await crypto.subtle.generateKey(L5,!0,["sign","verify"]),e=await crypto.subtle.exportKey("jwk",t.publicKey),r=await crypto.subtle.exportKey("jwk",t.privateKey);try{await hge(zW,{recursive:!0}),await fge(BW,JSON.stringify({pub:e,priv:r}),{mode:384})}catch{}return MW(e,r)}P_();function gge(t){return JSON.stringify([t.v,t.deviceId,t.seq,t.nonce,t.jobHash,t.servedSec,t.tokensOut,t.issuedAt])}async function LW(t,e){let r={v:1,deviceId:t.deviceId,seq:t.seq,nonce:(t.nonce??yge)(),jobHash:t.jobHash,servedSec:Math.max(0,Math.round(t.servedSec)),tokensOut:Math.max(0,Math.round(t.tokensOut)),issuedAt:(t.now??Date.now)()},n=await e(gge(r));return{...r,sig:n}}var nOe={secPerServedSec:1,secPerToken:.02,maxPerReceiptSec:300,dailyCapSec:3600,maxAgeMs:168*3600*1e3};function yge(){let t=new Uint8Array(8);(globalThis.crypto??globalThis.crypto)?.getRandomValues(t);let e="";for(let r of t)e+=r.toString(16).padStart(2,"0");return e}E_();jo();function JA(t){let e=_ge({input:process.stdin,output:process.stdout});return new Promise(r=>e.question(t,n=>{e.close(),r(n.trim())}))}async function GW(t){return Buffer.from(new Uint8Array(await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t)))).toString("base64url")}async function QA(){return(await za())?.token??null}async function WW(t){let e={},r=await QA(),n={"content-type":"application/json",...r?{authorization:`Bearer ${r}`}:{}};try{await fetch(`${gn}/api/devices/register`,{method:"POST",headers:n,body:JSON.stringify({publicKeyJwk:t,label:"CLI peer"})});let s=await fetch(`${gn}/api/devices/register`,{headers:n});if(s.ok){let i=await s.json();for(let o of i.devices??[])e[o.deviceId]=o.publicKeyJwk}}catch{}return e}var vge=0,eE=new Set;function KW(t,e,r,n){let s=Rm();eE.add(t),t.onLost(()=>{eE.delete(t)}),t.serve({generate:async({ctx:i})=>{if("error"in s)return{result:[]};let o=Date.now(),a=await s.coder.generate({task:i.task,grounding:wge(i),error:i.error,attempt:i.attempt}).catch(()=>[]),c;try{if(a.length){let u=await GW(JSON.stringify([i.task,a.map(l=>[l.path,l.content]).sort()]));c=await LW({deviceId:n,seq:++vge,jobHash:u,servedSec:(Date.now()-o)/1e3,tokensOut:Math.ceil(a.reduce((l,d)=>l+d.content.length,0)/4)},r)}}catch{}return{result:a,receipt:c}}}),console.log(` \u2713 connected \u2014 lending "${e}" to ${t.profile.label??t.peerId.slice(0,8)}.`)}function wge(t){return(t.files??[]).map(e=>`--- ${e.path} ---
261
+ `)).catch(()=>{});for(let o of e){if(!Vc(o.path)){console.error(` \u26A0 skipped unsafe path: ${o.path}`);continue}let a=Zi.resolve(r,o.path);if(a!==r&&!a.startsWith(r+Zi.sep)){console.error(` \u26A0 skipped path escaping repo: ${o.path}`);continue}let c=await b$(a).catch(()=>null);if(c!==null){let u=Zi.join(s,o.path);try{await dk(Zi.dirname(u),{recursive:!0}),await lk(u,c),i++}catch{}}await dk(Zi.dirname(a),{recursive:!0}),await lk(a,o.content,"utf8")}if(i){let o=Zi.relative(r,s)||s;console.log(` \u21B6 backed up ${i} overwritten file(s) \u2192 ${o} (restore: copy them back)`)}}function jne(){return new Date().toISOString().replace(/[-:]/g,"").replace(/\.\d+Z$/,"Z")}async function x$(t){if(!process.stdin.isTTY)return console.log(`${t} (no TTY \u2014 pass --yes to apply non-interactively)`),!1;let e=_$({input:process.stdin,output:process.stdout});try{let r=(await e.question(`${t} [y/N] `)).trim().toLowerCase();return r==="y"||r==="yes"}finally{e.close()}}import{createInterface as _ge}from"node:readline";P_();function E$(t,e,r){return JSON.stringify(["hello",t,e,r])}var R_=16*1024,Dne=3e4,C$=1500,Une=4e3;async function hk(t,e){let r=e.now??(()=>Date.now()),n=e.setTimer??((S,C)=>setTimeout(S,C)),s=e.clearTimer??(S=>clearTimeout(S)),i=e.rand??(()=>Math.random().toString(36).slice(2)+r().toString(36)),o=fk(e.keyLookup),a=r(),c=!1,u=new Map,l=new Map,d={},f=null;function p(S){if(c)return;let C=JSON.stringify(S);if(C.length<=R_){t.send(C);return}let E=i(),B=Math.ceil(C.length/R_);for(let G=0;G<B;G++)t.send(JSON.stringify({t:"chunk",id:E,i:G,total:B,part:C.slice(G*R_,(G+1)*R_)}))}let m=i(),y=await e.identity.sign(E$(e.identity.deviceId,t.channelId,m)),_={t:"hello",v:1,profile:e.profile,publicKeyJwk:e.identity.publicKeyJwk,nonce:m,sig:y};return await new Promise((S,C)=>{let E=null,B=null,G=new Set,se=!1;function _e(){if(!se){se=!0,c=!0,B&&s(B);for(let ut of u.values())ut.reject(new Error("link lost"));u.clear();try{t.close()}catch{}for(let ut of G)try{ut()}catch{}}}function he(ut){if(!c){c=!0,B&&s(B);for(let ze of u.values())ze.reject(new Error("link closed"));u.clear(),t.close(),C(new Error(ut))}}function Y(ut,ze){return{peerId:ut,profile:ze,idleMs:()=>r()-a,sendProject:$=>p({t:"project",v:1,code:$}),onProject:$=>{f=$},serve:$=>{d=$},onLost:$=>{se?$():G.add($)},call:(($,U)=>new Promise((be,ke)=>{let Le=i(),k=n(()=>{u.delete(Le),ke(new Error(`rpc ${$} timed out`))},Dne);u.set(Le,{resolve:P=>{s(k),be(P)},reject:P=>{s(k),ke(P)}}),p({t:"rpc",v:1,id:Le,method:$,arg:U})})),close:()=>{_e()}}}async function ct(ut){a=r();let ze;try{ze=JSON.parse(ut)}catch{return}if(ze.t==="chunk"){let $=ze,U=l.get($.id)??{total:$.total,parts:new Array($.total).fill("")};U.parts[$.i]=$.part,l.set($.id,U),U.parts.every(be=>be!=="")&&(l.delete($.id),await ct(U.parts.join("")));return}let z=ze;switch(z.t){case"hello":{if(E)return;let $=await nh(z.publicKeyJwk).catch(()=>null);if(!$)return he("hello: bad key");if(!await o(E$($,t.channelId,z.nonce),z.sig,$))return he("hello: signature/identity rejected");E={peerId:$,profile:z.profile};let be=Y($,z.profile);B=n(function ke(){if(!c){if(r()-a>Une){_e();return}p({t:"ping",v:1,ts:r()}),B=n(ke,C$)}},C$),S(be);return}case"ping":return p({t:"pong",v:1,ts:z.ts});case"pong":return;case"project":{f?.(z.code);return}case"rpc":{try{if(z.method==="generate"){if(!d.generate)throw new Error("generate not served here");let $=await d.generate(z.arg);p({t:"rpc-result",v:1,id:z.id,result:$.result,receipt:$.receipt})}else{if(!d.verify)throw new Error("verify not served here");let $=await d.verify(z.arg);p({t:"rpc-result",v:1,id:z.id,result:$.result})}}catch($){p({t:"rpc-error",v:1,id:z.id,error:String($?.message??$)})}return}case"rpc-result":{let $=u.get(z.id);if(!$)return;u.delete(z.id),$.resolve({result:z.result,receipt:z.receipt});return}case"rpc-error":{let $=u.get(z.id);if(!$)return;u.delete(z.id),$.reject(new Error(z.error));return}}}t.onClose(()=>{E?_e():he("channel closed before handshake")}),t.onMessage(ut=>{ct(ut)}),p(_)})}I_();I_();import{gzipSync as Fne,gunzipSync as Vne}from"zlib";var qne=1;function Hne(t){try{let e=JSON.parse(w$(t.trim()));if(!Array.isArray(e)||e[0]!==qne)return null;let[r,n,s,i,o,a]=e;return n!=="offer"&&n!=="answer"||typeof s!="string"||!s||typeof i!="string"||typeof o!="string"||!o?null:{v:r,role:n,sdp:s,token:i,deviceId:o,label:a||void 0}}catch{return null}}function k$(t,e){let r=t.token,n=e;if(r.length!==n.length)return!1;let s=0;for(let i=0;i<r.length;i++)s|=r.charCodeAt(i)^n.charCodeAt(i);return s===0}var pk="oiop1";function mk(t){let e=new TextEncoder().encode(JSON.stringify([t.v,t.role,t.sdp,t.token,t.deviceId,t.label??""]));return pk+T_(Fne(Buffer.from(e)))}function T$(t){let e=t.trim();if(!e.startsWith(pk))return Hne(e);try{let r=Vne(Buffer.from(Bm(e.slice(pk.length)))).toString("utf8"),n=JSON.parse(r);if(!Array.isArray(n)||n[0]!==1)return null;let[s,i,o,a,c,u]=n;return i!=="offer"&&i!=="answer"||typeof o!="string"||!o||typeof a!="string"||typeof c!="string"||!c?null:{v:s,role:i,sdp:o,token:a,deviceId:c,label:u||void 0}}catch{return null}}var l1;async function uge(){if(l1!==void 0)return l1;try{l1=await Promise.resolve().then(()=>(RW(),PW))}catch{l1=null}return l1}function OW(t,e=3e3){return t.iceGatheringState==="complete"?Promise.resolve():new Promise(r=>{let n=setTimeout(r,e);t.iceGatheringStateChange.subscribe(()=>{t.iceGatheringState==="complete"&&(clearTimeout(n),r())})})}function NW(t){let e=null,r=[];return t.onMessage.subscribe(n=>{let s=typeof n=="string"?n:n.toString();e?e(s):r.push(s)}),{channelId:t.label||"oioxo-mesh",send:n=>t.send(n),onMessage:n=>{e=n;let s=r.splice(0);for(let i of s)n(i)},onClose:n=>t.stateChanged.subscribe(s=>{s==="closed"&&n()}),close:()=>{try{t.close()}catch{}}}}var YA=class{deps;constructor(e){this.deps=e}async newPc(){let e=await uge();if(!e)throw new Error("mesh transport unavailable (werift not installed)");return new e.RTCPeerConnection({iceServers:[]})}async hostOffer(){let e=await this.newPc(),r=e.createDataChannel("oioxo-mesh"),n=this.linkWhenOpen(r);return await e.setLocalDescription(await e.createOffer()),await OW(e),{offerCode:mk({v:1,role:"offer",sdp:e.localDescription.sdp,token:this.deps.token,deviceId:this.deps.identity.deviceId}),pending:{pc:e,link:n}}}async joinFromOffer(e){let r=this.accept(e,"offer"),n=await this.newPc(),s=new Promise((o,a)=>{n.onDataChannel.subscribe(c=>{hk(NW(c),this.linkDeps()).then(o,a)})});return await n.setRemoteDescription({sdp:r.sdp,type:"offer"}),await n.setLocalDescription(await n.createAnswer()),await OW(n),{answerCode:mk({v:1,role:"answer",sdp:n.localDescription.sdp,token:this.deps.token,deviceId:this.deps.identity.deviceId}),link:s}}async completeWithAnswer(e,r){let n=this.accept(r,"answer");return await e.pc.setRemoteDescription({sdp:n.sdp,type:"answer"}),e.link}accept(e,r){let n=T$(e);if(!n||n.role!==r)throw new Error("invalid pairing code");if(!k$(n,this.deps.token))throw new Error("this code is for a different account");return n}linkWhenOpen(e){return new Promise((r,n)=>{let s=()=>hk(NW(e),this.linkDeps()).then(r,n);e.readyState==="open"?s():e.stateChanged.subscribe(i=>{i==="open"&&s()})})}linkDeps(){return{identity:this.deps.identity,profile:this.deps.profile,keyLookup:this.deps.keyLookup}}};import lge from"node:os";import jW from"node:path";import{readFile as dge,writeFile as fge,mkdir as hge}from"node:fs/promises";var zW=process.env.OIOXO_HOME||jW.join(lge.homedir(),".oioxo"),BW=jW.join(zW,"mesh-identity.json"),L5={name:"ECDSA",namedCurve:"P-256"},pge={name:"ECDSA",hash:"SHA-256"},mge=new TextEncoder;function $W(t){return Buffer.from(new Uint8Array(t)).toString("base64url")}async function MW(t,e){let r=await crypto.subtle.importKey("jwk",t,L5,!0,["verify"]),n=await crypto.subtle.importKey("jwk",e,L5,!1,["sign"]);return{deviceId:$W(await crypto.subtle.digest("SHA-256",await crypto.subtle.exportKey("raw",r))),publicKeyJwk:t,sign:async i=>$W(await crypto.subtle.sign(pge,n,mge.encode(i)))}}async function D5(){try{let n=JSON.parse(await dge(BW,"utf8"));if(n.pub&&n.priv)return MW(n.pub,n.priv)}catch{}let t=await crypto.subtle.generateKey(L5,!0,["sign","verify"]),e=await crypto.subtle.exportKey("jwk",t.publicKey),r=await crypto.subtle.exportKey("jwk",t.privateKey);try{await hge(zW,{recursive:!0}),await fge(BW,JSON.stringify({pub:e,priv:r}),{mode:384})}catch{}return MW(e,r)}P_();function gge(t){return JSON.stringify([t.v,t.deviceId,t.seq,t.nonce,t.jobHash,t.servedSec,t.tokensOut,t.issuedAt])}async function LW(t,e){let r={v:1,deviceId:t.deviceId,seq:t.seq,nonce:(t.nonce??yge)(),jobHash:t.jobHash,servedSec:Math.max(0,Math.round(t.servedSec)),tokensOut:Math.max(0,Math.round(t.tokensOut)),issuedAt:(t.now??Date.now)()},n=await e(gge(r));return{...r,sig:n}}var nOe={secPerServedSec:1,secPerToken:.02,maxPerReceiptSec:300,dailyCapSec:3600,maxAgeMs:168*3600*1e3};function yge(){let t=new Uint8Array(8);(globalThis.crypto??globalThis.crypto)?.getRandomValues(t);let e="";for(let r of t)e+=r.toString(16).padStart(2,"0");return e}E_();jo();function JA(t){let e=_ge({input:process.stdin,output:process.stdout});return new Promise(r=>e.question(t,n=>{e.close(),r(n.trim())}))}async function GW(t){return Buffer.from(new Uint8Array(await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t)))).toString("base64url")}async function QA(){return(await za())?.token??null}async function WW(t){let e={},r=await QA(),n={"content-type":"application/json",...r?{authorization:`Bearer ${r}`}:{}};try{await fetch(`${gn}/api/devices/register`,{method:"POST",headers:n,body:JSON.stringify({publicKeyJwk:t,label:"CLI peer"})});let s=await fetch(`${gn}/api/devices/register`,{headers:n});if(s.ok){let i=await s.json();for(let o of i.devices??[])e[o.deviceId]=o.publicKeyJwk}}catch{}return e}var vge=0,eE=new Set;function KW(t,e,r,n){let s=Rm();eE.add(t),t.onLost(()=>{eE.delete(t)}),t.serve({generate:async({ctx:i})=>{if("error"in s)return{result:[]};let o=Date.now(),a=await s.coder.generate({task:i.task,grounding:wge(i),error:i.error,attempt:i.attempt}).catch(()=>[]),c;try{if(a.length){let u=await GW(JSON.stringify([i.task,a.map(l=>[l.path,l.content]).sort()]));c=await LW({deviceId:n,seq:++vge,jobHash:u,servedSec:(Date.now()-o)/1e3,tokensOut:Math.ceil(a.reduce((l,d)=>l+d.content.length,0)/4)},r)}}catch{}return{result:a,receipt:c}}}),console.log(` \u2713 connected \u2014 lending "${e}" to ${t.profile.label??t.peerId.slice(0,8)}.`)}function wge(t){return(t.files??[]).map(e=>`--- ${e.path} ---
262
262
  ${e.content}`).join(`
263
263
 
264
264
  `)}async function d1(t){if(!await Ki()){console.log("Compute Mesh is account-scoped (same account, same Wi-Fi). Sign in first: `oioxo login`.");return}let e=t[0];if(e==="host"&&(e="invite"),e==="help"&&(e="join"),e==="devices"||e==="scan"){await Sge();return}if(e!=="invite"&&e!=="join"){console.log(`Compute Mesh \u2014 lend this machine's compute to a device that has a project (same
@@ -352,14 +352,15 @@ ${f.map(m=>` ${m}`).join(`
352
352
 
353
353
  IMPORTED BY (changing ${l} may break these):
354
354
  ${p.map(m=>` ${m}`).join(`
355
- `)||" (none \u2014 no in-project callers)"}`)}),e.tool("remember","Save one durable fact about THIS project to .oioxo/memory.md (on-device, shared with the OIOXO IDEs). Use for decisions, conventions, gotchas worth keeping.",{fact:Rf.string().describe("The fact, one concise sentence.")},async({fact:c})=>(await tre(t,c),$a("Saved to .oioxo/memory.md."))),e.tool("recall","This project's rules and learned memory from .oioxo/ (same files the OIOXO IDEs maintain). Call when starting work in an unfamiliar repo.",{},async()=>{let c=AB(await SB(t));return $a(c||"No .oioxo/rules.md or memory.md in this project yet.")});let a=new qC;await e.connect(a)}jo();var CB=(()=>{try{return"0.5.3"}catch{}try{return sre(import.meta.url)("../../package.json").version}catch{return"0.0.0"}})(),nre=`OIOXO ${CB} \u2014 context engine + terminal coding agent (https://oioxo.com)
355
+ `)||" (none \u2014 no in-project callers)"}`)}),e.tool("remember","Save one durable fact about THIS project to .oioxo/memory.md (on-device, shared with the OIOXO IDEs). Use for decisions, conventions, gotchas worth keeping.",{fact:Rf.string().describe("The fact, one concise sentence.")},async({fact:c})=>(await tre(t,c),$a("Saved to .oioxo/memory.md."))),e.tool("recall","This project's rules and learned memory from .oioxo/ (same files the OIOXO IDEs maintain). Call when starting work in an unfamiliar repo.",{},async()=>{let c=AB(await SB(t));return $a(c||"No .oioxo/rules.md or memory.md in this project yet.")});let a=new qC;await e.connect(a)}jo();var CB=(()=>{try{return"0.5.4"}catch{}try{return sre(import.meta.url)("../../package.json").version}catch{return"0.0.0"}})(),nre=`OIOXO ${CB} \u2014 context engine + terminal coding agent (https://oioxo.com)
356
356
 
357
357
  Usage: oioxo <command> [options]
358
358
 
359
359
  Code:
360
360
  code "<task>" Build/fix in this repo: capsule-grounded, verified on a shadow copy,
361
- then asks before writing. Flags: --verify "<cmd>" \xB7 --yes \xB7 --max-iters N
362
- (For a fully local run today: OIOXO_PROVIDER=ollama OIOXO_MODEL=qwen2.5-coder)
361
+ then asks before writing. Flags: --verify "<cmd>" \xB7 --yes \xB7 --max-iters N \xB7 --local
362
+ --local runs the built-in on-device coder, no key (one-time: npm i -g node-llama-cpp).
363
+ (Or use a local Ollama server: OIOXO_PROVIDER=ollama OIOXO_MODEL=qwen2.5-coder)
363
364
 
364
365
  Account:
365
366
  login Connect your OIOXO account (opens the browser)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "oioxo-mcp",
3
- "version": "0.5.3",
4
- "description": "OIOXO context engine for AI coding agents feeds Claude Code, Copilot, Cursor and any MCP-capable agent the minimal relevant slice of your codebase, on-device, so your tokens go further.",
3
+ "version": "0.5.4",
4
+ "description": "OIOXO a private terminal coding agent (`oioxo code`, on-device with --local or any key) plus the context engine that feeds Claude Code, Copilot, Cursor and any MCP agent the minimal relevant slice of your codebase. On-device, token-saving, and a lend-only Compute Mesh peer.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "type": "module",
7
7
  "bin": {
@@ -36,7 +36,12 @@
36
36
  "copilot",
37
37
  "cursor",
38
38
  "oioxo",
39
- "token-saver"
39
+ "token-saver",
40
+ "code-agent",
41
+ "on-device",
42
+ "local-llm",
43
+ "compute-mesh",
44
+ "private-ai"
40
45
  ],
41
46
  "homepage": "https://oioxo.com",
42
47
  "dependencies": {