@north-light/cli 0.3.441 → 0.3.442

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.
@@ -27,6 +27,13 @@ export declare function syncDirectory(path: string): void;
27
27
  * tmux inbox watcher, or crtrd serving a ticket read — sees either the old
28
28
  * bytes or the new ones and never a truncated file mid-write. */
29
29
  export declare function atomicWriteText(path: string, content: string | Uint8Array, opts?: AtomicWriteOptions): void;
30
+ /** Publish `path` with `content` so it is never observable empty or partial, and
31
+ * never replaces an existing file. The bytes reach stable storage under a
32
+ * temporary name first; a hard link then makes them appear at `path` in one
33
+ * step and fails with EEXIST if another initializer won. The containing
34
+ * directory is fsynced so the new entry survives a volume handoff or power loss.
35
+ * Returns false when `path` already existed. */
36
+ export declare function publishFileExclusive(path: string, content: string, mode: number): boolean;
30
37
  /** Fsync every file and directory under `root` (and `root` itself), so a tree about to be renamed into place
31
38
  * reaches stable storage before the rename can. */
32
39
  export declare function syncTree(root: string): void;
@@ -1,3 +1,3 @@
1
- var E=Object.defineProperty;var e=(r,t)=>E(r,"name",{value:t,configurable:!0});import{existsSync as c,lstatSync as W,mkdirSync as b,readdirSync as f,readFileSync as l,rmSync as S,statSync as w,symlinkSync as g,writeFileSync as a,cpSync as T,readlinkSync as I,renameSync as $,chmodSync as L,realpathSync as j,closeSync as F,fsyncSync as J,openSync as O}from"node:fs";import{dirname as u,join as x,relative as C,sep as M}from"node:path";import{homedir as h,platform as k}from"node:os";function D(r){try{return j(r)}catch{return r}}e(D,"realpathOrSelf");function V(r,t=h()){return r==="~"?t:r.startsWith("~/")||k()==="win32"&&r.startsWith("~\\")?x(t,r.slice(2)):r}e(V,"expandTilde");function X(r,t=h()){return r===t?"~":r.startsWith(t+M)?"~"+r.slice(t.length):r}e(X,"tildify");function y(r){b(r,{recursive:!0})}e(y,"ensureDir");function Y(r,t){y(u(r)),a(r,JSON.stringify(t,null,2)+`
2
- `,"utf8")}e(Y,"writeJson");function Z(r,t,n){q(r,JSON.stringify(t,null,2)+`
3
- `,n)}e(Z,"atomicWriteJson");function N(r){const t=O(r,"r");try{J(t)}finally{F(t)}}e(N,"syncFile");function v(r){const t=O(r,"r");try{J(t)}finally{F(t)}}e(v,"syncDirectory");function q(r,t,n){n?.createParent!==!1&&y(u(r));const i=`${r}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`,o=n?.mode??(c(r)?w(r).mode&511:384);try{a(i,t,{mode:o}),L(i,o),n?.durable===!0&&N(i),$(i,r),n?.durable===!0&&v(u(r))}finally{c(i)&&S(i,{force:!0})}}e(q,"atomicWriteText");function z(r){for(const t of f(r,{withFileTypes:!0})){const n=x(r,t.name);t.isDirectory()?z(n):t.isFile()&&N(n)}v(r)}e(z,"syncTree");function A(r){return JSON.parse(l(r,"utf8"))}e(A,"readJson");function _(r){try{return A(r)}catch(t){if(t.code==="ENOENT")return null;throw t}}e(_,"readJsonIfExists");function P(r){try{return JSON.parse(l(r,"utf8"))}catch{return null}}e(P,"readJsonOrNull");function rr(r){return c(r)?l(r,"utf8"):null}e(rr,"readTextIfExists");function tr(r){return l(r,"utf8")}e(tr,"readText");function er(r,t){y(u(r)),a(r,t,"utf8")}e(er,"writeText");function B(r){try{return w(r).isDirectory()}catch{return!1}}e(B,"isDir");function G(r){try{return W(r).isSymbolicLink()}catch{return!1}}e(G,"isSymlink");function nr(r){return c(r)}e(nr,"pathExists");function ir(r){return c(r)?f(r,{withFileTypes:!0}).filter(t=>t.isDirectory()||t.isSymbolicLink()).map(t=>t.name):[]}e(ir,"listDirs");function or(r){return c(r)?f(r):[]}e(or,"listEntries");function H(r){!c(r)&&!G(r)||S(r,{recursive:!0,force:!0})}e(H,"removePath");function cr(r,t,n={}){y(u(t)),H(t);const i=k()==="win32";if(!n.noSymlink&&!i){const o=C(D(u(t)),D(r));try{return g(o,t,B(r)?"dir":"file"),"symlink"}catch{return T(r,t,{recursive:!0}),"copy"}}return T(r,t,{recursive:!0}),"copy"}e(cr,"linkOrCopy");function ur(r){try{return I(r)}catch{return null}}e(ur,"readSymlinkTarget");function sr(r,t=()=>!0,n=()=>!1){const i=[];if(!c(r))return i;const o=[r];for(;o.length;){const m=o.pop();let p;try{p=f(m,{withFileTypes:!0})}catch{continue}for(const s of p){const d=x(m,s.name);s.isDirectory()?n(s.name)||o.push(d):s.isFile()&&t(s.name)&&i.push(d)}}return i}e(sr,"walkFiles");function fr(){return new Date().toISOString()}e(fr,"nowIso");export{Z as atomicWriteJson,q as atomicWriteText,y as ensureDir,V as expandTilde,B as isDir,G as isSymlink,cr as linkOrCopy,ir as listDirs,or as listEntries,fr as nowIso,nr as pathExists,A as readJson,_ as readJsonIfExists,P as readJsonOrNull,ur as readSymlinkTarget,tr as readText,rr as readTextIfExists,D as realpathOrSelf,H as removePath,v as syncDirectory,N as syncFile,z as syncTree,X as tildify,sr as walkFiles,Y as writeJson,er as writeText};
1
+ var D=Object.defineProperty;var e=(r,t)=>D(r,"name",{value:t,configurable:!0});import{existsSync as u,lstatSync as N,mkdirSync as $,readdirSync as y,readFileSync as a,rmSync as F,statSync as k,symlinkSync as b,writeFileSync as m,cpSync as E,readlinkSync as W,renameSync as I,chmodSync as L,realpathSync as M,closeSync as p,fsyncSync as S,openSync as d,linkSync as j,unlinkSync as B,writeSync as C}from"node:fs";import{dirname as f,join as w,relative as X,sep as q}from"node:path";import{homedir as J,platform as O}from"node:os";function g(r){try{return M(r)}catch{return r}}e(g,"realpathOrSelf");function Z(r,t=J()){return r==="~"?t:r.startsWith("~/")||O()==="win32"&&r.startsWith("~\\")?w(t,r.slice(2)):r}e(Z,"expandTilde");function _(r,t=J()){return r===t?"~":r.startsWith(t+q)?"~"+r.slice(t.length):r}e(_,"tildify");function x(r){$(r,{recursive:!0})}e(x,"ensureDir");function P(r,t){x(f(r)),m(r,JSON.stringify(t,null,2)+`
2
+ `,"utf8")}e(P,"writeJson");function rr(r,t,i){z(r,JSON.stringify(t,null,2)+`
3
+ `,i)}e(rr,"atomicWriteJson");function v(r){const t=d(r,"r");try{S(t)}finally{p(t)}}e(v,"syncFile");function h(r){const t=d(r,"r");try{S(t)}finally{p(t)}}e(h,"syncDirectory");function z(r,t,i){i?.createParent!==!1&&x(f(r));const n=`${r}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`,o=i?.mode??(u(r)?k(r).mode&511:384);try{m(n,t,{mode:o}),L(n,o),i?.durable===!0&&v(n),I(n,r),i?.durable===!0&&h(f(r))}finally{u(n)&&F(n,{force:!0})}}e(z,"atomicWriteText");function tr(r,t,i){const n=`${r}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`,o=d(n,"wx",i);try{try{const c=Buffer.from(t,"utf8");for(let s=0;s<c.length;)s+=C(o,c,s,c.length-s);S(o)}finally{p(o)}try{j(n,r)}catch(c){if(c.code==="EEXIST")return!1;throw c}return h(f(r)),!0}finally{try{B(n)}catch{}}}e(tr,"publishFileExclusive");function A(r){for(const t of y(r,{withFileTypes:!0})){const i=w(r,t.name);t.isDirectory()?A(i):t.isFile()&&v(i)}h(r)}e(A,"syncTree");function G(r){return JSON.parse(a(r,"utf8"))}e(G,"readJson");function er(r){try{return G(r)}catch(t){if(t.code==="ENOENT")return null;throw t}}e(er,"readJsonIfExists");function nr(r){try{return JSON.parse(a(r,"utf8"))}catch{return null}}e(nr,"readJsonOrNull");function ir(r){return u(r)?a(r,"utf8"):null}e(ir,"readTextIfExists");function or(r){return a(r,"utf8")}e(or,"readText");function cr(r,t){x(f(r)),m(r,t,"utf8")}e(cr,"writeText");function H(r){try{return k(r).isDirectory()}catch{return!1}}e(H,"isDir");function K(r){try{return N(r).isSymbolicLink()}catch{return!1}}e(K,"isSymlink");function ur(r){return u(r)}e(ur,"pathExists");function sr(r){return u(r)?y(r,{withFileTypes:!0}).filter(t=>t.isDirectory()||t.isSymbolicLink()).map(t=>t.name):[]}e(sr,"listDirs");function fr(r){return u(r)?y(r):[]}e(fr,"listEntries");function Q(r){!u(r)&&!K(r)||F(r,{recursive:!0,force:!0})}e(Q,"removePath");function lr(r,t,i={}){x(f(t)),Q(t);const n=O()==="win32";if(!i.noSymlink&&!n){const o=X(g(f(t)),g(r));try{return b(o,t,H(r)?"dir":"file"),"symlink"}catch{return E(r,t,{recursive:!0}),"copy"}}return E(r,t,{recursive:!0}),"copy"}e(lr,"linkOrCopy");function yr(r){try{return W(r)}catch{return null}}e(yr,"readSymlinkTarget");function ar(r,t=()=>!0,i=()=>!1){const n=[];if(!u(r))return n;const o=[r];for(;o.length;){const c=o.pop();let s;try{s=y(c,{withFileTypes:!0})}catch{continue}for(const l of s){const T=w(c,l.name);l.isDirectory()?i(l.name)||o.push(T):l.isFile()&&t(l.name)&&n.push(T)}}return n}e(ar,"walkFiles");function xr(){return new Date().toISOString()}e(xr,"nowIso");export{rr as atomicWriteJson,z as atomicWriteText,x as ensureDir,Z as expandTilde,H as isDir,K as isSymlink,lr as linkOrCopy,sr as listDirs,fr as listEntries,xr as nowIso,ur as pathExists,tr as publishFileExclusive,G as readJson,er as readJsonIfExists,nr as readJsonOrNull,yr as readSymlinkTarget,or as readText,ir as readTextIfExists,g as realpathOrSelf,Q as removePath,h as syncDirectory,v as syncFile,A as syncTree,_ as tildify,ar as walkFiles,P as writeJson,cr as writeText};
@@ -1,4 +1,4 @@
1
- var X=Object.defineProperty;var l=(t,e)=>X(t,"name",{value:e,configurable:!0});import{DatabaseSync as _}from"node:sqlite";import{randomUUID as N}from"node:crypto";import{symlinkSync as Z,chmodSync as j,chownSync as M,closeSync as $,cpSync as ee,existsSync as d,fsyncSync as R,lchownSync as te,lstatSync as f,mkdirSync as w,openSync as k,readFileSync as y,readlinkSync as D,readSync as U,readdirSync as h,renameSync as b,rmSync as L,rmdirSync as g,unlinkSync as A,writeSync as q}from"node:fs";import{request as ne}from"node:http";import{basename as T,dirname as m,isAbsolute as re,join as c,relative as S,resolve as oe}from"node:path";import{LAYOUT_VERSION as C}from"../layout.js";import{InputError as O}from"../io.js";import{oldToNewPathMap as se}from"./paths.js";import{conversations as ie,memoryAndProfiles as ce,previewStores as ae,profileFolders as ue,repairMovedRepositories as le,worktrees as de}from"./stores.js";import{storedPaths as fe}from"./stored-paths.js";import{OldToNewPathMap as Ce,oldToNewPathMap as Oe}from"./paths.js";const pe=new Set([".DS_Store","Thumbs.db","desktop.ini"]);function he(t){return pe.has(t)||t.startsWith("._")}l(he,"isJunkFile");function F(t,e,n){const o=c(t.map.old.root,"runtime","set-aside",e);let r=c(o,n);for(let i=2;d(r);i++)r=c(o,`${n}.${i}`);return r}l(F,"setAsidePath");function I(t,e,n,o=T(t)){const r=F(n,e,o);return w(m(r),{recursive:!0}),b(t,r),n.moved++,n.changes.push(`${t} -> ${r} (set aside)`),r}l(I,"setAside");function me(t){const e=f(t);return e.isDirectory()?h(t).length===0:e.isFile()&&e.size===0}l(me,"empty");function W(t,e,n,o=!1){const r=f(t),i=f(e);if(r.isDirectory()&&i.isDirectory()&&!o)return{action:"merge"};if(r.isFile()&&i.isFile()&&Q(t,e))return{action:"drop-source"};if(me(e))return{action:"remove-destination",line:`stray-destination: removed empty ${i.isDirectory()?"folder":"file"} ${e} that predates the migration`};const s=F(n,"replaced",S(n.map.old.root,e));return{action:"set-aside-destination",line:`stray-destination: ${e} predates the migration; moved it to ${s} and put ${t} in its place`}}l(W,"claim");function E(t,e,n,o=!1){if(d(t)&&t!==e){if(d(e)){const r=W(t,e,n,o);if(r.line&&n.resolutions.push(r.line),r.action==="merge"){for(const i of h(t))E(c(t,i),c(e,i),n);g(t);return}if(r.action==="drop-source"){L(t,{recursive:!0}),n.changes.push(`${t} (identical to ${e}; removed)`);return}r.action==="remove-destination"?(L(e,{recursive:!0}),n.changes.push(`${e} (removed)`)):I(e,"replaced",n,S(n.map.old.root,e))}w(m(e),{recursive:!0}),b(t,e),n.moved++,n.changes.push(`${t} -> ${e}`)}}l(E,"movePath");function z(t,e,n,o=!1){if(!d(t)||t===e||!d(e))return;const r=W(t,e,n,o);if(r.line&&n.resolutions.push(r.line),r.action==="merge")for(const i of h(t))z(c(t,i),c(e,i),n)}l(z,"previewMove");function B(t){const{map:e}=t,n=c(e.old.canvas,"nodes"),o=c(e.old.canvas,"canvas.db"),r=c(e.old.root,"runtime","canvas.db"),i=d(o)?o:d(r)&&f(r).size>0?r:"";if(!i){if(d(n))throw new Error("old node records exist without canvas.db");return null}const s=new _(i,{readOnly:t.dryRun||i!==o});try{if(i===o&&!t.dryRun&&s.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get().busy!==0)throw new Error("old canvas WAL is busy; stop every database user before migrating");return new Map(s.prepare("SELECT node_id, grantee FROM nodes").all().map(a=>{const u=a.grantee==="user:local"?"terminal":a.grantee.startsWith("app:")?a.grantee.slice(4):"";if(!u)throw new Error(`node ${a.node_id} has no app assignment`);return[a.node_id,u]}))}finally{s.close()}}l(B,"canvasNodeApps");function J(t,e){const n=c(t.map.old.canvas,"nodes");if(!d(n))return[];const o=[];for(const r of h(n)){const i=c(n,r),s=f(i).isDirectory();e.has(r)&&s||o.push({path:i,remove:!s&&he(r)})}return o}l(J,"orphanNodeEntries");function G(t,e){return t.remove?`node-leftover: deleted ${t.path} (not a node record)`:`node-leftover: ${t.path} has no node row; moved to ${F(e,"nodes",T(t.path))}`}l(G,"orphanLine");function ye(t){const{map:e}=t,n=c(e.old.canvas,"nodes"),o=B(t);if(o){for(const[r,i]of o)e.node(r,i);if(d(n)){for(const r of J(t,o)){if(t.resolutions.push(G(r,t)),r.remove){A(r.path),t.changes.push(`${r.path} (removed)`);continue}const i=I(r.path,"nodes",t),s=c(e.old.targetRoot,S(e.old.root,i));for(const a of new Set([e.old.storedCanvas,e.old.storedScope,c(e.old.storedScope,"canvas")]))e.add(c(a,"nodes",T(r.path)),s)}for(const[r,i]of o){const s=c(n,r);if(d(s)){for(const a of["context","reports","attachments"])E(c(s,a),c(e.old.root,"apps",i,"nodes",r,a),t);for(const a of h(s))["worktree","view.sock","broker-launch.json","system-prompt.md","situational-context.md","injected-docs.json"].includes(a)||E(c(s,a),c(e.old.root,"runtime","nodes",r,a),t);h(s).length===0&&g(s)}}h(n).length===0&&g(n)}}}l(ye,"records");const H=["auth.json","anthropic-credentials.json","openai-codex-credentials.json","openai-credentials.json","crouter-operations.db"];function ve(t,e,n){if(!d(t))return;if(!f(t).isFile())throw new Error(`credential source is not a regular file: ${t}`);const o=y(t);if(d(e)){(!f(e).isFile()||!y(e).equals(o))&&n.resolutions.push(`credential: kept the existing ${e}; the older ${t} differs and stays where it is`);return}w(m(e),{recursive:!0});const r=c(m(e),`.${N()}.tmp`);try{const i=k(r,"wx",384);try{for(let s=0;s<o.length;)s+=q(i,o,s,o.length-s);R(i)}finally{$(i)}if(b(r,e),!y(e).equals(o))throw new Error(`credential verification failed: ${e}`)}finally{d(r)&&A(r)}n.moved++,n.changes.push(`${t} -> ${e} (source retained)`)}l(ve,"copyCredential");function K(t){if(!d(t))return;const e=new _(t);try{if(e.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get().busy!==0)throw new Error(`SQLite WAL is busy: ${t}`)}finally{e.close()}}l(K,"checkpointDb");function Q(t,e){if(f(t).size!==f(e).size)return!1;const n=k(t,"r"),o=k(e,"r"),r=Buffer.allocUnsafe(64*1024),i=Buffer.allocUnsafe(r.length);try{for(;;){const s=U(n,r,0,r.length,null);if(s===0)return!0;let a=0;for(;a<s;){const u=U(o,i,a,s-a,null);if(u===0)return!1;a+=u}if(!r.subarray(0,s).equals(i.subarray(0,s)))return!1}}finally{$(n),$(o)}}l(Q,"sameFile");function P(t,e,n=!1){let o;try{o=f(e)}catch(u){if(u.code==="ENOENT")return!1;throw u}const r=f(t);if(r.isSymbolicLink())return o.isSymbolicLink()&&D(t)===D(e);if(r.isFile())return o.isFile()&&Q(t,e);if(!r.isDirectory()||!o.isDirectory())return!1;const i=h(t).filter(u=>!n||u!==".git"),s=h(e).filter(u=>!n||u!==".git"),a=new Set(s);return i.length===s.length&&i.every(u=>a.has(u)&&P(c(t,u),c(e,u),n))}l(P,"sameTree");function we(t,e,n){if(!d(t))return;if(!f(t).isDirectory())throw new Error(`hosted work is not a directory: ${t}`);if(d(e)){if(P(t,e,!0))return;if(!f(e).isDirectory()||h(e).length!==0){n.resolutions.push(`hosted-work: kept the existing ${e}; the old ${t} differs and stays where it is`);return}g(e)}w(m(e),{recursive:!0});const o=c(m(e),`.work-${N()}.migrating`);try{if(ee(t,o,{recursive:!0,verbatimSymlinks:!0,preserveTimestamps:!0}),!P(t,o))throw new Error(`hosted work copy verification failed: ${t}`);b(o,e),n.moved++,n.changes.push(`${t} -> ${e} (source retained)`)}finally{d(o)&&L(o,{recursive:!0,force:!0})}}l(we,"copyHostedWork");function V(t){const{old:e}=t.map,n=c(e.root,"runtime"),o=c(n,"scope"),r=[];for(const i of["canvas.db","canvas.db-wal","canvas.db-shm","on-disk-migrations.json","requests","reviews","plugins","marketplaces","crtrd.log","crtrd.err","cache"])r.push([c(e.canvas,i),c(n,i)]);for(const i of["prompts","personas","config.json","profile-defaults.json","operations.db","operations.db-wal","operations.db-shm"])r.push([c(e.scope,i),c(o,i)]);for(const i of["support","prompt-reviews","plugins","marketplaces"])r.push([c(e.scope,i),c(n,i)]);return r.push([c(e.canvas,"secrets"),c(n,"secrets")]),r}l(V,"topLevelMoves");function be(t){const{old:e}=t.map,n=c(e.root,"runtime");K(c(e.scope,"operations.db"));for(const[o,r]of V(t)){const i=ge(o);E(o,r,t,!0),$e(i,o,r,t)}K(c(e.agent,"crouter-operations.db"));for(const o of H)ve(c(e.agent,o),c(n,"secrets",o),t);process.platform==="linux"&&we(e.work,c(e.root,"user","files"),t)}l(be,"topLevel");function ge(t){if(!d(t)||!f(t).isDirectory())return[];const e=[];for(const n of h(t)){const o=c(t,n);if(!f(o).isSymbolicLink())continue;const r=D(o);re(r)||e.push({name:n,target:r})}return e}l(ge,"relativeLinks");function $e(t,e,n,o){if(!(t.length===0||!d(n)))for(const{name:r,target:i}of t){const s=c(n,r);if(!ke(s))continue;const a=o.map.mapPath(oe(e,i)),u=S(n,a);u!==i&&(A(s),Z(u,s),o.changes.push(`${s}: link ${i} -> ${u}`))}}l($e,"retargetLinks");function ke(t){try{return f(t),!0}catch{return!1}}l(ke,"lstatExists");function Se(t){if(process.platform!=="linux")return;if(process.getuid?.()!==0)throw new Error("Linux layout migration requires root for the ownership pass");const e=y("/etc/passwd","utf8").split(`
2
- `).find(s=>s.startsWith("crtrd:")),n=y("/etc/group","utf8").split(`
3
- `).find(s=>s.startsWith("crtr-data:"));if(!e||!n)throw new Error("layout migration requires crtrd user and crtr-data group");const o=Number(e.split(":")[2]),r=Number(n.split(":")[2]);if(!Number.isInteger(o)||!Number.isInteger(r))throw new Error("invalid migration user or group");const i=l((s,a)=>{if(!d(s))return;const u=f(s);if(u.isSymbolicLink()){te(s,o,a?r:u.gid);return}if(M(s,o,a?r:u.gid),u.isDirectory()){j(s,a?1528:448);for(const v of h(s))i(c(s,v),a)}else a&&j(s,u.mode&73?504:432)},"walk");i(c(t,"runtime"),!1),i(c(t,"user"),!0),i(c(t,"apps"),!0)}l(Se,"ownership");function Ee(t){return new Promise(e=>{const n=ne({socketPath:t,path:"/healthz",method:"GET",timeout:1e3},o=>{o.resume(),e(!0)});n.on("timeout",()=>n.destroy(new Error("daemon probe timed out"))),n.on("error",o=>{o.code==="ENOENT"||o.code==="ECONNREFUSED"?e(!1):e(!0)}),n.end()})}l(Ee,"daemonAnswers");async function Re(t){const e=se(),n=e.old,o=c(n.root,"runtime","layout");if(d(o)){if(y(o,"utf8").trim()!==C)throw new Error("unsupported runtime layout version");return null}const r=new Set([c(n.canvas,"crtrd.sock"),c(n.scope,"crtrd.sock"),process.platform==="linux"?"/run/crtr/api.sock":c(n.root,"runtime","api.sock")]);for(const p of r)if(await Ee(p))throw new O({error:"daemon_running",message:`cannot migrate while a daemon answers on ${p}`,next:"Stop the daemon that owns this volume, then run `crtr sys migrate` again."});if(process.platform==="linux"&&!d(n.agent))throw new O({error:"credential_volume_missing",message:`the old credential volume is not mounted at ${n.agent}`,next:`Mount the old provider-auth volume at ${n.agent}, then run \`crtr sys migrate\` again.`});const i=H.map(p=>c(n.agent,p)).filter(p=>!d(p)),s={map:e,moved:0,changes:[],resolutions:[],bodyPaths:[],absentCredentials:i,dryRun:t};if(t){const p=B(s);if(p)for(const x of J(s,p))s.resolutions.push(G(x,s));for(const[x,Y]of V(s))z(x,Y,s,!0);return ae(s),s}ue(s);const a=[ye,de,be,le,ce,ie,fe];for(const p of a)p(s);n.canvas!==n.root&&d(n.canvas)&&h(n.canvas).length===0&&g(n.canvas),Se(n.root),w(m(o),{recursive:!0});const u=`${o}.${N()}.tmp`,v=k(u,"wx",384);try{if(process.platform==="linux"){const p=f(m(o));M(u,p.uid,p.gid)}q(v,`${C}
4
- `),R(v)}finally{$(v)}return b(u,o),s}l(Re,"migrateLayout");export{Ce as OldToNewPathMap,W as claim,Ee as daemonAnswers,he as isJunkFile,Re as migrateLayout,E as movePath,Oe as oldToNewPathMap,z as previewMove,Q as sameFile,I as setAside,F as setAsidePath};
1
+ var Y=Object.defineProperty;var l=(t,e)=>Y(t,"name",{value:e,configurable:!0});import{DatabaseSync as M}from"node:sqlite";import{randomUUID as D}from"node:crypto";import{symlinkSync as Z,linkSync as ee,chmodSync as _,chownSync as j,closeSync as g,cpSync as te,existsSync as d,fsyncSync as R,lchownSync as ne,lstatSync as f,mkdirSync as v,openSync as S,readFileSync as w,readlinkSync as L,readSync as U,readdirSync as h,renameSync as k,rmSync as T,rmdirSync as b,unlinkSync as $,writeSync as q}from"node:fs";import{request as re}from"node:http";import{basename as A,dirname as y,isAbsolute as oe,join as c,relative as E,resolve as se}from"node:path";import{LAYOUT_VERSION as ie,assertSupportedLayoutMarker as C}from"../layout.js";import{InputError as I}from"../io.js";import{syncDirectory as ce}from"../fs-utils.js";import{oldToNewPathMap as ae}from"./paths.js";import{conversations as ue,memoryAndProfiles as le,previewStores as de,profileFolders as fe,repairMovedRepositories as pe,worktrees as he}from"./stores.js";import{storedPaths as ye}from"./stored-paths.js";import{OldToNewPathMap as ze,oldToNewPathMap as Be}from"./paths.js";const me=new Set([".DS_Store","Thumbs.db","desktop.ini"]);function ve(t){return me.has(t)||t.startsWith("._")}l(ve,"isJunkFile");function F(t,e,n){const r=c(t.map.old.root,"runtime","set-aside",e);let o=c(r,n);for(let i=2;d(o);i++)o=c(r,`${n}.${i}`);return o}l(F,"setAsidePath");function O(t,e,n,r=A(t)){const o=F(n,e,r);return v(y(o),{recursive:!0}),k(t,o),n.moved++,n.changes.push(`${t} -> ${o} (set aside)`),o}l(O,"setAside");function we(t){const e=f(t);return e.isDirectory()?h(t).length===0:e.isFile()&&e.size===0}l(we,"empty");function W(t,e,n,r=!1){const o=f(t),i=f(e);if(o.isDirectory()&&i.isDirectory()&&!r)return{action:"merge"};if(o.isFile()&&i.isFile()&&Q(t,e))return{action:"drop-source"};if(we(e))return{action:"remove-destination",line:`stray-destination: removed empty ${i.isDirectory()?"folder":"file"} ${e} that predates the migration`};const s=F(n,"replaced",E(n.map.old.root,e));return{action:"set-aside-destination",line:`stray-destination: ${e} predates the migration; moved it to ${s} and put ${t} in its place`}}l(W,"claim");function x(t,e,n,r=!1){if(d(t)&&t!==e){if(d(e)){const o=W(t,e,n,r);if(o.line&&n.resolutions.push(o.line),o.action==="merge"){for(const i of h(t))x(c(t,i),c(e,i),n);b(t);return}if(o.action==="drop-source"){T(t,{recursive:!0}),n.changes.push(`${t} (identical to ${e}; removed)`);return}o.action==="remove-destination"?(T(e,{recursive:!0}),n.changes.push(`${e} (removed)`)):O(e,"replaced",n,E(n.map.old.root,e))}v(y(e),{recursive:!0}),k(t,e),n.moved++,n.changes.push(`${t} -> ${e}`)}}l(x,"movePath");function z(t,e,n,r=!1){if(!d(t)||t===e||!d(e))return;const o=W(t,e,n,r);if(o.line&&n.resolutions.push(o.line),o.action==="merge")for(const i of h(t))z(c(t,i),c(e,i),n)}l(z,"previewMove");function B(t){const{map:e}=t,n=c(e.old.canvas,"nodes"),r=c(e.old.canvas,"canvas.db"),o=c(e.old.root,"runtime","canvas.db"),i=d(r)?r:d(o)&&f(o).size>0?o:"";if(!i){if(d(n))throw new Error("old node records exist without canvas.db");return null}const s=new M(i,{readOnly:t.dryRun||i!==r});try{if(i===r&&!t.dryRun&&s.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get().busy!==0)throw new Error("old canvas WAL is busy; stop every database user before migrating");return new Map(s.prepare("SELECT node_id, grantee FROM nodes").all().map(a=>{const u=a.grantee==="user:local"?"terminal":a.grantee.startsWith("app:")?a.grantee.slice(4):"";if(!u)throw new Error(`node ${a.node_id} has no app assignment`);return[a.node_id,u]}))}finally{s.close()}}l(B,"canvasNodeApps");function J(t,e){const n=c(t.map.old.canvas,"nodes");if(!d(n))return[];const r=[];for(const o of h(n)){const i=c(n,o),s=f(i).isDirectory();e.has(o)&&s||r.push({path:i,remove:!s&&ve(o)})}return r}l(J,"orphanNodeEntries");function G(t,e){return t.remove?`node-leftover: deleted ${t.path} (not a node record)`:`node-leftover: ${t.path} has no node row; moved to ${F(e,"nodes",A(t.path))}`}l(G,"orphanLine");function be(t){const{map:e}=t,n=c(e.old.canvas,"nodes"),r=B(t);if(r){for(const[o,i]of r)e.node(o,i);if(d(n)){for(const o of J(t,r)){if(t.resolutions.push(G(o,t)),o.remove){$(o.path),t.changes.push(`${o.path} (removed)`);continue}const i=O(o.path,"nodes",t),s=c(e.old.targetRoot,E(e.old.root,i));for(const a of new Set([e.old.storedCanvas,e.old.storedScope,c(e.old.storedScope,"canvas")]))e.add(c(a,"nodes",A(o.path)),s)}for(const[o,i]of r){const s=c(n,o);if(d(s)){for(const a of["context","reports","attachments"])x(c(s,a),c(e.old.root,"apps",i,"nodes",o,a),t);for(const a of h(s))["worktree","view.sock","broker-launch.json","system-prompt.md","situational-context.md","injected-docs.json"].includes(a)||x(c(s,a),c(e.old.root,"runtime","nodes",o,a),t);h(s).length===0&&b(s)}}h(n).length===0&&b(n)}}}l(be,"records");const H=["auth.json","anthropic-credentials.json","openai-codex-credentials.json","openai-credentials.json","crouter-operations.db"];function ge(t,e,n){if(!d(t))return;if(!f(t).isFile())throw new Error(`credential source is not a regular file: ${t}`);const r=w(t);if(d(e)){(!f(e).isFile()||!w(e).equals(r))&&n.resolutions.push(`credential: kept the existing ${e}; the older ${t} differs and stays where it is`);return}v(y(e),{recursive:!0});const o=c(y(e),`.${D()}.tmp`);try{const i=S(o,"wx",384);try{for(let s=0;s<r.length;)s+=q(i,r,s,r.length-s);R(i)}finally{g(i)}if(k(o,e),!w(e).equals(r))throw new Error(`credential verification failed: ${e}`)}finally{d(o)&&$(o)}n.moved++,n.changes.push(`${t} -> ${e} (source retained)`)}l(ge,"copyCredential");function K(t){if(!d(t))return;const e=new M(t);try{if(e.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get().busy!==0)throw new Error(`SQLite WAL is busy: ${t}`)}finally{e.close()}}l(K,"checkpointDb");function Q(t,e){if(f(t).size!==f(e).size)return!1;const n=S(t,"r"),r=S(e,"r"),o=Buffer.allocUnsafe(64*1024),i=Buffer.allocUnsafe(o.length);try{for(;;){const s=U(n,o,0,o.length,null);if(s===0)return!0;let a=0;for(;a<s;){const u=U(r,i,a,s-a,null);if(u===0)return!1;a+=u}if(!o.subarray(0,s).equals(i.subarray(0,s)))return!1}}finally{g(n),g(r)}}l(Q,"sameFile");function P(t,e,n=!1){let r;try{r=f(e)}catch(u){if(u.code==="ENOENT")return!1;throw u}const o=f(t);if(o.isSymbolicLink())return r.isSymbolicLink()&&L(t)===L(e);if(o.isFile())return r.isFile()&&Q(t,e);if(!o.isDirectory()||!r.isDirectory())return!1;const i=h(t).filter(u=>!n||u!==".git"),s=h(e).filter(u=>!n||u!==".git"),a=new Set(s);return i.length===s.length&&i.every(u=>a.has(u)&&P(c(t,u),c(e,u),n))}l(P,"sameTree");function Se(t,e,n){if(!d(t))return;if(!f(t).isDirectory())throw new Error(`hosted work is not a directory: ${t}`);if(d(e)){if(P(t,e,!0))return;if(!f(e).isDirectory()||h(e).length!==0){n.resolutions.push(`hosted-work: kept the existing ${e}; the old ${t} differs and stays where it is`);return}b(e)}v(y(e),{recursive:!0});const r=c(y(e),`.work-${D()}.migrating`);try{if(te(t,r,{recursive:!0,verbatimSymlinks:!0,preserveTimestamps:!0}),!P(t,r))throw new Error(`hosted work copy verification failed: ${t}`);k(r,e),n.moved++,n.changes.push(`${t} -> ${e} (source retained)`)}finally{d(r)&&T(r,{recursive:!0,force:!0})}}l(Se,"copyHostedWork");function V(t){const{old:e}=t.map,n=c(e.root,"runtime"),r=c(n,"scope"),o=[];for(const i of["canvas.db","canvas.db-wal","canvas.db-shm","on-disk-migrations.json","requests","reviews","plugins","marketplaces","crtrd.log","crtrd.err","cache"])o.push([c(e.canvas,i),c(n,i)]);for(const i of["prompts","personas","config.json","profile-defaults.json","operations.db","operations.db-wal","operations.db-shm"])o.push([c(e.scope,i),c(r,i)]);for(const i of["support","prompt-reviews","plugins","marketplaces"])o.push([c(e.scope,i),c(n,i)]);return o.push([c(e.canvas,"secrets"),c(n,"secrets")]),o}l(V,"topLevelMoves");function ke(t){const{old:e}=t.map,n=c(e.root,"runtime");K(c(e.scope,"operations.db"));for(const[r,o]of V(t)){const i=$e(r);x(r,o,t,!0),Ee(i,r,o,t)}K(c(e.agent,"crouter-operations.db"));for(const r of H)ge(c(e.agent,r),c(n,"secrets",r),t);process.platform==="linux"&&Se(e.work,c(e.root,"user","files"),t)}l(ke,"topLevel");function $e(t){if(!d(t)||!f(t).isDirectory())return[];const e=[];for(const n of h(t)){const r=c(t,n);if(!f(r).isSymbolicLink())continue;const o=L(r);oe(o)||e.push({name:n,target:o})}return e}l($e,"relativeLinks");function Ee(t,e,n,r){if(!(t.length===0||!d(n)))for(const{name:o,target:i}of t){const s=c(n,o);if(!xe(s))continue;const a=r.map.mapPath(se(e,i)),u=E(n,a);u!==i&&($(s),Z(u,s),r.changes.push(`${s}: link ${i} -> ${u}`))}}l(Ee,"retargetLinks");function xe(t){try{return f(t),!0}catch{return!1}}l(xe,"lstatExists");function Ne(t){if(process.platform!=="linux")return;if(process.getuid?.()!==0)throw new Error("Linux layout migration requires root for the ownership pass");const e=w("/etc/passwd","utf8").split(`
2
+ `).find(s=>s.startsWith("crtrd:")),n=w("/etc/group","utf8").split(`
3
+ `).find(s=>s.startsWith("crtr-data:"));if(!e||!n)throw new Error("layout migration requires crtrd user and crtr-data group");const r=Number(e.split(":")[2]),o=Number(n.split(":")[2]);if(!Number.isInteger(r)||!Number.isInteger(o))throw new Error("invalid migration user or group");const i=l((s,a)=>{if(!d(s))return;const u=f(s);if(u.isSymbolicLink()){ne(s,r,a?o:u.gid);return}if(j(s,r,a?o:u.gid),u.isDirectory()){_(s,a?1528:448);for(const m of h(s))i(c(s,m),a)}else a&&_(s,u.mode&73?504:432)},"walk");i(c(t,"runtime"),!1),i(c(t,"user"),!0),i(c(t,"apps"),!0)}l(Ne,"ownership");function De(t){return new Promise(e=>{const n=re({socketPath:t,path:"/healthz",method:"GET",timeout:1e3},r=>{r.resume(),e(!0)});n.on("timeout",()=>n.destroy(new Error("daemon probe timed out"))),n.on("error",r=>{r.code==="ENOENT"||r.code==="ECONNREFUSED"?e(!1):e(!0)}),n.end()})}l(De,"daemonAnswers");async function Ie(t){const e=ae(),n=e.old,r=c(n.root,"runtime","layout");if(d(r))return C(r),null;const o=new Set([c(n.canvas,"crtrd.sock"),c(n.scope,"crtrd.sock"),process.platform==="linux"?"/run/crtr/api.sock":c(n.root,"runtime","api.sock")]);for(const p of o)if(await De(p))throw new I({error:"daemon_running",message:`cannot migrate while a daemon answers on ${p}`,next:"Stop the daemon that owns this volume, then run `crtr sys migrate` again."});if(process.platform==="linux"&&!d(n.agent))throw new I({error:"credential_volume_missing",message:`the old credential volume is not mounted at ${n.agent}`,next:`Mount the old provider-auth volume at ${n.agent}, then run \`crtr sys migrate\` again.`});const i=H.map(p=>c(n.agent,p)).filter(p=>!d(p)),s={map:e,moved:0,changes:[],resolutions:[],bodyPaths:[],absentCredentials:i,dryRun:t};if(t){const p=B(s);if(p)for(const N of J(s,p))s.resolutions.push(G(N,s));for(const[N,X]of V(s))z(N,X,s,!0);return de(s),s}fe(s);const a=[be,he,ke,pe,le,ue,ye];for(const p of a)p(s);n.canvas!==n.root&&d(n.canvas)&&h(n.canvas).length===0&&b(n.canvas),Ne(n.root),v(y(r),{recursive:!0});const u=`${r}.${D()}.tmp`,m=S(u,"wx",384);try{try{if(process.platform==="linux"){const p=f(y(r));j(u,p.uid,p.gid)}q(m,`${ie}
4
+ `),R(m)}finally{g(m)}try{ee(u,r)}catch(p){if(p.code==="EEXIST")C(r);else throw p}}finally{$(u)}return ce(y(r)),s}l(Ie,"migrateLayout");export{ze as OldToNewPathMap,W as claim,De as daemonAnswers,ve as isJunkFile,Ie as migrateLayout,x as movePath,Be as oldToNewPathMap,z as previewMove,Q as sameFile,O as setAside,F as setAsidePath};
@@ -31,6 +31,14 @@ export declare function cronFolder(app: string, cronId: string): string;
31
31
  * and the terminal wherever a sandbox exists, keeps its own app home. Callers
