@ours.network/mcp 0.15.3 → 0.16.0-nightly.1
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.js
CHANGED
|
@@ -46,7 +46,7 @@ ${x}`,d++;break;case"id":l=x.includes("\0")?void 0:x;break;case"retry":/^\d+$/.t
|
|
|
46
46
|
`,{mode:384});try{Ne.chmodSync(t,384)}catch{}return t}var qs=".ours-identity";function Fs(e){if(!e.name.trim())throw new Error("identity name must not be empty");let t={identity:e.name.trim()};return e.force&&(t.force=!0),t.expose_local=e.exposeLocal??!0,t.local_auto_accept=e.localAutoAccept??!0,t}function Cu(e){let t=Ls(e);return Fz(t)===qs?t:Eu(t,qs)}function e_(e,t,r=!1){let n=Fs(t),o=Cu(e);if(!r&&Ne.existsSync(o))throw new Error(`${o} already exists \u2014 pass overwrite to replace it`);return Ne.mkdirSync(Iu(o),{recursive:!0}),Ne.writeFileSync(o,JSON.stringify(n,null,2)+`
|
|
47
47
|
`),o}import r_ from"node:process";var Vs=class{append(t){this._buffer=this._buffer?Buffer.concat([this._buffer,t]):t}readMessage(){if(!this._buffer)return null;let t=this._buffer.indexOf(`
|
|
48
48
|
`);if(t===-1)return null;let r=this._buffer.toString("utf8",0,t).replace(/\r$/,"");return this._buffer=this._buffer.subarray(t+1),Wz(r)}clear(){this._buffer=void 0}};function Wz(e){return Or.parse(JSON.parse(e))}function t_(e){return JSON.stringify(e)+`
|
|
49
|
-
`}var Hs=class{constructor(t=r_.stdin,r=r_.stdout){this._stdin=t,this._stdout=r,this._readBuffer=new Vs,this._started=!1,this._ondata=n=>{this._readBuffer.append(n),this.processReadBuffer()},this._onerror=n=>{this.onerror?.(n)}}async start(){if(this._started)throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");this._started=!0,this._stdin.on("data",this._ondata),this._stdin.on("error",this._onerror)}processReadBuffer(){for(;;)try{let t=this._readBuffer.readMessage();if(t===null)break;this.onmessage?.(t)}catch(t){this.onerror?.(t)}}async close(){this._stdin.off("data",this._ondata),this._stdin.off("error",this._onerror),this._stdin.listenerCount("data")===0&&this._stdin.pause(),this._readBuffer.clear(),this.onclose?.()}send(t){return new Promise(r=>{let n=t_(t);this._stdout.write(n)?r():this._stdout.once("drain",r)})}};function n_(e){let{selfVersion:t,daemonVersion:r}=e;return!r||r===t?null:`ours: version mismatch \u2014 this plugin/connector is v${t}, the running daemon is v${r}. Everything still works; for the best experience run matching versions. The daemon is a shared singleton and is never restarted automatically, so when no other session is mid-task run \`ours-mcp stop\` (the next session starts the daemon at the new version) \u2014 or update the lagging side to match. No action is required; this is advisory.`}import{mkdirSync as o_,writeFileSync as Yz,readFileSync as Qz,readdirSync as Xz,statSync as Nu,unlinkSync as eP,chmodSync as s_,createWriteStream as tP,accessSync as rP,constants as nP}from"node:fs";import{join as Bs,dirname as oP,resolve as sP}from"node:path";import{homedir as iP}from"node:os";import{Readable as aP}from"node:stream";import{pipeline as cP}from"node:stream/promises";var uP="0.
|
|
49
|
+
`}var Hs=class{constructor(t=r_.stdin,r=r_.stdout){this._stdin=t,this._stdout=r,this._readBuffer=new Vs,this._started=!1,this._ondata=n=>{this._readBuffer.append(n),this.processReadBuffer()},this._onerror=n=>{this.onerror?.(n)}}async start(){if(this._started)throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");this._started=!0,this._stdin.on("data",this._ondata),this._stdin.on("error",this._onerror)}processReadBuffer(){for(;;)try{let t=this._readBuffer.readMessage();if(t===null)break;this.onmessage?.(t)}catch(t){this.onerror?.(t)}}async close(){this._stdin.off("data",this._ondata),this._stdin.off("error",this._onerror),this._stdin.listenerCount("data")===0&&this._stdin.pause(),this._readBuffer.clear(),this.onclose?.()}send(t){return new Promise(r=>{let n=t_(t);this._stdout.write(n)?r():this._stdout.once("drain",r)})}};function n_(e){let{selfVersion:t,daemonVersion:r}=e;return!r||r===t?null:`ours: version mismatch \u2014 this plugin/connector is v${t}, the running daemon is v${r}. Everything still works; for the best experience run matching versions. The daemon is a shared singleton and is never restarted automatically, so when no other session is mid-task run \`ours-mcp stop\` (the next session starts the daemon at the new version) \u2014 or update the lagging side to match. No action is required; this is advisory.`}import{mkdirSync as o_,writeFileSync as Yz,readFileSync as Qz,readdirSync as Xz,statSync as Nu,unlinkSync as eP,chmodSync as s_,createWriteStream as tP,accessSync as rP,constants as nP}from"node:fs";import{join as Bs,dirname as oP,resolve as sP}from"node:path";import{homedir as iP}from"node:os";import{Readable as aP}from"node:stream";import{pipeline as cP}from"node:stream/promises";var uP="0.16.0-nightly.1",lP=new Set(["choose_identity","create_identity","create_root_identity"]),dP="save_file",pP="get_files",fP=["1","true","yes","on"].includes((process.env.OURS_FILES_ALWAYS_PROMPT??"").trim().toLowerCase()),mP=e=>{try{return rP(e,nP.R_OK),!0}catch{return!1}},hP=e=>` \u2022 ${e.filename} \u2014 ${e.mime||"application/octet-stream"}, ${e.size} B, sha256 ${e.sha256} \u2014 from ${e.sender} \u2014 wire_id ${e.wire_id}`;function gP(e,t){let r=e;if(!r||r.isError)return!1;let n=r.structuredContent?.files;if(!Array.isArray(n))return!1;let o=n.filter(a=>!!a&&typeof a=="object"&&typeof a.path=="string"&&typeof a.wire_id=="string");if(o.length===0)return!1;let s=o.filter(a=>!t(a.path));for(let a of o)a.readable=!s.includes(a);if(s.length===0)return!1;let i=[`${s.length} of ${o.length} received file(s) are NOT readable by your OS user: the ours daemon runs as a different OS user and keeps its files dir private. Nothing was lost \u2014 the bytes are safely on disk. IGNORE the on-disk paths reported below for these files; you cannot open them.`,"","TELL THE USER what arrived (details below) and ASK WHERE TO SAVE each file on this filesystem. Then call save_file({ wire_id, dest_path }) with the path they choose: the ours connector streams the bytes daemon\u2192proxy\u2192disk and writes them as YOUR OS user. The bytes never enter this conversation.","","Awaiting a destination:",...s.map(hP)];return r.content=[{type:"text",text:i.join(`
|
|
50
50
|
`)},...r.content??[]],!0}var i_=e=>new Promise(t=>setTimeout(t,e)),ju=e=>typeof e?.method=="string"&&e.id!==void 0&&e.id!==null,_P=e=>e&&typeof e=="object"&&!("method"in e)&&e.id!==void 0&&e.id!==null,a_=e=>{let t=e.result;return!!e.error||!!(t&&t.isError)},c_=Number(process.env.OURS_RESTORE_TTL_MS)>0?Number(process.env.OURS_RESTORE_TTL_MS):7*24*60*60*1e3,u_=["1","true","yes","on"].includes((process.env.OURS_NO_AUTORESTORE??"").trim().toLowerCase());async function l_(e){let t=new URL(e.url),r=(...k)=>process.stderr.write(`[ours-proxy] ${k.join(" ")}
|
|
51
51
|
`),n=k=>Number.isInteger(k)&&k>1,o=Number(process.env.OURS_CLIENT_PID),s=n(o)?o:n(process.ppid)?process.ppid:process.pid,i=(process.env.CLAUDE_CODE_SESSION_ID??"").trim()||`client:${s}`,a=null,c=null,l,u=null,d=new Map,f=new Set,m=null,h=u_?null:(process.env.CLAUDE_CODE_SESSION_ID??"").trim()||null,g=e.stateDir??process.env.OURS_STATE_DIR??Bs(iP(),".ours"),y=Bs(g,"session-restore"),S=h&&/^[A-Za-z0-9._-]{1,200}$/.test(h)?h:null,$=S?Bs(y,`${S}.json`):null,w=!1,T=null;function v(k){if($)try{o_(y,{recursive:!0,mode:448}),Yz($,JSON.stringify({claudeSessionId:h,identity:k,boundAt:new Date().toISOString()}),{mode:384}),s_(y,448),s_($,384)}catch(N){r("restore persist failed (non-fatal):",String(N))}}function x(){if(!$)return null;try{if(Date.now()-Nu($).mtimeMs>c_)return null;let k=JSON.parse(Qz($,"utf8"));return(typeof k.identity=="string"?k.identity.trim():"")||null}catch{return null}}function A(){try{let k=Date.now();for(let N of Xz(y)){if(!N.endsWith(".json"))continue;let C=Bs(y,N);try{k-Nu(C).mtimeMs>c_&&(eP(C),r("session-restore: pruned expired record",N))}catch{}}}catch{}}let B=(()=>{let k=(process.env.OURS_REQUEST_TIMEOUT_MS??"").trim();if(k==="")return 12e4;let N=Number(k);return Number.isFinite(N)&&N>=0?N:12e4})(),J=new Map,_e=new Map;function We(k,N,C){let K=J.get(k);if(!K)return;J.delete(k),K.progressToken!==void 0&&_e.delete(K.progressToken);let M=Date.now()-K.sentAt;r(`FAILING BACK request id=${String(k)} method=${K.method} waited_ms=${M} reason=${C}`),ft.send({jsonrpc:"2.0",id:k,error:{code:N,message:`ours proxy: no response from the ours daemon for "${K.method}" after ${M}ms (${C}). The request was NOT retried \u2014 retry it yourself if it is safe to repeat.`,data:{reason:C,method:K.method,waitedMs:M}}}).catch(Z=>r("fail-back send failed:",String(Z)))}B>0&&setInterval(()=>{let N=Date.now();for(let[C,K]of[...J])K.deadline<=N&&We(C,-32001,"watchdog timeout")},1e3).unref?.();let wr=6e4,Jt=3,Ye=0,$t=0;function F_(k,N){ft.send({jsonrpc:"2.0",method:"notifications/message",params:{level:k,logger:"ours-proxy",data:N}}).catch(C=>r("downstream notify failed:",String(C)))}let Ze=null,V_=()=>Ze,xo=!1,sn=!1,an=!1,cn=[],H_="__ours_proxy_init__",ei=0,un=new Set,ti=new Set,ft=new Hs;ft.onmessage=k=>{let N=[],C=!1;for(let K of Ks(k)){let M=K;if(M.method==="notifications/initialized"&&(C=!0),ju(M)){if(M.method==="initialize"&&(a=M,c)){r("absorbed re-initialize locally \u2014 upstream session kept stable"),ft.send({jsonrpc:"2.0",id:M.id,result:c}).catch(Z=>r("local initialize reply failed:",String(Z)));continue}if(M.method==="tools/call"){let Z=M.params;if(Z&&lP.has(Z.name??"")&&typeof Z.arguments?.name=="string"&&d.set(M.id,Z.arguments.name),Z?.name===pP&&f.add(M.id),Z?.name===dP){let ye=M.params?.arguments??{};J_(M.id,ye);continue}}if(B>0){let Z=M.id,ye=M.params?._meta,we=Date.now();J.set(Z,{method:typeof M.method=="string"?M.method:"unknown",sentAt:we,deadline:we+B,transport:null,progressToken:ye?.progressToken}),ye?.progressToken!==void 0&&_e.set(ye.progressToken,Z)}}N.push(M)}if(N.length){let K=Array.isArray(k)?N:N[0];Ku(K)}C&&B_()},ft.onclose=()=>void ni(0),ft.onerror=k=>r("downstream error:",String(k));function B_(){if(w||!u)return;w=!0;let k=`__ours_proxy_restore_${++ei}__`;un.add(k),T=k,r("session-restore: self-recovering bound identity",`"${u}"`),Ku({jsonrpc:"2.0",id:k,method:"tools/call",params:{name:"choose_identity",arguments:{name:u}}})}function K_(k,N){if(!(B<=0))for(let C of Ks(k)){let K=C.id;if(K===void 0)continue;let M=J.get(K);M&&(M.transport=N)}}async function J_(k,N){let C=String(N.wire_id??"").trim(),K=String(N.dest_path??"").trim(),M=(Z,ye=!1)=>ft.send({jsonrpc:"2.0",id:k,result:{content:[{type:"text",text:Z}],isError:ye}}).catch(we=>r("save_file reply failed:",String(we)));if(!C||!K){await M("save_file: both `wire_id` and `dest_path` are required.",!0);return}if(!/^[A-Za-z0-9]+$/.test(C)){await M("save_file: invalid wire_id.",!0);return}try{let Z=new URL(t.href);Z.pathname=`/files/${encodeURIComponent(C)}`,Z.search="";let ye={"x-ours-lease-token":i};e.apiToken&&(ye["x-ours-api-token"]=e.apiToken);let we=await fetch(Z,{headers:ye});if(we.status===401||we.status===403){await M("save_file: ours daemon authentication failed; the monitor may be disarmed.",!0);return}if(we.status===404){await M(`save_file: no file with wire_id ${C} is available to the bound identity. Run get_files first; it may already be saved, or it belongs to another identity.`,!0);return}if(!we.ok||!we.body){await M(`save_file: ours daemon returned HTTP ${we.status}. Your ours daemon may be too old to support save_file \u2014 update it, or use get_files and copy the returned path yourself.`,!0);return}let je=sP(K);o_(oP(je),{recursive:!0}),await cP(aP.fromWeb(we.body),tP(je));let Q_=Nu(je).size;await M(`Saved file (wire_id ${C}) to ${je} (${Q_} bytes). The bytes were streamed daemon\u2192proxy\u2192disk and never entered this result.`)}catch(Z){await M(`save_file failed: ${String(Z)}`,!0)}}function Ku(k){if(xo&&Ze){let N=Ze;K_(k,N),Ze.send(k).catch(C=>{r("upstream send failed:",String(C));let K=Ks(k),M=K.filter(Z=>!ju(Z));M.length&&cn.push(Array.isArray(k)?M:M[0]);for(let Z of K){let ye=Z;ju(ye)&&J.has(ye.id)&&We(ye.id,-32e3,"upstream send failed")}ri(N,"send failed")})}else cn.push(k)}function ri(k,N){if(sn||an||Ze!==null&&k!==Ze)return;r(`upstream dropped (${N}) \u2014 reconnecting`),xo=!1;let C=Ze;Ze=null;try{C?.close?.()}catch{}if(C)for(let[K,M]of[...J])M.transport===C&&We(K,-32e3,`upstream dropped (${N})`);W_()}function G_(k){k.onmessage=N=>{let C=[];for(let K of Ks(N)){let M=K;if(M.method==="notifications/progress"){let Z=M.params?.progressToken;if(Z!==void 0){let ye=_e.get(Z),we=ye!==void 0?J.get(ye):void 0;we&&(we.deadline=Date.now()+B)}}if(_P(M)){let Z=M.id,ye=J.get(Z);ye&&(J.delete(Z),ye.progressToken!==void 0&&_e.delete(ye.progressToken));let we=M.result?.protocolVersion;if(we&&(ti.has(Z)||Ju(Z))){l=we;try{Ze?.setProtocolVersion?.(we)}catch{}if(c==null&&Ju(Z)&&!un.has(Z)){let je=M.result??null;je&&m&&(je.instructions=`${je.instructions?je.instructions+`
|
|
52
52
|
|
|
@@ -79,7 +79,7 @@ OURS_STT_API_KEY, OURS_STT_MODEL, OURS_STT_BASE_URL, and OURS_STT_LANGUAGE.
|
|
|
79
79
|
Exit 2 means restart/readiness failed after a write and rollback was attempted.`;import*as _r from"node:fs";import{join as MP}from"node:path";var UP="startup-progress.json",DP=1,x_={initializing:"Initializing daemon",wrapper:"Starting protocol runtime",registrar:"Starting local contact book",identities:"Restoring identities",reconciliation:"Reconciling restored identities",server:"Opening local API",ready:"Daemon ready",failed:"Daemon startup failed"};function ZP(e){return MP(e,UP)}function qP(e){return typeof e=="string"&&Object.hasOwn(x_,e)}function k_(e){try{let t=JSON.parse(_r.readFileSync(ZP(e),"utf8"));return t.version!==DP||!Number.isInteger(t.pid)||(t.pid??0)<=0||typeof t.bootId!="string"||!t.bootId||!qP(t.phase)||!Number.isFinite(t.startedAt)||!Number.isFinite(t.updatedAt)||(t.completed!==void 0||t.total!==void 0)&&(!Number.isInteger(t.completed)||!Number.isInteger(t.total)||(t.completed??-1)<0||(t.total??-1)<0||(t.completed??0)>(t.total??0))?null:t}catch{return null}}function Du(e){let t=x_[e.phase];return e.phase==="identities"&&e.completed!==void 0&&e.total!==void 0?`${t} ${e.completed}/${e.total}`:t}function z_(e,t){let r=`startup: ${Du(e)}`;return t?`\r${r}\x1B[K`:`${r}
|
|
80
80
|
`}async function P_(e){let t=e.now??Date.now,r=e.sleep??(c=>new Promise(l=>setTimeout(l,c))),n=t(),o=n,s=-1,i="",a=null;for(;;){if(await e.isReady())return{ok:!0,elapsedMs:t()-n,lastProgress:a};let c=e.readProgress();if(c?.pid===e.pid){a=c,c.updatedAt>s&&(s=c.updatedAt,o=t());let f=`${c.phase}:${c.completed??""}:${c.total??""}`;if(f!==i&&(i=f,e.onProgress?.(c)),c.phase==="failed")return{ok:!1,reason:"daemon-reported-failure",elapsedMs:t()-n,lastProgress:a}}if(!e.isProcessAlive())return{ok:!1,reason:"process-exited",elapsedMs:t()-n,lastProgress:a};let l=t()-n;if(l>=e.absoluteMs)return{ok:!1,reason:"absolute-timeout",elapsedMs:l,lastProgress:a};if(t()-o>=e.inactivityMs)return{ok:!1,reason:"inactivity-timeout",elapsedMs:l,lastProgress:a};let u=e.absoluteMs-l,d=e.inactivityMs-(t()-o);await r(Math.max(1,Math.min(e.pollMs,u,d)))}}if(!process.env.XDG_RUNTIME_DIR&&typeof process.getuid=="function"){let e=`/run/user/${process.getuid()}`;H.existsSync(e)&&(process.env.XDG_RUNTIME_DIR=e)}var wt=Ou(),De=wt.stateDir,ie=wt.port,So=()=>{let e=Au(wt,{generate:!1})?.token??null;return e?{"x-ours-api-token":e}:{}},nn=wt.brokerUrl,br=Ot(De,"daemon.pid"),yr=Ot(De,"daemon.log"),Bt=FP(import.meta.url),b=(...e)=>process.stdout.write(`${e.join(" ")}
|
|
81
81
|
`),te=(...e)=>process.stderr.write(`${e.join(" ")}
|
|
82
|
-
`),vr="0.
|
|
82
|
+
`),vr="0.16.0-nightly.1";async function Vu(){for(let e of["/version","/state-dir"])try{let t=new AbortController,r=setTimeout(()=>t.abort(),1500),n=await fetch(`http://127.0.0.1:${ie}${e}`,{signal:t.signal});if(clearTimeout(r),n.ok){let o=await n.json();if(o&&typeof o.version=="string")return o}}catch{}return null}function T_(e){if(b(` cli: v${vr}`),e?.version){let t=e.version!==vr;b(` daemon: v${e.version}${e.compat?` (compat ${e.compat})`:""}`+(t?` \u26A0 differs from CLI \u2014 \`ours-mcp restart\` to load v${vr}`:""))}else b(" daemon: version unknown (running build predates /version \u2014 restart to update)")}var Xs=e=>new Promise(t=>setTimeout(t,e));function HP(){try{let e=parseInt(H.readFileSync(br,"utf8").trim(),10);return Number.isFinite(e)?e:null}catch{return null}}function Ys(e){try{if(process.kill(e,0),process.platform==="linux")try{if(d_(H.readFileSync(`/proc/${e}/stat`,"utf8")))return!1}catch{try{process.kill(e,0)}catch{return!1}}return!0}catch{return!1}}function on(){let e=HP();if(e&&Ys(e))return e;if(e)try{H.rmSync(br,{force:!0})}catch{}return null}function Kt(e,t=1e3){return new Promise(r=>{let n=LP({host:"127.0.0.1",port:e}),o=s=>{n.destroy(),r(s)};n.setTimeout(t),n.once("connect",()=>o(!0)),n.once("timeout",()=>o(!1)),n.once("error",()=>o(!1))})}async function BP(e,t=3e4){let r=Date.now()+t;for(;Date.now()<r;){if(await Kt(e))return!0;await Xs(400)}return!1}function Bu(e,t){let r=Number(process.env[e]||"");return Number.isFinite(r)&&r>0?r:t}var j_=Bu("OURS_TEST_STARTUP_ABSOLUTE_MS",18e4),M_=Bu("OURS_TEST_STARTUP_INACTIVITY_MS",3e4),U_=Bu("OURS_TEST_STARTUP_POLL_MS",400);async function KP(e){let t=new AbortController,r=setTimeout(()=>t.abort(),Math.min(1e3,U_));try{let n=await fetch(`http://127.0.0.1:${e}/identities`,{signal:t.signal,headers:So()}),o=n.ok;try{await n.body?.cancel()}catch{}return o}catch{return!1}finally{clearTimeout(r)}}function R_(e){return e<1e3?`${Math.round(e)}ms`:e%6e4===0?`${e/6e4}m`:`${Math.round(e/1e3)}s`}function JP(e,t){let r=t?` Last phase: ${Du(t)}.`:"";return e==="process-exited"||e==="daemon-reported-failure"?`daemon exited before becoming ready.${r}`:e==="inactivity-timeout"?`daemon startup made no observable progress for ${R_(M_)}.${r} The process is still running, but startup is stalled.`:`daemon startup did not finish within ${R_(j_)}.${r} The bounded wait expired.`}async function E_(){let e=on();if(e){b(`ours-mcp is already running (pid ${e}, port ${ie}).`);return}H.mkdirSync(De,{recursive:!0});let t=H.openSync(yr,"a"),r=A_(process.execPath,[Bt,"serve"],{detached:!0,stdio:["ignore",t,t],env:{...process.env,OURS_TRANSPORT:"http",OURS_PORT:String(ie),OURS_BROKER_URL:nn,OURS_STATE_DIR:De}});r.unref(),r.pid||(te("failed to spawn the daemon."),process.exit(1)),H.writeFileSync(br,String(r.pid)),b(`starting ours-mcp (pid ${r.pid})\u2026`);let n=!!process.stderr.isTTY,o=!1,s=await P_({pid:r.pid,absoluteMs:j_,inactivityMs:M_,pollMs:U_,isProcessAlive:()=>Ys(r.pid),isReady:()=>KP(ie),readProgress:()=>k_(De),onProgress:i=>{o=!0,process.stderr.write(z_(i,n))}});o&&n&&process.stderr.write(`
|
|
83
83
|
`),s.ok?(b(`ours-mcp is up on http://localhost:${ie}/mcp`),b(` broker: ${nn}`),b(` state: ${De}`),b(` logs: ${yr}`)):(te(`${JP(s.reason,s.lastProgress)} Check ${yr}.`),process.exit(1))}async function GP(){if(!await Kt(ie)){if(!on()){H.mkdirSync(De,{recursive:!0});let e=H.openSync(yr,"a"),t=A_(process.execPath,[Bt,"serve"],{detached:!0,stdio:["ignore",e,e],env:{...process.env,OURS_TRANSPORT:"http",OURS_PORT:String(ie),OURS_BROKER_URL:nn,OURS_STATE_DIR:De}});if(t.unref(),t.pid)try{H.writeFileSync(br,String(t.pid))}catch{}}await BP(ie)}}async function Qs(){let e=on();if(!e){b("ours-mcp is not running.");return}b(`stopping ours-mcp (pid ${e})\u2026`);try{process.kill(e,"SIGTERM")}catch(t){te(`failed to signal pid ${e}: ${String(t)}`)}for(let t=0;t<25&&Ys(e);t++)await Xs(200);if(Ys(e)){te(`pid ${e} did not exit; sending SIGKILL.`);try{process.kill(e,"SIGKILL")}catch{}}try{H.rmSync(br,{force:!0})}catch{}b("stopped.")}async function WP(){let e=on();if(!e){if(await Kt(ie)){let n=await Vu();b("ours-mcp: running (no pidfile \u2014 likely a stale process or external launcher)"),b(` url: http://localhost:${ie}/mcp (reachable)`),T_(n);return}b("ours-mcp: stopped"),b(` cli: v${vr}`),process.exitCode=1;return}let t=await Kt(ie),r=t?await Vu():null;b("ours-mcp: running"),b(` pid: ${e}`),b(` url: http://localhost:${ie}/mcp ${t?"(reachable)":"(port not answering!)"}`),b(` broker: ${nn}`),b(` state: ${De}`),b(` logs: ${yr}`),T_(r)}async function YP(){if(b(`ours-mcp v${vr}`),await Kt(ie)){let e=await Vu();e?.version?b(`running daemon: v${e.version}`+(e.version!==vr?" (differs \u2014 restart to update)":"")):b("running daemon: version unknown (predates /version)")}else b("daemon: not running")}async function QP(){let e=Ou(),t=bo();b(`ours-mcp setup \u2014 ${t}`),b("Enter a value, or press Enter to keep the current [bracketed] one."),b("");let r=Ws({input:process.stdin,output:process.stdout}),n;try{let u=async(J,_e)=>{let We=(await r.question(` ${J} [${_e}]: `)).trim();return We===""?_e:We},d=await u("broker URL",e.brokerUrl),f=await u("HTTP port",String(e.port)),m=await u("state dir",e.stateDir),h=await u("GC interval (ms)",String(e.gcIntervalMs)),g=await u("proxy auto-starts daemon (true/false)",String(e.autoStart)),y=await u("HTTP visibility (owner=same-user only / shared=cross-user w/ token / open=all local users)",e.apiVisibility);b(""),b(" Voice-message transcription (STT). Leave provider empty to keep it off \u2014"),b(" agents then deterministically tell users voice messages cannot be read."),b(" No provider or model is assumed; model strings pass to your provider verbatim.");let S=(await r.question(` STT provider (openai-compatible / elevenlabs / deepgram / custom, empty=off) [${e.stt?.provider??""}]: `)).trim()||(e.stt?.provider??""),$=e.stt?.model??"",w=e.stt?.baseUrl??"";S&&($=await u("STT model (verbatim, e.g. whisper-large-v3-turbo / scribe_v1 / nova-2)",$),w=await u("STT base URL (required for openai-compatible; empty = provider default)",w),b(" (API key is not echoed here. Run `ours-mcp voice-setup` for guided hidden input,"),b(" or set OURS_STT_API_KEY for environment-only secret injection.)"));let T=parseInt(f,10);(!Number.isFinite(T)||T<=0)&&(te(`invalid port: ${f}`),process.exit(1));let v=parseInt(h,10);(!Number.isFinite(v)||v<=0)&&(te(`invalid GC interval: ${h}`),process.exit(1));let x=g.toLowerCase()==="true"||g==="1",A=y.trim().toLowerCase();vo.includes(A)||(te(`invalid visibility: ${y} (expected one of ${vo.join(", ")})`),process.exit(1));let B=S?{...e.stt??{},provider:S,...$?{model:$}:{},...w?{baseUrl:w}:{}}:e.stt;n={brokerUrl:d,port:T,stateDir:N_(m),gcIntervalMs:v,autoStart:x,apiVisibility:A,...e.apiToken?{apiToken:e.apiToken}:{},...B?{stt:B}:{}}}finally{r.close()}Xg(n),b(""),b(`wrote ${t} (mode 0600):`);let o={...n,...n.apiToken?{apiToken:"[redacted]"}:{},...n.stt?{stt:{...n.stt,...n.stt.apiKey?{apiKey:"[redacted]"}:{}}}:{}};b(JSON.stringify(o,null,2));let s=["OURS_BROKER_URL","OURS_PORT","OURS_STATE_DIR","OURS_GC_INTERVAL_MS","OURS_AUTOSTART","OURS_API_VISIBILITY","OURS_API_TOKEN","OURS_STT_PROVIDER","OURS_STT_API_KEY","OURS_STT_MODEL","OURS_STT_BASE_URL","OURS_STT_LANGUAGE"].filter(u=>process.env[u]!==void 0);s.length&&(b(""),b(`note: these env vars are set and OVERRIDE the file at runtime: ${s.join(", ")}`));let i=on();if(!i)return;let a=Ws({input:process.stdin,output:process.stdout}),c=!1;try{let u=(await a.question(`
|
|
84
84
|
daemon is running (pid ${i}); restart now to apply? [y/N]: `)).trim().toLowerCase();c=u==="y"||u==="yes"}finally{a.close()}if(!c){b("not restarting \u2014 changes apply on the next `ours-mcp restart`.");return}await Qs();let l=rn(process.execPath,[Bt,"start"],{stdio:"inherit"});l.status!==0&&process.exit(l.status??1)}function XP(e){let t=wo(wt.stt),r=wt.stt?.provider?.trim().toLowerCase()||null,n=process.env.OURS_STT_API_KEY?.trim()?"environment":wt.stt?.apiKey?.trim()?"config":"missing",o=t.ready?{ready:!0,provider:t.provider,apiKey:"configured",keySource:n}:{ready:!1,provider:r,apiKey:n==="missing"?"missing":"configured",keySource:n,reason:t.reason};if(e.includes("--json")){b(JSON.stringify(o));return}o.ready?b(`voice transcription: ready (${o.provider}; API key from ${o.keySource})`):b(`voice transcription: not ready \u2014 ${o.reason}`)}async function eT(e){if(e.includes("--help")||e.includes("-h")){b(S_);return}let t=new Set(["--dry-run"]);if(e.filter(i=>!t.has(i)).length){te("voice-setup: unsupported command-line option or argument"),te("The provider key is never accepted as a CLI argument. Run `ours-mcp voice-setup --help`."),process.exitCode=1;return}let n=on()!==null,o=await Kt(ie),s=await $_({configFile:bo(),env:process.env,dryRun:e.includes("--dry-run"),daemonState:n?"managed":o?"external":"stopped",apply:async()=>{let i=rn(process.execPath,[Bt,"restart"],{stdio:"inherit"});if(i.error||i.status!==0)return{ok:!1};let a=rn(process.execPath,[Bt,"voice-status","--json"],{encoding:"utf8",stdio:["ignore","pipe","ignore"]});if(a.error||a.status!==0)return{ok:!1};try{return{ok:JSON.parse(a.stdout||"{}").ready===!0}}catch{return{ok:!1}}}});s!==0&&(process.exitCode=s)}function qu(e,t,r){return e.includes(r)?{value:!1,set:!0}:e.includes(t)?{value:!0,set:!0}:{value:void 0,set:!1}}function Lu(e,t){let r=e.indexOf(t);return r>=0&&r+1<e.length?e[r+1]:void 0}async function tT(e){let t=Lu(e,"--name"),r=qu(e,"--force-bind","--no-force-bind"),n=qu(e,"--local-book","--no-local-book"),o=qu(e,"--auto-accept-local","--no-auto-accept-local"),s=e.includes("--overwrite"),i=e.includes("--print"),a=Lu(e,"--path")??Lu(e,"--dir")??process.cwd(),c=t!==void 0||r.set||n.set||o.set||i,l;if(c?((!t||!t.trim())&&(te("define-local-identity-file: --name is required in non-interactive mode."),process.exit(1)),l={name:t.trim(),force:r.value??!1,exposeLocal:n.value??!0,localAutoAccept:o.value??!0}):l=await rT(),i){b(JSON.stringify(Fs(l),null,2));return}let u=Cu(a);if(!s&&H.existsSync(u)){c&&(te(`define-local-identity-file: ${u} already exists \u2014 pass --overwrite to replace it.`),process.exit(1));let f=Ws({input:process.stdin,output:process.stdout}),m=!1;try{let h=(await f.question(`
|
|
85
85
|
${u} already exists \u2014 overwrite? [y/N]: `)).trim().toLowerCase();m=h==="y"||h==="yes"}finally{f.close()}if(!m){b("aborted \u2014 nothing written.");return}}let d=e_(a,l,!0);b(""),b(`wrote ${d}:`),b(JSON.stringify(Fs(l),null,2))}async function rT(){b("ours-mcp define-local-identity-file \u2014 interactive"),b(`Answer the prompts; the result is written to ${Ot(process.cwd(),qs)}.`),b("");let e=Ws({input:process.stdin,output:process.stdout});try{let t=async(i,a)=>{let c=a?"Y/n":"y/N",l=(await e.question(` ${i} [${c}]: `)).trim().toLowerCase();return l===""?a:l==="y"||l==="yes"},r="";for(;!r;)r=(await e.question(" Identity name: ")).trim(),r||b(" (name is required)");let n=await t("Force-bind (pin pre-authorizes evicting another session)?",!1),o=await t("Add to the host-local contact book?",!0),s=await t("Auto-accept local invites/introductions?",!0);return{name:r,force:n,exposeLocal:o,localAutoAccept:s}}finally{e.close()}}async function nT(e){let t=(e[0]??"").trim();(!t||t.startsWith("--"))&&(te('usage: ours-mcp create-root "<your name>"'),process.exit(1)),await Kt(ie)||(te(`create-root: the daemon is not running on port ${ie} \u2014 start it with \`ours-mcp start\`.`),process.exit(1));let r=new Xr(new URL(`http://127.0.0.1:${ie}/mcp`),{requestInit:{headers:So()}}),n=new Us({name:"ours-mcp-cli",version:vr});try{await n.connect(r);let o=await n.callTool({name:"create_root_identity",arguments:{name:t,skip_if_root_exists:!0}}),s=(Array.isArray(o.content)?o.content:[]).map(i=>typeof i.text=="string"?i.text:"").join(" ").split(/\n\nAsk the user\b/)[0].replace(/^create_root_identity failed: /,"").trim();if(o.isError){if(/a root identity already exists/i.test(s)){b(`create-root: ${s.split("\u2014")[0].trim()} \u2014 nothing to do.`);return}te(`create-root failed: ${s}`),process.exit(1)}b(s)}finally{try{await r.terminateSession()}catch{}try{await n.close()}catch{}}}function D_(e,t){t.event==="message_received"?b(`[${e}] new message from ${t.from??"?"}`+(t.msg_id!==void 0?` (#${t.msg_id})`:"")+(t.date?` (${t.date})`:"")):t.event==="file_received"?b(`[${e}] new file ${t.filename??"?"} from ${t.from??"?"}`+(t.bytes!==void 0?` (${t.bytes} B)`:"")+(t.file_id!==void 0?` (#${t.file_id})`:"")+(t.date?` (${t.date})`:"")):t.event==="local_contact_request"?b(`[${e}] pending local introduction from ${t.from??"?"} \u2014 respond_to_introduction to approve/reject`):t.event==="pending_message"&&b(`[${e}] ${t.from??"?"} queued a message awaiting introduction approval (${t.queued??"?"} queued)`)}function Ht(e){te(`ours-mcp watch: ${e}`),process.exit(1)}async function oT(){try{let e=new AbortController,t=setTimeout(()=>e.abort(),1500),r=await fetch(`http://127.0.0.1:${ie}/state-dir`,{signal:e.signal});if(clearTimeout(t),r.ok){let n=await r.json();return typeof n.stateDir=="string"&&n.stateDir?N_(n.stateDir):De}}catch{}return null}var sT=e=>`http://127.0.0.1:${ie}/identities/${encodeURIComponent(e)}/notifications`;async function I_(e){let t=async o=>{let s=new AbortController,i=setTimeout(()=>s.abort(),35e3),a;try{a=await fetch(`${sT(e)}?since=${o}`,{headers:So(),signal:s.signal})}finally{clearTimeout(i)}if(a.status===401&&Ht(`daemon rejected the API token (401) for identity "${e}". Set OURS_API_TOKEN to the daemon's token (shared mode) or run watch as the daemon owner (owner mode).`),!a.ok)throw new Error(`daemon returned HTTP ${a.status}`);return await a.json()},r=(await t("tip")).cursor??0,n=0;for(;;)try{let o=await t(String(r));if(n=0,Array.isArray(o.events))for(let s of o.events)D_(e,s);typeof o.cursor=="number"&&(r=o.cursor)}catch(o){n=Math.min(n+1e3,5e3),te(`ours-mcp watch: notifications stream for "${e}" hiccupped (${String(o?.message??o)}), retrying\u2026`),await Xs(n)}}async function iT(e){if(e){await I_(e);return}let t=new Set;for(;;){try{let r=await fetch(`http://127.0.0.1:${ie}/identities`,{headers:So()});if(r.status===401&&Ht("daemon rejected the API token (401). Set OURS_API_TOKEN or run watch as the daemon owner."),r.ok){let n=await r.json();for(let o of n.identities??[]){let s=typeof o?.name=="string"?o.name:void 0;s&&!t.has(s)&&(t.add(s),I_(s))}}}catch{}await Xs(3e3)}}var Fu=e=>{let t=e?.code;return t==="EACCES"||t==="EPERM"};function aT(e,t){try{H.readdirSync(e)}catch(i){Ht(`cannot watch \u2014 the daemon API is unreachable AND ${e} is not readable (${i?.code??i}). On a multi-user host, run watch as the daemon owner, or enable the daemon API (start the daemon) so watch can stream over HTTP.`)}if(t)try{H.readdirSync(Ot(e,t))}catch(i){Ht(`cannot watch identity "${t}" \u2014 ${Ot(e,t)} is not readable (${i?.code??i}). Is the daemon running and the name correct?`)}let r=new Map,n=i=>{let a;try{a=H.readdirSync(e,{withFileTypes:!0}).filter(c=>c.isDirectory()).map(c=>c.name)}catch(c){Fu(c)&&Ht(`lost read access to ${e} (${c.code}) \u2014 cannot watch.`);return}for(let c of a){if(t&&c!==t)continue;let l=Ot(e,c,"notifications.log"),u;try{u=H.statSync(l).size}catch(m){Fu(m)&&Ht(`cannot read ${l} (${m.code}) \u2014 cannot watch identity "${c}".`);continue}let d=r.get(l);if(d===void 0&&(d=i?u:0,r.set(l,d),i))continue;if(u<=d){u<d&&r.set(l,u);continue}let f;try{let m=H.openSync(l,"r"),h=Buffer.alloc(u-d);H.readSync(m,h,0,h.length,d),H.closeSync(m),f=h.toString("utf8")}catch(m){Fu(m)&&Ht(`cannot read ${l} (${m.code}) \u2014 cannot watch identity "${c}".`);continue}r.set(l,u);for(let m of f.split(`
|
package/dist/contacts.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
|
|
2
|
+
function r(t){let n=new Map;for(let e of t)n.set(e.name,(n.get(e.name)??0)+1);return n}function a(t){let n=r(t);return t.map(e=>`\u2022 ${e.name} \u2014 ${e.container_id}${e.rootTag??""}${e.degradedQueued!==void 0?` \u2014 \u26A0 keys pending restore (${e.degradedQueued} queued)`:""}${(n.get(e.name)??0)>1?" \u2014 \u26A0 DUPLICATE NAME: sends to this name abort; address by container id, repair with rename_contact":""}${e.renamedFrom!==void 0?` \u2014 \u26A0 renamed on import (two contacts were named "${e.renamedFrom}"; do NOT assume the one keeping the bare name is the live one \u2014 verify by container id, settle with rename_contact)`:""}`)}export{a as buildContactLines,r as duplicateNameCounts};
|