loop-canvas 0.1.5 → 0.1.6

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/server.mjs CHANGED
@@ -1,35 +1,37 @@
1
1
  #!/usr/bin/env node
2
- import Yt from"node:http";import $ from"node:fs";import M from"node:fs/promises";import h from"node:path";import ae from"node:os";import Dn from"node:crypto";import{spawn as Oe,execFile as Xt,execFileSync as Mn}from"node:child_process";import Vt from"node:net";import{fileURLToPath as Fn}from"node:url";var Ln=!1,le=h.dirname(Fn(import.meta.url)),Qe=h.join(le,"public"),Z=h.join(le,"cli.mjs"),Zt=Eo(process.argv.slice(2)),A=h.resolve(Zt.dir||".loop"),X=h.dirname(A),Pe=h.join(A,"canvas.json"),Qt=h.join(A,"server.json");$.mkdirSync(h.join(A,"frames"),{recursive:!0});var De=process.env.LOOP_CLAUDE_BIN||ke("claude"),$t=process.env.LOOP_CODEX_BIN||ke("codex")||["/Applications/ChatGPT.app/Contents/Resources/codex-cli/bin/codex","/Applications/Codex.app/Contents/Resources/codex",h.join(ae.homedir(),"Applications/ChatGPT.app/Contents/Resources/codex-cli/bin/codex")].find(e=>{try{return $.accessSync(e,$.constants.X_OK),!0}catch{return!1}}),pe={claude:{label:"Claude Code",bin:De},codex:{label:"Codex",bin:$t}},Rn=["fable","opus","sonnet","haiku"],Nn=["low","medium","high","xhigh","max"],Et=0,Ct=null;function Wn(){return Date.now()-Et<3e4?Ct:(Et=Date.now(),Ct=zn())}function zn(){let e=h.join(ae.homedir(),".codex"),t=[],o={},i=null;try{let r=JSON.parse($.readFileSync(h.join(e,"models_cache.json"),"utf8"));for(let s of Array.isArray(r)?r:r.models||[]){let a=s.slug||s.id;!a||(s.visibility||"list")!=="list"||(t.push(a),o[a]=(s.supported_reasoning_levels||s.supported_reasoning_efforts||[]).map(l=>l?.effort||l).filter(l=>typeof l=="string"))}}catch{}try{i=/^\s*model_reasoning_effort\s*=\s*"([^"]+)"/m.exec($.readFileSync(h.join(e,"config.toml"),"utf8"))?.[1]||null}catch{}return{models:t,efforts:o,effort:i}}function Ee(e=Te()){let t=d.agent.pick?.[e]||{},o,i,r;if(e==="codex"){let a=Wn(),l=Hn();o={model:l,effort:a.effort},i=a.models,r=a.efforts[t.model||l]||["low","medium","high"]}else{let a=d.agent.chat?.runner==="claude"?d.agent.chat:{};o={model:process.env.LOOP_MODEL||a.model||null,effort:process.env.LOOP_EFFORT||a.effort||null},i=Rn,r=Nn}let s=t.effort||o.effort||null;return{runner:e,models:i,efforts:r,chat:o,pick:{model:t.model||null,effort:t.effort||null},model:t.model||o.model||null,effort:s&&r.includes(s)?s:e==="codex"?null:s}}function Hn(){if(process.env.LOOP_CODEX_MODEL)return process.env.LOOP_CODEX_MODEL;let e=h.join(ae.homedir(),".codex"),t=null,o=[];try{t=/^\s*model\s*=\s*"([^"]+)"/m.exec($.readFileSync(h.join(e,"config.toml"),"utf8"))?.[1]||null}catch{}try{let i=JSON.parse($.readFileSync(h.join(e,"models_cache.json"),"utf8"));o=(Array.isArray(i)?i:i.models||[]).filter(r=>(r.visibility||"list")==="list").map(r=>r.slug||r.id).filter(Boolean)}catch{}return t&&(!o.length||o.includes(t))?t:o[0]||t||null}var N=(e=8)=>Dn.randomBytes(e).toString("base64url").slice(0,e),d=Gn()||{version:1,name:h.basename(X),token:N(16),agent:{mode:"headless"},frames:[],strokes:[],comments:[]},v=new Map,de=null,me=null;function Gn(){try{return JSON.parse($.readFileSync(Pe,"utf8"))}catch{return null}}var ge=h.join(A,"canvas-history"),Un=80,qe=null,ot=null,qt=e=>({at:Date.now(),frames:new Set((e.frames||[]).map(t=>t.id)),comments:(e.comments||[]).length,sections:(e.sections||[]).length});function en(e="auto",t=!1){try{let o=qt(d),i=ot;ot=o;let r=i&&([...i.frames].some(b=>!o.frames.has(b))||o.comments<i.comments||o.sections<i.sections),s=!qe||Date.now()-qe.at>10*6e4;if(!t&&!r&&!s||!$.existsSync(Pe))return;let a=JSON.parse($.readFileSync(Pe,"utf8"));if(!a.frames?.length&&!a.comments?.length)return;$.mkdirSync(ge,{recursive:!0});let l=String(Date.now());$.writeFileSync(h.join(ge,`${l}.json`),JSON.stringify({label:t?e:r?"before a change":"auto",at:Date.now(),state:{name:a.name,frames:a.frames||[],sections:a.sections||[],strokes:a.strokes||[],comments:a.comments||[]}}));let p=$.readdirSync(ge).filter(b=>/^\d+\.json$/.test(b)).sort();for(let b of p.slice(0,Math.max(0,p.length-Un)))$.rmSync(h.join(ge,b),{force:!0});qe={at:Date.now()}}catch{}}function Bn(){try{return $.readdirSync(ge).filter(e=>/^\d+\.json$/.test(e)).sort().reverse().map(e=>{try{let t=JSON.parse($.readFileSync(h.join(ge,e),"utf8"));return{id:e.slice(0,-5),at:t.at,label:t.label,frames:t.state.frames.length,comments:t.state.comments.filter(o=>!o.parentId).length,names:t.state.frames.slice(0,4).map(o=>o.name)}}catch{return null}}).filter(Boolean)}catch{return[]}}function Me(){en();let e=Pe+".tmp";$.writeFileSync(e,JSON.stringify(d)),$.renameSync(e,Pe)}var it=null;function Jn(){clearTimeout(it),it=setTimeout(Me,120)}d.sections||=[];for(let e of d.frames)delete e.building;for(let e of d.comments)e.status==="working"&&(e.status="error",e.reply="Interrupted \u2014 the server restarted.");for(let e of d.strokes||[]){if(!e.frameId||e.fx!=null)continue;let t=d.frames.find(k=>k.id===e.frameId);if(!t)continue;e.fx=t.x,e.fy=t.y;let o=e.commentId&&d.comments.find(k=>k.id===e.commentId),i=o?.anchor;if(!i||i.scope==="area"||i.scope==="elements"||!Number.isFinite(i.x)||!Number.isFinite(i.y))continue;let r=d.strokes.filter(k=>k.commentId===o.id&&k.frameId===t.id),s=r.flatMap(k=>k.points.filter((I,T)=>T%2===0)),a=r.flatMap(k=>k.points.filter((I,T)=>T%2===1));if(!s.length)continue;let l=t.x+i.x-(Math.min(...s)+Math.max(...s))/2,p=t.y+i.y-(Math.min(...a)+Math.max(...a))/2;if(Math.hypot(l,p)<4)continue;let b=(k,I)=>s.filter((T,_)=>T+k>=t.x&&T+k<=t.x+t.w&&a[_]+I>=t.y&&a[_]+I<=t.y+t.h).length;if(!(b(l,p)<=b(0,0)))for(let k of r)k.fx=t.x-l,k.fy=t.y-p}var Kn=(()=>{let e=(o,i)=>{try{return Mn(o,i,{encoding:"utf8",stdio:["ignore","pipe","ignore"],timeout:1500,cwd:h.dirname(A)}).trim()}catch{return""}},t=process.env.LOOP_NAME||(process.platform==="darwin"?e("id",["-F"]):"")||e("git",["config","user.name"]);if(!t)try{t=ae.userInfo().username}catch{}return String(t||"").trim().slice(0,32)||"Host"})();function tn(e){let t={};for(let[a,l]of v)t[a]={commentId:l.commentId,step:l.step,startedAt:l.startedAt};let{token:o,cloud:i,...r}=d,s=Te();return{...r,codeId:An,down:[...ve],chatHistory:!!kn(),hostName:d.hostName||Kn,live:t,role:e,version:kt,update:e==="owner"?ee:void 0,emailSharing:!!H,chatListening:Ye(),agentBin:s,agentLabel:pe[s].label,models:Ee(s),runners:Object.fromEntries(Object.entries(pe).map(([a,l])=>[a,!!l.bin])),share:e==="owner"?yt():void 0}}var Q="/__loop";function Dt(e,t,o,i=409){e.writeHead(i,{"content-type":"text/html; charset=utf-8","cache-control":"no-store"}),e.end(`<!doctype html><meta charset="utf-8"><body style="margin:0;height:100vh;display:grid;place-items:center;background:#fff;color:#88888F;font:14px/1.5 Inter,system-ui,sans-serif"><div style="max-width:420px;padding:24px;text-align:center"><div style="color:#17171A;font-weight:600;font-size:15px;margin-bottom:4px">${t}</div>${o}</div></body>`)}var ve=new Set,xt=e=>{try{let t=new URL(e);return/^(localhost|127\.0\.0\.1|\[::1\])$/.test(t.hostname)?t.origin:null}catch{return null}};function Yn(){if(!He.size)return;let e=new Set(d.frames.filter(t=>t.kind==="url").map(t=>xt(t.src)).filter(Boolean));for(let t of[...ve])e.has(t)||ve.delete(t);for(let t of e){let o=new URL(t),i=Vt.connect({host:o.hostname.replace(/^\[|\]$/g,""),port:+o.port||80}),r=!1,s=a=>{r||(r=!0,i.destroy(),a===ve.has(t)&&(a?ve.delete(t):ve.add(t),J({type:"state"})))};i.setTimeout(1500,()=>s(!1)),i.on("connect",()=>s(!0)),i.on("error",()=>s(!1))}}setInterval(Yn,4e3).unref();function rt(e,t,o,i){let r=new URL(o),s={...e.headers,host:r.host},a=Yt.request({hostname:r.hostname,port:r.port||80,method:e.method,path:i||e.url,headers:s},l=>{let p={...l.headers};typeof p.location=="string"&&p.location.startsWith(r.origin)&&(p.location=p.location.slice(r.origin.length)||"/"),delete p["x-frame-options"],p.vary=[p.vary,"Referer"].filter(Boolean).join(", "),p["content-security-policy"]&&(p["content-security-policy"]=String(p["content-security-policy"]).replace(/frame-ancestors[^;]*;?/i,"")),t.writeHead(l.statusCode||502,p),l.pipe(t)});a.on("error",l=>{if(t.headersSent)return t.end();t.writeHead(502,{"content-type":"text/html; charset=utf-8"}),t.end(`<body style="font:14px system-ui;color:#8b8880;display:grid;place-items:center;height:100vh;margin:0">The dev server at ${be(o)} isn't responding (${be(l.code||l.message)}). Start it, or run <code>loop scan</code>.</body>`)}),e.pipe(a)}var He=new Set;function st(e,t){e.write(`data: ${JSON.stringify(t)}
2
+ import on from"node:http";import k from"node:fs";import M from"node:fs/promises";import h from"node:path";import ae from"node:os";import Un from"node:crypto";import{spawn as Te,execFile as rn,execFileSync as Bn}from"node:child_process";import sn from"node:net";import{fileURLToPath as Kn}from"node:url";var Jn=!1,le=h.dirname(Kn(import.meta.url)),it=h.join(le,"public"),Q=h.join(le,"cli.mjs"),an=Uo(process.argv.slice(2)),_=h.resolve(an.dir||".loop"),Z=h.dirname(_),Ce=h.join(_,"canvas.json"),Xe=h.join(_,"server.json");k.mkdirSync(h.join(_,"frames"),{recursive:!0});var Re=process.env.LOOP_CLAUDE_BIN||ke("claude"),jt=process.env.LOOP_CODEX_BIN||ke("codex")||["/Applications/ChatGPT.app/Contents/Resources/codex-cli/bin/codex","/Applications/Codex.app/Contents/Resources/codex",h.join(ae.homedir(),"Applications/ChatGPT.app/Contents/Resources/codex-cli/bin/codex")].find(e=>{try{return k.accessSync(e,k.constants.X_OK),!0}catch{return!1}}),pe={claude:{label:"Claude Code",bin:Re},codex:{label:"Codex",bin:jt}},Yn=["fable","opus","sonnet","haiku"],Xn=["low","medium","high","xhigh","max"],Rt=0,Nt=null;function Vn(){return Date.now()-Rt<3e4?Nt:(Rt=Date.now(),Nt=Zn())}function Zn(){let e=h.join(ae.homedir(),".codex"),t=[],o={},i=null;try{let r=JSON.parse(k.readFileSync(h.join(e,"models_cache.json"),"utf8"));for(let s of Array.isArray(r)?r:r.models||[]){let a=s.slug||s.id;!a||(s.visibility||"list")!=="list"||(t.push(a),o[a]=(s.supported_reasoning_levels||s.supported_reasoning_efforts||[]).map(l=>l?.effort||l).filter(l=>typeof l=="string"))}}catch{}try{i=/^\s*model_reasoning_effort\s*=\s*"([^"]+)"/m.exec(k.readFileSync(h.join(e,"config.toml"),"utf8"))?.[1]||null}catch{}return{models:t,efforts:o,effort:i}}function Me(e=Oe()){let t=d.agent.pick?.[e]||{},o,i,r;if(e==="codex"){let a=Vn(),l=Qn();o={model:l,effort:a.effort},i=a.models,r=a.efforts[t.model||l]||["low","medium","high"]}else{let a=d.agent.chat?.runner==="claude"?d.agent.chat:{};o={model:process.env.LOOP_MODEL||a.model||null,effort:process.env.LOOP_EFFORT||a.effort||null},i=Yn,r=Xn}let s=t.effort||o.effort||null;return{runner:e,models:i,efforts:r,chat:o,pick:{model:t.model||null,effort:t.effort||null},model:t.model||o.model||null,effort:s&&r.includes(s)?s:e==="codex"?null:s}}function Qn(){if(process.env.LOOP_CODEX_MODEL)return process.env.LOOP_CODEX_MODEL;let e=h.join(ae.homedir(),".codex"),t=null,o=[];try{t=/^\s*model\s*=\s*"([^"]+)"/m.exec(k.readFileSync(h.join(e,"config.toml"),"utf8"))?.[1]||null}catch{}try{let i=JSON.parse(k.readFileSync(h.join(e,"models_cache.json"),"utf8"));o=(Array.isArray(i)?i:i.models||[]).filter(r=>(r.visibility||"list")==="list").map(r=>r.slug||r.id).filter(Boolean)}catch{}return t&&(!o.length||o.includes(t))?t:o[0]||t||null}var N=(e=8)=>Un.randomBytes(e).toString("base64url").slice(0,e),d=qn()||{version:1,name:h.basename(Z),token:N(16),agent:{mode:"headless"},frames:[],strokes:[],comments:[]},v=new Map,se=null,me=null;function qn(){try{return JSON.parse(k.readFileSync(Ce,"utf8"))}catch{return null}}var ge=h.join(_,"canvas-history"),eo=80,rt=null,lt=null,cn=e=>({at:Date.now(),frames:new Set((e.frames||[]).map(t=>t.id)),comments:(e.comments||[]).length,sections:(e.sections||[]).length});function dn(e="auto",t=!1){try{let o=cn(d),i=lt;lt=o;let r=i&&([...i.frames].some(b=>!o.frames.has(b))||o.comments<i.comments||o.sections<i.sections),s=!rt||Date.now()-rt.at>10*6e4;if(!t&&!r&&!s||!k.existsSync(Ce))return;let a=JSON.parse(k.readFileSync(Ce,"utf8"));if(!a.frames?.length&&!a.comments?.length)return;k.mkdirSync(ge,{recursive:!0});let l=String(Date.now());k.writeFileSync(h.join(ge,`${l}.json`),JSON.stringify({label:t?e:r?"before a change":"auto",at:Date.now(),state:{name:a.name,frames:a.frames||[],sections:a.sections||[],strokes:a.strokes||[],comments:a.comments||[]}}));let m=k.readdirSync(ge).filter(b=>/^\d+\.json$/.test(b)).sort();for(let b of m.slice(0,Math.max(0,m.length-eo)))k.rmSync(h.join(ge,b),{force:!0});rt={at:Date.now()}}catch{}}function to(){try{return k.readdirSync(ge).filter(e=>/^\d+\.json$/.test(e)).sort().reverse().map(e=>{try{let t=JSON.parse(k.readFileSync(h.join(ge,e),"utf8"));return{id:e.slice(0,-5),at:t.at,label:t.label,frames:t.state.frames.length,comments:t.state.comments.filter(o=>!o.parentId).length,names:t.state.frames.slice(0,4).map(o=>o.name)}}catch{return null}}).filter(Boolean)}catch{return[]}}function Ne(){dn();let e=Ce+".tmp";k.writeFileSync(e,JSON.stringify(d)),k.renameSync(e,Ce)}var ft=null;function no(){clearTimeout(ft),ft=setTimeout(Ne,120)}d.sections||=[];for(let e of d.frames)delete e.building;for(let e of d.comments)e.status==="working"&&(e.status="error",e.reply="Interrupted \u2014 the server restarted.");for(let e of d.strokes||[]){if(!e.frameId||e.fx!=null)continue;let t=d.frames.find(w=>w.id===e.frameId);if(!t)continue;e.fx=t.x,e.fy=t.y;let o=e.commentId&&d.comments.find(w=>w.id===e.commentId),i=o?.anchor;if(!i||i.scope==="area"||i.scope==="elements"||!Number.isFinite(i.x)||!Number.isFinite(i.y))continue;let r=d.strokes.filter(w=>w.commentId===o.id&&w.frameId===t.id),s=r.flatMap(w=>w.points.filter((I,T)=>T%2===0)),a=r.flatMap(w=>w.points.filter((I,T)=>T%2===1));if(!s.length)continue;let l=t.x+i.x-(Math.min(...s)+Math.max(...s))/2,m=t.y+i.y-(Math.min(...a)+Math.max(...a))/2;if(Math.hypot(l,m)<4)continue;let b=(w,I)=>s.filter((T,E)=>T+w>=t.x&&T+w<=t.x+t.w&&a[E]+I>=t.y&&a[E]+I<=t.y+t.h).length;if(!(b(l,m)<=b(0,0)))for(let w of r)w.fx=t.x-l,w.fy=t.y-m}var oo=(()=>{let e=(o,i)=>{try{return Bn(o,i,{encoding:"utf8",stdio:["ignore","pipe","ignore"],timeout:1500,cwd:h.dirname(_)}).trim()}catch{return""}},t=process.env.LOOP_NAME||(process.platform==="darwin"?e("id",["-F"]):"")||e("git",["config","user.name"]);if(!t)try{t=ae.userInfo().username}catch{}return String(t||"").trim().slice(0,32)||"Host"})();function ln(e){let t={};for(let[a,l]of v)t[a]={commentId:l.commentId,step:l.step,startedAt:l.startedAt};let{token:o,cloud:i,...r}=d,s=Oe();return{...r,chatContext:e==="owner"?yn():void 0,publicLink:e==="owner"&&se?`${se}${X}/?t=${d.token}`:void 0,codeId:Nn,down:[...xe],chatHistory:!!An(),hostName:d.hostName||oo,live:t,role:e,version:Ot,update:e==="owner"?ee:void 0,emailSharing:!!H,chatListening:Fe(),agentBin:s,agentLabel:pe[s].label,models:Me(s),runners:Object.fromEntries(Object.entries(pe).map(([a,l])=>[a,!!l.bin])),share:e==="owner"?It():void 0}}var st=new Map;async function io(e){if(st.has(e))return st.get(e);let t=null,o=async i=>{try{let r=await fetch(i,{redirect:"follow",signal:AbortSignal.timeout(5e3)}),s=(r.headers.get("content-type")||"").split(";")[0];if(r.ok&&/^image\//.test(s)){let a=Buffer.from(await r.arrayBuffer());if(a.length&&a.length<3e5)return{type:s,buf:a}}}catch{}return null};try{let r=[...(await fetch(`https://${e}/`,{redirect:"follow",signal:AbortSignal.timeout(5e3),headers:{accept:"text/html"}}).then(a=>a.ok?a.text():"").catch(()=>"")).matchAll(/<link[^>]+rel=["'][^"']*icon[^"']*["'][^>]*>/gi)].map(a=>/href=["']([^"']+)["']/.exec(a[0])?.[1]).filter(Boolean),s=r.find(a=>/\.(png|svg)/i.test(a))||r[0];s&&(t=await o(new URL(s,`https://${e}/`).href))}catch{}return t||=await o(`https://${e}/favicon.ico`),st.set(e,t),t}var X="/__loop";function Wt(e,t,o,i=409){e.writeHead(i,{"content-type":"text/html; charset=utf-8","cache-control":"no-store"}),e.end(`<!doctype html><meta charset="utf-8"><body style="margin:0;height:100vh;display:grid;place-items:center;background:#fff;color:#88888F;font:14px/1.5 Inter,system-ui,sans-serif"><div style="max-width:420px;padding:24px;text-align:center"><div style="color:#17171A;font-weight:600;font-size:15px;margin-bottom:4px">${t}</div>${o}</div></body>`)}var xe=new Set,At=e=>{try{let t=new URL(e);return/^(localhost|127\.0\.0\.1|\[::1\])$/.test(t.hostname)?t.origin:null}catch{return null}};function ro(){if(!Ve.size)return;let e=new Set(d.frames.filter(t=>t.kind==="url").map(t=>At(t.src)).filter(Boolean));for(let t of[...xe])e.has(t)||xe.delete(t);for(let t of e){let o=new URL(t),i=sn.connect({host:o.hostname.replace(/^\[|\]$/g,""),port:+o.port||80}),r=!1,s=a=>{r||(r=!0,i.destroy(),a===xe.has(t)&&(a?xe.delete(t):xe.add(t),J({type:"state"})))};i.setTimeout(1500,()=>s(!1)),i.on("connect",()=>s(!0)),i.on("error",()=>s(!1))}}setInterval(ro,4e3).unref();function ut(e,t,o,i){let r=new URL(o),s={...e.headers,host:r.host},a=on.request({hostname:r.hostname,port:r.port||80,method:e.method,path:i||e.url,headers:s},l=>{let m={...l.headers};typeof m.location=="string"&&m.location.startsWith(r.origin)&&(m.location=m.location.slice(r.origin.length)||"/"),delete m["x-frame-options"],m.vary=[m.vary,"Referer"].filter(Boolean).join(", "),m["content-security-policy"]&&(m["content-security-policy"]=String(m["content-security-policy"]).replace(/frame-ancestors[^;]*;?/i,"")),t.writeHead(l.statusCode||502,m),l.pipe(t)});a.on("error",l=>{if(t.headersSent)return t.end();t.writeHead(502,{"content-type":"text/html; charset=utf-8"}),t.end(`<body style="font:14px system-ui;color:#8b8880;display:grid;place-items:center;height:100vh;margin:0">The dev server at ${Ie(o)} isn't responding (${Ie(l.code||l.message)}). Start it, or run <code>loop scan</code>.</body>`)}),e.pipe(a)}var Ve=new Set;function ht(e,t){e.write(`data: ${JSON.stringify(t)}
3
3
 
4
- `)}var nn=["frames","comments","strokes","sections"];function Xn(e,t){let o={};for(let[i,r]of Object.entries(t))o[i]=nn.includes(i)&&Array.isArray(r)?new Map(r.map(s=>[s.id,JSON.stringify(s)])):JSON.stringify(r??null);e.last=o}function on(e){let t=tn(e.role),o=e.last;if(!o)return Xn(e,t),{type:"state",state:t};let i={type:"patch"},r=!1;for(let[s,a]of Object.entries(t))if(nn.includes(s)&&Array.isArray(a)){let l=o[s]instanceof Map?o[s]:new Map,p=new Map,b=[];for(let T of a){let _=JSON.stringify(T);p.set(T.id,_),l.get(T.id)!==_&&b.push(T)}let k=[...p.keys()],I=k.length!==l.size||k.join(`
4
+ `)}var fn=["frames","comments","strokes","sections"];function so(e,t){let o={};for(let[i,r]of Object.entries(t))o[i]=fn.includes(i)&&Array.isArray(r)?new Map(r.map(s=>[s.id,JSON.stringify(s)])):JSON.stringify(r??null);e.last=o}function un(e){let t=ln(e.role),o=e.last;if(!o)return so(e,t),{type:"state",state:t};let i={type:"patch"},r=!1;for(let[s,a]of Object.entries(t))if(fn.includes(s)&&Array.isArray(a)){let l=o[s]instanceof Map?o[s]:new Map,m=new Map,b=[];for(let T of a){let E=JSON.stringify(T);m.set(T.id,E),l.get(T.id)!==E&&b.push(T)}let w=[...m.keys()],I=w.length!==l.size||w.join(`
5
5
  `)!==[...l.keys()].join(`
6
- `);(b.length||I)&&((i.lists||={})[s]={up:b,...I?{order:k}:{}},r=!0),o[s]=p}else{let l=JSON.stringify(a??null);o[s]!==l&&((i.set||={})[s]=a??null,o[s]=l,r=!0)}return r?i:null}function J(e){for(let t of He){if(e.type!=="state"){st(t.res,e);continue}let o=on(t);o&&st(t.res,o)}}var Mt=null,H=null,at=new Set;function S(e){Jn(),clearTimeout(Mt),Mt=setTimeout(()=>J({type:"state"}),16),e||H?.schedulePush();for(let t of[...at])t()}var Ie=new Map;function Ge(e){if(e.kind!=="folder"||Ie.has(e.id))return;let t=null;try{let o=$.watch(e.src,{recursive:!0},(i,r)=>{if(!(r&&(r.startsWith(".")||r.includes("node_modules")))){if(clearTimeout(t),Ue.add(e.id),v.get(e.id)?.inbox&&r&&Y(e.id,`Editing ${h.basename(String(r))}`),e.building&&e.optionOf&&Date.now()-e.createdAt>1500){let s=d.comments.find(a=>a.id===e.optionOf.cid)?.options?.find(a=>a.frameId===e.id);s&&!s.touched&&(s.touched=!0,e.building="filling",S(),v.has(e.optionOf.from)&&Y(e.optionOf.from,`Making option ${s.id}${s.placeholder?"":` \xB7 ${s.name}`}`))}t=setTimeout(()=>{J({type:"frame-changed",frameId:e.id,live:v.has(e.id)}),v.has(e.id)||H?.frameChanged(e)},v.has(e.id)?450:200)}});Ie.set(e.id,o)}catch{}}function _e(e){Ie.get(e)?.close(),Ie.delete(e)}d.frames.forEach(Ge);function ct(e=1280,t=800,o){let i=o&&d.frames.find(a=>a.id===o);if(i){let a=(b,k)=>d.frames.some(I=>b<I.x+I.w+60&&b+e+60>I.x&&k<I.y+I.h+60&&k+t+60>I.y),l=i.x+i.w+120,p=i.y;for(let b=0;b<50&&a(l,p);b++)l+=160;return{x:l,y:p}}if(!d.frames.length)return{x:0,y:0};let r=Math.max(...d.frames.map(a=>a.x+a.w)),s=Math.min(...d.frames.map(a=>a.y));return{x:r+120,y:s}}function rn(e){return(e||"frame").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"").slice(0,40)||"frame"}function Vn(e){let t=e.split(/[?#]/)[0];return t==="/"||!t?"Home":t.split("/").filter(Boolean).map(o=>decodeURIComponent(o).replace(/\.html?$/,"").replace(/[-_]+/g," ").replace(/^\w/,i=>i.toUpperCase())).join(" / ").slice(0,60)}function W({src:e,route:t,name:o,w:i,h:r,x:s,y:a,near:l,dup:p}){if(i=+i||ye.Desktop[0],r=+r||ye.Desktop[1],t!=null){t="/"+String(t).replace(/^\/+/,"");let O=!p&&d.frames.find(C=>C.kind==="route"&&C.src===t&&C.w===i);if(O)return O;let F=s!=null&&a!=null?{x:+s,y:+a}:ct(i,r,l),D={id:N(6),name:o||t,kind:"route",src:t,entry:"",w:i,h:r,...F,createdAt:Date.now()};return d.frames.push(D),S(),D}let b=/^https?:\/\//.test(e);if(b){let O=null;try{O=new URL(e)}catch{}if(O&&/^(localhost|127\.0\.0\.1|\[::1\])$/.test(O.hostname)&&+O.port===V){if(O.pathname===Q||O.pathname.startsWith(Q+"/")||!d.proxy)throw new Error(`${e} is the Loop canvas itself, not your app. Give the address your app runs on (loop scan --url http://localhost:<port>), or a folder with an index.html.`);return W({route:O.pathname+O.search,name:o,w:i,h:r,x:s,y:a,near:l,dup:p})}let F=xt(e);if(O&&F&&(!d.proxy||d.proxy===F))return d.proxy=F,W({route:O.pathname+O.search,name:o,w:i,h:r,x:s,y:a,near:l,dup:p})}let k=b?"url":"folder",I="index.html";b||(e=h.resolve(X,e),$.existsSync(e)&&$.statSync(e).isFile()&&(I=h.basename(e),e=h.dirname(e)));let T=s!=null&&a!=null?{x:+s,y:+a}:ct(i,r,l),_={id:N(6),name:o||(b?new URL(e).pathname:h.basename(e)),kind:k,src:e,entry:I,w:i,h:r,...T,createdAt:Date.now()};return d.frames.push(_),Ge(_),S(),_}var be=e=>String(e).replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t]),Zn=e=>`<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
7
- <title>${be(e)}</title><style>html,body{height:100%;margin:0}body{display:grid;place-items:center;font:14px/1.4 ui-sans-serif,system-ui;color:#9a978f;background:#fbfaf7}</style></head>
8
- <body><div>${be(e)} \u2014 waiting for the agent\u2026</div></body></html>`,Qn=e=>`<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
9
- <meta name="loop-empty" content="1"><title>${be(e)}</title><style>html,body{height:100%;margin:0;background:#fff}</style></head>
10
- <body></body></html>`;function we(e){let t=rn(e),o=h.join(A,"frames",t),i=2;for(;$.existsSync(o);)o=h.join(A,"frames",`${t}-${i++}`);return o}function re(e){let t=new Set(d.frames.map(o=>o.name));if(!t.has(e))return e;for(let o=2;;o++)if(!t.has(`${e} ${o}`))return`${e} ${o}`}function Lt(e,t=!1){let o=we(e);return $.mkdirSync(o,{recursive:!0}),$.writeFileSync(h.join(o,"index.html"),t?Qn(e):Zn(e)),o}var sn=new Set(["node_modules",".git",".loop",".next","dist","build"]);async function fe(e,t,o={bytes:3e7}){await M.mkdir(t,{recursive:!0});for(let i of await M.readdir(e,{withFileTypes:!0})){if(i.name.startsWith(".")||sn.has(i.name))continue;let r=h.join(e,i.name),s=h.join(t,i.name);if(i.isDirectory())await fe(r,s,o);else if(i.isFile()){let{size:a}=await M.stat(r);if((o.bytes-=a)<0)throw new Error("frame too large to snapshot");await M.copyFile(r,s)}}}async function dt(e,t=e,o=new Set){for(let i of await M.readdir(e,{withFileTypes:!0})){if(i.name.startsWith(".")||sn.has(i.name))continue;let r=h.join(e,i.name);i.isDirectory()?await dt(r,t,o):o.add(h.relative(t,r))}return o}function Je(e,t,o){for(let i of $.readdirSync(e,{withFileTypes:!0})){if(i.name.startsWith("."))continue;let r=h.join(e,i.name);i.isDirectory()?Je(r,t,o):o.add(h.relative(t,r))}}var Ft=new Map;async function vt(e,t){if(e.kind!=="folder")return null;let o=h.join(A,"history",t);try{return await fe(e.src,o),o}catch{return null}}async function Re(e,t){if(e.kind!=="folder")return null;let o=h.join(A,"history",t);try{return await M.rm(o,{recursive:!0,force:!0}),await fe(e.src,o),o}catch{return null}}async function Ne(e,t){let o=await dt(e.src),i=await dt(t);for(let r of o)i.has(r)||await M.rm(h.join(e.src,r),{force:!0});await fe(t,e.src)}function Rt(e,t={}){let o=t.file_path||t.path||t.notebook_path,i=o?h.basename(o):"",r=String(t.command||"");switch(e){case"Read":return o&&/\.(png|jpe?g|webp|gif)$/i.test(o)?o.includes(`${h.sep}shots${h.sep}`)?"Looking at the screen":o.includes(`${h.sep}annotations${h.sep}`)?"Looking at your marks":"Looking at your reference":i?`Reading ${i}`:"Reading the code";case"Edit":case"MultiEdit":return i?`Editing ${i}`:"Editing";case"Write":return i?`Writing ${i}`:"Writing";case"Glob":case"Grep":return"Searching the code";case"Bash":return/cli\.mjs"? shot\b|\bloop shot\b/.test(r)?"Taking a screenshot":/cli\.mjs"? (new|add-route)\b/.test(r)?"Adding a frame":/cli\.mjs"? option\b/.test(r)?"Setting up an option":r?"Running a command":"Working\u2026";case"TodoWrite":return"Planning";case"WebFetch":case"WebSearch":return"Looking something up";default:return e}}var ye={Mobile:[393,852],Tablet:[834,1194],Desktop:[1440,1024]},Ke=e=>e.w<=480?"Mobile":e.w>=1100?"Desktop":e.h>=e.w*1.15?"Tablet":"Custom-size";function lt(e){let t=d.frames.filter(o=>o.id!==e?.id);return["","How Loop canvases work (follow these conventions):",`- A frame is one screen at one device size. Standard sizes: Mobile ${ye.Mobile.join("\xD7")}, Tablet ${ye.Tablet.join("\xD7")}, Desktop ${ye.Desktop.join("\xD7")}. When someone asks for a "mobile", "phone", "tablet", "iPad", "desktop" or "web" version, use the matching size.`,"- Change only the project (or this frame's folder). Never edit Loop itself: its canvas, toolbar, menus or .loop/ files are not yours to change.","- A frame IS the device screen: design edge to edge at that size. Never draw a phone or laptop mockup, notch, status bar, home indicator or bezel inside a frame, and never letterbox a small layout in the middle of a big frame.",'- "A mobile version of this" means the same screen, content and copy, re-laid-out for that width: single column, stacked sections, a mobile navigation pattern (top bar, tab bar or menu), tap targets of at least 44px, readable type. Keep the same colours, fonts and components.','- Name frames "<Screen> \u2014 <Device>", e.g. "Settings \u2014 Mobile". New frames go next to the frame they came from.',"- Keep frames consistent with each other: reuse the visual style already on the canvas.",...t.length?["- Other frames on this canvas: "+t.map(o=>`"${o.name}" (${o.image?`reference image, ${o.src}, `:""}${Ke(o)} ${o.w}\xD7${o.h}${o.kind==="route"?`, app route ${o.src}`:""})`).join("; ")+"."]:[]]}function an(e,t,o){let i=(t.batch||[]).map(r=>d.comments.find(s=>s.id===r)).filter(Boolean);return i.length?[o,"",`There are ${i.length} more comment${i.length>1?"s":""} on this same screen. Handle them all in this one pass, in order:`,...i.map((r,s)=>`
6
+ `);(b.length||I)&&((i.lists||={})[s]={up:b,...I?{order:w}:{}},r=!0),o[s]=m}else{let l=JSON.stringify(a??null);o[s]!==l&&((i.set||={})[s]=a??null,o[s]=l,r=!0)}return r?i:null}function J(e){for(let t of Ve){if(e.type!=="state"){ht(t.res,e);continue}let o=un(t);o&&ht(t.res,o)}}var zt=null,H=null,mt=new Set;function $(e){no(),clearTimeout(zt),zt=setTimeout(()=>J({type:"state"}),16),e||H?.schedulePush();for(let t of[...mt])t()}var ve=new Map;function Ee(e){if(e.kind!=="folder"||ve.has(e.id))return;let t=null;try{let o=k.watch(e.src,{recursive:!0},(i,r)=>{if(!(r&&(r.startsWith(".")||r.includes("node_modules")))){if(clearTimeout(t),Ze.add(e.id),v.get(e.id)?.inbox&&r&&V(e.id,`Editing ${h.basename(String(r))}`),e.building&&e.optionOf&&Date.now()-e.createdAt>1500){let s=d.comments.find(a=>a.id===e.optionOf.cid)?.options?.find(a=>a.frameId===e.id);s&&!s.touched&&(s.touched=!0,e.building="filling",$(),v.has(e.optionOf.from)&&V(e.optionOf.from,`Making option ${s.id}${s.placeholder?"":` \xB7 ${s.name}`}`))}t=setTimeout(()=>{J({type:"frame-changed",frameId:e.id,live:v.has(e.id)}),v.has(e.id)||H?.frameChanged(e)},v.has(e.id)?450:200)}});ve.set(e.id,o)}catch{}}function De(e){ve.get(e)?.close(),ve.delete(e)}d.frames.forEach(Ee);function pt(e=1280,t=800,o){let i=o&&d.frames.find(a=>a.id===o);if(i){let a=(b,w)=>d.frames.some(I=>b<I.x+I.w+60&&b+e+60>I.x&&w<I.y+I.h+60&&w+t+60>I.y),l=i.x+i.w+120,m=i.y;for(let b=0;b<50&&a(l,m);b++)l+=160;return{x:l,y:m}}if(!d.frames.length)return{x:0,y:0};let r=Math.max(...d.frames.map(a=>a.x+a.w)),s=Math.min(...d.frames.map(a=>a.y));return{x:r+120,y:s}}function hn(e){return(e||"frame").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"").slice(0,40)||"frame"}function ao(e){let t=e.split(/[?#]/)[0];return t==="/"||!t?"Home":t.split("/").filter(Boolean).map(o=>decodeURIComponent(o).replace(/\.html?$/,"").replace(/[-_]+/g," ").replace(/^\w/,i=>i.toUpperCase())).join(" / ").slice(0,60)}function W({src:e,route:t,name:o,w:i,h:r,x:s,y:a,near:l,dup:m}){if(i=+i||ye.Desktop[0],r=+r||ye.Desktop[1],t!=null){t="/"+String(t).replace(/^\/+/,"");let j=!m&&d.frames.find(D=>D.kind==="route"&&D.src===t&&D.w===i);if(j)return j;let R=s!=null&&a!=null?{x:+s,y:+a}:pt(i,r,l),C={id:N(6),name:o||t,kind:"route",src:t,entry:"",w:i,h:r,...R,createdAt:Date.now()};return d.frames.push(C),$(),C}let b=/^https?:\/\//.test(e);if(b){let j=null;try{j=new URL(e)}catch{}if(j&&/^(localhost|127\.0\.0\.1|\[::1\])$/.test(j.hostname)&&+j.port===K){if(j.pathname===X||j.pathname.startsWith(X+"/")||!d.proxy)throw new Error(`${e} is the Loop canvas itself, not your app. Give the address your app runs on (loop scan --url http://localhost:<port>), or a folder with an index.html.`);return W({route:j.pathname+j.search,name:o,w:i,h:r,x:s,y:a,near:l,dup:m})}let R=At(e);if(j&&R&&(!d.proxy||d.proxy===R))return d.proxy=R,W({route:j.pathname+j.search,name:o,w:i,h:r,x:s,y:a,near:l,dup:m})}let w=b?"url":"folder",I="index.html";b||(e=h.resolve(Z,e),k.existsSync(e)&&k.statSync(e).isFile()&&(I=h.basename(e),e=h.dirname(e)));let T=s!=null&&a!=null?{x:+s,y:+a}:pt(i,r,l),E={id:N(6),name:o||(b?new URL(e).pathname:h.basename(e)),kind:w,src:e,entry:I,w:i,h:r,...T,createdAt:Date.now()};return d.frames.push(E),Ee(E),$(),E}var Ie=e=>String(e).replace(/[&<>"]/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"})[t]),co=e=>`<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
7
+ <meta name="loop-placeholder" content="1"><title>${Ie(e)}</title><style>html,body{height:100%;margin:0;background:#fff}</style></head><body></body></html>`,lo=e=>`<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
8
+ <meta name="loop-empty" content="1"><title>${Ie(e)}</title><style>html,body{height:100%;margin:0;background:#fff}</style></head>
9
+ <body></body></html>`;function we(e){let t=hn(e),o=h.join(_,"frames",t),i=2;for(;k.existsSync(o);)o=h.join(_,"frames",`${t}-${i++}`);return o}function re(e){let t=new Set(d.frames.map(o=>o.name));if(!t.has(e))return e;for(let o=2;;o++)if(!t.has(`${e} ${o}`))return`${e} ${o}`}function Ht(e,t=!1){let o=we(e);return k.mkdirSync(o,{recursive:!0}),k.writeFileSync(h.join(o,"index.html"),t?lo(e):co(e)),o}var mn=new Set(["node_modules",".git",".loop",".next","dist","build"]);async function fe(e,t,o={bytes:3e7}){await M.mkdir(t,{recursive:!0});for(let i of await M.readdir(e,{withFileTypes:!0})){if(i.name.startsWith(".")||mn.has(i.name))continue;let r=h.join(e,i.name),s=h.join(t,i.name);if(i.isDirectory())await fe(r,s,o);else if(i.isFile()){let{size:a}=await M.stat(r);if((o.bytes-=a)<0)throw new Error("frame too large to snapshot");await M.copyFile(r,s)}}}async function gt(e,t=e,o=new Set){for(let i of await M.readdir(e,{withFileTypes:!0})){if(i.name.startsWith(".")||mn.has(i.name))continue;let r=h.join(e,i.name);i.isDirectory()?await gt(r,t,o):o.add(h.relative(t,r))}return o}function qe(e,t,o){for(let i of k.readdirSync(e,{withFileTypes:!0})){if(i.name.startsWith("."))continue;let r=h.join(e,i.name);i.isDirectory()?qe(r,t,o):o.add(h.relative(t,r))}}var Gt=new Map;async function _t(e,t){if(e.kind!=="folder")return null;let o=h.join(_,"history",t);try{return await fe(e.src,o),o}catch{return null}}async function He(e,t){if(e.kind!=="folder")return null;let o=h.join(_,"history",t);try{return await M.rm(o,{recursive:!0,force:!0}),await fe(e.src,o),o}catch{return null}}async function Ge(e,t){let o=await gt(e.src),i=await gt(t);for(let r of o)i.has(r)||await M.rm(h.join(e.src,r),{force:!0});await fe(t,e.src)}function Ut(e,t={}){let o=t.file_path||t.path||t.notebook_path,i=o?h.basename(o):"",r=String(t.command||"");switch(e){case"Read":return o&&/\.(png|jpe?g|webp|gif)$/i.test(o)?o.includes(`${h.sep}shots${h.sep}`)?"Looking at the screen":o.includes(`${h.sep}annotations${h.sep}`)?"Looking at your marks":"Looking at your reference":i?`Reading ${i}`:"Reading the code";case"Edit":case"MultiEdit":return i?`Editing ${i}`:"Editing";case"Write":return i?`Writing ${i}`:"Writing";case"Glob":case"Grep":return"Searching the code";case"Bash":return/cli\.mjs"? shot\b|\bloop shot\b/.test(r)?"Taking a screenshot":/cli\.mjs"? (new|add-route)\b/.test(r)?"Adding a frame":/cli\.mjs"? option\b/.test(r)?"Setting up an option":r?"Running a command":"Working\u2026";case"TodoWrite":return"Planning";case"WebFetch":case"WebSearch":return"Looking something up";default:return e}}var ye={Mobile:[393,852],Tablet:[834,1194],Desktop:[1440,1024]},et=e=>e.w<=480?"Mobile":e.w>=1100?"Desktop":e.h>=e.w*1.15?"Tablet":"Custom-size";function wt(e){let t=d.frames.filter(o=>o.id!==e?.id);return["","How Loop canvases work (follow these conventions):",`- A frame is one screen at one device size. Standard sizes: Mobile ${ye.Mobile.join("\xD7")}, Tablet ${ye.Tablet.join("\xD7")}, Desktop ${ye.Desktop.join("\xD7")}. When someone asks for a "mobile", "phone", "tablet", "iPad", "desktop" or "web" version, use the matching size.`,"- Change only the project (or this frame's folder). Never edit Loop itself: its canvas, toolbar, menus or .loop/ files are not yours to change.","- A frame IS the device screen: design edge to edge at that size. Never draw a phone or laptop mockup, notch, status bar, home indicator or bezel inside a frame, and never letterbox a small layout in the middle of a big frame.",'- "A mobile version of this" means the same screen, content and copy, re-laid-out for that width: single column, stacked sections, a mobile navigation pattern (top bar, tab bar or menu), tap targets of at least 44px, readable type. Keep the same colours, fonts and components.','- Name frames "<Screen> \u2014 <Device>", e.g. "Settings \u2014 Mobile". New frames go next to the frame they came from.',"- Keep frames consistent with each other: reuse the visual style already on the canvas.",...t.length?["- Other frames on this canvas: "+t.map(o=>`"${o.name}" (${o.image?`reference image, ${o.src}, `:""}${et(o)} ${o.w}\xD7${o.h}${o.kind==="route"?`, app route ${o.src}`:""})`).join("; ")+"."]:[]]}function pn(e,t,o){let i=(t.batch||[]).map(r=>d.comments.find(s=>s.id===r)).filter(Boolean);return i.length?[o,"",`There are ${i.length} more comment${i.length>1?"s":""} on this same screen. Handle them all in this one pass, in order:`,...i.map((r,s)=>`
11
10
  --- Comment ${s+2} ---
12
- ${Tt(e,r).message.replace(/^On my Loop canvas I'm looking at[^\n]*\n?/,"")}`),"","In your reply, give one short line per comment (1, 2, \u2026) saying what you changed."].join(`
13
- `):o}function It(e,t){let o=[];if(t.kind==="create")return o.push(`On my Loop canvas, design a new screen called "${e.name}" (${Ke(e)}, ${e.w}\xD7${e.h}):`,"",t.text,"","Write it as a complete index.html in the current working directory (self-contained HTML/CSS/JS; CDN imports and web fonts are fine, no build step), real content and working interactions where they make sense.","","Design it the way you would if I had asked you directly in a chat: make real design decisions (layout, hierarchy, type, colour, detail) so it looks finished, not like a wireframe.",`Look at it as you go: \`node ${Z} shot ${e.id} --dir ${A}\` saves a screenshot of the frame and prints its path. Check it after your first pass and iterate until it looks right at ${e.w}\xD7${e.h}.`,...lt(e),"","When you're done, tell me briefly what you made."),o.join(`
14
- `);if(e.kind==="canvas"){o.push("You are the design agent behind Loop, a canvas of live frames for the project in the current working directory. The user left a general comment on the canvas itself (not on a single frame).","","Frames on the canvas:",...d.frames.map(r=>`- "${r.name}": ${r.image?`an image placed as a reference, in ${r.src}`:r.kind==="folder"?`static files in ${r.src}`:r.kind==="frozen"?`a frozen snapshot of ${r.from||"an app screen"} (read-only)`:r.kind==="route"?`the app's ${r.src} route (dev server ${d.proxy})`:r.src}`));let i=t.anchor?.near;return t.anchor?.area&&i?.length?o.push("",`They drew a box on the canvas over: ${i.map(r=>`"${r}"`).join(", ")}. Those frames are the focus; the box is approximate.`):i?.length&&o.push("",`The comment was placed near: ${i.map(r=>`"${r}"`).join(", ")}.`),o.push(...lt(null)),o.push("","Comment:",'"""',t.text,'"""',"",`To add a frame: \`node ${Z} new "<Screen> \u2014 <Device>" --w <w> --h <h> --dir ${A}\` prints a folder; write a self-contained index.html there.`,cn.slice(2),"Decide which frames or source files it concerns and make the change. If it is a question or a note that needs no change, just answer it. When done, reply to the user the way you would in a chat: a short, plain summary of what you changed (one to three sentences). If they asked a question, answer it properly. If something was ambiguous, say what you assumed."),o.join(`
15
- `)}return io(Tt(e,t))}var qn=(e,t)=>{let o=`\`node ${Z} shot ${e.id} --dir ${A}\``,i=to(t)||Ce.has(t.id)&&t.shotSoon;return["Work on this the way you would if I had asked you directly in a chat:",i?`- This is the screen as I see it right now: ${i} (look at it first, along with the relevant code). No need to take that screenshot yourself.`:`- Understand it first: read the relevant code, and look at the screen as I see it (${o} saves a screenshot and prints its path).`,`- Match the effort to what I asked. A small, specific fix (wording, a colour, spacing, one element): just make it, precisely, and don't spend time on more screenshots. Design work, a layout change, or anything vague ("make it nicer", "this feels off"): make real design decisions (hierarchy, spacing, type, colour, detail), then check the result with a screenshot (`+o+") and iterate until it looks right.","- Keep it consistent with the rest of the screen and canvas unless I'm asking you to change the style.","- When you're done, reply like you would in a chat: what you changed, briefly, and anything I should know. Answer questions properly.",cn]},cn="- If a command or tool is refused, don't look for another way to do the same thing. Finish what you can and say in your reply what you needed and why: I'll see it with an Allow button.",ue=new Map,Ce=new Map;function eo(e,t){if(!Xe||!V||t.kind==="canvas"||t.id===Se)return;let o=ue.get(t.id)||0;e.shotSoon=h.join(A,"shots",`${t.id}-${e.id}.png`);let i=he(t,void 0,h.basename(e.shotSoon)).then(r=>{e.shot=r,e.shotRev=o}).catch(()=>{e.shotSoon=null}).finally(()=>Ce.delete(e.id));Ce.set(e.id,i)}var to=e=>e.shot&&e.shotRev===(ue.get(e.frameId)||0)&&$.existsSync(e.shot)?e.shot:null,no=e=>Ce.has(e.id)?Promise.race([Ce.get(e.id),new Promise(t=>setTimeout(t,3e3))]):null;function Tt(e,t){let o=[];e.sessionCwd&&o.push("(You are a background copy of the chat that launched this Loop canvas, carrying on from what was said there, because that chat is not listening right now. Do not start `loop watch`, and do not use `loop reply`, `loop note` or `loop inbox` here: do what the comment asks and answer in your final message. Your working directory is the project; the frame paths below are absolute.)");let i=t.parentId?d.comments.filter(l=>(l.id===t.parentId||l.parentId===t.parentId)&&l.id!==t.id&&l.createdAt<t.createdAt):[];if(o.push(`On my Loop canvas I'm looking at "${e.name}", a live ${Ke(e)} screen (${e.w}\xD7${e.h}).`),e.image)o.push(`It's an image placed on the canvas as a reference (the file is in the current working directory). Look at it. When the request is about another screen ("make that one look like this"), change that screen, not this image.`);else if(e.kind==="folder")o.push(`It's a static page: its files are in the current working directory (entry: ${e.entry}).`);else if(e.kind==="frozen")o.push(`It's a frozen snapshot of ${e.from?`the app's "${e.from}" screen`:"an app screen"}, kept as the "before". Leave the snapshot alone and change the app's source for that screen; its live frames show the result.`);else if(e.optionOf?.live){let l=d.comments.find(p=>p.id===e.optionOf.cid)?.options?.find(p=>p.id===e.optionOf.oid);o.push(`It's option ${e.optionOf.oid} of an app screen: the real app running with this option's files in place of the project's. This option's files are in ${l?.dir} (same paths as in the project, ${X}). Change ONLY this option: edit the files in that folder, and when the change needs a project file the option doesn't have yet, copy it into that folder at the same path first and edit the copy. Never edit the project's own files for this.`)}else e.kind==="route"?o.push(`It's the app's "${e.src}" route, running live from the project in the current working directory (dev server ${d.proxy}, hot reload; don't restart it).`):o.push(`It's a live page at ${e.src}, served from the project in the current working directory.`);if(e.kind==="folder")try{$.readFileSync(h.join(e.src,e.entry),"utf8").includes('name="loop-empty"')&&o.push(`It's empty right now: design the whole screen from scratch at exactly ${e.w}\xD7${e.h} in index.html, and remove the loop-empty meta tag.`)}catch{}if(i.length){o.push("","What we said so far about this:");for(let l of i)o.push(l.kind==="note"?`- You: ${l.text}`:`- Me: ${l.text}${l.reply&&l.status!=="interrupted"?`
11
+ ${Et(e,r).message.replace(/^On my Loop canvas I'm looking at[^\n]*\n?/,"")}`),"","In your reply, give one short line per comment (1, 2, \u2026) saying what you changed."].join(`
12
+ `):o}function Pt(e,t){let o=[];if(t.kind==="create")return o.push(`On my Loop canvas, design a new screen called "${e.name}" (${et(e)}, ${e.w}\xD7${e.h}):`,"",t.text,"","Write it as a complete index.html in the current working directory (self-contained HTML/CSS/JS; CDN imports and web fonts are fine, no build step), real content and working interactions where they make sense.","","Design it the way you would if I had asked you directly in a chat: make real design decisions (layout, hierarchy, type, colour, detail) so it looks finished, not like a wireframe.","Build it in passes I can watch fill in on the canvas (it reloads the frame on every write): first write the page with its overall layout and each main block in place (real headings, the blocks as simple boxes), then fill the blocks one at a time, top to bottom, with one edit each. Never leave the frame empty while you work, and don't write the whole page in one go at the end.",`Look at it as you go: \`node ${Q} shot ${e.id} --dir ${_}\` saves a screenshot of the frame and prints its path. Check it after your first pass and iterate until it looks right at ${e.w}\xD7${e.h}.`,...wt(e),"","When you're done, tell me briefly what you made."),o.join(`
13
+ `);if(e.kind==="canvas"){o.push("You are the design agent behind Loop, a canvas of live frames for the project in the current working directory. The user left a general comment on the canvas itself (not on a single frame).","","Frames on the canvas:",...d.frames.map(r=>`- "${r.name}": ${r.image?`an image placed as a reference, in ${r.src}`:r.kind==="folder"?`static files in ${r.src}`:r.kind==="frozen"?`a frozen snapshot of ${r.from||"an app screen"} (read-only)`:r.kind==="route"?`the app's ${r.src} route (dev server ${d.proxy})`:r.src}`));let i=t.anchor?.near;return t.anchor?.area&&i?.length?o.push("",`They drew a box on the canvas over: ${i.map(r=>`"${r}"`).join(", ")}. Those frames are the focus; the box is approximate.`):i?.length&&o.push("",`The comment was placed near: ${i.map(r=>`"${r}"`).join(", ")}.`),o.push(...wt(null)),o.push("","Comment:",'"""',t.text,'"""',"",`To add a frame: \`node ${Q} new "<Screen> \u2014 <Device>" --w <w> --h <h> --dir ${_}\` prints a folder; write a self-contained index.html there.`,gn.slice(2),"Decide which frames or source files it concerns and make the change. If it is a question or a note that needs no change, just answer it. When done, reply to the user the way you would in a chat: a short, plain summary of what you changed (one to three sentences). If they asked a question, answer it properly. If something was ambiguous, say what you assumed."),o.join(`
14
+ `)}return wo(Et(e,t))}var fo=(e,t)=>{let o=`\`node ${Q} shot ${e.id} --dir ${_}\``,i=ho(t)||Le.has(t.id)&&t.shotSoon;return["Work on this the way you would if I had asked you directly in a chat:",i?`- This is the screen as I see it right now: ${i} (look at it first, along with the relevant code). No need to take that screenshot yourself.`:`- Understand it first: read the relevant code, and look at the screen as I see it (${o} saves a screenshot and prints its path).`,`- Match the effort to what I asked. A small, specific fix (wording, a colour, spacing, one element): just make it, precisely, and don't spend time on more screenshots. Design work, a layout change, or anything vague ("make it nicer", "this feels off"): make real design decisions (hierarchy, spacing, type, colour, detail), then check the result with a screenshot (`+o+") and iterate until it looks right.","- Keep it consistent with the rest of the screen and canvas unless I'm asking you to change the style.","- When you're done, reply like you would in a chat: what you changed, briefly, and anything I should know. Answer questions properly.",gn]},gn="- If a command or tool is refused, don't look for another way to do the same thing. Finish what you can and say in your reply what you needed and why: I'll see it with an Allow button.",ue=new Map,Le=new Map;function uo(e,t){if(!tt||!K||t.kind==="canvas"||t.id===be)return;let o=ue.get(t.id)||0;e.shotSoon=h.join(_,"shots",`${t.id}-${e.id}.png`);let i=he(t,void 0,h.basename(e.shotSoon)).then(r=>{e.shot=r,e.shotRev=o}).catch(()=>{e.shotSoon=null}).finally(()=>Le.delete(e.id));Le.set(e.id,i)}var ho=e=>e.shot&&e.shotRev===(ue.get(e.frameId)||0)&&k.existsSync(e.shot)?e.shot:null,mo=e=>Le.has(e.id)?Promise.race([Le.get(e.id),new Promise(t=>setTimeout(t,3e3))]):null,Ae=(e,t)=>typeof e=="string"?e.slice(0,t):void 0;function Bt(e){return(Array.isArray(e)?e:[]).slice(0,8).filter(t=>t&&typeof t.frameId=="string"&&F(t.frameId)).map(t=>({frameId:t.frameId,label:Ae(t.label,80)||"it",selector:Ae(t.selector,400),tag:Ae(t.tag,20),text:Ae(t.text,300),html:Ae(t.html,3e3)}))}function yt(e){return e.kind==="folder"?`static page, files in ${e.src} (entry ${e.entry||"index.html"})`:e.kind==="route"?`the app's ${e.src.replace(/^\/__loop\/o\/[^/]+\/[A-F]/,"")} route`:e.kind==="frozen"?"a frozen snapshot":`live page ${e.src}`}function po(e){return e.refs?.length?["","This message points at other things on the canvas (each written as @name in it):",...e.refs.map(t=>{let o=F(t.frameId);return o?t.selector?`- @${t.label}: the ${t.tag||"element"} \`${t.selector}\` in the frame "${o.name}" (${yt(o)})${t.text?`, text: "${t.text}"`:""}${t.html?`
15
+ Its HTML as it is now:
16
+ ${t.html.replace(/\n/g,`
17
+ `)}`:""}`:`- @${t.label}: the frame "${o.name}" (${yt(o)}, ${o.w}\xD7${o.h})`:`- @${t.label}: (no longer on the canvas)`})]:[]}function Et(e,t){let o=[];e.sessionCwd&&o.push("(You are a background copy of the chat that launched this Loop canvas, carrying on from what was said there, because that chat is not listening right now. Do not start `loop watch`, and do not use `loop reply`, `loop note` or `loop inbox` here: do what the comment asks and answer in your final message. Your working directory is the project; the frame paths below are absolute.)");let i=t.parentId?d.comments.filter(l=>(l.id===t.parentId||l.parentId===t.parentId)&&l.id!==t.id&&l.createdAt<t.createdAt):[];if(o.push(`On my Loop canvas I'm looking at "${e.name}", a live ${et(e)} screen (${e.w}\xD7${e.h}).`),e.image)o.push(`It's an image placed on the canvas as a reference (the file is in the current working directory). Look at it. When the request is about another screen ("make that one look like this"), change that screen, not this image.`);else if(e.kind==="folder"&&e.draftOf)o.push(`It's an editable draft copied from the app's "${e.draftOf}" screen: a static page whose files are in the current working directory (entry: ${e.entry}). Change this copy, not the app. Only when I ask to use it in the app (or "make it real"), build it into the app's source for that screen or as the new screen I describe, matching what this draft shows.`);else if(e.kind==="folder")o.push(`It's a static page: its files are in the current working directory (entry: ${e.entry}).`);else if(e.kind==="frozen")o.push(`It's a frozen snapshot of ${e.from?`the app's "${e.from}" screen`:"an app screen"}, kept as the "before". Leave the snapshot alone and change the app's source for that screen; its live frames show the result.`);else if(e.optionOf?.live){let l=d.comments.find(m=>m.id===e.optionOf.cid)?.options?.find(m=>m.id===e.optionOf.oid);o.push(`It's option ${e.optionOf.oid} of an app screen: the real app running with this option's files in place of the project's. This option's files are in ${l?.dir} (same paths as in the project, ${Z}). Change ONLY this option: edit the files in that folder, and when the change needs a project file the option doesn't have yet, copy it into that folder at the same path first and edit the copy. Never edit the project's own files for this.`)}else e.kind==="route"?o.push(`It's the app's "${e.src}" route, running live from the project in the current working directory (dev server ${d.proxy}, hot reload; don't restart it).`):o.push(`It's a live page at ${e.src}, served from the project in the current working directory.`);if(e.kind==="folder")try{k.readFileSync(h.join(e.src,e.entry),"utf8").includes('name="loop-empty"')&&o.push(`It's empty right now: design the whole screen from scratch at exactly ${e.w}\xD7${e.h} in index.html, and remove the loop-empty meta tag.`)}catch{}if(i.length){o.push("","What we said so far about this:");for(let l of i)o.push(l.kind==="note"?`- You: ${l.text}`:`- Me: ${l.text}${l.reply&&l.status!=="interrupted"?`
16
18
  - You: ${l.reply}`:l.status==="interrupted"?`
17
19
  - (you were working on that when I interrupted)`:""}`)}t.steeredFrom&&o.push("","I interrupted you to add this. The files may already have part of that work in them: carry on from where things are and do what both messages ask (this one wins where they conflict)."),o.push("",t.text);let r=t.anchor,s=[];if(r?.scope==="area")s.push(`I highlighted an area: x=${Math.round(r.x)}, y=${Math.round(r.y)}, ${Math.round(r.w)}\xD7${Math.round(r.h)}px in the ${e.w}\xD7${e.h} viewport. It's roughly where to look, not a strict boundary.`),r.elements?.length&&s.push("Inside it: "+r.elements.map(l=>`<${l.tag}> \`${l.selector}\`${l.text?` ("${l.text}")`:""}`).join("; ")+"."),r.container&&s.push(`Smallest element wrapping it: <${r.container.tag}> \`${r.container.selector}\`.`);else if(r?.scope==="elements"&&r.elements?.length){s.push(`I picked ${r.elements.length===1?"this exact element":`these ${r.elements.length} exact elements`}:`);for(let l of r.elements.slice(0,10))s.push(`- <${l.tag}> \`${l.selector}\`${l.text?` ("${l.text}")`:""}`),l.html&&s.push(" ```html"," "+l.html.replace(/\n/g,`
18
- `)," ```")}else r?.scope==="frame"?s.push("I mean the whole screen."):r&&r.selector?(s.push(`My comment pin is on <${r.tag}> \`${r.selector}\`${r.text?` ("${r.text}")`:""}. It may be about that element or the area around it.`),r.html&&s.push("```html",r.html,"```")):r&&s.push(`I pointed at x=${Math.round(r.x)}, y=${Math.round(r.y)} in the viewport.`);t.ink?.length&&s.push(`I drew marks over: ${t.ink.map(l=>`\`${l.selector}\`${l.text?` ("${l.text}")`:""}`).join(", ")}.`),t.annotation&&s.push(`A picture of my marks over the screen: ${t.annotation} (look at it).`),t.attachments?.length&&s.push(`I attached ${t.attachments.length===1?"a reference":`${t.attachments.length} references`}; look at ${t.attachments.length===1?"it":"them"} first: ${t.attachments.map(l=>`${l.path} (${l.type})`).join(", ")}.`);let a=oo(e,r);return a&&s.push(a),s.length&&o.push("",...s),o.push("",...qn(e,t),"",`(This message's id, for Loop commands: ${t.id})`),{message:o.join(`
19
- `),reference:dn(e)}}function oo(e,t){if(e.kind!=="folder"||!t)return"";let o;try{o=$.readFileSync(h.join(e.src,e.entry||"index.html"),"utf8")}catch{return""}let i=[...(t.elements||[]).map(s=>s.html),t.html,t.container?.html].filter(Boolean),r=[];for(let s of i){let a=/^<[^>]+>/.exec(s)?.[0];if(!a)continue;let l=o.indexOf(a);l<0||o.indexOf(a,l+1)>=0||r.push(o.slice(0,l).split(`
20
- `).length)}return r.length?`It is in ${e.entry||"index.html"} at line ${[...new Set(r)].sort((s,a)=>s-a).join(", ")}: open it there, no need to search for it.`:""}function dn(e){let t=[],[o,i]=ye.Mobile;t.push("You are working inside Loop, a canvas of live frames the user reviews and comments on. Their comments reach you as messages."),t.push("","New frames: if they ask for a new frame (a mobile/desktop version, or a separate copy to keep side by side), leave this frame as it is and add one next to it:"),t.push(`- Resize this frame (when what you are building needs another size): \`node ${Z} size ${e.id} <width> <height> --dir ${A}\``),e.kind==="route"?t.push(`- The same app screen at another size: \`node ${Z} add-route ${e.src} --name "<Screen> \u2014 Mobile" --w ${o} --h ${i} --near ${e.id} --dir ${A}\`. It's the real app, so if it doesn't fit that width, fix the responsive styles in the source.`,`- A brand-new static design: \`node ${Z} new "<Screen> \u2014 <Device>" --w <w> --h <h> --near ${e.id} --dir ${A}\` prints a folder; write a complete self-contained index.html there.`):t.push(`- \`node ${Z} new "<Screen> \u2014 <Device>" --w <w> --h <h> --near ${e.id} --dir ${A}\` (e.g. --w ${o} --h ${i} for mobile) prints a folder; write a complete, self-contained index.html there, based on this frame and adapted to that size.`);let r=e.kind==="folder"?e.src:X;return e.kind==="folder"?t.push("",`Options: if they ask for options to choose from (an option, a variation, alternatives, ideas), leave this frame's files alone. How many: exactly what they asked for. "A variation", "another version", "a better one" is ONE. A number is that number. Only a plural with no number ("some options", "ideas") is your call: 2 or 3, each genuinely different. Each message ends with its id; for each option run \`node ${Z} option <message id> "<short name>" --dir ${A}\`. It prints a folder that is a full copy of this frame and is already on the canvas as its own frame beside this one: make that option's changes there, editing its files in place. They watch each one fill in as you work, so start the first option early and finish one before starting the next. \`node ${Z} shot ${e.id} --option <message id>:A --dir ${A}\` shows how option A looks. Reply with a line per option on how it differs.`):t.push("",`Options: if they ask for options to choose from (an option, a variation, alternatives, ideas), don't change the real files. How many: exactly what they asked for. "A variation", "another version", "a better one" is ONE. A number is that number. Only a plural with no number ("some options", "ideas") is your call: 2 or 3, each genuinely different. Each message ends with its id; for each option, \`node ${Z} option <message id> "<short name>" --dir ${A}\` prints an empty folder: write the complete new version of every file that option changes there, at the same path relative to ${r}. Each option shows up running beside this screen as its own frame as you write it (or, for apps that need a build step, they preview it in the frame), and they pick one; reply with a line per option on how it differs.`),e.kind==="folder"&&t.push("","This frame is self-contained: keep its files in its folder (CDN imports are fine, no build step)."),t.push(...lt(e)),t.join(`
21
- `)}function io(e){return e.reference?`${e.message}
20
+ `)," ```")}else r?.scope==="frame"?s.push("I mean the whole screen."):r&&r.selector?(s.push(`My comment pin is on <${r.tag}> \`${r.selector}\`${r.text?` ("${r.text}")`:""}. It may be about that element or the area around it.`),r.html&&s.push("```html",r.html,"```")):r&&s.push(`I pointed at x=${Math.round(r.x)}, y=${Math.round(r.y)} in the viewport.`);t.ink?.length&&s.push(`I drew marks over: ${t.ink.map(l=>`\`${l.selector}\`${l.text?` ("${l.text}")`:""}`).join(", ")}.`),t.annotation&&s.push(`A picture of my marks over the screen: ${t.annotation} (look at it).`),t.attachments?.length&&s.push(`I attached ${t.attachments.length===1?"a reference":`${t.attachments.length} references`}; look at ${t.attachments.length===1?"it":"them"} first: ${t.attachments.map(l=>`${l.path} (${l.type})`).join(", ")}.`);let a=go(e,r);return a&&s.push(a),s.length&&o.push("",...s),o.push("",...fo(e,t),"",`(This message's id, for Loop commands: ${t.id})`),o.push(...po(t)),{message:o.join(`
21
+ `),reference:wn(e)}}function go(e,t){if(e.kind!=="folder"||!t)return"";let o;try{o=k.readFileSync(h.join(e.src,e.entry||"index.html"),"utf8")}catch{return""}let i=[...(t.elements||[]).map(s=>s.html),t.html,t.container?.html].filter(Boolean),r=[];for(let s of i){let a=/^<[^>]+>/.exec(s)?.[0];if(!a)continue;let l=o.indexOf(a);l<0||o.indexOf(a,l+1)>=0||r.push(o.slice(0,l).split(`
22
+ `).length)}return r.length?`It is in ${e.entry||"index.html"} at line ${[...new Set(r)].sort((s,a)=>s-a).join(", ")}: open it there, no need to search for it.`:""}function wn(e){let t=[],[o,i]=ye.Mobile;t.push("You are working inside Loop, a canvas of live frames the user reviews and comments on. Their comments reach you as messages."),t.push("","New frames: if they ask for a new frame (a mobile/desktop version, or a separate copy to keep side by side), leave this frame as it is and add one next to it:"),t.push(`- Resize this frame (when what you are building needs another size): \`node ${Q} size ${e.id} <width> <height> --dir ${_}\``),e.kind==="route"?t.push(`- The same app screen at another size: \`node ${Q} add-route ${e.src} --name "<Screen> \u2014 Mobile" --w ${o} --h ${i} --near ${e.id} --dir ${_}\`. It's the real app, so if it doesn't fit that width, fix the responsive styles in the source.`,`- A brand-new static design: \`node ${Q} new "<Screen> \u2014 <Device>" --w <w> --h <h> --near ${e.id} --dir ${_}\` prints a folder; write a complete self-contained index.html there.`):t.push(`- \`node ${Q} new "<Screen> \u2014 <Device>" --w <w> --h <h> --near ${e.id} --dir ${_}\` (e.g. --w ${o} --h ${i} for mobile) prints a folder; write a complete, self-contained index.html there, based on this frame and adapted to that size.`);let r=e.kind==="folder"?e.src:Z;return e.kind==="folder"?t.push("",`Options: if they ask for options to choose from (an option, a variation, alternatives, ideas), leave this frame's files alone. How many: exactly what they asked for. "A variation", "another version", "a better one" is ONE. A number is that number. Only a plural with no number ("some options", "ideas") is your call: 2 or 3, each genuinely different. Each message ends with its id; for each option run \`node ${Q} option <message id> "<short name>" --dir ${_}\`. It prints a folder that is a full copy of this frame and is already on the canvas as its own frame beside this one: make that option's changes there, editing its files in place. They watch each one fill in as you work, so start the first option early and finish one before starting the next. \`node ${Q} shot ${e.id} --option <message id>:A --dir ${_}\` shows how option A looks. Reply with a line per option on how it differs.`):t.push("",`Options: if they ask for options to choose from (an option, a variation, alternatives, ideas), don't change the real files. How many: exactly what they asked for. "A variation", "another version", "a better one" is ONE. A number is that number. Only a plural with no number ("some options", "ideas") is your call: 2 or 3, each genuinely different. Each message ends with its id; for each option, \`node ${Q} option <message id> "<short name>" --dir ${_}\` prints an empty folder: write the complete new version of every file that option changes there, at the same path relative to ${r}. Each option shows up running beside this screen as its own frame as you write it (or, for apps that need a build step, they preview it in the frame), and they pick one; reply with a line per option on how it differs.`),e.kind==="folder"&&t.push("","This frame is self-contained: keep its files in its folder (CDN imports are fine, no build step)."),t.push(...wt(e)),t.join(`
23
+ `)}function wo(e){return e.reference?`${e.message}
22
24
 
23
25
  ---
24
26
  How Loop works (reference):
25
- ${e.reference}`:e.message}var Se="__canvas";function R(e){return e!==Se?d.frames.find(t=>t.id===e):(d.canvasFrame||={id:Se,name:"Canvas",kind:"canvas",src:X,entry:"",w:0,h:0},d.canvasFrame)}var ft=0,Ye=()=>Date.now()-ft<45e3;function ro(e){let t=v.get(e.frameId),o=t&&d.comments.find(s=>s.id===t.commentId),i=e.parentId||e.id;if(e.status!=="queued"||!o||o.id!==i&&o.parentId!==i)return!1;e.steeredFrom=o.id,d.comments.splice(d.comments.indexOf(e),1);let r=d.comments.findIndex(s=>s.frameId===e.frameId&&s.status==="queued");return d.comments.splice(r<0?d.comments.length:r,0,e),t.steered=!0,t.canceled=!0,t.child?t.child.kill("SIGTERM"):oe(o,"canceled","Stopped."),S(),!0}function ln(e){return e.kind==="folder"?e.src:X}async function Nt(e){let t=new Set;try{Je(e,e,t)}catch{}return[...t]}async function et(e,t){let o=R(e.frameId);if(!o)throw new Error("no frame");if(e.optionChosen)throw new Error("an option was already chosen");let i=ln(o),r=h.join(A,"options",e.id,"_original"),s=new Set;for(let p of e.options||[])for(let b of await Nt(p.dir))s.add(b);if(!$.existsSync(r)){await M.mkdir(r,{recursive:!0});let p=[];for(let b of s){let k=h.join(i,b);ie(i,k)&&($.existsSync(k)?(await M.mkdir(h.dirname(h.join(r,b)),{recursive:!0}),await M.copyFile(k,h.join(r,b))):p.push(b))}await M.writeFile(h.join(r,".missing.json"),JSON.stringify(p))}let a=JSON.parse(await M.readFile(h.join(r,".missing.json"),"utf8").catch(()=>"[]"));for(let p of s){let b=h.join(i,p);ie(i,b)&&(a.includes(p)?await M.rm(b,{force:!0}):$.existsSync(h.join(r,p))&&await M.copyFile(h.join(r,p),b))}let l=t&&(e.options||[]).find(p=>p.id===t);if(t&&!l)throw new Error("no such option");if(l)for(let p of await Nt(l.dir)){let b=h.join(i,p);ie(i,b)&&(await M.mkdir(h.dirname(b),{recursive:!0}),await M.copyFile(h.join(l.dir,p),b))}e.optionApplied=l?l.id:null,S(),J({type:"frame-changed",frameId:o.id})}var Xe=process.env.LOOP_CHROME||["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome","/Applications/Chromium.app/Contents/MacOS/Chromium","/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge","/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",h.join(ae.homedir(),"Applications/Google Chrome.app/Contents/MacOS/Google Chrome")].find(e=>$.existsSync(e))||ke("google-chrome")||ke("chromium")||ke("chromium-browser");function so(e){return e.kind==="folder"||e.kind==="frozen"?`http://127.0.0.1:${V}/__loop/f/${e.id}/${encodeURIComponent(e.entry||"index.html")}`:e.kind==="route"?`http://127.0.0.1:${V}${e.src}`:e.src}var B=null;function ut(){let e=B;if(B=null,!!e){clearTimeout(e.idle);try{e.child.kill("SIGKILL")}catch{}$.rm(e.profile,{recursive:!0,force:!0},()=>{})}}async function fn(){if(B)return clearTimeout((await B).idle),B;B=(async()=>{let t=$.mkdtempSync(h.join(ae.tmpdir(),"loop-shot-")),o=Oe(Xe,["--headless=new","--use-angle=swiftshader","--enable-unsafe-swiftshader","--ignore-gpu-blocklist","--hide-scrollbars","--no-first-run","--no-default-browser-check","--mute-audio",`--user-data-dir=${t}`,"--remote-debugging-port=0","--window-size=1280,900","about:blank"],{stdio:"ignore"}),i={child:o,profile:t,port:null,idle:null};o.on("exit",()=>{B&&(B===e||B===i)&&(B=null,$.rm(t,{recursive:!0,force:!0},()=>{}))});for(let r=0;r<100&&!i.port;r++)try{i.port=$.readFileSync(h.join(t,"DevToolsActivePort"),"utf8").split(`
26
- `)[0]}catch{await new Promise(s=>setTimeout(s,50))}if(!i.port){try{o.kill("SIGKILL")}catch{}throw new Error("Chrome did not start")}return i})();let e=B;try{let t=await e;return B===e&&(B=t),t}catch(t){throw B===e&&(B=null),t}}var Ae=new Map,Ue=new Set;async function ao(e){let t=`${e.w}x${e.h}`,o=Ae.get(e.id);if(o&&!(o instanceof Promise)&&o.size===t&&!Ue.has(e.id)&&$.existsSync(o.file))return o.file;if(o instanceof Promise)return(await o).file;Ue.delete(e.id);let i=he(e).then(r=>{let s=Ae.get(e.id),a={file:r,size:t};return Ae.set(e.id,a),a}).catch(r=>{throw Ae.delete(e.id),r});return Ae.set(e.id,i),(await i).file}var Wt=Promise.resolve();function he(e,t,o){let i=Wt.then(()=>co(e,t,o));return Wt=i.catch(()=>{}),i}async function co(e,t,o){if(!Xe)throw new Error("No Chrome, Edge, Brave or Chromium found for screenshots (set LOOP_CHROME to one).");let i=globalThis.WebSocket;if(!i)throw new Error("Screenshots need Node 22 or newer");let r=h.join(A,"shots");$.mkdirSync(r,{recursive:!0});let s=h.join(r,o||`${e.id}-${Date.now()}${N(3)}.png`),a=Math.round(e.w),l=Math.round(e.h),p=await fn();p.busy=(p.busy||0)+1;let b=null,k=null;try{for(let C=0;C<40&&!b;C++)try{b=await(await fetch(`http://127.0.0.1:${p.port}/json/new?about:blank`,{method:"PUT"})).json()}catch{await new Promise(z=>setTimeout(z,50))}if(!b?.webSocketDebuggerUrl)throw new Error("Chrome tab not found");k=new i(b.webSocketDebuggerUrl),await new Promise((C,z)=>{k.onopen=C,k.onerror=()=>z(new Error("DevTools connection failed"))});let I=0,T=new Map,_=null;k.onmessage=C=>{let z=JSON.parse(C.data);z.id&&T.has(z.id)?(T.get(z.id)(z),T.delete(z.id)):z.method==="Page.loadEventFired"&&_?.()};let O=(C,z={})=>new Promise((U,te)=>{let je=++I;T.set(je,U),setTimeout(()=>te(new Error("Screenshot timed out")),3e4),k.send(JSON.stringify({id:je,method:C,params:z}))});await O("Page.enable"),await O("Page.bringToFront"),await O("Emulation.setDeviceMetricsOverride",{width:a,height:l,deviceScaleFactor:2,mobile:!1});let F=new Promise(C=>{_=C,setTimeout(C,12e3)});await O("Page.navigate",{url:t||so(e)}),await F,await Promise.race([O("Runtime.evaluate",{awaitPromise:!0,expression:"(async () => { try { await document.fonts.ready; } catch {} await Promise.all([...document.images].filter(i => !i.complete).map(i => new Promise(r => { i.onload = i.onerror = r; }))); await new Promise(r => setTimeout(r, 250)); })()"}),new Promise(C=>setTimeout(C,2500))]);let D=await O("Page.captureScreenshot",{format:"png",clip:{x:0,y:0,width:a,height:l,scale:1}});if(!D.result?.data)throw new Error("Screenshot failed");return $.writeFileSync(s,Buffer.from(D.result.data,"base64")),s}catch(I){throw b||ut(),I}finally{try{k?.close()}catch{}b?.id&&fetch(`http://127.0.0.1:${p.port}/json/close/${b.id}`).catch(()=>{}),p.busy--,clearTimeout(p.idle),p.busy||(p.idle=setTimeout(()=>{B===p&&!p.busy&&ut()},4*6e4))}}function se(e){if(v.has(e))return;let t=i=>i.frameId===e&&i.status==="queued"&&!i.inBatch&&(d.agent.mode!=="inbox"||i.background),o=d.comments.find(t);if(o){if(delete o.batch,o.kind!=="create"&&e!==Se&&!R(e)?.optionOf){let i=d.comments.filter(r=>r!==o&&t(r)&&r.kind!=="create"&&!ht(r.text)).slice(0,5);if(i.length&&!ht(o.text)){o.batch=i.map(r=>r.id);for(let r of i)r.inBatch=o.id,r.status="working"}}Sn(o)}}function Ot(){for(let e of d.frames)se(e.id);se(Se)}var lo=new Set(["Reading the code","Editing","Writing","Working\u2026","Setting up an option"]);function Y(e,t,o){let i=v.get(e);if(!i)return;i.step=t;let r=o==null&&t&&d.comments.find(s=>s.id===i.commentId);if(r&&!/^(Writing…|Reading your comment…|Your chat picked this up|Working…)$/.test(t)){let s=r.steps||=[],a=s[s.length-1];!lo.has(t)&&(!a||a.s!==t)&&(s.push({t:Date.now(),s:t}),s.length>60&&s.splice(0,s.length-60))}J({type:"activity",frameId:e,commentId:i.commentId,step:t,...o!=null?{partial:o}:{}}),H?.liveChanged(v)}async function un(e){let t=R(e.frameId);if(!(!t||t.kind!=="folder"||!e.options?.length||!V)){if(!e.origThumb&&!e.optionApplied)try{e.origThumb=`/__loop/shots/${h.basename(await he(t))}`,S()}catch{}for(let o of e.options)try{let i=await he(t,`http://127.0.0.1:${V}/__loop/optview/${e.id}/${o.id}/${encodeURIComponent(t.entry||"index.html")}`);o.thumb=`/__loop/shots/${h.basename(i)}`,S()}catch{}}}var G={GAP:60,PAD:48,TOP:72},We=(e,t)=>t*e.w+(t-1)*G.GAP+G.PAD*2;function ht(e){let t=String(e||""),o=/\b(\d|two|three|four|five|six|a couple of|couple of|a few|few|some|several|more|other|different)\s+(?:[\w-]+\s+){0,3}?(options?|variations?|variants?|alternatives?|versions|directions|takes|ideas|explorations?)\b/i.exec(t);if(o){let i=o[1].toLowerCase(),r={two:2,three:3,four:4,five:5,six:6,"a couple of":2,"couple of":2,"a few":3,few:3,some:2,several:3,more:2,other:2,different:2}[i]??+i;return Math.max(1,Math.min(4,r))}return/\b(options|variations|variants|alternatives|explorations)\b/i.test(t)&&/\b(give|make|create|show|try|explore|generate|come up|want|need|can (we|you)|let'?s|what about)\b/i.test(t)?2:/\b(another|a new|one more|a different|an alternative|a second)\s+(version|variation|variant|option|take|direction|design|layout)\b|\bduplicate (this|it)\b|\b(make|create) a copy\b/i.test(t)||/\b(a|an|one)\s+(?:[\w-]+\s+){0,2}?(variation|variant|alternative|option)\b/i.test(t)?1:0}function mt(e,t,o=new Set){let i=a=>a.y<e.y+e.h&&a.y+a.h>e.y&&a.x+a.w/2>t.x+t.w,r=[...d.frames.filter(a=>a.id!==t.id&&!o.has(a.id)&&i(a)),...d.sections.filter(a=>!o.has(a.id)&&i(a))];if(!r.length)return;let s=e.x+e.w+G.GAP-Math.min(...r.map(a=>a.x));if(s>0)for(let a of r)a.x+=s}function Ve(e,t,o){let i=d.sections.find(r=>r.id===e.optionSection);if(i){if(o>(i.slots||0)){let r=new Set([i.id,...(e.options||[]).map(s=>s.frameId)]);mt({x:i.x,y:i.y,w:We(t,o),h:i.h},t,r),i.w=We(t,o),i.slots=o}}else{let r={x:t.x+t.w+G.GAP+20,y:t.y-G.TOP,w:We(t,o),h:t.h+G.TOP+G.PAD};mt(r,t),i={id:N(6),name:`Options \xB7 ${t.name}`,...r,optionsFor:e.id,slots:o,createdAt:Date.now()},d.sections.push(i),e.optionSection=i.id}return i}function fo(){let e=[...v.entries()].filter(([s])=>s!==Se);if(e.length!==1)return null;let[t,o]=e[0],i=R(t),r=d.comments.find(s=>s.id===o.commentId);return i&&r&&i.kind==="folder"&&!i.optionOf?{c:r,f:i}:null}function uo(e,t,o,i){e.options||=[];let r=String.fromCharCode(65+e.options.length),s=e.options.length,a={id:r,name:i||`Option ${r}`};e.options.push(a);let l=Ve(e,t,Math.max(s+1,d.sections.find(b=>b.id===e.optionSection)?.slots||0)),p=W({src:o,name:re(i||`Option ${r}`),w:t.w,h:t.h,x:l.x+G.PAD+s*(t.w+G.GAP),y:t.y});return p.optionOf={cid:e.id,oid:r,from:t.id},p.building=!0,a.dir=o,a.frameId=p.id,a.madeAt=Date.now(),a.touched=!0,S(),p}async function zt(e,t,o){if(e.options||=[],e.options.length>=6)throw new Error("up to 6 options");let i=String.fromCharCode(65+e.options.length),r=e.options.length,s={id:i,name:o||`Option ${i}`,placeholder:!o};e.options.push(s);let a=Ve(e,t,Math.max(r+1,d.sections.find(b=>b.id===e.optionSection)?.slots||0)),l=we(`${t.name} option ${i} ${N(3)}`);await fe(t.src,l);let p=W({src:h.join(l,t.entry||"index.html"),name:re(o?`${i} \xB7 ${o}`:`Option ${i}`),w:t.w,h:t.h,x:a.x+G.PAD+r*(t.w+G.GAP),y:t.y});return p.optionOf={cid:e.id,oid:i,from:t.id},p.building=!0,s.dir=l,s.frameId=p.id,s.madeAt=Date.now(),S(),s}function ho(e){if(e.touched)return!0;let t=R(d.comments.find(o=>o.options?.includes(e))?.frameId)?.src;try{let o=new Set;return Je(e.dir,e.dir,o),[...o].some(i=>{let r=t&&h.join(t,i);try{return!r||!$.readFileSync(h.join(e.dir,i)).equals($.readFileSync(r))}catch{return!0}})}catch{return!1}}function Ht(e){let t=(e.options||[]).filter(a=>a.frameId);if(!t.length)return;let o=R(e.frameId),i=t.filter(a=>!a.placeholder&&ho(a)),r=new Set(t.filter(a=>!i.includes(a)).map(a=>a.frameId));for(let a of r)_e(a);d.frames=d.frames.filter(a=>!r.has(a.id)),e.options=e.options.filter(a=>!a.frameId||i.includes(a));let s=d.sections.find(a=>a.id===e.optionSection);i.forEach((a,l)=>{let p=d.frames.find(b=>b.id===a.frameId);p&&(delete p.building,s&&o&&(p.x=s.x+G.PAD+l*(o.w+G.GAP)))}),s&&!i.length?(d.sections=d.sections.filter(a=>a!==s),delete e.optionSection):s&&o&&(s.w=We(o,i.length),s.slots=i.length)}async function hn(e){let t=R(e.frameId),o=(e.options||[]).filter(s=>s.files?.length&&!s.frameId);if(!t||!o.length)return!1;if(t.kind!=="folder"){if(e.optionApplied)return!1;let s=0;for(let a of o)await gt(e,t,null,a).catch(()=>null)&&s++;return s>0}let i=Ve(e,t,o.length),r=0;for(let s of o){let a=we(`${t.name} ${s.id} ${s.name}`);try{await fe(t.src,a),await fe(s.dir,a)}catch{continue}let l=W({src:h.join(a,t.entry||"index.html"),name:re(`${s.id} \xB7 ${s.name}`),w:t.w,h:t.h,x:i.x+G.PAD+r++*(t.w+G.GAP),y:t.y});l.optionOf={cid:e.id,oid:s.id,from:t.id},s.frameId=l.id}return S(),r>0}var pt=/^\/__loop\/o\/([A-Za-z0-9_-]+)\/([A-F])(\/.*)?$/,mn=(e,t)=>`${Q}/o/${e.id}/${t.id}`,Gt=(e,t)=>{let o=d.comments.find(i=>i.id===e)?.options?.find(i=>i.id===t);return o?.live&&o.root?o:null};function mo(e,t,o,i){let r;try{r=decodeURIComponent(i.split("?")[0])}catch{return t.writeHead(400),t.end()}let s=h.join(o.root,r),a=h.join(o.dir,h.relative(X,s)),l=pt.exec(new URL(e.url,"http://x").pathname),p=l?`${Q}/o/${l[1]}/${l[2]}`:"",b=(I,T)=>/html|css/.test(T)&&p?I.replace(/(\s(?:href|src|action|poster)\s*=\s*["']?)\/(?!\/|__loop\/)/gi,`$1${p}/`).replace(/(url\(\s*["']?|@import\s+["'])\/(?!\/|__loop\/)/gi,`$1${p}/`):I,k=(I,T,_=200)=>{let O=/html|css/.test(T||"");t.writeHead(_,{"content-type":T||"application/octet-stream","cache-control":"no-store"}),t.end(O?b(I.toString("utf8"),T):I)};if(ie(o.root,s)&&ie(o.dir,a))try{let I=$.statSync(a).isDirectory()?h.join(a,"index.html"):a;if($.statSync(I).isFile())return k($.readFileSync(I),xn[h.extname(I).toLowerCase()])}catch{}if(e.method!=="GET")return rt(e,t,o.origin,i);fetch(o.origin+i,{signal:AbortSignal.timeout(15e3),redirect:"manual"}).then(async I=>{if(I.status>=300&&I.status<400&&I.headers.get("location")){let T=I.headers.get("location");return T.startsWith(o.origin)&&(T=T.slice(o.origin.length)||"/"),t.writeHead(I.status,{location:T.startsWith("/")?p+T:T}),t.end()}k(Buffer.from(await I.arrayBuffer()),I.headers.get("content-type"),I.status)}).catch(()=>{t.headersSent||rt(e,t,o.origin,i)})}var Be=e=>e.kind==="route"?d.proxy:e.kind==="url"?xt(e.src):null,pn=e=>{if(e.kind==="route")return e.src.replace(/^\/__loop\/o\/[A-Za-z0-9_-]+\/[A-F](?=\/)/,"");let t=new URL(e.src);return t.pathname+t.search},tt=new Map;async function po(e){let t=Be(e);if(!t)return null;if(tt.has(t))return tt.get(t);try{let o=new URL(pn(e),t),i=decodeURIComponent(o.pathname),r=i.endsWith("/")?i+"index.html":i,s=await fetch(t+o.pathname,{signal:AbortSignal.timeout(3e3)});if(!s.ok)return null;let a=Buffer.from(await s.arrayBuffer()),l=$.readdirSync(X,{withFileTypes:!0}).filter(p=>p.isDirectory()&&!p.name.startsWith(".")&&p.name!=="node_modules").map(p=>h.join(X,p.name));for(let p of[X,...l]){let b=h.join(p,r);try{if(ie(p,b)&&$.statSync(b).isFile()&&$.readFileSync(b).equals(a))return tt.set(t,p),p}catch{}}}catch{}return null}function gn(e,t){let o=null;try{Ie.get(t.id)?.close(),Ie.set(t.id,$.watch(e.dir,{recursive:!0},()=>{Date.now()-(e.madeAt||0)<1200||(t.building&&!e.touched&&(e.touched=!0,t.building="filling",S(),v.has(t.optionOf.from)&&Y(t.optionOf.from,`Making option ${e.id} \xB7 ${e.name}`)),clearTimeout(o),o=setTimeout(()=>J({type:"frame-changed",frameId:t.id,live:!0}),450))}))}catch{}}async function gt(e,t,o,i){let r=Be(t),s=await po(t);if(!r||!s)return null;e.options||=[];let a=!i;if(a){if(e.options.length>=6)throw new Error("up to 6 options");let k=String.fromCharCode(65+e.options.length);i={id:k,name:o||`Option ${k}`,dir:h.join(A,"options",e.id,k),...o?{}:{placeholder:!0}},e.options.push(i),await M.rm(i.dir,{recursive:!0,force:!0}),await M.mkdir(i.dir,{recursive:!0})}Object.assign(i,{live:!0,origin:r,root:s});let l=e.options.filter(k=>k.frameId).length,p=Ve(e,t,Math.max(l+1,d.sections.find(k=>k.id===e.optionSection)?.slots||0)),b=W({route:mn(e,i)+pn(t),dup:!0,name:re(i.placeholder?i.name:`${i.id} \xB7 ${i.name}`),w:t.w,h:t.h,x:p.x+G.PAD+l*(t.w+G.GAP),y:t.y});return b.optionOf={cid:e.id,oid:i.id,from:t.id,live:!0},a?b.building=!0:i.touched=!0,i.frameId=b.id,i.madeAt=Date.now(),gn(i,b),S(),i}async function go(){for(let e of d.comments){for(let t of e.options||[]){let o=t.live&&t.frameId&&d.frames.find(i=>i.id===t.frameId);if(o){if(o.kind==="url")try{let i=new URL(o.src);Object.assign(o,{kind:"route",src:mn(e,t)+i.pathname+i.search,entry:""}),delete t.port,S()}catch{}gn(t,o)}}e.status==="done"&&!e.optionChosen&&!e.optionsCleared&&!e.optionApplied&&e.options?.some(t=>t.files?.length&&!t.frameId)&&await hn(e).catch(()=>{})}}function Ut(e){let t=new Set((e.options||[]).map(o=>o.frameId).filter(Boolean));for(let o of t){let i=v.get(o);i&&(i.canceled=!0,i.child?.kill("SIGTERM"),v.delete(o)),_e(o)}d.frames=d.frames.filter(o=>!t.has(o.id)),d.comments=d.comments.filter(o=>!t.has(o.frameId)),d.sections=d.sections.filter(o=>o.id!==e.optionSection);for(let o of e.options||[])delete o.frameId;delete e.optionSection}function oe(e,t,o){if(e.auto==="allow"&&t==="done"&&!v.get(e.frameId)?.canceled&&/^\W*nothing more to do\W*$/i.test(String(o||"").trim())){v.delete(e.frameId),d.comments=d.comments.filter(s=>s!==e),S(),H?.liveChanged(v),setTimeout(()=>se(e.frameId),50);return}Ht(e);for(let s of d.comments)s!==e&&(s.id===e.parentId||s.frameId===e.frameId)&&s.options?.some(a=>a.frameId&&d.frames.find(l=>l.id===a.frameId)?.building)&&Ht(s);if(e.options?.length)for(let s of e.options)s.files=(()=>{try{let a=new Set;return Je(s.dir,s.dir,a),[...a]}catch{return[]}})();e.options?.length&&(e.options=e.options.filter(s=>s.files.length)),e.options?.some(s=>!s.frameId)&&t==="done"&&setTimeout(async()=>{await hn(e).catch(()=>!1)||un(e)},50);let i=v.get(e.frameId);v.delete(e.frameId),e.status=i?.steered?"interrupted":i?.canceled?"canceled":t,e.reply=i?.steered?"Interrupted to take your next message.":i?.canceled?"Stopped.":o,e.finishedAt=Date.now();for(let s of e.batch||[]){let a=d.comments.find(l=>l.id===s);if(a){if(delete a.inBatch,e.status==="interrupted"){a.status="queued";continue}Object.assign(a,{status:e.status,reply:(e.status==="done",e.reply),finishedAt:e.finishedAt,startedAt:e.startedAt,together:e.id})}}ue.set(e.frameId,(ue.get(e.frameId)||0)+1),setTimeout(Pn,300),Ue.add(e.frameId),S(),J({type:"frame-changed",frameId:e.frameId,done:!0,commentId:e.id,status:e.status});let r=d.frames.find(s=>s.id===e.frameId);r&&H?.frameChanged(r),H?.liveChanged(v),setTimeout(()=>se(e.frameId),50)}function Te(){let e=d.agent.runner;return e&&pe[e]?.bin?e:De?"claude":$t?"codex":"claude"}var q=new Map,wo=3,yo=10*6e4;function $e(e){clearTimeout(e.idle),e.dead=!0;try{e.child.kill("SIGTERM")}catch{}q.get(e.frameId)===e&&q.delete(e.frameId)}function wn(e){clearTimeout(e.idle),e.idle=setTimeout(()=>{e.turn||$e(e)},yo)}function wt(e,t=!1){let o=q.get(e.id),i=Ee("claude"),r=`${i.model}|${i.effort}`;if(o&&!o.dead){if(o.turn||o.cfgKey===r)return o;!o.turns&&(!o.resuming||o.forked)&&delete e.sessionId,$e(o)}let s=["-p","--input-format","stream-json","--output-format","stream-json","--verbose","--include-partial-messages","--permission-mode",So(),"--allowedTools",[`Read,Edit,MultiEdit,Write,Glob,Grep,TodoWrite,WebFetch,WebSearch,Bash(node ${Z} new:*),Bash(node ${Z} add-route:*),Bash(node ${Z} option:*),Bash(node ${Z} shot:*)`,...bn,...ko()].join(","),"--add-dir",A,...$o().flatMap(_=>["--add-dir",_])],a=!!(e.sessionId&&!t),l=!a&&!t?kn():null;a?s.push("--resume",e.sessionId):l&&s.push("--resume",l.session,"--fork-session"),l?e.sessionCwd=l.cwd:a||delete e.sessionCwd,i.model&&s.push("--model",i.model),i.effort&&s.push("--effort",i.effort),e.kind!=="canvas"&&s.push("--append-system-prompt",dn(e));let p=e.sessionCwd&&$.existsSync(e.sessionCwd)?e.sessionCwd:e.kind==="folder"?e.src:X,b=Oe(De,s,{cwd:p,env:process.env,stdio:["pipe","pipe","pipe"]}),k={frameId:e.id,child:b,turn:null,turns:0,resuming:a||!!l,forked:l?.session||null,idle:null,dead:!1,stderr:"",cfgKey:r};q.set(e.id,k),b.stdin.on("error",()=>{});let I="";b.stdout.on("data",_=>{I+=_;let O;for(;(O=I.indexOf(`
27
- `))>=0;){let F=I.slice(0,O);if(I=I.slice(O+1),!F.trim())continue;let D;try{D=JSON.parse(F)}catch{continue}process.env.LOOP_DEBUG&&console.log(Date.now()%1e6,D.type,D.subtype||D.event?.type||"",D.event?.content_block?.type||D.event?.delta?.type||"");try{To(k,e,D)}catch(C){console.error(C)}}}),b.stderr.on("data",_=>{k.stderr=(k.stderr+_).slice(-2e3)}),b.on("error",_=>{k.stderr+=_.message}),b.on("close",_=>{k.dead=!0,clearTimeout(k.idle),q.get(e.id)===k&&q.delete(e.id);let O=k.turn;k.turn=null;let F=k.resuming&&!k.turns;if(F&&k.forked&&k.turn&&(yn=k.forked),!O){F&&(e.sessionId=null);return}let D=O.c,C=v.get(e.id);if(C?.commentId===D.id){if(!C.canceled&&F)return e.sessionId=null,v.delete(e.id),Sn(D,!0);C.canceled&&!C.steered&&!k.turns&&!k.resuming&&(e.sessionId=null),oe(D,"error",String(O.lastText&&C.canceled?O.lastText:k.stderr.trim()||`Agent exited with code ${_}`).slice(-400))}}),wn(k);let T=[...q.values()].filter(_=>!_.turn&&_!==k);for(;q.size>wo&&T.length;)$e(T.shift());return k}var ko=()=>(d.agent.allow||[]).filter(e=>!e.startsWith("dir:")),yn=null,bo=6e6;function kn(){let e=d.agent.chat;if(d.agent.mode!=="inbox"||e?.runner!=="claude"||!e.session||!e.cwd||!e.transcript||yn===e.session)return null;try{return!e.transcript.startsWith(h.join(ae.homedir(),".claude","projects")+h.sep)||h.basename(e.transcript)!==`${e.session}.jsonl`||!$.statSync(e.cwd).isDirectory()||$.statSync(e.transcript).size>bo?null:{session:e.session,cwd:e.cwd}}catch{return null}}var So=()=>{let e=d.agent.chat?.runner==="claude"?d.agent.chat.permission:null;return e==="auto"||e==="bypassPermissions"?e:"acceptEdits"},bn=["ls","cat","head","tail","wc","pwd","echo","date","grep","rg","diff","file","stat","du","which","sort","uniq","cut","tr","basename","dirname","realpath","mkdir","cp","touch"].map(e=>`Bash(${e}:*)`),$o=()=>(d.agent.allow||[]).filter(e=>e.startsWith("dir:")).map(e=>e.slice(4)),xo=/^(cd|tail|head|grep|sort|uniq|wc|cat|echo|true|sleep|tee|xargs)$/;function vo(e){let t=String(e.tool_name||""),o=e.tool_input||{};if(t==="Bash"){let r=String(o.command||"").split(/&&|\|\||;|\|/).map(p=>p.trim()).filter(Boolean),s=r.find(p=>!xo.test(p.split(/\s+/)[0]))||r[0];if(!s)return null;let a=[];for(let p of s.split(/\s+/)){if(a.length===3||/^\d*[<>]|[<>&|$`'"*(){};]/.test(p))break;a.push(p)}if(!a.length)return null;let l=a.join(" ");return{tool:t,label:l,rule:`Bash(${l}:*)`,cmd:String(o.command).slice(0,300)}}let i=o.file_path||o.notebook_path||o.path;if(/^(Read|Edit|MultiEdit|Write|NotebookEdit|Glob|Grep)$/.test(t)&&i){let r=h.resolve(String(i)),s=/^(Glob|Grep)$/.test(t)?r:h.dirname(r);return{tool:t,label:`${/^(Read|Glob|Grep)$/.test(t)?"Read":"Edit"} files in ${s.replace(ae.homedir(),"~")}`,rule:`dir:${s}`,cmd:String(i)}}return t?{tool:t,label:t.replace(/^mcp__/,"").replace(/__/g," \xB7 "),rule:t}:null}function Io(e){let t=[],o=new Set([...d.agent.allow||[],...bn]);for(let i of e||[]){let r=vo(i);r&&!o.has(r.rule)&&!t.some(s=>s.rule===r.rule)&&t.push(r)}return t}function To(e,t,o){o.session_id&&(t.sessionId=o.session_id);let i=e.turn;if(!i)return;let r=i.c,s=o.type==="system"&&o.model||o.type==="assistant"&&o.message?.model;if(s&&r.model!==s&&(r.model=s,d.agent.lastModel=s,S()),r.t&&r.t.first==null&&o.type==="stream_event"&&(r.t.first=Date.now()-r.createdAt),r.t&&r.t.tool==null&&o.type==="stream_event"&&o.event?.content_block?.type==="tool_use"&&(r.t.tool=Date.now()-r.createdAt),o.type==="stream_event"){let a=o.event||{};if(a.type==="message_start"&&v.get(t.id)?.step==="Reading your comment\u2026"&&Y(t.id,"Thinking\u2026"),a.type==="content_block_start"){let l=a.content_block||{};l.type==="thinking"?Y(t.id,"Thinking\u2026"):l.type==="tool_use"?(i.partial="",Y(t.id,Rt(l.name,{}))):l.type==="text"&&(i.partial="")}else a.type==="content_block_delta"&&a.delta?.type==="text_delta"&&(i.partial+=a.delta.text,i.flush||(i.flush=setTimeout(()=>{i.flush=null,e.turn===i&&Y(t.id,"Writing\u2026",i.partial.slice(-4e3))},90)))}else if(o.type==="assistant"){for(let a of o.message?.content||[])if(a.type==="tool_use")Y(t.id,Rt(a.name,a.input));else if(a.type==="text"&&a.text.trim()){i.lastText=a.text.trim();let l=r.steps||=[];l.push({t:Date.now(),s:i.lastText.slice(0,600),say:!0}),l.length>60&&l.splice(0,l.length-60),i.partial="",S()}}else if(o.type==="result"){clearTimeout(i.flush),e.turn=null,e.turns++,wn(e);let a=o.result??i.lastText,l=r.steps?.[r.steps.length-1];l?.say&&String(a||"").trim().startsWith(l.s.slice(0,80))&&r.steps.pop();let p=!!o.is_error||(o.permission_denials||[]).some(k=>/^(Edit|MultiEdit|Write)$/.test(k.tool_name)),b=Io(o.permission_denials);if(b.length?r.blocked=b:delete r.blocked,e.restart&&$e(e),v.get(t.id)?.commentId!==r.id)return;a!=null&&!p?oe(r,"done",String(a).trim().slice(0,4e3)||"Done."):oe(r,"error",String(a||e.stderr.trim()||"Something went wrong.").slice(-400))}}function Oo(e){if(!De||Te()!=="claude"||v.has(e.id)||d.agent.mode==="inbox"&&Ye())return!1;try{return wt(e),!0}catch{return!1}}async function Sn(e,t=!1){let o=R(e.frameId);if(!o)return;if(Te()==="codex")return $n(e,o,t);if(!De)return v.set(o.id,{commentId:e.id,step:"",startedAt:Date.now()}),oe(e,"error",'No agent found to run this. Install Claude Code or Codex, or switch to "Your agent session" in the top-right menu.');let i={commentId:e.id,step:"Reading your comment\u2026",startedAt:e.startedAt&&t?e.startedAt:Date.now()};v.set(o.id,i),e.startedAt=i.startedAt,e.status="working",S();let r,s=!!q.get(o.id)&&!q.get(o.id).dead;try{r=wt(o,t)}catch(p){return oe(e,"error",`Couldn't start the agent: ${p.message}`)}r.warmed=s,i.child=r.child;let a=!t&&!e.snapshot?await vt(o,e.id):e.snapshot;if(e.snapshot=a,v.get(o.id)!==i)return;if(i.canceled)return oe(e,"canceled","Stopped.");if(r.dead){try{r=wt(o,!0)}catch(p){return oe(e,"error",`Couldn't start the agent: ${p.message}`)}i.child=r.child}let l=e.kind==="create"||o.kind==="canvas"?It(o,e):an(o,e,Tt(o,e).message);r.turn={c:e,lastText:"",partial:"",flush:null},clearTimeout(r.idle),e.t={sent:Date.now()-e.createdAt,warm:!!r.warmed},r.child.stdin.write(JSON.stringify({type:"user",message:{role:"user",content:[{type:"text",text:l}]}})+`
28
- `)}async function $n(e,t,o=!1){v.set(t.id,{commentId:e.id,step:"Starting Codex\u2026",startedAt:Date.now()}),e.startedAt=Date.now(),e.status="working",S(),!o&&!e.snapshot&&(e.snapshot=await vt(t,e.id));let i=an(t,e,It(t,e)),r=t.kind==="folder"?t.src:X,{model:s,effort:a}=Ee("codex"),l=["--json","-c","sandbox_workspace_write.network_access=true",...s?["-m",s]:[],...a?["-c",`model_reasoning_effort="${a}"`]:[]],p=!!(t.codexSession&&!o&&e.kind!=="create"),b=p?["exec","resume",...l,"--skip-git-repo-check","-c",'sandbox_mode="workspace-write"',"-c",`sandbox_workspace_write.writable_roots=${JSON.stringify([r,A])}`,t.codexSession,"-"]:["exec",...l,"-s","workspace-write","--skip-git-repo-check","-C",r,"--add-dir",A,"-"];s&&(e.model=s,d.agent.lastModel=s,S());let k;try{k=Oe($t,b,{cwd:r,env:process.env,stdio:["pipe","pipe","pipe"]})}catch(D){return oe(e,"error",`Couldn't start Codex: ${D.message}`)}v.get(t.id).child=k,k.stdin.on("error",()=>{}),k.stdin.end(i);let I="",T="",_=null,O=!1,F="";k.stdout.on("data",D=>{I+=D;let C;for(;(C=I.indexOf(`
29
- `))>=0;){let z=I.slice(0,C);I=I.slice(C+1);let U;try{U=JSON.parse(z)}catch{continue}let te=U.item||{};U.type==="thread.started"&&U.thread_id&&e.kind!=="create"?t.codexSession=U.thread_id:te.type==="command_execution"&&U.type==="item.started"?Y(t.id,/\bcat |sed -n|rg |ls\b|grep /.test(te.command||"")?"Reading the code":"Running a command"):te.type==="file_change"?Y(t.id,`Editing ${h.basename(te.changes?.[0]?.path||"")}`.trim()):te.type==="agent_message"&&te.text?.trim()?(T=te.text.trim(),Y(t.id,"Thinking\u2026")):te.type==="reasoning"?Y(t.id,"Thinking\u2026"):U.type==="turn.completed"?O=!0:(U.type==="turn.failed"||U.type==="error")&&(_=U.error?.message||U.message||"Codex failed")}}),k.stderr.on("data",D=>{F=(F+D).slice(-2e3)}),k.on("error",D=>{F+=D.message}),k.on("close",D=>{if(!v.get(t.id)?.canceled&&!O&&p)return t.codexSession=null,v.delete(t.id),$n(e,t,!0);O&&!_?oe(e,"done",T.slice(0,4e3)||"Done."):oe(e,"error",String(_||F.trim()||`Codex exited with code ${D}`).slice(-400))})}function jo(){for(let e of Object.values(ae.networkInterfaces()))for(let t of e||[])if(t.family==="IPv4"&&!t.internal)return t.address;return null}var V=0;function yt(){let e=jo();return{lan:e?`http://${e}:${V}${Q}/?t=${d.token}`:null,public:de?`${de}${Q}/?t=${d.token}`:null,tunnelAvailable:!!ke("cloudflared")}}function Ao(){return new Promise((e,t)=>{if(de)return e(de);let o=ke("cloudflared");if(!o)return t(new Error("Public links need cloudflared. Install it with: brew install cloudflared"));me=Oe(o,["tunnel","--no-autoupdate","--url",`http://localhost:${V}`],{stdio:["ignore","pipe","pipe"]});let i=r=>{let s=String(r).match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/);s&&!de&&(de=s[0],S(),e(de))};me.stdout.on("data",i),me.stderr.on("data",i),me.on("close",()=>{de=null,me=null,S()}),setTimeout(()=>t(new Error("The tunnel took too long to start")),2e4)})}var xn={".html":"text/html; charset=utf-8",".js":"text/javascript; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".css":"text/css; charset=utf-8",".json":"application/json",".svg":"image/svg+xml",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".woff2":"font/woff2",".woff":"font/woff",".ttf":"font/ttf",".ico":"image/x-icon",".mp4":"video/mp4",".txt":"text/plain; charset=utf-8"};function vn(e,t){let o=e.socket.remoteAddress||"",i=o==="127.0.0.1"||o==="::1"||o==="::ffff:127.0.0.1",r=e.headers["x-forwarded-for"]||e.headers["cf-connecting-ip"];if(i&&!r)return/^(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/i.test(String(e.headers.host||""))?"owner":null;let s=/(?:^|;\s*)loop_t=([^;]+)/.exec(e.headers.cookie||"")?.[1];return t.searchParams.get("t")===d.token||s===d.token?"guest":null}function _o(e){let t=e.headers;if(t.origin){let o=null;try{o=new URL(t.origin).host}catch{}if(o!==String(t.host||""))return!0}return t["sec-fetch-site"]==="cross-site"&&t["sec-fetch-mode"]!=="navigate"}async function L(e){let t=[],o=0;for await(let r of e){if(o+=r.length,o>15e6)throw new Error("too large");t.push(r)}let i=Buffer.concat(t).toString("utf8");return i?JSON.parse(i):{}}function u(e,t,o){e.writeHead(t,{"content-type":"application/json","cache-control":"no-store"}),e.end(JSON.stringify(o))}async function ce(e,t,o={}){try{if((await M.stat(t)).isDirectory())return ce(e,h.join(t,"index.html"),o);e.writeHead(200,{"content-type":xn[h.extname(t).toLowerCase()]||"application/octet-stream","cache-control":"no-store",...o}),$.createReadStream(t).pipe(e)}catch{e.writeHead(404),e.end("Not found")}}var ie=(e,t)=>t===e||t.startsWith(e+h.sep),ze=Yt.createServer(async(e,t)=>{let o=new URL(e.url,"http://x"),i;try{i=decodeURIComponent(o.pathname)}catch{return t.writeHead(400),t.end()}let r=vn(e,o);if(!r)return t.writeHead(403,{"content-type":"text/plain"}),t.end("This Loop canvas is private. Ask the host for a share link.");if((i===Q||i.startsWith(Q+"/"))&&_o(e))return t.writeHead(403,{"content-type":"text/plain"}),t.end("Refused: this request came from another website.");let s=r==="owner";if(i!==Q&&!i.startsWith(Q+"/")){if(e.method==="GET"||e.method==="HEAD"){let a=null;try{a=pt.exec(new URL(e.headers.referer).pathname)}catch{}if(a&&Gt(a[1],a[2]))return t.writeHead(307,{location:`${Q}/o/${a[1]}/${a[2]}${e.url}`,"cache-control":"no-store",vary:"Referer"}),t.end()}return d.proxy?rt(e,t,d.proxy):e.headers["sec-fetch-dest"]==="iframe"?Dt(t,"No app is connected to this canvas yet","Run <code>loop scan</code> in the project, or give Loop the running app with <code>loop scan --url http://localhost:&lt;port&gt;</code>."):i==="/"?(t.writeHead(302,{location:`${Q}/${o.search}`}),t.end()):(t.writeHead(404),t.end("Not found"))}if(i=i.slice(Q.length)||"/",i==="/"&&e.headers["sec-fetch-dest"]==="iframe")return Dt(t,"A frame can\u2019t show the canvas itself","This frame points at Loop\u2019s own address. Point it at your app instead.");try{if(i==="/"||i==="/index.html"){let n=o.searchParams.get("t")?{"set-cookie":`loop_t=${d.token}; Path=/; HttpOnly; SameSite=Lax`}:{};return ce(t,h.join(Qe,"index.html"),n)}if(i.startsWith("/app/")){let n=h.join(Qe,h.normalize(i.slice(5)));return ie(Qe,n)?ce(t,n):(t.writeHead(400),t.end())}{let n=pt.exec(o.pathname),c=n&&Gt(n[1],n[2]);if(n)return c?mo(e,t,c,(n[3]||"/")+o.search):(t.writeHead(404),t.end("This option is gone."))}let a=/^\/optview\/([A-Za-z0-9_-]+)\/([A-F])\/(.*)$/.exec(i);if(a){let n=d.comments.find(x=>x.id===a[1]),c=n&&R(n.frameId),f=n?.options?.find(x=>x.id===a[2]);if(!c||!f||c.kind!=="folder")return t.writeHead(404),t.end();let m=h.normalize(decodeURIComponent(a[3]||c.entry)),y=h.join(f.dir,m),g=h.join(c.src,m);return!ie(f.dir,y)||!ie(c.src,g)?(t.writeHead(400),t.end()):ce(t,$.existsSync(y)?y:g)}let l=/^\/hist\/([A-Za-z0-9_-]+)\/(.*)$/.exec(i);if(l){let n=h.join(A,"history",l[1]),c=h.join(n,h.normalize(l[2]||"index.html"));return ie(n,c)?ce(t,c):(t.writeHead(400),t.end())}let p=/^\/poster\/([A-Za-z0-9_-]+)$/.exec(i);if(p){let n=d.frames.find(c=>c.id===p[1]);if(!n)return t.writeHead(404),t.end();try{return ce(t,await ao(n),{"cache-control":"private, max-age=3600"})}catch{return t.writeHead(404),t.end()}}let b=/^\/shots\/([\w.-]+\.png)$/.exec(i);if(b)return ce(t,h.join(A,"shots",b[1]));let k=/^\/attachments\/([A-Za-z0-9_-]+)\/([^/]+)$/.exec(i);if(k){let n=h.join(A,"attachments",k[1]),c=h.join(n,decodeURIComponent(k[2]));return ie(n,c)?ce(t,c):(t.writeHead(400),t.end())}let I=/^\/f\/([^/]+)\/(.*)$/.exec(i);if(I){let n=d.frames.find(f=>f.id===I[1]);if(!n||n.kind!=="folder"&&n.kind!=="frozen")return t.writeHead(404),t.end();let c=h.join(n.src,h.normalize(I[2]||n.entry));return ie(n.src,c)?ce(t,c):(t.writeHead(400),t.end())}if(i==="/api/ping")return u(t,200,{ok:!0,name:d.name,pid:process.pid});if(i==="/api/state")return u(t,200,tn(r));if(i==="/api/events"){t.writeHead(200,{"content-type":"text/event-stream","cache-control":"no-store",connection:"keep-alive"});let n={res:t,role:r};He.add(n),st(t,on(n));let c=setInterval(()=>t.write(`: ka
27
+ ${e.reference}`:e.message}var be="__canvas";function F(e){return e!==be?d.frames.find(t=>t.id===e):(d.canvasFrame||={id:be,name:"Canvas",kind:"canvas",src:Z,entry:"",w:0,h:0},d.canvasFrame)}var Be=0,Ue={at:0,file:null,val:null};function yn(){if(d.agent?.chat?.runner==="codex")return null;let e=jn();if(!e)return null;if(Ue.file===e&&Date.now()-Ue.at<5e3)return Ue.val;let t=null;try{let o=k.statSync(e),i=Math.min(o.size,4e5),r=k.openSync(e,"r"),s=Buffer.alloc(i);k.readSync(r,s,0,i,o.size-i),k.closeSync(r);let a=[...s.toString("utf8").matchAll(/"usage":\{"input_tokens":(\d+),"cache_creation_input_tokens":(\d+),"cache_read_input_tokens":(\d+),"output_tokens":(\d+)/g)],l=a[a.length-1];if(l){let m=+l[1]+ +l[2]+ +l[3]+ +l[4],b=Math.max(...a.map(w=>+w[1]+ +w[2]+ +w[3]));t={used:m,window:b>2e5||m>2e5?1e6:2e5}}}catch{}return Ue={at:Date.now(),file:e,val:t},t}var Pe=0,Fe=()=>Pe>0||Date.now()-Be<6e3;function yo(e){let t=v.get(e.frameId),o=t&&d.comments.find(s=>s.id===t.commentId),i=e.parentId||e.id;if(e.status!=="queued"||!o||o.id!==i&&o.parentId!==i)return!1;e.steeredFrom=o.id,d.comments.splice(d.comments.indexOf(e),1);let r=d.comments.findIndex(s=>s.frameId===e.frameId&&s.status==="queued");return d.comments.splice(r<0?d.comments.length:r,0,e),t.steered=!0,t.canceled=!0,t.child?t.child.kill("SIGTERM"):oe(o,"canceled","Stopped."),$(),!0}function kn(e){return e.kind==="folder"?e.src:Z}async function Jt(e){let t=new Set;try{qe(e,e,t)}catch{}return[...t]}async function at(e,t){let o=F(e.frameId);if(!o)throw new Error("no frame");if(e.optionChosen)throw new Error("an option was already chosen");let i=kn(o),r=h.join(_,"options",e.id,"_original"),s=new Set;for(let m of e.options||[])for(let b of await Jt(m.dir))s.add(b);if(!k.existsSync(r)){await M.mkdir(r,{recursive:!0});let m=[];for(let b of s){let w=h.join(i,b);ie(i,w)&&(k.existsSync(w)?(await M.mkdir(h.dirname(h.join(r,b)),{recursive:!0}),await M.copyFile(w,h.join(r,b))):m.push(b))}await M.writeFile(h.join(r,".missing.json"),JSON.stringify(m))}let a=JSON.parse(await M.readFile(h.join(r,".missing.json"),"utf8").catch(()=>"[]"));for(let m of s){let b=h.join(i,m);ie(i,b)&&(a.includes(m)?await M.rm(b,{force:!0}):k.existsSync(h.join(r,m))&&await M.copyFile(h.join(r,m),b))}let l=t&&(e.options||[]).find(m=>m.id===t);if(t&&!l)throw new Error("no such option");if(l)for(let m of await Jt(l.dir)){let b=h.join(i,m);ie(i,b)&&(await M.mkdir(h.dirname(b),{recursive:!0}),await M.copyFile(h.join(l.dir,m),b))}e.optionApplied=l?l.id:null,$(),J({type:"frame-changed",frameId:o.id})}var tt=process.env.LOOP_CHROME||["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome","/Applications/Chromium.app/Contents/MacOS/Chromium","/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge","/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",h.join(ae.homedir(),"Applications/Google Chrome.app/Contents/MacOS/Google Chrome")].find(e=>k.existsSync(e))||ke("google-chrome")||ke("chromium")||ke("chromium-browser");function ko(e){return e.kind==="folder"||e.kind==="frozen"?`http://127.0.0.1:${K}/__loop/f/${e.id}/${encodeURIComponent(e.entry||"index.html")}`:e.kind==="route"?`http://127.0.0.1:${K}${e.src}`:e.src}var B=null;function kt(){let e=B;if(B=null,!!e){clearTimeout(e.idle);try{e.child.kill("SIGKILL")}catch{}k.rm(e.profile,{recursive:!0,force:!0},()=>{})}}async function bn(){if(B)return clearTimeout((await B).idle),B;B=(async()=>{let t=k.mkdtempSync(h.join(ae.tmpdir(),"loop-shot-")),o=Te(tt,["--headless=new","--use-angle=swiftshader","--enable-unsafe-swiftshader","--ignore-gpu-blocklist","--hide-scrollbars","--no-first-run","--no-default-browser-check","--mute-audio",`--user-data-dir=${t}`,"--remote-debugging-port=0","--window-size=1280,900","about:blank"],{stdio:"ignore"}),i={child:o,profile:t,port:null,idle:null};o.on("exit",()=>{B&&(B===e||B===i)&&(B=null,k.rm(t,{recursive:!0,force:!0},()=>{}))});for(let r=0;r<100&&!i.port;r++)try{i.port=k.readFileSync(h.join(t,"DevToolsActivePort"),"utf8").split(`
28
+ `)[0]}catch{await new Promise(s=>setTimeout(s,50))}if(!i.port){try{o.kill("SIGKILL")}catch{}throw new Error("Chrome did not start")}return i})();let e=B;try{let t=await e;return B===e&&(B=t),t}catch(t){throw B===e&&(B=null),t}}var _e=new Map,Ze=new Set;async function bo(e){let t=`${e.w}x${e.h}`,o=_e.get(e.id);if(o&&!(o instanceof Promise)&&o.size===t&&!Ze.has(e.id)&&k.existsSync(o.file))return o.file;if(o instanceof Promise)return(await o).file;Ze.delete(e.id);let i=he(e).then(r=>{let s=_e.get(e.id),a={file:r,size:t};return _e.set(e.id,a),a}).catch(r=>{throw _e.delete(e.id),r});return _e.set(e.id,i),(await i).file}var Kt=Promise.resolve();function he(e,t,o){let i=Kt.then(()=>So(e,t,o));return Kt=i.catch(()=>{}),i}async function So(e,t,o){if(!tt)throw new Error("No Chrome, Edge, Brave or Chromium found for screenshots (set LOOP_CHROME to one).");let i=globalThis.WebSocket;if(!i)throw new Error("Screenshots need Node 22 or newer");let r=h.join(_,"shots");k.mkdirSync(r,{recursive:!0});let s=h.join(r,o||`${e.id}-${Date.now()}${N(3)}.png`),a=Math.round(e.w),l=Math.round(e.h),m=await bn();m.busy=(m.busy||0)+1;let b=null,w=null;try{for(let D=0;D<40&&!b;D++)try{b=await(await fetch(`http://127.0.0.1:${m.port}/json/new?about:blank`,{method:"PUT"})).json()}catch{await new Promise(z=>setTimeout(z,50))}if(!b?.webSocketDebuggerUrl)throw new Error("Chrome tab not found");w=new i(b.webSocketDebuggerUrl),await new Promise((D,z)=>{w.onopen=D,w.onerror=()=>z(new Error("DevTools connection failed"))});let I=0,T=new Map,E=null;w.onmessage=D=>{let z=JSON.parse(D.data);z.id&&T.has(z.id)?(T.get(z.id)(z),T.delete(z.id)):z.method==="Page.loadEventFired"&&E?.()};let j=(D,z={})=>new Promise((U,te)=>{let je=++I;T.set(je,U),setTimeout(()=>te(new Error("Screenshot timed out")),3e4),w.send(JSON.stringify({id:je,method:D,params:z}))});await j("Page.enable"),await j("Page.bringToFront"),await j("Emulation.setDeviceMetricsOverride",{width:a,height:l,deviceScaleFactor:2,mobile:!1});let R=new Promise(D=>{E=D,setTimeout(D,12e3)});await j("Page.navigate",{url:t||ko(e)}),await R,await Promise.race([j("Runtime.evaluate",{awaitPromise:!0,expression:"(async () => { try { await document.fonts.ready; } catch {} await Promise.all([...document.images].filter(i => !i.complete).map(i => new Promise(r => { i.onload = i.onerror = r; }))); await new Promise(r => setTimeout(r, 250)); })()"}),new Promise(D=>setTimeout(D,2500))]);let C=await j("Page.captureScreenshot",{format:"png",clip:{x:0,y:0,width:a,height:l,scale:1}});if(!C.result?.data)throw new Error("Screenshot failed");return k.writeFileSync(s,Buffer.from(C.result.data,"base64")),s}catch(I){throw b||kt(),I}finally{try{w?.close()}catch{}b?.id&&fetch(`http://127.0.0.1:${m.port}/json/close/${b.id}`).catch(()=>{}),m.busy--,clearTimeout(m.idle),m.busy||(m.idle=setTimeout(()=>{B===m&&!m.busy&&kt()},4*6e4))}}function ce(e){if(v.has(e))return;let t=i=>i.frameId===e&&i.status==="queued"&&!i.inBatch&&(d.agent.mode!=="inbox"||i.background),o=d.comments.find(t);if(o){if(delete o.batch,o.kind!=="create"&&e!==be&&!F(e)?.optionOf){let i=d.comments.filter(r=>r!==o&&t(r)&&r.kind!=="create"&&!bt(r.text)).slice(0,5);if(i.length&&!bt(o.text)){o.batch=i.map(r=>r.id);for(let r of i)r.inBatch=o.id,r.status="working"}}Pn(o)}}function Dt(){for(let e of d.frames)ce(e.id);ce(be)}var $o=new Set(["Reading the code","Editing","Writing","Working\u2026","Setting up an option"]);function V(e,t,o){let i=v.get(e);if(!i)return;i.step=t;let r=o==null&&t&&d.comments.find(s=>s.id===i.commentId);if(r&&!/^(Writing…|Reading your comment…|Your chat picked this up|Working…)$/.test(t)){let s=r.steps||=[],a=s[s.length-1];!$o.has(t)&&(!a||a.s!==t)&&(s.push({t:Date.now(),s:t}),s.length>60&&s.splice(0,s.length-60))}J({type:"activity",frameId:e,commentId:i.commentId,step:t,...o!=null?{partial:o}:{}}),H?.liveChanged(v)}async function Sn(e){let t=F(e.frameId);if(!(!t||t.kind!=="folder"||!e.options?.length||!K)){if(!e.origThumb&&!e.optionApplied)try{e.origThumb=`/__loop/shots/${h.basename(await he(t))}`,$()}catch{}for(let o of e.options)try{let i=await he(t,`http://127.0.0.1:${K}/__loop/optview/${e.id}/${o.id}/${encodeURIComponent(t.entry||"index.html")}`);o.thumb=`/__loop/shots/${h.basename(i)}`,$()}catch{}}}var G={GAP:60,PAD:48,TOP:72},Je=(e,t)=>t*e.w+(t-1)*G.GAP+G.PAD*2;function bt(e){let t=String(e||""),o=/\b(\d|two|three|four|five|six|a couple of|couple of|a few|few|some|several|more|other|different)\s+(?:[\w-]+\s+){0,3}?(options?|variations?|variants?|alternatives?|versions|directions|takes|ideas|explorations?)\b/i.exec(t);if(o){let i=o[1].toLowerCase(),r={two:2,three:3,four:4,five:5,six:6,"a couple of":2,"couple of":2,"a few":3,few:3,some:2,several:3,more:2,other:2,different:2}[i]??+i;return Math.max(1,Math.min(4,r))}return/\b(options|variations|variants|alternatives|explorations)\b/i.test(t)&&/\b(give|make|create|show|try|explore|generate|come up|want|need|can (we|you)|let'?s|what about)\b/i.test(t)?2:/\b(another|a new|one more|a different|an alternative|a second)\s+(version|variation|variant|option|take|direction|design|layout)\b|\bduplicate (this|it)\b|\b(make|create) a copy\b/i.test(t)||/\b(a|an|one)\s+(?:[\w-]+\s+){0,2}?(variation|variant|alternative|option)\b/i.test(t)?1:0}function St(e,t,o=new Set){let i=a=>a.y<e.y+e.h&&a.y+a.h>e.y&&a.x+a.w/2>t.x+t.w,r=[...d.frames.filter(a=>a.id!==t.id&&!o.has(a.id)&&i(a)),...d.sections.filter(a=>!o.has(a.id)&&i(a))];if(!r.length)return;let s=e.x+e.w+G.GAP-Math.min(...r.map(a=>a.x));if(s>0)for(let a of r)a.x+=s}function nt(e,t,o){let i=d.sections.find(r=>r.id===e.optionSection);if(i){if(o>(i.slots||0)){let r=new Set([i.id,...(e.options||[]).map(s=>s.frameId)]);St({x:i.x,y:i.y,w:Je(t,o),h:i.h},t,r),i.w=Je(t,o),i.slots=o}}else{let r={x:t.x+t.w+G.GAP+20,y:t.y-G.TOP,w:Je(t,o),h:t.h+G.TOP+G.PAD};St(r,t),i={id:N(6),name:`Options \xB7 ${t.name}`,...r,optionsFor:e.id,slots:o,createdAt:Date.now()},d.sections.push(i),e.optionSection=i.id}return i}function xo(){let e=[...v.entries()].filter(([s])=>s!==be);if(e.length!==1)return null;let[t,o]=e[0],i=F(t),r=d.comments.find(s=>s.id===o.commentId);return i&&r&&i.kind==="folder"&&!i.optionOf?{c:r,f:i}:null}function vo(e,t,o,i){e.options||=[];let r=String.fromCharCode(65+e.options.length),s=e.options.length,a={id:r,name:i||`Option ${r}`};e.options.push(a);let l=nt(e,t,Math.max(s+1,d.sections.find(b=>b.id===e.optionSection)?.slots||0)),m=W({src:o,name:re(i||`Option ${r}`),w:t.w,h:t.h,x:l.x+G.PAD+s*(t.w+G.GAP),y:t.y});return m.optionOf={cid:e.id,oid:r,from:t.id},m.building=!0,a.dir=o,a.frameId=m.id,a.madeAt=Date.now(),a.touched=!0,$(),m}async function Yt(e,t,o){if(e.options||=[],e.options.length>=6)throw new Error("up to 6 options");let i=String.fromCharCode(65+e.options.length),r=e.options.length,s={id:i,name:o||`Option ${i}`,placeholder:!o};e.options.push(s);let a=nt(e,t,Math.max(r+1,d.sections.find(b=>b.id===e.optionSection)?.slots||0)),l=we(`${t.name} option ${i} ${N(3)}`);await fe(t.src,l);let m=W({src:h.join(l,t.entry||"index.html"),name:re(o?`${i} \xB7 ${o}`:`Option ${i}`),w:t.w,h:t.h,x:a.x+G.PAD+r*(t.w+G.GAP),y:t.y});return m.optionOf={cid:e.id,oid:i,from:t.id},m.building=!0,s.dir=l,s.frameId=m.id,s.madeAt=Date.now(),$(),s}function Io(e){if(e.touched)return!0;let t=F(d.comments.find(o=>o.options?.includes(e))?.frameId)?.src;try{let o=new Set;return qe(e.dir,e.dir,o),[...o].some(i=>{let r=t&&h.join(t,i);try{return!r||!k.readFileSync(h.join(e.dir,i)).equals(k.readFileSync(r))}catch{return!0}})}catch{return!1}}function Xt(e){let t=(e.options||[]).filter(a=>a.frameId);if(!t.length)return;let o=F(e.frameId),i=t.filter(a=>!a.placeholder&&Io(a)),r=new Set(t.filter(a=>!i.includes(a)).map(a=>a.frameId));for(let a of r)De(a);d.frames=d.frames.filter(a=>!r.has(a.id)),e.options=e.options.filter(a=>!a.frameId||i.includes(a));let s=d.sections.find(a=>a.id===e.optionSection);i.forEach((a,l)=>{let m=d.frames.find(b=>b.id===a.frameId);m&&(delete m.building,s&&o&&(m.x=s.x+G.PAD+l*(o.w+G.GAP)))}),s&&!i.length?(d.sections=d.sections.filter(a=>a!==s),delete e.optionSection):s&&o&&(s.w=Je(o,i.length),s.slots=i.length)}async function $n(e){let t=F(e.frameId),o=(e.options||[]).filter(s=>s.files?.length&&!s.frameId);if(!t||!o.length)return!1;if(t.kind!=="folder"){if(e.optionApplied)return!1;let s=0;for(let a of o)await xt(e,t,null,a).catch(()=>null)&&s++;return s>0}let i=nt(e,t,o.length),r=0;for(let s of o){let a=we(`${t.name} ${s.id} ${s.name}`);try{await fe(t.src,a),await fe(s.dir,a)}catch{continue}let l=W({src:h.join(a,t.entry||"index.html"),name:re(`${s.id} \xB7 ${s.name}`),w:t.w,h:t.h,x:i.x+G.PAD+r++*(t.w+G.GAP),y:t.y});l.optionOf={cid:e.id,oid:s.id,from:t.id},s.frameId=l.id}return $(),r>0}var $t=/^\/__loop\/o\/([A-Za-z0-9_-]+)\/([A-F])(\/.*)?$/,xn=(e,t)=>`${X}/o/${e.id}/${t.id}`,Vt=(e,t)=>{let o=d.comments.find(i=>i.id===e)?.options?.find(i=>i.id===t);return o?.live&&o.root?o:null};function Oo(e,t,o,i){let r;try{r=decodeURIComponent(i.split("?")[0])}catch{return t.writeHead(400),t.end()}let s=h.join(o.root,r),a=h.join(o.dir,h.relative(Z,s)),l=$t.exec(new URL(e.url,"http://x").pathname),m=l?`${X}/o/${l[1]}/${l[2]}`:"",b=(I,T)=>/html|css/.test(T)&&m?I.replace(/(\s(?:href|src|action|poster)\s*=\s*["']?)\/(?!\/|__loop\/)/gi,`$1${m}/`).replace(/(url\(\s*["']?|@import\s+["'])\/(?!\/|__loop\/)/gi,`$1${m}/`):I,w=(I,T,E=200)=>{let j=/html|css/.test(T||"");t.writeHead(E,{"content-type":T||"application/octet-stream","cache-control":"no-store"}),t.end(j?b(I.toString("utf8"),T):I)};if(ie(o.root,s)&&ie(o.dir,a))try{let I=k.statSync(a).isDirectory()?h.join(a,"index.html"):a;if(k.statSync(I).isFile())return w(k.readFileSync(I),Dn[h.extname(I).toLowerCase()])}catch{}if(e.method!=="GET")return ut(e,t,o.origin,i);fetch(o.origin+i,{signal:AbortSignal.timeout(15e3),redirect:"manual"}).then(async I=>{if(I.status>=300&&I.status<400&&I.headers.get("location")){let T=I.headers.get("location");return T.startsWith(o.origin)&&(T=T.slice(o.origin.length)||"/"),t.writeHead(I.status,{location:T.startsWith("/")?m+T:T}),t.end()}w(Buffer.from(await I.arrayBuffer()),I.headers.get("content-type"),I.status)}).catch(()=>{t.headersSent||ut(e,t,o.origin,i)})}var Qe=e=>e.kind==="route"?d.proxy:e.kind==="url"?At(e.src):null,vn=e=>{if(e.kind==="route")return e.src.replace(/^\/__loop\/o\/[A-Za-z0-9_-]+\/[A-F](?=\/)/,"");let t=new URL(e.src);return t.pathname+t.search},ct=new Map;async function To(e){let t=Qe(e);if(!t)return null;if(ct.has(t))return ct.get(t);try{let o=new URL(vn(e),t),i=decodeURIComponent(o.pathname),r=i.endsWith("/")?i+"index.html":i,s=await fetch(t+o.pathname,{signal:AbortSignal.timeout(3e3)});if(!s.ok)return null;let a=Buffer.from(await s.arrayBuffer()),l=k.readdirSync(Z,{withFileTypes:!0}).filter(m=>m.isDirectory()&&!m.name.startsWith(".")&&m.name!=="node_modules").map(m=>h.join(Z,m.name));for(let m of[Z,...l]){let b=h.join(m,r);try{if(ie(m,b)&&k.statSync(b).isFile()&&k.readFileSync(b).equals(a))return ct.set(t,m),m}catch{}}}catch{}return null}function In(e,t){let o=null;try{ve.get(t.id)?.close(),ve.set(t.id,k.watch(e.dir,{recursive:!0},()=>{Date.now()-(e.madeAt||0)<1200||(t.building&&!e.touched&&(e.touched=!0,t.building="filling",$(),v.has(t.optionOf.from)&&V(t.optionOf.from,`Making option ${e.id} \xB7 ${e.name}`)),clearTimeout(o),o=setTimeout(()=>J({type:"frame-changed",frameId:t.id,live:!0}),450))}))}catch{}}async function xt(e,t,o,i){let r=Qe(t),s=await To(t);if(!r||!s)return null;e.options||=[];let a=!i;if(a){if(e.options.length>=6)throw new Error("up to 6 options");let w=String.fromCharCode(65+e.options.length);i={id:w,name:o||`Option ${w}`,dir:h.join(_,"options",e.id,w),...o?{}:{placeholder:!0}},e.options.push(i),await M.rm(i.dir,{recursive:!0,force:!0}),await M.mkdir(i.dir,{recursive:!0})}Object.assign(i,{live:!0,origin:r,root:s});let l=e.options.filter(w=>w.frameId).length,m=nt(e,t,Math.max(l+1,d.sections.find(w=>w.id===e.optionSection)?.slots||0)),b=W({route:xn(e,i)+vn(t),dup:!0,name:re(i.placeholder?i.name:`${i.id} \xB7 ${i.name}`),w:t.w,h:t.h,x:m.x+G.PAD+l*(t.w+G.GAP),y:t.y});return b.optionOf={cid:e.id,oid:i.id,from:t.id,live:!0},a?b.building=!0:i.touched=!0,i.frameId=b.id,i.madeAt=Date.now(),In(i,b),$(),i}async function jo(){for(let e of d.comments){for(let t of e.options||[]){let o=t.live&&t.frameId&&d.frames.find(i=>i.id===t.frameId);if(o){if(o.kind==="url")try{let i=new URL(o.src);Object.assign(o,{kind:"route",src:xn(e,t)+i.pathname+i.search,entry:""}),delete t.port,$()}catch{}In(t,o)}}e.status==="done"&&!e.optionChosen&&!e.optionsCleared&&!e.optionApplied&&e.options?.some(t=>t.files?.length&&!t.frameId)&&await $n(e).catch(()=>{})}}function Zt(e){let t=new Set((e.options||[]).map(o=>o.frameId).filter(Boolean));for(let o of t){let i=v.get(o);i&&(i.canceled=!0,i.child?.kill("SIGTERM"),v.delete(o)),De(o)}d.frames=d.frames.filter(o=>!t.has(o.id)),d.comments=d.comments.filter(o=>!t.has(o.frameId)),d.sections=d.sections.filter(o=>o.id!==e.optionSection);for(let o of e.options||[])delete o.frameId;delete e.optionSection}function oe(e,t,o){if(e.auto==="allow"&&t==="done"&&!v.get(e.frameId)?.canceled&&/^\W*nothing more to do\W*$/i.test(String(o||"").trim())){v.delete(e.frameId),d.comments=d.comments.filter(s=>s!==e),$(),H?.liveChanged(v),setTimeout(()=>ce(e.frameId),50);return}Xt(e);for(let s of d.comments)s!==e&&(s.id===e.parentId||s.frameId===e.frameId)&&s.options?.some(a=>a.frameId&&d.frames.find(l=>l.id===a.frameId)?.building)&&Xt(s);if(e.options?.length)for(let s of e.options)s.files=(()=>{try{let a=new Set;return qe(s.dir,s.dir,a),[...a]}catch{return[]}})();e.options?.length&&(e.options=e.options.filter(s=>s.files.length)),e.options?.some(s=>!s.frameId)&&t==="done"&&setTimeout(async()=>{await $n(e).catch(()=>!1)||Sn(e)},50);let i=v.get(e.frameId);v.delete(e.frameId),e.status=i?.steered?"interrupted":i?.canceled?"canceled":t,e.reply=i?.steered?"Interrupted to take your next message.":i?.canceled?"Stopped.":o,e.finishedAt=Date.now();for(let s of e.batch||[]){let a=d.comments.find(l=>l.id===s);if(a){if(delete a.inBatch,e.status==="interrupted"){a.status="queued";continue}Object.assign(a,{status:e.status,reply:(e.status==="done",e.reply),finishedAt:e.finishedAt,startedAt:e.startedAt,together:e.id})}}ue.set(e.frameId,(ue.get(e.frameId)||0)+1),setTimeout(zn,300),Ze.add(e.frameId),$(),J({type:"frame-changed",frameId:e.frameId,done:!0,commentId:e.id,status:e.status});let r=d.frames.find(s=>s.id===e.frameId);r&&H?.frameChanged(r),H?.liveChanged(v),setTimeout(()=>ce(e.frameId),50)}function Oe(){let e=d.agent.runner;return e&&pe[e]?.bin?e:Re?"claude":jt?"codex":"claude"}var q=new Map,Ao=3,_o=10*6e4;function Se(e){clearTimeout(e.idle),e.dead=!0;try{e.child.kill("SIGTERM")}catch{}q.get(e.frameId)===e&&q.delete(e.frameId)}function On(e){clearTimeout(e.idle),e.idle=setTimeout(()=>{e.turn||Se(e)},_o)}function vt(e,t=!1){let o=q.get(e.id),i=Me("claude"),r=`${i.model}|${i.effort}`;if(o&&!o.dead){if(o.turn||o.cfgKey===r)return o;!o.turns&&(!o.resuming||o.forked)&&delete e.sessionId,Se(o)}let s=["-p","--input-format","stream-json","--output-format","stream-json","--verbose","--include-partial-messages","--permission-mode",Do(),"--allowedTools",[`Read,Edit,MultiEdit,Write,Glob,Grep,TodoWrite,WebFetch,WebSearch,Bash(node ${Q} new:*),Bash(node ${Q} add-route:*),Bash(node ${Q} option:*),Bash(node ${Q} shot:*)`,..._n,...Po()].join(","),"--add-dir",_,...Co().flatMap(E=>["--add-dir",E])],a=!!(e.sessionId&&!t),l=!a&&!t?An():null;a?s.push("--resume",e.sessionId):l&&s.push("--resume",l.session,"--fork-session"),l?e.sessionCwd=l.cwd:a||delete e.sessionCwd,i.model&&s.push("--model",i.model),i.effort&&s.push("--effort",i.effort),e.kind!=="canvas"&&s.push("--append-system-prompt",wn(e));let m=e.sessionCwd&&k.existsSync(e.sessionCwd)?e.sessionCwd:e.kind==="folder"?e.src:Z,b=Te(Re,s,{cwd:m,env:process.env,stdio:["pipe","pipe","pipe"]}),w={frameId:e.id,child:b,turn:null,turns:0,resuming:a||!!l,forked:l?.session||null,idle:null,dead:!1,stderr:"",cfgKey:r};q.set(e.id,w),b.stdin.on("error",()=>{});let I="";b.stdout.on("data",E=>{I+=E;let j;for(;(j=I.indexOf(`
29
+ `))>=0;){let R=I.slice(0,j);if(I=I.slice(j+1),!R.trim())continue;let C;try{C=JSON.parse(R)}catch{continue}process.env.LOOP_DEBUG&&console.log(Date.now()%1e6,C.type,C.subtype||C.event?.type||"",C.event?.content_block?.type||C.event?.delta?.type||"");try{Ro(w,e,C)}catch(D){console.error(D)}}}),b.stderr.on("data",E=>{w.stderr=(w.stderr+E).slice(-2e3)}),b.on("error",E=>{w.stderr+=E.message}),b.on("close",E=>{w.dead=!0,clearTimeout(w.idle),q.get(e.id)===w&&q.delete(e.id);let j=w.turn;w.turn=null;let R=w.resuming&&!w.turns;if(R&&w.forked&&w.turn&&(Tn=w.forked),!j){R&&(e.sessionId=null);return}let C=j.c,D=v.get(e.id);if(D?.commentId===C.id){if(!D.canceled&&R)return e.sessionId=null,v.delete(e.id),Pn(C,!0);D.canceled&&!D.steered&&!w.turns&&!w.resuming&&(e.sessionId=null),oe(C,"error",String(j.lastText&&D.canceled?j.lastText:w.stderr.trim()||`Agent exited with code ${E}`).slice(-400))}}),On(w);let T=[...q.values()].filter(E=>!E.turn&&E!==w);for(;q.size>Ao&&T.length;)Se(T.shift());return w}var Po=()=>(d.agent.allow||[]).filter(e=>!e.startsWith("dir:")),Tn=null,Eo=6e6,Qt=0;function jn(){let e=d.agent?.chat;if(!e?.session)return e?.transcript||null;if(e.transcript&&k.existsSync(e.transcript))return e.transcript;if(Date.now()-Qt<1e4)return null;Qt=Date.now();try{let t=h.join(ae.homedir(),".claude","projects");for(let o of k.readdirSync(t)){let i=h.join(t,o,`${e.session}.jsonl`);if(k.existsSync(i)){e.transcript=i;try{let r=k.statSync(i),s=Math.min(r.size,2e5),a=k.openSync(i,"r"),l=Buffer.alloc(s);k.readSync(a,l,0,s,r.size-s),k.closeSync(a);let m=[...l.toString("utf8").matchAll(/"cwd":"((?:[^"\\]|\\.)*)"/g)];m.length&&(e.cwd=JSON.parse(`"${m[m.length-1][1]}"`))}catch{}return $(),i}}}catch{}return null}function An(){let e=d.agent.chat;if(jn(),d.agent.mode!=="inbox"||e?.runner!=="claude"||!e.session||!e.cwd||!e.transcript||Tn===e.session)return null;try{return!e.transcript.startsWith(h.join(ae.homedir(),".claude","projects")+h.sep)||h.basename(e.transcript)!==`${e.session}.jsonl`||!k.statSync(e.cwd).isDirectory()||k.statSync(e.transcript).size>Eo?null:{session:e.session,cwd:e.cwd}}catch{return null}}var Do=()=>{let e=d.agent.chat?.runner==="claude"?d.agent.chat.permission:null;return e==="auto"||e==="bypassPermissions"?e:"acceptEdits"},_n=["ls","cat","head","tail","wc","pwd","echo","date","grep","rg","diff","file","stat","du","which","sort","uniq","cut","tr","basename","dirname","realpath","mkdir","cp","touch"].map(e=>`Bash(${e}:*)`),Co=()=>(d.agent.allow||[]).filter(e=>e.startsWith("dir:")).map(e=>e.slice(4)),Mo=/^(cd|tail|head|grep|sort|uniq|wc|cat|echo|true|sleep|tee|xargs)$/;function Lo(e){let t=String(e.tool_name||""),o=e.tool_input||{};if(t==="Bash"){let r=String(o.command||"").split(/&&|\|\||;|\|/).map(m=>m.trim()).filter(Boolean),s=r.find(m=>!Mo.test(m.split(/\s+/)[0]))||r[0];if(!s)return null;let a=[];for(let m of s.split(/\s+/)){if(a.length===3||/^\d*[<>]|[<>&|$`'"*(){};]/.test(m))break;a.push(m)}if(!a.length)return null;let l=a.join(" ");return{tool:t,label:l,rule:`Bash(${l}:*)`,cmd:String(o.command).slice(0,300)}}let i=o.file_path||o.notebook_path||o.path;if(/^(Read|Edit|MultiEdit|Write|NotebookEdit|Glob|Grep)$/.test(t)&&i){let r=h.resolve(String(i)),s=/^(Glob|Grep)$/.test(t)?r:h.dirname(r);return{tool:t,label:`${/^(Read|Glob|Grep)$/.test(t)?"Read":"Edit"} files in ${s.replace(ae.homedir(),"~")}`,rule:`dir:${s}`,cmd:String(i)}}return t?{tool:t,label:t.replace(/^mcp__/,"").replace(/__/g," \xB7 "),rule:t}:null}function Fo(e){let t=[],o=new Set([...d.agent.allow||[],..._n]);for(let i of e||[]){let r=Lo(i);r&&!o.has(r.rule)&&!t.some(s=>s.rule===r.rule)&&t.push(r)}return t}function Ro(e,t,o){o.session_id&&(t.sessionId=o.session_id);let i=e.turn;if(!i)return;let r=i.c,s=o.type==="system"&&o.model||o.type==="assistant"&&o.message?.model;if(s&&r.model!==s&&(r.model=s,d.agent.lastModel=s,$()),r.t&&r.t.first==null&&o.type==="stream_event"&&(r.t.first=Date.now()-r.createdAt),r.t&&r.t.tool==null&&o.type==="stream_event"&&o.event?.content_block?.type==="tool_use"&&(r.t.tool=Date.now()-r.createdAt),o.type==="stream_event"){let a=o.event||{};if(a.type==="message_start"&&v.get(t.id)?.step==="Reading your comment\u2026"&&V(t.id,"Thinking\u2026"),a.type==="content_block_start"){let l=a.content_block||{};l.type==="thinking"?V(t.id,"Thinking\u2026"):l.type==="tool_use"?(i.partial="",V(t.id,Ut(l.name,{}))):l.type==="text"&&(i.partial="")}else a.type==="content_block_delta"&&a.delta?.type==="text_delta"&&(i.partial+=a.delta.text,i.flush||(i.flush=setTimeout(()=>{i.flush=null,e.turn===i&&V(t.id,"Writing\u2026",i.partial.slice(-4e3))},90)))}else if(o.type==="assistant"){for(let a of o.message?.content||[])if(a.type==="tool_use")V(t.id,Ut(a.name,a.input));else if(a.type==="text"&&a.text.trim()){i.lastText=a.text.trim();let l=r.steps||=[];l.push({t:Date.now(),s:i.lastText.slice(0,600),say:!0}),l.length>60&&l.splice(0,l.length-60),i.partial="",$()}}else if(o.type==="result"){clearTimeout(i.flush),e.turn=null,e.turns++,On(e);let a=o.result??i.lastText,l=r.steps?.[r.steps.length-1];l?.say&&String(a||"").trim().startsWith(l.s.slice(0,80))&&r.steps.pop();let m=!!o.is_error||(o.permission_denials||[]).some(w=>/^(Edit|MultiEdit|Write)$/.test(w.tool_name)),b=Fo(o.permission_denials);if(b.length?r.blocked=b:delete r.blocked,e.restart&&Se(e),v.get(t.id)?.commentId!==r.id)return;a!=null&&!m?oe(r,"done",String(a).trim().slice(0,4e3)||"Done."):oe(r,"error",String(a||e.stderr.trim()||"Something went wrong.").slice(-400))}}function No(e){if(!Re||Oe()!=="claude"||v.has(e.id)||d.agent.mode==="inbox"&&Fe())return!1;try{return vt(e),!0}catch{return!1}}async function Pn(e,t=!1){let o=F(e.frameId);if(!o)return;if(Oe()==="codex")return En(e,o,t);if(!Re)return v.set(o.id,{commentId:e.id,step:"",startedAt:Date.now()}),oe(e,"error",'No agent found to run this. Install Claude Code or Codex, or switch to "Your agent session" in the top-right menu.');let i={commentId:e.id,step:"Reading your comment\u2026",startedAt:e.startedAt&&t?e.startedAt:Date.now()};v.set(o.id,i),e.startedAt=i.startedAt,e.status="working",$();let r,s=!!q.get(o.id)&&!q.get(o.id).dead;try{r=vt(o,t)}catch(m){return oe(e,"error",`Couldn't start the agent: ${m.message}`)}r.warmed=s,i.child=r.child;let a=!t&&!e.snapshot?await _t(o,e.id):e.snapshot;if(e.snapshot=a,v.get(o.id)!==i)return;if(i.canceled)return oe(e,"canceled","Stopped.");if(r.dead){try{r=vt(o,!0)}catch(m){return oe(e,"error",`Couldn't start the agent: ${m.message}`)}i.child=r.child}let l=e.kind==="create"||o.kind==="canvas"?Pt(o,e):pn(o,e,Et(o,e).message);r.turn={c:e,lastText:"",partial:"",flush:null},clearTimeout(r.idle),e.t={sent:Date.now()-e.createdAt,warm:!!r.warmed},r.child.stdin.write(JSON.stringify({type:"user",message:{role:"user",content:[{type:"text",text:l}]}})+`
30
+ `)}async function En(e,t,o=!1){v.set(t.id,{commentId:e.id,step:"Starting Codex\u2026",startedAt:Date.now()}),e.startedAt=Date.now(),e.status="working",$(),!o&&!e.snapshot&&(e.snapshot=await _t(t,e.id));let i=pn(t,e,Pt(t,e)),r=t.kind==="folder"?t.src:Z,{model:s,effort:a}=Me("codex"),l=["--json","-c","sandbox_workspace_write.network_access=true",...s?["-m",s]:[],...a?["-c",`model_reasoning_effort="${a}"`]:[]],m=!!(t.codexSession&&!o&&e.kind!=="create"),b=m?["exec","resume",...l,"--skip-git-repo-check","-c",'sandbox_mode="workspace-write"',"-c",`sandbox_workspace_write.writable_roots=${JSON.stringify([r,_])}`,t.codexSession,"-"]:["exec",...l,"-s","workspace-write","--skip-git-repo-check","-C",r,"--add-dir",_,"-"];s&&(e.model=s,d.agent.lastModel=s,$());let w;try{w=Te(jt,b,{cwd:r,env:process.env,stdio:["pipe","pipe","pipe"]})}catch(C){return oe(e,"error",`Couldn't start Codex: ${C.message}`)}v.get(t.id).child=w,w.stdin.on("error",()=>{}),w.stdin.end(i);let I="",T="",E=null,j=!1,R="";w.stdout.on("data",C=>{I+=C;let D;for(;(D=I.indexOf(`
31
+ `))>=0;){let z=I.slice(0,D);I=I.slice(D+1);let U;try{U=JSON.parse(z)}catch{continue}let te=U.item||{};U.type==="thread.started"&&U.thread_id&&e.kind!=="create"?t.codexSession=U.thread_id:te.type==="command_execution"&&U.type==="item.started"?V(t.id,/\bcat |sed -n|rg |ls\b|grep /.test(te.command||"")?"Reading the code":"Running a command"):te.type==="file_change"?V(t.id,`Editing ${h.basename(te.changes?.[0]?.path||"")}`.trim()):te.type==="agent_message"&&te.text?.trim()?(T=te.text.trim(),V(t.id,"Thinking\u2026")):te.type==="reasoning"?V(t.id,"Thinking\u2026"):U.type==="turn.completed"?j=!0:(U.type==="turn.failed"||U.type==="error")&&(E=U.error?.message||U.message||"Codex failed")}}),w.stderr.on("data",C=>{R=(R+C).slice(-2e3)}),w.on("error",C=>{R+=C.message}),w.on("close",C=>{if(!v.get(t.id)?.canceled&&!j&&m)return t.codexSession=null,v.delete(t.id),En(e,t,!0);j&&!E?oe(e,"done",T.slice(0,4e3)||"Done."):oe(e,"error",String(E||R.trim()||`Codex exited with code ${C}`).slice(-400))})}function Wo(){for(let e of Object.values(ae.networkInterfaces()))for(let t of e||[])if(t.family==="IPv4"&&!t.internal)return t.address;return null}var K=0;function It(){let e=Wo();return{lan:e?`http://${e}:${K}${X}/?t=${d.token}`:null,public:se?`${se}${X}/?t=${d.token}`:null,tunnelAvailable:!!ke("cloudflared")}}function zo(){return new Promise((e,t)=>{if(se)return e(se);let o=ke("cloudflared");if(!o)return t(new Error("Public links need cloudflared. Install it with: brew install cloudflared"));me=Te(o,["tunnel","--no-autoupdate","--url",`http://localhost:${K}`],{stdio:["ignore","pipe","pipe"]});let i=r=>{let s=String(r).match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/);s&&!se&&(se=s[0],$(),e(se))};me.stdout.on("data",i),me.stderr.on("data",i),me.on("close",()=>{se=null,me=null,$()}),setTimeout(()=>t(new Error("The tunnel took too long to start")),2e4)})}var Dn={".html":"text/html; charset=utf-8",".js":"text/javascript; charset=utf-8",".mjs":"text/javascript; charset=utf-8",".css":"text/css; charset=utf-8",".json":"application/json",".svg":"image/svg+xml",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp",".woff2":"font/woff2",".woff":"font/woff",".ttf":"font/ttf",".ico":"image/x-icon",".mp4":"video/mp4",".txt":"text/plain; charset=utf-8"};function Cn(e,t){let o=e.socket.remoteAddress||"",i=o==="127.0.0.1"||o==="::1"||o==="::ffff:127.0.0.1",r=e.headers["x-forwarded-for"]||e.headers["cf-connecting-ip"];if(i&&!r)return/^(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/i.test(String(e.headers.host||""))?"owner":null;let s=/(?:^|;\s*)loop_t=([^;]+)/.exec(e.headers.cookie||"")?.[1];return t.searchParams.get("t")===d.token||s===d.token?"guest":null}function Ho(e){let t=e.headers;if(t.origin){let o=null;try{o=new URL(t.origin).host}catch{}if(o!==String(t.host||""))return!0}return t["sec-fetch-site"]==="cross-site"&&t["sec-fetch-mode"]!=="navigate"}async function L(e){let t=[],o=0;for await(let r of e){if(o+=r.length,o>15e6)throw new Error("too large");t.push(r)}let i=Buffer.concat(t).toString("utf8");return i?JSON.parse(i):{}}function u(e,t,o){e.writeHead(t,{"content-type":"application/json","cache-control":"no-store"}),e.end(JSON.stringify(o))}async function de(e,t,o={}){try{if((await M.stat(t)).isDirectory())return de(e,h.join(t,"index.html"),o);e.writeHead(200,{"content-type":Dn[h.extname(t).toLowerCase()]||"application/octet-stream","cache-control":"no-store",...o}),k.createReadStream(t).pipe(e)}catch{e.writeHead(404),e.end("Not found")}}var ie=(e,t)=>t===e||t.startsWith(e+h.sep),Ke=on.createServer(async(e,t)=>{let o=new URL(e.url,"http://x"),i;try{i=decodeURIComponent(o.pathname)}catch{return t.writeHead(400),t.end()}let r=Cn(e,o);if(!r)return t.writeHead(403,{"content-type":"text/plain"}),t.end("This Loop canvas is private. Ask the host for a share link.");if((i===X||i.startsWith(X+"/"))&&Ho(e))return t.writeHead(403,{"content-type":"text/plain"}),t.end("Refused: this request came from another website.");let s=r==="owner";if(i!==X&&!i.startsWith(X+"/")){if(e.method==="GET"||e.method==="HEAD"){let a=null;try{a=$t.exec(new URL(e.headers.referer).pathname)}catch{}if(a&&Vt(a[1],a[2]))return t.writeHead(307,{location:`${X}/o/${a[1]}/${a[2]}${e.url}`,"cache-control":"no-store",vary:"Referer"}),t.end()}return d.proxy?ut(e,t,d.proxy):e.headers["sec-fetch-dest"]==="iframe"?Wt(t,"No app is connected to this canvas yet","Run <code>loop scan</code> in the project, or give Loop the running app with <code>loop scan --url http://localhost:&lt;port&gt;</code>."):i==="/"?(t.writeHead(302,{location:`${X}/${o.search}`}),t.end()):(t.writeHead(404),t.end("Not found"))}if(i=i.slice(X.length)||"/",i==="/"&&e.headers["sec-fetch-dest"]==="iframe")return Wt(t,"A frame can\u2019t show the canvas itself","This frame points at Loop\u2019s own address. Point it at your app instead.");try{if(i==="/"||i==="/index.html"){let n=o.searchParams.get("t")?{"set-cookie":`loop_t=${d.token}; Path=/; HttpOnly; SameSite=Lax`}:{};return de(t,h.join(it,"index.html"),n)}if(i.startsWith("/app/")){let n=h.join(it,h.normalize(i.slice(5)));return ie(it,n)?de(t,n):(t.writeHead(400),t.end())}{let n=$t.exec(o.pathname),c=n&&Vt(n[1],n[2]);if(n)return c?Oo(e,t,c,(n[3]||"/")+o.search):(t.writeHead(404),t.end("This option is gone."))}let a=/^\/optview\/([A-Za-z0-9_-]+)\/([A-F])\/(.*)$/.exec(i);if(a){let n=d.comments.find(S=>S.id===a[1]),c=n&&F(n.frameId),f=n?.options?.find(S=>S.id===a[2]);if(!c||!f||c.kind!=="folder")return t.writeHead(404),t.end();let p=h.normalize(decodeURIComponent(a[3]||c.entry)),x=h.join(f.dir,p),y=h.join(c.src,p);return!ie(f.dir,x)||!ie(c.src,y)?(t.writeHead(400),t.end()):de(t,k.existsSync(x)?x:y)}let l=/^\/hist\/([A-Za-z0-9_-]+)\/(.*)$/.exec(i);if(l){let n=h.join(_,"history",l[1]),c=h.join(n,h.normalize(l[2]||"index.html"));return ie(n,c)?de(t,c):(t.writeHead(400),t.end())}let m=/^\/poster\/([A-Za-z0-9_-]+)$/.exec(i);if(m){let n=d.frames.find(c=>c.id===m[1]);if(!n)return t.writeHead(404),t.end();try{return de(t,await bo(n),{"cache-control":"private, max-age=3600"})}catch{return t.writeHead(404),t.end()}}let b=/^\/shots\/([\w.-]+\.png)$/.exec(i);if(b)return de(t,h.join(_,"shots",b[1]));let w=/^\/attachments\/([A-Za-z0-9_-]+)\/([^/]+)$/.exec(i);if(w){let n=h.join(_,"attachments",w[1]),c=h.join(n,decodeURIComponent(w[2]));return ie(n,c)?de(t,c):(t.writeHead(400),t.end())}let I=/^\/f\/([^/]+)\/(.*)$/.exec(i);if(I){let n=d.frames.find(f=>f.id===I[1]);if(!n||n.kind!=="folder"&&n.kind!=="frozen")return t.writeHead(404),t.end();let c=h.join(n.src,h.normalize(I[2]||n.entry));return ie(n.src,c)?de(t,c):(t.writeHead(400),t.end())}if(i==="/api/ping")return u(t,200,{ok:!0,name:d.name,pid:process.pid});if(i==="/api/state")return u(t,200,ln(r));if(i==="/api/events"){t.writeHead(200,{"content-type":"text/event-stream","cache-control":"no-store",connection:"keep-alive"});let n={res:t,role:r};Ve.add(n),ht(t,un(n));let c=setInterval(()=>t.write(`: ka
30
32
 
31
- `),2e4);e.on("close",()=>{clearInterval(c),He.delete(n)});return}if(e.method==="POST"&&i==="/api/presence"){let n=await L(e);return J({type:"presence",id:String(n.id).slice(0,32),name:String(n.name||"Guest").slice(0,32),color:String(n.color||"#888").slice(0,16),x:+n.x,y:+n.y,leave:!!n.leave}),u(t,200,{ok:!0})}if(e.method==="POST"&&i==="/api/comments"){let n=await L(e),c=R(n.frameId);if(!c||!String(n.text||"").trim())return u(t,400,{error:"frame and text required"});let f=typeof n.id=="string"&&/^[A-Za-z0-9_-]{8}$/.test(n.id)&&!d.comments.some(w=>w.id===n.id)?n.id:N(8),m=null;typeof n.annotationPng=="string"&&n.annotationPng.startsWith("data:image/png;base64,")&&(await M.mkdir(h.join(A,"annotations"),{recursive:!0}),m=h.join(A,"annotations",`${f}.png`),await M.writeFile(m,Buffer.from(n.annotationPng.split(",")[1],"base64")));let y=[];for(let w of(Array.isArray(n.attachments)?n.attachments:[]).slice(0,6)){let P=/^data:([\w.+-]+\/[\w.+-]+)?(;[^,]*)?;base64,(.*)$/s.exec(String(w?.data||""));if(!P)continue;let E=String(w.name||"file").replace(/[^\w.\- ]+/g,"_").replace(/^\.+/,"").slice(-80)||"file",j=h.join(A,"attachments",f);await M.mkdir(j,{recursive:!0});let ne=E,Fe=1;for(;y.some(K=>K.name===ne);)ne=`${Fe++}-${E}`;await M.writeFile(h.join(j,ne),Buffer.from(P[3],"base64")),y.push({name:ne,type:P[1]||"application/octet-stream",path:h.join(j,ne),url:`/__loop/attachments/${f}/${encodeURIComponent(ne)}`})}let g=n.parentId?d.comments.find(w=>w.id===n.parentId):null,x={id:f,frameId:c.id,parentId:g?g.parentId||g.id:null,text:String(n.text).slice(0,4e3),anchor:n.anchor||g?.anchor||null,ink:Array.isArray(n.ink)?n.ink.slice(0,20):[],inkStrokes:Array.isArray(n.inkStrokes)?n.inkStrokes.map(String):[],annotation:m,author:{name:String(n.author?.name||(s?"You":"Guest")).slice(0,32),color:String(n.author?.color||"#2F5BFF").slice(0,16),guest:!s},status:s?"queued":"waiting",reply:null,createdAt:Date.now(),...y.length?{attachments:y}:{}};d.comments.push(x);for(let w of d.strokes)x.inkStrokes.includes(w.id)&&(w.commentId=x.id);if(s&&eo(x,c),S(),s&&x.kind!=="create"&&(c.kind==="folder"||(c.kind==="route"||c.kind==="url")&&!c.optionOf)){let w=ht(x.text);for(let P=0;P<w;P++)try{if(!(c.kind==="folder"?await zt(x,c):await gt(x,c)))break}catch{break}}return se(c.id),u(t,200,x)}let T=/^\/api\/comments\/([^/]+)(?:\/(\w+))?$/.exec(i);if(T){let n=d.comments.find(f=>f.id===T[1]);if(!n)return u(t,404,{error:"no such comment"});let c=T[2];if(e.method==="DELETE"&&!c){if(!s&&!n.author?.guest)return u(t,403,{});let f=new Set([n.id,...d.comments.filter(y=>y.parentId===n.id).map(y=>y.id)]),m=v.get(n.frameId);return m&&f.has(m.commentId)&&(m.canceled=!0,m.child?m.child.kill("SIGTERM"):v.delete(n.frameId)),d.comments=d.comments.filter(y=>!f.has(y.id)),d.strokes=d.strokes.filter(y=>!f.has(y.commentId)),S(),u(t,200,{ok:!0})}if(!s)return u(t,403,{error:"host only"});if(c==="send")return["waiting","error","canceled"].includes(n.status)&&(n.status="queued",n.snapshot=null,n.undone=!1,n.redoSnap=null,S(),se(n.frameId)),u(t,200,n);if(c==="background"){if(!s)return u(t,403,{});if(n.status!=="queued")return u(t,409,{error:"It already started"});n.background=!0;for(let f of d.comments)f.frameId===n.frameId&&f.status==="queued"&&!f.local&&(f.background=!0);return S(),se(n.frameId),u(t,200,{ok:!0})}if(c==="steer")return s?ro(n)?u(t,200,{ok:!0}):u(t,409,{error:"Nothing to steer into: the run already finished"}):u(t,403,{});if(c==="cancel"){let f=v.get(n.frameId);return f?.commentId===n.id?(f.canceled=!0,f.child?f.child.kill("SIGTERM"):oe(n,"canceled","Stopped.")):n.status==="queued"&&(n.status="canceled",S()),u(t,200,{ok:!0})}if(c==="undo"){let f=R(n.frameId);if(!n.snapshot||!f||f.kind!=="folder")return u(t,400,{error:"nothing to undo"});if(v.has(f.id))return u(t,409,{error:"The agent is still working on this frame \u2014 wait for it to finish (or stop it) first."});let m=d.comments.filter(g=>g.id!==n.id&&g.frameId===n.frameId&&g.snapshot&&g.status==="done"&&!g.undone&&(g.startedAt||g.createdAt)>(n.startedAt||n.createdAt)),y=await L(e);if(m.length&&!y.force)return u(t,409,{error:"newer",newer:m.length});n.redoSnap=await Re(f,`${n.id}-after`),await Ne(f,n.snapshot),n.undone=!0;for(let g of m)g.undone=!0;return S(),J({type:"frame-changed",frameId:f.id}),u(t,200,{ok:!0,reverted:1+m.length})}if(c==="redo"){let f=R(n.frameId);return!n.redoSnap||!n.undone||!f?u(t,400,{error:"nothing to redo"}):v.has(f.id)?u(t,409,{error:"The agent is still working on this frame \u2014 wait for it to finish first."}):(await Ne(f,n.redoSnap),n.undone=!1,S(),J({type:"frame-changed",frameId:f.id}),u(t,200,{ok:!0}))}if(c==="resolve")return n.resolved=!n.resolved,S(),u(t,200,n);if(c==="allow"&&e.method==="POST"){let f=await L(e),m=(f.all?n.blocked||[]:[n.blocked?.[+f.i]]).filter(E=>E&&!E.allowed);if(!m.length)return u(t,400,{error:"Nothing to allow"});let y=d.agent.allow||=[];for(let E of m)y.includes(E.rule)||y.push(E.rule),E.allowed=!0;let g=q.get(n.frameId);g&&!g.turn?$e(g):g&&(g.restart=!0);let x=d.comments.find(E=>E.id===(n.parentId||n.id)),w=m.map(E=>E.tool==="Bash"?`\`${E.label}\``:E.label.charAt(0).toLowerCase()+E.label.slice(1)),P=w.length>1?`${w.slice(0,-1).join(", ")} and ${w[w.length-1]}`:w[0];return d.comments.push({id:N(8),frameId:n.frameId,parentId:x?.id||n.id,anchor:n.anchor,ink:[],inkStrokes:[],auto:"allow",text:`Allowed ${P}. If you still need ${w.length>1?"them":"it"} to finish what was asked, use ${w.length>1?"them":"it"} now and say what changed. If the work is already complete without ${w.length>1?"them":"it"}, change nothing and reply with exactly: Nothing more to do.`,author:x?.author||{name:"You",color:"#2F5BFF"},status:"queued",reply:null,createdAt:Date.now()}),S(),se(n.frameId),u(t,200,{ok:!0})}if(c==="note"){let f=await L(e),m=String(f.text||"").trim().slice(0,2e3);if(!m)return u(t,400,{error:"text required"});let y={id:N(8),kind:"note",frameId:n.frameId,parentId:n.parentId||n.id,text:m,anchor:n.anchor,author:{name:pe[Te()].label,agent:!0,color:"#17171A"},model:f.model?String(f.model).slice(0,60):void 0,status:"done",createdAt:Date.now()};return d.comments.push(y),S(),J({type:"frame-changed",frameId:n.frameId}),u(t,200,y)}if(c==="step"){let f=await L(e);return Y(n.frameId,String(f.text||"").slice(0,120)),u(t,200,{ok:!0})}if(c==="option"&&e.method==="POST"){let f=await L(e),m=R(n.frameId);if(!m)return u(t,404,{error:"no frame"});if(n.options=n.options||[],m.kind==="folder"){let x=String(f.name||"").trim().slice(0,60),w=n.options.find(P=>P.placeholder&&P.frameId);try{if(w){w.placeholder=!1,w.name=x||w.name;let P=d.frames.find(E=>E.id===w.frameId);P&&x&&(P.name=re(`${w.id} \xB7 ${x}`)),w.madeAt=Date.now(),S()}else w=await zt(n,m,x||`Option ${String.fromCharCode(65+n.options.length)}`)}catch(P){return u(t,400,{error:P.message})}return u(t,200,{id:w.id,dir:w.dir,root:m.src,frame:!0})}{let x=String(f.name||"").trim().slice(0,60),w=n.options.find(E=>E.placeholder&&E.live&&E.frameId),P=w&&d.frames.find(E=>E.id===w.frameId);if(P)return w.placeholder=!1,x&&(w.name=x,P.name=re(`${w.id} \xB7 ${x}`)),w.madeAt=Date.now(),S(),u(t,200,{id:w.id,dir:w.dir,root:X,beside:!0})}try{let x=await gt(n,m,String(f.name||"").trim().slice(0,60)||`Option ${String.fromCharCode(65+n.options.length)}`);if(x)return u(t,200,{id:x.id,dir:x.dir,root:X,beside:!0})}catch(x){return u(t,400,{error:x.message})}if(n.options.length>=6)return u(t,400,{error:"up to 6 options"});let y=String.fromCharCode(65+n.options.length),g=h.join(A,"options",n.id,y);return await M.rm(g,{recursive:!0,force:!0}),await M.mkdir(g,{recursive:!0}),n.options.push({id:y,name:String(f.name||`Option ${y}`).slice(0,60),dir:g}),S(),u(t,200,{id:y,dir:g,root:ln(m)})}if(c==="thumbs"&&e.method==="POST")return un(n),u(t,200,{ok:!0});if(c==="preview"&&e.method==="POST"){if(!s)return u(t,403,{});let f=await L(e);try{await et(n,f.option||null)}catch(m){return u(t,400,{error:m.message})}return u(t,200,{ok:!0})}if(c==="clearoptions"&&e.method==="POST")return Ut(n),n.optionsCleared=!0,S(),u(t,200,{ok:!0});if(c==="choose"&&e.method==="POST"){if(!s)return u(t,403,{});let f=await L(e),m=n.options?.find(g=>g.id===f.option&&g.frameId),y=m&&d.frames.find(g=>g.id===m.frameId);if(y&&!m.live){let g=R(n.frameId);if(!g||g.kind!=="folder")return u(t,404,{error:"The original frame is gone."});if(v.has(g.id))return u(t,409,{error:"The agent is still working on the original frame. Wait for it to finish (or stop it) first."});let x=`r${N(7)}`;return await Re(g,x)?((g.restores||=[]).push({id:x,at:Date.now()}),await Ne(g,y.src),n.optionChosen=m.id,ue.set(g.id,(ue.get(g.id)||0)+1),S(),J({type:"frame-changed",frameId:g.id}),u(t,200,{ok:!0})):u(t,500,{error:"Could not keep the current version, so nothing was changed."})}if(m?.live){let g=R(n.frameId),x=g&&Be(g);if(g&&v.has(g.id))return u(t,409,{error:"The agent is still working on this screen. Wait for it to finish (or stop it) first."});try{await et(n,f.option)}catch(w){return u(t,400,{error:w.message})}n.optionChosen=f.option,Ut(n),S();for(let w of d.frames)x&&Be(w)===x&&J({type:"frame-changed",frameId:w.id});return u(t,200,{ok:!0})}try{await et(n,f.option)}catch(g){return u(t,400,{error:g.message})}return n.optionChosen=f.option,S(),await M.rm(h.join(A,"options",n.id,"_original"),{recursive:!0,force:!0}).catch(()=>{}),u(t,200,{ok:!0})}if(c==="reply"){let f=await L(e);return v.has(n.frameId)||v.set(n.frameId,{commentId:n.id,step:"",startedAt:Date.now()}),f.model&&(n.model=String(f.model).slice(0,60),d.agent.lastModel=n.model),n.startedAt||(n.startedAt=v.get(n.frameId)?.startedAt),oe(n,f.error?"error":"done",String(f.text||"Done.").slice(0,4e3)),u(t,200,n)}}if(!s)return u(t,403,{error:"host only"});if(i==="/api/inbox"){let n=Date.now()+(o.searchParams.get("wait")?25e3:0),c=Ye(),f=!1;for(e.on("close",()=>{f=!0}),ft=Date.now(),c||S(!0);;){if(f)return;ft=Date.now();let m=d.comments.find(y=>y.status==="queued"&&!y.background&&!v.has(y.frameId));if(m){let y=R(m.frameId),g={commentId:m.id,step:"Your chat picked this up",startedAt:Date.now(),inbox:!0};return v.set(y.id,g),setTimeout(()=>{v.get(y.id)===g&&g.step==="Your chat picked this up"&&Y(y.id,"Working\u2026")},3e3),m.status="working",S(),[m.snapshot]=await Promise.all([m.snapshot||vt(y,m.id),no(m)]),u(t,200,{comment:{id:m.id,kind:m.kind||"comment",text:m.text,anchor:m.anchor,ink:m.ink,annotation:m.annotation,attachments:m.attachments},frame:{id:y.id,name:y.name,kind:y.kind,src:y.src,entry:y.entry,w:y.w,h:y.h},prompt:It(y,m)})}if(Date.now()>n)return u(t,200,{comment:null});await new Promise(y=>{let g=()=>{clearTimeout(x),at.delete(g),y()},x=setTimeout(g,2e3);at.add(g)})}}if(e.method==="POST"&&i==="/api/frames"){let n=await L(e);if(n.image||n.imageUrl){let c,f;try{if(n.image){let w=/^data:(image\/(?:png|jpeg|gif|webp|svg\+xml));base64,(.+)$/s.exec(String(n.image));if(!w)throw new Error("That is not an image Loop can show (PNG, JPEG, GIF, WebP or SVG).");f=w[1],c=Buffer.from(w[2],"base64")}else{let w=new URL(String(n.imageUrl));if(!/^https?:$/.test(w.protocol))throw new Error("Not a web address");let P=await fetch(w,{signal:AbortSignal.timeout(15e3),headers:{accept:"image/*"}});if(f=(P.headers.get("content-type")||"").split(";")[0].trim(),!P.ok||!/^image\/(png|jpeg|gif|webp|svg\+xml)$/.test(f))throw new Error("That address isn't an image Loop can show.");c=Buffer.from(await P.arrayBuffer())}}catch(w){return u(t,400,{error:w.message})}if(c.length>12e6)return u(t,400,{error:"That image is too big (over 12 MB)."});let m={"image/png":"png","image/jpeg":"jpg","image/gif":"gif","image/webp":"webp","image/svg+xml":"svg"}[f],y=String(n.name||"Image").replace(/\.[a-z0-9]+$/i,"").slice(0,60)||"Image",g=we(y);$.mkdirSync(g,{recursive:!0}),$.writeFileSync(h.join(g,`image.${m}`),c),$.writeFileSync(h.join(g,"index.html"),`<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="loop-image" content="image.${m}"><title>${be(y)}</title><style>html,body{margin:0;height:100%;background:transparent}img{display:block;width:100%;height:100%;object-fit:contain}</style></head><body><img src="image.${m}" alt="${be(y)}"></body></html>`);let x=W({src:g,name:re(y),w:Math.max(40,Math.round(+n.w||800)),h:Math.max(40,Math.round(+n.h||600)),x:n.x,y:n.y});return x.image=!0,x.fill="none",S(),u(t,200,x)}if(n.prompt){let c=n.name||n.prompt.split(/[.\n]/)[0].slice(0,40),f=Lt(c),m=W({src:f,name:c,w:n.w,h:n.h,x:n.x,y:n.y});return d.comments.push({id:N(8),kind:"create",frameId:m.id,text:n.prompt,author:{name:"You",color:"#2F5BFF"},status:"queued",createdAt:Date.now()}),S(),se(m.id),u(t,200,{...m,dir:f})}if(n.new){let c=Lt(n.name||"Untitled",!!n.blank);typeof n.html=="string"&&n.html.length<4e6&&$.writeFileSync(h.join(c,"index.html"),n.html);let f=n.x==null&&fo();if(f)return u(t,200,{...uo(f.c,f.f,c,n.name),dir:c});let m=W({...n,src:c});return n.parent&&d.frames.some(y=>y.id===n.parent&&y.id!==m.id)&&(m.parent=n.parent,S()),(n.fill==="none"||/^#[0-9a-f]{6}$/i.test(n.fill||""))&&(m.fill=n.fill.toLowerCase(),S()),u(t,200,{...m,dir:c})}return n.route!=null?u(t,200,W(n)):n.src?!/^https?:\/\//.test(n.src)&&!$.existsSync(h.resolve(X,n.src))?u(t,400,{error:`No such file or folder: ${n.src}`}):u(t,200,W(n)):u(t,400,{error:"src, route, new or prompt required"})}let _=/^\/api\/frames\/([^/]+)\/versions(\/restore|\/duplicate)?$/.exec(i);if(_&&e.method==="POST"){let n=d.frames.find(w=>w.id===_[1]);if(!n||n.kind!=="folder")return u(t,400,{error:"Only static frames keep versions."});let c=encodeURIComponent(n.entry||"index.html");if(!_[2]){let w=[...d.comments.filter(j=>j.frameId===n.id&&j.snapshot&&!j.snapThumb&&$.existsSync(j.snapshot)),...(n.restores||[]).filter(j=>!j.thumb)].slice(-14),P=async j=>{try{let ne=await he(n,`http://127.0.0.1:${V}/__loop/hist/${j.id}/${c}`);j["frameId"in j?"snapThumb":"thumb"]=`/__loop/shots/${h.basename(ne)}`,S()}catch{}},[E]=await Promise.all([he(n).catch(()=>null),...w.map(P)]);return u(t,200,{now:E?`/__loop/shots/${h.basename(E)}`:null})}let f=await L(e),m=String(f.id||"");if(!/^[A-Za-z0-9_-]+$/.test(m))return u(t,400,{error:"no such version"});let y=d.comments.some(w=>w.id===m&&w.frameId===n.id&&w.snapshot)||(n.restores||[]).some(w=>w.id===m),g=h.join(A,"history",m);if(!y||!$.existsSync(g))return u(t,404,{error:"That version is no longer kept."});if(_[2]==="/duplicate"){let w=re(String(f.name||`${n.name} \xB7 earlier version`).slice(0,80)),P=we(w);return await fe(g,P),u(t,200,W({src:h.join(P,n.entry||"index.html"),name:w,w:n.w,h:n.h,near:n.id}))}if(v.has(n.id))return u(t,409,{error:"The agent is still working on this frame. Wait for it to finish (or stop it) first."});let x=`r${N(7)}`;return await Re(n,x)?((n.restores||=[]).push({id:x,at:Date.now()}),await Ne(n,g),ue.set(n.id,(ue.get(n.id)||0)+1),S(),J({type:"frame-changed",frameId:n.id}),u(t,200,{ok:!0})):u(t,500,{error:"Could not keep the current version, so nothing was changed."})}let O=/^\/api\/frames\/([^/]+)\/(source|edit)$/.exec(i);if(O){let n=d.frames.find(m=>m.id===O[1]);if(!n||!s)return u(t,404,{error:"no such frame"});if(n.kind!=="folder")return u(t,400,{error:"Only static frames can be edited directly."});let c=h.join(n.src,n.entry||"index.html"),f;try{f=await M.readFile(c,"utf8")}catch{return u(t,404,{error:"The frame has no page to edit."})}if(O[2]==="source"&&e.method==="GET")return u(t,200,{text:f});if(O[2]==="edit"&&e.method==="POST"){if(v.has(n.id))return u(t,409,{error:"The agent is working on this frame. Try again when it is done."});let m=await L(e),y=(Array.isArray(m.edits)?m.edits:[]).map(j=>({a:j.a|0,b:j.b|0,before:String(j.before??""),text:String(j.text??"")})).sort((j,ne)=>j.a-ne.a);if(!y.length||y.length>40)return u(t,400,{error:"nothing to change"});let g=0;for(let j of y){if(j.a<g||j.b<j.a||j.b>f.length||f.slice(j.a,j.b)!==j.before)return u(t,409,{error:"The page changed since you started editing. Try again."});g=j.b}let x=Ft.get(n.id)||0,w=Date.now();if(w-x>9e4){let j=`e${N(7)}`;await Re(n,j)&&(n.restores||=[]).push({id:j,at:w,what:String(m.what||"Edited by hand").slice(0,80)})}Ft.set(n.id,w);let P="",E=0;for(let j of y)P+=f.slice(E,j.a)+j.text,E=j.b;return P+=f.slice(E),await M.writeFile(c,P),S(),u(t,200,{ok:!0})}}let F=/^\/api\/frames\/([^/]+)\/warm$/.exec(i);if(F&&e.method==="POST"){let n=R(F[1]);return Xe&&n?.kind!=="canvas"&&fn().catch(()=>{}),u(t,200,{warm:!!(n&&Oo(n))})}let D=/^\/api\/frames\/([^/]+)\/sizes$/.exec(i);if(D&&e.method==="POST"){if(!s)return u(t,403,{});let n=R(D[1]);if(!n||!["route","url","folder"].includes(n.kind)||n.image)return u(t,400,{error:"Sizes work for screens: an app page, a web page or a static page."});let c=d.sections.find(K=>K.sizesOf===n.id&&d.frames.some(xe=>xe.sizeOf===n.id));if(c)return u(t,200,{section:c.id,frames:d.frames.filter(K=>K.sizeOf===n.id).map(K=>K.id),existing:!0});let f=Ke(n),m=Object.entries(ye).filter(([K])=>K!==f),y=60,g=48,x=72,w=m.reduce((K,[,[xe]])=>K+xe,0)+y*(m.length-1)+g*2,P=Math.max(...m.map(([,[,K]])=>K))+x+g,E={x:n.x+n.w+y+20,y:n.y-x,w,h:P};mt(E,n);let j={id:N(6),name:`Sizes \xB7 ${n.name}`,...E,sizesOf:n.id,createdAt:Date.now()};d.sections.push(j);let ne=E.x+g,Fe=[];for(let[K,[xe,En]]of m){let Cn=re(`${n.name} \xB7 ${K}`),Ze={w:xe,h:En,x:ne,y:n.y,name:Cn},Pt=n.kind==="route"?W({route:n.src,dup:!0,...Ze}):n.kind==="url"?W({src:n.src,...Ze}):W({src:h.join(n.src,n.entry||"index.html"),...Ze});Pt.sizeOf=n.id,Fe.push(Pt.id),ne+=xe+y}return S(),u(t,200,{section:j.id,frames:Fe})}let C=/^\/api\/frames\/([^/]+)\/asset$/.exec(i);if(C&&e.method==="POST"){if(!s)return u(t,403,{});let n=R(C[1]);if(!n||n.kind!=="folder")return u(t,400,{error:"Only static frames keep their own files."});let c=await L(e),f=/^data:(image\/(?:png|jpeg|gif|webp|svg\+xml));base64,(.+)$/s.exec(String(c.image||""));if(!f)return u(t,400,{error:"That is not an image Loop can use (PNG, JPEG, GIF, WebP or SVG)."});let m={"image/png":"png","image/jpeg":"jpg","image/gif":"gif","image/webp":"webp","image/svg+xml":"svg"}[f[1]],y=rn(String(c.name||"image").replace(/\.[a-z0-9]+$/i,""))||"image",g=h.join(n.src,"assets");$.mkdirSync(g,{recursive:!0});let x=`${y}.${m}`,w=2;for(;$.existsSync(h.join(g,x));)x=`${y}-${w++}.${m}`;return $.writeFileSync(h.join(g,x),Buffer.from(f[2],"base64")),u(t,200,{path:`assets/${x}`})}let z=/^\/api\/frames\/([^/]+)\/shot$/.exec(i);if(z&&e.method==="POST"){let n=d.frames.find(m=>m.id===z[1]);if(!n||!s)return u(t,404,{error:"no such frame"});let c=await L(e),f;if(c.option){let[m,y]=String(c.option).split(":"),g=d.comments.find(w=>w.id===m);if(!g?.options?.some(w=>w.id===y))return u(t,400,{error:"no such option"});let x=d.frames.find(w=>w.id===g.options.find(P=>P.id===y)?.frameId);if(x)try{return u(t,200,{path:await he(x)})}catch(w){return u(t,500,{error:w.message})}if(n.kind!=="folder")return u(t,400,{error:"Options on app screens can't be screenshotted before they're applied. Describe them clearly instead."});f=`http://127.0.0.1:${V}/__loop/optview/${m}/${y}/${encodeURIComponent(n.entry||"index.html")}`}v.get(n.id)?.inbox&&Y(n.id,"Looking at the screen");try{return u(t,200,{path:await he(n,f)})}catch(m){return u(t,500,{error:m.message})}}let U=/^\/api\/frames\/([^/]+)\/freeze$/.exec(i);if(U&&e.method==="POST"){let n=d.frames.find(x=>x.id===U[1]);if(!n)return u(t,404,{error:"no such frame"});let c=await L(e);if(typeof c.html!="string"||!c.html)return u(t,400,{error:"nothing captured"});let f=re(`${n.name.replace(/ · frozen( \d+)?$/,"")} \xB7 frozen`),m=we(f);await M.mkdir(m,{recursive:!0}),await M.writeFile(h.join(m,"index.html"),c.html);let y=typeof c.from=="string"&&c.from.startsWith("/")&&!c.from.startsWith("/__loop/")?c.from.slice(0,2e3):n.kind==="frozen"?n.from:n.kind==="route"?n.src:void 0,g={id:N(6),name:f,kind:"frozen",src:m,entry:"index.html",from:y,frozenAt:Date.now(),w:n.w,h:n.h,keyboard:n.keyboard,...c.x!=null&&c.y!=null?{x:+c.x,y:+c.y}:ct(n.w,n.h,n.id),createdAt:Date.now()};return d.frames.push(g),S(),u(t,200,g)}let te=/^\/api\/frames\/([^/]+)\/duplicate$/.exec(i);if(te&&e.method==="POST"){let n=d.frames.find(w=>w.id===te[1]);if(!n)return u(t,404,{error:"no such frame"});let c=await L(e),f=n.kind==="route"&&typeof c.route=="string"&&c.route.startsWith("/")&&!c.route.startsWith("/__loop/")?c.route.slice(0,2e3):null,m=f&&f!==n.src,y=re(m?Vn(f):`${n.name.replace(/ copy( \d+)?$/,"")} copy`),g=c.x!=null&&c.y!=null?{x:c.x,y:c.y}:{near:n.id},x;if(n.kind==="folder"||n.kind==="frozen"){let w=we(y);await fe(n.src,w),x=W({src:h.join(w,n.entry),name:y,w:n.w,h:n.h,...g}),n.kind==="frozen"&&(_e(x.id),Object.assign(x,{kind:"frozen",from:n.from,frozenAt:n.frozenAt}),S())}else n.kind==="route"?x=W({route:f||n.src,name:y,w:n.w,h:n.h,dup:!0,...g}):x=W({src:n.src,name:y,w:n.w,h:n.h,...g});return u(t,200,x)}let je=/^\/api\/frames\/([^/]+)$/.exec(i);if(je){let n=d.frames.find(c=>c.id===je[1]);if(!n)return u(t,404,{});if(e.method==="PATCH"){let c=await L(e);for(let f of["x","y","w","h"])c[f]!=null&&isFinite(c[f])&&(n[f]=Math.round(+c[f]));return c.name&&(n.name=String(c.name).slice(0,80)),typeof c.keyboard=="boolean"&&(n.keyboard=c.keyboard||void 0),"fill"in c&&(n.fill=c.fill==="none"?"none":typeof c.fill=="string"&&/^#[0-9a-f]{6}$/i.test(c.fill)?c.fill.toLowerCase():void 0),"parent"in c&&(n.parent=c.parent&&c.parent!==n.id&&d.frames.some(f=>f.id===c.parent)?c.parent:void 0),S(),u(t,200,n)}if(e.method==="DELETE"){let c=v.get(n.id);return c&&(c.canceled=!0,c.child?.kill("SIGTERM"),v.delete(n.id)),d.frames=d.frames.filter(f=>f.id!==n.id),d.comments=d.comments.filter(f=>f.frameId!==n.id),_e(n.id),S(),u(t,200,{ok:!0})}}if(e.method==="POST"&&i==="/api/sections"){let n=await L(e);if(![n.x,n.y,n.w,n.h].every(Number.isFinite))return u(t,400,{error:"x, y, w and h required"});let c=d.sections.length+1;for(;d.sections.some(m=>m.name===`Section ${c}`);)c++;let f={id:N(6),name:String(n.name||`Section ${c}`).slice(0,80),x:Math.round(n.x),y:Math.round(n.y),w:Math.max(120,Math.round(n.w)),h:Math.max(120,Math.round(n.h)),createdAt:Date.now()};return d.sections.push(f),S(),u(t,200,f)}let jt=/^\/api\/sections\/([^/]+)$/.exec(i);if(jt){let n=d.sections.find(c=>c.id===jt[1]);if(!n)return u(t,404,{});if(e.method==="PATCH"){let c=await L(e);for(let f of["x","y","w","h"])c[f]!=null&&isFinite(c[f])&&(n[f]=Math.round(+c[f]));return c.name&&(n.name=String(c.name).slice(0,80)),S(),u(t,200,n)}if(e.method==="DELETE")return d.sections=d.sections.filter(c=>c.id!==n.id),S(),u(t,200,{ok:!0})}if(e.method==="GET"&&i==="/api/canvas-history")return u(t,200,{items:Bn()});let At=/^\/api\/canvas-history\/(\d+)\/restore$/.exec(i);if(At&&e.method==="POST"){if(!s)return u(t,403,{});if(v.size)return u(t,409,{error:"An agent is working. Restore when it has finished (or stop it)."});let n;try{n=JSON.parse($.readFileSync(h.join(ge,`${At[1]}.json`),"utf8"))}catch{return u(t,404,{error:"That moment is no longer kept."})}Me(),en("before restoring",!0);for(let c of d.frames)_e(c.id);d.frames=n.state.frames,d.sections=n.state.sections,d.strokes=n.state.strokes,d.comments=n.state.comments.map(c=>["working","queued"].includes(c.status)?{...c,status:"canceled",reply:c.reply||"Stopped."}:c);for(let c of d.frames)delete c.building,Ge(c);return ot=qt(d),S(),J({type:"state"}),u(t,200,{ok:!0,frames:d.frames.length})}if(e.method==="POST"&&i==="/api/probe-url"){let n=await L(e),c;try{if(c=new URL(String(n.url)),!/^https?:$/.test(c.protocol))throw 0}catch{return u(t,400,{error:"Not a web address"})}try{let f=await fetch(c,{redirect:"follow",signal:AbortSignal.timeout(8e3),headers:{"user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15",accept:"text/html"}}),m=f.headers.get("x-frame-options")||"",y=/frame-ancestors([^;]*)/i.exec(f.headers.get("content-security-policy")||"")?.[1]?.trim()||"",g=/deny|sameorigin/i.test(m)||y&&!/(^|\s)\*(\s|$)/.test(y);return u(t,200,{embeddable:f.ok&&!g,status:f.status,blocked:g})}catch{return u(t,200,{embeddable:!1,status:0})}}if(e.method==="POST"&&i==="/api/restore"){let n=await L(e);for(let c of n.sections||[])c?.id&&!d.sections.find(f=>f.id===c.id)&&d.sections.push(c);for(let c of n.frames||[])c?.id&&!d.frames.find(f=>f.id===c.id)&&(d.frames.push(c),Ge(c));for(let c of n.strokes||[])c?.id&&!d.strokes.find(f=>f.id===c.id)&&d.strokes.push(c);for(let c of n.comments||[])c?.id&&!d.comments.find(f=>f.id===c.id)&&d.comments.push({...c,status:["working","queued"].includes(c.status)?"canceled":c.status});return S(),u(t,200,{ok:!0})}if(e.method==="POST"&&i==="/api/strokes"){let n=await L(e),c={id:N(8),points:(n.points||[]).slice(0,6e3).map(m=>Math.round(m*10)/10),color:String(n.color||"#E5484D").slice(0,16),width:+n.width||3,frameId:n.frameId||null,createdAt:Date.now()},f=c.frameId&&d.frames.find(m=>m.id===c.frameId);return f&&(c.fx=Number.isFinite(+n.fx)?+n.fx:f.x,c.fy=Number.isFinite(+n.fy)?+n.fy:f.y),d.strokes.push(c),S(),u(t,200,c)}let _t=/^\/api\/strokes\/([^/]+)$/.exec(i);if(_t&&e.method==="DELETE")return d.strokes=d.strokes.filter(n=>n.id!==_t[1]),S(),u(t,200,{ok:!0});if(e.method==="POST"&&i==="/api/update")return!ee||ee.how!=="restart"?u(t,400,{error:"No update to apply here"}):ee.state==="updating"?u(t,200,{ok:!0}):v.size?(ee.state="queued",S(),u(t,200,{ok:!0,queued:!0})):(Tn(),u(t,200,{ok:!0}));if(e.method==="POST"&&i==="/api/settings"){let n=await L(e);if((n.mode==="headless"||n.mode==="inbox")&&(d.agent.mode=n.mode),n.runner&&pe[n.runner]){if(!pe[n.runner].bin)return u(t,400,{error:`${pe[n.runner].label} isn't installed on this computer`});d.agent.runner=n.runner}if(n.proxy!==void 0){if(n.proxy&&!/^http:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/.test(n.proxy))return u(t,400,{error:"proxy must be a local dev server origin, e.g. http://localhost:3000"});d.proxy=n.proxy||null}if(n.chat&&typeof n.chat=="object"){let c=m=>typeof m=="string"&&/^[\w.:\-\[\]]{1,60}$/.test(m)?m:null,f=m=>typeof m=="string"&&m.length<600&&h.isAbsolute(m)?m:void 0;d.agent.chat={runner:n.chat.runner==="codex"?"codex":"claude",model:c(n.chat.model),effort:c(n.chat.effort),permission:c(n.chat.permission),session:c(n.chat.session),cwd:f(n.chat.cwd),transcript:f(n.chat.transcript)}}if(n.pick&&typeof n.pick=="object"){let c=Te(),f=Ee(c),m=(d.agent.pick||={})[c]||={};if("model"in n.pick){if(n.pick.model&&!f.models.includes(n.pick.model))return u(t,400,{error:"No such model"});m.model=n.pick.model||void 0}if("effort"in n.pick){if(n.pick.effort&&!Ee(c).efforts.includes(n.pick.effort))return u(t,400,{error:"No such effort level"});m.effort=n.pick.effort||void 0}}if(n.disallow){d.agent.allow=(d.agent.allow||[]).filter(c=>c!==n.disallow);for(let c of[...q.values()])c.turn?c.restart=!0:$e(c)}return n.name&&(d.name=String(n.name).slice(0,80)),n.hostName!==void 0&&(d.hostName=String(n.hostName).trim().slice(0,32)||void 0),S(),Ot(),u(t,200,{ok:!0})}if(e.method==="POST"&&i==="/api/share/public")try{return await Ao(),u(t,200,yt())}catch(n){return u(t,400,{error:n.message})}if(e.method==="POST"&&i==="/api/share/stop")return me?.kill(),de=null,S(),u(t,200,{ok:!0});if(e.method==="POST"&&i==="/api/share/rotate")return d.token=N(16),S(),u(t,200,yt());if(i.startsWith("/api/cloud")&&!H)return u(t,404,{error:"Email sharing is not available in this version of Loop."});if(i==="/api/cloud"&&e.method==="GET")return u(t,200,await H.status());if(e.method==="POST"&&i==="/api/cloud/session"){let n=await L(e);return u(t,200,{user:await H.setSession(n)})}if(e.method==="POST"&&i==="/api/cloud/signout")return H.signOut(),u(t,200,{ok:!0});if(e.method==="POST"&&i==="/api/cloud/publish")return u(t,200,{link:await H.publish()});if(e.method==="POST"&&i==="/api/cloud/snapshot"){let n=await L(e),c=d.frames.find(m=>m.id===n.frameId);if(!c||c.kind!=="route"||typeof n.html!="string")return u(t,400,{error:"route frame and html required"});let f=await H.publishSnapshot(c,n.html);return H.schedulePush(200),u(t,200,f)}if(e.method==="POST"&&i==="/api/cloud/invite"){let n=await L(e);return u(t,200,await H.invite(n.emails||[],n.role))}if(e.method==="DELETE"&&i.startsWith("/api/cloud/members/"))return await H.removeMember(decodeURIComponent(i.slice(19))),u(t,200,{ok:!0});if(e.method==="POST"&&i==="/api/shutdown")return u(t,200,{ok:!0}),setTimeout(Le,50);u(t,404,{error:"not found"})}catch(a){u(t,500,{error:a.message})}});ze.on("upgrade",(e,t,o)=>{let i=new URL(e.url,"http://x");if(!d.proxy||i.pathname.startsWith(Q)||!vn(e,i))return t.destroy();let r=new URL(d.proxy),s=Vt.connect(+(r.port||80),r.hostname,()=>{let a=`${e.method} ${e.url} HTTP/1.1\r
32
- `;for(let l=0;l<e.rawHeaders.length;l+=2){let p=e.rawHeaders[l],b=e.rawHeaders[l+1];p.toLowerCase()==="host"&&(b=r.host),a+=`${p}: ${b}\r
33
+ `),2e4);e.on("close",()=>{clearInterval(c),Ve.delete(n)});return}if(e.method==="POST"&&i==="/api/presence"){let n=await L(e);return J({type:"presence",id:String(n.id).slice(0,32),name:String(n.name||"Guest").slice(0,32),color:String(n.color||"#888").slice(0,16),x:+n.x,y:+n.y,leave:!!n.leave}),u(t,200,{ok:!0})}if(e.method==="POST"&&i==="/api/comments"){let n=await L(e),c=F(n.frameId);if(!c||!String(n.text||"").trim())return u(t,400,{error:"frame and text required"});let f=typeof n.id=="string"&&/^[A-Za-z0-9_-]{8}$/.test(n.id)&&!d.comments.some(g=>g.id===n.id)?n.id:N(8),p=null;typeof n.annotationPng=="string"&&n.annotationPng.startsWith("data:image/png;base64,")&&(await M.mkdir(h.join(_,"annotations"),{recursive:!0}),p=h.join(_,"annotations",`${f}.png`),await M.writeFile(p,Buffer.from(n.annotationPng.split(",")[1],"base64")));let x=[];for(let g of(Array.isArray(n.attachments)?n.attachments:[]).slice(0,6)){let O=/^data:([\w.+-]+\/[\w.+-]+)?(;[^,]*)?;base64,(.*)$/s.exec(String(g?.data||""));if(!O)continue;let P=String(g.name||"file").replace(/[^\w.\- ]+/g,"_").replace(/^\.+/,"").slice(-80)||"file",A=h.join(_,"attachments",f);await M.mkdir(A,{recursive:!0});let ne=P,ze=1;for(;x.some(Y=>Y.name===ne);)ne=`${ze++}-${P}`;await M.writeFile(h.join(A,ne),Buffer.from(O[3],"base64")),x.push({name:ne,type:O[1]||"application/octet-stream",path:h.join(A,ne),url:`/__loop/attachments/${f}/${encodeURIComponent(ne)}`})}let y=n.parentId?d.comments.find(g=>g.id===n.parentId):null,S={id:f,frameId:c.id,parentId:y?y.parentId||y.id:null,text:String(n.text).slice(0,4e3),anchor:n.anchor||y?.anchor||null,ink:Array.isArray(n.ink)?n.ink.slice(0,20):[],inkStrokes:Array.isArray(n.inkStrokes)?n.inkStrokes.map(String):[],annotation:p,author:{name:String(n.author?.name||(s?"You":"Guest")).slice(0,32),color:String(n.author?.color||"#2F5BFF").slice(0,16),guest:!s},status:s?"queued":"waiting",reply:null,createdAt:Date.now(),...x.length?{attachments:x}:{},...Bt(n.refs).length?{refs:Bt(n.refs)}:{}};d.comments.push(S);for(let g of d.strokes)S.inkStrokes.includes(g.id)&&(g.commentId=S.id);if(s&&uo(S,c),$(),s&&S.kind!=="create"&&(c.kind==="folder"||(c.kind==="route"||c.kind==="url")&&!c.optionOf)){let g=bt(S.text);for(let O=0;O<g;O++)try{if(!(c.kind==="folder"?await Yt(S,c):await xt(S,c)))break}catch{break}}return ce(c.id),u(t,200,S)}let T=/^\/api\/comments\/([^/]+)(?:\/(\w+))?$/.exec(i);if(T){let n=d.comments.find(f=>f.id===T[1]);if(!n)return u(t,404,{error:"no such comment"});let c=T[2];if(e.method==="DELETE"&&!c){if(!s&&!n.author?.guest)return u(t,403,{});let f=new Set([n.id,...d.comments.filter(x=>x.parentId===n.id).map(x=>x.id)]),p=v.get(n.frameId);return p&&f.has(p.commentId)&&(p.canceled=!0,p.child?p.child.kill("SIGTERM"):v.delete(n.frameId)),d.comments=d.comments.filter(x=>!f.has(x.id)),d.strokes=d.strokes.filter(x=>!f.has(x.commentId)),$(),u(t,200,{ok:!0})}if(!s)return u(t,403,{error:"host only"});if(c==="send")return["waiting","error","canceled"].includes(n.status)&&(n.status="queued",n.snapshot=null,n.undone=!1,n.redoSnap=null,$(),ce(n.frameId)),u(t,200,n);if(c==="background"){if(!s)return u(t,403,{});if(n.status!=="queued")return u(t,409,{error:"It already started"});n.background=!0;for(let f of d.comments)f.frameId===n.frameId&&f.status==="queued"&&!f.local&&(f.background=!0);return $(),ce(n.frameId),u(t,200,{ok:!0})}if(c==="steer")return s?yo(n)?u(t,200,{ok:!0}):u(t,409,{error:"Nothing to steer into: the run already finished"}):u(t,403,{});if(c==="cancel"){let f=v.get(n.frameId);return f?.commentId===n.id?(f.canceled=!0,f.child?f.child.kill("SIGTERM"):oe(n,"canceled","Stopped.")):n.status==="queued"&&(n.status="canceled",$()),u(t,200,{ok:!0})}if(c==="undo"){let f=F(n.frameId);if(!n.snapshot||!f||f.kind!=="folder")return u(t,400,{error:"nothing to undo"});if(v.has(f.id))return u(t,409,{error:"The agent is still working on this frame \u2014 wait for it to finish (or stop it) first."});let p=d.comments.filter(y=>y.id!==n.id&&y.frameId===n.frameId&&y.snapshot&&y.status==="done"&&!y.undone&&(y.startedAt||y.createdAt)>(n.startedAt||n.createdAt)),x=await L(e);if(p.length&&!x.force)return u(t,409,{error:"newer",newer:p.length});n.redoSnap=await He(f,`${n.id}-after`),await Ge(f,n.snapshot),n.undone=!0;for(let y of p)y.undone=!0;return $(),J({type:"frame-changed",frameId:f.id}),u(t,200,{ok:!0,reverted:1+p.length})}if(c==="redo"){let f=F(n.frameId);return!n.redoSnap||!n.undone||!f?u(t,400,{error:"nothing to redo"}):v.has(f.id)?u(t,409,{error:"The agent is still working on this frame \u2014 wait for it to finish first."}):(await Ge(f,n.redoSnap),n.undone=!1,$(),J({type:"frame-changed",frameId:f.id}),u(t,200,{ok:!0}))}if(c==="resolve")return n.resolved=!n.resolved,$(),u(t,200,n);if(c==="allow"&&e.method==="POST"){let f=await L(e),p=(f.all?n.blocked||[]:[n.blocked?.[+f.i]]).filter(P=>P&&!P.allowed);if(!p.length)return u(t,400,{error:"Nothing to allow"});let x=d.agent.allow||=[];for(let P of p)x.includes(P.rule)||x.push(P.rule),P.allowed=!0;let y=q.get(n.frameId);y&&!y.turn?Se(y):y&&(y.restart=!0);let S=d.comments.find(P=>P.id===(n.parentId||n.id)),g=p.map(P=>P.tool==="Bash"?`\`${P.label}\``:P.label.charAt(0).toLowerCase()+P.label.slice(1)),O=g.length>1?`${g.slice(0,-1).join(", ")} and ${g[g.length-1]}`:g[0];return d.comments.push({id:N(8),frameId:n.frameId,parentId:S?.id||n.id,anchor:n.anchor,ink:[],inkStrokes:[],auto:"allow",text:`Allowed ${O}. If you still need ${g.length>1?"them":"it"} to finish what was asked, use ${g.length>1?"them":"it"} now and say what changed. If the work is already complete without ${g.length>1?"them":"it"}, change nothing and reply with exactly: Nothing more to do.`,author:S?.author||{name:"You",color:"#2F5BFF"},status:"queued",reply:null,createdAt:Date.now()}),$(),ce(n.frameId),u(t,200,{ok:!0})}if(c==="note"){let f=await L(e),p=String(f.text||"").trim().slice(0,2e3);if(!p)return u(t,400,{error:"text required"});let x={id:N(8),kind:"note",frameId:n.frameId,parentId:n.parentId||n.id,text:p,anchor:n.anchor,author:{name:pe[Oe()].label,agent:!0,color:"#17171A"},model:f.model?String(f.model).slice(0,60):void 0,status:"done",createdAt:Date.now()};return d.comments.push(x),$(),J({type:"frame-changed",frameId:n.frameId}),u(t,200,x)}if(c==="step"){let f=await L(e);return V(n.frameId,String(f.text||"").slice(0,120)),u(t,200,{ok:!0})}if(c==="option"&&e.method==="POST"){let f=await L(e),p=F(n.frameId);if(!p)return u(t,404,{error:"no frame"});if(n.options=n.options||[],p.kind==="folder"){let S=String(f.name||"").trim().slice(0,60),g=n.options.find(O=>O.placeholder&&O.frameId);try{if(g){g.placeholder=!1,g.name=S||g.name;let O=d.frames.find(P=>P.id===g.frameId);O&&S&&(O.name=re(`${g.id} \xB7 ${S}`)),g.madeAt=Date.now(),$()}else g=await Yt(n,p,S||`Option ${String.fromCharCode(65+n.options.length)}`)}catch(O){return u(t,400,{error:O.message})}return u(t,200,{id:g.id,dir:g.dir,root:p.src,frame:!0})}{let S=String(f.name||"").trim().slice(0,60),g=n.options.find(P=>P.placeholder&&P.live&&P.frameId),O=g&&d.frames.find(P=>P.id===g.frameId);if(O)return g.placeholder=!1,S&&(g.name=S,O.name=re(`${g.id} \xB7 ${S}`)),g.madeAt=Date.now(),$(),u(t,200,{id:g.id,dir:g.dir,root:Z,beside:!0})}try{let S=await xt(n,p,String(f.name||"").trim().slice(0,60)||`Option ${String.fromCharCode(65+n.options.length)}`);if(S)return u(t,200,{id:S.id,dir:S.dir,root:Z,beside:!0})}catch(S){return u(t,400,{error:S.message})}if(n.options.length>=6)return u(t,400,{error:"up to 6 options"});let x=String.fromCharCode(65+n.options.length),y=h.join(_,"options",n.id,x);return await M.rm(y,{recursive:!0,force:!0}),await M.mkdir(y,{recursive:!0}),n.options.push({id:x,name:String(f.name||`Option ${x}`).slice(0,60),dir:y}),$(),u(t,200,{id:x,dir:y,root:kn(p)})}if(c==="thumbs"&&e.method==="POST")return Sn(n),u(t,200,{ok:!0});if(c==="preview"&&e.method==="POST"){if(!s)return u(t,403,{});let f=await L(e);try{await at(n,f.option||null)}catch(p){return u(t,400,{error:p.message})}return u(t,200,{ok:!0})}if(c==="clearoptions"&&e.method==="POST")return Zt(n),n.optionsCleared=!0,$(),u(t,200,{ok:!0});if(c==="choose"&&e.method==="POST"){if(!s)return u(t,403,{});let f=await L(e),p=n.options?.find(y=>y.id===f.option&&y.frameId),x=p&&d.frames.find(y=>y.id===p.frameId);if(x&&!p.live){let y=F(n.frameId);if(!y||y.kind!=="folder")return u(t,404,{error:"The original frame is gone."});if(v.has(y.id))return u(t,409,{error:"The agent is still working on the original frame. Wait for it to finish (or stop it) first."});let S=`r${N(7)}`;return await He(y,S)?((y.restores||=[]).push({id:S,at:Date.now()}),await Ge(y,x.src),n.optionChosen=p.id,ue.set(y.id,(ue.get(y.id)||0)+1),$(),J({type:"frame-changed",frameId:y.id}),u(t,200,{ok:!0})):u(t,500,{error:"Could not keep the current version, so nothing was changed."})}if(p?.live){let y=F(n.frameId),S=y&&Qe(y);if(y&&v.has(y.id))return u(t,409,{error:"The agent is still working on this screen. Wait for it to finish (or stop it) first."});try{await at(n,f.option)}catch(g){return u(t,400,{error:g.message})}n.optionChosen=f.option,Zt(n),$();for(let g of d.frames)S&&Qe(g)===S&&J({type:"frame-changed",frameId:g.id});return u(t,200,{ok:!0})}try{await at(n,f.option)}catch(y){return u(t,400,{error:y.message})}return n.optionChosen=f.option,$(),await M.rm(h.join(_,"options",n.id,"_original"),{recursive:!0,force:!0}).catch(()=>{}),u(t,200,{ok:!0})}if(c==="reply"){let f=await L(e);return v.has(n.frameId)||v.set(n.frameId,{commentId:n.id,step:"",startedAt:Date.now()}),f.model&&(n.model=String(f.model).slice(0,60),d.agent.lastModel=n.model),n.startedAt||(n.startedAt=v.get(n.frameId)?.startedAt),oe(n,f.error?"error":"done",String(f.text||"Done.").slice(0,4e3)),u(t,200,n)}}if(!s)return u(t,403,{error:"host only"});if(i==="/api/inbox"){let n=Date.now()+(o.searchParams.get("wait")?25e3:0),c=Fe(),f=!1,p=!!o.searchParams.get("wait");p&&Pe++;let x=!1,y=()=>{x||!p||(x=!0,Pe=Math.max(0,Pe-1),Be=Date.now(),Pe||setTimeout(()=>{Fe()||$(!0)},6200))};for(e.on("close",()=>{f=!0,y()}),t.on("finish",y),Be=Date.now(),c||$(!0);;){if(f)return;Be=Date.now();let S=d.comments.find(g=>g.status==="queued"&&!g.background&&!v.has(g.frameId));if(S){let g=F(S.frameId),O={commentId:S.id,step:"Your chat picked this up",startedAt:Date.now(),inbox:!0};return v.set(g.id,O),setTimeout(()=>{v.get(g.id)===O&&O.step==="Your chat picked this up"&&V(g.id,"Working\u2026")},3e3),S.status="working",$(),[S.snapshot]=await Promise.all([S.snapshot||_t(g,S.id),mo(S)]),u(t,200,{comment:{id:S.id,kind:S.kind||"comment",text:S.text,anchor:S.anchor,ink:S.ink,annotation:S.annotation,attachments:S.attachments},frame:{id:g.id,name:g.name,kind:g.kind,src:g.src,entry:g.entry,w:g.w,h:g.h},prompt:Pt(g,S)})}if(Date.now()>n)return u(t,200,{comment:null});await new Promise(g=>{let O=()=>{clearTimeout(P),mt.delete(O),g()},P=setTimeout(O,2e3);mt.add(O)})}}if(e.method==="POST"&&i==="/api/frames"){let n=await L(e);if(n.image||n.imageUrl){let c,f;try{if(n.image){let g=/^data:(image\/(?:png|jpeg|gif|webp|svg\+xml));base64,(.+)$/s.exec(String(n.image));if(!g)throw new Error("That is not an image Loop can show (PNG, JPEG, GIF, WebP or SVG).");f=g[1],c=Buffer.from(g[2],"base64")}else{let g=new URL(String(n.imageUrl));if(!/^https?:$/.test(g.protocol))throw new Error("Not a web address");let O=await fetch(g,{signal:AbortSignal.timeout(15e3),headers:{accept:"image/*"}});if(f=(O.headers.get("content-type")||"").split(";")[0].trim(),!O.ok||!/^image\/(png|jpeg|gif|webp|svg\+xml)$/.test(f))throw new Error("That address isn't an image Loop can show.");c=Buffer.from(await O.arrayBuffer())}}catch(g){return u(t,400,{error:g.message})}if(c.length>12e6)return u(t,400,{error:"That image is too big (over 12 MB)."});let p={"image/png":"png","image/jpeg":"jpg","image/gif":"gif","image/webp":"webp","image/svg+xml":"svg"}[f],x=String(n.name||"Image").replace(/\.[a-z0-9]+$/i,"").slice(0,60)||"Image",y=we(x);k.mkdirSync(y,{recursive:!0}),k.writeFileSync(h.join(y,`image.${p}`),c),k.writeFileSync(h.join(y,"index.html"),`<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="loop-image" content="image.${p}"><title>${Ie(x)}</title><style>html,body{margin:0;height:100%;background:transparent}img{display:block;width:100%;height:100%;object-fit:contain}</style></head><body><img src="image.${p}" alt="${Ie(x)}"></body></html>`);let S=W({src:y,name:re(x),w:Math.max(40,Math.round(+n.w||800)),h:Math.max(40,Math.round(+n.h||600)),x:n.x,y:n.y});return S.image=!0,S.fill="none",$(),u(t,200,S)}if(n.prompt){let c=n.name||n.prompt.split(/[.\n]/)[0].slice(0,40),f=Ht(c),p=W({src:f,name:c,w:n.w,h:n.h,x:n.x,y:n.y});return typeof n.source=="string"&&/^https?:\/\//.test(n.source)&&(p.source=n.source.slice(0,2e3)),d.comments.push({id:N(8),kind:"create",frameId:p.id,text:n.prompt,author:{name:"You",color:"#2F5BFF"},status:"queued",createdAt:Date.now()}),$(),ce(p.id),u(t,200,{...p,dir:f})}if(n.new){let c=Ht(n.name||"Untitled",!!n.blank);typeof n.html=="string"&&n.html.length<4e6&&k.writeFileSync(h.join(c,"index.html"),n.html);let f=n.x==null&&xo();if(f)return u(t,200,{...vo(f.c,f.f,c,n.name),dir:c});let p=W({...n,src:c});return n.parent&&d.frames.some(x=>x.id===n.parent&&x.id!==p.id)&&(p.parent=n.parent,$()),(n.fill==="none"||/^#[0-9a-f]{6}$/i.test(n.fill||""))&&(p.fill=n.fill.toLowerCase(),$()),u(t,200,{...p,dir:c})}return n.route!=null?u(t,200,W(n)):n.src?!/^https?:\/\//.test(n.src)&&!k.existsSync(h.resolve(Z,n.src))?u(t,400,{error:`No such file or folder: ${n.src}`}):u(t,200,W(n)):u(t,400,{error:"src, route, new or prompt required"})}let E=/^\/api\/frames\/([^/]+)\/versions(\/restore|\/duplicate)?$/.exec(i);if(E&&e.method==="POST"){let n=d.frames.find(g=>g.id===E[1]);if(!n||n.kind!=="folder")return u(t,400,{error:"Only static frames keep versions."});let c=encodeURIComponent(n.entry||"index.html");if(!E[2]){let g=[...d.comments.filter(A=>A.frameId===n.id&&A.snapshot&&!A.snapThumb&&k.existsSync(A.snapshot)),...(n.restores||[]).filter(A=>!A.thumb)].slice(-14),O=async A=>{try{let ne=await he(n,`http://127.0.0.1:${K}/__loop/hist/${A.id}/${c}`);A["frameId"in A?"snapThumb":"thumb"]=`/__loop/shots/${h.basename(ne)}`,$()}catch{}},[P]=await Promise.all([he(n).catch(()=>null),...g.map(O)]);return u(t,200,{now:P?`/__loop/shots/${h.basename(P)}`:null})}let f=await L(e),p=String(f.id||"");if(!/^[A-Za-z0-9_-]+$/.test(p))return u(t,400,{error:"no such version"});let x=d.comments.some(g=>g.id===p&&g.frameId===n.id&&g.snapshot)||(n.restores||[]).some(g=>g.id===p),y=h.join(_,"history",p);if(!x||!k.existsSync(y))return u(t,404,{error:"That version is no longer kept."});if(E[2]==="/duplicate"){let g=re(String(f.name||`${n.name} \xB7 earlier version`).slice(0,80)),O=we(g);return await fe(y,O),u(t,200,W({src:h.join(O,n.entry||"index.html"),name:g,w:n.w,h:n.h,near:n.id}))}if(v.has(n.id))return u(t,409,{error:"The agent is still working on this frame. Wait for it to finish (or stop it) first."});let S=`r${N(7)}`;return await He(n,S)?((n.restores||=[]).push({id:S,at:Date.now()}),await Ge(n,y),ue.set(n.id,(ue.get(n.id)||0)+1),$(),J({type:"frame-changed",frameId:n.id}),u(t,200,{ok:!0})):u(t,500,{error:"Could not keep the current version, so nothing was changed."})}let j=/^\/api\/frames\/([^/]+)\/(source|edit)$/.exec(i);if(j){let n=d.frames.find(p=>p.id===j[1]);if(!n||!s)return u(t,404,{error:"no such frame"});if(n.kind!=="folder")return u(t,400,{error:"Only static frames can be edited directly."});let c=h.join(n.src,n.entry||"index.html"),f;try{f=await M.readFile(c,"utf8")}catch{return u(t,404,{error:"The frame has no page to edit."})}if(j[2]==="source"&&e.method==="GET")return u(t,200,{text:f});if(j[2]==="edit"&&e.method==="POST"){if(v.has(n.id))return u(t,409,{error:"The agent is working on this frame. Try again when it is done."});let p=await L(e),x=(Array.isArray(p.edits)?p.edits:[]).map(A=>({a:A.a|0,b:A.b|0,before:String(A.before??""),text:String(A.text??"")})).sort((A,ne)=>A.a-ne.a);if(!x.length||x.length>40)return u(t,400,{error:"nothing to change"});let y=0;for(let A of x){if(A.a<y||A.b<A.a||A.b>f.length||f.slice(A.a,A.b)!==A.before)return u(t,409,{error:"The page changed since you started editing. Try again."});y=A.b}let S=Gt.get(n.id)||0,g=Date.now();if(g-S>9e4){let A=`e${N(7)}`;await He(n,A)&&(n.restores||=[]).push({id:A,at:g,what:String(p.what||"Edited by hand").slice(0,80)})}Gt.set(n.id,g);let O="",P=0;for(let A of x)O+=f.slice(P,A.a)+A.text,P=A.b;return O+=f.slice(P),await M.writeFile(c,O),$(),u(t,200,{ok:!0})}}let R=/^\/api\/frames\/([^/]+)\/warm$/.exec(i);if(R&&e.method==="POST"){let n=F(R[1]);return tt&&n?.kind!=="canvas"&&bn().catch(()=>{}),u(t,200,{warm:!!(n&&No(n))})}let C=/^\/api\/frames\/([^/]+)\/sizes$/.exec(i);if(C&&e.method==="POST"){if(!s)return u(t,403,{});let n=F(C[1]);if(!n||!["route","url","folder"].includes(n.kind)||n.image)return u(t,400,{error:"Sizes work for screens: an app page, a web page or a static page."});let c=d.sections.find(Y=>Y.sizesOf===n.id&&d.frames.some($e=>$e.sizeOf===n.id));if(c)return u(t,200,{section:c.id,frames:d.frames.filter(Y=>Y.sizeOf===n.id).map(Y=>Y.id),existing:!0});let f=et(n),p=Object.entries(ye).filter(([Y])=>Y!==f),x=60,y=48,S=72,g=p.reduce((Y,[,[$e]])=>Y+$e,0)+x*(p.length-1)+y*2,O=Math.max(...p.map(([,[,Y]])=>Y))+S+y,P={x:n.x+n.w+x+20,y:n.y-S,w:g,h:O};St(P,n);let A={id:N(6),name:`Sizes \xB7 ${n.name}`,...P,sizesOf:n.id,createdAt:Date.now()};d.sections.push(A);let ne=P.x+y,ze=[];for(let[Y,[$e,Hn]]of p){let Gn=re(`${n.name} \xB7 ${Y}`),ot={w:$e,h:Hn,x:ne,y:n.y,name:Gn},Ft=n.kind==="route"?W({route:n.src,dup:!0,...ot}):n.kind==="url"?W({src:n.src,...ot}):W({src:h.join(n.src,n.entry||"index.html"),...ot});Ft.sizeOf=n.id,ze.push(Ft.id),ne+=$e+x}return $(),u(t,200,{section:A.id,frames:ze})}let D=/^\/api\/frames\/([^/]+)\/asset$/.exec(i);if(D&&e.method==="POST"){if(!s)return u(t,403,{});let n=F(D[1]);if(!n||n.kind!=="folder")return u(t,400,{error:"Only static frames keep their own files."});let c=await L(e),f=/^data:(image\/(?:png|jpeg|gif|webp|svg\+xml));base64,(.+)$/s.exec(String(c.image||""));if(!f)return u(t,400,{error:"That is not an image Loop can use (PNG, JPEG, GIF, WebP or SVG)."});let p={"image/png":"png","image/jpeg":"jpg","image/gif":"gif","image/webp":"webp","image/svg+xml":"svg"}[f[1]],x=hn(String(c.name||"image").replace(/\.[a-z0-9]+$/i,""))||"image",y=h.join(n.src,"assets");k.mkdirSync(y,{recursive:!0});let S=`${x}.${p}`,g=2;for(;k.existsSync(h.join(y,S));)S=`${x}-${g++}.${p}`;return k.writeFileSync(h.join(y,S),Buffer.from(f[2],"base64")),u(t,200,{path:`assets/${S}`})}let z=/^\/api\/frames\/([^/]+)\/shot$/.exec(i);if(z&&e.method==="POST"){let n=d.frames.find(p=>p.id===z[1]);if(!n||!s)return u(t,404,{error:"no such frame"});let c=await L(e),f;if(c.option){let[p,x]=String(c.option).split(":"),y=d.comments.find(g=>g.id===p);if(!y?.options?.some(g=>g.id===x))return u(t,400,{error:"no such option"});let S=d.frames.find(g=>g.id===y.options.find(O=>O.id===x)?.frameId);if(S)try{return u(t,200,{path:await he(S)})}catch(g){return u(t,500,{error:g.message})}if(n.kind!=="folder")return u(t,400,{error:"Options on app screens can't be screenshotted before they're applied. Describe them clearly instead."});f=`http://127.0.0.1:${K}/__loop/optview/${p}/${x}/${encodeURIComponent(n.entry||"index.html")}`}v.get(n.id)?.inbox&&V(n.id,"Looking at the screen");try{return u(t,200,{path:await he(n,f)})}catch(p){return u(t,500,{error:p.message})}}let U=/^\/api\/frames\/([^/]+)\/freeze$/.exec(i);if(U&&e.method==="POST"){let n=d.frames.find(S=>S.id===U[1]);if(!n)return u(t,404,{error:"no such frame"});let c=await L(e);if(typeof c.html!="string"||!c.html)return u(t,400,{error:"nothing captured"});let f=re(`${n.name.replace(/ · (frozen|draft)( \d+)?$/,"")} \xB7 ${c.editable?"draft":"frozen"}`),p=we(f);await M.mkdir(p,{recursive:!0}),await M.writeFile(h.join(p,"index.html"),c.html);let x=typeof c.from=="string"&&c.from.startsWith("/")&&!c.from.startsWith("/__loop/")?c.from.slice(0,2e3):n.kind==="frozen"?n.from:n.kind==="route"?n.src:void 0,y={id:N(6),name:f,kind:c.editable?"folder":"frozen",src:p,entry:"index.html",from:x,frozenAt:Date.now(),...c.editable?{draftOf:x||n.src}:{},w:n.w,h:n.h,keyboard:n.keyboard,...c.x!=null&&c.y!=null?{x:+c.x,y:+c.y}:pt(n.w,n.h,n.id),createdAt:Date.now()};return d.frames.push(y),c.editable&&Ee(y),$(),u(t,200,y)}let te=/^\/api\/frames\/([^/]+)\/duplicate$/.exec(i);if(te&&e.method==="POST"){let n=d.frames.find(g=>g.id===te[1]);if(!n)return u(t,404,{error:"no such frame"});let c=await L(e),f=n.kind==="route"&&typeof c.route=="string"&&c.route.startsWith("/")&&!c.route.startsWith("/__loop/")?c.route.slice(0,2e3):null,p=f&&f!==n.src,x=re(p?ao(f):`${n.name.replace(/ copy( \d+)?$/,"")} copy`),y=c.x!=null&&c.y!=null?{x:c.x,y:c.y}:{near:n.id},S;if(n.kind==="folder"||n.kind==="frozen"){let g=we(x);await fe(n.src,g),S=W({src:h.join(g,n.entry),name:x,w:n.w,h:n.h,...y}),n.kind==="frozen"&&(De(S.id),Object.assign(S,{kind:"frozen",from:n.from,frozenAt:n.frozenAt}),$())}else n.kind==="route"?S=W({route:f||n.src,name:x,w:n.w,h:n.h,dup:!0,...y}):S=W({src:n.src,name:x,w:n.w,h:n.h,...y});return u(t,200,S)}let je=/^\/api\/frames\/([^/]+)$/.exec(i);if(je){let n=d.frames.find(c=>c.id===je[1]);if(!n)return u(t,404,{});if(e.method==="PATCH"){let c=await L(e);for(let f of["x","y","w","h"])c[f]!=null&&isFinite(c[f])&&(n[f]=Math.round(+c[f]));return c.name&&(n.name=String(c.name).slice(0,80)),typeof c.keyboard=="boolean"&&(n.keyboard=c.keyboard||void 0),"fill"in c&&(n.fill=c.fill==="none"?"none":typeof c.fill=="string"&&/^#[0-9a-f]{6}$/i.test(c.fill)?c.fill.toLowerCase():void 0),"parent"in c&&(n.parent=c.parent&&c.parent!==n.id&&d.frames.some(f=>f.id===c.parent)?c.parent:void 0),$(),u(t,200,n)}if(e.method==="DELETE"){let c=v.get(n.id);return c&&(c.canceled=!0,c.child?.kill("SIGTERM"),v.delete(n.id)),d.frames=d.frames.filter(f=>f.id!==n.id),d.comments=d.comments.filter(f=>f.frameId!==n.id),De(n.id),$(),u(t,200,{ok:!0})}}if(e.method==="POST"&&i==="/api/sections"){let n=await L(e);if(![n.x,n.y,n.w,n.h].every(Number.isFinite))return u(t,400,{error:"x, y, w and h required"});let c=d.sections.length+1;for(;d.sections.some(p=>p.name===`Section ${c}`);)c++;let f={id:N(6),name:String(n.name||`Section ${c}`).slice(0,80),x:Math.round(n.x),y:Math.round(n.y),w:Math.max(120,Math.round(n.w)),h:Math.max(120,Math.round(n.h)),createdAt:Date.now()};return d.sections.push(f),$(),u(t,200,f)}let Ct=/^\/api\/sections\/([^/]+)$/.exec(i);if(Ct){let n=d.sections.find(c=>c.id===Ct[1]);if(!n)return u(t,404,{});if(e.method==="PATCH"){let c=await L(e);for(let f of["x","y","w","h"])c[f]!=null&&isFinite(c[f])&&(n[f]=Math.round(+c[f]));return c.name&&(n.name=String(c.name).slice(0,80)),$(),u(t,200,n)}if(e.method==="DELETE")return d.sections=d.sections.filter(c=>c.id!==n.id),$(),u(t,200,{ok:!0})}if(e.method==="GET"&&i==="/api/site-icon"){let n="";try{n=new URL(o.searchParams.get("u")||"").hostname}catch{}if(!n)return t.writeHead(404),t.end();let c=await io(n);return c?(t.writeHead(200,{"content-type":c.type,"cache-control":"max-age=86400"}),t.end(c.buf)):(t.writeHead(404),t.end())}if(e.method==="GET"&&i==="/api/resolve"){let n=F(o.searchParams.get("f")||"");return n?u(t,200,{frame:{id:n.id,name:n.name,kind:n.kind,where:yt(n),w:n.w,h:n.h,src:n.src,entry:n.entry},selector:o.searchParams.get("e")||null,label:o.searchParams.get("n")||null}):u(t,404,{error:"That frame is no longer on this canvas."})}if(e.method==="GET"&&i==="/api/canvas-history")return u(t,200,{items:to()});let Mt=/^\/api\/canvas-history\/(\d+)\/restore$/.exec(i);if(Mt&&e.method==="POST"){if(!s)return u(t,403,{});if(v.size)return u(t,409,{error:"An agent is working. Restore when it has finished (or stop it)."});let n;try{n=JSON.parse(k.readFileSync(h.join(ge,`${Mt[1]}.json`),"utf8"))}catch{return u(t,404,{error:"That moment is no longer kept."})}Ne(),dn("before restoring",!0);for(let c of d.frames)De(c.id);d.frames=n.state.frames,d.sections=n.state.sections,d.strokes=n.state.strokes,d.comments=n.state.comments.map(c=>["working","queued"].includes(c.status)?{...c,status:"canceled",reply:c.reply||"Stopped."}:c);for(let c of d.frames)delete c.building,Ee(c);return lt=cn(d),$(),J({type:"state"}),u(t,200,{ok:!0,frames:d.frames.length})}if(e.method==="POST"&&i==="/api/probe-url"){let n=await L(e),c;try{if(c=new URL(String(n.url)),!/^https?:$/.test(c.protocol))throw 0}catch{return u(t,400,{error:"Not a web address"})}try{let f=await fetch(c,{redirect:"follow",signal:AbortSignal.timeout(8e3),headers:{"user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15",accept:"text/html"}}),p=f.headers.get("x-frame-options")||"",x=/frame-ancestors([^;]*)/i.exec(f.headers.get("content-security-policy")||"")?.[1]?.trim()||"",y=/deny|sameorigin/i.test(p)||x&&!/(^|\s)\*(\s|$)/.test(x);return u(t,200,{embeddable:f.ok&&!y,status:f.status,blocked:y})}catch{return u(t,200,{embeddable:!1,status:0})}}if(e.method==="POST"&&i==="/api/restore"){let n=await L(e);for(let c of n.sections||[])c?.id&&!d.sections.find(f=>f.id===c.id)&&d.sections.push(c);for(let c of n.frames||[])c?.id&&!d.frames.find(f=>f.id===c.id)&&(d.frames.push(c),Ee(c));for(let c of n.strokes||[])c?.id&&!d.strokes.find(f=>f.id===c.id)&&d.strokes.push(c);for(let c of n.comments||[])c?.id&&!d.comments.find(f=>f.id===c.id)&&d.comments.push({...c,status:["working","queued"].includes(c.status)?"canceled":c.status});return $(),u(t,200,{ok:!0})}if(e.method==="POST"&&i==="/api/strokes"){let n=await L(e),c={id:N(8),points:(n.points||[]).slice(0,6e3).map(p=>Math.round(p*10)/10),color:String(n.color||"#E5484D").slice(0,16),width:+n.width||3,frameId:n.frameId||null,createdAt:Date.now()},f=c.frameId&&d.frames.find(p=>p.id===c.frameId);return f&&(c.fx=Number.isFinite(+n.fx)?+n.fx:f.x,c.fy=Number.isFinite(+n.fy)?+n.fy:f.y),d.strokes.push(c),$(),u(t,200,c)}let Lt=/^\/api\/strokes\/([^/]+)$/.exec(i);if(Lt&&e.method==="DELETE")return d.strokes=d.strokes.filter(n=>n.id!==Lt[1]),$(),u(t,200,{ok:!0});if(e.method==="POST"&&i==="/api/update")return!ee||ee.how!=="restart"?u(t,400,{error:"No update to apply here"}):ee.state==="updating"?u(t,200,{ok:!0}):v.size?(ee.state="queued",$(),u(t,200,{ok:!0,queued:!0})):(Ln(),u(t,200,{ok:!0}));if(e.method==="POST"&&i==="/api/settings"){let n=await L(e);if((n.mode==="headless"||n.mode==="inbox")&&(d.agent.mode=n.mode),n.runner&&pe[n.runner]){if(!pe[n.runner].bin)return u(t,400,{error:`${pe[n.runner].label} isn't installed on this computer`});d.agent.runner=n.runner}if(n.proxy!==void 0){if(n.proxy&&!/^http:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/.test(n.proxy))return u(t,400,{error:"proxy must be a local dev server origin, e.g. http://localhost:3000"});d.proxy=n.proxy||null}if(n.chat&&typeof n.chat=="object"){let c=p=>typeof p=="string"&&/^[\w.:\-\[\]]{1,60}$/.test(p)?p:null,f=p=>typeof p=="string"&&p.length<600&&h.isAbsolute(p)?p:void 0;d.agent.chat={runner:n.chat.runner==="codex"?"codex":"claude",model:c(n.chat.model),effort:c(n.chat.effort),permission:c(n.chat.permission),session:c(n.chat.session),cwd:f(n.chat.cwd),transcript:f(n.chat.transcript)}}if(n.pick&&typeof n.pick=="object"){let c=Oe(),f=Me(c),p=(d.agent.pick||={})[c]||={};if("model"in n.pick){if(n.pick.model&&!f.models.includes(n.pick.model))return u(t,400,{error:"No such model"});p.model=n.pick.model||void 0}if("effort"in n.pick){if(n.pick.effort&&!Me(c).efforts.includes(n.pick.effort))return u(t,400,{error:"No such effort level"});p.effort=n.pick.effort||void 0}}if(n.disallow){d.agent.allow=(d.agent.allow||[]).filter(c=>c!==n.disallow);for(let c of[...q.values()])c.turn?c.restart=!0:Se(c)}return n.name&&(d.name=String(n.name).slice(0,80)),n.hostName!==void 0&&(d.hostName=String(n.hostName).trim().slice(0,32)||void 0),$(),Dt(),u(t,200,{ok:!0})}if(e.method==="POST"&&i==="/api/share/public")try{return await zo(),u(t,200,It())}catch(n){return u(t,400,{error:n.message})}if(e.method==="POST"&&i==="/api/share/stop")return me?.kill(),se=null,$(),u(t,200,{ok:!0});if(e.method==="POST"&&i==="/api/share/rotate")return d.token=N(16),$(),u(t,200,It());if(i.startsWith("/api/cloud")&&!H)return u(t,404,{error:"Email sharing is not available in this version of Loop."});if(i==="/api/cloud"&&e.method==="GET")return u(t,200,await H.status());if(e.method==="POST"&&i==="/api/cloud/session"){let n=await L(e);return u(t,200,{user:await H.setSession(n)})}if(e.method==="POST"&&i==="/api/cloud/signout")return H.signOut(),u(t,200,{ok:!0});if(e.method==="POST"&&i==="/api/cloud/publish")return u(t,200,{link:await H.publish()});if(e.method==="POST"&&i==="/api/cloud/snapshot"){let n=await L(e),c=d.frames.find(p=>p.id===n.frameId);if(!c||c.kind!=="route"||typeof n.html!="string")return u(t,400,{error:"route frame and html required"});let f=await H.publishSnapshot(c,n.html);return H.schedulePush(200),u(t,200,f)}if(e.method==="POST"&&i==="/api/cloud/invite"){let n=await L(e);return u(t,200,await H.invite(n.emails||[],n.role))}if(e.method==="DELETE"&&i.startsWith("/api/cloud/members/"))return await H.removeMember(decodeURIComponent(i.slice(19))),u(t,200,{ok:!0});if(e.method==="POST"&&i==="/api/shutdown")return u(t,200,{ok:!0}),setTimeout(We,50);u(t,404,{error:"not found"})}catch(a){u(t,500,{error:a.message})}});Ke.on("upgrade",(e,t,o)=>{let i=new URL(e.url,"http://x");if(!d.proxy||i.pathname.startsWith(X)||!Cn(e,i))return t.destroy();let r=new URL(d.proxy),s=sn.connect(+(r.port||80),r.hostname,()=>{let a=`${e.method} ${e.url} HTTP/1.1\r
34
+ `;for(let l=0;l<e.rawHeaders.length;l+=2){let m=e.rawHeaders[l],b=e.rawHeaders[l+1];m.toLowerCase()==="host"&&(b=r.host),a+=`${m}: ${b}\r
33
35
  `}s.write(a+`\r
34
- `),o?.length&&s.write(o),s.pipe(t),t.pipe(s)});s.on("error",()=>t.destroy()),t.on("error",()=>s.destroy())});Ln&&(H={createCloud:()=>null}.createCloud({state:d,changed:S,pump:se,broadcast:J,log:e=>console.log(e)}));var Bt=!1;setInterval(()=>{let e=Ye();e!==Bt&&(Bt=e,S(!0));for(let t of[...q.values()])!t.turn&&t.frameId!==Se&&!d.frames.some(o=>o.id===t.frameId)&&$e(t);if(d.agent.mode==="inbox"&&!e){for(let[t,o]of v)if(o.inbox){v.delete(t);let i=d.comments.find(r=>r.id===o.commentId);i&&i.status==="working"&&(i.status="queued",S())}if(d.agent.mode==="inbox"&&Jt>=0){let t=!1;for(let o of d.comments)o.status==="queued"&&!o.background&&Date.now()-(o.queuedAt||o.createdAt)>Jt&&R(o.frameId)&&(o.background=!0,t=!0);t&&(S(),Ot())}}},5e3);var Jt=Number(process.env.LOOP_CHAT_WAIT??15e3),kt=(()=>{try{return JSON.parse($.readFileSync(h.join(le,"package.json"),"utf8")).version||null}catch{return null}})(),bt=process.env.LOOP_UPDATE_TEST?"test":/[\\/]_npx[\\/]/.test(le)?"npx":/[\\/]node_modules[\\/]/.test(le)?"global":null,ee=null,Po=(e,t)=>{let o=String(e).split(".").map(Number),i=String(t).split(".").map(Number);for(let r=0;r<3;r++)if((o[r]||0)!==(i[r]||0))return(o[r]||0)>(i[r]||0);return!1};async function In(){if(!(!bt||!kt||ee?.state==="updating"))try{let e=process.env.LOOP_UPDATE_TEST||(await(await fetch("https://registry.npmjs.org/loop-canvas/latest",{signal:AbortSignal.timeout(8e3),headers:{accept:"application/json"}})).json()).version;e&&Po(e,kt)&&ee?.latest!==e&&(ee={latest:e,how:bt==="npx"?"next-start":"restart",state:"available"},S())}catch{}}setTimeout(In,4e3).unref?.();setInterval(In,3*36e5).unref?.();async function Tn(){if(ee.state="updating",delete ee.error,S(),bt==="global")try{await new Promise((e,t)=>Xt("npm",["install","-g",`loop-canvas@${ee.latest}`],{timeout:18e4},(o,i,r)=>o?t(new Error(String(r||o.message).trim().split(`
35
- `).slice(-3).join(" "))):e()))}catch(e){ee.state="failed",ee.error=e.message.slice(0,300),S();return}Me(),Oe(process.execPath,[h.join(le,"server.mjs"),"--dir",A,"--port",String(V)],{detached:!0,stdio:"ignore",env:{...process.env,LOOP_SAME_PORT:"1",LOOP_UPDATE_TEST:""}}).unref(),setTimeout(Le,150)}var On=["server.mjs","cli.mjs","public/app.js","public/app.css","public/index.html"].map(e=>h.join(le,e)),jn=()=>On.map(e=>{try{return Math.round($.statSync(e).mtimeMs)}catch{return 0}}).join("."),An=jn(),_n=!1,Kt=null;for(let e of On)try{$.watch(e,()=>{clearTimeout(Kt),Kt=setTimeout(()=>{jn()!==An&&(_n=!0,Pn())},1500)}).unref?.()}catch{}function Pn(){if(ee?.state==="queued"&&!v.size)return Tn();!_n||v.size||nt||(nt=!0,Xt(process.execPath,["--check",h.join(le,"server.mjs")],e=>{if(e||v.size){nt=!1;return}console.log("Loop \u25B8 restarting into the new version"),Me(),Oe(process.execPath,[h.join(le,"server.mjs"),"--dir",A,"--port",String(V)],{detached:!0,stdio:"ignore",env:{...process.env,LOOP_SAME_PORT:"1",LOOP_UPDATE_TEST:""}}).unref(),setTimeout(Le,150)}))}var nt=!1;function St(e,t=20){ze.once("error",o=>{if(o.code==="EADDRINUSE"&&process.env.LOOP_SAME_PORT&&t>0)return setTimeout(()=>St(e,t-1),500);o.code==="EADDRINUSE"&&t>0?St(e+1,t-1):(console.error(o),process.exit(1))}),ze.listen(e,"0.0.0.0",()=>{V=ze.address().port,$.writeFileSync(Qt,JSON.stringify({port:V,pid:process.pid})),console.log(`Loop \u25B8 http://localhost:${V} (${A})`),Ot(),go().catch(()=>{})})}function Le(){try{$.rmSync(Qt,{force:!0})}catch{}me?.kill(),ut();for(let e of q.values())try{e.child.kill("SIGTERM")}catch{}for(let e of v.values())e.child?.kill("SIGTERM");clearTimeout(it),Me(),process.exit(0)}process.on("SIGINT",Le);process.on("SIGTERM",Le);St(+Zt.port||4848);function Eo(e){let t={};for(let o=0;o<e.length;o++)if(e[o].startsWith("--")){let i=e[o].slice(2);t[i]=e[o+1]&&!e[o+1].startsWith("--")?e[++o]:!0}return t}function ke(e){let t=[...(process.env.PATH||"").split(":"),h.join(ae.homedir(),".local/bin"),"/opt/homebrew/bin","/usr/local/bin"];for(let o of t){if(!o)continue;let i=h.join(o,e);try{return $.accessSync(i,$.constants.X_OK),i}catch{}}return null}export{ye as PRESETS};
36
+ `),o?.length&&s.write(o),s.pipe(t),t.pipe(s)});s.on("error",()=>t.destroy()),t.on("error",()=>s.destroy())});Jn&&(H={createCloud:()=>null}.createCloud({state:d,changed:$,pump:ce,broadcast:J,log:e=>console.log(e)}));var qt=!1,en="";setInterval(()=>{let e=JSON.stringify(yn()||null);e!==en&&(en=e,$(!0))},15e3).unref?.();setInterval(()=>{let e=Fe();e!==qt&&(qt=e,$(!0));for(let t of[...q.values()])!t.turn&&t.frameId!==be&&!d.frames.some(o=>o.id===t.frameId)&&Se(t);if(d.agent.mode==="inbox"&&!e){for(let[t,o]of v)if(o.inbox){v.delete(t);let i=d.comments.find(r=>r.id===o.commentId);i&&i.status==="working"&&(i.status="queued",$())}if(d.agent.mode==="inbox"&&tn>=0){let t=!1;for(let o of d.comments)o.status==="queued"&&!o.background&&Date.now()-(o.queuedAt||o.createdAt)>tn&&F(o.frameId)&&(o.background=!0,t=!0);t&&($(),Dt())}}},5e3);var tn=Number(process.env.LOOP_CHAT_WAIT??15e3),Ot=(()=>{try{return JSON.parse(k.readFileSync(h.join(le,"package.json"),"utf8")).version||null}catch{return null}})(),Tt=process.env.LOOP_UPDATE_TEST?"test":/[\\/]_npx[\\/]/.test(le)?"npx":/[\\/]node_modules[\\/]/.test(le)?"global":null,ee=null,Go=(e,t)=>{let o=String(e).split(".").map(Number),i=String(t).split(".").map(Number);for(let r=0;r<3;r++)if((o[r]||0)!==(i[r]||0))return(o[r]||0)>(i[r]||0);return!1};async function Mn(){if(!(!Tt||!Ot||ee?.state==="updating"))try{let e=process.env.LOOP_UPDATE_TEST||(await(await fetch("https://registry.npmjs.org/loop-canvas/latest",{signal:AbortSignal.timeout(8e3),headers:{accept:"application/json"}})).json()).version;e&&Go(e,Ot)&&ee?.latest!==e&&(ee={latest:e,how:Tt==="npx"?"next-start":"restart",state:"available"},$())}catch{}}setTimeout(Mn,4e3).unref?.();setInterval(Mn,3*36e5).unref?.();async function Ln(){if(ee.state="updating",delete ee.error,$(),Tt==="global")try{await new Promise((e,t)=>rn("npm",["install","-g",`loop-canvas@${ee.latest}`],{timeout:18e4},(o,i,r)=>o?t(new Error(String(r||o.message).trim().split(`
37
+ `).slice(-3).join(" "))):e()))}catch(e){ee.state="failed",ee.error=e.message.slice(0,300),$();return}Ne(),Te(process.execPath,[h.join(le,"server.mjs"),"--dir",_,"--port",String(K)],{detached:!0,stdio:"ignore",env:{...process.env,LOOP_SAME_PORT:"1",LOOP_UPDATE_TEST:""}}).unref(),setTimeout(()=>We(!0),150)}var Fn=["server.mjs","cli.mjs","public/app.js","public/app.css","public/index.html"].map(e=>h.join(le,e)),Rn=()=>Fn.map(e=>{try{return Math.round(k.statSync(e).mtimeMs)}catch{return 0}}).join("."),Nn=Rn(),Wn=!1,nn=null;for(let e of Fn)try{k.watch(e,()=>{clearTimeout(nn),nn=setTimeout(()=>{Rn()!==Nn&&(Wn=!0,zn())},1500)}).unref?.()}catch{}function zn(){if(ee?.state==="queued"&&!v.size)return Ln();!Wn||v.size||dt||(dt=!0,rn(process.execPath,["--check",h.join(le,"server.mjs")],e=>{if(e||v.size){dt=!1;return}console.log("Loop \u25B8 restarting into the new version"),Ne(),Te(process.execPath,[h.join(le,"server.mjs"),"--dir",_,"--port",String(K)],{detached:!0,stdio:"ignore",env:{...process.env,LOOP_SAME_PORT:"1",LOOP_UPDATE_TEST:""}}).unref(),setTimeout(()=>We(!0),150)}))}var dt=!1;function Ye(e,t=20){Ke.once("error",o=>{if(o.code==="EADDRINUSE"&&process.env.LOOP_SAME_PORT&&t>0)return setTimeout(()=>Ye(e,t-1),500);if(o.code==="EADDRINUSE"&&process.env.LOOP_SAME_PORT)return delete process.env.LOOP_SAME_PORT,Ye(e+1,20);o.code==="EADDRINUSE"&&t>0?Ye(e+1,t-1):(console.error(o),process.exit(1))}),Ke.listen(e,"0.0.0.0",()=>{K=Ke.address().port,k.writeFileSync(Xe,JSON.stringify({port:K,pid:process.pid})),console.log(`Loop \u25B8 http://localhost:${K} (${_})`),Dt(),jo().catch(()=>{})})}function We(e=!1){try{e?k.writeFileSync(Xe,JSON.stringify({port:K,pid:process.pid,restarting:Date.now()})):k.rmSync(Xe,{force:!0})}catch{}me?.kill(),kt();for(let t of q.values())try{t.child.kill("SIGTERM")}catch{}for(let t of v.values())t.child?.kill("SIGTERM");clearTimeout(ft),Ne(),process.exit(0)}process.on("SIGINT",We);process.on("SIGTERM",We);(async()=>{if(!process.env.LOOP_SAME_PORT){let e=null;try{e=JSON.parse(k.readFileSync(Xe,"utf8"))}catch{}let t=o=>{try{return process.kill(o,0),!0}catch{return!1}};if(e?.pid&&e.pid!==process.pid&&t(e.pid))for(let o=0;o<(e.restarting?30:3);o++){try{(await fetch(`http://127.0.0.1:${e.port}/__loop/api/ping`,{signal:AbortSignal.timeout(800)})).ok&&(console.log(`Loop \u25B8 this canvas is already served at http://localhost:${e.port}/__loop/`),process.exit(0))}catch{}await new Promise(i=>setTimeout(i,500))}}Ye(+an.port||4848)})();function Uo(e){let t={};for(let o=0;o<e.length;o++)if(e[o].startsWith("--")){let i=e[o].slice(2);t[i]=e[o+1]&&!e[o+1].startsWith("--")?e[++o]:!0}return t}function ke(e){let t=[...(process.env.PATH||"").split(":"),h.join(ae.homedir(),".local/bin"),"/opt/homebrew/bin","/usr/local/bin"];for(let o of t){if(!o)continue;let i=h.join(o,e);try{return k.accessSync(i,k.constants.X_OK),i}catch{}}return null}export{ye as PRESETS};