32
32
  * keep `PI_CODING_AGENT_DIR` in the app home either way. */
33
33
  export declare function appProcessHome(app: string): string;
34
+ /** The marker's byte length and an escaped, truncated preview, safe to print: a
35
+ * corrupt marker (empty, truncated, NUL-filled by a lost write) is otherwise
36
+ * indistinguishable from an intentional newer layout in a crash log. */
37
+ export declare function describeLayoutMarker(bytes: Uint8Array): string;
38
+ /** Refuse any marker other than the supported version. A non-matching marker is
39
+ * never rewritten: an empty or damaged one means the volume lost a write, and
40
+ * stamping the current version over it would assert a layout nobody verified. */
41
+ export declare function assertSupportedLayoutMarker(path: string): void;
34
42
  /** A missing version on an occupied volume is never initialized by daemon startup. */
35
43
  export declare function layoutNeedsMigration(): boolean;
36
44
  export declare function initializeEmptyLayout(): void;
@@ -1,2 +1,2 @@
1
- var w=Object.defineProperty;var t=(r,o)=>w(r,"name",{value:o,configurable:!0});import{existsSync as i,mkdirSync as m,readFileSync as E,statSync as R,writeFileSync as _}from"node:fs";import{homedir as S,userInfo as T}from"node:os";import{basename as l,dirname as A,isAbsolute as O,join as e,resolve as f}from"node:path";import{envHomeOverride as $}from"../shared/env.js";import{readConfig as b}from"./config.js";const d="1";function s(){const r=f($()||(process.platform==="linux"?"/":e(S(),".crouter")));if(g()&&r===z())throw new Error(`refusing to use the account's real crouter root ${r} from a node --test process; point CRTR_HOME (not only HOME) at a temporary directory`);return r}t(s,"layoutRoot");function g(){return process.env.NODE_TEST_CONTEXT!==void 0||process.execArgv.includes("--test")}t(g,"runningUnderNodeTest");let c;function z(){if(c===void 0)try{c=f(T().homedir,".crouter")}catch{c=null}return c}t(z,"accountLayoutRoot");function u(){return e(s(),"runtime")}t(u,"runtimeRoot");function h(){return e(s(),"user")}t(h,"userRoot");function x(){return e(s(),"apps")}t(x,"appsRoot");function y(){return e(u(),"secrets")}t(y,"secretsDir");function D(r,o){if(!/^[a-z0-9]+(?:-[a-z0-9]+)*-[0-9a-f]{8}$/.test(o))throw new Error(`invalid profile id: ${o}`);const n=p(r);return{memory:e(n.memory,"profiles",o),env:e(y(),"env",l(n.root),o),home:n.home}}t(D,"profileSpace");function v(){return process.platform==="linux"?"/run/crtr":u()}t(v,"socketDir");function a(){return e(u(),"layout")}t(a,"layoutVersionFile");function F(){const r=b("user").paths?.user_files??(process.platform==="linux"?e(h(),"files"):A(s()));if(!O(r))throw new Error("paths.user_files must be absolute");return f(r)}t(F,"userFilesRoot");function p(r){const o=r.startsWith("app:")?r.slice(4):r;if(!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(o)||o==="."||o==="..")throw new Error(`invalid app id: ${r}`);const n=e(x(),o);return{root:n,memory:e(n,"memory"),conversations:e(n,"conversations"),nodes:e(n,"nodes"),files:e(n,"files"),home:e(n,"home"),cache:e(n,"cache")}}t(p,"appSpace");function M(r,o){if(!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(o))throw new Error(`invalid cron id: ${o}`);return e(p(r).root,"crons",o)}t(M,"cronFolder");function V(r){const o=p(r);return process.platform==="linux"?o.home:l(o.root)==="terminal"?F():o.home}t(V,"appProcessHome");function C(){if(process.env.CRTR_LAYOUT_REFUSE==="1")return!0;if(i(a())){if(E(a(),"utf8").trim()!==d)throw new Error("unsupported runtime layout version");return!1}const r=s(),o=process.platform==="linux"&&r==="/"&&i("/volume")?"/volume":r;return i(e(o,"canvas"))||i(e(o,"canvas.db"))?!0:i(e(u(),"canvas.db"))}t(C,"layoutNeedsMigration");function Y(){if(C())throw new Error("layout_migration_required");for(const r of[u(),h(),x(),y()])m(r,{recursive:!0,mode:448});if(m(v(),{recursive:!0,mode:493}),process.platform==="linux"&&(R(v()).mode&511)!==493)throw new Error("launcher socket directory must be mode 0755");i(a())||_(a(),`${d}
2
- `,{flag:"wx",mode:384})}t(Y,"initializeEmptyLayout");export{d as LAYOUT_VERSION,V as appProcessHome,p as appSpace,x as appsRoot,M as cronFolder,Y as initializeEmptyLayout,C as layoutNeedsMigration,s as layoutRoot,a as layoutVersionFile,D as profileSpace,u as runtimeRoot,y as secretsDir,v as socketDir,F as userFilesRoot,h as userRoot};
1
+ var $=Object.defineProperty;var n=(r,o)=>$(r,"name",{value:o,configurable:!0});import{existsSync as i,mkdirSync as d,readFileSync as S,statSync as R}from"node:fs";import{homedir as _,userInfo as g}from"node:os";import{basename as x,dirname as f,isAbsolute as A,join as t,resolve as p}from"node:path";import{envHomeOverride as T}from"../shared/env.js";import{readConfig as O}from"./config.js";import{publishFileExclusive as z,syncDirectory as h}from"./fs-utils.js";const m="1";function s(){const r=p(T()||(process.platform==="linux"?"/":t(_(),".crouter")));if(C()&&r===L())throw new Error(`refusing to use the account's real crouter root ${r} from a node --test process; point CRTR_HOME (not only HOME) at a temporary directory`);return r}n(s,"layoutRoot");function C(){return process.env.NODE_TEST_CONTEXT!==void 0||process.execArgv.includes("--test")}n(C,"runningUnderNodeTest");let c;function L(){if(c===void 0)try{c=p(g().homedir,".crouter")}catch{c=null}return c}n(L,"accountLayoutRoot");function u(){return t(s(),"runtime")}n(u,"runtimeRoot");function v(){return t(s(),"user")}n(v,"userRoot");function y(){return t(s(),"apps")}n(y,"appsRoot");function w(){return t(u(),"secrets")}n(w,"secretsDir");function Y(r,o){if(!/^[a-z0-9]+(?:-[a-z0-9]+)*-[0-9a-f]{8}$/.test(o))throw new Error(`invalid profile id: ${o}`);const e=l(r);return{memory:t(e.memory,"profiles",o),env:t(w(),"env",x(e.root),o),home:e.home}}n(Y,"profileSpace");function E(){return process.platform==="linux"?"/run/crtr":u()}n(E,"socketDir");function a(){return t(u(),"layout")}n(a,"layoutVersionFile");function k(){const r=O("user").paths?.user_files??(process.platform==="linux"?t(v(),"files"):f(s()));if(!A(r))throw new Error("paths.user_files must be absolute");return p(r)}n(k,"userFilesRoot");function l(r){const o=r.startsWith("app:")?r.slice(4):r;if(!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(o)||o==="."||o==="..")throw new Error(`invalid app id: ${r}`);const e=t(y(),o);return{root:e,memory:t(e,"memory"),conversations:t(e,"conversations"),nodes:t(e,"nodes"),files:t(e,"files"),home:t(e,"home"),cache:t(e,"cache")}}n(l,"appSpace");function q(r,o){if(!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(o))throw new Error(`invalid cron id: ${o}`);return t(l(r).root,"crons",o)}n(q,"cronFolder");function P(r){const o=l(r);return process.platform==="linux"?o.home:x(o.root)==="terminal"?k():o.home}n(P,"appProcessHome");function F(r){const o=Array.from(r.subarray(0,32),e=>e===92?"\\\\":e===34?'\\"':e===10?"\\n":e===13?"\\r":e===9?"\\t":e>=32&&e<127?String.fromCharCode(e):`\\x${e.toString(16).padStart(2,"0")}`).join("");return`${r.length} bytes, preview "${o}"${r.length>32?"\u2026":""}`}n(F,"describeLayoutMarker");function M(r){const o=S(r);if(o.toString("utf8").trim()!==m)throw new Error(`unsupported runtime layout version: ${r} holds ${F(o)}; expected "${m}"`)}n(M,"assertSupportedLayoutMarker");function N(){if(process.env.CRTR_LAYOUT_REFUSE==="1")return!0;if(i(a()))return M(a()),!1;const r=s(),o=process.platform==="linux"&&r==="/"&&i("/volume")?"/volume":r;return i(t(o,"canvas"))||i(t(o,"canvas.db"))?!0:i(t(u(),"canvas.db"))}n(N,"layoutNeedsMigration");function W(){if(N())throw new Error("layout_migration_required");for(const r of[u(),v(),y(),w()]){const o=d(r,{recursive:!0,mode:448});if(o!==void 0){for(let e=r;h(e),e!==o;e=f(e));h(f(o))}}if(d(E(),{recursive:!0,mode:493}),process.platform==="linux"&&(R(E()).mode&511)!==493)throw new Error("launcher socket directory must be mode 0755");i(a())||z(a(),`${m}
2
+ `,384)}n(W,"initializeEmptyLayout");export{m as LAYOUT_VERSION,P as appProcessHome,l as appSpace,y as appsRoot,M as assertSupportedLayoutMarker,q as cronFolder,F as describeLayoutMarker,W as initializeEmptyLayout,N as layoutNeedsMigration,s as layoutRoot,a as layoutVersionFile,Y as profileSpace,u as runtimeRoot,w as secretsDir,E as socketDir,k as userFilesRoot,v as userRoot};
@@ -1 +1 @@
1
- var T=Object.defineProperty;var r=(c,t)=>T(c,"name",{value:t,configurable:!0});import{chmodSync as f,existsSync as a,fsyncSync as u,mkdirSync as F,openSync as m,readFileSync as y,renameSync as L,writeFileSync as S,closeSync as w}from"node:fs";import{dirname as _}from"node:path";import x from"proper-lockfile";const b={encoding:"utf-8",mode:384},E=3e4,O=5e3;class K{static{r(this,"LockedJsonFile")}path;durable;initialContent;constructor(t,e=!1,i="{}"){this.path=t,this.durable=e,this.initialContent=i}readUnlocked(){return a(this.path)?y(this.path,"utf-8"):void 0}ensureFile(){const t=_(this.path);a(t)||F(t,{recursive:!0,mode:448}),a(this.path)||(S(this.path,this.initialContent,b),f(this.path,384))}async withLockAsync(t){this.ensureFile();let e,i;const s=r(()=>{if(i)throw i},"throwIfCompromised");try{e=await x.lock(this.path,{realpath:!1,retries:{retries:10,factor:2,minTimeout:100,maxTimeout:1e4,randomize:!0},stale:E,update:O,onCompromised:r(h=>{i=h},"onCompromised")}),s();const k=a(this.path)?y(this.path,"utf-8"):void 0;let l=!1;const d=r(h=>{if(l)throw new Error(`locked file was published twice: ${this.path}`);s();const o=`${this.path}.${process.pid}.${Date.now()}.tmp`;if(S(o,h,b),f(o,384),this.durable){const n=m(o,"r");try{u(n)}finally{w(n)}}if(L(o,this.path),this.durable){const n=m(_(this.path),"r");try{u(n)}finally{w(n)}}l=!0},"publish"),{result:C,next:p}=await t(k,s,d);return s(),p!==void 0&&d(p),s(),C}finally{if(e)try{await e()}catch{}}}}export{K as LockedJsonFile};
1
+ var k=Object.defineProperty;var o=(c,t)=>k(c,"name",{value:t,configurable:!0});import{chmodSync as f,existsSync as a,fsyncSync as u,mkdirSync as C,openSync as m,readFileSync as y,renameSync as F,writeFileSync as T,closeSync as S}from"node:fs";import{dirname as w}from"node:path";import x from"proper-lockfile";import{publishFileExclusive as E}from"./fs-utils.js";const L={encoding:"utf-8",mode:384},O=3e4,$=5e3;class M{static{o(this,"LockedJsonFile")}path;durable;initialContent;constructor(t,i=!1,e="{}"){this.path=t,this.durable=i,this.initialContent=e}readUnlocked(){return a(this.path)?y(this.path,"utf-8"):void 0}ensureFile(){const t=w(this.path);a(t)||C(t,{recursive:!0,mode:448}),a(this.path)||(E(this.path,this.initialContent,384),f(this.path,384))}async withLockAsync(t){this.ensureFile();let i,e;const s=o(()=>{if(e)throw e},"throwIfCompromised");try{i=await x.lock(this.path,{realpath:!1,retries:{retries:10,factor:2,minTimeout:100,maxTimeout:1e4,randomize:!0},stale:O,update:$,onCompromised:o(h=>{e=h},"onCompromised")}),s();const b=a(this.path)?y(this.path,"utf-8"):void 0;let l=!1;const p=o(h=>{if(l)throw new Error(`locked file was published twice: ${this.path}`);s();const r=`${this.path}.${process.pid}.${Date.now()}.tmp`;if(T(r,h,L),f(r,384),this.durable){const n=m(r,"r");try{u(n)}finally{S(n)}}if(F(r,this.path),this.durable){const n=m(w(this.path),"r");try{u(n)}finally{S(n)}}l=!0},"publish"),{result:_,next:d}=await t(b,s,p);return s(),d!==void 0&&p(d),s(),_}finally{if(i)try{await i()}catch{}}}}export{M as LockedJsonFile};
@@ -1,2 +1,2 @@
1
- var T=Object.defineProperty;var n=(e,o)=>T(e,"name",{value:o,configurable:!0});import{DatabaseSync as p}from"node:sqlite";import{forgetPrepared as R}from"../sqlite-statements.js";import{chmodSync as m,existsSync as s,mkdirSync as y,readdirSync as O,rmSync as I,statSync as P,writeFileSync as v}from"node:fs";import{join as i}from"node:path";import j from"proper-lockfile";import{CONFIG_FILE as S,PROFILE_DIR as N,STATE_FILE as f}from"../../types.js";import{withExclusiveDirectoryLock as _}from"../exclusive-lock.js";import{layoutNeedsMigration as k}from"../layout.js";import{providerStore as C,projectStore as D,userStore as G}from"./paths.js";import{createScopedSchema as g,ensureProviderModelCooldowns as $,PROVIDER_SCHEMA_VERSION as w,SCOPED_SCHEMA_VERSION as d}from"./schema.js";const l=new Map,B=/^[a-z0-9]+(?:-[a-z0-9]+)*-[0-9a-f]{8}$/;function F(e,o,r){e.prepare("UPDATE scope_owners SET root=? WHERE owner_key=?").run(r,o)}n(F,"writeOwnerRoot");function c(e){return Number(e.prepare("PRAGMA user_version").get().user_version)}n(c,"version");function L(e,o){const r=c(e);if(r===0)throw new Error(`scoped SQL store is uninitialized: ${o.path}; crtr was updated and \`crtr sys daemon restart\` converts local state`);const t=o.kind==="provider"?w:d;if(r>t)throw new Error(`scoped SQL store ${o.path} uses unsupported schema version ${r}`);if(r!==t)throw new Error(`scoped SQL store ${o.path} requires an explicit conversion`)}n(L,"assertVersion");function x(e,o){if(o.kind==="provider"){c(e)===w&&(e.exec("PRAGMA busy_timeout = 5000"),$(e));return}if(!(c(e)>=d)){e.exec("PRAGMA busy_timeout = 5000; BEGIN IMMEDIATE");try{c(e)===1&&(e.prepare("PRAGMA table_info(profiles)").all().some(({name:t})=>t==="grantee")||e.exec("ALTER TABLE profiles ADD COLUMN grantee TEXT"),e.exec("PRAGMA user_version = 2")),c(e)===2&&(e.prepare("PRAGMA table_info(profiles)").all().some(({name:t})=>t==="home")&&e.exec("ALTER TABLE profiles DROP COLUMN home"),e.exec("PRAGMA user_version = 3")),c(e)===3&&(e.exec("ALTER TABLE profiles ADD COLUMN delivery_limits_json TEXT NOT NULL DEFAULT '{}' CHECK(json_valid(delivery_limits_json))"),e.exec("PRAGMA user_version = 4")),c(e)===4&&(e.exec("CREATE TABLE IF NOT EXISTS legacy_project_caps (profile_id TEXT NOT NULL, path TEXT NOT NULL, memory TEXT NOT NULL CHECK(memory IN ('none','name','preview')), PRIMARY KEY(profile_id,path))"),e.prepare("PRAGMA table_info(profile_projects)").all().some(({name:t})=>t==="memory")&&e.exec(`INSERT INTO legacy_project_caps SELECT profile_id, path, memory FROM profile_projects WHERE memory != 'content';
2
- ALTER TABLE profile_projects DROP COLUMN memory;`),e.exec(`PRAGMA user_version = ${d}`)),e.exec("COMMIT")}catch(r){throw e.exec("ROLLBACK"),r}}}n(x,"upgradeScopedStore");function ne(e){const o=new p(e.path);try{x(o,e),L(o,e)}finally{o.close()}}n(ne,"assertScopedStoreReady");function U(e){if(!M(e))return;const o=new p(e.path,{readOnly:!0});let r;try{r=c(o)}finally{o.close()}if(r===0)for(const t of[e.path,`${e.path}-wal`,`${e.path}-shm`])I(t,{force:!0})}n(U,"discardIncompleteScopedStore");function H(e,o){return _(i(e,".init.lock"),()=>{const r=i(e,f),t=Date.now()+5e3;let a=10;for(;;){let E;try{E=j.lockSync(r,{realpath:!1,retries:0,stale:3e4,update:1e4})}catch(u){if(u.code!=="ELOCKED")throw u;const A=t-Date.now();if(A<=0)throw new Error(`timed out waiting for project state conversion lock: ${r}`,{cause:u});Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,Math.min(a,A)),a=Math.min(a*2,250);continue}try{return o()}finally{E()}}})}n(H,"withProjectStateLock");function h(e){s(e)&&m(e,384);for(const o of["-wal","-shm"])s(`${e}${o}`)&&m(`${e}${o}`,384)}n(h,"secure");function M(e){return s(e.path)}n(M,"scopedStoreExists");function K(e){if(e.kind==="project")return[i(e.root,f)];if(e.kind==="provider")return[i(e.root,"anthropic-subscriptions.json"),i(e.root,"openai-codex-subscriptions.json"),i(e.root,"provider-rotation.json")];const o=[i(e.root,S),i(e.root,f),i(e.root,"profile-defaults.json")],r=i(e.root,N);if(s(r))for(const t of O(r,{withFileTypes:!0}))t.isDirectory()&&B.test(t.name)&&o.push(i(r,t.name,"profile.json"),i(r,t.name,S),i(r,t.name,f));return o}n(K,"legacyScopedStorePaths");function X(e){const o=e.kind==="user"?G(e.root):e.kind==="project"?D(e.root):C(e.root);o.kind!=="project"&&y(o.root,{recursive:!0,mode:448});const r=n(()=>{if(o.kind==="project"&&U(o),M(o))return;const t=K(o).find(s);if(t!==void 0)throw new Error(`legacy scoped state exists at ${t}; crtr was updated and \`crtr sys daemon restart\` converts local state`);Q(o,a=>{g(a,o.kind),o.kind!=="provider"&&F(a,o.kind,o.root)})},"initialize");o.kind==="project"?H(o.root,r):_(i(o.root,".init.lock"),r)}n(X,"initializeFreshScopedStore");function z(e){if(k())throw new Error("layout_migration_required");const o=l.get(e.path);if(o!==void 0)return o;X(e);const r=new p(e.path);try{return r.exec("PRAGMA busy_timeout = 5000; PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON;"),x(r,e),L(r,e),h(e.path),l.set(e.path,r),r}catch(t){throw r.close(),t}}n(z,"openScopedStore");function Q(e,o){if(s(e.path))throw new Error(`scoped SQL store already exists: ${e.path}`);if(e.kind!=="project"&&y(e.root,{recursive:!0,mode:448}),!P(e.root).isDirectory())throw new Error(`scoped SQL root is not a directory: ${e.root}`);(e.kind!=="project"||process.platform!=="linux")&&m(e.root,448),v(e.path,"",{mode:384});const r=new p(e.path);try{r.exec("PRAGMA busy_timeout = 5000; PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; BEGIN IMMEDIATE;"),o(r),r.exec("COMMIT")}catch(t){try{r.exec("ROLLBACK")}catch{}throw t}finally{h(e.path),r.close()}}n(Q,"createScopedStore");function ie(e,o){const r=z(e);if(r.isTransaction)return o(r);r.exec("BEGIN IMMEDIATE");try{const t=o(r);return r.exec("COMMIT"),h(e.path),t}catch(t){try{r.exec("ROLLBACK")}catch{}throw t}}n(ie,"withScopedWrite");function ce(){for(const e of l.values())R(e),e.close();l.clear()}n(ce,"closeScopedStores");export{B as PROFILE_ID_SHAPE,ne as assertScopedStoreReady,ce as closeScopedStores,Q as createScopedStore,U as discardIncompleteScopedStore,X as initializeFreshScopedStore,K as legacyScopedStorePaths,z as openScopedStore,M as scopedStoreExists,H as withProjectStateLock,ie as withScopedWrite,F as writeOwnerRoot};
1
+ var T=Object.defineProperty;var n=(e,o)=>T(e,"name",{value:o,configurable:!0});import{DatabaseSync as p}from"node:sqlite";import{forgetPrepared as R}from"../sqlite-statements.js";import{chmodSync as m,existsSync as s,mkdirSync as y,readdirSync as O,rmSync as I,statSync as P,writeFileSync as v}from"node:fs";import{join as i}from"node:path";import j from"proper-lockfile";import{CONFIG_FILE as S,PROFILE_DIR as N,STATE_FILE as f}from"../../types.js";import{withExclusiveDirectoryLock as _}from"../exclusive-lock.js";import{syncDirectory as k,syncFile as D}from"../fs-utils.js";import{layoutNeedsMigration as C}from"../layout.js";import{providerStore as G,projectStore as g,userStore as $}from"./paths.js";import{createScopedSchema as B,ensureProviderModelCooldowns as F,PROVIDER_SCHEMA_VERSION as w,SCOPED_SCHEMA_VERSION as d}from"./schema.js";const l=new Map,U=/^[a-z0-9]+(?:-[a-z0-9]+)*-[0-9a-f]{8}$/;function H(e,o,r){e.prepare("UPDATE scope_owners SET root=? WHERE owner_key=?").run(r,o)}n(H,"writeOwnerRoot");function c(e){return Number(e.prepare("PRAGMA user_version").get().user_version)}n(c,"version");function L(e,o){const r=c(e);if(r===0)throw new Error(`scoped SQL store is uninitialized: ${o.path}; crtr was updated and \`crtr sys daemon restart\` converts local state`);const t=o.kind==="provider"?w:d;if(r>t)throw new Error(`scoped SQL store ${o.path} uses unsupported schema version ${r}`);if(r!==t)throw new Error(`scoped SQL store ${o.path} requires an explicit conversion`)}n(L,"assertVersion");function x(e,o){if(o.kind==="provider"){c(e)===w&&(e.exec("PRAGMA busy_timeout = 5000"),F(e));return}if(!(c(e)>=d)){e.exec("PRAGMA busy_timeout = 5000; BEGIN IMMEDIATE");try{c(e)===1&&(e.prepare("PRAGMA table_info(profiles)").all().some(({name:t})=>t==="grantee")||e.exec("ALTER TABLE profiles ADD COLUMN grantee TEXT"),e.exec("PRAGMA user_version = 2")),c(e)===2&&(e.prepare("PRAGMA table_info(profiles)").all().some(({name:t})=>t==="home")&&e.exec("ALTER TABLE profiles DROP COLUMN home"),e.exec("PRAGMA user_version = 3")),c(e)===3&&(e.exec("ALTER TABLE profiles ADD COLUMN delivery_limits_json TEXT NOT NULL DEFAULT '{}' CHECK(json_valid(delivery_limits_json))"),e.exec("PRAGMA user_version = 4")),c(e)===4&&(e.exec("CREATE TABLE IF NOT EXISTS legacy_project_caps (profile_id TEXT NOT NULL, path TEXT NOT NULL, memory TEXT NOT NULL CHECK(memory IN ('none','name','preview')), PRIMARY KEY(profile_id,path))"),e.prepare("PRAGMA table_info(profile_projects)").all().some(({name:t})=>t==="memory")&&e.exec(`INSERT INTO legacy_project_caps SELECT profile_id, path, memory FROM profile_projects WHERE memory != 'content';
2
+ ALTER TABLE profile_projects DROP COLUMN memory;`),e.exec(`PRAGMA user_version = ${d}`)),e.exec("COMMIT")}catch(r){throw e.exec("ROLLBACK"),r}}}n(x,"upgradeScopedStore");function ae(e){const o=new p(e.path);try{x(o,e),L(o,e)}finally{o.close()}}n(ae,"assertScopedStoreReady");function K(e){if(!M(e))return;const o=new p(e.path,{readOnly:!0});let r;try{r=c(o)}finally{o.close()}if(r===0)for(const t of[e.path,`${e.path}-wal`,`${e.path}-shm`])I(t,{force:!0})}n(K,"discardIncompleteScopedStore");function X(e,o){return _(i(e,".init.lock"),()=>{const r=i(e,f),t=Date.now()+5e3;let a=10;for(;;){let E;try{E=j.lockSync(r,{realpath:!1,retries:0,stale:3e4,update:1e4})}catch(u){if(u.code!=="ELOCKED")throw u;const A=t-Date.now();if(A<=0)throw new Error(`timed out waiting for project state conversion lock: ${r}`,{cause:u});Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,Math.min(a,A)),a=Math.min(a*2,250);continue}try{return o()}finally{E()}}})}n(X,"withProjectStateLock");function h(e){s(e)&&m(e,384);for(const o of["-wal","-shm"])s(`${e}${o}`)&&m(`${e}${o}`,384)}n(h,"secure");function M(e){return s(e.path)}n(M,"scopedStoreExists");function z(e){if(e.kind==="project")return[i(e.root,f)];if(e.kind==="provider")return[i(e.root,"anthropic-subscriptions.json"),i(e.root,"openai-codex-subscriptions.json"),i(e.root,"provider-rotation.json")];const o=[i(e.root,S),i(e.root,f),i(e.root,"profile-defaults.json")],r=i(e.root,N);if(s(r))for(const t of O(r,{withFileTypes:!0}))t.isDirectory()&&U.test(t.name)&&o.push(i(r,t.name,"profile.json"),i(r,t.name,S),i(r,t.name,f));return o}n(z,"legacyScopedStorePaths");function Q(e){const o=e.kind==="user"?$(e.root):e.kind==="project"?g(e.root):G(e.root);o.kind!=="project"&&y(o.root,{recursive:!0,mode:448});const r=n(()=>{if(o.kind==="project"&&K(o),M(o))return;const t=z(o).find(s);if(t!==void 0)throw new Error(`legacy scoped state exists at ${t}; crtr was updated and \`crtr sys daemon restart\` converts local state`);V(o,a=>{B(a,o.kind),o.kind!=="provider"&&H(a,o.kind,o.root)})},"initialize");o.kind==="project"?X(o.root,r):_(i(o.root,".init.lock"),r)}n(Q,"initializeFreshScopedStore");function W(e){if(C())throw new Error("layout_migration_required");const o=l.get(e.path);if(o!==void 0)return o;Q(e);const r=new p(e.path);try{return r.exec("PRAGMA busy_timeout = 5000; PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON;"),x(r,e),L(r,e),h(e.path),l.set(e.path,r),r}catch(t){throw r.close(),t}}n(W,"openScopedStore");function V(e,o){if(s(e.path))throw new Error(`scoped SQL store already exists: ${e.path}`);if(e.kind!=="project"&&y(e.root,{recursive:!0,mode:448}),!P(e.root).isDirectory())throw new Error(`scoped SQL root is not a directory: ${e.root}`);(e.kind!=="project"||process.platform!=="linux")&&m(e.root,448),v(e.path,"",{mode:384});const r=new p(e.path);try{r.exec("PRAGMA busy_timeout = 5000; PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON; BEGIN IMMEDIATE;"),o(r),r.exec("COMMIT")}catch(t){try{r.exec("ROLLBACK")}catch{}throw t}finally{h(e.path),r.close()}D(e.path),k(e.root)}n(V,"createScopedStore");function se(e,o){const r=W(e);if(r.isTransaction)return o(r);r.exec("BEGIN IMMEDIATE");try{const t=o(r);return r.exec("COMMIT"),h(e.path),t}catch(t){try{r.exec("ROLLBACK")}catch{}throw t}}n(se,"withScopedWrite");function pe(){for(const e of l.values())R(e),e.close();l.clear()}n(pe,"closeScopedStores");export{U as PROFILE_ID_SHAPE,ae as assertScopedStoreReady,pe as closeScopedStores,V as createScopedStore,K as discardIncompleteScopedStore,Q as initializeFreshScopedStore,z as legacyScopedStorePaths,W as openScopedStore,M as scopedStoreExists,X as withProjectStateLock,se as withScopedWrite,H as writeOwnerRoot};
@@ -21,10 +21,9 @@ export interface ScopedApiToken {
21
21
  created: string;
22
22
  }
