@ours.network/mcp 1.1.1 → 1.2.0-nightly.2
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/README.md +83 -0
- package/dist/application-identities.js +2 -2
- package/dist/cli.js +5 -4
- package/dist/connector.js +37 -36
- package/dist/container.js +6 -0
- package/dist/host-client.js +3 -0
- package/dist/host-profile.js +1 -0
- package/dist/native-session.js +2 -0
- package/dist/network-integration.js +82 -0
- package/dist/server.js +31 -31
- package/package.json +9 -4
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
var C=Object.defineProperty;var b=(e,t)=>()=>(e&&(t=e(e=0)),t);var $=(e,t)=>{for(var i in t)C(e,i,{get:t[i],enumerable:!0})};var O={};$(O,{APPLICATION_IDENTITIES_ENV:()=>v,APPLICATION_IDENTITIES_VERSION:()=>w,ApplicationIdentityStore:()=>g,applicationIdentityConfigPath:()=>E,filterApplicationIdentities:()=>K});import{randomUUID as k}from"node:crypto";import{chmod as D,mkdir as x,readFile as L,rename as J,unlink as j,writeFile as M}from"node:fs/promises";import{homedir as F}from"node:os";import{dirname as G,resolve as f}from"node:path";function E(e=process.env){let t=(e[v]??"").trim();return f(t||f(F(),".ours-mcp","config.json"))}function I(e,t,i,n){if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`Invalid ours-mcp application identity config at ${t}: "${i}s" must be an object.`);let r={};for(let[a,o]of Object.entries(e)){if(!o||typeof o!="object"||Array.isArray(o))throw new Error(`Invalid ours-mcp application identity config at ${t}: ${i} ${JSON.stringify(a)} must be an object.`);let s=o;if(!Array.isArray(s.identities)||s.identities.some(p=>typeof p!="string"||p.length===0))throw new Error(`Invalid ours-mcp application identity config at ${t}: ${i} ${JSON.stringify(a)} has an invalid identity list.`);r[n(a)]={identities:[...new Set(s.identities)].sort()}}return r}function W(e,t){let i;try{i=JSON.parse(e)}catch(o){throw new Error(`Invalid ours-mcp application identity config at ${t}: ${String(o)}`)}if(!i||typeof i!="object"||Array.isArray(i))throw new Error(`Invalid ours-mcp application identity config at ${t}: expected an object.`);let n=i;if(n.version!==w)throw new Error(`Unsupported ours-mcp application identity config version at ${t}: expected ${w}, found ${String(n.version)}.`);let r=I(n.daemons,t,"daemon",f),a=n.instances===void 0?{}:I(n.instances,t,"instance",o=>{if(!S.test(o))throw new Error(`Invalid ours-mcp application identity config at ${t}: instance ${JSON.stringify(o)} must be a lowercase UUID.`);return o});return{version:1,daemons:r,instances:a}}async function y(e){try{return W(await L(e,"utf8"),e)}catch(t){if(t.code==="ENOENT")return V();throw t}}async function h(e,t){let i=G(e);await x(i,{recursive:!0,mode:448});let n=`${e}.${process.pid}.${k()}.tmp`;try{await M(n,`${JSON.stringify(t,null,2)}
|
|
2
|
+
`,{mode:384,flag:"wx"}),await J(n,e),await D(e,384)}catch(r){try{await j(n)}catch{}throw r}}async function K(e,t){let i=new Set(await e.list());return t.filter(n=>i.has(n.name))}var w,v,S,V,g,A=b(()=>{"use strict";w=1,v="OURS_MCP_CONFIG",S=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,V=()=>({version:1,daemons:{},instances:{}});g=class{stateDir;instanceId;path;constructor(t,i={}){if(typeof t=="string")this.stateDir=f(t),this.instanceId=null;else{if(!t||!S.test(t.instanceId))throw new Error("Application identity instanceId must be a lowercase UUID.");this.stateDir=null,this.instanceId=t.instanceId}this.path=f(i.path??E(i.env))}rows(t){return this.instanceId===null?t.daemons:t.instances}key(){return this.instanceId??this.stateDir}async list(){let t=await y(this.path);return[...this.rows(t)[this.key()]?.identities??[]]}async has(t){return(await this.list()).includes(t)}async add(t){if(!t)throw new Error("Cannot add an empty identity name to ours-mcp.");let i=await y(this.path),n=this.rows(i),r=this.key(),a=n[r]?.identities??[];a.includes(t)||(n[r]={identities:[...a,t].sort()},await h(this.path,i))}async remove(t){let i=await y(this.path),n=this.rows(i),r=this.key(),a=n[r];if(!a?.identities.includes(t))return;let o=a.identities.filter(s=>s!==t);o.length===0?delete n[r]:n[r]={identities:o},await h(this.path,i)}}});import{readFileSync as Y,createReadStream as q,createWriteStream as z,mkdirSync as N,rmSync as R}from"node:fs";import{basename as B,join as l}from"node:path";import{pipeline as P}from"node:stream/promises";import{randomUUID as H}from"node:crypto";import{fileURLToPath as Q}from"node:url";var[m,c="proxy",...d]=process.argv.slice(2);if(!m||m!==process.env.OURS_DAEMON_ID)throw new Error("MCP target does not match the selected daemon");var u=l(process.env.OURS_STATE_DIR||"/var/lib/ours",".mcp"),T=l(u,"profile.json"),U=JSON.parse(Y(T,"utf8"));if(U.expectedInstanceId!==m)throw new Error("MCP profile does not match the selected daemon");for(let e of["OURS_API_TOKEN","OURS_PORT","OURS_STATE_DIR","OURS_DAEMON_ID","OURS_CLIENT_PID","OURS_AUTOSTART"])delete process.env[e];process.env.OURS_CONFIG=T;process.env.OURS_MCP_CONFIG=l(u,"config.json");process.env.OURS_FILES_ALWAYS_PROMPT="1";var _=new URL("./cli.js",import.meta.url),X=Q(_);if(["file-stage","file-target","file-read","file-remove"].includes(c)){let[e,t]=d;if(!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(e??""))throw new Error("Invalid file transfer ID");if(d.length!==(c==="file-stage"?2:1))throw new Error("Invalid file transfer arguments");let i=l(u,"transfers"),n=l(i,e);if(c==="file-remove")R(n,{recursive:!0,force:!0});else if(c==="file-read")await P(q(l(n,"file")),process.stdout);else{if(c==="file-stage"&&(!t||B(t)!==t||[".",".."].includes(t)))throw new Error("Invalid transfer filename");N(i,{recursive:!0,mode:448}),N(n,{mode:448});let r=l(n,c==="file-stage"?t:"file");try{c==="file-stage"&&await P(process.stdin,z(r,{flags:"wx",mode:384})),process.stdout.write(JSON.stringify({path:r})+`
|
|
3
|
+
`)}catch(a){throw R(n,{recursive:!0,force:!0}),a}}}else if(["proxy","session-end","version"].includes(c))process.argv=[process.execPath,X,c,...d],await import(_.href);else if(["application-identities","hook-state","watch"].includes(c)){let{ApplicationIdentityStore:e}=await Promise.resolve().then(()=>(A(),O)),i=await new e({instanceId:m}).list();if(c==="application-identities")process.stdout.write(JSON.stringify(i)+`
|
|
4
|
+
`);else{let{attachOursClient:n}=await import("@ours.network/sdk"),r=await n({...U,sessionMode:"external",leaseToken:H(),env:{}});try{if(c==="hook-state"){let a=await r.listIdentities(),o=await r.unread();process.stdout.write(JSON.stringify({identities:a.map(s=>s.name),bindings:a.filter(s=>"session"in s&&(s.session==="mine"||s.session==="other-live")).map(s=>s.name),unread:{identities:o.identities.filter(s=>typeof s.name=="string"&&i.includes(s.name))}})+`
|
|
5
|
+
`)}else{if(d.length>1||d[0]?.startsWith("-"))throw new Error("Usage: watch [identity]");let a=d.length?[d[0]]:i,o=new AbortController;process.stdin.once("end",()=>o.abort()),process.stdin.resume();for(let s of["SIGINT","SIGTERM"])process.once(s,()=>o.abort());await Promise.all(a.map(async s=>{for await(let p of r.watchNotifications(s,{kinds:["inbound"],signal:o.signal}))process.stdout.write(JSON.stringify({identity:s,...p})+`
|
|
6
|
+
`)}))}}finally{await r.close()}}}else throw new Error("Unsupported container MCP operation");
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{existsSync as it,lstatSync as U,readFileSync as st}from"node:fs";import{homedir as J}from"node:os";import{resolve as I}from"node:path";var O=["endpoint","expectedInstanceId","credentialPath"],at=["port","stateDir","apiToken","apiVisibility"],ct=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,dt=["OURS_API_TOKEN","OURS_PORT","OURS_STATE_DIR","OURS_DAEMON_ID"];function l(t){return new Error(`Invalid external host profile: ${t}`)}function w(t){if(!t||typeof t!="object"||Array.isArray(t))throw l("expected a complete host profile object.");let r=t;if(O.filter(d=>Object.hasOwn(r,d)).length!==O.length)throw l("expected a complete host profile tuple: endpoint, expectedInstanceId, credentialPath.");let n=at.filter(d=>Object.hasOwn(r,d));if(n.length)throw l(`legacy selection keys cannot be mixed with a host profile (${n.join(", ")}).`);let o=r.endpoint,i=r.expectedInstanceId,s=r.credentialPath;if(typeof o!="string"||o.trim()!==o||o==="")throw l("endpoint must be a non-empty HTTP origin.");if(typeof i!="string"||!ct.test(i))throw l("expectedInstanceId must be a lowercase UUID.");if(typeof s!="string"||s===""||!s.startsWith("/")||I(s)!==s)throw l("credentialPath must be a normalized absolute path.");let a;try{a=new URL(o)}catch{throw l("endpoint must be an HTTP origin.")}if(a.protocol!=="http:"||a.username||a.password||a.pathname!=="/"||a.search||a.hash)throw l("endpoint must be an HTTP origin without credentials, path, query, or fragment.");return{endpoint:a.origin,expectedInstanceId:i,credentialPath:s}}function lt(t){let r;try{r=st(t,"utf8")}catch{throw new Error(`Cannot read host profile ${JSON.stringify(t)}.`)}let e;try{e=JSON.parse(r)}catch{throw l(`config ${JSON.stringify(t)} is not valid JSON.`)}if(!e||typeof e!="object"||Array.isArray(e))throw l(`config ${JSON.stringify(t)} must be an object.`);return e}function ft(t){let r;try{r=U(t)}catch{throw new Error(`Cannot read host profile ${JSON.stringify(t)}.`)}if(!r.isFile())throw l(`config ${JSON.stringify(t)} must be a regular file.`);let e=process.getuid?.();if(e===void 0||r.uid!==e)throw l(`config ${JSON.stringify(t)} must be owned by the current user.`);if(r.mode&63)throw l(`config ${JSON.stringify(t)} must have private permissions.`)}function C(t){let r=lt(t);return O.filter(n=>Object.hasOwn(r,n)).length===0?null:(ft(t),w(r))}function ut(t,r){return(t[r]??"").trim()!==""}function A(t=process.env){let r=(t.OURS_CONFIG??"").trim(),e=I(t.HOME||J(),".ours-client","profile.json"),n=r;if(!n)try{U(e),n=e}catch(s){if(s.code!=="ENOENT")throw new Error(`Cannot read host profile ${JSON.stringify(e)}.`);if(n=I(J(),".ours","config.json"),!it(n))return null}let o=C(n);if(o===null){if(I(n)===e)throw l("managed client config must contain a complete host profile tuple.");return null}let i=dt.filter(s=>ut(t,s));if(i.length)throw l(`${i.join(", ")} conflicts with host-profile mode.`);return{profile:o,configPath:n}}function D(t=process.env){return A(t)?.profile??null}import{createReadStream as pt,createWriteStream as mt,mkdirSync as yt,statSync as L}from"node:fs";import{basename as wt,dirname as gt,isAbsolute as ht,resolve as q}from"node:path";import{isDeepStrictEqual as vt}from"node:util";import{buildIdentityFile as It,writeIdentityFile as Et}from"@ours.network/sdk/connector";import{Readable as G}from"node:stream";import{pipeline as j}from"node:stream/promises";function $(t){return t.reason instanceof Error?t.reason:new Error(String(t.reason??"operation cancelled"))}function g(t){if(t?.aborted)throw $(t)}function W(t){return t.method!=="tools/call"||!t.params||typeof t.params!="object"?null:t.params}async function St(t,r,e){g(e);let n=W(r);if(n?.name!=="send_file"||typeof n.arguments?.path!="string")return r;let o=n.arguments,i=o.path;if(o.upload_id!==void 0||o.data_base64!==void 0)throw new Error("send_file accepts exactly one host file input.");let s=q(i),a=L(s).size,d=typeof o.filename=="string"&&o.filename?o.filename:wt(s),R=typeof o.mime=="string"?o.mime:void 0,f=pt(s),p=()=>f.destroy($(e));e?.addEventListener("abort",p,{once:!0});let c;try{let u=G.toWeb(f);c=await t.uploadFile(u,{filename:d,mime:R,size:a}),g(e)}finally{e?.removeEventListener("abort",p),e?.aborted&&f.destroy()}let y={...o,upload_id:c.upload_id,filename:d};return delete y.path,{...r,params:{...r.params,arguments:y}}}async function bt(t,r,e,n){g(n);let o=W(r);if(o?.name==="define_local_identity_file"){let f=e.result;if(f?.isError===!0)return e;let p=o.arguments??{},c={name:p.name,path:p.path,force:p.force??!1,expose_local:p.expose_local??!0,local_auto_accept:p.local_auto_accept??!0,overwrite:p.overwrite??!1},y=f?.structuredContent;if(!vt(y?.oursHostIdentityFile,c)||typeof c.name!="string"||typeof c.path!="string"||!ht(c.path)||[c.force,c.expose_local,c.local_auto_accept,c.overwrite].some(u=>typeof u!="boolean"))throw new Error("define_local_identity_file returned an invalid or mismatched host intent.");try{let u={name:c.name,force:c.force,exposeLocal:c.expose_local,localAutoAccept:c.local_auto_accept},N=Et(c.path,u,c.overwrite);return{...e,result:{content:[{type:"text",text:`Wrote ${N}:
|
|
2
|
+
${JSON.stringify(It(u),null,2)}`}],isError:!1}}}catch(u){return{...e,result:{content:[{type:"text",text:`define_local_identity_file failed: ${u instanceof Error?u.message:String(u)}`}],isError:!0}}}}if(o?.name!=="save_file")return e;let i=e.result,a=i?.structuredContent?.oursHostSave;if(i?.isError===!0||!a)return e;let d=o.arguments?.wire_id,R=o.arguments?.dest_path;if(a.wire_id!==d||a.dest_path!==R||typeof a.wire_id!="string"||typeof a.dest_path!="string")throw new Error("save_file returned a host intent that does not match the request.");try{let f=t.openFile(a.wire_id),p=n?await new Promise((ot,T)=>{let H=!1,_=()=>{H=!0,T($(n))};n.addEventListener("abort",_,{once:!0}),f.then(v=>{n.removeEventListener("abort",_),H?v.cancel().catch(()=>{}):ot(v)},v=>{n.removeEventListener("abort",_),T(v)})}):await f;g(n);let c=q(a.dest_path);yt(gt(c),{recursive:!0});let y=G.fromWeb(p),u=mt(c);n?await j(y,u,{signal:n}):await j(y,u),g(n);let N=L(c).size;return{...e,result:{content:[{type:"text",text:`Saved file (wire_id ${a.wire_id}) to ${c} (${N} bytes). The bytes were streamed daemon\u2192disk and never entered this result.`}],isError:!1}}}catch(f){return g(n),{...e,result:{content:[{type:"text",text:`save_file failed: ${f instanceof Error?f.message:String(f)}`}],isError:!0}}}}import{createHash as $t,randomUUID as B}from"node:crypto";import{chmod as K,mkdir as kt,readFile as Ft,rename as Tt,unlink as Ht,writeFile as Jt}from"node:fs/promises";import{dirname as Ut,join as Dt,resolve as jt}from"node:path";import{attachOursClient as Lt}from"@ours.network/sdk";import{createHash as Pt}from"node:crypto";import{openSync as xt,closeSync as Rt,lstatSync as k,mkdirSync as Nt,chmodSync as _t,constants as E}from"node:fs";import{join as M}from"node:path";import{createRequire as z}from"node:module";var Ot=5e3,h=null,V=!1;function Ct(){if(h)return h;if(V)throw new Error("No SQLite implementation available for native session guard");V=!0;try{return h=z(import.meta.url)("node:sqlite").DatabaseSync,h}catch{try{let t=z(import.meta.url)("better-sqlite3");return h=t.default??t,h}catch{throw new Error("Native session guard requires node:sqlite (Node >=22.13) or better-sqlite3")}}}function At(t){try{let e=xt(t,E.O_RDWR|E.O_CREAT|E.O_EXCL|E.O_NOFOLLOW,384);Rt(e)}catch(e){if(e.code!=="EEXIST")throw e}let r=k(t);if(!r.isFile()||r.uid!==process.getuid()||r.mode&63||r.nlink!==1)throw new Error("Native session guard file must be an owner-only regular file")}function Y(t){let r=process.getuid(),e=M(`/tmp/ours-${r}`,"session-guards");Nt(e,{recursive:!0,mode:448});try{_t(e,448)}catch{}let n=Pt("sha256").update(t).digest("hex"),o=M(e,`${n}.guard`);At(o);let i=k(o),s=Ct(),a=new s(o);try{let d=k(o);if(i.dev!==d.dev||i.ino!==d.ino||i.mode!==d.mode||i.uid!==d.uid)throw new Error("Native session guard file changed during acquisition");return a.exec(`PRAGMA busy_timeout=${Ot}; BEGIN EXCLUSIVE`),()=>{try{a.close()}catch{}}}catch(d){try{a.close()}catch{}throw d}}var P=1,Q=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,m=new Map,S=new Map;function qt(t){if(typeof t!="string"||t.length===0||t.length>4096||t.trim()!==t||t.includes("\0"))throw new Error("Native session metadata is missing or invalid for this host-profile tool call.");return t}function x(t,r,e){if(!t||!t.startsWith("/")||jt(t)!==t)throw new Error("Native session host record root must be a normalized absolute path.");if(!Q.test(r))throw new Error("Native session daemon instance must be a lowercase UUID.");let n=$t("sha256").update(qt(e),"utf8").digest("hex");return Dt(t,"sessions",r,`${n}.json`)}function Gt(t,r){let e;try{e=JSON.parse(t)}catch(i){throw new Error(`Invalid native session record at ${r}: ${String(i)}`)}if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`Invalid native session record at ${r}: expected an object.`);let n=e;if(Object.keys(n).sort().join(",")!=="ownerInstanceId,state,version"||n.version!==P||typeof n.ownerInstanceId!="string"||!Q.test(n.ownerInstanceId)||!["active","pending","ended"].includes(String(n.state)))throw new Error(`Invalid native session record at ${r}.`);return n}async function b(t){try{return Gt(await Ft(t,"utf8"),t)}catch(r){if(r.code==="ENOENT")return null;throw r}}async function F(t,r){let e=Ut(t);await kt(e,{recursive:!0,mode:448}),await K(e,448);let n=`${t}.${process.pid}.${B()}.tmp`;try{await Jt(n,`${JSON.stringify(r,null,2)}
|
|
3
|
+
`,{mode:384,flag:"wx"}),await Tt(n,t),await K(t,384)}catch(o){try{await Ht(n)}catch{}throw o}}async function Z(t,r){let e=S.get(t)??Promise.resolve(),n,o=new Promise(a=>{n=a}),i=e.catch(()=>{}).then(()=>o);S.set(t,i),await e.catch(()=>{});let s;try{return s=Y(t),await r()}finally{try{s?.()}finally{n(),S.get(t)===i&&S.delete(t)}}}function tt(t,r){return Lt({endpoint:t.endpoint,expectedInstanceId:t.expectedInstanceId,credentialPath:t.credentialPath,sessionMode:"external",leaseToken:r,env:{}})}function X(t,r,e){let n=m.get(t);if(n?.ownerInstanceId===e)return n.promise;n&&(m.delete(t),n.promise.then(s=>s.close()).catch(()=>{}));let o=tt(r,e),i={ownerInstanceId:e,promise:o};return m.set(t,i),o.catch(()=>{m.get(t)===i&&m.delete(t)}),o}async function Wt(t,r,e){let n=m.get(t),o=n?.ownerInstanceId===e?await n.promise:await tt(r,e);try{await o.releaseLease()}finally{n?.ownerInstanceId!==e&&await o.close()}}async function et(t,r,e){await Wt(t,r,e);let n=await b(t);n?.state==="pending"&&n.ownerInstanceId===e&&await F(t,{version:P,ownerInstanceId:e,state:"ended"});let o=m.get(t);o?.ownerInstanceId===e&&(m.delete(t),o.promise.then(i=>i.close()).catch(()=>{}))}async function nt(t,r,e){let n=w(t),o=x(e,n.expectedInstanceId,r);return Z(o,async()=>{let i=await b(o);if(i?.state==="pending"&&(await et(o,n,i.ownerInstanceId),i=await b(o)),i?.state==="active")return X(o,n,i.ownerInstanceId);let s=B();return await F(o,{version:P,ownerInstanceId:s,state:"active"}),X(o,n,s)})}async function rt(t,r,e){let n=w(t),o=x(e,n.expectedInstanceId,r);await Z(o,async()=>{let i=await b(o);i===null||i.state==="ended"||(i.state==="active"&&(i={version:P,ownerInstanceId:i.ownerInstanceId,state:"pending"},await F(o,i)),await et(o,n,i.ownerInstanceId))})}export{bt as completeHostFileCall,rt as endNativeSessionAtRoot,D as hostProfileFromEnv,A as hostProfileSelectionFromEnv,nt as nativeClientForAtRoot,x as nativeSessionRecordPath,St as prepareHostFileCall,C as readHostProfile,w as validateHostProfile};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{existsSync as h,lstatSync as u,readFileSync as g}from"node:fs";import{homedir as d}from"node:os";import{resolve as l}from"node:path";var p=["endpoint","expectedInstanceId","credentialPath"],m=["port","stateDir","apiToken","apiVisibility"],w=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,y=["OURS_API_TOKEN","OURS_PORT","OURS_STATE_DIR","OURS_DAEMON_ID"];function r(t){return new Error(`Invalid external host profile: ${t}`)}function O(t){if(!t||typeof t!="object"||Array.isArray(t))throw r("expected a complete host profile object.");let e=t;if(p.filter(f=>Object.hasOwn(e,f)).length!==p.length)throw r("expected a complete host profile tuple: endpoint, expectedInstanceId, credentialPath.");let o=m.filter(f=>Object.hasOwn(e,f));if(o.length)throw r(`legacy selection keys cannot be mixed with a host profile (${o.join(", ")}).`);let s=e.endpoint,a=e.expectedInstanceId,i=e.credentialPath;if(typeof s!="string"||s.trim()!==s||s==="")throw r("endpoint must be a non-empty HTTP origin.");if(typeof a!="string"||!w.test(a))throw r("expectedInstanceId must be a lowercase UUID.");if(typeof i!="string"||i===""||!i.startsWith("/")||l(i)!==i)throw r("credentialPath must be a normalized absolute path.");let c;try{c=new URL(s)}catch{throw r("endpoint must be an HTTP origin.")}if(c.protocol!=="http:"||c.username||c.password||c.pathname!=="/"||c.search||c.hash)throw r("endpoint must be an HTTP origin without credentials, path, query, or fragment.");return{endpoint:c.origin,expectedInstanceId:a,credentialPath:i}}function S(t){let e;try{e=g(t,"utf8")}catch{throw new Error(`Cannot read host profile ${JSON.stringify(t)}.`)}let n;try{n=JSON.parse(e)}catch{throw r(`config ${JSON.stringify(t)} is not valid JSON.`)}if(!n||typeof n!="object"||Array.isArray(n))throw r(`config ${JSON.stringify(t)} must be an object.`);return n}function E(t){let e;try{e=u(t)}catch{throw new Error(`Cannot read host profile ${JSON.stringify(t)}.`)}if(!e.isFile())throw r(`config ${JSON.stringify(t)} must be a regular file.`);let n=process.getuid?.();if(n===void 0||e.uid!==n)throw r(`config ${JSON.stringify(t)} must be owned by the current user.`);if(e.mode&63)throw r(`config ${JSON.stringify(t)} must have private permissions.`)}function I(t){let e=S(t);return p.filter(o=>Object.hasOwn(e,o)).length===0?null:(E(t),O(e))}function N(t,e){return(t[e]??"").trim()!==""}function b(t=process.env){let e=(t.OURS_CONFIG??"").trim(),n=l(t.HOME||d(),".ours-client","profile.json"),o=e;if(!o)try{u(n),o=n}catch(i){if(i.code!=="ENOENT")throw new Error(`Cannot read host profile ${JSON.stringify(n)}.`);if(o=l(d(),".ours","config.json"),!h(o))return null}let s=I(o);if(s===null){if(l(o)===n)throw r("managed client config must contain a complete host profile tuple.");return null}let a=y.filter(i=>N(t,i));if(a.length)throw r(`${a.join(", ")} conflicts with host-profile mode.`);return{profile:s,configPath:o}}function J(t=process.env){return b(t)?.profile??null}export{J as hostProfileFromEnv,b as hostProfileSelectionFromEnv,I as readHostProfile,O as validateHostProfile};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{createHash as z,randomUUID as $}from"node:crypto";import{chmod as x,mkdir as K,readFile as X,rename as B,unlink as Q,writeFile as Z}from"node:fs/promises";import{dirname as I,join as tt,resolve as et}from"node:path";import{attachOursClient as nt}from"@ours.network/sdk";import{homedir as U}from"node:os";import{dirname as lt,resolve as E}from"node:path";var _="OURS_MCP_CONFIG";function w(t=process.env){let e=(t[_]??"").trim();return E(e||E(U(),".ours-mcp","config.json"))}import{resolve as j}from"node:path";var S=["endpoint","expectedInstanceId","credentialPath"],J=["port","stateDir","apiToken","apiVisibility"],H=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;function d(t){return new Error(`Invalid external host profile: ${t}`)}function y(t){if(!t||typeof t!="object"||Array.isArray(t))throw d("expected a complete host profile object.");let e=t;if(S.filter(c=>Object.hasOwn(e,c)).length!==S.length)throw d("expected a complete host profile tuple: endpoint, expectedInstanceId, credentialPath.");let i=J.filter(c=>Object.hasOwn(e,c));if(i.length)throw d(`legacy selection keys cannot be mixed with a host profile (${i.join(", ")}).`);let r=e.endpoint,o=e.expectedInstanceId,s=e.credentialPath;if(typeof r!="string"||r.trim()!==r||r==="")throw d("endpoint must be a non-empty HTTP origin.");if(typeof o!="string"||!H.test(o))throw d("expectedInstanceId must be a lowercase UUID.");if(typeof s!="string"||s===""||!s.startsWith("/")||j(s)!==s)throw d("credentialPath must be a normalized absolute path.");let a;try{a=new URL(r)}catch{throw d("endpoint must be an HTTP origin.")}if(a.protocol!=="http:"||a.username||a.password||a.pathname!=="/"||a.search||a.hash)throw d("endpoint must be an HTTP origin without credentials, path, query, or fragment.");return{endpoint:a.origin,expectedInstanceId:o,credentialPath:s}}import{createHash as L}from"node:crypto";import{openSync as F,closeSync as q,lstatSync as h,mkdirSync as G,chmodSync as M,constants as u}from"node:fs";import{join as P}from"node:path";import{createRequire as N}from"node:module";var V=5e3,f=null,O=!1;function W(){if(f)return f;if(O)throw new Error("No SQLite implementation available for native session guard");O=!0;try{return f=N(import.meta.url)("node:sqlite").DatabaseSync,f}catch{try{let t=N(import.meta.url)("better-sqlite3");return f=t.default??t,f}catch{throw new Error("Native session guard requires node:sqlite (Node >=22.13) or better-sqlite3")}}}function Y(t){try{let n=F(t,u.O_RDWR|u.O_CREAT|u.O_EXCL|u.O_NOFOLLOW,384);q(n)}catch(n){if(n.code!=="EEXIST")throw n}let e=h(t);if(!e.isFile()||e.uid!==process.getuid()||e.mode&63||e.nlink!==1)throw new Error("Native session guard file must be an owner-only regular file")}function b(t){let e=process.getuid(),n=P(`/tmp/ours-${e}`,"session-guards");G(n,{recursive:!0,mode:448});try{M(n,448)}catch{}let i=L("sha256").update(t).digest("hex"),r=P(n,`${i}.guard`);Y(r);let o=h(r),s=W(),a=new s(r);try{let c=h(r);if(o.dev!==c.dev||o.ino!==c.ino||o.mode!==c.mode||o.uid!==c.uid)throw new Error("Native session guard file changed during acquisition");return a.exec(`PRAGMA busy_timeout=${V}; BEGIN EXCLUSIVE`),()=>{try{a.close()}catch{}}}catch(c){try{a.close()}catch{}throw c}}var m=1,R=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,l=new Map,p=new Map;function it(t){if(typeof t!="string"||t.length===0||t.length>4096||t.trim()!==t||t.includes("\0"))throw new Error("Native session metadata is missing or invalid for this host-profile tool call.");return t}function A(t,e,n){if(!t||!t.startsWith("/")||et(t)!==t)throw new Error("Native session host record root must be a normalized absolute path.");if(!R.test(e))throw new Error("Native session daemon instance must be a lowercase UUID.");let i=z("sha256").update(it(n),"utf8").digest("hex");return tt(t,"sessions",e,`${i}.json`)}function rt(t,e){let n;try{n=JSON.parse(t)}catch(o){throw new Error(`Invalid native session record at ${e}: ${String(o)}`)}if(!n||typeof n!="object"||Array.isArray(n))throw new Error(`Invalid native session record at ${e}: expected an object.`);let i=n;if(Object.keys(i).sort().join(",")!=="ownerInstanceId,state,version"||i.version!==m||typeof i.ownerInstanceId!="string"||!R.test(i.ownerInstanceId)||!["active","pending","ended"].includes(String(i.state)))throw new Error(`Invalid native session record at ${e}.`);return i}async function g(t){try{return rt(await X(t,"utf8"),t)}catch(e){if(e.code==="ENOENT")return null;throw e}}async function v(t,e){let n=I(t);await K(n,{recursive:!0,mode:448}),await x(n,448);let i=`${t}.${process.pid}.${$()}.tmp`;try{await Z(i,`${JSON.stringify(e,null,2)}
|
|
2
|
+
`,{mode:384,flag:"wx"}),await B(i,t),await x(t,384)}catch(r){try{await Q(i)}catch{}throw r}}async function T(t,e){let n=p.get(t)??Promise.resolve(),i,r=new Promise(a=>{i=a}),o=n.catch(()=>{}).then(()=>r);p.set(t,o),await n.catch(()=>{});let s;try{return s=b(t),await e()}finally{try{s?.()}finally{i(),p.get(t)===o&&p.delete(t)}}}function k(t,e){return nt({endpoint:t.endpoint,expectedInstanceId:t.expectedInstanceId,credentialPath:t.credentialPath,sessionMode:"external",leaseToken:e,env:{}})}function C(t,e,n){let i=l.get(t);if(i?.ownerInstanceId===n)return i.promise;i&&(l.delete(t),i.promise.then(s=>s.close()).catch(()=>{}));let r=k(e,n),o={ownerInstanceId:n,promise:r};return l.set(t,o),r.catch(()=>{l.get(t)===o&&l.delete(t)}),r}async function ot(t,e,n){let i=l.get(t),r=i?.ownerInstanceId===n?await i.promise:await k(e,n);try{await r.releaseLease()}finally{i?.ownerInstanceId!==n&&await r.close()}}async function D(t,e,n){await ot(t,e,n);let i=await g(t);i?.state==="pending"&&i.ownerInstanceId===n&&await v(t,{version:m,ownerInstanceId:n,state:"ended"});let r=l.get(t);r?.ownerInstanceId===n&&(l.delete(t),r.promise.then(o=>o.close()).catch(()=>{}))}async function bt(t,e,n=process.env){return st(t,e,I(w(n)))}async function st(t,e,n){let i=y(t),r=A(n,i.expectedInstanceId,e);return T(r,async()=>{let o=await g(r);if(o?.state==="pending"&&(await D(r,i,o.ownerInstanceId),o=await g(r)),o?.state==="active")return C(r,i,o.ownerInstanceId);let s=$();return await v(r,{version:m,ownerInstanceId:s,state:"active"}),C(r,i,s)})}async function xt(t,e,n=process.env){return at(t,e,I(w(n)))}async function at(t,e,n){let i=y(t),r=A(n,i.expectedInstanceId,e);await T(r,async()=>{let o=await g(r);o===null||o.state==="ended"||(o.state==="active"&&(o={version:m,ownerInstanceId:o.ownerInstanceId,state:"pending"},await v(r,o)),await D(r,i,o.ownerInstanceId))})}export{xt as endNativeSession,at as endNativeSessionAtRoot,bt as nativeClientFor,st as nativeClientForAtRoot,A as nativeSessionRecordPath};
|