← Files DXIRARCHIVED FILE
scripts/project.mjs
11.4 KB · Nov 9, 2026 · 01:16 UTC
import r from"node:fs/promises";import a from"node:path";import k from"node:os";import{createHash as S,randomUUID as y}from"node:crypto";import{pathToFileURL as b}from"node:url";import{artifact as N,uploadArtifact as O}from"./journal.mjs";const p=t=>{if(!/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(t||""))throw new Error("invalid_id");return t},f=t=>S("sha256").update(t).digest("hex"),h=(t,i)=>{const e=a.relative(t,i);if(e===".."||e.startsWith(`..${a.sep}`)||a.isAbsolute(e))throw new Error("path_outside_project")};async function x(t,i=k.homedir()){if(typeof t!="string"||!t.trim()||t!==t.trim()||t.length>100||/^[.]/.test(t)||/[<>:"/\\|?*\x00-\x1f\x7f]/.test(t)||/[. ]$/.test(t)||/^(con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³])(?:\.|$)/i.test(t))throw new Error("invalid_folder_name");if(!a.isAbsolute(i))throw new Error("absolute_parent_required");const e=await r.realpath(i);if(!(await r.stat(e)).isDirectory())throw new Error("invalid_parent_directory");const n=a.join(e,t);h(e,n);try{throw await r.lstat(n),new Error("folder_already_exists")}catch(o){if(o.code!=="ENOENT")throw o}return{status:"awaiting_folder_approval",folder_path:n,host_project_status:"not_verified"}}async function F(t,i,e){if(!e)throw new Error("folder_approval_required");const n=await x(t,i);if(e!==n.folder_path)throw new Error("folder_approval_path_mismatch");try{await r.mkdir(n.folder_path,{mode:448})}catch(s){throw s.code==="EEXIST"?new Error("folder_already_exists"):s}const o=await r.lstat(n.folder_path);if(o.isSymbolicLink()||!o.isDirectory())throw new Error("unsafe_project_directory");if(await r.realpath(n.folder_path)!==n.folder_path)throw new Error("project_location_changed");return{...n,status:"folder_created"}}async function d(t,i){let e=t;for(const n of i){e=a.join(e,n);try{await r.mkdir(e)}catch(s){if(s.code!=="EEXIST")throw s}const o=await r.lstat(e);if(o.isSymbolicLink()||!o.isDirectory())throw new Error("unsafe_project_directory");if(h(t,await r.realpath(e)),e===a.join(t,".dxir"))try{await r.writeFile(a.join(e,".gitignore"),`*
`,{flag:"wx"})}catch(s){if(s.code!=="EEXIST")throw s}}return e}async function _(t,i){try{await r.writeFile(t,i,{flag:"wx",mode:384})}catch(e){if(e.code!=="EEXIST")throw e;if((await r.lstat(t)).isSymbolicLink()||!(await r.readFile(t)).equals(Buffer.from(i)))throw new Error("existing_file_differs")}}async function v(t,i,e,n){t=await r.realpath(t),p(i),p(e);const o=await d(t,[".dxir"]);await _(a.join(o,"location.json"),JSON.stringify({location_key:f(process.platform==="win32"?t.toLowerCase():t)})+`
`);const s=a.join(o,"project.json");try{if((await r.lstat(s)).isSymbolicLink())throw new Error("unsafe_project_mapping");const l=JSON.parse(await r.readFile(s,"utf8"));if(l.project_id!==i||l.account_id!==e)throw new Error("folder_already_connected");if(n&&l.folder_key!==n)throw new Error("folder_key_mismatch");return p(l.folder_key),l}catch(l){if(l.code!=="ENOENT")throw l}const c={version:1,project_id:i,account_id:e,folder_key:n?p(n):y()};return await _(s,JSON.stringify(c,null,2)+`
`),c}async function u(t,i){t=await r.realpath(t),p(i);const e=await d(t,[".dxir"]);await _(a.join(e,"location.json"),JSON.stringify({location_key:f(process.platform==="win32"?t.toLowerCase():t)})+`
`);for(const o of["project.json","app-context.json"])try{const s=a.join(e,o);if((await r.lstat(s)).isSymbolicLink())throw new Error("unsafe_project_mapping");const c=JSON.parse(await r.readFile(s,"utf8"));if(c.account_id!==i)throw new Error("folder_already_connected");return p(c.folder_key),c}catch(s){if(s.code!=="ENOENT")throw s}const n={version:1,account_id:i,folder_key:y()};return await _(a.join(e,"app-context.json"),JSON.stringify(n,null,2)+`
`),n}async function J(t,i){const e=await u(t,p(i.account_id));if(e.folder_key!==i.folder_key||i.status!=="active")throw new Error("context_mismatch");const n=await v(t,p(i.project_id),i.account_id,e.folder_key),o=await E(t,p(i.connection_id));return{...n,...o}}async function L(t,i){t=await r.realpath(t);const e=await u(t,p(i.account_id));if(i.project_id!==e.project_id)throw new Error("context_mismatch");const n=p(i.mutation_id),o=i.input;if(!o||typeof o!="object"||Array.isArray(o))throw new Error("invalid_input");const s={mutation_id:n,...i.selection_id?{selection_id:p(i.selection_id)}:{},service_key:i.service_key,input:o,input_evidence:i.input_evidence||{},source_ids:i.source_ids||[],ai_model:i.ai_model,ai_budget_usd:i.ai_budget_usd},c=await d(await r.realpath(t),[".dxir","analyses",n]);if(i.selection_id){const l=a.join(c,"selection.json");if((await r.lstat(l)).isSymbolicLink())throw new Error("unsafe_project_mapping");const w=JSON.parse(await r.readFile(l,"utf8"));if(w.selection_id!==i.selection_id||w.mutation_id!==n)throw new Error("selection_checkpoint_mismatch")}return await _(a.join(c,"input.json"),JSON.stringify(s,null,2)+`
`),{file:a.relative(t,a.join(c,"input.json")),mutation_id:n}}async function T(t,i){t=await r.realpath(t);const e=await u(t,p(i.account_id));if(i.project_id!==e.project_id)throw new Error("context_mismatch");const n=p(i.mutation_id),o=a.join(t,".dxir","connection.json");if((await r.lstat(o)).isSymbolicLink())throw new Error("unsafe_project_mapping");const s=JSON.parse(await r.readFile(o,"utf8"));if(p(i.connection_id)!==s.connection_id)throw new Error("context_mismatch");const c={mutation_id:n,connection_id:i.connection_id},l=await d(t,[".dxir","analyses",n]);if(await _(a.join(l,"selection-request.json"),JSON.stringify(c,null,2)+`
`),i.selection_id){const w={...c,selection_id:p(i.selection_id)};return await _(a.join(l,"selection.json"),JSON.stringify(w,null,2)+`
`),w}return c}async function z(t,i){t=await r.realpath(t);const e=await u(t,p(i.account_id));if(i.project_id!==e.project_id)throw new Error("context_mismatch");const n=p(i.mutation_id),o=await d(t,[".dxir","analyses",n]),s=a.join(o,"input.json");if((await r.lstat(s)).isSymbolicLink())throw new Error("unsafe_project_mapping");const c=JSON.parse(await r.readFile(s,"utf8"));if(c.mutation_id!==n||c.service_key!==i.service_key||(c.selection_id||null)!==(i.selection_id||null))throw new Error("analysis_checkpoint_mismatch");if(!/^[a-f0-9]{64}$/.test(i.input_sha256))throw new Error("invalid_hash");const l={mutation_id:n,analysis_id:p(i.analysis_id),input_sha256:i.input_sha256};return await _(a.join(o,"receipt.json"),JSON.stringify(l,null,2)+`
`),l}async function E(t,i){t=await r.realpath(t),p(i);const e=await d(t,[".dxir"]),n=a.join(e,"connection.json");return await _(n,JSON.stringify({connection_id:i},null,2)+`
`),{connection_id:i}}async function A(t,i){if(t=await r.realpath(t),!i||a.isAbsolute(i))throw new Error("select_relative_file");const e=a.resolve(t,i);h(t,e);const n=a.relative(t,e).split(a.sep);if(n.some(m=>[".git",".dxir","node_modules",".ssh"].includes(m))||/(^\.env(?:\.|$)|\.(?:pem|key|p12|pfx)$)/i.test(a.basename(e)))throw new Error("sensitive_or_generated_path");let o=t;for(const m of n)if(o=a.join(o,m),(await r.lstat(o)).isSymbolicLink())throw new Error("symlink_not_allowed");h(t,await r.realpath(e));const s=await r.stat(e);if(!s.isFile()||s.size<1||s.size>15*1024*1024)throw new Error("invalid_file_size");const c=await r.readFile(e),l=f(c),w=await d(t,[".dxir","sources",l]);await _(a.join(w,"source"),c);const g=a.extname(e).toLowerCase(),j={".txt":"text/plain",".md":"text/markdown",".csv":"text/csv",".json":"application/json",".pdf":"application/pdf",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}[g];if(!j)throw new Error("unsupported_file_type");return{source_id:y(),file_name:a.basename(e),content_type:j,size_bytes:c.length,sha256:l,snapshot:a.relative(t,a.join(w,"source"))}}async function C(t,i){if(t=await r.realpath(t),!/^[a-f0-9]{64}$/.test(i.sha256))throw new Error("invalid_hash");const e=await d(t,[".dxir","sources",i.sha256]),n=a.join(e,"source");if((await r.lstat(n)).isSymbolicLink())throw new Error("symlink_not_allowed");const o=await r.readFile(n);if(f(o)!==i.sha256)throw new Error("snapshot_hash_mismatch");const s=new URL(i.upload_url);if(s.protocol!=="https:"||s.hostname!=="weufexgfzkdzqkfjxdpj.supabase.co"||s.port||s.username||s.password||!s.pathname.startsWith("/storage/v1/object/upload/sign/dxir-project-sources/"))throw new Error("invalid_upload_destination");if(!(await fetch(s,{method:"PUT",headers:{"Content-Type":i.content_type},body:o,redirect:"error"})).ok)throw new Error("upload_failed");return{uploaded:!0,sha256:i.sha256,size_bytes:o.length}}async function $(t,i){if(t=await r.realpath(t),p(i.analysis_id),typeof i.html!="string"||!/^[a-f0-9]{64}$/.test(i.sha256)||f(Buffer.from(i.html))!==i.sha256)throw new Error("report_hash_mismatch");const e=await d(t,[".dxir","reports",i.analysis_id,i.sha256]),n=a.join(e,"report.html");return await _(n,i.html),{file:a.relative(t,n),sha256:i.sha256,verified:!0}}async function P(t,i){t=await r.realpath(t);const e=await d(t,[".dxir"]),n=JSON.parse(await r.readFile(a.join(e,"project.json"),"utf8")),o=JSON.parse(await r.readFile(a.join(e,"connection.json"),"utf8"));if(JSON.parse(await r.readFile(a.join(e,"location.json"),"utf8")).location_key!==f(process.platform==="win32"?t.toLowerCase():t))throw new Error("project_location_changed");if(i.account_id!==n.account_id||i.project_id!==n.project_id||i.connection_id!==o.connection_id)throw new Error("room_project_mismatch");if(i.binding_status!=="active"||i.decision!=="start")throw new Error("room_not_bound");const c={version:1,handoff_id:p(i.id),room_id:p(i.room_id),binding_id:p(i.binding_id),executor_key:p(i.executor_key),execution_context_id:p(i.execution_context_id),project_id:n.project_id,connection_id:o.connection_id,account_id:n.account_id},l=await d(t,[".dxir","rooms",c.room_id]);return await _(a.join(l,`${c.binding_id}.json`),JSON.stringify(c,null,2)+`
`),c}async function U(){const[t,...i]=process.argv.slice(2),e=Object.fromEntries(i.reduce((c,l,w)=>w%2===0?[...c,[l.replace(/^--/,""),i[w+1]]]:c,[])),n=e.root||process.cwd();if(t==="folder-plan"||t==="folder-create"){const c=t==="folder-plan"?await x(e.name,e.parent):await F(e.name,e.parent,e["approved-path"]);process.stdout.write(JSON.stringify(c)+`
`);return}let o;if(["upload","upload-artifact","save-report","room-bind","context-bind","checkpoint-input","save-selection","save-analysis"].includes(t)){const c=[];let l=0;for await(const w of process.stdin){if(l+=w.length,l>20*1024*1024)throw new Error("input_too_large");c.push(w)}o=JSON.parse(Buffer.concat(c).toString("utf8"))}const s=t==="artifact"?N(e.file,n):t==="upload-artifact"?await O({file:o.relative_path,cwd:n,sha256:o.sha256,contentType:o.content_type,uploadUrl:o.upload_url}):t==="context-init"?await u(n,e["account-id"]):t==="context-bind"?await J(n,o):t==="checkpoint-input"?await L(n,o):t==="save-selection"?await T(n,o):t==="save-analysis"?await z(n,o):t==="init"?await v(n,e["project-id"],e["account-id"]):t==="bind"?await E(n,e["connection-id"]):t==="snapshot"?await A(n,e.file):t==="upload"?await C(n,o):t==="room-bind"?await P(n,o):t==="save-report"?await $(n,o):(()=>{throw new Error("unknown_command")})();process.stdout.write(JSON.stringify(s)+`
`)}process.argv[1]&&import.meta.url===b(a.resolve(process.argv[1])).href&&U().catch(t=>{process.stderr.write(String(t.message).replace(/https?:\/\/\S+/g,"[url]")+`
`),process.exitCode=1});export{J as bindAppContext,E as bindProject,F as createProjectFolder,u as initializeAppContext,v as initializeProject,x as planProjectFolder,L as saveAnalysisCheckpoint,z as saveAnalysisReceipt,$ as saveProjectReport,P as saveRoomBinding,T as saveSelectionCheckpoint,A as snapshotProjectFile,C as uploadProjectSnapshot};
SHA-256: 0fdfede24d716285f12cdf1db31a0a0f3d74042227e0e25717a050d6ce5f365a