23
23
  export declare function readSecrets(scope: Scope): SecretsStore;
24
- /** Writes secrets.json directly (never via `fs-utils.writeJson`, which has no
25
- * mode control) so the file is created 0600 from the first byte, then
26
- * `chmodSync`s belt-and-suspenders in case the file pre-existed with looser
27
- * permissions (e.g. hand-copied from another machine). */
24
+ /** Replaces secrets.json whole (temp file, fsync, rename, directory fsync) at mode 0600 -- forced even
25
+ * over a pre-existing looser file, e.g. one hand-copied from another machine -- so a killed VM or a
26
+ * volume handoff never leaves the relay tokens empty or partial. */
28
27
  export declare function writeSecrets(scope: Scope, store: SecretsStore): void;
29
28
  export declare function getRelayToken(ref: string, scope: Scope): string | undefined;
30
29
  export declare function setRelayToken(ref: string, token: string, scope: Scope): void;
@@ -1,2 +1 @@
1
- var f=Object.defineProperty;var t=(e,o)=>f(e,"name",{value:o,configurable:!0});import{createHash as u}from"node:crypto";import{existsSync as y,readFileSync as d,writeFileSync as l,chmodSync as A}from"node:fs";import{join as m}from"node:path";import{scopeRoot as S,requireScopeRoot as g,userScopeRoot as a}from"./scope.js";import{secretsDir as k}from"./layout.js";import{ensureDir as x}from"./fs-utils.js";const h="secrets.json";function T(e){return m(e===a()?k():e,h)}t(T,"secretsPathFor");function s(e){const o=S(e);if(!o)return{relayTokens:{}};const n=T(o);if(!y(n))return{relayTokens:{}};try{const r=JSON.parse(d(n,"utf8")),c=Array.isArray(r.scopedApiTokens)?r.scopedApiTokens.filter(i=>typeof i?.token=="string"&&i.token!==""&&typeof i.grantee=="string"&&/^app:[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(i.grantee)&&Array.isArray(i.scopes)&&typeof i.created=="string"):[];return{relayTokens:r.relayTokens??{},...typeof r.apiToken=="string"&&r.apiToken!==""?{apiToken:r.apiToken}:{},...c.length>0?{scopedApiTokens:c}:{}}}catch{return{relayTokens:{}}}}t(s,"readSecrets");function p(e,o){const n=g(e),r=T(n);x(n===a()?k():n),l(r,JSON.stringify(o,null,2)+`
2
- `,{mode:384}),A(r,384)}t(p,"writeSecrets");function J(e,o){return s(o).relayTokens[e]}t(J,"getRelayToken");function N(e,o,n){const r=s(n);r.relayTokens[e]=o,p(n,r)}t(N,"setRelayToken");function O(e,o){const n=s(o);delete n.relayTokens[e],p(o,n)}t(O,"deleteRelayToken");function Z(e){return s(e).apiToken}t(Z,"getApiToken");function _(e,o){const n=s(o);n.apiToken=e,p(o,n)}t(_,"setApiToken");function q(e){return s(e).scopedApiTokens??[]}t(q,"listScopedApiTokens");function C(e,o){const n=s(o);n.scopedApiTokens=[...n.scopedApiTokens??[],e],p(o,n)}t(C,"addScopedApiToken");function H(e){return u("sha256").update(e).digest("hex").slice(0,12)}t(H,"fingerprint");export{C as addScopedApiToken,O as deleteRelayToken,H as fingerprint,Z as getApiToken,J as getRelayToken,q as listScopedApiTokens,s as readSecrets,_ as setApiToken,N as setRelayToken,p as writeSecrets};
1
+ var u=Object.defineProperty;var t=(e,o)=>u(e,"name",{value:o,configurable:!0});import{createHash as f}from"node:crypto";import{existsSync as y,readFileSync as d}from"node:fs";import{join as l}from"node:path";import{scopeRoot as A,requireScopeRoot as m,userScopeRoot as a}from"./scope.js";import{secretsDir as k}from"./layout.js";import{atomicWriteJson as x,ensureDir as g}from"./fs-utils.js";const S="secrets.json";function T(e){return l(e===a()?k():e,S)}t(T,"secretsPathFor");function s(e){const o=A(e);if(!o)return{relayTokens:{}};const r=T(o);if(!y(r))return{relayTokens:{}};try{const n=JSON.parse(d(r,"utf8")),c=Array.isArray(n.scopedApiTokens)?n.scopedApiTokens.filter(i=>typeof i?.token=="string"&&i.token!==""&&typeof i.grantee=="string"&&/^app:[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(i.grantee)&&Array.isArray(i.scopes)&&typeof i.created=="string"):[];return{relayTokens:n.relayTokens??{},...typeof n.apiToken=="string"&&n.apiToken!==""?{apiToken:n.apiToken}:{},...c.length>0?{scopedApiTokens:c}:{}}}catch{return{relayTokens:{}}}}t(s,"readSecrets");function p(e,o){const r=m(e),n=T(r);g(r===a()?k():r),x(n,o,{mode:384,durable:!0})}t(p,"writeSecrets");function J(e,o){return s(o).relayTokens[e]}t(J,"getRelayToken");function Z(e,o,r){const n=s(r);n.relayTokens[e]=o,p(r,n)}t(Z,"setRelayToken");function _(e,o){const r=s(o);delete r.relayTokens[e],p(o,r)}t(_,"deleteRelayToken");function b(e){return s(e).apiToken}t(b,"getApiToken");function q(e,o){const r=s(o);r.apiToken=e,p(o,r)}t(q,"setApiToken");function w(e){return s(e).scopedApiTokens??[]}t(w,"listScopedApiTokens");function C(e,o){const r=s(o);r.scopedApiTokens=[...r.scopedApiTokens??[],e],p(o,r)}t(C,"addScopedApiToken");function H(e){return f("sha256").update(e).digest("hex").slice(0,12)}t(H,"fingerprint");export{C as addScopedApiToken,_ as deleteRelayToken,H as fingerprint,b as getApiToken,J as getRelayToken,w as listScopedApiTokens,s as readSecrets,q as setApiToken,Z as setRelayToken,p as writeSecrets};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.441",
3
+ "version": "0.3.442",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -137,7 +137,7 @@
137
137
  },
138
138
  "license": "UNLICENSED",
139
139
  "dependencies": {
140
- "@north-light/identity": "0.3.441",
140
+ "@north-light/identity": "0.3.442",
141
141
  "@earendil-works/pi-agent-core": "1.1.0",
142
142
  "@earendil-works/pi-ai": "1.1.0",
143
143
  "@earendil-works/pi-coding-agent": "1.1.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.441",
3
+ "version": "0.3.442",
4
4
  "description": "Northlight access-token verification and scope rules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.441",
3
+ "version": "0.3.442",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/cli",
9
- "version": "0.3.441",
9
+ "version": "0.3.442",
10
10
  "hasInstallScript": true,
11
11
  "license": "UNLICENSED",
12
12
  "workspaces": [
@@ -18,7 +18,7 @@
18
18
  "@earendil-works/pi-ai": "1.1.0",
19
19
  "@earendil-works/pi-coding-agent": "1.1.0",
20
20
  "@earendil-works/pi-tui": "1.1.0",
21
- "@north-light/identity": "0.3.441",
21
+ "@north-light/identity": "0.3.442",
22
22
  "cron-parser": "^5.6.0",
23
23
  "esbuild": "^0.27.7",
24
24
  "jose": "^6.2.12",
@@ -12095,17 +12095,17 @@
12095
12095
  },
12096
12096
  "packages/crouter-api": {
12097
12097
  "name": "@north-light/api",
12098
- "version": "0.3.441",
12098
+ "version": "0.3.442",
12099
12099
  "license": "UNLICENSED"
12100
12100
  },
12101
12101
  "packages/crouter-env-docker": {
12102
12102
  "name": "@north-light/env-docker",
12103
- "version": "0.3.441",
12103
+ "version": "0.3.442",
12104
12104
  "license": "UNLICENSED"
12105
12105
  },
12106
12106
  "packages/crouter-identity": {
12107
12107
  "name": "@north-light/identity",
12108
- "version": "0.3.441",
12108
+ "version": "0.3.442",
12109
12109
  "license": "UNLICENSED",
12110
12110
  "dependencies": {
12111
12111
  "jose": "^6.2.1"
@@ -12113,20 +12113,20 @@
12113
12113
  },
12114
12114
  "packages/crouter-plugin": {
12115
12115
  "name": "@north-light/plugin",
12116
- "version": "0.3.441",
12116
+ "version": "0.3.442",
12117
12117
  "license": "UNLICENSED",
12118
12118
  "dependencies": {
12119
- "@north-light/api": "0.3.441",
12120
- "@north-light/identity": "0.3.441"
12119
+ "@north-light/api": "0.3.442",
12120
+ "@north-light/identity": "0.3.442"
12121
12121
  }
12122
12122
  },
12123
12123
  "packages/crouter-sdk": {
12124
12124
  "name": "@north-light/sdk",
12125
- "version": "0.3.441",
12125
+ "version": "0.3.442",
12126
12126
  "license": "UNLICENSED",
12127
12127
  "dependencies": {
12128
- "@north-light/api": "0.3.441",
12129
- "@north-light/identity": "0.3.441",
12128
+ "@north-light/api": "0.3.442",
12129
+ "@north-light/identity": "0.3.442",
12130
12130
  "jose": "^6.2.1"
12131
12131
  },
12132
12132
  "bin": {