pixie-mcp 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixie-mcp",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "MCP server for Pixie — draw pixel art from the AI client you already pay for. Holds no API key and bills nobody.",
5
5
  "type": "module",
6
6
  "bin": {
package/pixie-mcp.mjs CHANGED
@@ -1,42 +1,42 @@
1
1
  #!/usr/bin/env node
2
- var O=(r,e)=>()=>(r&&(e=r(r=0)),e);function Te(r){let e="";for(let t=0;t<r.length;t+=32768)e+=String.fromCharCode.apply(null,r.subarray(t,t+32768));return btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}var Pe=O(()=>{"use strict"});var I,St,Et,Nn,Oe=O(()=>{"use strict";I="[A-Za-z0-9_-]{1,64}",St="token|members|link|slow|suggest|project",Et=[{owner:"accounts",re:new RegExp("^/auth/")},{owner:"accounts",re:new RegExp("^/a/[A-Za-z0-9_-]{1,64}\\.png$")},{owner:"accounts",re:new RegExp("^/projects(/|$)")},{owner:"accounts",re:new RegExp(`^/documents/${I}/(${St})$`)},{owner:"library",re:new RegExp("^/documents$")},{owner:"library",re:new RegExp("^/documents/(new|register|move|order|folders|folders/delete|trash|trash/empty)$")},{owner:"library",re:new RegExp(`^/documents/${I}(/(thumbnail\\.png|export|duplicate|rename|restore))?$`)},{owner:"library",re:new RegExp(`^/documents/${I}/history/at/\\d+/thumbnail\\.png$`)},{owner:"library",re:new RegExp("^/wall\\.png$")},{owner:"document",re:new RegExp(`^/documents/${I}/history(/at/\\d+|/\\d+)?$`)},{owner:"document",re:new RegExp(`^/documents/${I}/comments(/[A-Za-z0-9]{1,40})?$`)},{owner:"document",re:new RegExp(`^/documents/${I}/snapshots(/enabled|/[A-Za-z0-9_-]{1,128})?$`)},{owner:"document",re:new RegExp(`^/favourites(/order|/(document|project)/${I})?$`)}],Nn=Et.map(({owner:r,re:e})=>({owner:r,source:e.source}))});var ae,ce=O(()=>{"use strict";Oe();ae=["read","draw","create","duplicate","organize"]});async function Ne(r){let e=new TextEncoder().encode(String(r));return Te(new Uint8Array(await crypto.subtle.digest("SHA-256",e)))}var Fn,Ie=O(()=>{"use strict";Pe();ce();Fn=120*1e3});function j(r=Date.now()){let e="0123456789ABCDEFGHJKMNPQRSTVWXYZ",t=(BigInt(r)&0xffffffffffffn)<<80n,n=crypto.getRandomValues(new Uint8Array(10)),o=0n;for(let i of n)o=o<<8n|BigInt(i);t|=o;let s="";for(let i=0;i<26;i++)s=e[Number(t&31n)]+s,t>>=5n;return s}function P(r){return r==null?{at:0}:typeof r=="number"?{at:r}:{frame:r}}var At,Tt,Pt,q,C,le,B=O(()=>{"use strict";At=new TextEncoder,Tt=new TextDecoder,Pt=1;q=class r{#e;#n;constructor(e){this.#e=e,this.#n=e.exports}static async load(e){let t;if(e instanceof WebAssembly.Module)t=await WebAssembly.instantiate(e,{});else if(e instanceof ArrayBuffer||ArrayBuffer.isView(e))({instance:t}=await WebAssembly.instantiate(e,{}));else{let n=e instanceof Response||typeof e?.then=="function"?e:fetch(e);({instance:t}=await WebAssembly.instantiateStreaming(n,{}))}return new r(t)}get#i(){return new Uint8Array(this.#n.memory.buffer)}call(e,t={}){let n=At.encode(JSON.stringify({m:e,a:t})),o=this.#n.sprite_alloc(n.length);if(!o)throw new Error("sprite-core: allocation failed");let s=0;try{if(this.#i.set(n,o),s=this.#n.sprite_dispatch(o,n.length),!s)throw new Error("sprite-core: dispatch returned nothing");let i=this.#i,a=i[s]|i[s+1]<<8|i[s+2]<<16|i[s+3]<<24,c=i[s+4],l=i.slice(s+5,s+5+a);if(c===Pt)return l;let h=JSON.parse(Tt.decode(l));if(h&&h.error)throw new Error(h.error);return h}finally{s?this.#n.sprite_free_result(s):this.#n.sprite_dealloc(o,n.length)}}reset(){this.#n.sprite_reset()}newDocument(e={}){return this.call("doc.new",{w:32,h:32,name:"untitled",...e})}decodePng(e){let t=this.call("image.decode",{png:[...e]}),n=new DataView(t.buffer,t.byteOffset,t.byteLength),o=n.getUint32(0,!0),s=n.getUint32(4,!0);return{w:o,h:s,pixels:t.subarray(8)}}openSession(e,t,n=1){let{h:o}=this.call("session.new",{doc:e,author:t,seed:n});return new C(this,o)}openRoom(e,{seq:t=0}={}){let{h:n}=this.call("room.new",{doc:e,...t>0?{seq:t}:{}});return new le(this,n)}},C=class{constructor(e,t){this.core=e,this.h=t}#e(e,t={}){return this.core.call(e,{h:this.h,...t})}state(){return this.#e("session.state")}apply(e,{now:t=Date.now(),label:n="Edit",job:o=null}={}){let s={now:t,label:n,bodies:Array.isArray(e)?e:[e]};return o&&(s.job=o),this.#e("session.apply",s).ops}remote(e){return this.#e("session.remote",{sop:e})}reject(e){return this.#e("session.reject",{op:e})}undo(e=Date.now()){return this.#e("session.undo",{now:e}).ops}redo(e=Date.now()){return this.#e("session.redo",{now:e}).ops}pending(){return this.#e("session.pending")}checkpoint({doc:e=!0}={}){return this.#e("session.checkpoint",{doc:e})}save(){return this.#e("session.save")}drawArt(e,t,n={}){return this.#e("session.drawArt",{rows:e,colors:t,...n}).bodies}drawAnimation(e,t,n={}){return this.#e("session.drawAnimation",{frames:e,colors:t,...n}).bodies}readArt(e,t,n={}){return this.#e("session.readArt",{layer:e,...P(t),...n})}cel(e,t){return this.#e("session.cel",{layer:e,...P(t)})}layerImage(e,t){return this.#e("session.layerImage",{layer:e,...P(t)})}tileCel(e,t){return this.#e("session.tileCel",{layer:e,...P(t)})}tile(e,t){return this.#e("session.tile",{tileset:e,index:t})}composite(e,{include:t="export"}={}){return this.#e("session.composite",{...P(e),include:t})}png(e,{scale:t=1,rect:n=null}={}){return this.#e("session.png",{...P(e),scale:Math.max(1,Math.round(t)||1),...n?{rect:n}:{}})}contactSheet({columns:e=4,scale:t=2}={}){return this.#e("session.sheet",{columns:e,scale:t})}thumbnail(e=48){return this.#e("session.thumbnail",{size:e})}card({frame:e=null,width:t=1200,height:n=630,framing:o=null,bleed:s=0,raw:i=!1}={}){return this.#e("session.card",{width:t,height:n,...o??{},...P(e),...s?{bleed:s}:{},...i?{raw:i}:{}})}cardLayout({frame:e=null,width:t=1200,height:n=630,framing:o=null}={}){return this.#e("session.cardLayout",{width:t,height:n,...o??{},...P(e)})}fit({width:e,height:t,margin:n=0,inscribed:o=!1,dx:s=0,dy:i=0,frame:a=null}){return this.#e("session.fit",{width:e,height:t,margin:n,inscribed:o,dx:s,dy:i,...P(a)})}close(){this.#e("session.free")}},le=class{constructor(e,t){this.core=e,this.h=t}#e(e,t={}){return this.core.call(e,{h:this.h,...t})}state(){return this.#e("room.state")}submit(e,t=Date.now()){return this.#e("room.submit",{op:e,now:t})}since(e){return this.#e("room.since",{seq:e})}compact(){return this.#e("room.compact")}save(){return this.#e("room.save")}lease(e,t){return this.#e("room.lease",{action:e,...t})}close(){this.#e("room.free")}}});function Be(r){return JSON.stringify(r)}function Ge(r){let e=r.rle instanceof Uint8Array?r.rle:null,t=new TextEncoder().encode(JSON.stringify({...r,rle:void 0}));if(t.length>Ot)return null;let n=new Uint8Array(3+t.length+(e?.length??0));return n[0]=We,n[1]=t.length&255,n[2]=t.length>>8&255,n.set(t,3),e&&n.set(e,3+t.length),n}function Nt(r){return r instanceof Uint8Array?r:r instanceof ArrayBuffer?new Uint8Array(r):ArrayBuffer.isView(r)?new Uint8Array(r.buffer,r.byteOffset,r.byteLength):null}function qe(r){if(typeof r!="string"){let e=Nt(r);if(!e||e.length<3||e[0]!==We)return null;let t=e[1]|e[2]<<8;if(e.length<3+t)return null;try{let n=JSON.parse(new TextDecoder().decode(e.subarray(3,3+t)));if(!n||typeof n.t!="string")return null;let o=e.subarray(3+t);return n.rle=o.length?Uint8Array.from(o):null,n}catch{return null}}try{let e=JSON.parse(r);return e&&typeof e.t=="string"?e:null}catch{return null}}var S,ze,E,Fe,We,Ot,He=O(()=>{"use strict";S={HELLO:"hello",SUBMIT:"submit",PRESENCE:"presence",STROKE:"stroke",LEASE:"lease",REVIEW:"review",PING:"ping",PROMPT:"prompt",ANSWER:"answer",STOP:"stop"},ze={DOCUMENT:"document",LIBRARY:"library"},E={WELCOME:"welcome",REHELLO:"rehello",OPS:"ops",REJECT:"reject",PRESENCE:"presence",STROKE:"stroke",LEASE:"lease",INK:"ink",SUGGESTIONS:"suggestions",SUGGESTION:"suggestion",ERROR:"error",PONG:"pong",PROMPT:"prompt",ANSWER:"answer",STOP:"stop"},Fe={OPS:"ops",SNAPSHOT:"snapshot"};We=157,Ot=65535});function Je(r){return j().toLowerCase().slice(0,16)}var Ve=O(()=>{"use strict";B()});function Mt(r){let e=[];if(!r.length)return new Uint8Array;let t=r[0],n=r[1],o=r[2],s=r[3],i=0,a=()=>{let c=i;do{let l=c&127;c>>>=7,e.push(c?l|128:l)}while(c);e.push(t,n,o,s)};for(let c=0;c<r.length;c+=4)r[c]===t&&r[c+1]===n&&r[c+2]===o&&r[c+3]===s?i++:(a(),t=r[c],n=r[c+1],o=r[c+2],s=r[c+3],i=1);return a(),new Uint8Array(e)}function Ct(r){let e="";for(let n=0;n<r.length;n+=32768)e+=String.fromCharCode.apply(null,r.subarray(n,n+32768));return btoa(e)}function Ut(r){return Ct(Mt(r))}function Xe(r,e,t,n){return{t:"paintRegion",layer:r,frame:e,rect:{x:t.x,y:t.y,w:t.w,h:t.h},rle:Ut(n)}}var Ze=O(()=>{"use strict"});import{readFile as fn}from"node:fs/promises";import{readFile as dt,writeFile as pt,mkdir as mt,chmod as ft}from"node:fs/promises";import{readFileSync as wt}from"node:fs";import{homedir as gt,hostname as yt}from"node:os";import{join as se}from"node:path";import{execFile as bt}from"node:child_process";var b=r=>String(r??"").replace(/\/+$/,"").replace(/^ws/,"http"),we=r=>String(r??"").replace(/\/+$/,"").replace(/^http/,"ws");async function ge(r,e){if(!r?.ok||!(r.headers.get("content-type")??"").includes("json"))return null;let t=await r.json().catch(()=>null);return!t||typeof t!="object"?null:e.some(n=>n in t)?t:null}async function ye(r,{timeoutMs:e=3e3,fetchImpl:t=fetch}={}){try{let n=await t(`${b(r)}/setup`,{signal:AbortSignal.timeout(e)});return await ge(n,["accounts","expectedPort","install"])}catch{return null}}var oe=new Map;function be(r,{fetchImpl:e=fetch,timeoutMs:t=3e3}={}){let n=b(r);return oe.has(n)||oe.set(n,ut(r,{fetchImpl:e,timeoutMs:t})),oe.get(n)}async function ut(r,{fetchImpl:e,timeoutMs:t}){try{let n=await e(`${b(r)}/config.json`,{signal:AbortSignal.timeout(t)}),s=(await ge(n,["rooms","accounts"]))?.rooms;if(typeof s=="string"&&/^(wss?|https?):\/\/./.test(s))return we(s)}catch{}return we(r)}var z=process.env.SPRITE_PUBLIC_SERVER||xt()||null;function xt(){return"wss://drawpixie.com"}var xe=se(process.env.XDG_CONFIG_HOME||se(gt(),".config"),"sprite"),ie=se(xe,"agent.json"),Se=r=>String(r??"").replace(/\/+$/,"");function Ee(){return`${yt().replace(/\.local$/,"")}`.slice(0,60)||"a computer"}async function ke(){try{let r=JSON.parse(await dt(ie,"utf8"));return r&&typeof r=="object"&&r.servers?r:{servers:{}}}catch{return{servers:{}}}}async function F(r){return process.env.SPRITE_TOKEN?{token:process.env.SPRITE_TOKEN,from:"the environment"}:(await ke()).servers[Se(r)]??null}async function $e(r,e){await mt(xe,{recursive:!0,mode:448});let t=await ke();t.servers[Se(r)]={...e,at:Date.now()};let n=`${ie}.tmp`;await pt(n,JSON.stringify(t,null,2),{mode:384}),await ft(n,384);let{rename:o}=await import("node:fs/promises");await o(n,ie)}async function ve(r,{timeoutMs:e=3e3}={}){try{let t=await fetch(`${b(r)}/setup`,{signal:AbortSignal.timeout(e)});return t.ok?!!(await t.json()).accounts:!1}catch{return!1}}async function je(r,e,{session:t=null}={}){let n=t??await F(r);if(!n?.token)return;let o=await fetch(`${b(r)}/documents/${encodeURIComponent(e)}/token`,{headers:{Authorization:`Bearer ${n.token}`}});if(o.status===401)throw new Error("this machine is signed out \u2014 pair it again");if(o.status===403)throw new Error(`no access to ${e}`);if(!o.ok)throw new Error(`could not get access to ${e}`);return(await o.json()).token}async function _e(r,{name:e,w:t,h:n},{session:o=null}={}){let s=o??await F(r);if(!s?.token)return;let i=await fetch(`${b(r)}/documents/new`,{method:"POST",headers:{Authorization:`Bearer ${s.token}`,"Content-Type":"application/json"},body:JSON.stringify({name:e,w:t,h:n})});if(i.status===401)throw new Error("this machine is signed out \u2014 pair it again");if(!i.ok)throw new Error(`could not make a document: the server said ${i.status}`);let{id:a}=await i.json();if(typeof a!="string"||!a)throw new Error("could not make a document: the server named none");return a}async function Re(r,e,{method:t="GET",body:n=null,session:o=null}={}){let s=o??await F(r);if(!s?.token)return;let i=await fetch(`${b(r)}${e}`,{method:t,headers:{Authorization:`Bearer ${s.token}`,...n!==null?{"Content-Type":"application/json"}:{}},body:n!==null?JSON.stringify(n):void 0});if(i.status===401)throw new Error("this machine is signed out \u2014 pair it again");let a=await i.json().catch(()=>({}));if(!i.ok)throw new Error(a?.error?String(a.error):`the server said ${i.status}`);return a}function Ae(r){let e=process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open";return new Promise(t=>bt(e,[r],()=>t()))}Ie();ce();import{createServer as kt}from"node:http";import{randomBytes as Ce}from"node:crypto";var $t=300*1e3,vt=()=>Ce(32).toString("base64url"),jt=()=>Ce(16).toString("base64url");function Me(r,e){return`<!doctype html><meta charset="utf-8"><title>Pixie</title><body style="background:#101014;color:#d8d8e0;font:14px/1.6 system-ui,sans-serif;display:grid;place-items:center;height:100vh;margin:0;text-align:center"><div><p style="font-size:16px">${r}</p><p style="color:#8a8a99;font-size:12px">${e}</p></div>`}async function _t({state:r,timeoutMs:e}){let t,n,o=new Promise((c,l)=>{t=c,n=l}),s=kt((c,l)=>{let h=new URL(c.url,"http://127.0.0.1");if(h.pathname!=="/callback"){l.writeHead(404).end();return}if(h.searchParams.get("state")!==r){l.writeHead(404).end();return}let d=h.searchParams.get("error");l.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}).end(d?Me("Nothing was granted.","You can close this window."):Me("This computer can now draw in your sprites.","You can close this window and go back to what you were doing.")),t(h.searchParams)});await new Promise((c,l)=>{s.once("error",l),s.listen(0,"127.0.0.1",c)});let i=setTimeout(()=>n(new Error("nobody answered the browser in time")),e);i.unref?.();let{port:a}=s.address();return{redirect:`http://127.0.0.1:${a}/callback`,wait:o,close:()=>{clearTimeout(i),s.close(),s.closeAllConnections?.()}}}async function Ue(r,{onUrl:e=()=>{},open:t=Ae,label:n="the Pixie agent",scope:o=ae,timeoutMs:s=$t}={}){let i=vt(),a=await Ne(i),c=jt(),l=await _t({state:c,timeoutMs:s});try{let h=new URL(`${b(r)}/authorize`);h.searchParams.set("challenge",a),h.searchParams.set("state",c),h.searchParams.set("redirect",l.redirect),h.searchParams.set("scope",o.join(",")),h.searchParams.set("name",n),e(h.toString()),await t(h.toString());let d=await l.wait,u=d.get("error");if(u)throw new Error(u==="access_denied"?"you said no on that screen, so nothing was granted":`that request was refused: ${u}`);let p=d.get("code");if(!p)throw new Error("the browser came back without an authorization");let w=await fetch(`${b(r)}/auth/authorize/exchange`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({code:p,verifier:i,name:Ee()})}),m=await w.json().catch(()=>({}));if(!w.ok||!m.token)throw new Error(m.error||"that authorization could not be exchanged");return await $e(r,{token:m.token,name:m.name,email:m.email,scope:m.scope??null}),m}finally{l.close()}}import{realpathSync as Le}from"node:fs";import{fileURLToPath as Rt}from"node:url";function De(r){let e=process.argv[1];if(!e)return!1;try{return Le(Rt(r))===Le(e)}catch{return!1}}B();import{execFile as Kt}from"node:child_process";He();B();var It=5e3,H=class{#e=null;constructor(e,t,n){if(!n||!n.author)throw new Error("DocumentClient needs an author");if(this.core=e,this.author=n.author,this.name=n.name||"anonymous",this.agent=n.agent===!0,this.answers=n.answers===!0,this.seed=n.seed??1,this.epoch=0,this.resumed=null,n.resume){let{h:o,dropped:s}=e.call("session.resume",{doc:t,author:this.author,seed:this.seed,seq:n.resume.seq??0,pending:n.resume.pending??[]});this.session=new C(e,o),this.resumed={seq:n.resume.seq??0,dropped:s??[]}}else this.session=e.openSession(t,this.author,this.seed);this.getToken=n.getToken??null,this.transport=null,this.connected=!1,this.welcomed=!1,this.ready=new Promise(o=>{this.#e=o}),this.participants=[],this.leases=[],this.presence=null,this.you=null,this.onChange=()=>{},this.onPresence=()=>{},this.onStroke=()=>{},this.strokePreview=!0,this.onLeases=()=>{},this.ink=null,this.onInk=()=>{},this.proposals=new Map,this.suggesting=!1,this.onSuggestions=()=>{},this.onLost=()=>{},this.waiters=new Set,this.onPrompt=()=>{},this.onAnswer=()=>{},this.onStop=()=>{},this.onStatus=()=>{}}apply(e,t={}){let n=this.session.apply(e,t);return this.#r(n),this.onChange(),n}acked(e,{timeoutMs:t=3e4}={}){let n=new Set((Array.isArray(e)?e:[e]).map(o=>o?.opId??o).filter(Boolean));return new Promise((o,s)=>{let i={ids:n,resolve:o,reject:s,timer:null};i.timer=setTimeout(()=>{this.waiters.delete(i);let a=[...n].filter(c=>this.session.pending().some(l=>l.opId===c)).length;s(new Error(`${a} operation${a===1?"":"s"} not confirmed by the room after ${Math.round(t/1e3)}s \u2014 `+(this.connected?"it is answering slowly":`the connection dropped; nothing since sequence ${this.session.state().seq} has been saved`)))},t),this.waiters.add(i),this.#n()})}#n(){if(!this.waiters.size)return;let e=new Set(this.session.pending().map(t=>t.opId));for(let t of this.waiters)[...t.ids].some(n=>e.has(n))||(this.waiters.delete(t),clearTimeout(t.timer),t.resolve())}#i(e,t){for(let n of this.waiters)n.ids.has(e)&&(this.waiters.delete(n),clearTimeout(n.timer),n.reject(new Error(t)))}undo(e=Date.now()){let t=this.session.undo(e);return this.#r(t),this.onChange(),t}redo(e=Date.now()){let t=this.session.redo(e);return this.#r(t),this.onChange(),t}setPresence(e){this.presence=e,this.connected&&this.#o({t:S.PRESENCE,...e})}sendStroke({layer:e,frame:t,rect:n,rle:o,done:s=!1}){if(!this.connected||!this.welcomed||!this.strokePreview||this.suggesting)return;this._strokeSentAt=Date.now();let i=Ge({t:S.STROKE,layer:e,frame:t,rect:n,rle:o,done:s});i&&this.#u(i)}suggest(e){if(!e.length||!this.connected||!this.welcomed)return[];let t=this.session.state().seq,n=e.map(o=>({opId:j(),author:this.author,parentSeq:t,...o}));return this.#o({t:S.SUBMIT,ops:n}),n}review(e,t){!this.connected||!this.welcomed||this.#o({t:S.REVIEW,author:e,verdict:t})}lease(e,t={}){this.#o({t:S.LEASE,action:e,...t})}prompt(e,{where:t=ze.DOCUMENT}={}){if(!this.connected)return null;let n=String(e??"").trim().slice(0,2e3);if(!n)return null;let o=j();return this.#o({t:S.PROMPT,id:o,text:n,where:t}),o}answer(e,t="",{reply:n=null,made:o=null,model:s=null}={}){return this.connected?(this.#o({t:S.ANSWER,state:e,text:String(t??"").slice(0,2e3),reply:n,made:o,model:s}),!0):!1}stopAnswer(e){return!this.connected||!e?!1:(this.#o({t:S.STOP,id:e}),!0)}get state(){return this.session.state()}checkpoint({doc:e=!0}={}){return{epoch:this.epoch,author:this.author,...this.session.checkpoint({doc:e})}}composite(e){return this.session.composite(e)}connect(e){return this.transport=e,this.pendingToken=this.getToken?this.getToken():void 0,this.pendingToken?.catch?.(()=>{}),e.onopen=async()=>{this.connected=!0,this.welcomed=!1,clearInterval(this.heartbeat),this.heartbeat=setInterval(()=>{this.connected&&this.#o({t:S.PING})},It),await this.#s()},e.onmessage=t=>this.#l(t),e.onclose=()=>{this.connected=!1,this.welcomed=!1,clearInterval(this.heartbeat),this.onStatus({connected:!1})},e.readyState===1&&e.onopen(),this}disconnect(){if(this.transport){this.connected=!1,this.welcomed=!1,clearInterval(this.heartbeat);try{this.transport.close()}catch{}this.transport=null,this.onStatus({connected:!1})}}async#s(){let e;if(this.pendingToken!==void 0){let n=this.pendingToken;this.pendingToken=void 0,e=await n}else e=this.getToken?await this.getToken():void 0;let t=this.session.state();this.#o({t:S.HELLO,author:this.author,name:this.name,token:e,since:t.seq,...this.agent?{agent:!0}:{},...this.answers?{answers:!0}:{},...this.presence?.active===!1?{active:!1}:{},docId:t.doc.id})}#l(e){let t=qe(e);if(t)switch(t.t){case E.WELCOME:return this.#t(t);case E.REHELLO:return this.welcomed=!1,void this.#s();case E.OPS:return this.#c(t);case E.REJECT:return this.#h(t);case E.PRESENCE:return this.participants=t.participants||[],this.onPresence(this.participants);case E.STROKE:return this.onStroke(t);case E.LEASE:return this.leases=t.leases||[],this.onLeases(this.leases);case E.INK:return this.ink={left:t.left,cap:t.cap,refillMs:t.refillMs,nextInMs:t.nextInMs??0,at:Date.now()},this.onInk(this.ink);case E.SUGGESTIONS:return this.proposals=new Map((t.proposals??[]).map(n=>[n.author,{...n,ops:[...n.ops??[]]}])),this.onSuggestions(this.proposals);case E.SUGGESTION:{if(t.gone)return this.proposals.delete(t.author),this.onSuggestions(this.proposals,{author:t.author,name:t.name??null,gone:t.gone,by:t.by??null,applied:t.applied??0});let n=this.proposals.get(t.author)??{author:t.author,name:t.name??null,cc:t.cc??null,ops:[],at:t.at};if(n.name=t.name??n.name,n.at=t.at??n.at,t.replaced?.length){let o=new Set(t.replaced);n.ops=n.ops.filter(s=>!(s.rect?.w===1&&s.rect?.h===1&&o.has(`${s.rect.x},${s.rect.y}`)))}return n.ops.push(...t.ops??[]),this.proposals.set(t.author,n),this.onSuggestions(this.proposals,{author:t.author,added:t.ops?.length??0})}case E.PROMPT:return this.onPrompt(t);case E.ANSWER:return this.onAnswer(t);case E.STOP:return this.onStop(t);case E.ERROR:if(this.#d(t.message)){this.strokePreview=!1;return}return this.onLost({kind:t.fatal?"fatal":"protocol",message:t.message});default:return}}#t(e){this.you=e.you,this.suggesting=e.you?.suggest===!0,this.welcomed=!0;let t=this.session.pending();if(e.catchup.kind===Fe.SNAPSHOT){let{h:n,dropped:o}=this.core.call("session.resume",{doc:e.catchup.doc,author:this.author,seed:this.seed,seq:e.catchup.seq,pending:t});if(this.session.close(),this.session=new C(this.core,n),this.epoch++,o&&o.length){for(let s of o)this.#i(s,"could not be replayed onto the current document");this.onLost({kind:"rebased",ops:o,message:`${o.length} edit${o.length>1?"s":""} could not be replayed onto the current document`})}}else for(let n of e.catchup.ops||[])this.#a(n);if(this.#n(),this.participants=e.participants||[],this.leases=e.leases||[],t.length&&this.suggesting)for(let n of t)this.session.reject(n.opId);else t.length&&this.#o({t:S.SUBMIT,ops:t});this.presence&&this.#o({t:S.PRESENCE,...this.presence}),this.onStatus({connected:!0,resent:t.length}),this.onPresence(this.participants),this.onLeases(this.leases),this.onChange(),this.#e?.(),this.#e=null}#c(e){let t=!1;for(let n of e.ops||[])this.#a(n),t=!0;t&&(this.#n(),this.onChange())}#a(e){let t=this.session.remote(e);t.dropped&&t.dropped.length&&this.onLost({kind:"obsolete",ops:t.dropped,message:`${t.dropped.length} of your edits no longer apply and were dropped`})}#h(e){e.opId&&this.session.reject(e.opId),e.opId&&this.#i(e.opId,e.message),this.onLost({kind:e.kind,ops:e.opId?[e.opId]:[],message:e.message}),this.onChange()}#r(e){!e.length||!this.connected||!this.welcomed||this.#o({t:S.SUBMIT,ops:e})}#o(e){this.#u(Be(e))}#d(e){return typeof e!="string"?!1:e===`unknown message: ${S.STROKE}`?!0:e==="unreadable message"&&Date.now()-(this._strokeSentAt??0)<2e3}#u(e){if(this.transport)try{this.transport.send(e)}catch{this.connected=!1}}close(){this.disconnect(),this.session.close()}};B();Ve();var A=r=>{let e=String(r).replace("#","");return[parseInt(e.slice(0,2),16),parseInt(e.slice(2,4),16),parseInt(e.slice(4,6),16)]};var U=([r,e,t])=>.3*r+.59*e+.11*t;function J([r,e,t]){let n=Math.max(r,e,t),o=Math.min(r,e,t),s=n-o,i=0;return s&&(n===r?i=(e-t)/s%6:n===e?i=(t-r)/s+2:i=(r-e)/s+4,i*=60,i<0&&(i+=360)),[i,n?s/n:0,n/255]}var Ke={luminance:(r,e)=>U(A(r))-U(A(e)),hue:(r,e)=>{let[t,n]=J(A(r)),[o,s]=J(A(e));return n<.08||s<.08?n<.08&&s<.08?U(A(r))-U(A(e)):n<.08?-1:1:Math.round(t/20)!==Math.round(o/20)?t-o:U(A(r))-U(A(e))},saturation:(r,e)=>J(A(r))[1]-J(A(e))[1]};function L(r,e="hue"){let t=Ke[e]??Ke.hue;return[...r].sort(t)}Ze();import{readFile as Xt}from"node:fs/promises";function Lt(r){try{return/^(localhost|127\.0\.0\.1|\[::1\]|::1)$/.test(new URL(b(r)).hostname)}catch{return!1}}function Ye(r,{publicServer:e=z}={}){let t=b(r);return e&&r===e?`nothing is answering at ${t} \u2014 this machine is offline, or the site is down`:Lt(r)?`nothing is answering at ${t} \u2014 start the editor with \`npm start\` in the Pixie repository, then try again`:`nothing is answering at ${t} \u2014 check the address and that whatever serves it is up`}import{readFile as zt,writeFile as Qe}from"node:fs/promises";function N(r,e=0){if(r==null||typeof r=="number"||typeof r=="boolean")return r;if(typeof r=="string")return r.length>120?`${r.slice(0,119)}\u2026`:r;if(Array.isArray(r)){if(r.length&&r.every(t=>typeof t=="string")){let t=new Set(r.map(n=>n.length));return`${r.length}\xD7${t.size===1?r[0].length:`${Math.min(...t)}\u2013${Math.max(...t)}`}`}return`[${r.length}]`}if(typeof r=="object"){let t=Object.keys(r),n=t.length<=8&&t.every(s=>r[s]==null||typeof r[s]!="object");if(t.length>32||e>0&&!n)return`{${t.length}}`;let o={};for(let s of t)o[s]=N(r[s],e+1);return o}return String(r)}function Dt(r){if(r&&typeof r=="object"&&Array.isArray(r.content)){let e=r.content.find(n=>n.type==="image"),t=r.content.find(n=>n.type==="text")?.text??"";if(r.isError)return{ok:!1,error:N(t)};if(e)return{ok:!0,result:`image \u2014 ${N(t)}`};try{return{ok:!0,result:N(JSON.parse(t))}}catch{return{ok:!0,result:N(t)}}}return{ok:!0,result:N(r)}}var V=class{constructor(){this.entries=[],this.next=1,this.open=new Map,this.starts=new Map}async record(e,t,n){let o=Date.now(),s={i:this.next++,t:new Date(o).toISOString(),tool:e,args:N(t??{})};try{let i=await n();return Object.assign(s,Dt(i),{ms:Date.now()-o}),this.#e(s),i}catch(i){throw Object.assign(s,{ok:!1,error:N(i?.message??String(i)),ms:Date.now()-o}),this.#e(s),i}}#e(e){this.entries.push(e),this.entries.length>4e3&&this.entries.splice(0,this.entries.length-4e3)}began(e){if(!e)return;let t=this.entries.at(-1)?.i??this.next;this.open.set(e,t),this.starts.set(t,e)}finished(e){let t=e??(this.open.size===1?[...this.open.keys()][0]:null);if(!t||!this.open.has(t))return[];let n=this.open.get(t);return this.open.delete(t),this.entries.filter(o=>o.i>=n&&(o.args?.job==null||o.args.job===t)&&(this.starts.get(o.i)??t)===t)}};var K={type:"string",description:"#rrggbb"};async function Ft(r,e){let t=await zt(r).catch(s=>{throw new Error(`could not read ${r}: ${s.code==="ENOENT"?"no such file":s.message}`)});if(t.length>8&&t[0]===137&&t[1]===80&&t[2]===78&&t[3]===71){if(!e.core?.decodePng)throw new Error("this server cannot decode a PNG \u2014 send rows and colors");return Bt(e.core.decodePng(t))}let o;try{o=JSON.parse(t.toString("utf8"))}catch{throw new Error(`${r} is neither a PNG nor JSON`)}if(!o||typeof o!="object"||!Array.isArray(o.rows)||typeof o.colors!="object")throw new Error(`${r} needs "rows" (an array of strings) and "colors" (a map of character to #rrggbb)`);return o}function Bt({w:r,h:e,pixels:t}){let n={},o=new Map,s=33,i=l=>{let h=o.get(l);if(h)return h;for(;;)if(s===34||s===46||s===92||s===127)s+=1;else if(s>=128&&s<161)s=161;else if(s>=55296&&s<57344)s=57344;else break;return h=String.fromCodePoint(s),s+=1,o.set(l,h),n[h]=l,h},a=l=>l.toString(16).padStart(2,"0"),c=[];for(let l=0;l<e;l++){let h="";for(let d=0;d<r;d++){let u=(l*r+d)*4,p=t[u+3];if(p===0){h+=".";continue}let w=p===255?`#${a(t[u])}${a(t[u+1])}${a(t[u+2])}`:`#${a(t[u])}${a(t[u+1])}${a(t[u+2])}${a(p)}`;h+=i(w)}c.push(h)}return{rows:c,colors:n}}var $={type:"string",description:"The job this call belongs to. May be omitted when exactly one job is open."},X={type:"string",description:"Which document. Omit to use the one your job owns. The id \u2014 the one new_sprite, get_info and list_documents hand back \u2014 or the exact name when only one document has it. In a local editor a 0-based tab index works too."},Wt=r=>({isError:!0,content:[{type:"text",text:r}]}),he=r=>({content:[{type:"text",text:typeof r=="string"?r:JSON.stringify(r)}]}),et=(r,e)=>({content:[{type:"image",data:Buffer.from(r).toString("base64"),mimeType:"image/png"},{type:"text",text:e}]}),_={w:80,h:42},Gt="40\xD721, 80\xD742, 120\xD763, 160\xD784, 240\xD7126",qt="40\xD740, 50\xD750, 80\xD780, 100\xD7100",Ht="60\xD720, 75\xD725, 150\xD750, 300\xD7100";function ue(r,e){if(!r||!e)return null;let t=r/e,n=1200/630;if(Math.abs(t-n)<.15){let o=Math.round(r/n);return`${r}\xD7${e} is ${t.toFixed(2)}:1; the link-card shape at this width is ${r}\xD7${o} \u2014 resize_canvas if it is meant to be posted.`}return Math.abs(t-1)<.1?`${r}\xD7${e} is nearly square; ${Math.max(r,e)}\xD7${Math.max(r,e)} would be a profile picture.`:Math.abs(t-3)<.25?`${r}\xD7${e} is nearly 3:1; ${r}\xD7${Math.round(r/3)} would be a profile banner.`:null}function Z(r,e){return!r||!e?null:r===e?"This is square, the shape of a profile picture: in the editor, file \u25B8 Save as profile picture (400\xD7400) fits it inside the circle it will be cropped to. Tell the user.":r===3*e?"This is 3:1, the shape of a profile banner: in the editor, file \u25B8 Save as profile banner (1500\xD7500) exports it at the size X wants. Tell the user.":Math.abs(r/e-1200/630)<.02?"This is the link-card shape: the share button posts it edge to edge.":null}function Jt(r){let e=String(r??"").trim();if(!/^[A-Za-z0-9]+([-_][A-Za-z0-9]+)+$/.test(e))return e;let t=e.split(/[-_]+/).join(" ");return t[0].toUpperCase()+t.slice(1)}var Vt=[{name:"new_sprite",description:"Create a sprite and claim it. Call this FIRST \u2014 before writing a script, computing a grid, or thinking about pixels. It costs milliseconds and puts a labeled document in front of the user immediately, so they can see what you are making and where. The tab opens in the background and never steals their focus.",inputSchema:{type:"object",properties:{name:{type:"string",description:'The title, the way a person would write it: "Blue cake", not "blue-cake". Shown on the tab and in the library; the URL is minted from it. A hyphenated lowercase name is turned into words.'},w:{type:"integer",default:_.w,description:`Width in pixels. WHAT THE USER ASKED FOR WINS: a stated size or shape is used as stated, and a named artist, piece or style is matched in scale and detail. The rest is defaults for when nothing was said: ${_.w}\xD7${_.h} \u2014 1.91:1, the ratio link cards crop to, so a piece drawn on it posts edge to edge \u2014 at any size (${Gt}, 480\xD7252, 600\xD7315: examples, not limits). A PROFILE PICTURE is square and cropped to a circle \u2014 keep the subject inside it: ${qt}. A BANNER is 3:1: ${Ht}. Use a game asset's own size (8\xD78, 16\xD716, 32\xD732) only when the user asks for a tile, icon, or sprite for a game.`},h:{type:"integer",default:_.h,description:"Height in pixels. See w."},palette:{type:"array",items:K,description:"Optional starting palette."},intent:{type:"string",description:"One line on what you are making. Shown while you work."},folder:{type:"string",description:'Optional folder to file it in, by path (`food/sushi`). Made if it does not exist. Use this when the user asks for sprites "in a folder" rather than moving them afterwards.'},challenge:{type:"string",enum:["daily","weekly"],description:'"daily" when the user asks for the sprite of the day / today\'s theme / the Pixel Dailies; "weekly" for the sprite of the week / this week\'s word / Weekly Pixels. The name comes from the theme itself (pass `name` only to override), and finish_job will say how to post it as an entry. Call `challenges` first if you want to see the theme before drawing.'}},required:["intent"]},run:async(r,e)=>{let t=r.name?Jt(r.name):null,n=r.w??null,o=r.h??null;if(r.challenge){let s=await e.challenge(r.challenge);if(t??=s.theme,n===null&&o===null&&s.sizes?.length){let i=s.sizes[Math.floor((s.sizes.length-1)/2)],[a,c]=i.split(/[x×]/).map(Number);a&&c&&([n,o]=[a,c])}}if(!t)throw new Error("new_sprite needs a name, or a challenge to take one from");return e.newSprite({name:t,w:n??_.w,h:o??_.h,intent:r.intent,palette:r.palette,folder:r.folder??null,challenge:r.challenge??null})}},{name:"challenges",description:`The sprite of the day and the sprite of the week: Pixel Dailies sets a theme every day ("Today's theme is #Gateway") and Weekly Pixels a word every week, with sizes. When the user says "the sprite of the day", "today's theme", "the daily", "the weekly", "this week's word" or names either community, this is what they mean \u2014 read it here rather than asking them. The server reads the accounts and keeps the answer; nothing is fetched from Bluesky by you.`,inputSchema:{type:"object",properties:{}},run:(r,e)=>e.challenges()},{name:"begin_job",description:"Claim an existing document for one piece of work \u2014 the other way to start, where new_sprite is the one for new art. Refused by name if another job holds it, which is what keeps parallel requests from colliding. With no `document` and no job open, it picks up the document you last finished \u2014 one more pass on the same sprite is begin_job({ intent }).",inputSchema:{type:"object",properties:{document:{type:"string",description:"Which document: the id, or the exact name when only one document has it. Omit, with no job open, for the one you last finished."},intent:{type:"string"},layer:{type:"string",description:"Restrict the claim to one layer, leaving the rest free. Rooms only."}},required:["intent"]},run:(r,e)=>e.beginJob({document:r.document,intent:r.intent,layer:r.layer??null})},{name:"finish_job",description:"Release the document. Call this when the sprite is done, so the user knows to look. The result says what to do next for a profile picture, a banner or a card \u2014 pass that on to the user \u2014 and carries `trace`: every call of this job, as the server saw it. When the task asked for a report back, save it as trace.jsonl (one entry per line) beside report.md.",inputSchema:{type:"object",properties:{job:$}},run:(r,e)=>e.finishJob({job:r.job})},{name:"draw_art",description:'Returns when the room has confirmed the drawing \u2014 `seq` is the room\'s number for it and `committed` is true \u2014 or fails saying why; if it says the connection dropped, wait a moment and check get_info before drawing more. Draw a character grid. Each string in `rows` is one row of pixels and each character maps to a color via `colors`. "." and " " leave existing pixels alone; a color mapped to null erases. Every character is resolved before a pixel moves, so a typo fails rather than half-drawing. With no style reference, keep the palette small \u2014 four to eight colors, reused for shading \u2014 because that is what makes a small sprite read as pixel art rather than as a downscaled photograph. When matching an artist or a piece, use as many colors as their work does. A canvas past ~300 wide goes in bands: several calls with `y`, a few dozen rows each. A LARGE OR GENERATED GRID SHOULD NOT PASS THROUGH YOU: have the script write it to a file and pass `file` instead of `rows` \u2014 a JSON file of {rows, colors, x?, y?, \u2026}, or a PNG, which is the whole canvas or, with `rect`, one region of it. The reply says how many pixels changed and which colors joined the palette, so growth is never silent (with `palette: "replace"` nothing joins: the palette is this drawing\'s colors, and the reply says so). read here on the same machine. Then the call is fifty bytes, whatever the canvas.',inputSchema:{type:"object",properties:{file:{type:"string",description:"Path to a grid on this machine, instead of rows and colors: a JSON file of draw_art arguments, or a PNG. Anything given in the call as well wins."},rows:{type:"array",items:{type:"string"}},colors:{type:"object",additionalProperties:{oneOf:[K,{type:"null"}]}},x:{type:"integer"},y:{type:"integer"},center:{type:"boolean"},clear:{type:"boolean",description:"Erase the cel first."},rect:{type:"object",description:"With `file`: replace only this region `{x, y, w, h}` of the canvas from a full-canvas file \u2014 a one-object change is not a whole-canvas redraw. With `clear`, only the region is emptied first.",properties:{x:{type:"integer"},y:{type:"integer"},w:{type:"integer"},h:{type:"integer"}}},palette:{type:"string",enum:["add","replace"],description:"What this drawing's colors do to the document palette: `add` (default) joins them; `replace` makes them the whole palette \u2014 for a generated piece redrawn several times, so the palette does not pile up."},layer:{type:"string",description:"Layer name or index. Defaults to the top one."},frame:{type:"integer",description:"Frame index. Defaults to the first."},label:{type:"string",description:"What this shows as in the undo history."},job:$}},run:async(r,e)=>{let t=r.file?{...await Ft(r.file,e),...r}:r;if(!Array.isArray(t.rows)||!t.colors)throw new Error("draw_art needs rows and colors, or a file that has them");return e.drawArt({job:t.job,rows:t.rows,colors:t.colors,x:t.x??0,y:t.y??0,center:t.center??!1,clear:t.clear??!1,layer:t.layer,frame:t.frame??0,label:t.label,palette:t.palette??"add",rect:t.rect??null})}},{name:"draw_animation",description:"Write every frame in ONE call, as a single undo step. Use copyPrevious so a frame carries the last one forward and sends only the pixels that moved \u2014 that is what takes a six-frame loop from four thousand tokens of restated grid to under a thousand. Never loop draw_art. SILHOUETTE FIRST: at 32px a front-on animal is any animal; when the subject could go several ways, draw 2-3 rough silhouettes as frames, look with render_animation, keep the one that reads, then add detail \u2014 three candidates cost barely more than one.",inputSchema:{type:"object",properties:{frames:{type:"array",minItems:1,items:{type:"object",properties:{rows:{type:"array",items:{type:"string"}},copyPrevious:{type:"boolean"},x:{type:"integer"},y:{type:"integer"},center:{type:"boolean"}},required:["rows"]}},colors:{type:"object",additionalProperties:{oneOf:[K,{type:"null"}]}},fps:{type:"number"},layer:{type:"string"},label:{type:"string"},job:$},required:["frames","colors"]},run:(r,e)=>e.drawAnimation({job:r.job,frames:r.frames,colors:r.colors,fps:r.fps,layer:r.layer,label:r.label})},{name:"read_art",description:"Read a cel back as a grid \u2014 the exact inverse of draw_art, so you can extend art a human drew rather than only replacing it. Reads never need a job and never wait.",inputSchema:{type:"object",properties:{document:X,layer:{type:"string"},frame:{type:"integer"},trim:{type:"boolean",description:"Return only the drawn area. Rooms only."},file:{type:"string",description:"Write the grid to this path on this machine as JSON \u2014 the shape draw_art takes as `file` \u2014 instead of returning it, so a script can round-trip art without it passing through you. The reply says where it went and how big it is."},png:{type:"string",description:"Write the document to this path as a PNG, one image pixel per sprite pixel \u2014 the composite, or one `layer` \u2014 so an image tool, `references/compare.py` or `references/scene.py guides` can open the room's truth directly. Rooms only."},job:$}},run:async(r,e)=>{if(r.png){if(!e.readPng)throw new Error("a PNG of the document is a room thing \u2014 in a local editor, read_art({ file }) and render it");let i=await e.readPng({document:r.document,job:r.job,layer:r.layer??null,frame:r.frame??0});return await Qe(r.png,i.png).catch(a=>{throw new Error(`could not write ${r.png}: ${a.message}`)}),{document:i.document,png:r.png,size:`${i.w}\xD7${i.h}`,layer:r.layer??"composite"}}let t=await e.readArt({document:r.document,job:r.job,layer:r.layer,frame:r.frame??0,trim:r.trim??!1});if(!r.file)return t;let{rows:n,colors:o,...s}=t;return await Qe(r.file,JSON.stringify({rows:n,colors:o},null,0)).catch(i=>{throw new Error(`could not write ${r.file}: ${i.message}`)}),{...s,file:r.file,size:`${n[0]?.length??0}\xD7${n.length}`,colors:Object.keys(o??{}).length}}},{name:"render_preview",description:"See one frame as an image, so you can check your own work rather than working blind. Rendered at a readable size by default (about a thousand pixels on the long side); `scale` is screen pixels per sprite pixel if you want a particular one. To look closely at one part of a big canvas \u2014 a cell of a sprite sheet \u2014 pass `rect` `{x, y, w, h}` in sprite pixels. The text beside the image says exactly what was rendered and at what scale. WHEN MATCHING A REFERENCE, pass `reference` (a PNG path on this machine): the reference is brought to the canvas grid and shown on the LEFT of the render, same rect, same scale \u2014 the only comparison that catches an object built nothing like the original. Judge per object from that, never from the whole image or from memory \u2014 and judge construction, not resemblance: count the faces, name the light, say where the shadow falls, on theirs and on yours. `values` renders both as grays \u2014 the squint test for a value plan and a focal point.",inputSchema:{type:"object",properties:{document:X,frame:{type:"integer"},scale:{type:"integer",description:"Screen pixels per sprite pixel. Default: whatever makes the long side about 1000px."},rect:{type:"object",description:"A window onto the canvas in sprite pixels, cropped before scaling. Omit w/h to run to the edge.",properties:{x:{type:"integer"},y:{type:"integer"},w:{type:"integer"},h:{type:"integer"}}},reference:{type:"string",description:"Path to a reference PNG on this machine. Shown beside the render at the same crop and scale, resampled to the canvas grid by width."},values:{type:"boolean",description:"Render as values only (grayscale) \u2014 the render, and the reference when one is given. A picture that is mid-tone everywhere shows it here and nowhere else."},job:$}},async run(r,e){let t=await e.preview({document:r.document,job:r.job,frame:r.frame??0,scale:r.scale??null,rect:r.rect??null,reference:r.reference??null,values:r.values===!0});return et(t.png,t.caption)}},{name:"render_animation",description:"Every frame as one numbered contact sheet. Stills inspected one call at a time, minutes apart, cannot tell you whether motion reads; a contact sheet can, in one call. Check any animation with this before calling it done.",inputSchema:{type:"object",properties:{document:X,columns:{type:"integer",default:4},scale:{type:"integer",default:2},job:$}},async run(r,e){let t=await e.sheet({document:r.document,job:r.job,columns:r.columns??4,scale:r.scale??2});return et(t.png,t.caption)}},{name:"add_layer",description:"Add a layer above the others. Anything you will revise while the rest stays \u2014 a figure over a background, a caption over a figure \u2014 goes on its own layer, so a revision is one draw_art with `clear: true` on that layer and the base is never redrawn or at risk.",inputSchema:{type:"object",properties:{name:{type:"string"},job:$}},run:(r,e)=>e.addLayer({job:r.job,name:r.name})},{name:"add_frame",description:"Add an animation frame after the last one.",inputSchema:{type:"object",properties:{copy:{type:"boolean",description:"Start from the previous frame."},job:$}},run:(r,e)=>e.addFrame({job:r.job,copy:r.copy??!1})},{name:"set_palette",description:"Replace the palette. Constraining yourself to a small fixed palette is the single biggest quality lever in pixel art \u2014 choose it before you draw, because every draw_art adds its colors. With `prune: true` and no `colors`, drop every palette entry no pixel uses instead: the way to clean up after a session that did not. Never done for you on finish_job.",inputSchema:{type:"object",properties:{colors:{type:"array",items:K,minItems:1},prune:{type:"boolean",description:"Keep only the colors some pixel uses. `colors` is ignored."},job:$}},run:(r,e)=>!r.prune&&!r.colors?.length?Wt("set_palette wants `colors`, or `prune: true`"):e.setPalette({job:r.job,colors:r.colors??[],prune:r.prune===!0})},{name:"set_fps",description:"Set playback speed for every frame. Eight to twelve suits character animation.",inputSchema:{type:"object",properties:{fps:{type:"integer",minimum:1,maximum:60},job:$},required:["fps"]},run:(r,e)=>e.setFps({job:r.job,fps:r.fps})},{name:"clear",description:"Erase a cel. Destructive, so it goes through a job like every other write.",inputSchema:{type:"object",properties:{layer:{type:"string"},frame:{type:"integer"},job:$}},run:(r,e)=>e.clearCel({job:r.job,layer:r.layer,frame:r.frame??0})},{name:"trim_canvas",description:"Crop the canvas to the drawn pixels. Call this before finish_job on every GAME ASSET \u2014 a tile, an icon, a sprite for a sheet: you cannot know its extent before you have drawn it, so overshoot at new_sprite and trim afterwards, or empty space is inherited by every export and sheet. Do NOT trim a piece made to be posted: its canvas is the card, and the space around the subject is the composition.",inputSchema:{type:"object",properties:{margin:{type:"integer",default:0},job:$}},run:(r,e)=>e.trim({job:r.job,margin:r.margin??0})},{name:"resize_canvas",description:"Change the canvas size, keeping the art \u2014 the fix for a sprite made at the wrong size, in place, on the same document. `anchor` says where the old canvas sits on the new one: `center` (default), `top-left`, `top`, `top-right`, `left`, `right`, `bottom-left`, `bottom`, `bottom-right`. Never make a second document for a size change.",inputSchema:{type:"object",properties:{w:{type:"integer",minimum:1,maximum:1024},h:{type:"integer",minimum:1,maximum:1024},anchor:{type:"string"},job:$},required:["w","h"]},run:(r,e)=>e.resizeCanvas({job:r.job,w:r.w,h:r.h,anchor:r.anchor??"center"})},{name:"get_info",description:"What is in a document \u2014 size, layers, frames, palette \u2014 and who else is looking at it; `connected` says whether the room is on the other end and `pending` how many operations it has not confirmed. Both should be true and zero before you trust a read.",inputSchema:{type:"object",properties:{document:X,job:$}},run:(r,e)=>e.info({document:r.document,job:r.job})},{name:"list_documents",description:'Every document you can reach, and every job currently open. Call this when the user names a sprite ("add a frame to the hamster") so you can pass it to begin_job. Never guess a name.',inputSchema:{type:"object",properties:{}},run:(r,e)=>e.documents()},{name:"create_folder",description:"Make a folder in the library, by path \u2014 `food/sushi` nests. Already existing is fine. Prefer passing `folder` to new_sprite when the sprites are new; use this for an empty folder or before move_documents.",inputSchema:{type:"object",properties:{name:{type:"string",description:"The folder path."}},required:["name"]},run:(r,e)=>e.createFolder({name:r.name})},{name:"move_documents",description:"File documents into a folder, or back to the top level with folder null. Takes document ids from list_documents. The folder is made if it does not exist.",inputSchema:{type:"object",properties:{documents:{type:"array",items:{type:"string"},description:"Document ids."},folder:{type:["string","null"],description:"Folder path, or null for the top level."}},required:["documents"]},run:(r,e)=>e.moveDocuments({documents:r.documents,folder:r.folder??null,make:!0})},{name:"take_requests",description:"Requests people have typed into the editor and not yet been answered. Call this when the user asks you to check, or after joining a room they are in. Each one is somebody asking for a sprite in words \u2014 treat it exactly as if they had said it to you directly. Each carries what it wants: a `room` to answer in, or `room: null` for a request typed at the library, which wants a brand new sprite rather than a change to any existing one.",inputSchema:{type:"object",properties:{}},run:(r,e)=>e.takeRequests()},{name:"open_editor",description:"Open the Pixie editor in the default browser and wait for it to connect. Run this first if nothing is connected.",inputSchema:{type:"object",properties:{}},async run(r,e){let t=await e.openEditor();return he(t.message)}},{name:"select_document",description:"Bring a document to the front for the USER to look at. This does not give you the right to draw in it \u2014 begin_job does that.",inputSchema:{type:"object",properties:{document:{type:["string","integer"]}},required:["document"]},run:(r,e)=>e.select({document:r.document})},{name:"close_document",description:"Close a document: in a local editor its tab, in a room the connection and any job you hold on it. The document stays in the library, so this tidies the workspace rather than deleting anything.",inputSchema:{type:"object",properties:{document:{type:["string","integer"]}}},run:(r,e)=>e.closeDocument({document:r.document??null})},{name:"set_frame",description:"Move the user to a frame, by 0-based index.",inputSchema:{type:"object",properties:{index:{type:"integer"}},required:["index"]},run:(r,e)=>e.setFrame({index:r.index})},{name:"set_layer",description:"Move the user to a layer, by 0-based index counting from the bottom.",inputSchema:{type:"object",properties:{index:{type:"integer"}},required:["index"]},run:(r,e)=>e.setLayer({index:r.index})},{name:"undo",description:"Undo the last change, so the user can always reject generated art.",inputSchema:{type:"object",properties:{}},run:(r,e)=>e.undo()},{name:"redo",description:"Redo the last undone change.",inputSchema:{type:"object",properties:{}},run:(r,e)=>e.redo()}];function tt(r){let e=r.trace??=new V,t=n=>n&&typeof n=="object"&&Array.isArray(n.content);return Vt.filter(n=>r.supports(n.name)).map(n=>({name:n.name,description:n.description,inputSchema:n.inputSchema,run:async o=>{let s=await e.record(n.name,o,()=>n.run(o??{},r));return(n.name==="new_sprite"||n.name==="begin_job")&&s?.job&&e.began(s.job),n.name==="finish_job"&&s&&typeof s=="object"&&!t(s)?he({...s,trace:e.finished(s.job??o?.job)}):t(s)?s:he(s)}}))}var nt=/^[A-Za-z0-9_-]{1,64}$/,Zt=6e3;function Yt(r,e){return Math.max(1,Math.min(32,Math.round(1e3/Math.max(r,e,1))))}function Qt(r,e,t){let n=new Uint8Array(e*t*4),o=r.w/e,s=r.h/t;for(let i=0;i<t;i++){let a=Math.floor(i*s),c=Math.max(a+1,Math.floor((i+1)*s));for(let l=0;l<e;l++){let h=Math.floor(l*o),d=Math.max(h+1,Math.floor((l+1)*o)),u=0,p=0,w=0,m=0,f=0;for(let x=a;x<c&&x<r.h;x++)for(let R=h;R<d&&R<r.w;R++){let k=(x*r.w+R)*4;u+=r.pixels[k],p+=r.pixels[k+1],w+=r.pixels[k+2],m+=r.pixels[k+3],f++}let g=(i*e+l)*4;f&&n.set([Math.round(u/f),Math.round(p/f),Math.round(w/f),Math.round(m/f)],g)}}return{w:e,h:t,pixels:n}}function de(r,e=new Uint8Array(r.length)){for(let t=0;t<r.length;t+=4){let n=Math.round(.299*r[t]+.587*r[t+1]+.114*r[t+2]);e[t]=e[t+1]=e[t+2]=n,e[t+3]=r[t+3]}return e}function en(r,e,t){let n=new Uint8Array(t.w*t.h*4);for(let o=0;o<t.h;o++){let s=((t.y+o)*e+t.x)*4;n.set(r.subarray(s,s+t.w*4),o*t.w*4)}return n}function rt(r,e,t){let n=(c,l)=>Number.isFinite(+c)?Math.max(0,Math.round(+c)):l,o=Math.min(n(r?.x,0),e),s=Math.min(n(r?.y,0),t),i=Math.min(n(r?.w,e-o),e-o),a=Math.min(n(r?.h,t-s),t-s);return{x:o,y:s,w:i,h:a}}var Y=class{constructor(e,t){this.core=e,this.author=t.author,this.name=t.name||"agent",this.connectTransport=t.connect,this.getToken=t.getToken??null,this.createDocument=t.createDocument??null,this.control=t.control??null,this.probe=t.probe??null,this.rooms=new Map,this.asked=[],this.onAsked=null,this.jobs=new Map,this.log=[]}async open(e,{w:t=32,h:n=32,name:o=e}={}){let s=this.rooms.get(e);if(s)return s.client;let i=this.core.newDocument({w:t,h:n,name:o,seed:st(),now:Date.now()}),a=new H(this.core,i,{author:this.author,name:this.name,seed:st(),agent:!0,getToken:this.getToken?()=>this.getToken(e):null});a.onLost=d=>{this.lastRefusal=d},a.onPrompt=d=>{if(d.author===this.author)return;let u=d.where==="library";this.asked.push({text:d.text,from:d.name,at:d.at,wants:u?"a new sprite":"a change to this one",room:u?null:e,askedIn:e}),this.asked.length>32&&this.asked.shift(),this.onAsked?.()};let c=this.connectTransport(e,{w:t,h:n,name:o}),l=!1;return await new Promise((d,u)=>{let p=setTimeout(()=>u(new Error(`timed out joining ${e}`)),1e4);a.onStatus=m=>{m.connected&&(l=!0,clearTimeout(p),d())},a.connect(c);let w=c.onclose;c.onclose=()=>{if(w?.(),l)return this.#n(e,{w:t,h:n,name:o});clearTimeout(p),this.#e(e).then(m=>u(new Error(m)))}}),this.rooms.set(e,{client:a,attempts:0}),a}async#e(e){return this.serverUrl?(this.probe?await this.probe(this.serverUrl).catch(()=>!1):!1)?nt.test(e)?`the room "${e}" would not open, and the server is up: try once more \u2014 a room can be away for a moment \u2014 and if it repeats, there is no such document or it is not this account's; list_documents names what it can`:`the room server refused "${e}": that is a name, not a document id \u2014 list_documents gives the id, and passing the exact name works when it is unique`:Ye(this.serverUrl):"nothing is answering at the room server"}#n(e,t){let n=this.rooms.get(e);if(!n||n.closing)return;let o=Math.min(15e3,1e3*2**Math.min(n.attempts,4));n.attempts+=1,n.retry=setTimeout(()=>{if(!this.rooms.get(e)||n.closing)return;let s=this.connectTransport(e,t),i=!1,a=s.onclose;n.client.onStatus=l=>{l.connected&&(i=!0,n.attempts=0)},n.client.connect(s);let c=s.onclose;s.onclose=()=>{c?.(),a?.(),this.#n(e,t)}},o),n.retry.unref?.()}client(e){let t=this.rooms.get(e);if(!t)throw new Error(`not in room ${e}`);return t.client}async peek(e){return this.rooms.has(e)?this.client(e):this.open(e)}async begin(e,t,{layer:n=null,open:o={}}={}){let s=await this.open(e,o),i=j(),a=on(s,i);s.lease("acquire",{job:i,intent:t,...n?{layers:[n]}:{}});let c=await a;if(c!=="held"){let h=s.leases.find(d=>d.job!==i);throw new Error(c==="refused"&&this.lastRefusal?.message?`could not claim "${e}": ${this.lastRefusal.message}`:h?`"${e}" is already held by another job: ${h.intent}`:c==="silent"?`could not claim "${e}": the room did not answer in time \u2014 check get_info and try again`:`could not claim "${e}"`)}let l={room:e,layer:n,intent:t,cancelled:!1,timer:setInterval(()=>{this.jobs.has(i)&&s.lease("renew",{job:i})},Zt)};return l.timer.unref&&l.timer.unref(),this.jobs.set(i,l),this.note("begin_job",e,t),i}resolve(e){if(e){let o=this.jobs.get(e);if(!o)throw new Error(`unknown job ${e} \u2014 it may have finished`);if(o.cancelled)throw new Error("the user stopped you \u2014 do not retry; say what you were about to do and ask");return{job:e,...o}}if(this.jobs.size===0)throw new Error(this.lastRoom?`no job is open \u2014 begin_job({ document: "${this.lastRoom}" }) picks ${this.lastRoom} up again, or new_sprite starts another`:"no job is open \u2014 call new_sprite or begin_job first");if(this.jobs.size>1)throw new Error(`${this.jobs.size} jobs are open \u2014 pass the job argument`);let[t,n]=[...this.jobs.entries()][0];return{job:t,...n}}finish(e){let t=this.jobs.get(e);t&&(clearInterval(t.timer),this.jobs.delete(e),this.lastRoom=t.room,this.client(t.room).lease("release",{job:e}),this.note("finish_job",t.room,t.intent))}async submit(e,t,n){let o=this.resolve(e),s=this.client(o.room);if(!n.length)return{ops:0};if(!s.connected||!s.welcomed)throw new Error(`the room connection dropped \u2014 nothing since sequence ${s.state.seq} has been saved. Reconnecting; try again in a moment, and check get_info before drawing more.`);this.lastRefusal=null;let i=s.state.seq,a=s.apply(n,{label:t,job:o.job,now:Date.now()});try{await s.acked(a,{timeoutMs:3e4})}catch(l){throw this.lastRefusal?.kind==="revoked"&&(o.cancelled=!0),this.lastRefusal=null,l}if(this.lastRefusal){let l=this.lastRefusal.message;throw this.lastRefusal.kind==="revoked"&&(o.cancelled=!0),this.lastRefusal=null,new Error(l)}this.note(t,o.room,`${n.length} op${n.length>1?"s":""}`);let c=s.state.seq;return{ops:n.length,seq:c,committed:c>i}}cancel(e){let t=this.jobs.get(e);t&&(t.cancelled=!0)}note(e,t,n){this.log.unshift({at:Date.now(),tool:e,room:t,detail:n}),this.log.length>200&&this.log.pop()}announce(){if(!this.serverUrl)return;let e=`${this.serverUrl.replace(/^ws/,"http").replace(/\/$/,"")}/agent`,t=()=>{fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:this.name})}).catch(()=>{})};t(),clearInterval(this.heartbeat),this.heartbeat=setInterval(t,15e3),this.heartbeat.unref?.()}close(){for(let e of this.rooms.values())e.closing=!0,clearTimeout(e.retry);clearInterval(this.heartbeat);for(let[e]of this.jobs)this.finish(e);for(let{client:e}of this.rooms.values())e.close();this.rooms.clear()}get kind(){return"room"}get where(){return this.serverUrl??"a room server"}supports(e){return e==="open_editor"?!!this.editorUrl:tn.has(e)}async openEditor({document:e,job:t}={}){if(!this.editorUrl)return{ok:!1,message:"there is no editor to open from here"};let n=this.editorUrl;try{n=`${this.editorUrl}/d/${await this.#s(e,t)}`}catch{n=`${this.editorUrl}/library`}return await rn(n),{ok:!0,message:`Opened ${n}`}}async newSprite({name:e,w:t=_.w,h:n=_.h,intent:o,palette:s,folder:i=null,challenge:a=null}){let c=await this.createDocument?.({name:e,w:t,h:n})??Je(e),l=await this.begin(c,o,{open:{w:t,h:n,name:e}});a&&(this.jobs.get(l).challenge=a),s?.length&&await this.submit(l,"set_palette",[{t:"setPalette",colors:L(s,"hue")}]),i&&await this.moveDocuments({documents:[c],folder:i,make:!0}),this.#i(c,o);let h=Z(t,n)??ue(t,n);return{job:l,...this.#t(c),...h?{shape:h}:{}}}async#i(e,t){let n=process.env.SPRITE_ASKED_IN,o=process.env.SPRITE_REPLY_TO??null;if(n)try{(await this.open(n)).answer("thinking",t?String(t).slice(0,200):"making a new sprite",{reply:o,made:e})}catch{}}async beginJob({document:e,intent:t,layer:n=null}){let o=!e&&this.jobs.size===0&&this.lastRoom?this.lastRoom:await this.#s(e,null);return{job:await this.begin(o,t,{layer:n}),...this.#t(o)}}finishJob({job:e}){let t=this.resolve(e);this.finish(t.job);let n=this.client(t.room).state,o=Z(n.doc.w,n.doc.h),i=[t.challenge?`This is ${t.challenge==="weekly"?"this week's Weekly Pixels":"today's Pixel Dailies"} entry: tell the user to post it from the share button with the ${t.challenge==="weekly"?"Weekly Pixels":"Pixel Dailies"} switch on, which puts the theme and the tags on the post.`:null,o].filter(Boolean).join(" ");return{finished:t.intent,job:t.job,document:t.room,...i?{next:i}:{}}}async challenges(){if(!this.newsUrl)throw new Error("no server to ask: the themes come from the Pixie server, and this session has none");if(!(this.newsAt&&Date.now()-this.newsAt<300*1e3)){let n=await fetch(this.newsUrl);if(!n.ok)throw new Error(`the server said ${n.status} for the themes`);this.news=await n.json(),this.newsAt=Date.now()}let t=(n,o)=>n?{theme:n.theme,tags:n.tags,detail:n.detail??null,sizes:n.sizes??[],postedAt:new Date(n.at).toISOString(),post:n.url}:{theme:null,note:`no ${o} theme is known right now`};return{daily:t(this.news?.dailies,"daily"),weekly:t(this.news?.weekly,"weekly"),how:'new_sprite({ challenge: "daily" | "weekly", intent }) names the sprite after the theme; finish_job says how to post it as an entry.'}}async challenge(e){let t=await this.challenges(),n=e==="weekly"?t.weekly:t.daily;if(!n?.theme)throw new Error(`no ${e} theme is known right now \u2014 ask the user what to draw`);return n}async#s(e,t){if(!e)return this.resolve(t).room;let n=String(e).trim();if(this.rooms.has(n))return n;let o=await this.#l();if(o===null||o.some(i=>i.id===n))return n;let s=o.filter(i=>(i.name??"").trim().toLowerCase()===n.toLowerCase());if(s.length===1)return s[0].id;if(s.length>1)throw new Error(`${s.length} documents are called "${n}" \u2014 pass the id: ${s.map(i=>i.id).join(", ")}`);if(nt.test(n))return n;throw new Error(`no document is called "${n}" \u2014 list_documents names every one with its id; pass the id`)}async#l(){let e=this.control?await this.control("/documents",{}).catch(()=>null):null;if(e?.documents)return e.documents.map(n=>({id:n.id,name:n.name}));if(!this.serverUrl)return null;let t=this.serverUrl.replace(/^ws/,"http").replace(/\/$/,"");return fetch(`${t}/documents`).then(n=>n.ok?n.json():null).then(n=>n?.documents?.map(o=>({id:o.id,name:o.name}))??null).catch(()=>null)}async info({document:e,job:t}={}){let n=await this.#s(e,t);return await this.peek(n),this.#t(n)}#t(e){let t=this.client(e),n=t.state;return{document:e,name:n.doc.name,w:n.doc.w,h:n.doc.h,layers:n.doc.layers.map(o=>o.name),frames:n.doc.frames.length,palette:n.doc.palette,tags:n.doc.tags.map(o=>o.name),seq:n.seq,connected:!!(t.connected&&t.welcomed),pending:n.pending,participants:t.participants.map(o=>o.name)}}#c(e){let t=this.client(e),n=t.state;return n.pending===0&&t.connected?{}:{warning:n.pending?`includes ${n.pending} operation${n.pending===1?"":"s"} the room has not confirmed${t.connected?"":" \u2014 the connection dropped, reconnecting"}`:"the room connection dropped, reconnecting"}}async readArt({document:e,job:t,layer:n,frame:o=0,trim:s=!1}){let i=await this.#s(e,t),a=await this.peek(i);return{document:i,...a.session.readArt(this.#r(i,n),o,{trim:s}),...this.#c(i)}}async readPng({document:e,job:t,layer:n=null,frame:o=0}){let s=await this.#s(e,t),i=await this.peek(s),a=i.state.doc;if(n==null)return{document:s,png:i.session.png(o,{scale:1}),w:a.w,h:a.h};let c=i.session.cel(this.#r(s,n),o);return{document:s,png:this.#a(a.w,a.h,c),w:a.w,h:a.h}}#a(e,t,n,o=1){let s=this.core.newDocument({w:e,h:t,name:"scratch",seed:1,now:Date.now()}),i=this.core.openSession(s,j(),1);try{let a=i.state();return i.apply([Xe(a.doc.layers[0].id,a.doc.frames[0].id,{x:0,y:0,w:e,h:t},n)],{now:Date.now()}),i.png(0,{scale:o})}finally{i.close()}}async#h(e,t,n,o,s,i=!1){let a=await Xt(t).catch(g=>{throw new Error(`could not read the reference ${t}: ${g.code==="ENOENT"?"no such file":g.message}`)}),c=this.core.decodePng(a),l=e.state.doc,h=Qt(c,l.w,Math.max(1,Math.round(c.h*l.w/c.w))),d=i?de(e.session.composite(o)):e.session.composite(o);i&&de(h.pixels,h.pixels);let u=n.w,p=n.h,w=new Uint8Array((u*2+1)*p*4),m=(u*2+1)*4;for(let g=0;g<p;g++){for(let x=0;x<u;x++){let R=n.x+x,k=n.y+g,T=(k*h.w+R)*4,re=k<h.h&&R<h.w;w.set(re?h.pixels.subarray(T,T+4):[0,0,0,255],g*m+x*4);let D=(k*l.w+R)*4;w.set(d.subarray(D,D+4),g*m+(u+1+x)*4)}w.set([255,0,255,255],g*m+u*4)}let f=h.h!==l.h?` \u2014 the reference is ${c.w}\xD7${c.h}, brought to ${h.w}\xD7${h.h} by width; the canvas is ${l.w}\xD7${l.h}`:"";return{png:this.#a(u*2+1,p,w,s),note:f}}async preview({document:e,job:t,frame:n=0,scale:o=null,rect:s=null,reference:i=null,values:a=!1}){let c=await this.#s(e,t),l=await this.peek(c),{warning:h}=this.#c(c),d=l.state.doc,u=s?rt(s,d.w,d.h):null,p=u?u.w:d.w,w=u?u.h:d.h,m=Number.isInteger(o)&&o>0?o:Yt(i?p*2:p,w),f=Math.max(1,Math.min(m,Math.floor(8192/Math.max(i?p*2:p,w,1)))),g=u?`${u.w}\xD7${u.h} at (${u.x},${u.y}) of ${d.w}\xD7${d.h}`:`${d.w}\xD7${d.h}`,x=f<m?` \u2014 asked for \xD7${m}, held to \xD7${f} so a side stays under 8192px; render a smaller rect to look closer`:"";if(i){let{png:k,note:T}=await this.#h(l,String(i),u??{x:0,y:0,w:d.w,h:d.h},n,f,a);return{png:k,caption:`${c}, frame ${n+1}: reference (left) beside the render (right), ${g} at \xD7${f}${a?", values only":""}${T}${x}${h?` \u2014 ${h}`:""}. Per object, say how each is built \u2014 count the faces, name the light, say where the shadow falls \u2014 and what differs; "reads as" is not a verdict.`}}return{png:a?this.#a(p,w,en(de(l.session.composite(n)),d.w,u??{x:0,y:0,w:d.w,h:d.h}),f):l.session.png(n,{scale:f,...u?{rect:u}:{}}),caption:`${c}, frame ${n+1}: ${g} at \xD7${f} (${p*f}\xD7${w*f}px)${a?", values only \u2014 squint: is there a focal point, and where is the darkest dark":""}${x}${h?` \u2014 ${h}`:""}`}}async sheet({document:e,job:t,columns:n=4,scale:o=2}){let s=await this.#s(e,t),i=await this.peek(s);return{png:i.session.contactSheet({columns:n,scale:o}),caption:`${s}, ${i.state.doc.frames.length} frames, numbered left to right`}}async documents(){let e=[...this.jobs.entries()].map(([c,l])=>({job:c,document:l.room,intent:l.intent})),t=[...this.rooms.keys()],n=null,o=null,s=null,i=this.control?await this.control("/documents",{}).catch(()=>null):null;if(i)n=i.documents?.map(c=>c.id)??null,o=Array.isArray(i.folders)?i.folders.map(c=>typeof c=="string"?c:c?.path??c?.name).filter(Boolean):null,s=i.documents?Object.fromEntries(i.documents.filter(c=>c.folder).map(c=>[c.id,c.folder])):null;else if(this.serverUrl){let c=this.serverUrl.replace(/^ws/,"http").replace(/\/$/,"");n=await fetch(`${c}/documents`).then(l=>l.ok?l.json():null).then(l=>l?.documents?.map(h=>h.id)).catch(()=>null)}return{documents:n?[...new Set([...t,...n])]:t,jobs:e,open:t,...o?{folders:o}:{},...s?{filed:s}:{}}}async createFolder({name:e}){if(!this.control)throw new Error("no library here: this Sprite has no accounts, so there are no folders");let t=String(e??"").trim();if(!t)throw new Error("a folder needs a name");let n=await this.folders();if(n.includes(t))return{folder:t,created:!1,folders:n};let o=t.split("/");for(let s=1;s<=o.length;s++){let i=o.slice(0,s).join("/");n.includes(i)||(await this.control("/documents/folders",{method:"POST",body:{name:i}}),n.push(i))}return{folder:t,created:!0,folders:await this.folders()}}async moveDocuments({documents:e,folder:t=null,make:n=!1}){if(!this.control)throw new Error("no library here: this Sprite has no accounts, so there are no folders");let o=(Array.isArray(e)?e:[e]).map(String).filter(Boolean);if(!o.length)throw new Error("say which documents");let s=t===null||t===""?null:String(t).trim();s&&n&&await this.createFolder({name:s});let i=await this.control("/documents/move",{method:"POST",body:{ids:o,folder:s}});return{moved:o,folder:s,ok:i?.ok!==!1}}async folders(){let e=await this.control("/documents",{});return Array.isArray(e?.folders)?e.folders.map(t=>typeof t=="string"?t:t?.path??t?.name).filter(Boolean):[]}takeRequests(){let e=this.asked.splice(0,this.asked.length);return{requests:e,note:e.length?"Each says what it wants. Where `room` is set, answer in that document. Where it is null the request came from the library and wants a new sprite \u2014 call new_sprite and work there, and do not touch `askedIn`, which is only the room the words travelled through. The person is watching.":"Nobody has asked for anything."}}#r(e,t){let n=this.client(e).state;if(t!=null&&t!==""){let o=typeof t=="number"?n.doc.layers[t]:n.doc.layers.find(s=>s.name===t||s.id===t);if(!o)throw new Error(`no layer ${JSON.stringify(t)} in ${e}`);return o.id}return n.doc.layers[n.doc.layers.length-1].id}async drawArt({job:e,rows:t,colors:n,x:o=0,y:s=0,center:i=!1,clear:a=!1,layer:c,frame:l=0,label:h,palette:d="add",rect:u=null}){let p=this.resolve(e),w=this.client(p.room),m=t,f={x:o,y:s},g=a;if(u){let y=rt(u,Math.max(...t.map(v=>v.length),1),t.length);if(m=t.slice(y.y,y.y+y.h).map(v=>v.slice(y.x,y.x+y.w).padEnd(y.w,".")),f={x:y.x,y:y.y},a){let v=[..."~^`|"].find(M=>!(M in n))??"\xA1";m=m.map(M=>M.replace(/[. ]/g,v)),n={...n,[v]:null},g=!1}}let x=new Set((w.state.doc.palette??[]).map(y=>String(y).toLowerCase())),R=w.session.drawArt(m,n,{layer:c!=null?this.#r(p.room,c):p.layer??this.#r(p.room,null),at:l,x:f.x,y:f.y,center:u?!1:i,clear:g}),{committed:k}=await this.submit(p.job,h||"draw_art",R),T=(this.client(p.room).state.doc.palette??[]).map(y=>String(y).toLowerCase()).filter(y=>!x.has(y)),re=m.reduce((y,v)=>y+[...v].filter(M=>M!=="."&&M!==" ").length,0);if(d==="replace"&&k){let y=[...new Set(Object.values(n).filter(v=>typeof v=="string"&&/^#[0-9a-f]{6}$/i.test(v)).map(v=>v.toLowerCase()))];y.length&&await this.submit(p.job,"set_palette",[{t:"setPalette",colors:L(y,"hue")}])}let D=(this.client(p.room).state.doc.palette??[]).length,ht=d==="replace"&&k?{note:`palette replaced: ${D} color${D===1?"":"s"}, this drawing's`}:T.length?{added:L(T,"hue"),note:`${T.length} color${T.length===1?"":"s"} joined the palette \u2014 draw from a chosen palette, or set_palette({ prune: true }) later`}:{};return{...this.#t(p.room),drew:u?`${m[0]?.length??0}\xD7${m.length} at (${f.x},${f.y})`:`${t[0]?.length??0}\xD7${t.length}`,changed:re,committed:k,...ht}}async resizeCanvas({job:e,w:t,h:n,anchor:o="center"}){let s=this.resolve(e),i=this.client(s.room).state,a=Math.max(1,Math.min(1024,Math.round(Number(t)||i.doc.w))),c=Math.max(1,Math.min(1024,Math.round(Number(n)||i.doc.h)));if(a===i.doc.w&&c===i.doc.h)return{...this.#t(s.room),resized:"already that size"};let l=String(o??"center").toLowerCase(),h=l.includes("left")?0:l.includes("right")?2:1,d=l.includes("top")?0:l.includes("bottom")?2:1,u=Math.round((a-i.doc.w)*h/2),p=Math.round((c-i.doc.h)*d/2);await this.submit(s.job,"resize_canvas",[{t:"resize",w:a,h:c,dx:u,dy:p}]);let w=Z(a,c)??ue(a,c);return{...this.#t(s.room),resized:`${i.doc.w}\xD7${i.doc.h} \u2192 ${a}\xD7${c}, the art kept at ${u},${p}`,...w?{shape:w}:{}}}async closeDocument({document:e}){let t=await this.#s(e,null),n=this.rooms.get(t);if(!n)return{closed:t,open:!1};for(let[o,s]of this.jobs)s.room===t&&this.finish(o);return n.closing=!0,clearTimeout(n.retry),n.client.close(),this.rooms.delete(t),{closed:t,open:!1}}async drawAnimation({job:e,frames:t,colors:n,fps:o,layer:s,label:i}){let a=this.resolve(e),c=this.client(a.room),l=s!=null?this.#r(a.room,s):a.layer??this.#r(a.room,null),h=c.state.doc.frames.length,d=t.length-h;if(d>0){let p=[],w=c.state.doc.frames[h-1]?.index??"V";for(let m=0;m<d;m++)w=w+"V",p.push({t:"addFrame",id:j(),index:w,durMs:pe(o)});await this.submit(a.job,"add frames",p)}if(o){let p=pe(o);await this.submit(a.job,"set fps",c.state.doc.frames.map(w=>({t:"setFrameDur",id:w.id,durMs:p})))}let u=c.session.drawAnimation(t,n,{layer:l});return await this.submit(a.job,i||"draw_animation",u),{...this.#t(a.room),wrote:`${t.length} frames`}}async addLayer({job:e,name:t}){let n=this.resolve(e),o=this.client(n.room).state,s=o.doc.layers[o.doc.layers.length-1];return await this.submit(n.job,"add_layer",[{t:"addLayer",id:j(),index:(s?.index??"V")+"V",name:t||`Layer ${o.doc.layers.length+1}`,visible:!0,locked:!1,opacity:1}]),this.#t(n.room)}async addFrame({job:e,copy:t=!1}){let n=this.resolve(e),o=this.client(n.room),s=o.state,i=s.doc.frames[s.doc.frames.length-1],a=j(),c=[{t:"addFrame",id:a,index:(i?.index??"V")+"V",durMs:i?.durMs??83}];if(t&&i)for(let l of s.doc.layers){let h=o.session.cel(l.id,i.id);h.every(d=>d===0)||c.push(ot(l.id,a,s.doc,h))}return await this.submit(n.job,"add_frame",c),this.#t(n.room)}async setPalette({job:e,colors:t,prune:n=!1}){let o=this.resolve(e),s;if(n){let i=this.client(o.room),a=i.state,c=new Set;for(let h of a.doc.layers)for(let d of a.doc.frames){let u=i.session.cel(h.id,d.id);for(let p=0;p<u.length;p+=4)u[p+3]!==0&&c.add(`#${[u[p],u[p+1],u[p+2]].map(w=>w.toString(16).padStart(2,"0")).join("")}`)}let l=a.doc.palette??[];s=l.filter(h=>c.has(String(h).toLowerCase()));for(let h of c)s.some(d=>String(d).toLowerCase()===h)||s.push(h);return s=L(s,"hue"),await this.submit(o.job,"set_palette",[{t:"setPalette",colors:s}]),{...this.#t(o.room),palette:s.length,pruned:l.length-s.length}}return s=L(t,"hue"),await this.submit(o.job,"set_palette",[{t:"setPalette",colors:s}]),{...this.#t(o.room),palette:s.length}}async setFps({job:e,fps:t}){let n=this.resolve(e),o=this.client(n.room).state,s=pe(t);return await this.submit(n.job,"set_fps",o.doc.frames.map(i=>({t:"setFrameDur",id:i.id,durMs:s}))),{...this.#t(n.room),fps:t}}async clearCel({job:e,layer:t,frame:n=0}){let o=this.resolve(e),i=this.client(o.room).state,a=this.#r(o.room,t??o.layer),c=i.doc.frames[n];if(!c)throw new Error(`no frame ${n} in ${o.room}`);let l=new Uint8Array(i.doc.w*i.doc.h*4);return await this.submit(o.job,"clear",[ot(a,c.id,i.doc,l)]),this.#t(o.room)}async trim({job:e,margin:t=0}){let n=this.resolve(e),o=this.client(n.room),s=o.state,i=this.#r(n.room,n.layer),a=o.session.readArt(i,0,{trim:!0}),c=a.rect.w+t*2,l=a.rect.h+t*2;return c>=s.doc.w&&l>=s.doc.h?{...this.#t(n.room),trimmed:"already tight"}:(await this.submit(n.job,"trim_canvas",[{t:"resize",w:c,h:l,dx:-a.rect.x+t,dy:-a.rect.y+t}]),{...this.#t(n.room),trimmed:`${c}\xD7${l}`})}},tn=new Set(["new_sprite","begin_job","finish_job","draw_art","draw_animation","read_art","render_preview","render_animation","add_layer","add_frame","set_palette","set_fps","clear","trim_canvas","resize_canvas","close_document","get_info","list_documents","challenges","take_requests","create_folder","move_documents"]);function ot(r,e,t,n){return{t:"paintRegion",layer:r,frame:e,rect:{x:0,y:0,w:t.w,h:t.h},rle:nn(n)}}function nn(r){let e=[];if(r.length){let t=[r[0],r[1],r[2],r[3]],n=0,o=()=>{let s=n;do{let i=s&127;s>>=7,e.push(s?i|128:i)}while(s);e.push(...t)};for(let s=0;s<r.length;s+=4)r[s]===t[0]&&r[s+1]===t[1]&&r[s+2]===t[2]&&r[s+3]===t[3]?n++:(o(),t=[r[s],r[s+1],r[s+2],r[s+3]],n=1);o()}return Buffer.from(Uint8Array.from(e)).toString("base64")}function rn(r){let e=process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open";return new Promise(t=>Kt(e,[r],()=>t()))}function pe(r){return!r||r<=0?83:Math.max(10,Math.round(1e3/r))}function on(r,e,t=8e3){return new Promise(n=>{let o=r.onLeases,s=r.onLost,i=!1,a=l=>{i||(i=!0,clearTimeout(c),r.onLeases=o,r.onLost=s,n(l))};r.onLeases=l=>{o?.(l),l.some(h=>h.job===e)&&a("held")},r.onLost=l=>{s?.(l),l?.ops?.length||a("refused")};let c=setTimeout(()=>a("silent"),t);r.leases.some(l=>l.job===e)&&a("held")})}function st(){return(Date.now()^Math.floor(Math.random()*4294967295))>>>0}import{execFile as sn}from"node:child_process";var an=2e4,cn=5e3,W=class r{constructor({base:e,name:t="agent"}={}){this.base=String(e).replace(/\/$/,""),this.name=t,this.log=[],this.beat=setInterval(()=>void this.announce(),cn),this.beat.unref&&this.beat.unref()}get kind(){return"local"}get where(){return this.base}supports(e){return ln.has(e)}static async probe(e,{timeoutMs:t=700}={}){try{let n=await fetch(`${e.replace(/\/$/,"")}/bridge/status`,{signal:AbortSignal.timeout(t)});if(!n.ok)return null;let o=await n.json();return o?.server==="sprite"?o:null}catch{return null}}async announce(){try{await fetch(`${this.base}/bridge/agent`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:this.name,version:null}),signal:AbortSignal.timeout(2e3)})}catch{}}async call(e,t=[],n=an){let o;try{o=await(await fetch(`${this.base}/bridge/call`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({method:e,args:t,timeoutMs:n}),signal:AbortSignal.timeout(n+2e3)})).json()}catch(s){throw new Error(`lost contact with the Pixie editor at ${this.base}: ${s.message}`)}if(!o.ok)throw new Error(o.error);return this.note(e),o.result}note(e,t=""){this.log.unshift({at:Date.now(),tool:e,room:"local",detail:t}),this.log.length>200&&this.log.pop()}newSprite({name:e,w:t=_.w,h:n=_.h,intent:o,palette:s}){return this.call("newSprite",[{w:t,h:n,name:e,palette:s,intent:o,replace:!1}])}async challenges(){let e=await fetch(`${this.base}/news.json`).catch(()=>null);if(!e?.ok)throw new Error("this editor does not serve the themes \u2014 ask the user what to draw");let t=await e.json(),n=(o,s)=>o?{theme:o.theme,tags:o.tags,detail:o.detail??null,sizes:o.sizes??[],postedAt:new Date(o.at).toISOString(),post:o.url}:{theme:null,note:`no ${s} theme is known right now`};return{daily:n(t?.dailies,"daily"),weekly:n(t?.weekly,"weekly")}}async challenge(e){let t=await this.challenges(),n=e==="weekly"?t.weekly:t.daily;if(!n?.theme)throw new Error(`no ${e} theme is known right now \u2014 ask the user what to draw`);return n}beginJob({document:e,intent:t}){return this.call("beginJob",[{document:e,intent:t}])}finishJob({job:e}){return this.call("finishJob",[{job:e}])}async info({document:e}={}){let t=await this.call("info",[]);return{editor:this.base,...e?{asked:e}:{},...t}}readArt({document:e,layer:t,frame:n}){return this.call("readArt",[{document:e,layer:t,frame:n}])}async preview({document:e,frame:t,scale:n=null,rect:o=null,reference:s=null}){if(o)throw new Error("the local editor cannot render a region \u2014 render the whole frame");if(s)throw new Error("a reference beside the render is a room thing \u2014 in a local editor, read_art({ file }) and compare with references/compare.py");let i=await this.call("toDataURL",[t??null,Number.isInteger(n)&&n>0?n:8,e??null]);return{png:it(i.url),caption:i.caption}}async sheet({document:e,columns:t=0,scale:n=0}){let o=await this.call("toStripDataURL",[{document:e??null,columns:t,scale:n}]);return{png:it(o.url),caption:o.caption}}documents(){return this.call("listDocuments",[])}drawArt({job:e,rows:t,colors:n,x:o,y:s,center:i,clear:a,layer:c,frame:l,label:h,rect:d=null}){if(d){let u=Math.max(0,d.x|0),p=Math.max(0,d.y|0);t=t.slice(p,p+(d.h|0)).map(w=>w.slice(u,u+(d.w|0))),o=u,s=p,i=!1}return this.call("drawArt",[t,n,{job:e,x:o,y:s,center:i,clear:a,layer:c,frame:l,label:h}])}drawAnimation({job:e,frames:t,colors:n,fps:o,layer:s,label:i}){return this.call("drawAnimation",[{frames:t,colors:n,fps:o,job:e,layer:s,label:i}])}addLayer({name:e}){return this.call("addLayer",[e])}addFrame({copy:e=!1}){return this.call("addFrame",[{copy:!!e}])}setPalette({colors:e,prune:t=!1}){if(t)throw new Error("prune works in a room \u2014 in a local editor, pass the colors to keep");return this.call("setPalette",[e])}setFps({fps:e}){return this.call("setFps",[e])}clearCel({job:e,layer:t,frame:n}){return this.call("clear",[{job:e,layer:t,frame:n}])}trim({job:e,margin:t=0}){return this.call("trimCanvas",[{job:e,margin:t}])}setFrame({index:e}){return this.call("setFrame",[e])}setLayer({index:e}){return this.call("setLayer",[e])}select({document:e}){return this.call("selectDocument",[e])}closeDocument({document:e}){return this.call("closeDocument",[e??null])}undo(){return this.call("undo",[])}redo(){return this.call("redo",[])}async openEditor(){if((await r.probe(this.base))?.connected)return{ok:!0,message:`Editor already connected at ${this.base}`};await hn(this.base);let t=Date.now()+1e4;for(;Date.now()<t;){if((await r.probe(this.base))?.connected)return{ok:!0,message:`Editor connected at ${this.base}`};await new Promise(o=>setTimeout(o,200))}return{ok:!1,message:`Opened ${this.base} but nothing connected \u2014 check the browser window.`}}close(){clearInterval(this.beat)}},ln=new Set(["new_sprite","begin_job","finish_job","draw_art","draw_animation","read_art","render_preview","render_animation","add_layer","add_frame","set_palette","set_fps","clear","trim_canvas","get_info","list_documents","challenges","open_editor","select_document","close_document","set_frame","set_layer","undo","redo"]);function it(r){let e=String(r??"").split(",")[1];if(!e)throw new Error("the editor returned something that was not an image");return Buffer.from(e,"base64")}function hn(r){let e=process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open";return new Promise(t=>sn(e,[r],()=>t()))}import{readFileSync as un}from"node:fs";var dn="0.6.0";function pn(){let r=new URL("./version.json",import.meta.url);try{let e=JSON.parse(un(r,"utf8"))?.version;if(typeof e=="string"&&e)return e;throw new Error("it names no version")}catch(e){throw new Error(`${r.pathname} could not be read as a version: ${e.message}`)}}var at=dn??pn();function me(r="room"){return`Pixel art editor with live collaboration. ${r==="local"?`Documents are tabs in a browser window on this machine; the user is looking
3
- at it while you work, and can take the canvas back at any moment.`:`Documents are rooms; other people and other agents may be in one with
2
+ var C=(r,e)=>()=>(r&&(e=r(r=0)),e);function He(r){let e="";for(let t=0;t<r.length;t+=32768)e+=String.fromCharCode.apply(null,r.subarray(t,t+32768));return btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}var Je=C(()=>{"use strict"});var D,Ft,qt,yr,Ve=C(()=>{"use strict";D="[A-Za-z0-9_-]{1,64}",Ft="token|members|link|slow|suggest|project",qt=[{owner:"accounts",re:new RegExp("^/auth/")},{owner:"accounts",re:new RegExp("^/a/[A-Za-z0-9_-]{1,64}\\.png$")},{owner:"accounts",re:new RegExp("^/projects(/|$)")},{owner:"accounts",re:new RegExp(`^/documents/${D}/(${Ft})$`)},{owner:"library",re:new RegExp("^/documents$")},{owner:"library",re:new RegExp("^/documents/(new|register|move|order|folders|folders/delete|trash|trash/empty)$")},{owner:"library",re:new RegExp(`^/documents/${D}(/(thumbnail\\.png|export|duplicate|rename|restore))?$`)},{owner:"library",re:new RegExp(`^/documents/${D}/history/at/\\d+/thumbnail\\.png$`)},{owner:"library",re:new RegExp("^/wall\\.png$")},{owner:"document",re:new RegExp(`^/documents/${D}/history(/at/\\d+|/\\d+|/source(/[A-Za-z0-9_-]{1,64})?)?$`)},{owner:"document",re:new RegExp(`^/documents/${D}/comments(/[A-Za-z0-9]{1,40})?$`)},{owner:"document",re:new RegExp(`^/documents/${D}/snapshots(/enabled|/[A-Za-z0-9_-]{1,128})?$`)},{owner:"document",re:new RegExp(`^/favourites(/order|/(document|project)/${D})?$`)}],yr=qt.map(({owner:r,re:e})=>({owner:r,source:e.source}))});var ye,be=C(()=>{"use strict";Ve();ye=["read","draw","create","duplicate","organize"]});async function Ke(r){let e=new TextEncoder().encode(String(r));return He(new Uint8Array(await crypto.subtle.digest("SHA-256",e)))}var vr,Xe=C(()=>{"use strict";Je();be();vr=120*1e3});function _(r=Date.now()){let e="0123456789ABCDEFGHJKMNPQRSTVWXYZ",t=(BigInt(r)&0xffffffffffffn)<<80n,n=crypto.getRandomValues(new Uint8Array(10)),o=0n;for(let i of n)o=o<<8n|BigInt(i);t|=o;let s="";for(let i=0;i<26;i++)s=e[Number(t&31n)]+s,t>>=5n;return s}function N(r){return r==null?{at:0}:typeof r=="number"?{at:r}:{frame:r}}var Kt,Xt,Zt,ne,z,xe,J=C(()=>{"use strict";Kt=new TextEncoder,Xt=new TextDecoder,Zt=1;ne=class r{#e;#n;constructor(e){this.#e=e,this.#n=e.exports}static async load(e){let t;if(e instanceof WebAssembly.Module)t=await WebAssembly.instantiate(e,{});else if(e instanceof ArrayBuffer||ArrayBuffer.isView(e))({instance:t}=await WebAssembly.instantiate(e,{}));else{let n=e instanceof Response||typeof e?.then=="function"?e:fetch(e);({instance:t}=await WebAssembly.instantiateStreaming(n,{}))}return new r(t)}get#i(){return new Uint8Array(this.#n.memory.buffer)}call(e,t={}){let n=Kt.encode(JSON.stringify({m:e,a:t})),o=this.#n.sprite_alloc(n.length);if(!o)throw new Error("sprite-core: allocation failed");let s=0;try{if(this.#i.set(n,o),s=this.#n.sprite_dispatch(o,n.length),!s)throw new Error("sprite-core: dispatch returned nothing");let i=this.#i,a=i[s]|i[s+1]<<8|i[s+2]<<16|i[s+3]<<24,c=i[s+4],h=i.slice(s+5,s+5+a);if(c===Zt)return h;let l=JSON.parse(Xt.decode(h));if(l&&l.error)throw new Error(l.error);return l}finally{s?this.#n.sprite_free_result(s):this.#n.sprite_dealloc(o,n.length)}}reset(){this.#n.sprite_reset()}newDocument(e={}){return this.call("doc.new",{w:32,h:32,name:"untitled",...e})}decodePng(e){let t=this.call("image.decode",{png:[...e]}),n=new DataView(t.buffer,t.byteOffset,t.byteLength),o=n.getUint32(0,!0),s=n.getUint32(4,!0);return{w:o,h:s,pixels:t.subarray(8)}}openSession(e,t,n=1){let{h:o}=this.call("session.new",{doc:e,author:t,seed:n});return new z(this,o)}openRoom(e,{seq:t=0}={}){let{h:n}=this.call("room.new",{doc:e,...t>0?{seq:t}:{}});return new xe(this,n)}},z=class{constructor(e,t){this.core=e,this.h=t}#e(e,t={}){return this.core.call(e,{h:this.h,...t})}state(){return this.#e("session.state")}apply(e,{now:t=Date.now(),label:n="Edit",job:o=null}={}){let s={now:t,label:n,bodies:Array.isArray(e)?e:[e]};return o&&(s.job=o),this.#e("session.apply",s).ops}remote(e){return this.#e("session.remote",{sop:e})}reject(e){return this.#e("session.reject",{op:e})}undo(e=Date.now()){return this.#e("session.undo",{now:e}).ops}redo(e=Date.now()){return this.#e("session.redo",{now:e}).ops}pending(){return this.#e("session.pending")}checkpoint({doc:e=!0}={}){return this.#e("session.checkpoint",{doc:e})}save(){return this.#e("session.save")}drawArt(e,t,n={}){return this.#e("session.drawArt",{rows:e,colors:t,...n}).bodies}drawAnimation(e,t,n={}){return this.#e("session.drawAnimation",{frames:e,colors:t,...n}).bodies}readArt(e,t,n={}){return this.#e("session.readArt",{layer:e,...N(t),...n})}cel(e,t){return this.#e("session.cel",{layer:e,...N(t)})}layerImage(e,t){return this.#e("session.layerImage",{layer:e,...N(t)})}tileCel(e,t){return this.#e("session.tileCel",{layer:e,...N(t)})}tile(e,t){return this.#e("session.tile",{tileset:e,index:t})}composite(e,{include:t="export"}={}){return this.#e("session.composite",{...N(e),include:t})}png(e,{scale:t=1,rect:n=null}={}){return this.#e("session.png",{...N(e),scale:Math.max(1,Math.round(t)||1),...n?{rect:n}:{}})}contactSheet({columns:e=4,scale:t=2}={}){return this.#e("session.sheet",{columns:e,scale:t})}thumbnail(e=48){return this.#e("session.thumbnail",{size:e})}card({frame:e=null,width:t=1200,height:n=630,framing:o=null,bleed:s=0,raw:i=!1}={}){return this.#e("session.card",{width:t,height:n,...o??{},...N(e),...s?{bleed:s}:{},...i?{raw:i}:{}})}cardLayout({frame:e=null,width:t=1200,height:n=630,framing:o=null}={}){return this.#e("session.cardLayout",{width:t,height:n,...o??{},...N(e)})}fit({width:e,height:t,margin:n=0,inscribed:o=!1,dx:s=0,dy:i=0,frame:a=null}){return this.#e("session.fit",{width:e,height:t,margin:n,inscribed:o,dx:s,dy:i,...N(a)})}close(){this.#e("session.free")}},xe=class{constructor(e,t){this.core=e,this.h=t}#e(e,t={}){return this.core.call(e,{h:this.h,...t})}state(){return this.#e("room.state")}submit(e,t=Date.now()){return this.#e("room.submit",{op:e,now:t})}since(e){return this.#e("room.since",{seq:e})}compact(){return this.#e("room.compact")}save(){return this.#e("room.save")}lease(e,t){return this.#e("room.lease",{action:e,...t})}region(e,t,n){return this.#e("room.region",{layer:e,frame:t,rect:{x:n.x??0,y:n.y??0,w:n.w,h:n.h}})}close(){this.#e("room.free")}}});function ot(r){return JSON.stringify(r)}function it(r){let e=r.rle instanceof Uint8Array?r.rle:null,t=new TextEncoder().encode(JSON.stringify({...r,rle:void 0}));if(t.length>Yt)return null;let n=new Uint8Array(3+t.length+(e?.length??0));return n[0]=st,n[1]=t.length&255,n[2]=t.length>>8&255,n.set(t,3),e&&n.set(e,3+t.length),n}function Qt(r){return r instanceof Uint8Array?r:r instanceof ArrayBuffer?new Uint8Array(r):ArrayBuffer.isView(r)?new Uint8Array(r.buffer,r.byteOffset,r.byteLength):null}function at(r){if(typeof r!="string"){let e=Qt(r);if(!e||e.length<3||e[0]!==st)return null;let t=e[1]|e[2]<<8;if(e.length<3+t)return null;try{let n=JSON.parse(new TextDecoder().decode(e.subarray(3,3+t)));if(!n||typeof n.t!="string")return null;let o=e.subarray(3+t);return n.rle=o.length?Uint8Array.from(o):null,n}catch{return null}}try{let e=JSON.parse(r);return e&&typeof e.t=="string"?e:null}catch{return null}}var S,nt,$,rt,st,Yt,ct=C(()=>{"use strict";S={HELLO:"hello",SUBMIT:"submit",PRESENCE:"presence",STROKE:"stroke",LEASE:"lease",REVIEW:"review",PING:"ping",PROMPT:"prompt",ANSWER:"answer",STOP:"stop"},nt={DOCUMENT:"document",LIBRARY:"library"},$={WELCOME:"welcome",REHELLO:"rehello",OPS:"ops",REJECT:"reject",PRESENCE:"presence",STROKE:"stroke",LEASE:"lease",INK:"ink",SUGGESTIONS:"suggestions",SUGGESTION:"suggestion",ERROR:"error",PONG:"pong",PROMPT:"prompt",ANSWER:"answer",STOP:"stop"},rt={OPS:"ops",SNAPSHOT:"snapshot"};st=157,Yt=65535});function ht(r){return _().toLowerCase().slice(0,16)}var lt=C(()=>{"use strict";J()});function tn(r){let e=[];if(!r.length)return new Uint8Array;let t=r[0],n=r[1],o=r[2],s=r[3],i=0,a=()=>{let c=i;do{let h=c&127;c>>>=7,e.push(c?h|128:h)}while(c);e.push(t,n,o,s)};for(let c=0;c<r.length;c+=4)r[c]===t&&r[c+1]===n&&r[c+2]===o&&r[c+3]===s?i++:(a(),t=r[c],n=r[c+1],o=r[c+2],s=r[c+3],i=1);return a(),new Uint8Array(e)}function nn(r){let e="";for(let n=0;n<r.length;n+=32768)e+=String.fromCharCode.apply(null,r.subarray(n,n+32768));return btoa(e)}function rn(r){return nn(tn(r))}function dt(r,e,t,n){return{t:"paintRegion",layer:r,frame:e,rect:{x:t.x,y:t.y,w:t.w,h:t.h},rle:rn(n)}}var pt=C(()=>{"use strict"});import{readFile as er}from"node:fs/promises";import{readFile as Ot,writeFile as Nt,mkdir as It,chmod as Mt}from"node:fs/promises";import{readFileSync as Ct}from"node:fs";import{homedir as Ut,hostname as Lt}from"node:os";import{join as we}from"node:path";import{execFile as Dt}from"node:child_process";var x=r=>String(r??"").replace(/\/+$/,"").replace(/^ws/,"http"),Oe=r=>String(r??"").replace(/\/+$/,"").replace(/^http/,"ws");async function Ne(r,e){if(!r?.ok||!(r.headers.get("content-type")??"").includes("json"))return null;let t=await r.json().catch(()=>null);return!t||typeof t!="object"?null:e.some(n=>n in t)?t:null}async function Ie(r,{timeoutMs:e=3e3,fetchImpl:t=fetch}={}){try{let n=await t(`${x(r)}/setup`,{signal:AbortSignal.timeout(e)});return await Ne(n,["accounts","expectedPort","install"])}catch{return null}}var fe=new Map;function Me(r,{fetchImpl:e=fetch,timeoutMs:t=3e3}={}){let n=x(r);return fe.has(n)||fe.set(n,Pt(r,{fetchImpl:e,timeoutMs:t})),fe.get(n)}async function Pt(r,{fetchImpl:e,timeoutMs:t}){try{let n=await e(`${x(r)}/config.json`,{signal:AbortSignal.timeout(t)}),s=(await Ne(n,["rooms","accounts"]))?.rooms;if(typeof s=="string"&&/^(wss?|https?):\/\/./.test(s))return Oe(s)}catch{}return Oe(r)}var W=process.env.SPRITE_PUBLIC_SERVER||zt()||null;function zt(){return"wss://drawpixie.com"}var Ce=we(process.env.XDG_CONFIG_HOME||we(Ut(),".config"),"sprite"),ge=we(Ce,"agent.json"),Ue=r=>String(r??"").replace(/\/+$/,"");function Le(){return`${Lt().replace(/\.local$/,"")}`.slice(0,60)||"a computer"}async function De(){try{let r=JSON.parse(await Ot(ge,"utf8"));return r&&typeof r=="object"&&r.servers?r:{servers:{}}}catch{return{servers:{}}}}async function H(r){return process.env.SPRITE_TOKEN?{token:process.env.SPRITE_TOKEN,from:"the environment"}:(await De()).servers[Ue(r)]??null}async function ze(r,e){await It(Ce,{recursive:!0,mode:448});let t=await De();t.servers[Ue(r)]={...e,at:Date.now()};let n=`${ge}.tmp`;await Nt(n,JSON.stringify(t,null,2),{mode:384}),await Mt(n,384);let{rename:o}=await import("node:fs/promises");await o(n,ge)}async function Fe(r,{timeoutMs:e=3e3}={}){try{let t=await fetch(`${x(r)}/setup`,{signal:AbortSignal.timeout(e)});return t.ok?!!(await t.json()).accounts:!1}catch{return!1}}async function qe(r,e,{session:t=null}={}){let n=t??await H(r);if(!n?.token)return;let o=await fetch(`${x(r)}/documents/${encodeURIComponent(e)}/token`,{headers:{Authorization:`Bearer ${n.token}`}});if(o.status===401)throw new Error("this machine is signed out \u2014 pair it again");if(o.status===403)throw new Error(`no access to ${e}`);if(!o.ok)throw new Error(`could not get access to ${e}`);return(await o.json()).token}async function Be(r,{name:e,w:t,h:n},{session:o=null}={}){let s=o??await H(r);if(!s?.token)return;let i=await fetch(`${x(r)}/documents/new`,{method:"POST",headers:{Authorization:`Bearer ${s.token}`,"Content-Type":"application/json"},body:JSON.stringify({name:e,w:t,h:n})});if(i.status===401)throw new Error("this machine is signed out \u2014 pair it again");if(!i.ok)throw new Error(`could not make a document: the server said ${i.status}`);let{id:a}=await i.json();if(typeof a!="string"||!a)throw new Error("could not make a document: the server named none");return a}async function Ge(r,e,{method:t="GET",body:n=null,session:o=null}={}){let s=o??await H(r);if(!s?.token)return;let i=await fetch(`${x(r)}${e}`,{method:t,headers:{Authorization:`Bearer ${s.token}`,...n!==null?{"Content-Type":"application/json"}:{}},body:n!==null?JSON.stringify(n):void 0});if(i.status===401)throw new Error("this machine is signed out \u2014 pair it again");let a=await i.json().catch(()=>({}));if(!i.ok)throw new Error(a?.error?String(a.error):`the server said ${i.status}`);return a}function We(r){let e=process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open";return new Promise(t=>Dt(e,[r],()=>t()))}Xe();be();import{createServer as Bt}from"node:http";import{randomBytes as Ye}from"node:crypto";var Gt=300*1e3,Wt=()=>Ye(32).toString("base64url"),Ht=()=>Ye(16).toString("base64url");function Ze(r,e){return`<!doctype html><meta charset="utf-8"><title>Pixie</title><body style="background:#101014;color:#d8d8e0;font:14px/1.6 system-ui,sans-serif;display:grid;place-items:center;height:100vh;margin:0;text-align:center"><div><p style="font-size:16px">${r}</p><p style="color:#8a8a99;font-size:12px">${e}</p></div>`}async function Jt({state:r,timeoutMs:e}){let t,n,o=new Promise((c,h)=>{t=c,n=h}),s=Bt((c,h)=>{let l=new URL(c.url,"http://127.0.0.1");if(l.pathname!=="/callback"){h.writeHead(404).end();return}if(l.searchParams.get("state")!==r){h.writeHead(404).end();return}let u=l.searchParams.get("error");h.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}).end(u?Ze("Nothing was granted.","You can close this window."):Ze("This computer can now draw in your sprites.","You can close this window and go back to what you were doing.")),t(l.searchParams)});await new Promise((c,h)=>{s.once("error",h),s.listen(0,"127.0.0.1",c)});let i=setTimeout(()=>n(new Error("nobody answered the browser in time")),e);i.unref?.();let{port:a}=s.address();return{redirect:`http://127.0.0.1:${a}/callback`,wait:o,close:()=>{clearTimeout(i),s.close(),s.closeAllConnections?.()}}}async function Qe(r,{onUrl:e=()=>{},open:t=We,label:n="the Pixie agent",scope:o=ye,timeoutMs:s=Gt}={}){let i=Wt(),a=await Ke(i),c=Ht(),h=await Jt({state:c,timeoutMs:s});try{let l=new URL(`${x(r)}/authorize`);l.searchParams.set("challenge",a),l.searchParams.set("state",c),l.searchParams.set("redirect",h.redirect),l.searchParams.set("scope",o.join(",")),l.searchParams.set("name",n),e(l.toString()),await t(l.toString());let u=await h.wait,p=u.get("error");if(p)throw new Error(p==="access_denied"?"you said no on that screen, so nothing was granted":`that request was refused: ${p}`);let d=u.get("code");if(!d)throw new Error("the browser came back without an authorization");let f=await fetch(`${x(r)}/auth/authorize/exchange`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({code:d,verifier:i,name:Le()})}),m=await f.json().catch(()=>({}));if(!f.ok||!m.token)throw new Error(m.error||"that authorization could not be exchanged");return await ze(r,{token:m.token,name:m.name,email:m.email,scope:m.scope??null}),m}finally{h.close()}}import{realpathSync as et}from"node:fs";import{fileURLToPath as Vt}from"node:url";function tt(r){let e=process.argv[1];if(!e)return!1;try{return et(Vt(r))===et(e)}catch{return!1}}J();import{execFile as vt}from"node:child_process";ct();J();var en=5e3,re=class{#e=null;constructor(e,t,n){if(!n||!n.author)throw new Error("DocumentClient needs an author");if(this.core=e,this.author=n.author,this.name=n.name||"anonymous",this.agent=n.agent===!0,this.answers=n.answers===!0,this.seed=n.seed??1,this.epoch=0,this.resumed=null,n.resume){let{h:o,dropped:s}=e.call("session.resume",{doc:t,author:this.author,seed:this.seed,seq:n.resume.seq??0,pending:n.resume.pending??[]});this.session=new z(e,o),this.resumed={seq:n.resume.seq??0,dropped:s??[]}}else this.session=e.openSession(t,this.author,this.seed);this.getToken=n.getToken??null,this.transport=null,this.connected=!1,this.welcomed=!1,this.ready=new Promise(o=>{this.#e=o}),this.participants=[],this.leases=[],this.presence=null,this.you=null,this.onChange=()=>{},this.onPresence=()=>{},this.onStroke=()=>{},this.strokePreview=!0,this.onLeases=()=>{},this.ink=null,this.onInk=()=>{},this.proposals=new Map,this.suggesting=!1,this.onSuggestions=()=>{},this.onLost=()=>{},this.waiters=new Set,this.onPrompt=()=>{},this.onAnswer=()=>{},this.onStop=()=>{},this.onStatus=()=>{}}apply(e,t={}){let n=this.session.apply(e,t);return this.#l(n),this.onChange(),n}acked(e,{timeoutMs:t=3e4}={}){let n=new Set((Array.isArray(e)?e:[e]).map(o=>o?.opId??o).filter(Boolean));return new Promise((o,s)=>{let i={ids:n,resolve:o,reject:s,timer:null};i.timer=setTimeout(()=>{this.waiters.delete(i);let a=[...n].filter(c=>this.session.pending().some(h=>h.opId===c)).length;s(new Error(`${a} operation${a===1?"":"s"} not confirmed by the room after ${Math.round(t/1e3)}s \u2014 `+(this.connected?"it is answering slowly":`the connection dropped; nothing since sequence ${this.session.state().seq} has been saved`)))},t),this.waiters.add(i),this.#n()})}#n(){if(!this.waiters.size)return;let e=new Set(this.session.pending().map(t=>t.opId));for(let t of this.waiters)[...t.ids].some(n=>e.has(n))||(this.waiters.delete(t),clearTimeout(t.timer),t.resolve())}#i(e,t){for(let n of this.waiters)n.ids.has(e)&&(this.waiters.delete(n),clearTimeout(n.timer),n.reject(new Error(t)))}undo(e=Date.now()){let t=this.session.undo(e);return this.#l(t),this.onChange(),t}redo(e=Date.now()){let t=this.session.redo(e);return this.#l(t),this.onChange(),t}setPresence(e){this.presence=e,this.connected&&this.#r({t:S.PRESENCE,...e})}sendStroke({layer:e,frame:t,rect:n,rle:o,done:s=!1}){if(!this.connected||!this.welcomed||!this.strokePreview||this.suggesting)return;this._strokeSentAt=Date.now();let i=it({t:S.STROKE,layer:e,frame:t,rect:n,rle:o,done:s});i&&this.#s(i)}suggest(e){if(!e.length||!this.connected||!this.welcomed)return[];let t=this.session.state().seq,n=e.map(o=>({opId:_(),author:this.author,parentSeq:t,...o}));return this.#r({t:S.SUBMIT,ops:n}),n}review(e,t){!this.connected||!this.welcomed||this.#r({t:S.REVIEW,author:e,verdict:t})}lease(e,t={}){this.#r({t:S.LEASE,action:e,...t})}prompt(e,{where:t=nt.DOCUMENT}={}){if(!this.connected)return null;let n=String(e??"").trim().slice(0,2e3);if(!n)return null;let o=_();return this.#r({t:S.PROMPT,id:o,text:n,where:t}),o}answer(e,t="",{reply:n=null,made:o=null,model:s=null}={}){return this.connected?(this.#r({t:S.ANSWER,state:e,text:String(t??"").slice(0,2e3),reply:n,made:o,model:s}),!0):!1}stopAnswer(e){return!this.connected||!e?!1:(this.#r({t:S.STOP,id:e}),!0)}get state(){return this.session.state()}checkpoint({doc:e=!0}={}){return{epoch:this.epoch,author:this.author,...this.session.checkpoint({doc:e})}}composite(e){return this.session.composite(e)}connect(e){return this.transport=e,this.pendingToken=this.getToken?this.getToken():void 0,this.pendingToken?.catch?.(()=>{}),e.onopen=async()=>{this.connected=!0,this.welcomed=!1,clearInterval(this.heartbeat),this.heartbeat=setInterval(()=>{this.connected&&this.#r({t:S.PING})},en),await this.#a()},e.onmessage=t=>this.#o(t),e.onclose=()=>{this.connected=!1,this.welcomed=!1,clearInterval(this.heartbeat),this.onStatus({connected:!1})},e.readyState===1&&e.onopen(),this}disconnect(){if(this.transport){this.connected=!1,this.welcomed=!1,clearInterval(this.heartbeat);try{this.transport.close()}catch{}this.transport=null,this.onStatus({connected:!1})}}async#a(){let e;if(this.pendingToken!==void 0){let n=this.pendingToken;this.pendingToken=void 0,e=await n}else e=this.getToken?await this.getToken():void 0;let t=this.session.state();this.#r({t:S.HELLO,author:this.author,name:this.name,token:e,since:t.seq,...this.agent?{agent:!0}:{},...this.answers?{answers:!0}:{},...this.presence?.active===!1?{active:!1}:{},docId:t.doc.id})}#o(e){let t=at(e);if(t)switch(t.t){case $.WELCOME:return this.#u(t);case $.REHELLO:return this.welcomed=!1,void this.#a();case $.OPS:return this.#t(t);case $.REJECT:return this.#h(t);case $.PRESENCE:return this.participants=t.participants||[],this.onPresence(this.participants);case $.STROKE:return this.onStroke(t);case $.LEASE:return this.leases=t.leases||[],this.onLeases(this.leases);case $.INK:return this.ink={left:t.left,cap:t.cap,refillMs:t.refillMs,nextInMs:t.nextInMs??0,at:Date.now()},this.onInk(this.ink);case $.SUGGESTIONS:return this.proposals=new Map((t.proposals??[]).map(n=>[n.author,{...n,ops:[...n.ops??[]]}])),this.onSuggestions(this.proposals);case $.SUGGESTION:{if(t.gone)return this.proposals.delete(t.author),this.onSuggestions(this.proposals,{author:t.author,name:t.name??null,gone:t.gone,by:t.by??null,applied:t.applied??0});let n=this.proposals.get(t.author)??{author:t.author,name:t.name??null,cc:t.cc??null,ops:[],at:t.at};if(n.name=t.name??n.name,n.at=t.at??n.at,t.replaced?.length){let o=new Set(t.replaced);n.ops=n.ops.filter(s=>!(s.rect?.w===1&&s.rect?.h===1&&o.has(`${s.rect.x},${s.rect.y}`)))}return n.ops.push(...t.ops??[]),this.proposals.set(t.author,n),this.onSuggestions(this.proposals,{author:t.author,added:t.ops?.length??0})}case $.PROMPT:return this.onPrompt(t);case $.ANSWER:return this.onAnswer(t);case $.STOP:return this.onStop(t);case $.ERROR:if(this.#d(t.message)){this.strokePreview=!1;return}return this.onLost({kind:t.fatal?"fatal":"protocol",message:t.message});default:return}}#u(e){this.you=e.you,this.suggesting=e.you?.suggest===!0,this.welcomed=!0;let t=this.session.pending();if(e.catchup.kind===rt.SNAPSHOT){let{h:n,dropped:o}=this.core.call("session.resume",{doc:e.catchup.doc,author:this.author,seed:this.seed,seq:e.catchup.seq,pending:t});if(this.session.close(),this.session=new z(this.core,n),this.epoch++,o&&o.length){for(let s of o)this.#i(s,"could not be replayed onto the current document");this.onLost({kind:"rebased",ops:o,message:`${o.length} edit${o.length>1?"s":""} could not be replayed onto the current document`})}}else for(let n of e.catchup.ops||[])this.#c(n);if(this.#n(),this.participants=e.participants||[],this.leases=e.leases||[],t.length&&this.suggesting)for(let n of t)this.session.reject(n.opId);else t.length&&this.#r({t:S.SUBMIT,ops:t});this.presence&&this.#r({t:S.PRESENCE,...this.presence}),this.onStatus({connected:!0,resent:t.length}),this.onPresence(this.participants),this.onLeases(this.leases),this.onChange(),this.#e?.(),this.#e=null}#t(e){let t=!1;for(let n of e.ops||[])this.#c(n),t=!0;t&&(this.#n(),this.onChange())}#c(e){let t=this.session.remote(e);t.dropped&&t.dropped.length&&this.onLost({kind:"obsolete",ops:t.dropped,message:`${t.dropped.length} of your edits no longer apply and were dropped`})}#h(e){e.opId&&this.session.reject(e.opId),e.opId&&this.#i(e.opId,e.message),this.onLost({kind:e.kind,ops:e.opId?[e.opId]:[],message:e.message}),this.onChange()}#l(e){!e.length||!this.connected||!this.welcomed||this.#r({t:S.SUBMIT,ops:e})}#r(e){this.#s(ot(e))}#d(e){return typeof e!="string"?!1:e===`unknown message: ${S.STROKE}`?!0:e==="unreadable message"&&Date.now()-(this._strokeSentAt??0)<2e3}#s(e){if(this.transport)try{this.transport.send(e)}catch{this.connected=!1}}close(){this.disconnect(),this.session.close()}};J();lt();var O=r=>{let e=String(r).replace("#","");return[parseInt(e.slice(0,2),16),parseInt(e.slice(2,4),16),parseInt(e.slice(4,6),16)]};var F=([r,e,t])=>.3*r+.59*e+.11*t;function oe([r,e,t]){let n=Math.max(r,e,t),o=Math.min(r,e,t),s=n-o,i=0;return s&&(n===r?i=(e-t)/s%6:n===e?i=(t-r)/s+2:i=(r-e)/s+4,i*=60,i<0&&(i+=360)),[i,n?s/n:0,n/255]}var ut={luminance:(r,e)=>F(O(r))-F(O(e)),hue:(r,e)=>{let[t,n]=oe(O(r)),[o,s]=oe(O(e));return n<.08||s<.08?n<.08&&s<.08?F(O(r))-F(O(e)):n<.08?-1:1:Math.round(t/20)!==Math.round(o/20)?t-o:F(O(r))-F(O(e))},saturation:(r,e)=>oe(O(r))[1]-oe(O(e))[1]};function q(r,e="hue"){let t=ut[e]??ut.hue;return[...r].sort(t)}pt();import{readFile as K,writeFile as An,mkdir as Rn}from"node:fs/promises";import{access as on,mkdir as sn,writeFile as an}from"node:fs/promises";import{dirname as cn}from"node:path";async function se(r,e,{overwrite:t=!1,seq:n=null}={}){let o=await on(r).then(()=>!0,()=>!1);if(o&&!t){let s=r.replace(/(\.png)?$/i,`-seq${n??"N"}$1`);throw new Error(`${r} already exists \u2014 a chat that showed it once will keep showing the old picture, so a preview gets a new name each time: ${s}. Pass overwrite: true only for a file nothing has shown yet.`)}return await sn(cn(r),{recursive:!0}),await an(r,e),o}import{createHash as Tn}from"node:crypto";import{tmpdir as Pn}from"node:os";import{join as _e,basename as Ae,extname as On,resolve as Nn}from"node:path";function Se(r){try{return/^(localhost|127\.0\.0\.1|\[::1\]|::1)$/.test(new URL(x(r)).hostname)}catch{return!1}}function mt(r,{publicServer:e=W}={}){let t=x(r);return e&&r===e?`nothing is answering at ${t} \u2014 this machine is offline, or the site is down`:Se(r)?`nothing is answering at ${t} \u2014 start the editor with \`npm start\` in the Pixie repository, then try again`:`nothing is answering at ${t} \u2014 check the address and that whatever serves it is up`}import{readFile as bn,writeFile as gt}from"node:fs/promises";function U(r,e=0){if(r==null||typeof r=="number"||typeof r=="boolean")return r;if(typeof r=="string")return r.length>120?`${r.slice(0,119)}\u2026`:r;if(Array.isArray(r)){if(r.length&&r.every(t=>typeof t=="string")){let t=new Set(r.map(n=>n.length));return`${r.length}\xD7${t.size===1?r[0].length:`${Math.min(...t)}\u2013${Math.max(...t)}`}`}return`[${r.length}]`}if(typeof r=="object"){let t=Object.keys(r),n=t.length<=8&&t.every(s=>r[s]==null||typeof r[s]!="object");if(t.length>32||e>0&&!n)return`{${t.length}}`;let o={};for(let s of t)o[s]=U(r[s],e+1);return o}return String(r)}function hn(r){if(r&&typeof r=="object"&&Array.isArray(r.content)){let e=r.content.find(n=>n.type==="image"),t=r.content.find(n=>n.type==="text")?.text??"";if(r.isError)return{ok:!1,error:U(t)};if(e)return{ok:!0,result:`image \u2014 ${U(t)}`};try{return{ok:!0,result:U(JSON.parse(t))}}catch{return{ok:!0,result:U(t)}}}return{ok:!0,result:U(r)}}var ie=class{constructor(){this.entries=[],this.next=1,this.open=new Map,this.starts=new Map,this.idleFrom=1,this.lead=new Map}async record(e,t,n){let o=Date.now(),s={i:this.next++,t:new Date(o).toISOString(),tool:e,args:U(t??{})};try{let i=await n();return Object.assign(s,hn(i),{ms:Date.now()-o}),this.#e(s),i}catch(i){throw Object.assign(s,{ok:!1,error:U(i?.message??String(i)),ms:Date.now()-o}),this.#e(s),i}}#e(e){this.entries.push(e),this.entries.length>4e3&&this.entries.splice(0,this.entries.length-4e3)}began(e){if(!e)return;let t=this.entries.at(-1)?.i??this.next;this.open.size===0&&this.idleFrom<t&&this.lead.set(e,this.idleFrom),this.open.set(e,t),this.starts.set(t,e)}finished(e){let t=e??(this.open.size===1?[...this.open.keys()][0]:null);if(!t||!this.open.has(t))return[];let n=this.open.get(t);this.open.delete(t);let o=this.lead.get(t)??n;return this.lead.delete(t),this.open.size===0&&(this.idleFrom=this.next),this.entries.filter(s=>s.i>=o&&(s.args?.job==null||s.args.job===t)&&(this.starts.get(s.i)??t)===t).map(s=>s.i<n?{...s,between:!0}:s)}};function ft(r,e){let t=r.inputSchema??{},n=t.properties??{},o=Object.keys(n),s=e&&typeof e=="object"?e:{},i={};for(let[c,h]of Object.entries(s)){if(!(c in n)){let l=un(c,o),u=o.length?`${r.name} takes: ${o.join(", ")}.`:`${r.name} takes no arguments.`;throw new Error(`no such argument \`${c}\`${l?` \u2014 did you mean \`${l}\`?`:""} ${u}`)}i[c]=ln(r.name,c,n[c],h)}let a=(t.required??[]).filter(c=>i[c]===void 0);if(a.length)throw new Error(`${r.name} needs ${a.map(c=>`\`${c}\``).join(" and ")}. It takes: ${o.join(", ")}.`);return i}function ln(r,e,t,n){if(n==null)return n;let o=t.type;if(o==="integer"||o==="number"){let s=typeof n=="string"&&n.trim()!==""?Number(n):n;if(typeof s!="number"||!Number.isFinite(s))throw new Error(`\`${e}\` must be a number, got ${B(n)}. ${r} takes \`${e}\` as ${o==="integer"?"an integer":"a number"}.`);if(o==="integer"&&!Number.isInteger(s))throw new Error(`\`${e}\` must be a whole number, got ${s}.`);if(t.minimum!==void 0&&s<t.minimum)throw new Error(`\`${e}\` must be at least ${t.minimum}, got ${s}.`);if(t.maximum!==void 0&&s>t.maximum)throw new Error(`\`${e}\` must be at most ${t.maximum}, got ${s}.`);return s}if(o==="boolean"){if(n==="true")return!0;if(n==="false")return!1;if(typeof n!="boolean")throw new Error(`\`${e}\` must be true or false, got ${B(n)}.`);return n}if(o==="string"&&typeof n!="string"){if(typeof n=="number")return String(n);throw new Error(`\`${e}\` must be text, got ${B(n)}.`)}if(o==="array"&&!Array.isArray(n))throw new Error(`\`${e}\` must be a list, got ${B(n)}.`);if(o==="object"&&(typeof n!="object"||Array.isArray(n)))throw new Error(`\`${e}\` must be an object, got ${B(n)}.`);if(t.enum&&!t.enum.includes(n))throw new Error(`\`${e}\` must be one of ${t.enum.map(s=>`\`${s}\``).join(", ")}, got ${B(n)}.`);return n}function B(r){return typeof r=="string"?`"${r.length>40?r.slice(0,37)+"\u2026":r}"`:Array.isArray(r)?"a list":r&&typeof r=="object"?"an object":String(r)}function un(r,e){let t=r.toLowerCase(),n=e.filter(i=>{let a=i.toLowerCase();return(t.startsWith(a)||a.startsWith(t))&&a!==t});if(n.length)return n.sort((i,a)=>Math.abs(i.length-t.length)-Math.abs(a.length-t.length))[0];let o=null,s=3;for(let i of e){let a=dn(t,i.toLowerCase());a<s&&(s=a,o=i)}return o}function dn(r,e){let t=Array.from({length:e.length+1},(n,o)=>o);for(let n=1;n<=r.length;n++){let o=t[0];t[0]=n;for(let s=1;s<=e.length;s++){let i=t[s];t[s]=Math.min(t[s]+1,t[s-1]+1,o+(r[n-1]===e[s-1]?0:1)),o=i}}return t[e.length]}import{readFileSync as pn}from"node:fs";import{execFileSync as mn}from"node:child_process";import{fileURLToPath as fn}from"node:url";import{dirname as wn}from"node:path";var wt="0.6.1";function gn(){let r=new URL("./version.json",import.meta.url);try{let e=JSON.parse(pn(r,"utf8"))?.version;if(typeof e=="string"&&e)return e;throw new Error("it names no version")}catch(e){throw new Error(`${r.pathname} could not be read as a version: ${e.message}`)}}var ae=wt??gn();function yn(){if(wt)return null;let r=wn(fn(import.meta.url)),e=(...t)=>mn("git",t,{cwd:r,stdio:["ignore","pipe","ignore"],timeout:2e3}).toString().trim();try{let t=e("rev-parse","--short=12","HEAD");return t?e("status","--porcelain","--",".","../../edge/src","../../core/js")?`${t}-dirty`:t:null}catch{return null}}var $e=yn();var ce={type:"string",description:"#rrggbb"};async function xn(r,e){let t=await bn(r).catch(s=>{throw new Error(`could not read ${r}: ${s.code==="ENOENT"?"no such file":s.message}`)});if(t.length>8&&t[0]===137&&t[1]===80&&t[2]===78&&t[3]===71){if(!e.core?.decodePng)throw new Error("this server cannot decode a PNG \u2014 send rows and colors");return Sn(e.core.decodePng(t))}let o;try{o=JSON.parse(t.toString("utf8"))}catch{throw new Error(`${r} is neither a PNG nor JSON`)}if(!o||typeof o!="object"||!Array.isArray(o.rows)||typeof o.colors!="object")throw new Error(`${r} needs "rows" (an array of strings) and "colors" (a map of character to #rrggbb)`);return o}function Sn({w:r,h:e,pixels:t}){let n={},o=new Map,s=33,i=h=>{let l=o.get(h);if(l)return l;for(;;)if(s===34||s===46||s===92||s===127)s+=1;else if(s>=128&&s<161)s=161;else if(s>=55296&&s<57344)s=57344;else break;return l=String.fromCodePoint(s),s+=1,o.set(h,l),n[l]=h,l},a=h=>h.toString(16).padStart(2,"0"),c=[];for(let h=0;h<e;h++){let l="";for(let u=0;u<r;u++){let p=(h*r+u)*4,d=t[p+3];if(d===0){l+=".";continue}let f=d===255?`#${a(t[p])}${a(t[p+1])}${a(t[p+2])}`:`#${a(t[p])}${a(t[p+1])}${a(t[p+2])}${a(d)}`;l+=i(f)}c.push(l)}return{rows:c,colors:n}}var E={type:"string",description:"The job this call belongs to. May be omitted when exactly one job is open."},V={type:"string",description:"Which document. Omit to use the one your job owns. The id \u2014 the one new_sprite, get_info and list_documents hand back \u2014 or the exact name when only one document has it. In a local editor a 0-based tab index works too."},$n=r=>({isError:!0,content:[{type:"text",text:r}]}),Ee=r=>({content:[{type:"text",text:typeof r=="string"?r:JSON.stringify(r)}]}),yt=(r,e)=>({content:[{type:"image",data:Buffer.from(r).toString("base64"),mimeType:"image/png"},{type:"text",text:e}]}),R={w:240,h:126},bt={w:80,h:42},En="40\xD721, 80\xD742, 120\xD763, 160\xD784, 240\xD7126, 480\xD7252, 600\xD7315",kn="40\xD740, 50\xD750, 80\xD780, 100\xD7100",jn="60\xD720, 75\xD725, 150\xD750, 300\xD7100";function ke(r,e){if(!r||!e)return null;let t=r/e,n=1200/630;if(Math.abs(t-n)<.15){let o=Math.round(r/n);return`${r}\xD7${e} is ${t.toFixed(2)}:1; the link-card shape at this width is ${r}\xD7${o} \u2014 resize_canvas if it is meant to be posted.`}return Math.abs(t-1)<.1?`${r}\xD7${e} is nearly square; ${Math.max(r,e)}\xD7${Math.max(r,e)} would be a profile picture.`:Math.abs(t-3)<.25?`${r}\xD7${e} is nearly 3:1; ${r}\xD7${Math.round(r/3)} would be a profile banner.`:null}function he(r,e){return!r||!e?null:r===e?"This is square, the shape of a profile picture: in the editor, file \u25B8 Save as profile picture (400\xD7400) fits it inside the circle it will be cropped to. Tell the user.":r===3*e?"This is 3:1, the shape of a profile banner: in the editor, file \u25B8 Save as profile banner (1500\xD7500) exports it at the size X wants. Tell the user.":Math.abs(r/e-1200/630)<.02?"This is the link-card shape: the share button posts it edge to edge.":null}function vn(r){let e=String(r??"").trim();if(!/^[A-Za-z0-9]+([-_][A-Za-z0-9]+)+$/.test(e))return e;let t=e.split(/[-_]+/).join(" ");return t[0].toUpperCase()+t.slice(1)}var _n=[{name:"new_sprite",description:"Create a sprite and claim it. Call this FIRST \u2014 before writing a script, computing a grid, or thinking about pixels. It costs milliseconds and puts a labeled document in front of the user immediately, so they can see what you are making and where. The tab opens in the background and never steals their focus.",inputSchema:{type:"object",properties:{name:{type:"string",description:'The title, the way a person would write it: "Blue cake", not "blue-cake". Shown on the tab and in the library; the URL is minted from it. A hyphenated lowercase name is turned into words.'},w:{type:"integer",default:R.w,description:`Width in pixels. WHAT THE USER ASKED FOR WINS: a stated size or shape is used as stated, and a named artist, piece or style is matched in scale and detail. The rest is defaults for when nothing was said: ${R.w}\xD7${R.h} \u2014 1.91:1, the ratio link cards crop to, so a piece drawn on it posts edge to edge, and big enough for a subject with parts (a dish with its slice and garnish, a figure with props, a room). ${bt.w}\xD7${bt.h} when the user asks for something SIMPLE \u2014 "simple", "quick", "small", "minimal", "a little", "basic", "rough", "doodle", "icon-like" \u2014 bold shapes and big pixels. Any multiple of 40\xD721 fills the card (${En}: examples, not limits). A PROFILE PICTURE is square and cropped to a circle \u2014 keep the subject inside it: ${kn}. A BANNER is 3:1: ${jn}. Use a game asset's own size (8\xD78, 16\xD716, 32\xD732) only when the user asks for a tile, icon, or sprite for a game.`},h:{type:"integer",default:R.h,description:"Height in pixels. See w."},palette:{type:"array",items:ce,description:"Optional starting palette. Kept in ramp order \u2014 by hue, then dark to light \u2014 not the order given, so a script that indexes the document's palette by position will be off: map by hex."},intent:{type:"string",description:'One line on what you are making, including the VIEW. Shown while you work. The default view is three-quarter: the camera above and to one side, so an object shows its top and two sides and a scene has depth \u2014 unless the user says "flat", "side view", "front", "profile", "top-down", "icon" or "tile", which mean what they say and no perspective at all.'},folder:{type:"string",description:'Optional folder to file it in, by path (`food/sushi`). Made if it does not exist. Use this when the user asks for sprites "in a folder" rather than moving them afterwards.'},challenge:{type:"string",enum:["daily","weekly"],description:'"daily" when the user asks for the sprite of the day / today\'s theme / the Pixel Dailies; "weekly" for the sprite of the week / this week\'s word / Weekly Pixels. The name comes from the theme itself (pass `name` only to override), and finish_job will say how to post it as an entry. Call `challenges` first if you want to see the theme before drawing.'}},required:["intent"]},run:async(r,e)=>{let t=r.name?vn(r.name):null,n=r.w??null,o=r.h??null;if(r.challenge){let s=await e.challenge(r.challenge);if(t??=s.theme,n===null&&o===null&&s.sizes?.length){let i=s.sizes[Math.floor((s.sizes.length-1)/2)],[a,c]=i.split(/[x×]/).map(Number);a&&c&&([n,o]=[a,c])}}if(!t)throw new Error("new_sprite needs a name, or a challenge to take one from");return e.newSprite({name:t,w:n??R.w,h:o??R.h,intent:r.intent,palette:r.palette,folder:r.folder??null,challenge:r.challenge??null})}},{name:"challenges",description:`The sprite of the day and the sprite of the week: Pixel Dailies sets a theme every day ("Today's theme is #Gateway") and Weekly Pixels a word every week, with sizes. When the user says "the sprite of the day", "today's theme", "the daily", "the weekly", "this week's word" or names either community, this is what they mean \u2014 read it here rather than asking them. The server reads the accounts and keeps the answer; nothing is fetched from Bluesky by you.`,inputSchema:{type:"object",properties:{}},run:(r,e)=>e.challenges()},{name:"begin_job",description:"Claim an existing document for one piece of work \u2014 the other way to start, where new_sprite is the one for new art. Refused by name if another job holds it, which is what keeps parallel requests from colliding. With no `document` and no job open, it picks up the document you last finished \u2014 one more pass on the same sprite is begin_job({ intent }).",inputSchema:{type:"object",properties:{document:{type:"string",description:"Which document: the id, or the exact name when only one document has it. Omit, with no job open, for the one you last finished."},intent:{type:"string"},layer:{type:"string",description:"Restrict the claim to one layer, leaving the rest free. Rooms only."}},required:["intent"]},run:(r,e)=>e.beginJob({document:r.document,intent:r.intent,layer:r.layer??null})},{name:"finish_job",description:"Release the document. Call this when the sprite is done, so the user knows to look. The result says what to do next for a profile picture, a banner or a card \u2014 pass that on to the user \u2014 and carries `trace`: every call of this job, as the server saw it \u2014 and, marked `between: true`, any made with no job open since the last finish. When the task asked for a report back, save it as trace.jsonl (one entry per line) beside report.md; `versions` (which pixie-mcp this was \u2014 and, from a checkout, which commit) travels in the trace, and feedback.py stamps the report with it.",inputSchema:{type:"object",properties:{job:E}},run:async(r,e)=>({...await e.finishJob({job:r.job}),versions:{mcp:ae,...$e?{commit:$e}:{}}})},{name:"push_source",description:"Push the generator behind this job \u2014 the script(s) that made the sprite \u2014 so the next agent (or you, tomorrow) can pick the work up rather than redraw it from the pixels. Stored beside the job's mark in the document's version history, with the seq it reproduces; READABLE BY THIS ACCOUNT ONLY, never by anyone else in the room. Push the source, not its output: the .py, and anything it imports that is yours; not the grid JSON or a PNG, which it regenerates (text, \u2264 16 files, \u2264 96 KiB). `run` is the exact command; `takes` what it needs that is not in the push (a reference from Commons: its URL; the skill's references/ helpers by name); `unfinished` what you would do next. Push before finish_job, and again after a real change.",inputSchema:{type:"object",properties:{files:{type:"array",items:{type:"string"},description:"Paths on this machine to the files to push. Names are kept, directories are not."},run:{type:"string",description:'The command that regenerates the sprite from these files, e.g. "python3 buche.py .context/yule/solo.json && draw_art({ file })".'},takes:{type:"string",description:"What it needs that is not in the push: reference URLs, the references/ helpers it imports, a palette. Empty if nothing."},unfinished:{type:"string",description:"What is not done, or what you would change next. The next agent reads this first."},job:E},required:["files","run"]},run:(r,e)=>e.pushSource({job:r.job,files:r.files,run:r.run,takes:r.takes??"",unfinished:r.unfinished??""})},{name:"pull_source",description:"What this account pushed on a document: with no `job`, the list (job, seq, run, unfinished, file names) \u2014 with a `job`, its files written to disk under `into` (default .context/source/<job>/) with how to run them, what they take and what was unfinished. Call it before redrawing a sprite an agent made: if there is a push, start from it. Only your own pushes come back; a mark with `source: true` in the history that is not listed here is somebody else's. READ THE FILES BEFORE RUNNING THEM, and run them in the workspace with nothing the task does not need \u2014 they are code somebody pushed.",inputSchema:{type:"object",properties:{document:V,job:{type:"string",description:"The job whose push to write to disk. Omit to list."},into:{type:"string",description:"Directory to write the files into. Default .context/source/<job>/. A file that exists with different content is not overwritten."}}},run:(r,e)=>e.pullSource({document:r.document??null,job:r.job??null,into:r.into??null})},{name:"draw_art",description:'Returns when the room has confirmed the drawing \u2014 `seq` is the room\'s number for it and `committed` is true \u2014 or fails saying why; if it says the connection dropped, wait a moment and check get_info before drawing more. Draw a character grid. Each string in `rows` is one row of pixels and each character maps to a color via `colors`. A key is any single character except "." and " " \u2014 letters, digits, punctuation, any Unicode symbol \u2014 so a generator that runs past 62 colors keeps going. "." and " " leave existing pixels alone; a color mapped to null erases. Every character is resolved before a pixel moves, so a typo fails rather than half-drawing. With no style reference, keep the palette small \u2014 four to eight colors, reused for shading \u2014 because that is what makes a small sprite read as pixel art rather than as a downscaled photograph. When matching an artist or a piece, use as many colors as their work does. A canvas past ~300 wide goes in bands: several calls with `y`, a few dozen rows each. A LARGE OR GENERATED GRID SHOULD NOT PASS THROUGH YOU: have the script write it to a file and pass `file` instead of `rows` \u2014 a JSON file of {rows, colors, x?, y?, \u2026}, or a PNG. Either is the whole canvas or, with `rect`, one region of it: a pass that touched only the fire base sends the same file and names the rect, and the history says "fire base", not everything. The reply says how many pixels changed and which colors joined the palette, so growth is never silent (with `palette: "replace"` nothing joins: the palette is this drawing\'s colors, and the reply says so). read here on the same machine. Then the call is fifty bytes, whatever the canvas.',inputSchema:{type:"object",properties:{file:{type:"string",description:'Path to a grid on this machine, instead of rows and colors: a JSON file of draw_art arguments, or a PNG. A PNG\'s colors are used as they are \u2014 not quantized to the palette; each distinct color joins it (or with `palette: "replace"`, becomes it) \u2014 so read_art({ png }) \u2192 edit \u2192 draw_art({ file }) round-trips exactly. Anything given in the call as well wins.'},rows:{type:"array",items:{type:"string"}},colors:{type:"object",additionalProperties:{oneOf:[ce,{type:"null"}]}},x:{type:"integer"},y:{type:"integer"},center:{type:"boolean"},clear:{type:"boolean",description:"Erase the cel first."},rect:{type:"object",description:"With `file`, JSON or PNG: replace only this region `{x, y, w, h}` of the canvas from a full-canvas file \u2014 a one-object change is not a whole-canvas redraw. With `clear`, only the region is emptied first.",properties:{x:{type:"integer"},y:{type:"integer"},w:{type:"integer"},h:{type:"integer"}}},palette:{type:"string",enum:["add","replace"],description:"What this drawing's colors do to the document palette: `add` (default) joins them; `replace` makes them the whole palette \u2014 for a generated piece redrawn several times, so the palette does not pile up."},layer:{type:"string",description:"Layer name or index. Defaults to the top one."},frame:{type:"integer",description:"Frame index. Defaults to the first."},label:{type:"string",description:"What this shows as in the undo history."},job:E}},run:async(r,e)=>{let t=r.file?{...await xn(r.file,e),...r}:r;if(!Array.isArray(t.rows)||!t.colors)throw new Error("draw_art needs rows and colors, or a file that has them");return e.drawArt({job:t.job,rows:t.rows,colors:t.colors,x:t.x??0,y:t.y??0,center:t.center??!1,clear:t.clear??!1,layer:t.layer,frame:t.frame??0,label:t.label,palette:t.palette??"add",rect:t.rect??null})}},{name:"draw_animation",description:"Write every frame in ONE call, as a single undo step. Use copyPrevious so a frame carries the last one forward and sends only the pixels that moved \u2014 that is what takes a six-frame loop from four thousand tokens of restated grid to under a thousand. Never loop draw_art. SILHOUETTE FIRST: at 32px a front-on animal is any animal; when the subject could go several ways, draw 2-3 rough silhouettes as frames, look with render_animation, keep the one that reads, then add detail \u2014 three candidates cost barely more than one.",inputSchema:{type:"object",properties:{frames:{type:"array",minItems:1,items:{type:"object",properties:{rows:{type:"array",items:{type:"string"}},copyPrevious:{type:"boolean"},x:{type:"integer"},y:{type:"integer"},center:{type:"boolean"}},required:["rows"]}},colors:{type:"object",additionalProperties:{oneOf:[ce,{type:"null"}]}},fps:{type:"number"},layer:{type:"string"},label:{type:"string"},job:E},required:["frames","colors"]},run:(r,e)=>e.drawAnimation({job:r.job,frames:r.frames,colors:r.colors,fps:r.fps,layer:r.layer,label:r.label})},{name:"read_art",description:"Read a cel back as a grid \u2014 the exact inverse of draw_art, so you can extend art a human drew rather than only replacing it. Reads never need a job and never wait.",inputSchema:{type:"object",properties:{document:V,layer:{type:"string"},frame:{type:"integer"},trim:{type:"boolean",description:"Return only the drawn area. Rooms only."},file:{type:"string",description:"Write the grid to this path on this machine as JSON \u2014 the shape draw_art takes as `file` \u2014 instead of returning it, so a script can round-trip art without it passing through you. The reply says where it went and how big it is."},png:{type:"string",description:"Write the document to this path as a PNG, one image pixel per sprite pixel \u2014 the composite, or one `layer` \u2014 so an image tool, `references/compare.py` or `references/scene.py guides` can open the room's truth directly. A local editor writes the composite only."},job:E}},run:async(r,e)=>{if(r.png){if(!e.readPng)throw new Error("a PNG of the document is a room thing \u2014 in a local editor, read_art({ file }) and render it");let i=await e.readPng({document:r.document,job:r.job,layer:r.layer??null,frame:r.frame??0});return await gt(r.png,i.png).catch(a=>{throw new Error(`could not write ${r.png}: ${a.message}`)}),{document:i.document,png:r.png,size:`${i.w}\xD7${i.h}`,layer:r.layer??"composite"}}let t=await e.readArt({document:r.document,job:r.job,layer:r.layer,frame:r.frame??0,trim:r.trim??!1});if(!r.file)return t;let{rows:n,colors:o,...s}=t;return await gt(r.file,JSON.stringify({rows:n,colors:o},null,0)).catch(i=>{throw new Error(`could not write ${r.file}: ${i.message}`)}),{...s,file:r.file,size:`${n[0]?.length??0}\xD7${n.length}`,colors:Object.keys(o??{}).length}}},{name:"render_preview",description:"See one frame as an image, so you can check your own work rather than working blind. Rendered at a readable size by default (about a thousand pixels on the long side); `scale` is screen pixels per sprite pixel if you want a particular one. To look closely at one part of a big canvas \u2014 a cell of a sprite sheet \u2014 pass `rect` `{x, y, w, h}` in sprite pixels. The text beside the image says exactly what was rendered, at what scale, and at which document seq \u2014 the room's confirmed state, never a queued or stale mirror: a render waits for the room to confirm what you sent and is refused while the connection is down. WHEN MATCHING A REFERENCE, pass `reference` (a path on this machine \u2014 PNG, or a JPEG on a Mac): the reference is brought to the canvas grid and shown on the LEFT of the render, same rect, same scale \u2014 the only comparison that catches an object built nothing like the original. A reference that is not the shape of the canvas is fitted whole (black where it does not reach); `referenceRect` brings one part of it \u2014 the object you are checking, in reference pixels \u2014 to the grid instead, which is how to compare one thing up close. Judge per object from that, never from the whole image or from memory \u2014 and judge construction, not resemblance: count the faces, name the light, say where the shadow falls, on theirs and on yours. `values` renders both as grays \u2014 the squint test for a value plan and a focal point.",inputSchema:{type:"object",properties:{document:V,frame:{type:"integer"},scale:{type:"integer",description:"Screen pixels per sprite pixel. Default: whatever makes the long side about 1000px."},rect:{type:"object",description:"A window onto the canvas in sprite pixels, cropped before scaling. Omit w/h to run to the edge.",properties:{x:{type:"integer"},y:{type:"integer"},w:{type:"integer"},h:{type:"integer"}}},reference:{type:"string",description:"Path to a reference image on this machine: a PNG anywhere, a JPEG on a Mac (converted on the way; elsewhere convert it first). Shown beside the render at the same crop and scale, fitted whole to the canvas grid and centered \u2014 or the part of it `referenceRect` names."},referenceRect:{type:"object",description:"The part of the reference to bring to the canvas grid, in reference pixels: `{x, y, w, h}`. Omit for the whole picture. Pick the region that holds the object `rect` shows, at the same proportions, and the two sides line up.",properties:{x:{type:"integer"},y:{type:"integer"},w:{type:"integer"},h:{type:"integer"}}},values:{type:"boolean",description:"Render as values only (grayscale) \u2014 the render, and the reference when one is given. A picture that is mid-tone everywhere shows it here and nowhere else."},file:{type:"string",description:"Also write the image to this path \u2014 exactly what is returned: the rect, the scale, the reference beside it, the values. For `final.png` and the crops a report keeps, for any check that wants the room's picture rather than a local re-render, and for showing the user. A path that already exists is REFUSED unless `overwrite` is true: a chat that has shown a path once keeps showing the old picture under it, so a preview meant to be seen gets a new name every time (`preview-<seq>.png`; the caption says the seq)."},overwrite:{type:"boolean",description:"Let `file` replace a file that exists. Only for a file nothing has shown yet \u2014 a report's `final.png` being redone \u2014 never for a preview embedded in a reply."},job:E}},async run(r,e){let t=await e.preview({document:r.document,job:r.job,frame:r.frame??0,scale:r.scale??null,rect:r.rect??null,reference:r.reference??null,referenceRect:r.referenceRect??null,values:r.values===!0,file:r.file??null,overwrite:r.overwrite===!0});return yt(t.png,t.caption)}},{name:"render_animation",description:"Every frame as one numbered contact sheet. Stills inspected one call at a time, minutes apart, cannot tell you whether motion reads; a contact sheet can, in one call. Check any animation with this before calling it done.",inputSchema:{type:"object",properties:{document:V,columns:{type:"integer",default:4},scale:{type:"integer",default:2},job:E}},async run(r,e){let t=await e.sheet({document:r.document,job:r.job,columns:r.columns??4,scale:r.scale??2});return yt(t.png,t.caption)}},{name:"add_layer",description:"Add a layer above the others. Anything you will revise while the rest stays \u2014 a figure over a background, a caption over a figure \u2014 goes on its own layer, so a revision is one draw_art with `clear: true` on that layer and the base is never redrawn or at risk.",inputSchema:{type:"object",properties:{name:{type:"string"},job:E}},run:(r,e)=>e.addLayer({job:r.job,name:r.name})},{name:"add_frame",description:"Add an animation frame after the last one.",inputSchema:{type:"object",properties:{copy:{type:"boolean",description:"Start from the previous frame."},job:E}},run:(r,e)=>e.addFrame({job:r.job,copy:r.copy??!1})},{name:"set_palette",description:"Replace the palette. Constraining yourself to a small fixed palette is the single biggest quality lever in pixel art \u2014 choose it before you draw, because every draw_art adds its colors. With `prune: true` and no `colors`, drop every palette entry no pixel uses instead: the way to clean up after a session that did not. Never done for you on finish_job.",inputSchema:{type:"object",properties:{colors:{type:"array",items:ce,minItems:1},prune:{type:"boolean",description:"Keep only the colors some pixel uses. `colors` is ignored."},job:E}},run:(r,e)=>!r.prune&&!r.colors?.length?$n("set_palette wants `colors`, or `prune: true`"):e.setPalette({job:r.job,colors:r.colors??[],prune:r.prune===!0})},{name:"set_fps",description:"Set playback speed for every frame. Eight to twelve suits character animation.",inputSchema:{type:"object",properties:{fps:{type:"integer",minimum:1,maximum:60},job:E},required:["fps"]},run:(r,e)=>e.setFps({job:r.job,fps:r.fps})},{name:"clear",description:"Erase a cel. Destructive, so it goes through a job like every other write.",inputSchema:{type:"object",properties:{layer:{type:"string"},frame:{type:"integer"},job:E}},run:(r,e)=>e.clearCel({job:r.job,layer:r.layer,frame:r.frame??0})},{name:"trim_canvas",description:"Crop the canvas to the drawn pixels. Call this before finish_job on every GAME ASSET \u2014 a tile, an icon, a sprite for a sheet: you cannot know its extent before you have drawn it, so overshoot at new_sprite and trim afterwards, or empty space is inherited by every export and sheet. Do NOT trim a piece made to be posted: its canvas is the card, and the space around the subject is the composition.",inputSchema:{type:"object",properties:{margin:{type:"integer",default:0},job:E}},run:(r,e)=>e.trim({job:r.job,margin:r.margin??0})},{name:"resize_canvas",description:"Change the canvas size, keeping the art \u2014 the fix for a sprite made at the wrong size, in place, on the same document. `anchor` says where the old canvas sits on the new one: `center` (default), `top-left`, `top`, `top-right`, `left`, `right`, `bottom-left`, `bottom`, `bottom-right`. Never make a second document for a size change. The old art is kept at `anchor`; pass `clear: true` to start the new size blank in the same call \u2014 otherwise the room shows the old picture in the corner until the next full draw_art.",inputSchema:{type:"object",properties:{w:{type:"integer",minimum:1,maximum:1024},h:{type:"integer",minimum:1,maximum:1024},anchor:{type:"string"},clear:{type:"boolean",description:"Erase everything after resizing, so a full redraw at the new size does not follow an old picture in the corner."},job:E},required:["w","h"]},run:(r,e)=>e.resizeCanvas({job:r.job,w:r.w,h:r.h,anchor:r.anchor??"center",clear:r.clear===!0})},{name:"get_info",description:"What is in a document \u2014 size, layers, frames, palette \u2014 and who else is looking at it; `connected` says whether the room is on the other end and `pending` how many operations it has not confirmed. Both should be true and zero before you trust a read.",inputSchema:{type:"object",properties:{document:V,job:E}},run:(r,e)=>e.info({document:r.document,job:r.job})},{name:"list_documents",description:'Every document you can reach, and every job currently open. Call this when the user names a sprite ("add a frame to the hamster") so you can pass it to begin_job. Never guess a name.',inputSchema:{type:"object",properties:{}},run:(r,e)=>e.documents()},{name:"create_folder",description:"Make a folder in the library, by path \u2014 `food/sushi` nests. Already existing is fine. Prefer passing `folder` to new_sprite when the sprites are new; use this for an empty folder or before move_documents.",inputSchema:{type:"object",properties:{name:{type:"string",description:"The folder path."}},required:["name"]},run:(r,e)=>e.createFolder({name:r.name})},{name:"move_documents",description:"File documents into a folder, or back to the top level with folder null. Takes document ids from list_documents. The folder is made if it does not exist.",inputSchema:{type:"object",properties:{documents:{type:"array",items:{type:"string"},description:"Document ids."},folder:{type:["string","null"],description:"Folder path, or null for the top level."}},required:["documents"]},run:(r,e)=>e.moveDocuments({documents:r.documents,folder:r.folder??null,make:!0})},{name:"take_requests",description:"Requests people have typed into the editor and not yet been answered. Call this when the user asks you to check, or after joining a room they are in. Each one is somebody asking for a sprite in words \u2014 treat it exactly as if they had said it to you directly. Each carries what it wants: a `room` to answer in, or `room: null` for a request typed at the library, which wants a brand new sprite rather than a change to any existing one.",inputSchema:{type:"object",properties:{}},run:(r,e)=>e.takeRequests()},{name:"open_editor",description:"Open the Pixie editor in the default browser and wait for it to connect. Run this first if nothing is connected.",inputSchema:{type:"object",properties:{}},async run(r,e){let t=await e.openEditor();return Ee(t.message)}},{name:"select_document",description:"Bring a document to the front for the USER to look at. This does not give you the right to draw in it \u2014 begin_job does that.",inputSchema:{type:"object",properties:{document:{type:["string","integer"]}},required:["document"]},run:(r,e)=>e.select({document:r.document})},{name:"close_document",description:"Close a document: in a local editor its tab, in a room the connection and any job you hold on it. The document stays in the library, so this tidies the workspace rather than deleting anything.",inputSchema:{type:"object",properties:{document:{type:["string","integer"]}}},run:(r,e)=>e.closeDocument({document:r.document??null})},{name:"set_frame",description:"Move the user to a frame, by 0-based index.",inputSchema:{type:"object",properties:{index:{type:"integer"}},required:["index"]},run:(r,e)=>e.setFrame({index:r.index})},{name:"set_layer",description:"Move the user to a layer, by 0-based index counting from the bottom.",inputSchema:{type:"object",properties:{index:{type:"integer"}},required:["index"]},run:(r,e)=>e.setLayer({index:r.index})},{name:"undo",description:"Undo the last change, so the user can always reject generated art.",inputSchema:{type:"object",properties:{}},run:(r,e)=>e.undo()},{name:"redo",description:"Redo the last undone change.",inputSchema:{type:"object",properties:{}},run:(r,e)=>e.redo()}];function xt(r){let e=r.trace??=new ie,t=n=>n&&typeof n=="object"&&Array.isArray(n.content);return _n.filter(n=>r.supports(n.name)).map(n=>({name:n.name,description:n.description,inputSchema:n.inputSchema,run:async o=>{let s=await e.record(n.name,o,()=>n.run(ft(n,o),r));return(n.name==="new_sprite"||n.name==="begin_job")&&s?.job&&e.began(s.job),n.name==="finish_job"&&s&&typeof s=="object"&&!t(s)?Ee({...s,trace:e.finished(s.job??o?.job)}):t(s)?s:Ee(s)}}))}var St=/^[A-Za-z0-9_-]{1,64}$/,In=6e3;function Mn(r,e){return Math.max(1,Math.min(32,Math.round(1e3/Math.max(r,e,1))))}function Cn(r,e,t,n=null){let o=Un(n,r.w,r.h),s=o.x===0&&o.y===0&&o.w===r.w&&o.h===r.h?r:Ln(r,o),i=Math.min(e/o.w,t/o.h),a=Math.max(1,Math.round(o.w*i)),c=Math.max(1,Math.round(o.h*i)),h=zn(s,a,c),l=Math.floor((e-a)/2),u=Math.floor((t-c)/2),p=new Uint8Array(e*t*4);for(let d=0;d<c;d++)p.set(h.pixels.subarray(d*a*4,(d+1)*a*4),((u+d)*e+l)*4);return{w:e,h:t,pixels:p,placed:{x:l,y:u,w:a,h:c},region:o,by:e/o.w<=t/o.h?"width":"height"}}function Un(r,e,t){let n=Math.max(0,Math.min(e-1,Math.floor(Number(r?.x??0))||0)),o=Math.max(0,Math.min(t-1,Math.floor(Number(r?.y??0))||0)),s=Math.max(1,Math.min(e-n,Math.floor(Number(r?.w??e-n))||e-n)),i=Math.max(1,Math.min(t-o,Math.floor(Number(r?.h??t-o))||t-o));return{x:n,y:o,w:s,h:i}}function Ln(r,e){let t=new Uint8Array(e.w*e.h*4);for(let n=0;n<e.h;n++){let o=((e.y+n)*r.w+e.x)*4;t.set(r.pixels.subarray(o,o+e.w*4),n*e.w*4)}return{w:e.w,h:e.h,pixels:t}}async function Dn(r,e){if(e.length>8&&e[0]===137&&e[1]===80&&e[2]===78&&e[3]===71)return e;let n=e[0]===255&&e[1]===216?"JPEG":e.subarray(0,4).toString()==="GIF8"?"GIF":e.subarray(8,12).toString()==="WEBP"?"WebP":"not a PNG",o="convert it first: python3 references/pngio.py in.jpg out.png (Pillow, or `sips` on a Mac), or any image tool";if(process.platform!=="darwin")throw new Error(`the reference ${r} is ${n==="not a PNG"?n:`a ${n}`}, and this server reads PNG \u2014 ${o}`);let s=_e(Pn(),`pixie-ref-${Tn("sha256").update(e).digest("hex").slice(0,16)}-${Ae(r,On(r))}.png`),i=await K(s).catch(()=>null);return i||(await new Promise((a,c)=>{vt("sips",["-s","format","png",r,"--out",s],(h,l,u)=>{h?c(new Error(`the reference ${r} is ${n==="not a PNG"?n:`a ${n}`}, and sips could not convert it (${String(u||h.message).trim()}) \u2014 ${o}`)):a()})}),K(s))}function zn(r,e,t){let n=new Uint8Array(e*t*4),o=r.w/e,s=r.h/t;for(let i=0;i<t;i++){let a=Math.floor(i*s),c=Math.max(a+1,Math.floor((i+1)*s));for(let h=0;h<e;h++){let l=Math.floor(h*o),u=Math.max(l+1,Math.floor((h+1)*o)),p=0,d=0,f=0,m=0,w=0;for(let y=a;y<c&&y<r.h;y++)for(let T=l;T<u&&T<r.w;T++){let k=(y*r.w+T)*4;p+=r.pixels[k],d+=r.pixels[k+1],f+=r.pixels[k+2],m+=r.pixels[k+3],w++}let g=(i*e+h)*4;w&&n.set([Math.round(p/w),Math.round(d/w),Math.round(f/w),Math.round(m/w)],g)}}return{w:e,h:t,pixels:n}}function je(r,e=new Uint8Array(r.length)){for(let t=0;t<r.length;t+=4){let n=Math.round(.299*r[t]+.587*r[t+1]+.114*r[t+2]);e[t]=e[t+1]=e[t+2]=n,e[t+3]=r[t+3]}return e}function Fn(r,e,t){let n=new Uint8Array(t.w*t.h*4);for(let o=0;o<t.h;o++){let s=((t.y+o)*e+t.x)*4;n.set(r.subarray(s,s+t.w*4),o*t.w*4)}return n}function $t(r,e,t){let n=(c,h)=>Number.isFinite(+c)?Math.max(0,Math.round(+c)):h,o=Math.min(n(r?.x,0),e),s=Math.min(n(r?.y,0),t),i=Math.min(n(r?.w,e-o),e-o),a=Math.min(n(r?.h,t-s),t-s);return{x:o,y:s,w:i,h:a}}var le=class{constructor(e,t){this.core=e,this.author=t.author,this.name=t.name||"agent",this.connectTransport=t.connect,this.getToken=t.getToken??null,this.createDocument=t.createDocument??null,this.control=t.control??null,this.probe=t.probe??null,this.rooms=new Map,this.asked=[],this.onAsked=null,this.jobs=new Map,this.log=[]}async open(e,{w:t=32,h:n=32,name:o=e}={}){let s=this.rooms.get(e);if(s)return s.client;let i=this.core.newDocument({w:t,h:n,name:o,seed:jt(),now:Date.now()}),a=new re(this.core,i,{author:this.author,name:this.name,seed:jt(),agent:!0,getToken:this.getToken?()=>this.getToken(e):null});a.onLost=u=>{this.lastRefusal=u},a.onPrompt=u=>{if(u.author===this.author)return;let p=u.where==="library";this.asked.push({text:u.text,from:u.name,at:u.at,wants:p?"a new sprite":"a change to this one",room:p?null:e,askedIn:e}),this.asked.length>32&&this.asked.shift(),this.onAsked?.()};let c=this.connectTransport(e,{w:t,h:n,name:o}),h=!1;return await new Promise((u,p)=>{let d=setTimeout(()=>p(new Error(`timed out joining ${e}`)),1e4);a.onStatus=m=>{m.connected&&(h=!0,clearTimeout(d),u())},a.connect(c);let f=c.onclose;c.onclose=()=>{if(f?.(),h)return this.#n(e,{w:t,h:n,name:o});clearTimeout(d),this.#e(e).then(m=>p(new Error(m)))}}),this.rooms.set(e,{client:a,attempts:0}),a}async#e(e){return this.serverUrl?(this.probe?await this.probe(this.serverUrl).catch(()=>!1):!1)?St.test(e)?`the room "${e}" would not open, and the server is up: try once more \u2014 a room can be away for a moment \u2014 and if it repeats, there is no such document or it is not this account's; list_documents names what it can`:`the room server refused "${e}": that is a name, not a document id \u2014 list_documents gives the id, and passing the exact name works when it is unique`:mt(this.serverUrl):"nothing is answering at the room server"}#n(e,t){let n=this.rooms.get(e);if(!n||n.closing)return;let o=Math.min(15e3,1e3*2**Math.min(n.attempts,4));n.attempts+=1,n.retry=setTimeout(()=>{if(!this.rooms.get(e)||n.closing)return;let s=this.connectTransport(e,t),i=!1,a=s.onclose;n.client.onStatus=h=>{h.connected&&(i=!0,n.attempts=0)},n.client.connect(s);let c=s.onclose;s.onclose=()=>{c?.(),a?.(),this.#n(e,t)}},o),n.retry.unref?.()}client(e){let t=this.rooms.get(e);if(!t)throw new Error(`not in room ${e}`);return t.client}async peek(e){return this.rooms.has(e)?this.client(e):this.open(e)}async begin(e,t,{layer:n=null,open:o={}}={}){let s=await this.open(e,o),i=_();this.lastRefusal=null;let a=await Hn(s,i,{intent:t,...n?{layers:[n]}:{}});if(a!=="held"){let h=s.leases.find(l=>l.job!==i);throw new Error(a==="refused"&&this.lastRefusal?.message?`could not claim "${e}": ${this.lastRefusal.message}`:h?`"${e}" is already held by another job: ${h.intent}`:a==="silent"?`could not claim "${e}": the room did not answer in time \u2014 check get_info and try again`:`could not claim "${e}"`)}let c={room:e,layer:n,intent:t,cancelled:!1,timer:setInterval(()=>{this.jobs.has(i)&&s.lease("renew",{job:i})},In)};return c.timer.unref&&c.timer.unref(),this.jobs.set(i,c),this.note("begin_job",e,t),i}resolve(e){if(e){let o=this.jobs.get(e);if(!o)throw new Error(`unknown job ${e} \u2014 it may have finished`);if(o.cancelled)throw new Error("the user stopped you \u2014 do not retry; say what you were about to do and ask");return{job:e,...o}}if(this.jobs.size===0)throw new Error(this.lastRoom?`no job is open \u2014 begin_job({ document: "${this.lastRoom}" }) picks ${this.lastRoom} up again, or new_sprite starts another`:"no job is open \u2014 call new_sprite or begin_job first");if(this.jobs.size>1)throw new Error(`${this.jobs.size} jobs are open \u2014 pass the job argument`);let[t,n]=[...this.jobs.entries()][0];return{job:t,...n}}finish(e){let t=this.jobs.get(e);t&&(clearInterval(t.timer),this.jobs.delete(e),this.lastRoom=t.room,this.client(t.room).lease("release",{job:e}),this.note("finish_job",t.room,t.intent))}async submit(e,t,n){let o=this.resolve(e),s=this.client(o.room);if(!n.length)return{ops:0};if(!s.connected||!s.welcomed)throw new Error(`the room connection dropped \u2014 nothing since sequence ${s.state.seq} has been saved. Reconnecting; try again in a moment, and check get_info before drawing more.`);this.lastRefusal=null;let i=s.state.seq,a=s.apply(n,{label:t,job:o.job,now:Date.now()});try{await s.acked(a,{timeoutMs:3e4})}catch(h){throw this.lastRefusal?.kind==="revoked"&&(o.cancelled=!0),this.lastRefusal=null,h}if(this.lastRefusal){let h=this.lastRefusal.message;throw this.lastRefusal.kind==="revoked"&&(o.cancelled=!0),this.lastRefusal=null,new Error(h)}this.note(t,o.room,`${n.length} op${n.length>1?"s":""}`);let c=s.state.seq;return{ops:n.length,seq:c,committed:c>i}}cancel(e){let t=this.jobs.get(e);t&&(t.cancelled=!0)}note(e,t,n){this.log.unshift({at:Date.now(),tool:e,room:t,detail:n}),this.log.length>200&&this.log.pop()}announce(){if(!this.serverUrl)return;let e=`${this.serverUrl.replace(/^ws/,"http").replace(/\/$/,"")}/agent`,t=Se(this.serverUrl)?15e3:6e4,n=()=>{fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:this.name,every:t})}).catch(()=>{})};n(),clearInterval(this.heartbeat),this.heartbeat=setInterval(n,t),this.heartbeat.unref?.()}close(){for(let e of this.rooms.values())e.closing=!0,clearTimeout(e.retry);clearInterval(this.heartbeat);for(let[e]of this.jobs)this.finish(e);for(let{client:e}of this.rooms.values())e.close();this.rooms.clear()}get kind(){return"room"}get where(){return this.serverUrl??"a room server"}supports(e){return e==="open_editor"?!!this.editorUrl:qn.has(e)}async openEditor({document:e,job:t}={}){if(!this.editorUrl)return{ok:!1,message:"there is no editor to open from here"};let n=this.editorUrl;try{n=`${this.editorUrl}/d/${await this.#o(e,t)}`}catch{n=`${this.editorUrl}/library`}return await Gn(n),{ok:!0,message:`Opened ${n}`}}async newSprite({name:e,w:t=R.w,h:n=R.h,intent:o,palette:s,folder:i=null,challenge:a=null}){let c=await this.createDocument?.({name:e,w:t,h:n})??ht(e),h=await this.begin(c,o,{open:{w:t,h:n,name:e}});a&&(this.jobs.get(h).challenge=a),s?.length&&await this.submit(h,"set_palette",[{t:"setPalette",colors:q(s,"hue")}]),i&&await this.moveDocuments({documents:[c],folder:i,make:!0}),this.#i(c,o);let l=he(t,n)??ke(t,n);return{job:h,...this.#t(c),...l?{shape:l}:{}}}async#i(e,t){let n=process.env.SPRITE_ASKED_IN,o=process.env.SPRITE_REPLY_TO??null;if(n)try{(await this.open(n)).answer("thinking",t?String(t).slice(0,200):"making a new sprite",{reply:o,made:e})}catch{}}async beginJob({document:e,intent:t,layer:n=null}){let o=!e&&this.jobs.size===0&&this.lastRoom?this.lastRoom:await this.#o(e,null);return{job:await this.begin(o,t,{layer:n}),...this.#t(o)}}finishJob({job:e}){let t=this.resolve(e);this.finish(t.job);let n=this.client(t.room).state,o=he(n.doc.w,n.doc.h),i=[t.challenge?`This is ${t.challenge==="weekly"?"this week's Weekly Pixels":"today's Pixel Dailies"} entry: tell the user to post it from the share button with the ${t.challenge==="weekly"?"Weekly Pixels":"Pixel Dailies"} switch on, which puts the theme and the tags on the post.`:null,o].filter(Boolean).join(" "),a=t.pushed?{source:t.pushed}:{source:null,note:"nothing pushed \u2014 if a script made this, push_source before the next job, so the next agent (or you, tomorrow) can pick it up rather than redraw it"};return{finished:t.intent,job:t.job,document:t.room,...i?{next:i}:{},...a}}async#a(e,t={}){if(this.control)try{return await this.control(e,t)}catch(n){if(!this.openControl||!/no library here/.test(n.message))throw n}if(this.openControl)return this.openControl(e,t);throw new Error("no server to push to: this session has no control plane")}async pushSource({job:e,files:t,run:n,takes:o="",unfinished:s=""}){let i=this.resolve(e),a=this.client(i.room),c=(Array.isArray(t)?t:[t]).map(String).filter(Boolean);if(!c.length)throw new Error("say which files: the generator, and anything it imports that is yours");let h=[];for(let p of c){let d=await K(p).catch(f=>{throw new Error(`could not read ${p}: ${f.code==="ENOENT"?"no such file":f.message}`)});if(d.includes(0))throw new Error(`${p} is not text \u2014 a push is source, not a PNG or a grid; the next agent regenerates those`);h.push({name:Ae(p),text:d.toString("utf8")})}let u=(await this.#a(`/documents/${encodeURIComponent(i.room)}/history/source`,{method:"POST",body:{job:i.job,seq:a.state.seq,run:String(n??""),takes:String(o??""),unfinished:String(s??""),name:this.name,files:h}}))?.source??null;return u&&this.jobs.has(i.job)&&(this.jobs.get(i.job).pushed={seq:u.seq,files:u.files.map(p=>p.name)}),this.note("push_source",i.room,`${h.length} file${h.length===1?"":"s"}`),{document:i.room,job:i.job,seq:a.state.seq,files:u?.files??h.map(p=>({name:p.name})),note:"pushed beside this job's mark in the version history; readable by this account only. pull_source({ document, job }) brings it back to disk."}}async pullSource({document:e=null,job:t=null,into:n=null}){let o=null;if(e?o=await this.#o(e,null):this.jobs.size===1?o=this.resolve(null).room:o=this.lastRoom,!o)throw new Error("say which document \u2014 none is open and none was finished in this session");let s=`/documents/${encodeURIComponent(o)}/history/source`;if(!t){let h=await this.#a(s,{}),l=Array.isArray(h?.sources)?h.sources:[];return{document:o,sources:l.map(u=>({job:u.job,seq:u.seq,at:u.at?new Date(u.at).toISOString():null,run:u.run,unfinished:u.unfinished||null,files:u.files.map(p=>p.name)})),note:l.length?"yours on this document, newest first \u2014 pull_source({ job, into }) writes one to disk; `seq` is the document state it reproduces (get_info says where the document is now)":"nothing pushed on this document by this account \u2014 a mark with `source: true` in the history was pushed by somebody else, and is theirs"}}let i=await this.#a(`${s}/${encodeURIComponent(String(t))}`,{});if(!i?.files)throw new Error(`no source of yours under job ${t} on ${o}`);let a=Nn(n?String(n):_e(".context","source",String(t)));await Rn(a,{recursive:!0});let c=[];for(let h of i.files){let l=_e(a,Ae(h.name)),u=await K(l,"utf8").catch(()=>null);if(u!==null&&u!==h.text)throw new Error(`${l} exists and differs \u2014 pull into another directory, or move it first`);await An(l,h.text),c.push(l)}return{document:o,job:i.job,seq:i.seq,pushedAt:i.at?new Date(i.at).toISOString():null,by:i.by?.name??null,run:i.run,takes:i.takes||null,unfinished:i.unfinished||null,files:c,note:`written to ${a}. Read the files before running them; run them from there, in the workspace, with nothing the task does not need. They reproduce seq ${i.seq} \u2014 compare get_info's seq to see what happened since.`}}async challenges(){if(!this.newsUrl)throw new Error("no server to ask: the themes come from the Pixie server, and this session has none");if(!(this.newsAt&&Date.now()-this.newsAt<300*1e3)){let n=await fetch(this.newsUrl);if(!n.ok)throw new Error(`the server said ${n.status} for the themes`);this.news=await n.json(),this.newsAt=Date.now()}let t=(n,o)=>n?{theme:n.theme,tags:n.tags,detail:n.detail??null,sizes:n.sizes??[],postedAt:new Date(n.at).toISOString(),post:n.url}:{theme:null,note:`no ${o} theme is known right now`};return{daily:t(this.news?.dailies,"daily"),weekly:t(this.news?.weekly,"weekly"),how:'new_sprite({ challenge: "daily" | "weekly", intent }) names the sprite after the theme; finish_job says how to post it as an entry.'}}async challenge(e){let t=await this.challenges(),n=e==="weekly"?t.weekly:t.daily;if(!n?.theme)throw new Error(`no ${e} theme is known right now \u2014 ask the user what to draw`);return n}async#o(e,t){if(!e)return this.resolve(t).room;let n=String(e).trim();if(this.rooms.has(n))return n;let o=await this.#u();if(o===null||o.some(i=>i.id===n))return n;let s=o.filter(i=>(i.name??"").trim().toLowerCase()===n.toLowerCase());if(s.length===1)return s[0].id;if(s.length>1)throw new Error(`${s.length} documents are called "${n}" \u2014 pass the id: ${s.map(i=>i.id).join(", ")}`);if(St.test(n))return n;throw new Error(`no document is called "${n}" \u2014 list_documents names every one with its id; pass the id`)}async#u(){let e=this.control?await this.control("/documents",{}).catch(()=>null):null;if(e?.documents)return e.documents.map(n=>({id:n.id,name:n.name}));if(!this.serverUrl)return null;let t=this.serverUrl.replace(/^ws/,"http").replace(/\/$/,"");return fetch(`${t}/documents`).then(n=>n.ok?n.json():null).then(n=>n?.documents?.map(o=>({id:o.id,name:o.name}))??null).catch(()=>null)}async info({document:e,job:t}={}){let n=await this.#o(e,t);return await this.peek(n),this.#t(n)}#t(e){let t=this.client(e),n=t.state;return{document:e,name:n.doc.name,w:n.doc.w,h:n.doc.h,layers:n.doc.layers.map(o=>o.name),frames:n.doc.frames.length,palette:n.doc.palette,tags:n.doc.tags.map(o=>o.name),seq:n.seq,connected:!!(t.connected&&t.welcomed),pending:n.pending,participants:t.participants.map(o=>o.name)}}#c(e){let t=this.client(e),n=t.state;return n.pending===0&&t.connected?{}:{warning:n.pending?`includes ${n.pending} operation${n.pending===1?"":"s"} the room has not confirmed${t.connected?"":" \u2014 the connection dropped, reconnecting"}`:"the room connection dropped, reconnecting"}}async readArt({document:e,job:t,layer:n,frame:o=0,trim:s=!1}){let i=await this.#o(e,t),a=await this.peek(i);return{document:i,...a.session.readArt(this.#s(i,n),o,{trim:s}),...this.#c(i)}}async readPng({document:e,job:t,layer:n=null,frame:o=0}){let s=await this.#o(e,t),i=await this.peek(s),a=i.state.doc;if(n==null)return{document:s,png:i.session.png(o,{scale:1}),w:a.w,h:a.h};let c=i.session.cel(this.#s(s,n),o);return{document:s,png:this.#h(a.w,a.h,c),w:a.w,h:a.h}}#h(e,t,n,o=1){let s=this.core.newDocument({w:e,h:t,name:"scratch",seed:1,now:Date.now()}),i=this.core.openSession(s,_(),1);try{let a=i.state();return i.apply([dt(a.doc.layers[0].id,a.doc.frames[0].id,{x:0,y:0,w:e,h:t},n)],{now:Date.now()}),i.png(0,{scale:o})}finally{i.close()}}async#l(e,t,n,o,s,i=!1,a=null){let c=await K(t).catch(j=>{throw new Error(`could not read the reference ${t}: ${j.code==="ENOENT"?"no such file":j.message}`)}),h=this.core.decodePng(await Dn(t,c)),l=e.state.doc,u=Cn(h,l.w,l.h,a),p=i?je(e.session.composite(o)):e.session.composite(o);i&&je(u.pixels,u.pixels);let d=n.w,f=n.h,m=new Uint8Array((d*2+1)*f*4),w=(d*2+1)*4;for(let j=0;j<f;j++){for(let v=0;v<d;v++){let I=n.x+v,G=n.y+j,ee=(G*u.w+I)*4,pe=G<u.h&&I<u.w;m.set(pe?u.pixels.subarray(ee,ee+4):[0,0,0,255],j*w+v*4);let te=(G*l.w+I)*4;m.set(p.subarray(te,te+4),j*w+(d+1+v)*4)}m.set([255,0,255,255],j*w+d*4)}let g=u.placed,y=u.region.w===h.w&&u.region.h===h.h,T=y?`the reference is ${h.w}\xD7${h.h}`:`the region ${u.region.w}\xD7${u.region.h} at (${u.region.x}, ${u.region.y}) of the ${h.w}\xD7${h.h} reference`,k=g.w===l.w&&g.h===l.h?`brought to the ${l.w}\xD7${l.h} canvas`:`fitted whole to ${g.w}\xD7${g.h} by ${u.by}, centered on the ${l.w}\xD7${l.h} canvas (black where it does not reach)`,L=y&&g.w===l.w&&g.h===l.h?"":` \u2014 ${T}, ${k}${y&&(g.w!==l.w||g.h!==l.h)?"; pass `referenceRect` to bring one part of it to the grid instead":""}`;return{png:this.#h(d*2+1,f,m,s),note:L}}async preview({document:e,job:t,frame:n=0,scale:o=null,rect:s=null,reference:i=null,referenceRect:a=null,values:c=!1,file:h=null,overwrite:l=!1}){let u=await this.#d({document:e,job:t,frame:n,scale:o,rect:s,reference:i,referenceRect:a,values:c});if(h){let p=String(h),d=await se(p,u.png,{overwrite:l,seq:u.seq});u.caption+=` \u2014 written to ${p}${d?" (replaced; anything that showed this path before shows the old picture)":""}`}return u}async#r(e){let t=await this.peek(e);if(t.state.pending>0&&await Jn(t,3e3),!t.connected||!t.welcomed)throw new Error(`the room connection dropped \u2014 a render now could be behind what the room holds (last confirmed seq ${t.state.seq}). Reconnecting; try again in a moment.`);return t}async#d({document:e,job:t,frame:n=0,scale:o=null,rect:s=null,reference:i=null,referenceRect:a=null,values:c=!1}){let h=await this.#o(e,t),l=await this.#r(h),{warning:u}=this.#c(h),p=l.state.seq,d=l.state.doc,f=s?$t(s,d.w,d.h):null,m=f?f.w:d.w,w=f?f.h:d.h,g=Number.isInteger(o)&&o>0?o:Mn(i?m*2:m,w),y=Math.max(1,Math.min(g,Math.floor(8192/Math.max(i?m*2:m,w,1)))),T=f?`${f.w}\xD7${f.h} at (${f.x},${f.y}) of ${d.w}\xD7${d.h}`:`${d.w}\xD7${d.h}`,k=y<g?` \u2014 asked for \xD7${g}, held to \xD7${y} so a side stays under 8192px; render a smaller rect to look closer`:"";if(i){let{png:j,note:v}=await this.#l(l,String(i),f??{x:0,y:0,w:d.w,h:d.h},n,y,c,a);return{png:j,seq:p,caption:`${h} at seq ${p}, frame ${n+1}: reference (left) beside the render (right), ${T} at \xD7${y}${c?", values only":""}${v}${k}${u?` \u2014 ${u}`:""}. Per object, say how each is built \u2014 count the faces, name the light, say where the shadow falls \u2014 and what differs; "reads as" is not a verdict.`}}return{png:c?this.#h(m,w,Fn(je(l.session.composite(n)),d.w,f??{x:0,y:0,w:d.w,h:d.h}),y):l.session.png(n,{scale:y,...f?{rect:f}:{}}),seq:p,caption:`${h} at seq ${p}, frame ${n+1}: ${T} at \xD7${y} (${m*y}\xD7${w*y}px)${c?", values only \u2014 squint: is there a focal point, and where is the darkest dark":""}${k}${u?` \u2014 ${u}`:""}`}}async sheet({document:e,job:t,columns:n=4,scale:o=2}){let s=await this.#o(e,t),i=await this.peek(s);return{png:i.session.contactSheet({columns:n,scale:o}),caption:`${s}, ${i.state.doc.frames.length} frames, numbered left to right`}}async documents(){let e=[...this.jobs.entries()].map(([c,h])=>({job:c,document:h.room,intent:h.intent})),t=[...this.rooms.keys()],n=null,o=null,s=null,i=this.control?await this.control("/documents",{}).catch(()=>null):null;if(i)n=i.documents?.map(c=>c.id)??null,o=Array.isArray(i.folders)?i.folders.map(c=>typeof c=="string"?c:c?.path??c?.name).filter(Boolean):null,s=i.documents?Object.fromEntries(i.documents.filter(c=>c.folder).map(c=>[c.id,c.folder])):null;else if(this.serverUrl){let c=this.serverUrl.replace(/^ws/,"http").replace(/\/$/,"");n=await fetch(`${c}/documents`).then(h=>h.ok?h.json():null).then(h=>h?.documents?.map(l=>l.id)).catch(()=>null)}return{documents:n?[...new Set([...t,...n])]:t,jobs:e,open:t,...o?{folders:o}:{},...s?{filed:s}:{}}}async createFolder({name:e}){if(!this.control)throw new Error("no library here: this Sprite has no accounts, so there are no folders");let t=String(e??"").trim();if(!t)throw new Error("a folder needs a name");let n=await this.folders();if(n.includes(t))return{folder:t,created:!1,folders:n};let o=t.split("/");for(let s=1;s<=o.length;s++){let i=o.slice(0,s).join("/");n.includes(i)||(await this.control("/documents/folders",{method:"POST",body:{name:i}}),n.push(i))}return{folder:t,created:!0,folders:await this.folders()}}async moveDocuments({documents:e,folder:t=null,make:n=!1}){if(!this.control)throw new Error("no library here: this Sprite has no accounts, so there are no folders");let o=(Array.isArray(e)?e:[e]).map(String).filter(Boolean);if(!o.length)throw new Error("say which documents");let s=t===null||t===""?null:String(t).trim();s&&n&&await this.createFolder({name:s});let i=await this.control("/documents/move",{method:"POST",body:{ids:o,folder:s}});return{moved:o,folder:s,ok:i?.ok!==!1}}async folders(){let e=await this.control("/documents",{});return Array.isArray(e?.folders)?e.folders.map(t=>typeof t=="string"?t:t?.path??t?.name).filter(Boolean):[]}takeRequests(){let e=this.asked.splice(0,this.asked.length);return{requests:e,note:e.length?"Each says what it wants. Where `room` is set, answer in that document. Where it is null the request came from the library and wants a new sprite \u2014 call new_sprite and work there, and do not touch `askedIn`, which is only the room the words travelled through. The person is watching.":"Nobody has asked for anything."}}#s(e,t){let n=this.client(e).state;if(t!=null&&t!==""){let o=typeof t=="number"?n.doc.layers[t]:n.doc.layers.find(s=>s.name===t||s.id===t);if(!o)throw new Error(`no layer ${JSON.stringify(t)} in ${e}`);return o.id}return n.doc.layers[n.doc.layers.length-1].id}async drawArt({job:e,rows:t,colors:n,x:o=0,y:s=0,center:i=!1,clear:a=!1,layer:c,frame:h=0,label:l,palette:u="add",rect:p=null}){let d=this.resolve(e),f=this.client(d.room),m=t,w={x:o,y:s},g=a;if(p){let b=$t(p,Math.max(...t.map(P=>P.length),1),t.length);if(m=t.slice(b.y,b.y+b.h).map(P=>P.slice(b.x,b.x+b.w).padEnd(b.w,".")),w={x:b.x,y:b.y},a){let P=[..."~^`|"].find(M=>!(M in n))??"\xA1";m=m.map(M=>M.replace(/[. ]/g,P)),n={...n,[P]:null},g=!1}}let y=new Set((f.state.doc.palette??[]).map(b=>String(b).toLowerCase())),T=f.session.drawArt(m,n,{layer:c!=null?this.#s(d.room,c):d.layer??this.#s(d.room,null),at:h,x:w.x,y:w.y,center:p?!1:i,clear:g}),{committed:k}=await this.submit(d.job,l||"draw_art",T),L=(this.client(d.room).state.doc.palette??[]).map(b=>String(b).toLowerCase()).filter(b=>!y.has(b)),j=m.reduce((b,P)=>b+[...P].filter(M=>M!=="."&&M!==" ").length,0),v=[];if(u==="replace"&&k){let b=A=>typeof A=="string"&&/^#[0-9a-f]{6}$/i.test(A)?A.toLowerCase():null,P=p?null:new Set(m.flatMap(A=>[...A])),M=Object.entries(n).filter(([A,Tt])=>b(Tt)&&(P===null||P.has(A))),me=[...new Set(M.map(([,A])=>b(A)))],Rt=new Set(me);v=[...new Set(Object.values(n).map(b).filter(A=>A&&!Rt.has(A)))],me.length&&await this.submit(d.job,"set_palette",[{t:"setPalette",colors:q(me,"hue")}])}let I=(this.client(d.room).state.doc.palette??[]).length,G=p?`the file's \u2014 the whole picture's, not the ${m[0]?.length??0}\xD7${m.length} that was drawn; nothing outside the rect moved`:"this drawing's",ee=v.length?`; ${v.length} in \`colors\` landed on no pixel and ${v.length===1?"was":"were"} left out: ${v.join(" ")}`:"",pe=u==="replace"&&I>48?` \u2014 ${I} is a lot for a piece with no reference; ramps that read alike can merge`:"",te=u==="replace"&&k?{note:`palette replaced: ${I} color${I===1?"":"s"}, ${G}${ee}${pe}`}:L.length?{added:q(L,"hue"),note:`${L.length} color${L.length===1?"":"s"} joined the palette \u2014 draw from a chosen palette, or set_palette({ prune: true }) later`}:{};return{...this.#t(d.room),drew:p?`${m[0]?.length??0}\xD7${m.length} at (${w.x},${w.y})`:`${t[0]?.length??0}\xD7${t.length}`,changed:j,committed:k,...te}}async resizeCanvas({job:e,w:t,h:n,anchor:o="center",clear:s=!1}){let i=this.resolve(e),a=this.client(i.room).state,c=Math.max(1,Math.min(1024,Math.round(Number(t)||a.doc.w))),h=Math.max(1,Math.min(1024,Math.round(Number(n)||a.doc.h)));if(c===a.doc.w&&h===a.doc.h)return s&&await this.clearCel({job:e,frame:0}),{...this.#t(i.room),resized:s?"already that size; cleared":"already that size"};let l=String(o??"center").toLowerCase(),u=l.includes("left")?0:l.includes("right")?2:1,p=l.includes("top")?0:l.includes("bottom")?2:1,d=Math.round((c-a.doc.w)*u/2),f=Math.round((h-a.doc.h)*p/2);if(await this.submit(i.job,"resize_canvas",[{t:"resize",w:c,h,dx:d,dy:f}]),s){let w=this.client(i.room).state.doc.frames.length;for(let g=0;g<w;g++)await this.clearCel({job:e,frame:g})}let m=he(c,h)??ke(c,h);return{...this.#t(i.room),resized:`${a.doc.w}\xD7${a.doc.h} \u2192 ${c}\xD7${h}, ${s?"cleared":`the art kept at ${d},${f}`}`,...m?{shape:m}:{}}}async closeDocument({document:e}){let t=await this.#o(e,null),n=this.rooms.get(t);if(!n)return{closed:t,open:!1};for(let[o,s]of this.jobs)s.room===t&&this.finish(o);return n.closing=!0,clearTimeout(n.retry),n.client.close(),this.rooms.delete(t),{closed:t,open:!1}}async drawAnimation({job:e,frames:t,colors:n,fps:o,layer:s,label:i}){let a=this.resolve(e),c=this.client(a.room),h=s!=null?this.#s(a.room,s):a.layer??this.#s(a.room,null),l=c.state.doc.frames.length,u=t.length-l;if(u>0){let d=[],f=c.state.doc.frames[l-1]?.index??"V";for(let m=0;m<u;m++)f=f+"V",d.push({t:"addFrame",id:_(),index:f,durMs:ve(o)});await this.submit(a.job,"add frames",d)}if(o){let d=ve(o);await this.submit(a.job,"set fps",c.state.doc.frames.map(f=>({t:"setFrameDur",id:f.id,durMs:d})))}let p=c.session.drawAnimation(t,n,{layer:h});return await this.submit(a.job,i||"draw_animation",p),{...this.#t(a.room),wrote:`${t.length} frames`}}async addLayer({job:e,name:t}){let n=this.resolve(e),o=this.client(n.room).state,s=o.doc.layers[o.doc.layers.length-1];return await this.submit(n.job,"add_layer",[{t:"addLayer",id:_(),index:(s?.index??"V")+"V",name:t||`Layer ${o.doc.layers.length+1}`,visible:!0,locked:!1,opacity:1}]),this.#t(n.room)}async addFrame({job:e,copy:t=!1}){let n=this.resolve(e),o=this.client(n.room),s=o.state,i=s.doc.frames[s.doc.frames.length-1],a=_(),c=[{t:"addFrame",id:a,index:(i?.index??"V")+"V",durMs:i?.durMs??83}];if(t&&i)for(let h of s.doc.layers){let l=o.session.cel(h.id,i.id);l.every(u=>u===0)||c.push(Et(h.id,a,s.doc,l))}return await this.submit(n.job,"add_frame",c),this.#t(n.room)}async setPalette({job:e,colors:t,prune:n=!1}){let o=this.resolve(e),s;if(n){let i=this.client(o.room),a=i.state,c=new Set;for(let l of a.doc.layers)for(let u of a.doc.frames){let p=i.session.cel(l.id,u.id);for(let d=0;d<p.length;d+=4)p[d+3]!==0&&c.add(`#${[p[d],p[d+1],p[d+2]].map(f=>f.toString(16).padStart(2,"0")).join("")}`)}let h=a.doc.palette??[];s=h.filter(l=>c.has(String(l).toLowerCase()));for(let l of c)s.some(u=>String(u).toLowerCase()===l)||s.push(l);return s=q(s,"hue"),await this.submit(o.job,"set_palette",[{t:"setPalette",colors:s}]),{...this.#t(o.room),palette:s.length,pruned:h.length-s.length}}return s=q(t,"hue"),await this.submit(o.job,"set_palette",[{t:"setPalette",colors:s}]),{...this.#t(o.room),palette:s.length}}async setFps({job:e,fps:t}){let n=this.resolve(e),o=this.client(n.room).state,s=ve(t);return await this.submit(n.job,"set_fps",o.doc.frames.map(i=>({t:"setFrameDur",id:i.id,durMs:s}))),{...this.#t(n.room),fps:t}}async clearCel({job:e,layer:t,frame:n=0}){let o=this.resolve(e),i=this.client(o.room).state,a=this.#s(o.room,t??o.layer),c=i.doc.frames[n];if(!c)throw new Error(`no frame ${n} in ${o.room}`);let h=new Uint8Array(i.doc.w*i.doc.h*4);return await this.submit(o.job,"clear",[Et(a,c.id,i.doc,h)]),this.#t(o.room)}async trim({job:e,margin:t=0}){let n=this.resolve(e),o=this.client(n.room),s=o.state,i=this.#s(n.room,n.layer),a=o.session.readArt(i,0,{trim:!0}),c=a.rect.w+t*2,h=a.rect.h+t*2;return c>=s.doc.w&&h>=s.doc.h?{...this.#t(n.room),trimmed:"already tight"}:(await this.submit(n.job,"trim_canvas",[{t:"resize",w:c,h,dx:-a.rect.x+t,dy:-a.rect.y+t}]),{...this.#t(n.room),trimmed:`${c}\xD7${h}`})}},qn=new Set(["new_sprite","begin_job","finish_job","draw_art","draw_animation","read_art","render_preview","render_animation","add_layer","add_frame","set_palette","set_fps","clear","trim_canvas","resize_canvas","close_document","get_info","list_documents","challenges","take_requests","create_folder","move_documents","push_source","pull_source"]);function Et(r,e,t,n){return{t:"paintRegion",layer:r,frame:e,rect:{x:0,y:0,w:t.w,h:t.h},rle:Bn(n)}}function Bn(r){let e=[];if(r.length){let t=[r[0],r[1],r[2],r[3]],n=0,o=()=>{let s=n;do{let i=s&127;s>>=7,e.push(s?i|128:i)}while(s);e.push(...t)};for(let s=0;s<r.length;s+=4)r[s]===t[0]&&r[s+1]===t[1]&&r[s+2]===t[2]&&r[s+3]===t[3]?n++:(o(),t=[r[s],r[s+1],r[s+2],r[s+3]],n=1);o()}return Buffer.from(Uint8Array.from(e)).toString("base64")}function Gn(r){let e=process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open";return new Promise(t=>vt(e,[r],()=>t()))}function ve(r){return!r||r<=0?83:Math.max(10,Math.round(1e3/r))}function Wn(r,e,t=8e3){return new Promise(n=>{let o=r.onLeases,s=r.onLost,i=!1,a=h=>{i||(i=!0,clearTimeout(c),r.onLeases=o,r.onLost=s,n(h))};r.onLeases=h=>{o?.(h),h.some(l=>l.job===e)&&a("held")},r.onLost=h=>{s?.(h),h?.ops?.length||a("refused")};let c=setTimeout(()=>a("silent"),t);r.leases.some(h=>h.job===e)&&a("held")})}function kt(r,e=8e3){return new Promise(t=>{let n=Date.now(),o=()=>{if(r.welcomed)return t(!0);if(Date.now()-n>e)return t(!1);setTimeout(o,20)};o()})}async function Hn(r,e,t,{ms:n}={}){await kt(r,n);let o=()=>{let i=Wn(r,e,n);return r.lease("acquire",{job:e,...t}),i},s=await o();return s==="refused"&&!r.welcomed&&await kt(r,n)&&(s=await o()),s}function Jn(r,e=400){return new Promise(t=>{let n=Date.now(),o=()=>{if(r.state.pending===0&&Date.now()-n>40||Date.now()-n>e)return t();setTimeout(o,10)};o()})}function jt(){return(Date.now()^Math.floor(Math.random()*4294967295))>>>0}import{execFile as Vn}from"node:child_process";var Kn=2e4,Xn=5e3,X=class r{constructor({base:e,name:t="agent"}={}){this.base=String(e).replace(/\/$/,""),this.name=t,this.log=[],this.beat=setInterval(()=>void this.announce(),Xn),this.beat.unref&&this.beat.unref()}get kind(){return"local"}get where(){return this.base}supports(e){return Zn.has(e)}static async probe(e,{timeoutMs:t=700}={}){try{let n=await fetch(`${e.replace(/\/$/,"")}/bridge/status`,{signal:AbortSignal.timeout(t)});if(!n.ok)return null;let o=await n.json();return o?.server==="sprite"?o:null}catch{return null}}async announce(){try{await fetch(`${this.base}/bridge/agent`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:this.name,version:null}),signal:AbortSignal.timeout(2e3)})}catch{}}async call(e,t=[],n=Kn){let o;try{o=await(await fetch(`${this.base}/bridge/call`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({method:e,args:t,timeoutMs:n}),signal:AbortSignal.timeout(n+2e3)})).json()}catch(s){throw new Error(`lost contact with the Pixie editor at ${this.base}: ${s.message}`)}if(!o.ok)throw new Error(o.error);return this.note(e),o.result}note(e,t=""){this.log.unshift({at:Date.now(),tool:e,room:"local",detail:t}),this.log.length>200&&this.log.pop()}newSprite({name:e,w:t=R.w,h:n=R.h,intent:o,palette:s}){return this.call("newSprite",[{w:t,h:n,name:e,palette:s,intent:o,replace:!1}])}async challenges(){let e=await fetch(`${this.base}/news.json`).catch(()=>null);if(!e?.ok)throw new Error("this editor does not serve the themes \u2014 ask the user what to draw");let t=await e.json(),n=(o,s)=>o?{theme:o.theme,tags:o.tags,detail:o.detail??null,sizes:o.sizes??[],postedAt:new Date(o.at).toISOString(),post:o.url}:{theme:null,note:`no ${s} theme is known right now`};return{daily:n(t?.dailies,"daily"),weekly:n(t?.weekly,"weekly")}}async challenge(e){let t=await this.challenges(),n=e==="weekly"?t.weekly:t.daily;if(!n?.theme)throw new Error(`no ${e} theme is known right now \u2014 ask the user what to draw`);return n}beginJob({document:e,intent:t}){return this.call("beginJob",[{document:e,intent:t}])}finishJob({job:e}){return this.call("finishJob",[{job:e}])}async info({document:e}={}){let t=await this.call("info",[]);return{editor:this.base,...e?{asked:e}:{},...t}}readArt({document:e,layer:t,frame:n}){return this.call("readArt",[{document:e,layer:t,frame:n}])}async preview({document:e,frame:t,scale:n=null,rect:o=null,reference:s=null,file:i=null,overwrite:a=!1}){if(o)throw new Error("the local editor cannot render a region \u2014 render the whole frame");if(s)throw new Error("a reference beside the render is a room thing \u2014 in a local editor, read_art({ file }) and compare with references/compare.py");let c=await this.call("toDataURL",[t??null,Number.isInteger(n)&&n>0?n:8,e??null]),h=Re(c.url),l=c.caption;if(i){let u=String(i),p=await se(u,h,{overwrite:a});l+=` \u2014 written to ${u}${p?" (replaced; anything that showed this path before shows the old picture)":""}`}return{png:h,caption:l}}async readPng({document:e,frame:t=0,layer:n=null}){if(n!=null)throw new Error("one layer as a PNG is a room thing \u2014 in a local editor, read_art({ layer, file }) and render it with references/render.py");let o=await this.call("toDataURL",[t??null,1,e??null]),s=/(\d+)×(\d+)/.exec(o.caption??"");return{document:e??null,png:Re(o.url),w:s?Number(s[1]):0,h:s?Number(s[2]):0}}async sheet({document:e,columns:t=0,scale:n=0}){let o=await this.call("toStripDataURL",[{document:e??null,columns:t,scale:n}]);return{png:Re(o.url),caption:o.caption}}documents(){return this.call("listDocuments",[])}drawArt({job:e,rows:t,colors:n,x:o,y:s,center:i,clear:a,layer:c,frame:h,label:l,rect:u=null}){if(u){let p=Math.max(0,u.x|0),d=Math.max(0,u.y|0);t=t.slice(d,d+(u.h|0)).map(f=>f.slice(p,p+(u.w|0))),o=p,s=d,i=!1}return this.call("drawArt",[t,n,{job:e,x:o,y:s,center:i,clear:a,layer:c,frame:h,label:l}])}drawAnimation({job:e,frames:t,colors:n,fps:o,layer:s,label:i}){return this.call("drawAnimation",[{frames:t,colors:n,fps:o,job:e,layer:s,label:i}])}addLayer({name:e}){return this.call("addLayer",[e])}addFrame({copy:e=!1}){return this.call("addFrame",[{copy:!!e}])}setPalette({colors:e,prune:t=!1}){if(t)throw new Error("prune works in a room \u2014 in a local editor, pass the colors to keep");return this.call("setPalette",[e])}setFps({fps:e}){return this.call("setFps",[e])}clearCel({job:e,layer:t,frame:n}){return this.call("clear",[{job:e,layer:t,frame:n}])}trim({job:e,margin:t=0}){return this.call("trimCanvas",[{job:e,margin:t}])}setFrame({index:e}){return this.call("setFrame",[e])}setLayer({index:e}){return this.call("setLayer",[e])}select({document:e}){return this.call("selectDocument",[e])}closeDocument({document:e}){return this.call("closeDocument",[e??null])}undo(){return this.call("undo",[])}redo(){return this.call("redo",[])}async openEditor(){if((await r.probe(this.base))?.connected)return{ok:!0,message:`Editor already connected at ${this.base}`};await Yn(this.base);let t=Date.now()+1e4;for(;Date.now()<t;){if((await r.probe(this.base))?.connected)return{ok:!0,message:`Editor connected at ${this.base}`};await new Promise(o=>setTimeout(o,200))}return{ok:!1,message:`Opened ${this.base} but nothing connected \u2014 check the browser window.`}}close(){clearInterval(this.beat)}},Zn=new Set(["new_sprite","begin_job","finish_job","draw_art","draw_animation","read_art","render_preview","render_animation","add_layer","add_frame","set_palette","set_fps","clear","trim_canvas","get_info","list_documents","challenges","open_editor","select_document","close_document","set_frame","set_layer","undo","redo"]);function Re(r){let e=String(r??"").split(",")[1];if(!e)throw new Error("the editor returned something that was not an image");return Buffer.from(e,"base64")}function Yn(r){let e=process.platform==="darwin"?"open":process.platform==="win32"?"start":"xdg-open";return new Promise(t=>Vn(e,[r],()=>t()))}function Te(r="room"){return`Pixel art editor with live collaboration. ${r==="local"?`Documents are tabs in a browser on this machine; the user is watching
3
+ while you work, and can take the canvas back at any time.`:`Documents are rooms; other people and other agents may be in one with
4
4
  you, and they see what you do as you do it.`}
5
5
 
6
6
  CALL new_sprite FIRST \u2014 before a script, a grid, or a thought about pixels.
7
7
  It needs a size, a name and a line of intent, and it puts a labeled
8
- document in front of the user at once; nothing else should happen while
9
- they look at an unchanged canvas. A wrong size is resize_canvas on the
10
- same document, never a second new_sprite.
8
+ document in front of the user at once; nothing should happen while they
9
+ look at an unchanged canvas. A wrong size is resize_canvas on the same
10
+ document, never a second new_sprite.
11
11
 
12
12
  WITH A REFERENCE, MEASURE FIRST. Pixel art that was upscaled or re-encoded
13
- has a native scale (a whole number of screen pixels per sprite pixel); the
14
- sprite is made at that size. A photo or a painting has none: choose the
15
- smallest grid at which the smallest object that matters still reads.
13
+ has a native scale (whole screen pixels per sprite pixel); the sprite is
14
+ made at that size. A photo or a painting has none: choose the smallest
15
+ grid at which the smallest object that matters still reads.
16
16
 
17
17
  AUDIT CONSTRUCTION, NOT RESEMBLANCE, BEFORE YOU FINISH. Per object, crop
18
18
  the reference beside yours at one zoom (render_preview with reference and
19
19
  rect) and say how each is built: count the faces, name the light, say where
20
20
  the shadow falls. "Reads as a house" passed one flat wall three times;
21
- "theirs shows two walls, mine one" is a verdict. Nothing is done until
22
- every object has had one.
21
+ "theirs shows two walls, mine one" is a verdict. Every object gets one.
23
22
 
24
23
  JOBS. A job claims one document; a call that draws is refused without one.
25
24
  With one job open omit the job argument; with several pass it. begin_job
26
25
  with no document picks up the one you last finished. The user can revoke
27
- your job \u2014 if a call fails saying the user stopped you, do NOT retry: say
28
- what you were about to do and ask.
26
+ your job \u2014 if a call fails saying they stopped you, do NOT retry: say what
27
+ you were about to do and ask.
29
28
 
30
29
  WHAT THE USER SAYS BEATS EVERY DEFAULT \u2014 size, shape, palette, ratio,
31
- artist. Only when nobody said what it should look like do the defaults in
32
- the tool descriptions apply (new_sprite has the sizes and the naming,
33
- challenges the daily and weekly themes).
30
+ artist, view. Only when nobody said what it should look like do the
31
+ defaults apply: 240\xD7126 (80\xD742 if they said "simple"), a three-quarter view
32
+ (flat only if they said flat, side, front, top-down, icon or tile); new_sprite
33
+ has the sizes and the naming, challenges the daily and weekly themes.
34
34
 
35
35
  A BIG OR GENERATED GRID DOES NOT PASS THROUGH YOU: write it to a file and
36
- call draw_art with file. Do not ask the user abstract clarifying questions:
37
- commit to a good default and show something. finish_job when it is done.`}var mn=me("room"),Q=class{constructor({name:e="pixie",version:t=at,tools:n=[],instructions:o=mn}={}){this.name=e,this.version=t,this.tools=n,this.instructions=o,this.client=null,this.onCall=null}async handle(e){if(!e||e.jsonrpc!=="2.0")return null;let t=e.id===void 0||e.id===null;try{let n=await this.#e(e);return t?null:{jsonrpc:"2.0",id:e.id,result:n}}catch(n){return t?null:{jsonrpc:"2.0",id:e.id,error:{code:-32603,message:n?.message??String(n)}}}}async#e(e){switch(e.method){case"initialize":return this.client=e.params?.clientInfo??null,{protocolVersion:e.params?.protocolVersion??"2024-11-05",capabilities:{tools:{}},serverInfo:{name:this.name,version:this.version},instructions:this.instructions};case"notifications/initialized":case"notifications/cancelled":return{};case"ping":return{};case"tools/list":return{tools:this.tools.map(t=>({name:t.name,description:t.description,inputSchema:t.inputSchema}))};case"tools/call":{let t=this.tools.find(n=>n.name===e.params?.name);if(!t)throw new Error(`unknown tool: ${e.params?.name}`);this.onCall?.(t.name,e.params?.arguments??{});try{return await t.run(e.params?.arguments??{})}catch(n){return{isError:!0,content:[{type:"text",text:n?.message??String(n)}]}}}case"resources/list":return{resources:[]};case"prompts/list":return{prompts:[]};default:throw new Error(`unknown method: ${e.method}`)}}serve(e=process.stdin,t=process.stdout){let n="";return e.setEncoding("utf8"),e.on("data",async o=>{n+=o;let s;for(;(s=n.indexOf(`
36
+ call draw_art with file. Do not ask abstract clarifying questions: commit
37
+ to a good default and show something. finish_job when it is done.`}var Qn=Te("room"),ue=class{constructor({name:e="pixie",version:t=ae,tools:n=[],instructions:o=Qn}={}){this.name=e,this.version=t,this.tools=n,this.instructions=o,this.client=null,this.onCall=null}async handle(e){if(!e||e.jsonrpc!=="2.0")return null;let t=e.id===void 0||e.id===null;try{let n=await this.#e(e);return t?null:{jsonrpc:"2.0",id:e.id,result:n}}catch(n){return t?null:{jsonrpc:"2.0",id:e.id,error:{code:-32603,message:n?.message??String(n)}}}}async#e(e){switch(e.method){case"initialize":return this.client=e.params?.clientInfo??null,{protocolVersion:e.params?.protocolVersion??"2024-11-05",capabilities:{tools:{}},serverInfo:{name:this.name,version:this.version},instructions:this.instructions};case"notifications/initialized":case"notifications/cancelled":return{};case"ping":return{};case"tools/list":return{tools:this.tools.map(t=>({name:t.name,description:t.description,inputSchema:t.inputSchema}))};case"tools/call":{let t=this.tools.find(n=>n.name===e.params?.name);if(!t)throw new Error(`unknown tool: ${e.params?.name}`);this.onCall?.(t.name,e.params?.arguments??{});try{return await t.run(e.params?.arguments??{})}catch(n){return{isError:!0,content:[{type:"text",text:n?.message??String(n)}]}}}case"resources/list":return{resources:[]};case"prompts/list":return{prompts:[]};default:throw new Error(`unknown method: ${e.method}`)}}serve(e=process.stdin,t=process.stdout){let n="";return e.setEncoding("utf8"),e.on("data",async o=>{n+=o;let s;for(;(s=n.indexOf(`
38
38
  `))!==-1;){let i=n.slice(0,s).trim();if(n=n.slice(s+1),!i)continue;let a;try{a=JSON.parse(i)}catch{continue}let c=await this.handle(a);c&&t.write(JSON.stringify(c)+`
39
- `)}}),this}};var G=(r,e)=>{let t=process.argv.indexOf(`--${r}`);return t>-1?process.argv[t+1]:e},fe=process.env.SPRITE_EDITOR||"http://127.0.0.1:4242",ne=process.env.SPRITE_SERVER||"ws://127.0.0.1:4243",wn=new URL("./sprite_core.wasm",import.meta.url);function gn(r){let e=String(r??"").trim();return!e||!/^[a-z0-9]+([-_][a-z0-9]+)*$/.test(e)?e||"an agent":e.split(/[-_]/).map(t=>yn.has(t)?t.toUpperCase():t[0].toUpperCase()+t.slice(1)).join(" ")}var yn=new Set(["cli","ai","api","ide","mcp","sdk","gpt","llm"]);function bn(r){let e=new WebSocket(r),t={readyState:0,onopen:null,onmessage:null,onclose:null,send:n=>e.send(n),close:()=>e.close()};return e.addEventListener("open",()=>{t.readyState=1,t.onopen?.()}),e.addEventListener("message",n=>t.onmessage?.(n.data)),e.addEventListener("close",()=>{t.readyState=3,t.onclose?.()}),e.addEventListener("error",()=>{}),t}async function lt(r,{timeoutMs:e=700}={}){if(await ye(r,{timeoutMs:e}))return!0;try{let t=await fetch(`${b(r)}/health`,{signal:AbortSignal.timeout(e)});return!t.ok||!(t.headers.get("content-type")??"").includes("json")?!1:(await t.json().catch(()=>null))?.ok===!0}catch{return!1}}async function xn({editor:r=fe,server:e=ne,allowHost:t=!0,publicServer:n=z}={}){if(n)return{kind:"room",url:n,why:`the Pixie this agent belongs to, at ${b(n)}`};let o=await W.probe(r);return o?{kind:"local",url:r,why:o.connected?`a Pixie editor is open at ${r}`:`a Pixie server is running at ${r}, with no editor open yet`}:await lt(e)?{kind:"room",url:e,why:`a room server is running at ${e}`}:t?{kind:"room",url:e,start:!0,why:"nothing was running, so this process is the server"}:{kind:"room",url:e,why:`nothing is running yet; waiting for one at ${e}`}}async function Sn({kind:r=null,editor:e=fe,server:t=ne,wasm:n=wn,name:o="agent",allowHost:s=!0}={}){let i=r?{kind:r,url:r==="local"?e:t,why:"you said so"}:await xn({editor:e,server:t,allowHost:s}),a,c=null,l=null;if(i.kind==="local")a=new W({base:i.url,name:o}),a.announce();else{i.start;let u=be(i.url),p=n instanceof Uint8Array?n:await fn(n);c=await q.load(p);let w=await u;a=new Y(c,{author:j(),name:o,connect:(m,f={})=>{let g=new URLSearchParams;f.w&&g.set("w",String(f.w)),f.h&&g.set("h",String(f.h)),f.name&&g.set("name",f.name);let x=g.toString();return bn(`${w}/rooms/${m}${x?`?${x}`:""}`)},probe:()=>lt(i.url)}),a.serverUrl=w,a.getToken=async m=>{let f=await je(i.url,m);if(f!==void 0)return f;if(await ee(i.url))throw new Error(te(i.url))},a.createDocument=async m=>{let f=await _e(i.url,m);if(f!==void 0)return f;if(await ee(i.url))throw new Error(te(i.url))},a.control=async(m,f)=>{let g=await Re(i.url,m,f);if(g!==void 0)return g;throw new Error(await ee(i.url)?te(i.url):"no library here: this Sprite has no accounts, so there are no folders")},a.editorUrl=b(i.url),a.newsUrl=`${b(i.url)}/news.json`,a.hosted=l}let h=new Q({tools:tt(a),instructions:me(a.kind)}),d=h.handle.bind(h);return h.handle=async u=>{let p=await d(u);return u?.method==="initialize"&&h.client?.name&&(a.name=gn(h.client.name),a.announce()),p},{core:c,backend:a,mcp:h,chosen:i}}var ct=null;function ee(r){return ct??=ve(r).catch(()=>!1),ct}function En(r=process.argv){let e=r[1]??"";return/[\\/]pixie-mcp$/.test(e)?"pixie-mcp":/[\\/]sprite-mcp$/.test(e)?"sprite-mcp":"npx pixie-mcp"}function te(r,e=En()){return`this machine has not been given a credential for ${b(r)} yet. Run \`${e} login\` once, in a terminal \u2014 it opens a browser, and one button in it is the whole of it.`}if(De(import.meta.url)){let r=G("server",null)?"room":G("editor",null)?"local":null;if(process.argv.includes("login")){let e=G("server",z||ne);try{let t=await Ue(e,{onUrl:n=>process.stderr.write(`
39
+ `)}}),this}};var Z=(r,e)=>{let t=process.argv.indexOf(`--${r}`);return t>-1?process.argv[t+1]:e},Pe=process.env.SPRITE_EDITOR||"http://127.0.0.1:4242",de=process.env.SPRITE_SERVER||"ws://127.0.0.1:4243",tr=new URL("./sprite_core.wasm",import.meta.url);function nr(r){let e=String(r??"").trim();return!e||!/^[a-z0-9]+([-_][a-z0-9]+)*$/.test(e)?e||"an agent":e.split(/[-_]/).map(t=>rr.has(t)?t.toUpperCase():t[0].toUpperCase()+t.slice(1)).join(" ")}var rr=new Set(["cli","ai","api","ide","mcp","sdk","gpt","llm"]);function or(r){let e=new WebSocket(r),t={readyState:0,onopen:null,onmessage:null,onclose:null,send:n=>e.send(n),close:()=>e.close()};return e.addEventListener("open",()=>{t.readyState=1,t.onopen?.()}),e.addEventListener("message",n=>t.onmessage?.(n.data)),e.addEventListener("close",()=>{t.readyState=3,t.onclose?.()}),e.addEventListener("error",()=>{}),t}async function At(r,{timeoutMs:e=700}={}){if(await Ie(r,{timeoutMs:e}))return!0;try{let t=await fetch(`${x(r)}/health`,{signal:AbortSignal.timeout(e)});return!t.ok||!(t.headers.get("content-type")??"").includes("json")?!1:(await t.json().catch(()=>null))?.ok===!0}catch{return!1}}async function sr({editor:r=Pe,server:e=de,allowHost:t=!0,publicServer:n=W}={}){if(n)return{kind:"room",url:n,why:`the Pixie this agent belongs to, at ${x(n)}`};let o=await X.probe(r);return o?{kind:"local",url:r,why:o.connected?`a Pixie editor is open at ${r}`:`a Pixie server is running at ${r}, with no editor open yet`}:await At(e)?{kind:"room",url:e,why:`a room server is running at ${e}`}:t?{kind:"room",url:e,start:!0,why:"nothing was running, so this process is the server"}:{kind:"room",url:e,why:`nothing is running yet; waiting for one at ${e}`}}async function ir({kind:r=null,editor:e=Pe,server:t=de,wasm:n=tr,name:o="agent",allowHost:s=!0}={}){let i=r?{kind:r,url:r==="local"?e:t,why:"you said so"}:await sr({editor:e,server:t,allowHost:s}),a,c=null,h=null;if(i.kind==="local")a=new X({base:i.url,name:o}),a.announce();else{i.start;let p=Me(i.url),d=n instanceof Uint8Array?n:await er(n);c=await ne.load(d);let f=await p;a=new le(c,{author:_(),name:o,connect:(m,w={})=>{let g=new URLSearchParams;w.w&&g.set("w",String(w.w)),w.h&&g.set("h",String(w.h)),w.name&&g.set("name",w.name);let y=g.toString();return or(`${f}/rooms/${m}${y?`?${y}`:""}`)},probe:()=>At(i.url)}),a.serverUrl=f,a.getToken=async m=>{let w=await qe(i.url,m);if(w!==void 0)return w;if(await Y(i.url))throw new Error(Q(i.url))},a.createDocument=async m=>{let w=await Be(i.url,m);if(w!==void 0)return w;if(await Y(i.url))throw new Error(Q(i.url))},a.control=async(m,w)=>{let g=await Ge(i.url,m,w);if(g!==void 0)return g;throw new Error(await Y(i.url)?Q(i.url):"no library here: this Sprite has no accounts, so there are no folders")},a.openControl=async(m,w={})=>{if(await Y(i.url))throw new Error(Q(i.url));let g=await fetch(`${x(i.url)}${m}`,{method:w.method??"GET",headers:w.body!=null?{"Content-Type":"application/json"}:{},body:w.body!=null?JSON.stringify(w.body):void 0}),y=await g.json().catch(()=>({}));if(!g.ok)throw new Error(y?.error?String(y.error):`the server said ${g.status}`);return y},a.editorUrl=x(i.url),a.newsUrl=`${x(i.url)}/news.json`,a.hosted=h}let l=new ue({tools:xt(a),instructions:Te(a.kind)}),u=l.handle.bind(l);return l.handle=async p=>{let d=await u(p);return p?.method==="initialize"&&l.client?.name&&(a.name=nr(l.client.name),a.announce()),d},{core:c,backend:a,mcp:l,chosen:i}}var _t=null;function Y(r){return _t??=Fe(r).catch(()=>!1),_t}function ar(r=process.argv){let e=r[1]??"";return/[\\/]pixie-mcp$/.test(e)?"pixie-mcp":/[\\/]sprite-mcp$/.test(e)?"sprite-mcp":"npx pixie-mcp"}function Q(r,e=ar()){return`this machine has not been given a credential for ${x(r)} yet. Run \`${e} login\` once, in a terminal \u2014 it opens a browser, and one button in it is the whole of it.`}if(tt(import.meta.url)){let r=Z("server",null)?"room":Z("editor",null)?"local":null;if(process.argv.includes("login")){let e=Z("server",W||de);try{let t=await Qe(e,{onUrl:n=>process.stderr.write(`
40
40
  ${n}
41
41
  `),label:"the Pixie MCP server"});process.stderr.write(`
42
42
  This machine can now draw as ${t.email??t.name??"you"}.
@@ -44,6 +44,6 @@ commit to a good default and show something. finish_job when it is done.`}var mn
44
44
  `)}catch(t){process.stderr.write(`
45
45
  ${t.message}
46
46
 
47
- `),process.exit(1)}}else{let{mcp:e,chosen:t}=await Sn({kind:r,editor:G("editor",fe),server:G("server",ne),allowHost:!process.argv.includes("--no-host")});process.stderr.write(`sprite-mcp \u2192 ${t.kind} (${t.why})
48
- `),e.serve(),t.kind==="room"&&(async()=>(await F(t.url))?.token||await ee(t.url)&&process.stderr.write(`sprite-mcp \u2192 ${te(t.url)}
49
- `))()}}export{fe as DEFAULT_EDITOR,ne as DEFAULT_SERVER,En as commandName,Sn as createServer,xn as detect,gn as presentable,lt as probeRooms,te as signInFirst,bn as websocketTransport};
47
+ `),process.exit(1)}}else{let{mcp:e,chosen:t}=await ir({kind:r,editor:Z("editor",Pe),server:Z("server",de),allowHost:!process.argv.includes("--no-host")});process.stderr.write(`sprite-mcp \u2192 ${t.kind} (${t.why})
48
+ `),e.serve(),t.kind==="room"&&(async()=>(await H(t.url))?.token||await Y(t.url)&&process.stderr.write(`sprite-mcp \u2192 ${Q(t.url)}
49
+ `))()}}export{Pe as DEFAULT_EDITOR,de as DEFAULT_SERVER,ar as commandName,ir as createServer,sr as detect,nr as presentable,At as probeRooms,Q as signInFirst,or as websocketTransport};
package/sprite_core.wasm CHANGED
Binary file