@howaboua/pi-pet 0.1.1 → 0.1.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @howaboua/pi-pet
2
2
 
3
+ ## 0.1.3
4
+
5
+ - Expose Pi Pet's extension from the package root so aggregate extension packages can load it.
6
+
7
+ ## 0.1.2
8
+
9
+ - Fixed waiting indicators for extension UI prompts.
10
+
3
11
  ## 0.1.1
4
12
 
5
13
  ### Changes
@@ -11,6 +19,3 @@
11
19
  - Keep authored pets and generated displays in durable Pi agent storage, with optional per-repository pet selection.
12
20
  - Attach the local device directly or remote devices through Pi-owned SSH sessions, with each folder remembering where its sessions appear.
13
21
  - Get exact install and reload guidance when GipPity Control is missing or outdated.
14
-
15
- - Updated dependencies [[`4b4e42f`](https://github.com/IgorWarzocha/howaboua-pi-stuff/commit/4b4e42f7659e42854ec81cb502bf69a48422d9eb)]:
16
- - @howaboua/pi-gippity-control@0.0.13
package/README.md CHANGED
@@ -47,9 +47,9 @@ The remote device needs npm and Node 22 or newer; runtime extraction uses PowerS
47
47
  /pet attach desktop
48
48
  ```
49
49
 
50
- Pi Pet starts GipPity for the active Pi session and uses its saved LAN settings unless `/pet attach <device> <gippity-url>` supplies an override. Local attachment runs the Pi-owned bootstrap directly; remote attachment sends it through SSH. Both keep the desktop source and build at `~/.pi/agent/pi-pet` on that device. On connection Pi Pet compares the loaded package version and source digest with the recorded build, runs the copy, `npm install`, and `npm run build` steps only when they differ, then starts Electron. Pi or Clawa exiting stops Electron and GipPity but does not discard the build. It creates no application installation, login item, or background service.
50
+ Pi Pet starts GipPity for the active Pi session and uses its saved LAN settings unless `/pet attach <device> <gippity-url>` supplies an override. Local attachment runs the Pi-owned bootstrap directly and keeps its build under Pi's agent directory at `pi-pet/`. Remote attachment sends the bootstrap through SSH and uses `~/.pi/agent/pi-pet` on that device. On connection Pi Pet compares the loaded package version and source digest with the recorded build, runs the copy, `npm install`, and `npm run build` steps only when they differ, then starts Electron. Pi exiting stops Electron and GipPity. Closing Clawa stops Electron while GipPity remains active for its Pi session. Neither path discards the build or creates an application installation, login item, or background service.
51
51
 
52
- The global registry at `<pi-agent-directory>/pi-pet.json`, normally `~/.pi/agent/pi-pet.json`, retains reusable local and SSH device definitions. The current folder's `.pi/pi-pet.json` selects which devices receive that folder's Pi sessions. Later sessions opened in the same folder start only those devices automatically. Inspect them with `/pet status`, relaunch them with `/pet restart`, or remove one from the folder with `/pet detach desktop`. SSH options, keys, proxies, and host routing stay in `~/.ssh/config`.
52
+ The global registry at `<pi-agent-directory>/pi-pet.json`, normally `~/.pi/agent/pi-pet.json`, retains reusable local and SSH device definitions. `PI_CODING_AGENT_DIR` changes that local agent directory. The current folder's `.pi/pi-pet.json` selects which devices receive that folder's Pi sessions. Later sessions opened in the same folder start only those devices automatically. Inspect them with `/pet status`, relaunch them with `/pet restart`, or remove one from the folder with `/pet detach desktop`. SSH options, keys, proxies, and host routing stay in `~/.ssh/config`.
53
53
 
54
54
  ```json
55
55
  {
@@ -77,7 +77,7 @@ Authoring guides are loaded only when requested rather than exposed as permanent
77
77
 
78
78
  Pi Pet resolves `authoring/PET-GUIDE.md` from its own source or npm installation and asks Pi to follow it. That guide routes one-action work or full character hatching into the bundled references, deterministic atlas tooling, and visual review process.
79
79
 
80
- Pi resolves its installed package from the guide path and uses its npm/Node authoring commands without requiring Bun or a monorepo checkout. Bundled pets are templates: the first edit copies one to `~/.pi/agent/pi-pet/pets/`, authoring evidence stays under `~/.pi/agent/pi-pet/runs/`, and generated miniapp assets stay under `~/.pi/agent/pi-pet/web/`. Package updates therefore do not overwrite user pets.
80
+ Pi resolves its installed package from the guide path and uses its npm/Node authoring commands without requiring Bun or a monorepo checkout. Bundled pets are templates: the first edit copies one to `<pi-agent-directory>/pi-pet/pets/`, authoring evidence stays under `<pi-agent-directory>/pi-pet/runs/`, and generated miniapp assets stay under `<pi-agent-directory>/pi-pet/web/`. Package updates therefore do not overwrite user pets.
81
81
 
82
82
  A repository can select any generated durable pet and its devices without duplicating assets in `.pi/pi-pet.json`:
83
83
 
package/changelog.ts CHANGED
@@ -182,9 +182,19 @@ function packageMarkdown(
182
182
  registration: PackageRegistration,
183
183
  entries: ChangelogEntry[],
184
184
  ): string {
185
+ const body = (entry: ChangelogEntry) =>
186
+ entry.content
187
+ .replace(/^##[^\n]*\n*/, "")
188
+ .replace(/^###\s+(?:Changes|Minor Changes|Patch Changes)\s*\n*/gim, "")
189
+ .replace(/^###\s+(.+)$/gm, "**$1**");
190
+ if (entries.length === 1) {
191
+ const [entry] = entries;
192
+ if (!entry) return "";
193
+ return `## ${registration.name} ${entry.version}\n\n${body(entry)}`;
194
+ }
185
195
  return [
186
196
  `## ${registration.name}`,
187
- ...entries.map((entry) => entry.content.replace(/^##\s+/, "### ")),
197
+ ...entries.map((entry) => `**${entry.version}**\n\n${body(entry)}`),
188
198
  ].join("\n\n");
189
199
  }
190
200
 
@@ -1,3 +1,3 @@
1
- import{join as ye}from"node:path";import{fileURLToPath as Ee}from"node:url";import{randomUUID as ae}from"node:crypto";import{copyFileSync as De,lstatSync as ce,mkdirSync as Ue,readFileSync as Le,statSync as Fe}from"node:fs";import{cp as fe,mkdir as ue,readFile as de,rename as pe,rm as we,stat as me,writeFile as ze}from"node:fs/promises";import{homedir as O}from"node:os";import{dirname as ge,join as m}from"node:path";import{readFile as M,realpath as P,stat as N}from"node:fs/promises";import{join as y,resolve as G,sep as v}from"node:path";import{readFile as j}from"node:fs/promises";function T(e,t,n){if(n<10)return;return{format:"webp",width:1+e.readUIntLE(t+4,3),height:1+e.readUIntLE(t+7,3)}}function D(e,t,n){if(n<10||e[t+3]!==157||e[t+4]!==1||e[t+5]!==42)return;return{format:"webp",width:e.readUInt16LE(t+6)&16383,height:e.readUInt16LE(t+8)&16383}}function U(e,t,n){if(n<5||e[t]!==47)return;let r=e[t+1]??0,o=e[t+2]??0,a=e[t+3]??0,s=e[t+4]??0;return{format:"webp",width:1+((r|o<<8)&16383),height:1+((o>>6|a<<2|s<<10)&16383)}}function L(e,t,n,r){if(t==="VP8X")return T(e,n,r);if(t==="VP8 ")return D(e,n,r);if(t==="VP8L")return U(e,n,r);return}function F(e){if(e.length<30||e.toString("ascii",0,4)!=="RIFF"||e.toString("ascii",8,12)!=="WEBP")return;let t=12;while(t+8<=e.length){let n=e.toString("ascii",t,t+4),r=e.readUInt32LE(t+4),o=t+8;if(o+r>e.length)throw Error("Truncated WebP chunk.");let a=L(e,n,o,r);if(a)return a;t=o+r+r%2}throw Error("WebP dimensions were not found.")}function V(e){if(e.length>=24&&e.subarray(0,8).toString("hex")==="89504e470d0a1a0a")return{format:"png",width:e.readUInt32BE(16),height:e.readUInt32BE(20)};let n=F(e);if(n)return n;throw Error("Asset must be a PNG or WebP image.")}async function k(e){return V(await j(e))}var z=/^[a-z0-9][a-z0-9._-]{0,63}$/,J=new Set(["__proto__","constructor"]),u=Object.freeze({actionName:64,noteText:280,manifestBytes:131072,catalogBytes:262144,assetBytes:16777216,frameCount:64,decodedPixels:16000000});class i extends Error{name="ContractError"}function B(e,t,n){if(typeof e!=="string")throw new i(`${t} must be a string.`);let r=e.trim();if(!r)throw new i(`${t} cannot be empty.`);if(r.length>n)throw new i(`${t} exceeds ${n} characters.`);return r}function d(e,t="action"){let n=B(e,t,u.actionName);if(n==="list")throw new i(`${t} cannot use the reserved pet_show discovery name list.`);if(J.has(n))throw new i(`${t} cannot use a reserved object property name.`);if(!z.test(n))throw new i(`${t} must use lowercase letters, numbers, dots, underscores, or hyphens.`);return n}function b(e){if(!e||e.includes("\x00")||e.includes("\\")||e.startsWith("/"))return!1;return e.split("/").every((n)=>n!==""&&n!=="."&&n!=="..")}var p={width:192,height:208},W=[["idle",0,[280,110,110,140,140,320]],["running-right",1,[120,120,120,120,120,120,120,220]],["running-left",2,[120,120,120,120,120,120,120,220]],["waving",3,[140,140,140,280]],["jumping",4,[140,140,140,140,280]],["failed",5,[140,140,140,140,140,140,140,240]],["waiting",6,[150,150,150,150,150,260]],["running",7,[120,120,120,120,120,220]],["review",8,[150,150,150,150,150,280]]],H=Object.freeze({active:"running",error:"failed",hello:"waving",settled:"review",success:"jumping",thinking:"running",working:"running"});function w(e,t){if(!e||typeof e!=="object"||Array.isArray(e))throw new i(`${t} must be an object.`);return e}function h(e,t,n,r){if(!Number.isInteger(e)||e<n||e>r)throw new i(`${t} must be an integer from ${n} to ${r}.`);return e}async function A(e,t){let n=await N(e);if(!n.isFile()||n.size>t)throw new i(`${e} is not a bounded regular file.`);return JSON.parse(await M(e,"utf8"))}function X(e,t){return t.map((n,r)=>({x:r*p.width,y:e*p.height,width:p.width,height:p.height,durationMs:n}))}function Y(e){let t=w(e,"pet.json"),n=new Set(["id","displayName","description","spriteVersionNumber","spritesheetPath"]);for(let s of Object.keys(t))if(!n.has(s))throw new i(`pet.json has unknown field: ${s}.`);let r=d(t.id,"pet id");if(typeof t.displayName!=="string"||!t.displayName.trim()||t.displayName.length>80)throw new i("pet.json displayName must contain 1-80 characters.");if(typeof t.description!=="string"||!t.description.trim()||t.description.length>280)throw new i("pet.json description must contain 1-280 characters.");if(t.spriteVersionNumber!==2)throw new i("Only Codex spriteVersionNumber 2 is supported.");if(typeof t.spritesheetPath!=="string"||!b(t.spritesheetPath))throw new i("pet.json spritesheetPath must be a safe relative path.");let o={},a={};for(let[s,f,c]of W)o[s]={name:s,asset:t.spritesheetPath,frames:X(f,c),loop:!0};for(let s=0;s<16;s+=1){let f=s*22.5,c=`look-${String(f).padStart(3,"0").replace(".5","_5")}`,E=s<8?9:10,l=s%8;a[c]={name:c,asset:t.spritesheetPath,frames:[{x:l*p.width,y:E*p.height,...p,durationMs:1000}],loop:!0}}return{schemaVersion:1,id:r,displayName:t.displayName.trim(),description:t.description.trim(),defaultAction:"idle",canvas:{...p},actions:o,aliases:{...H},directions:a}}function q(e,t){let n=w(t,`action ${e}`),r=new Set(["asset","frames","loop","next"]);for(let s of Object.keys(n))if(!r.has(s))throw new i(`action ${e} has unknown field: ${s}.`);if(typeof n.asset!=="string"||!b(n.asset))throw new i(`action ${e} asset must be a safe relative path.`);if(!Array.isArray(n.frames)||n.frames.length===0||n.frames.length>u.frameCount)throw new i(`action ${e} must have 1-${u.frameCount} frames.`);let o=n.frames.map((s,f)=>{let c=w(s,`action ${e} frame ${f}`),E=new Set(["x","y","width","height","durationMs"]);for(let x of Object.keys(c))if(!E.has(x))throw new i(`action ${e} frame ${f} has unknown field: ${x}.`);let l=h(c.width,"frame width",1,4096),$=h(c.height,"frame height",1,4096);if(l*$>u.decodedPixels)throw new i(`action ${e} frame ${f} is too large.`);return{x:h(c.x,"frame x",0,32768),y:h(c.y,"frame y",0,32768),width:l,height:$,durationMs:h(c.durationMs,"frame durationMs",16,60000)}});if(typeof n.loop!=="boolean")throw new i(`action ${e} loop must be a boolean.`);let a={name:e,asset:n.asset,frames:o,loop:n.loop};if(n.next!==void 0)a.next=d(n.next,`action ${e} next`);return a}async function K(e){let t=y(e,"pet.pi.json");try{return w(await A(t,u.manifestBytes),"pet.pi.json")}catch(n){if(n.code==="ENOENT")return;throw n}}function Z(e){let t=new Set(["schemaVersion","defaultAction","actions","aliases"]);for(let n of Object.keys(e))if(!t.has(n))throw new i(`pet.pi.json has unknown field: ${n}.`);if(e.schemaVersion!==1)throw new i("pet.pi.json schemaVersion must be 1.")}function Q(e,t){if(e===void 0)return;for(let[n,r]of Object.entries(w(e,"pet.pi.json actions"))){let o=d(n,"action name");t.actions[o]=q(o,r),delete t.aliases[o]}}function ee(e,t){if(e===void 0)return;for(let[n,r]of Object.entries(w(e,"pet.pi.json aliases"))){let o=d(n,"alias name");if(t.actions[o])throw new i(`Alias ${o} conflicts with an action of the same name.`);t.aliases[o]=d(r,`alias ${o} target`)}}function te(e){if(!e.actions[e.defaultAction])throw new i(`Unknown defaultAction: ${e.defaultAction}.`);for(let t of Object.values(e.actions))if(t.next!==void 0&&!e.actions[t.next])throw new i(`Action ${t.name} has unknown next action: ${t.next}.`);for(let[t,n]of Object.entries(e.aliases))if(!e.actions[n])throw new i(`Alias ${t} has unknown target action: ${n}.`)}function ne(e){if(Buffer.byteLength(JSON.stringify(e))>u.catalogBytes)throw new i(`Generated pet catalog exceeds ${u.catalogBytes} bytes.`)}async function re(e,t){let n=await K(e);if(!n)return;if(Z(n),Q(n.actions,t),ee(n.aliases,t),n.defaultAction!==void 0)t.defaultAction=d(n.defaultAction,"defaultAction");te(t)}async function oe(e,t){let n=await P(y(e,t));if(n!==e&&!n.startsWith(`${e}${v}`))throw new i(`Asset escapes pet directory: ${t}.`);let r=await N(n);if(!r.isFile()||r.size===0||r.size>u.assetBytes)throw new i(`Asset is not a bounded regular file: ${t}.`);let o=await k(n);if(o.width*o.height>u.decodedPixels)throw new i(`Asset has too many decoded pixels: ${t}.`);return o}function ie(e,t,n,r){for(let o of t)for(let a of o.frames)if(a.x+a.width>n||a.y+a.height>r)throw new i(`Action ${o.name} has a frame outside ${e}.`)}async function se(e,t,n){let r=await P(e),o=[...Object.values(t.actions),...Object.values(t.directions)],a=new Set(o.map((s)=>s.asset));for(let s of a){let f=await oe(r,s);if(s===n&&(f.width!==1536||f.height!==2288))throw new i(`Codex v2 spritesheet must be 1536x2288; got ${f.width}x${f.height}.`);ie(s,o.filter((c)=>c.asset===s),f.width,f.height)}}async function S(e,t){let n=d(t,"active pet"),r=await P(G(e)),o=await P(y(r,n));if(o!==r&&!o.startsWith(`${r}${v}`))throw new i("Active pet escapes the pets directory.");let a=await A(y(o,"pet.json"),u.manifestBytes),s=Y(a);if(s.id!==n)throw new i(`Active pet directory ${n} does not match manifest id ${s.id}.`);await re(o,s),ne(s);let f=w(a,"pet.json").spritesheetPath;return await se(o,s,f),{directory:o,catalog:s}}var he=16384;function le(e=process.env,t=O()){return e.PI_CODING_AGENT_DIR?.trim()||m(t,".pi","agent")}function g(e=process.env,t=O()){return m(le(e,t),"pi-pet")}function Pe(e){if(!e||typeof e!=="object"||Array.isArray(e))throw Error("Pi Pet config must be an object.");let t=e,n=Object.keys(t).find((r)=>!["schemaVersion","activePet"].includes(r));if(n)throw Error(`Pi Pet config has unknown field: ${n}.`);if(t.schemaVersion!==1)throw Error("Unsupported Pi Pet config schemaVersion.");return{schemaVersion:1,activePet:d(t.activePet,"active pet")}}async function _(e=g()){try{let t=m(e,"config.json"),n=await me(t);if(!n.isFile()||n.size>he)throw Error(`Pi Pet config must be a bounded regular file: ${t}`);return Pe(JSON.parse(await de(t,"utf8")))}catch(t){if(t.code==="ENOENT")return;throw t}}async function C(e,t,n=g()){let r=d(t,"pet id"),o=m(e,"pets",r),a=m(n,"pets",r),s=!0;try{let c=ce(a);if(!c.isDirectory()||c.isSymbolicLink())throw Error(`User pet path is not a directory: ${a}`)}catch(c){if(c.code!=="ENOENT")throw c;s=!1}if(s)return await S(m(n,"pets"),r),a;await ue(ge(a),{recursive:!0,mode:448});let f=`${a}.${process.pid}.${ae()}.tmp`;try{return await fe(o,f,{recursive:!0,errorOnExist:!0,force:!1}),await pe(f,a),a}catch(c){throw await we(f,{recursive:!0,force:!0}),c}}async function R(e=g()){return(await _(e))?.activePet||"clawa"}var be=Ee(new URL("../../",import.meta.url)),I=process.argv[2]||await R(),Se=await C(be,I);process.stdout.write(`Pet: ${Se}
2
- Runs: ${ye(g(),"runs",I)}
1
+ import{join as Ee}from"node:path";import{fileURLToPath as be}from"node:url";import{randomUUID as ce}from"node:crypto";import{copyFileSync as Ue,lstatSync as fe,mkdirSync as Le,readFileSync as Fe,statSync as Ve}from"node:fs";import{cp as ue,mkdir as de,readFile as pe,rename as we,rm as me,stat as ge,writeFile as Je}from"node:fs/promises";import{homedir as _}from"node:os";import{dirname as he,join as m}from"node:path";import{readFile as G,realpath as P,stat as v}from"node:fs/promises";import{join as y,resolve as W,sep as A}from"node:path";import{readFile as T}from"node:fs/promises";function D(e,t,n){if(n<10)return;return{format:"webp",width:1+e.readUIntLE(t+4,3),height:1+e.readUIntLE(t+7,3)}}function U(e,t,n){if(n<10||e[t+3]!==157||e[t+4]!==1||e[t+5]!==42)return;return{format:"webp",width:e.readUInt16LE(t+6)&16383,height:e.readUInt16LE(t+8)&16383}}function L(e,t,n){if(n<5||e[t]!==47)return;let r=e[t+1]??0,o=e[t+2]??0,a=e[t+3]??0,s=e[t+4]??0;return{format:"webp",width:1+((r|o<<8)&16383),height:1+((o>>6|a<<2|s<<10)&16383)}}function F(e,t,n,r){if(t==="VP8X")return D(e,n,r);if(t==="VP8 ")return U(e,n,r);if(t==="VP8L")return L(e,n,r);return}function V(e){if(e.length<30||e.toString("ascii",0,4)!=="RIFF"||e.toString("ascii",8,12)!=="WEBP")return;let t=12;while(t+8<=e.length){let n=e.toString("ascii",t,t+4),r=e.readUInt32LE(t+4),o=t+8;if(o+r>e.length)throw Error("Truncated WebP chunk.");let a=F(e,n,o,r);if(a)return a;t=o+r+r%2}throw Error("WebP dimensions were not found.")}function z(e){if(e.length>=24&&e.subarray(0,8).toString("hex")==="89504e470d0a1a0a")return{format:"png",width:e.readUInt32BE(16),height:e.readUInt32BE(20)};let n=V(e);if(n)return n;throw Error("Asset must be a PNG or WebP image.")}async function N(e){return z(await T(e))}var J=/^[a-z0-9][a-z0-9._-]{0,63}$/,B=new Set(["__proto__","constructor"]),u=Object.freeze({actionName:64,noteText:280,manifestBytes:131072,catalogBytes:262144,assetBytes:16777216,frameCount:64,decodedPixels:16000000});class i extends Error{name="ContractError"}function M(e,t,n){if(typeof e!=="string")throw new i(`${t} must be a string.`);let r=e.trim();if(!r)throw new i(`${t} cannot be empty.`);if(r.length>n)throw new i(`${t} exceeds ${n} characters.`);return r}function d(e,t="action"){let n=M(e,t,u.actionName);if(n==="list")throw new i(`${t} cannot use the reserved pet_show discovery name list.`);if(B.has(n))throw new i(`${t} cannot use a reserved object property name.`);if(!J.test(n))throw new i(`${t} must use lowercase letters, numbers, dots, underscores, or hyphens.`);return n}function b(e){if(!e||e.includes("\x00")||e.includes("\\")||e.startsWith("/"))return!1;return e.split("/").every((n)=>n!==""&&n!=="."&&n!=="..")}var p={width:192,height:208},H=[["idle",0,[280,110,110,140,140,320]],["running-right",1,[120,120,120,120,120,120,120,220]],["running-left",2,[120,120,120,120,120,120,120,220]],["waving",3,[140,140,140,280]],["jumping",4,[140,140,140,140,280]],["failed",5,[140,140,140,140,140,140,140,240]],["waiting",6,[150,150,150,150,150,260]],["running",7,[120,120,120,120,120,220]],["review",8,[150,150,150,150,150,280]]],X=Object.freeze({active:"running",error:"failed",hello:"waving",settled:"review",success:"jumping",thinking:"running",working:"running"});function w(e,t){if(!e||typeof e!=="object"||Array.isArray(e))throw new i(`${t} must be an object.`);return e}function h(e,t,n,r){if(!Number.isInteger(e)||e<n||e>r)throw new i(`${t} must be an integer from ${n} to ${r}.`);return e}async function O(e,t){let n=await v(e);if(!n.isFile()||n.size>t)throw new i(`${e} is not a bounded regular file.`);return JSON.parse(await G(e,"utf8"))}function Y(e,t){return t.map((n,r)=>({x:r*p.width,y:e*p.height,width:p.width,height:p.height,durationMs:n}))}function q(e){let t=w(e,"pet.json"),n=new Set(["id","displayName","description","spriteVersionNumber","spritesheetPath"]);for(let s of Object.keys(t))if(!n.has(s))throw new i(`pet.json has unknown field: ${s}.`);let r=d(t.id,"pet id");if(typeof t.displayName!=="string"||!t.displayName.trim()||t.displayName.length>80)throw new i("pet.json displayName must contain 1-80 characters.");if(typeof t.description!=="string"||!t.description.trim()||t.description.length>280)throw new i("pet.json description must contain 1-280 characters.");if(t.spriteVersionNumber!==2)throw new i("Only Codex spriteVersionNumber 2 is supported.");if(typeof t.spritesheetPath!=="string"||!b(t.spritesheetPath))throw new i("pet.json spritesheetPath must be a safe relative path.");let o={},a={};for(let[s,f,c]of H)o[s]={name:s,asset:t.spritesheetPath,frames:Y(f,c),loop:!0};for(let s=0;s<16;s+=1){let f=s*22.5,c=`look-${String(f).padStart(3,"0").replace(".5","_5")}`,E=s<8?9:10,l=s%8;a[c]={name:c,asset:t.spritesheetPath,frames:[{x:l*p.width,y:E*p.height,...p,durationMs:1000}],loop:!0}}return{schemaVersion:1,id:r,displayName:t.displayName.trim(),description:t.description.trim(),defaultAction:"idle",canvas:{...p},actions:o,aliases:{...X},directions:a}}function K(e,t){let n=w(t,`action ${e}`),r=new Set(["asset","frames","loop","next"]);for(let s of Object.keys(n))if(!r.has(s))throw new i(`action ${e} has unknown field: ${s}.`);if(typeof n.asset!=="string"||!b(n.asset))throw new i(`action ${e} asset must be a safe relative path.`);if(!Array.isArray(n.frames)||n.frames.length===0||n.frames.length>u.frameCount)throw new i(`action ${e} must have 1-${u.frameCount} frames.`);let o=n.frames.map((s,f)=>{let c=w(s,`action ${e} frame ${f}`),E=new Set(["x","y","width","height","durationMs"]);for(let k of Object.keys(c))if(!E.has(k))throw new i(`action ${e} frame ${f} has unknown field: ${k}.`);let l=h(c.width,"frame width",1,4096),x=h(c.height,"frame height",1,4096);if(l*x>u.decodedPixels)throw new i(`action ${e} frame ${f} is too large.`);return{x:h(c.x,"frame x",0,32768),y:h(c.y,"frame y",0,32768),width:l,height:x,durationMs:h(c.durationMs,"frame durationMs",16,60000)}});if(typeof n.loop!=="boolean")throw new i(`action ${e} loop must be a boolean.`);let a={name:e,asset:n.asset,frames:o,loop:n.loop};if(n.next!==void 0)a.next=d(n.next,`action ${e} next`);return a}async function Z(e){let t=y(e,"pet.pi.json");try{return w(await O(t,u.manifestBytes),"pet.pi.json")}catch(n){if(n.code==="ENOENT")return;throw n}}function Q(e){let t=new Set(["schemaVersion","defaultAction","actions","aliases"]);for(let n of Object.keys(e))if(!t.has(n))throw new i(`pet.pi.json has unknown field: ${n}.`);if(e.schemaVersion!==1)throw new i("pet.pi.json schemaVersion must be 1.")}function ee(e,t){if(e===void 0)return;for(let[n,r]of Object.entries(w(e,"pet.pi.json actions"))){let o=d(n,"action name");t.actions[o]=K(o,r),delete t.aliases[o]}}function te(e,t){if(e===void 0)return;for(let[n,r]of Object.entries(w(e,"pet.pi.json aliases"))){let o=d(n,"alias name");if(t.actions[o])throw new i(`Alias ${o} conflicts with an action of the same name.`);t.aliases[o]=d(r,`alias ${o} target`)}}function ne(e){if(!e.actions[e.defaultAction])throw new i(`Unknown defaultAction: ${e.defaultAction}.`);for(let t of Object.values(e.actions))if(t.next!==void 0&&!e.actions[t.next])throw new i(`Action ${t.name} has unknown next action: ${t.next}.`);for(let[t,n]of Object.entries(e.aliases))if(!e.actions[n])throw new i(`Alias ${t} has unknown target action: ${n}.`)}function re(e){if(Buffer.byteLength(JSON.stringify(e))>u.catalogBytes)throw new i(`Generated pet catalog exceeds ${u.catalogBytes} bytes.`)}async function oe(e,t){let n=await Z(e);if(!n)return;if(Q(n),ee(n.actions,t),te(n.aliases,t),n.defaultAction!==void 0)t.defaultAction=d(n.defaultAction,"defaultAction");ne(t)}async function ie(e,t){let n=await P(y(e,t));if(n!==e&&!n.startsWith(`${e}${A}`))throw new i(`Asset escapes pet directory: ${t}.`);let r=await v(n);if(!r.isFile()||r.size===0||r.size>u.assetBytes)throw new i(`Asset is not a bounded regular file: ${t}.`);let o=await N(n);if(o.width*o.height>u.decodedPixels)throw new i(`Asset has too many decoded pixels: ${t}.`);return o}function se(e,t,n,r){for(let o of t)for(let a of o.frames)if(a.x+a.width>n||a.y+a.height>r)throw new i(`Action ${o.name} has a frame outside ${e}.`)}async function ae(e,t,n){let r=await P(e),o=[...Object.values(t.actions),...Object.values(t.directions)],a=new Set(o.map((s)=>s.asset));for(let s of a){let f=await ie(r,s);if(s===n&&(f.width!==1536||f.height!==2288))throw new i(`Codex v2 spritesheet must be 1536x2288; got ${f.width}x${f.height}.`);se(s,o.filter((c)=>c.asset===s),f.width,f.height)}}async function S(e,t){let n=d(t,"active pet"),r=await P(W(e)),o=await P(y(r,n));if(o!==r&&!o.startsWith(`${r}${A}`))throw new i("Active pet escapes the pets directory.");let a=await O(y(o,"pet.json"),u.manifestBytes),s=q(a);if(s.id!==n)throw new i(`Active pet directory ${n} does not match manifest id ${s.id}.`);await oe(o,s),re(s);let f=w(a,"pet.json").spritesheetPath;return await ae(o,s,f),{directory:o,catalog:s}}var le=16384;function Pe(e=process.env,t=_()){return e.PI_CODING_AGENT_DIR?.trim()||m(t,".pi","agent")}function g(e=process.env,t=_()){return m(Pe(e,t),"pi-pet")}function ye(e){if(!e||typeof e!=="object"||Array.isArray(e))throw Error("Pi Pet config must be an object.");let t=e,n=Object.keys(t).find((r)=>!["schemaVersion","activePet"].includes(r));if(n)throw Error(`Pi Pet config has unknown field: ${n}.`);if(t.schemaVersion!==1)throw Error("Unsupported Pi Pet config schemaVersion.");return{schemaVersion:1,activePet:d(t.activePet,"active pet")}}async function C(e=g()){try{let t=m(e,"config.json"),n=await ge(t);if(!n.isFile()||n.size>le)throw Error(`Pi Pet config must be a bounded regular file: ${t}`);return ye(JSON.parse(await pe(t,"utf8")))}catch(t){if(t.code==="ENOENT")return;throw t}}async function R(e,t,n=g()){let r=d(t,"pet id"),o=m(e,"pets",r),a=m(n,"pets",r),s=!0;try{let c=fe(a);if(!c.isDirectory()||c.isSymbolicLink())throw Error(`User pet path is not a directory: ${a}`)}catch(c){if(c.code!=="ENOENT")throw c;s=!1}if(s)return await S(m(n,"pets"),r),a;await de(he(a),{recursive:!0,mode:448});let f=`${a}.${process.pid}.${ce()}.tmp`;try{return await ue(o,f,{recursive:!0,errorOnExist:!0,force:!1}),await we(f,a),a}catch(c){throw await me(f,{recursive:!0,force:!0}),c}}async function I(e=g()){return(await C(e))?.activePet||"clawa"}var Se=be(new URL("../../",import.meta.url)),j=process.argv[2]||await I(),$e=await R(Se,j);process.stdout.write(`Pet: ${$e}
2
+ Runs: ${Ee(g(),"runs",j)}
3
3
  `);
@@ -1,4 +1,4 @@
1
- import{fileURLToPath as Re}from"node:url";import{randomUUID as L}from"node:crypto";import{cp as F,mkdir as V,rename as k,rm as x,writeFile as ve}from"node:fs/promises";import{dirname as Ae,join as m}from"node:path";import{readFile as ee,realpath as P,stat as O}from"node:fs/promises";import{join as y,resolve as te,sep as _}from"node:path";import{readFile as M}from"node:fs/promises";function G(e,t,n){if(n<10)return;return{format:"webp",width:1+e.readUIntLE(t+4,3),height:1+e.readUIntLE(t+7,3)}}function W(e,t,n){if(n<10||e[t+3]!==157||e[t+4]!==1||e[t+5]!==42)return;return{format:"webp",width:e.readUInt16LE(t+6)&16383,height:e.readUInt16LE(t+8)&16383}}function H(e,t,n){if(n<5||e[t]!==47)return;let r=e[t+1]??0,o=e[t+2]??0,a=e[t+3]??0,i=e[t+4]??0;return{format:"webp",width:1+((r|o<<8)&16383),height:1+((o>>6|a<<2|i<<10)&16383)}}function X(e,t,n,r){if(t==="VP8X")return G(e,n,r);if(t==="VP8 ")return W(e,n,r);if(t==="VP8L")return H(e,n,r);return}function Y(e){if(e.length<30||e.toString("ascii",0,4)!=="RIFF"||e.toString("ascii",8,12)!=="WEBP")return;let t=12;while(t+8<=e.length){let n=e.toString("ascii",t,t+4),r=e.readUInt32LE(t+4),o=t+8;if(o+r>e.length)throw Error("Truncated WebP chunk.");let a=X(e,n,o,r);if(a)return a;t=o+r+r%2}throw Error("WebP dimensions were not found.")}function q(e){if(e.length>=24&&e.subarray(0,8).toString("hex")==="89504e470d0a1a0a")return{format:"png",width:e.readUInt32BE(16),height:e.readUInt32BE(20)};let n=Y(e);if(n)return n;throw Error("Asset must be a PNG or WebP image.")}async function A(e){return q(await M(e))}var K=/^[a-z0-9][a-z0-9._-]{0,63}$/,Z=new Set(["__proto__","constructor"]),d=Object.freeze({actionName:64,noteText:280,manifestBytes:131072,catalogBytes:262144,assetBytes:16777216,frameCount:64,decodedPixels:16000000});class s extends Error{name="ContractError"}function Q(e,t,n){if(typeof e!=="string")throw new s(`${t} must be a string.`);let r=e.trim();if(!r)throw new s(`${t} cannot be empty.`);if(r.length>n)throw new s(`${t} exceeds ${n} characters.`);return r}function u(e,t="action"){let n=Q(e,t,d.actionName);if(n==="list")throw new s(`${t} cannot use the reserved pet_show discovery name list.`);if(Z.has(n))throw new s(`${t} cannot use a reserved object property name.`);if(!K.test(n))throw new s(`${t} must use lowercase letters, numbers, dots, underscores, or hyphens.`);return n}function S(e){if(!e||e.includes("\x00")||e.includes("\\")||e.startsWith("/"))return!1;return e.split("/").every((n)=>n!==""&&n!=="."&&n!=="..")}var p={width:192,height:208},ne=[["idle",0,[280,110,110,140,140,320]],["running-right",1,[120,120,120,120,120,120,120,220]],["running-left",2,[120,120,120,120,120,120,120,220]],["waving",3,[140,140,140,280]],["jumping",4,[140,140,140,140,280]],["failed",5,[140,140,140,140,140,140,140,240]],["waiting",6,[150,150,150,150,150,260]],["running",7,[120,120,120,120,120,220]],["review",8,[150,150,150,150,150,280]]],re=Object.freeze({active:"running",error:"failed",hello:"waving",settled:"review",success:"jumping",thinking:"running",working:"running"});function w(e,t){if(!e||typeof e!=="object"||Array.isArray(e))throw new s(`${t} must be an object.`);return e}function g(e,t,n,r){if(!Number.isInteger(e)||e<n||e>r)throw new s(`${t} must be an integer from ${n} to ${r}.`);return e}async function C(e,t){let n=await O(e);if(!n.isFile()||n.size>t)throw new s(`${e} is not a bounded regular file.`);return JSON.parse(await ee(e,"utf8"))}function oe(e,t){return t.map((n,r)=>({x:r*p.width,y:e*p.height,width:p.width,height:p.height,durationMs:n}))}function ie(e){let t=w(e,"pet.json"),n=new Set(["id","displayName","description","spriteVersionNumber","spritesheetPath"]);for(let i of Object.keys(t))if(!n.has(i))throw new s(`pet.json has unknown field: ${i}.`);let r=u(t.id,"pet id");if(typeof t.displayName!=="string"||!t.displayName.trim()||t.displayName.length>80)throw new s("pet.json displayName must contain 1-80 characters.");if(typeof t.description!=="string"||!t.description.trim()||t.description.length>280)throw new s("pet.json description must contain 1-280 characters.");if(t.spriteVersionNumber!==2)throw new s("Only Codex spriteVersionNumber 2 is supported.");if(typeof t.spritesheetPath!=="string"||!S(t.spritesheetPath))throw new s("pet.json spritesheetPath must be a safe relative path.");let o={},a={};for(let[i,c,f]of ne)o[i]={name:i,asset:t.spritesheetPath,frames:oe(c,f),loop:!0};for(let i=0;i<16;i+=1){let c=i*22.5,f=`look-${String(c).padStart(3,"0").replace(".5","_5")}`,b=i<8?9:10,l=i%8;a[f]={name:f,asset:t.spritesheetPath,frames:[{x:l*p.width,y:b*p.height,...p,durationMs:1000}],loop:!0}}return{schemaVersion:1,id:r,displayName:t.displayName.trim(),description:t.description.trim(),defaultAction:"idle",canvas:{...p},actions:o,aliases:{...re},directions:a}}function se(e,t){let n=w(t,`action ${e}`),r=new Set(["asset","frames","loop","next"]);for(let i of Object.keys(n))if(!r.has(i))throw new s(`action ${e} has unknown field: ${i}.`);if(typeof n.asset!=="string"||!S(n.asset))throw new s(`action ${e} asset must be a safe relative path.`);if(!Array.isArray(n.frames)||n.frames.length===0||n.frames.length>d.frameCount)throw new s(`action ${e} must have 1-${d.frameCount} frames.`);let o=n.frames.map((i,c)=>{let f=w(i,`action ${e} frame ${c}`),b=new Set(["x","y","width","height","durationMs"]);for(let v of Object.keys(f))if(!b.has(v))throw new s(`action ${e} frame ${c} has unknown field: ${v}.`);let l=g(f.width,"frame width",1,4096),N=g(f.height,"frame height",1,4096);if(l*N>d.decodedPixels)throw new s(`action ${e} frame ${c} is too large.`);return{x:g(f.x,"frame x",0,32768),y:g(f.y,"frame y",0,32768),width:l,height:N,durationMs:g(f.durationMs,"frame durationMs",16,60000)}});if(typeof n.loop!=="boolean")throw new s(`action ${e} loop must be a boolean.`);let a={name:e,asset:n.asset,frames:o,loop:n.loop};if(n.next!==void 0)a.next=u(n.next,`action ${e} next`);return a}async function ae(e){let t=y(e,"pet.pi.json");try{return w(await C(t,d.manifestBytes),"pet.pi.json")}catch(n){if(n.code==="ENOENT")return;throw n}}function ce(e){let t=new Set(["schemaVersion","defaultAction","actions","aliases"]);for(let n of Object.keys(e))if(!t.has(n))throw new s(`pet.pi.json has unknown field: ${n}.`);if(e.schemaVersion!==1)throw new s("pet.pi.json schemaVersion must be 1.")}function fe(e,t){if(e===void 0)return;for(let[n,r]of Object.entries(w(e,"pet.pi.json actions"))){let o=u(n,"action name");t.actions[o]=se(o,r),delete t.aliases[o]}}function de(e,t){if(e===void 0)return;for(let[n,r]of Object.entries(w(e,"pet.pi.json aliases"))){let o=u(n,"alias name");if(t.actions[o])throw new s(`Alias ${o} conflicts with an action of the same name.`);t.aliases[o]=u(r,`alias ${o} target`)}}function ue(e){if(!e.actions[e.defaultAction])throw new s(`Unknown defaultAction: ${e.defaultAction}.`);for(let t of Object.values(e.actions))if(t.next!==void 0&&!e.actions[t.next])throw new s(`Action ${t.name} has unknown next action: ${t.next}.`);for(let[t,n]of Object.entries(e.aliases))if(!e.actions[n])throw new s(`Alias ${t} has unknown target action: ${n}.`)}function pe(e){if(Buffer.byteLength(JSON.stringify(e))>d.catalogBytes)throw new s(`Generated pet catalog exceeds ${d.catalogBytes} bytes.`)}async function we(e,t){let n=await ae(e);if(!n)return;if(ce(n),fe(n.actions,t),de(n.aliases,t),n.defaultAction!==void 0)t.defaultAction=u(n.defaultAction,"defaultAction");ue(t)}async function me(e,t){let n=await P(y(e,t));if(n!==e&&!n.startsWith(`${e}${_}`))throw new s(`Asset escapes pet directory: ${t}.`);let r=await O(n);if(!r.isFile()||r.size===0||r.size>d.assetBytes)throw new s(`Asset is not a bounded regular file: ${t}.`);let o=await A(n);if(o.width*o.height>d.decodedPixels)throw new s(`Asset has too many decoded pixels: ${t}.`);return o}function ge(e,t,n,r){for(let o of t)for(let a of o.frames)if(a.x+a.width>n||a.y+a.height>r)throw new s(`Action ${o.name} has a frame outside ${e}.`)}async function he(e,t,n){let r=await P(e),o=[...Object.values(t.actions),...Object.values(t.directions)],a=new Set(o.map((i)=>i.asset));for(let i of a){let c=await me(r,i);if(i===n&&(c.width!==1536||c.height!==2288))throw new s(`Codex v2 spritesheet must be 1536x2288; got ${c.width}x${c.height}.`);ge(i,o.filter((f)=>f.asset===i),c.width,c.height)}}async function $(e,t){let n=u(t,"active pet"),r=await P(te(e)),o=await P(y(r,n));if(o!==r&&!o.startsWith(`${r}${_}`))throw new s("Active pet escapes the pets directory.");let a=await C(y(o,"pet.json"),d.manifestBytes),i=ie(a);if(i.id!==n)throw new s(`Active pet directory ${n} does not match manifest id ${i.id}.`);await we(o,i),pe(i);let c=w(a,"pet.json").spritesheetPath;return await he(o,i,c),{directory:o,catalog:i}}import{randomUUID as le}from"node:crypto";import{cp as Xe,mkdir as Pe,readFile as ye,rename as Ee,rm as R,stat as be,writeFile as Se}from"node:fs/promises";import{homedir as I}from"node:os";import{dirname as $e,join as E}from"node:path";var ke=16384,xe=["app.js","index.html","manifest.webmanifest","pet-icon.svg","styles.css"];function Ne(e=process.env,t=I()){return e.PI_CODING_AGENT_DIR?.trim()||E(t,".pi","agent")}function h(e=process.env,t=I()){return E(Ne(e,t),"pi-pet")}function j(e){if(!e||typeof e!=="object"||Array.isArray(e))throw Error("Pi Pet config must be an object.");let t=e,n=Object.keys(t).find((r)=>!["schemaVersion","activePet"].includes(r));if(n)throw Error(`Pi Pet config has unknown field: ${n}.`);if(t.schemaVersion!==1)throw Error("Unsupported Pi Pet config schemaVersion.");return{schemaVersion:1,activePet:u(t.activePet,"active pet")}}async function T(e=h()){try{let t=E(e,"config.json"),n=await be(t);if(!n.isFile()||n.size>ke)throw Error(`Pi Pet config must be a bounded regular file: ${t}`);return j(JSON.parse(await ye(t,"utf8")))}catch(t){if(t.code==="ENOENT")return;throw t}}async function D(e,t=h()){let n=j(e),r=E(t,"config.json");await Pe($e(r),{recursive:!0,mode:448});let o=`${r}.${process.pid}.${le()}.tmp`;try{await R(o,{force:!0}),await Se(o,`${JSON.stringify(n,null,2)}
2
- `,{mode:384,flag:"wx"}),await Ee(o,r)}catch(a){throw await R(o,{force:!0}),a}}var U=xe;function Oe(e){return new Set([...Object.values(e.actions),...Object.values(e.directions)].map((t)=>t.asset))}async function _e(e,t){for(let n of Oe(t.catalog)){let r=m(e,n);await V(Ae(r),{recursive:!0}),await F(m(t.directory,n),r)}await ve(m(e,"catalog.json"),`${JSON.stringify(t.catalog)}
3
- `)}async function Ce(e,t){let n=`${t}.${process.pid}.${L()}.old`,r=!1;try{await k(t,n),r=!0}catch(o){if(o.code!=="ENOENT")throw o}try{await k(e,t)}catch(o){if(r)try{await k(n,t)}catch(a){if(!["EEXIST","ENOTEMPTY"].includes(a.code||""))throw AggregateError([o,a],`Could not publish or restore pet root: ${t}`);await x(n,{recursive:!0,force:!0})}throw o}if(r)await x(n,{recursive:!0,force:!0})}async function z(e=h()){return(await T(e))?.activePet||"clawa"}async function J(e,t,n=h()){let r=await $(m(n,"pets"),t),o=m(n,"web",r.catalog.id),a=`${o}.${process.pid}.${L()}.tmp`;await V(a,{recursive:!0,mode:448});try{for(let i of U)await F(m(e,"dist","web",i),m(a,i));await _e(a,r),await Ce(a,o)}catch(i){throw await x(a,{recursive:!0,force:!0}),i}return await D({schemaVersion:1,activePet:r.catalog.id},n),r}var Ie=Re(new URL("../../",import.meta.url)),je=process.argv[2]||await z(),B=await J(Ie,je);process.stdout.write(`${B.catalog.displayName}: ${Object.keys(B.catalog.actions).length} actions rebuilt
1
+ import{fileURLToPath as Ie}from"node:url";import{randomUUID as F}from"node:crypto";import{cp as V,mkdir as z,rename as x,rm as N,writeFile as Ae}from"node:fs/promises";import{dirname as Oe,join as m}from"node:path";import{readFile as te,realpath as P,stat as _}from"node:fs/promises";import{join as y,resolve as ne,sep as C}from"node:path";import{readFile as G}from"node:fs/promises";function W(e,t,n){if(n<10)return;return{format:"webp",width:1+e.readUIntLE(t+4,3),height:1+e.readUIntLE(t+7,3)}}function H(e,t,n){if(n<10||e[t+3]!==157||e[t+4]!==1||e[t+5]!==42)return;return{format:"webp",width:e.readUInt16LE(t+6)&16383,height:e.readUInt16LE(t+8)&16383}}function X(e,t,n){if(n<5||e[t]!==47)return;let r=e[t+1]??0,o=e[t+2]??0,a=e[t+3]??0,i=e[t+4]??0;return{format:"webp",width:1+((r|o<<8)&16383),height:1+((o>>6|a<<2|i<<10)&16383)}}function Y(e,t,n,r){if(t==="VP8X")return W(e,n,r);if(t==="VP8 ")return H(e,n,r);if(t==="VP8L")return X(e,n,r);return}function q(e){if(e.length<30||e.toString("ascii",0,4)!=="RIFF"||e.toString("ascii",8,12)!=="WEBP")return;let t=12;while(t+8<=e.length){let n=e.toString("ascii",t,t+4),r=e.readUInt32LE(t+4),o=t+8;if(o+r>e.length)throw Error("Truncated WebP chunk.");let a=Y(e,n,o,r);if(a)return a;t=o+r+r%2}throw Error("WebP dimensions were not found.")}function K(e){if(e.length>=24&&e.subarray(0,8).toString("hex")==="89504e470d0a1a0a")return{format:"png",width:e.readUInt32BE(16),height:e.readUInt32BE(20)};let n=q(e);if(n)return n;throw Error("Asset must be a PNG or WebP image.")}async function O(e){return K(await G(e))}var Z=/^[a-z0-9][a-z0-9._-]{0,63}$/,Q=new Set(["__proto__","constructor"]),d=Object.freeze({actionName:64,noteText:280,manifestBytes:131072,catalogBytes:262144,assetBytes:16777216,frameCount:64,decodedPixels:16000000});class s extends Error{name="ContractError"}function ee(e,t,n){if(typeof e!=="string")throw new s(`${t} must be a string.`);let r=e.trim();if(!r)throw new s(`${t} cannot be empty.`);if(r.length>n)throw new s(`${t} exceeds ${n} characters.`);return r}function u(e,t="action"){let n=ee(e,t,d.actionName);if(n==="list")throw new s(`${t} cannot use the reserved pet_show discovery name list.`);if(Q.has(n))throw new s(`${t} cannot use a reserved object property name.`);if(!Z.test(n))throw new s(`${t} must use lowercase letters, numbers, dots, underscores, or hyphens.`);return n}function S(e){if(!e||e.includes("\x00")||e.includes("\\")||e.startsWith("/"))return!1;return e.split("/").every((n)=>n!==""&&n!=="."&&n!=="..")}var p={width:192,height:208},re=[["idle",0,[280,110,110,140,140,320]],["running-right",1,[120,120,120,120,120,120,120,220]],["running-left",2,[120,120,120,120,120,120,120,220]],["waving",3,[140,140,140,280]],["jumping",4,[140,140,140,140,280]],["failed",5,[140,140,140,140,140,140,140,240]],["waiting",6,[150,150,150,150,150,260]],["running",7,[120,120,120,120,120,220]],["review",8,[150,150,150,150,150,280]]],oe=Object.freeze({active:"running",error:"failed",hello:"waving",settled:"review",success:"jumping",thinking:"running",working:"running"});function w(e,t){if(!e||typeof e!=="object"||Array.isArray(e))throw new s(`${t} must be an object.`);return e}function g(e,t,n,r){if(!Number.isInteger(e)||e<n||e>r)throw new s(`${t} must be an integer from ${n} to ${r}.`);return e}async function R(e,t){let n=await _(e);if(!n.isFile()||n.size>t)throw new s(`${e} is not a bounded regular file.`);return JSON.parse(await te(e,"utf8"))}function ie(e,t){return t.map((n,r)=>({x:r*p.width,y:e*p.height,width:p.width,height:p.height,durationMs:n}))}function se(e){let t=w(e,"pet.json"),n=new Set(["id","displayName","description","spriteVersionNumber","spritesheetPath"]);for(let i of Object.keys(t))if(!n.has(i))throw new s(`pet.json has unknown field: ${i}.`);let r=u(t.id,"pet id");if(typeof t.displayName!=="string"||!t.displayName.trim()||t.displayName.length>80)throw new s("pet.json displayName must contain 1-80 characters.");if(typeof t.description!=="string"||!t.description.trim()||t.description.length>280)throw new s("pet.json description must contain 1-280 characters.");if(t.spriteVersionNumber!==2)throw new s("Only Codex spriteVersionNumber 2 is supported.");if(typeof t.spritesheetPath!=="string"||!S(t.spritesheetPath))throw new s("pet.json spritesheetPath must be a safe relative path.");let o={},a={};for(let[i,c,f]of re)o[i]={name:i,asset:t.spritesheetPath,frames:ie(c,f),loop:!0};for(let i=0;i<16;i+=1){let c=i*22.5,f=`look-${String(c).padStart(3,"0").replace(".5","_5")}`,b=i<8?9:10,l=i%8;a[f]={name:f,asset:t.spritesheetPath,frames:[{x:l*p.width,y:b*p.height,...p,durationMs:1000}],loop:!0}}return{schemaVersion:1,id:r,displayName:t.displayName.trim(),description:t.description.trim(),defaultAction:"idle",canvas:{...p},actions:o,aliases:{...oe},directions:a}}function ae(e,t){let n=w(t,`action ${e}`),r=new Set(["asset","frames","loop","next"]);for(let i of Object.keys(n))if(!r.has(i))throw new s(`action ${e} has unknown field: ${i}.`);if(typeof n.asset!=="string"||!S(n.asset))throw new s(`action ${e} asset must be a safe relative path.`);if(!Array.isArray(n.frames)||n.frames.length===0||n.frames.length>d.frameCount)throw new s(`action ${e} must have 1-${d.frameCount} frames.`);let o=n.frames.map((i,c)=>{let f=w(i,`action ${e} frame ${c}`),b=new Set(["x","y","width","height","durationMs"]);for(let A of Object.keys(f))if(!b.has(A))throw new s(`action ${e} frame ${c} has unknown field: ${A}.`);let l=g(f.width,"frame width",1,4096),v=g(f.height,"frame height",1,4096);if(l*v>d.decodedPixels)throw new s(`action ${e} frame ${c} is too large.`);return{x:g(f.x,"frame x",0,32768),y:g(f.y,"frame y",0,32768),width:l,height:v,durationMs:g(f.durationMs,"frame durationMs",16,60000)}});if(typeof n.loop!=="boolean")throw new s(`action ${e} loop must be a boolean.`);let a={name:e,asset:n.asset,frames:o,loop:n.loop};if(n.next!==void 0)a.next=u(n.next,`action ${e} next`);return a}async function ce(e){let t=y(e,"pet.pi.json");try{return w(await R(t,d.manifestBytes),"pet.pi.json")}catch(n){if(n.code==="ENOENT")return;throw n}}function fe(e){let t=new Set(["schemaVersion","defaultAction","actions","aliases"]);for(let n of Object.keys(e))if(!t.has(n))throw new s(`pet.pi.json has unknown field: ${n}.`);if(e.schemaVersion!==1)throw new s("pet.pi.json schemaVersion must be 1.")}function de(e,t){if(e===void 0)return;for(let[n,r]of Object.entries(w(e,"pet.pi.json actions"))){let o=u(n,"action name");t.actions[o]=ae(o,r),delete t.aliases[o]}}function ue(e,t){if(e===void 0)return;for(let[n,r]of Object.entries(w(e,"pet.pi.json aliases"))){let o=u(n,"alias name");if(t.actions[o])throw new s(`Alias ${o} conflicts with an action of the same name.`);t.aliases[o]=u(r,`alias ${o} target`)}}function pe(e){if(!e.actions[e.defaultAction])throw new s(`Unknown defaultAction: ${e.defaultAction}.`);for(let t of Object.values(e.actions))if(t.next!==void 0&&!e.actions[t.next])throw new s(`Action ${t.name} has unknown next action: ${t.next}.`);for(let[t,n]of Object.entries(e.aliases))if(!e.actions[n])throw new s(`Alias ${t} has unknown target action: ${n}.`)}function we(e){if(Buffer.byteLength(JSON.stringify(e))>d.catalogBytes)throw new s(`Generated pet catalog exceeds ${d.catalogBytes} bytes.`)}async function me(e,t){let n=await ce(e);if(!n)return;if(fe(n),de(n.actions,t),ue(n.aliases,t),n.defaultAction!==void 0)t.defaultAction=u(n.defaultAction,"defaultAction");pe(t)}async function ge(e,t){let n=await P(y(e,t));if(n!==e&&!n.startsWith(`${e}${C}`))throw new s(`Asset escapes pet directory: ${t}.`);let r=await _(n);if(!r.isFile()||r.size===0||r.size>d.assetBytes)throw new s(`Asset is not a bounded regular file: ${t}.`);let o=await O(n);if(o.width*o.height>d.decodedPixels)throw new s(`Asset has too many decoded pixels: ${t}.`);return o}function he(e,t,n,r){for(let o of t)for(let a of o.frames)if(a.x+a.width>n||a.y+a.height>r)throw new s(`Action ${o.name} has a frame outside ${e}.`)}async function le(e,t,n){let r=await P(e),o=[...Object.values(t.actions),...Object.values(t.directions)],a=new Set(o.map((i)=>i.asset));for(let i of a){let c=await ge(r,i);if(i===n&&(c.width!==1536||c.height!==2288))throw new s(`Codex v2 spritesheet must be 1536x2288; got ${c.width}x${c.height}.`);he(i,o.filter((f)=>f.asset===i),c.width,c.height)}}async function k(e,t){let n=u(t,"active pet"),r=await P(ne(e)),o=await P(y(r,n));if(o!==r&&!o.startsWith(`${r}${C}`))throw new s("Active pet escapes the pets directory.");let a=await R(y(o,"pet.json"),d.manifestBytes),i=se(a);if(i.id!==n)throw new s(`Active pet directory ${n} does not match manifest id ${i.id}.`);await me(o,i),we(i);let c=w(a,"pet.json").spritesheetPath;return await le(o,i,c),{directory:o,catalog:i}}import{randomUUID as Pe}from"node:crypto";import{cp as Ye,mkdir as ye,readFile as Ee,rename as be,rm as I,stat as Se,writeFile as $e}from"node:fs/promises";import{homedir as j}from"node:os";import{dirname as ke,join as E}from"node:path";var xe=16384,Ne=["app.js","index.html","manifest.webmanifest","pet-icon.svg","styles.css"];function ve(e=process.env,t=j()){return e.PI_CODING_AGENT_DIR?.trim()||E(t,".pi","agent")}function h(e=process.env,t=j()){return E(ve(e,t),"pi-pet")}function T(e){if(!e||typeof e!=="object"||Array.isArray(e))throw Error("Pi Pet config must be an object.");let t=e,n=Object.keys(t).find((r)=>!["schemaVersion","activePet"].includes(r));if(n)throw Error(`Pi Pet config has unknown field: ${n}.`);if(t.schemaVersion!==1)throw Error("Unsupported Pi Pet config schemaVersion.");return{schemaVersion:1,activePet:u(t.activePet,"active pet")}}async function D(e=h()){try{let t=E(e,"config.json"),n=await Se(t);if(!n.isFile()||n.size>xe)throw Error(`Pi Pet config must be a bounded regular file: ${t}`);return T(JSON.parse(await Ee(t,"utf8")))}catch(t){if(t.code==="ENOENT")return;throw t}}async function U(e,t=h()){let n=T(e),r=E(t,"config.json");await ye(ke(r),{recursive:!0,mode:448});let o=`${r}.${process.pid}.${Pe()}.tmp`;try{await I(o,{force:!0}),await $e(o,`${JSON.stringify(n,null,2)}
2
+ `,{mode:384,flag:"wx"}),await be(o,r)}catch(a){throw await I(o,{force:!0}),a}}var L=Ne;function _e(e){return new Set([...Object.values(e.actions),...Object.values(e.directions)].map((t)=>t.asset))}async function Ce(e,t){for(let n of _e(t.catalog)){let r=m(e,n);await z(Oe(r),{recursive:!0}),await V(m(t.directory,n),r)}await Ae(m(e,"catalog.json"),`${JSON.stringify(t.catalog)}
3
+ `)}async function Re(e,t){let n=`${t}.${process.pid}.${F()}.old`,r=!1;try{await x(t,n),r=!0}catch(o){if(o.code!=="ENOENT")throw o}try{await x(e,t)}catch(o){if(r)try{await x(n,t)}catch(a){if(!["EEXIST","ENOTEMPTY"].includes(a.code||""))throw AggregateError([o,a],`Could not publish or restore pet root: ${t}`);await N(n,{recursive:!0,force:!0})}throw o}if(r)await N(n,{recursive:!0,force:!0})}async function J(e=h()){return(await D(e))?.activePet||"clawa"}async function B(e,t,n=h()){let r=await k(m(n,"pets"),t),o=m(n,"web",r.catalog.id),a=`${o}.${process.pid}.${F()}.tmp`;await z(a,{recursive:!0,mode:448});try{for(let i of L)await V(m(e,"dist","web",i),m(a,i));await Ce(a,r),await Re(a,o)}catch(i){throw await N(a,{recursive:!0,force:!0}),i}return await U({schemaVersion:1,activePet:r.catalog.id},n),r}var je=Ie(new URL("../../",import.meta.url)),Te=process.argv[2]||await J(),M=await B(je,Te);process.stdout.write(`${M.catalog.displayName}: ${Object.keys(M.catalog.actions).length} actions rebuilt
4
4
  `);
@@ -1,2 +1,2 @@
1
- import{basename as pe,dirname as we,join as me,resolve as ge}from"node:path";import{readFile as M,realpath as l,stat as N}from"node:fs/promises";import{join as P,resolve as G,sep as v}from"node:path";import{readFile as R}from"node:fs/promises";function T(e,t,n){if(n<10)return;return{format:"webp",width:1+e.readUIntLE(t+4,3),height:1+e.readUIntLE(t+7,3)}}function D(e,t,n){if(n<10||e[t+3]!==157||e[t+4]!==1||e[t+5]!==42)return;return{format:"webp",width:e.readUInt16LE(t+6)&16383,height:e.readUInt16LE(t+8)&16383}}function L(e,t,n){if(n<5||e[t]!==47)return;let r=e[t+1]??0,o=e[t+2]??0,a=e[t+3]??0,s=e[t+4]??0;return{format:"webp",width:1+((r|o<<8)&16383),height:1+((o>>6|a<<2|s<<10)&16383)}}function U(e,t,n,r){if(t==="VP8X")return T(e,n,r);if(t==="VP8 ")return D(e,n,r);if(t==="VP8L")return L(e,n,r);return}function F(e){if(e.length<30||e.toString("ascii",0,4)!=="RIFF"||e.toString("ascii",8,12)!=="WEBP")return;let t=12;while(t+8<=e.length){let n=e.toString("ascii",t,t+4),r=e.readUInt32LE(t+4),o=t+8;if(o+r>e.length)throw Error("Truncated WebP chunk.");let a=U(e,n,o,r);if(a)return a;t=o+r+r%2}throw Error("WebP dimensions were not found.")}function V(e){if(e.length>=24&&e.subarray(0,8).toString("hex")==="89504e470d0a1a0a")return{format:"png",width:e.readUInt32BE(16),height:e.readUInt32BE(20)};let n=F(e);if(n)return n;throw Error("Asset must be a PNG or WebP image.")}async function k(e){return V(await R(e))}var z=/^[a-z0-9][a-z0-9._-]{0,63}$/,J=new Set(["__proto__","constructor"]),d=Object.freeze({actionName:64,noteText:280,manifestBytes:131072,catalogBytes:262144,assetBytes:16777216,frameCount:64,decodedPixels:16000000});class i extends Error{name="ContractError"}function B(e,t,n){if(typeof e!=="string")throw new i(`${t} must be a string.`);let r=e.trim();if(!r)throw new i(`${t} cannot be empty.`);if(r.length>n)throw new i(`${t} exceeds ${n} characters.`);return r}function u(e,t="action"){let n=B(e,t,d.actionName);if(n==="list")throw new i(`${t} cannot use the reserved pet_show discovery name list.`);if(J.has(n))throw new i(`${t} cannot use a reserved object property name.`);if(!z.test(n))throw new i(`${t} must use lowercase letters, numbers, dots, underscores, or hyphens.`);return n}function b(e){if(!e||e.includes("\x00")||e.includes("\\")||e.startsWith("/"))return!1;return e.split("/").every((n)=>n!==""&&n!=="."&&n!=="..")}var p={width:192,height:208},W=[["idle",0,[280,110,110,140,140,320]],["running-right",1,[120,120,120,120,120,120,120,220]],["running-left",2,[120,120,120,120,120,120,120,220]],["waving",3,[140,140,140,280]],["jumping",4,[140,140,140,140,280]],["failed",5,[140,140,140,140,140,140,140,240]],["waiting",6,[150,150,150,150,150,260]],["running",7,[120,120,120,120,120,220]],["review",8,[150,150,150,150,150,280]]],H=Object.freeze({active:"running",error:"failed",hello:"waving",settled:"review",success:"jumping",thinking:"running",working:"running"});function w(e,t){if(!e||typeof e!=="object"||Array.isArray(e))throw new i(`${t} must be an object.`);return e}function m(e,t,n,r){if(!Number.isInteger(e)||e<n||e>r)throw new i(`${t} must be an integer from ${n} to ${r}.`);return e}async function A(e,t){let n=await N(e);if(!n.isFile()||n.size>t)throw new i(`${e} is not a bounded regular file.`);return JSON.parse(await M(e,"utf8"))}function X(e,t){return t.map((n,r)=>({x:r*p.width,y:e*p.height,width:p.width,height:p.height,durationMs:n}))}function Y(e){let t=w(e,"pet.json"),n=new Set(["id","displayName","description","spriteVersionNumber","spritesheetPath"]);for(let s of Object.keys(t))if(!n.has(s))throw new i(`pet.json has unknown field: ${s}.`);let r=u(t.id,"pet id");if(typeof t.displayName!=="string"||!t.displayName.trim()||t.displayName.length>80)throw new i("pet.json displayName must contain 1-80 characters.");if(typeof t.description!=="string"||!t.description.trim()||t.description.length>280)throw new i("pet.json description must contain 1-280 characters.");if(t.spriteVersionNumber!==2)throw new i("Only Codex spriteVersionNumber 2 is supported.");if(typeof t.spritesheetPath!=="string"||!b(t.spritesheetPath))throw new i("pet.json spritesheetPath must be a safe relative path.");let o={},a={};for(let[s,c,f]of W)o[s]={name:s,asset:t.spritesheetPath,frames:X(c,f),loop:!0};for(let s=0;s<16;s+=1){let c=s*22.5,f=`look-${String(c).padStart(3,"0").replace(".5","_5")}`,E=s<8?9:10,h=s%8;a[f]={name:f,asset:t.spritesheetPath,frames:[{x:h*p.width,y:E*p.height,...p,durationMs:1000}],loop:!0}}return{schemaVersion:1,id:r,displayName:t.displayName.trim(),description:t.description.trim(),defaultAction:"idle",canvas:{...p},actions:o,aliases:{...H},directions:a}}function q(e,t){let n=w(t,`action ${e}`),r=new Set(["asset","frames","loop","next"]);for(let s of Object.keys(n))if(!r.has(s))throw new i(`action ${e} has unknown field: ${s}.`);if(typeof n.asset!=="string"||!b(n.asset))throw new i(`action ${e} asset must be a safe relative path.`);if(!Array.isArray(n.frames)||n.frames.length===0||n.frames.length>d.frameCount)throw new i(`action ${e} must have 1-${d.frameCount} frames.`);let o=n.frames.map((s,c)=>{let f=w(s,`action ${e} frame ${c}`),E=new Set(["x","y","width","height","durationMs"]);for(let x of Object.keys(f))if(!E.has(x))throw new i(`action ${e} frame ${c} has unknown field: ${x}.`);let h=m(f.width,"frame width",1,4096),$=m(f.height,"frame height",1,4096);if(h*$>d.decodedPixels)throw new i(`action ${e} frame ${c} is too large.`);return{x:m(f.x,"frame x",0,32768),y:m(f.y,"frame y",0,32768),width:h,height:$,durationMs:m(f.durationMs,"frame durationMs",16,60000)}});if(typeof n.loop!=="boolean")throw new i(`action ${e} loop must be a boolean.`);let a={name:e,asset:n.asset,frames:o,loop:n.loop};if(n.next!==void 0)a.next=u(n.next,`action ${e} next`);return a}async function K(e){let t=P(e,"pet.pi.json");try{return w(await A(t,d.manifestBytes),"pet.pi.json")}catch(n){if(n.code==="ENOENT")return;throw n}}function Z(e){let t=new Set(["schemaVersion","defaultAction","actions","aliases"]);for(let n of Object.keys(e))if(!t.has(n))throw new i(`pet.pi.json has unknown field: ${n}.`);if(e.schemaVersion!==1)throw new i("pet.pi.json schemaVersion must be 1.")}function Q(e,t){if(e===void 0)return;for(let[n,r]of Object.entries(w(e,"pet.pi.json actions"))){let o=u(n,"action name");t.actions[o]=q(o,r),delete t.aliases[o]}}function ee(e,t){if(e===void 0)return;for(let[n,r]of Object.entries(w(e,"pet.pi.json aliases"))){let o=u(n,"alias name");if(t.actions[o])throw new i(`Alias ${o} conflicts with an action of the same name.`);t.aliases[o]=u(r,`alias ${o} target`)}}function te(e){if(!e.actions[e.defaultAction])throw new i(`Unknown defaultAction: ${e.defaultAction}.`);for(let t of Object.values(e.actions))if(t.next!==void 0&&!e.actions[t.next])throw new i(`Action ${t.name} has unknown next action: ${t.next}.`);for(let[t,n]of Object.entries(e.aliases))if(!e.actions[n])throw new i(`Alias ${t} has unknown target action: ${n}.`)}function ne(e){if(Buffer.byteLength(JSON.stringify(e))>d.catalogBytes)throw new i(`Generated pet catalog exceeds ${d.catalogBytes} bytes.`)}async function re(e,t){let n=await K(e);if(!n)return;if(Z(n),Q(n.actions,t),ee(n.aliases,t),n.defaultAction!==void 0)t.defaultAction=u(n.defaultAction,"defaultAction");te(t)}async function oe(e,t){let n=await l(P(e,t));if(n!==e&&!n.startsWith(`${e}${v}`))throw new i(`Asset escapes pet directory: ${t}.`);let r=await N(n);if(!r.isFile()||r.size===0||r.size>d.assetBytes)throw new i(`Asset is not a bounded regular file: ${t}.`);let o=await k(n);if(o.width*o.height>d.decodedPixels)throw new i(`Asset has too many decoded pixels: ${t}.`);return o}function ie(e,t,n,r){for(let o of t)for(let a of o.frames)if(a.x+a.width>n||a.y+a.height>r)throw new i(`Action ${o.name} has a frame outside ${e}.`)}async function se(e,t,n){let r=await l(e),o=[...Object.values(t.actions),...Object.values(t.directions)],a=new Set(o.map((s)=>s.asset));for(let s of a){let c=await oe(r,s);if(s===n&&(c.width!==1536||c.height!==2288))throw new i(`Codex v2 spritesheet must be 1536x2288; got ${c.width}x${c.height}.`);ie(s,o.filter((f)=>f.asset===s),c.width,c.height)}}async function y(e,t){let n=u(t,"active pet"),r=await l(G(e)),o=await l(P(r,n));if(o!==r&&!o.startsWith(`${r}${v}`))throw new i("Active pet escapes the pets directory.");let a=await A(P(o,"pet.json"),d.manifestBytes),s=Y(a);if(s.id!==n)throw new i(`Active pet directory ${n} does not match manifest id ${s.id}.`);await re(o,s),ne(s);let c=w(a,"pet.json").spritesheetPath;return await se(o,s,c),{directory:o,catalog:s}}import{cp as Oe,mkdir as _e,readFile as ae,rename as Ce,rm as je,stat as ce,writeFile as Ie}from"node:fs/promises";import{homedir as O}from"node:os";import{dirname as De,join as S}from"node:path";var fe=16384;function de(e=process.env,t=O()){return e.PI_CODING_AGENT_DIR?.trim()||S(t,".pi","agent")}function g(e=process.env,t=O()){return S(de(e,t),"pi-pet")}function ue(e){if(!e||typeof e!=="object"||Array.isArray(e))throw Error("Pi Pet config must be an object.");let t=e,n=Object.keys(t).find((r)=>!["schemaVersion","activePet"].includes(r));if(n)throw Error(`Pi Pet config has unknown field: ${n}.`);if(t.schemaVersion!==1)throw Error("Unsupported Pi Pet config schemaVersion.");return{schemaVersion:1,activePet:u(t.activePet,"active pet")}}async function _(e=g()){try{let t=S(e,"config.json"),n=await ce(t);if(!n.isFile()||n.size>fe)throw Error(`Pi Pet config must be a bounded regular file: ${t}`);return ue(JSON.parse(await ae(t,"utf8")))}catch(t){if(t.code==="ENOENT")return;throw t}}async function C(e=g()){return(await _(e))?.activePet||"clawa"}var he=await C(),j=ge(process.argv[2]||me(g(),"pets",he)),I=await y(we(j),pe(j));process.stdout.write(`${I.catalog.displayName}: ${Object.keys(I.catalog.actions).length} actions valid
1
+ import{basename as we,dirname as me,join as ge,resolve as he}from"node:path";import{readFile as G,realpath as l,stat as v}from"node:fs/promises";import{join as P,resolve as W,sep as A}from"node:path";import{readFile as T}from"node:fs/promises";function D(e,t,n){if(n<10)return;return{format:"webp",width:1+e.readUIntLE(t+4,3),height:1+e.readUIntLE(t+7,3)}}function L(e,t,n){if(n<10||e[t+3]!==157||e[t+4]!==1||e[t+5]!==42)return;return{format:"webp",width:e.readUInt16LE(t+6)&16383,height:e.readUInt16LE(t+8)&16383}}function U(e,t,n){if(n<5||e[t]!==47)return;let r=e[t+1]??0,o=e[t+2]??0,a=e[t+3]??0,s=e[t+4]??0;return{format:"webp",width:1+((r|o<<8)&16383),height:1+((o>>6|a<<2|s<<10)&16383)}}function F(e,t,n,r){if(t==="VP8X")return D(e,n,r);if(t==="VP8 ")return L(e,n,r);if(t==="VP8L")return U(e,n,r);return}function V(e){if(e.length<30||e.toString("ascii",0,4)!=="RIFF"||e.toString("ascii",8,12)!=="WEBP")return;let t=12;while(t+8<=e.length){let n=e.toString("ascii",t,t+4),r=e.readUInt32LE(t+4),o=t+8;if(o+r>e.length)throw Error("Truncated WebP chunk.");let a=F(e,n,o,r);if(a)return a;t=o+r+r%2}throw Error("WebP dimensions were not found.")}function z(e){if(e.length>=24&&e.subarray(0,8).toString("hex")==="89504e470d0a1a0a")return{format:"png",width:e.readUInt32BE(16),height:e.readUInt32BE(20)};let n=V(e);if(n)return n;throw Error("Asset must be a PNG or WebP image.")}async function N(e){return z(await T(e))}var J=/^[a-z0-9][a-z0-9._-]{0,63}$/,B=new Set(["__proto__","constructor"]),d=Object.freeze({actionName:64,noteText:280,manifestBytes:131072,catalogBytes:262144,assetBytes:16777216,frameCount:64,decodedPixels:16000000});class i extends Error{name="ContractError"}function M(e,t,n){if(typeof e!=="string")throw new i(`${t} must be a string.`);let r=e.trim();if(!r)throw new i(`${t} cannot be empty.`);if(r.length>n)throw new i(`${t} exceeds ${n} characters.`);return r}function u(e,t="action"){let n=M(e,t,d.actionName);if(n==="list")throw new i(`${t} cannot use the reserved pet_show discovery name list.`);if(B.has(n))throw new i(`${t} cannot use a reserved object property name.`);if(!J.test(n))throw new i(`${t} must use lowercase letters, numbers, dots, underscores, or hyphens.`);return n}function b(e){if(!e||e.includes("\x00")||e.includes("\\")||e.startsWith("/"))return!1;return e.split("/").every((n)=>n!==""&&n!=="."&&n!=="..")}var p={width:192,height:208},H=[["idle",0,[280,110,110,140,140,320]],["running-right",1,[120,120,120,120,120,120,120,220]],["running-left",2,[120,120,120,120,120,120,120,220]],["waving",3,[140,140,140,280]],["jumping",4,[140,140,140,140,280]],["failed",5,[140,140,140,140,140,140,140,240]],["waiting",6,[150,150,150,150,150,260]],["running",7,[120,120,120,120,120,220]],["review",8,[150,150,150,150,150,280]]],X=Object.freeze({active:"running",error:"failed",hello:"waving",settled:"review",success:"jumping",thinking:"running",working:"running"});function w(e,t){if(!e||typeof e!=="object"||Array.isArray(e))throw new i(`${t} must be an object.`);return e}function m(e,t,n,r){if(!Number.isInteger(e)||e<n||e>r)throw new i(`${t} must be an integer from ${n} to ${r}.`);return e}async function O(e,t){let n=await v(e);if(!n.isFile()||n.size>t)throw new i(`${e} is not a bounded regular file.`);return JSON.parse(await G(e,"utf8"))}function Y(e,t){return t.map((n,r)=>({x:r*p.width,y:e*p.height,width:p.width,height:p.height,durationMs:n}))}function q(e){let t=w(e,"pet.json"),n=new Set(["id","displayName","description","spriteVersionNumber","spritesheetPath"]);for(let s of Object.keys(t))if(!n.has(s))throw new i(`pet.json has unknown field: ${s}.`);let r=u(t.id,"pet id");if(typeof t.displayName!=="string"||!t.displayName.trim()||t.displayName.length>80)throw new i("pet.json displayName must contain 1-80 characters.");if(typeof t.description!=="string"||!t.description.trim()||t.description.length>280)throw new i("pet.json description must contain 1-280 characters.");if(t.spriteVersionNumber!==2)throw new i("Only Codex spriteVersionNumber 2 is supported.");if(typeof t.spritesheetPath!=="string"||!b(t.spritesheetPath))throw new i("pet.json spritesheetPath must be a safe relative path.");let o={},a={};for(let[s,c,f]of H)o[s]={name:s,asset:t.spritesheetPath,frames:Y(c,f),loop:!0};for(let s=0;s<16;s+=1){let c=s*22.5,f=`look-${String(c).padStart(3,"0").replace(".5","_5")}`,E=s<8?9:10,h=s%8;a[f]={name:f,asset:t.spritesheetPath,frames:[{x:h*p.width,y:E*p.height,...p,durationMs:1000}],loop:!0}}return{schemaVersion:1,id:r,displayName:t.displayName.trim(),description:t.description.trim(),defaultAction:"idle",canvas:{...p},actions:o,aliases:{...X},directions:a}}function K(e,t){let n=w(t,`action ${e}`),r=new Set(["asset","frames","loop","next"]);for(let s of Object.keys(n))if(!r.has(s))throw new i(`action ${e} has unknown field: ${s}.`);if(typeof n.asset!=="string"||!b(n.asset))throw new i(`action ${e} asset must be a safe relative path.`);if(!Array.isArray(n.frames)||n.frames.length===0||n.frames.length>d.frameCount)throw new i(`action ${e} must have 1-${d.frameCount} frames.`);let o=n.frames.map((s,c)=>{let f=w(s,`action ${e} frame ${c}`),E=new Set(["x","y","width","height","durationMs"]);for(let k of Object.keys(f))if(!E.has(k))throw new i(`action ${e} frame ${c} has unknown field: ${k}.`);let h=m(f.width,"frame width",1,4096),x=m(f.height,"frame height",1,4096);if(h*x>d.decodedPixels)throw new i(`action ${e} frame ${c} is too large.`);return{x:m(f.x,"frame x",0,32768),y:m(f.y,"frame y",0,32768),width:h,height:x,durationMs:m(f.durationMs,"frame durationMs",16,60000)}});if(typeof n.loop!=="boolean")throw new i(`action ${e} loop must be a boolean.`);let a={name:e,asset:n.asset,frames:o,loop:n.loop};if(n.next!==void 0)a.next=u(n.next,`action ${e} next`);return a}async function Z(e){let t=P(e,"pet.pi.json");try{return w(await O(t,d.manifestBytes),"pet.pi.json")}catch(n){if(n.code==="ENOENT")return;throw n}}function Q(e){let t=new Set(["schemaVersion","defaultAction","actions","aliases"]);for(let n of Object.keys(e))if(!t.has(n))throw new i(`pet.pi.json has unknown field: ${n}.`);if(e.schemaVersion!==1)throw new i("pet.pi.json schemaVersion must be 1.")}function ee(e,t){if(e===void 0)return;for(let[n,r]of Object.entries(w(e,"pet.pi.json actions"))){let o=u(n,"action name");t.actions[o]=K(o,r),delete t.aliases[o]}}function te(e,t){if(e===void 0)return;for(let[n,r]of Object.entries(w(e,"pet.pi.json aliases"))){let o=u(n,"alias name");if(t.actions[o])throw new i(`Alias ${o} conflicts with an action of the same name.`);t.aliases[o]=u(r,`alias ${o} target`)}}function ne(e){if(!e.actions[e.defaultAction])throw new i(`Unknown defaultAction: ${e.defaultAction}.`);for(let t of Object.values(e.actions))if(t.next!==void 0&&!e.actions[t.next])throw new i(`Action ${t.name} has unknown next action: ${t.next}.`);for(let[t,n]of Object.entries(e.aliases))if(!e.actions[n])throw new i(`Alias ${t} has unknown target action: ${n}.`)}function re(e){if(Buffer.byteLength(JSON.stringify(e))>d.catalogBytes)throw new i(`Generated pet catalog exceeds ${d.catalogBytes} bytes.`)}async function oe(e,t){let n=await Z(e);if(!n)return;if(Q(n),ee(n.actions,t),te(n.aliases,t),n.defaultAction!==void 0)t.defaultAction=u(n.defaultAction,"defaultAction");ne(t)}async function ie(e,t){let n=await l(P(e,t));if(n!==e&&!n.startsWith(`${e}${A}`))throw new i(`Asset escapes pet directory: ${t}.`);let r=await v(n);if(!r.isFile()||r.size===0||r.size>d.assetBytes)throw new i(`Asset is not a bounded regular file: ${t}.`);let o=await N(n);if(o.width*o.height>d.decodedPixels)throw new i(`Asset has too many decoded pixels: ${t}.`);return o}function se(e,t,n,r){for(let o of t)for(let a of o.frames)if(a.x+a.width>n||a.y+a.height>r)throw new i(`Action ${o.name} has a frame outside ${e}.`)}async function ae(e,t,n){let r=await l(e),o=[...Object.values(t.actions),...Object.values(t.directions)],a=new Set(o.map((s)=>s.asset));for(let s of a){let c=await ie(r,s);if(s===n&&(c.width!==1536||c.height!==2288))throw new i(`Codex v2 spritesheet must be 1536x2288; got ${c.width}x${c.height}.`);se(s,o.filter((f)=>f.asset===s),c.width,c.height)}}async function y(e,t){let n=u(t,"active pet"),r=await l(W(e)),o=await l(P(r,n));if(o!==r&&!o.startsWith(`${r}${A}`))throw new i("Active pet escapes the pets directory.");let a=await O(P(o,"pet.json"),d.manifestBytes),s=q(a);if(s.id!==n)throw new i(`Active pet directory ${n} does not match manifest id ${s.id}.`);await oe(o,s),re(s);let c=w(a,"pet.json").spritesheetPath;return await ae(o,s,c),{directory:o,catalog:s}}import{cp as _e,mkdir as Ce,readFile as ce,rename as je,rm as Ie,stat as fe,writeFile as Re}from"node:fs/promises";import{homedir as _}from"node:os";import{dirname as Le,join as S}from"node:path";var de=16384;function ue(e=process.env,t=_()){return e.PI_CODING_AGENT_DIR?.trim()||S(t,".pi","agent")}function g(e=process.env,t=_()){return S(ue(e,t),"pi-pet")}function pe(e){if(!e||typeof e!=="object"||Array.isArray(e))throw Error("Pi Pet config must be an object.");let t=e,n=Object.keys(t).find((r)=>!["schemaVersion","activePet"].includes(r));if(n)throw Error(`Pi Pet config has unknown field: ${n}.`);if(t.schemaVersion!==1)throw Error("Unsupported Pi Pet config schemaVersion.");return{schemaVersion:1,activePet:u(t.activePet,"active pet")}}async function C(e=g()){try{let t=S(e,"config.json"),n=await fe(t);if(!n.isFile()||n.size>de)throw Error(`Pi Pet config must be a bounded regular file: ${t}`);return pe(JSON.parse(await ce(t,"utf8")))}catch(t){if(t.code==="ENOENT")return;throw t}}async function j(e=g()){return(await C(e))?.activePet||"clawa"}var le=await j(),I=he(process.argv[2]||ge(g(),"pets",le)),R=await y(me(I),we(I));process.stdout.write(`${R.catalog.displayName}: ${Object.keys(R.catalog.actions).length} actions valid
2
2
  `);
@@ -1,5 +1,5 @@
1
- import{createReadStream as Qt}from"node:fs";import{writeFile as vt}from"node:fs/promises";import{dirname as dt,join as ct}from"node:path";import{fileURLToPath as qt}from"node:url";import{app as i,BrowserWindow as Wt,Menu as ft,screen as pt,session as $}from"electron";import{randomUUID as Nt}from"node:crypto";import{mkdir as At,readFile as _t,rename as kt,unlink as Dt,writeFile as St}from"node:fs/promises";import{dirname as Ut}from"node:path";var xt=new Set(["schemaVersion","mode","petSize","snoozedUntil"]),bt=4096,Ct=604800000;function O(){return{schemaVersion:1,mode:"normal",petSize:"medium",snoozedUntil:null}}function It(t){let n=t??"medium";if(n!=="small"&&n!=="medium"&&n!=="large")throw Error("Desktop petSize must be small, medium, or large.");return n}function T(t){if(!t||typeof t!=="object"||Array.isArray(t))throw Error("Desktop attention preferences must be an object.");let n=t,r=Object.keys(n).find((e)=>!xt.has(e));if(r)throw Error(`Desktop attention preferences have unknown field: ${r}.`);if(n.schemaVersion!==1)throw Error("Unsupported desktop attention schemaVersion.");if(n.mode!=="normal"&&n.mode!=="quiet")throw Error("Desktop attention mode must be normal or quiet.");let o=It(n.petSize);if(n.snoozedUntil!==null&&typeof n.snoozedUntil!=="string")throw Error("Desktop snoozedUntil must be an ISO timestamp or null.");if(typeof n.snoozedUntil==="string"){let e=Date.parse(n.snoozedUntil);if(!Number.isFinite(e))throw Error("Desktop snoozedUntil must be a valid ISO timestamp.");if(e-Date.now()>Ct)throw Error("Desktop snoozedUntil cannot be more than seven days away.")}return{schemaVersion:1,mode:n.mode,petSize:o,snoozedUntil:n.snoozedUntil}}async function X(t){try{let n=await _t(t,"utf8");if(Buffer.byteLength(n)>bt)throw Error("Attention preferences are too large.");return T(JSON.parse(n))}catch(n){if(n.code==="ENOENT")return O();throw Error(`Invalid desktop attention preferences at ${t}: ${n.message}`)}}async function K(t,n){let r=T(n);await At(Ut(t),{recursive:!0,mode:448});let o=`${t}.${process.pid}.${Nt()}.tmp`;try{await St(o,`${JSON.stringify(r)}
2
- `,{encoding:"utf8",flag:"wx",mode:384}),await kt(o,t)}catch(e){throw await Dt(o).catch(()=>{return}),e}}function Y(t=new Date){return new Date(t.getFullYear(),t.getMonth(),t.getDate()+1,8)}function k(t,n=Date.now()){if(!t.snoozedUntil)return 0;return Math.max(0,Date.parse(t.snoozedUntil)-n)}var H="pi-pet:cursor-position";function G(t){if(t===null)return null;if(!t||typeof t!=="object"||Array.isArray(t))throw Error("Cursor position must be an object.");let n=t;if(Object.keys(n).some((r)=>r!=="x"&&r!=="y"))throw Error("Cursor position has an unknown field.");if(typeof n.x!=="number"||!Number.isFinite(n.x)||Math.abs(n.x)>1e5||typeof n.y!=="number"||!Number.isFinite(n.y)||Math.abs(n.y)>1e5)throw Error("Cursor position is invalid.");return{x:n.x,y:n.y}}import{constants as M}from"node:fs";import{open as Ot}from"node:fs/promises";import{homedir as B}from"node:os";import{join as P,resolve as Ht}from"node:path";var Ft=new Set(["schemaVersion","gippityUrl"]),Z=16384;function $t(t=process.env,n=process.platform,r=B()){if(t.PI_PET_DESKTOP_CONFIG)return Ht(t.PI_PET_DESKTOP_CONFIG);if(n==="win32")return P(t.APPDATA||P(r,"AppData","Roaming"),"PiPetDesktop","config.json");if(n==="darwin")return P(r,"Library","Application Support","Pi Pet Desktop","config.json");return P(t.XDG_CONFIG_HOME||P(r,".config"),"pi-pet-desktop","config.json")}function Q(t=process.env,n=process.platform,r=B()){if(n==="win32")return P(t.LOCALAPPDATA||P(r,"AppData","Local"),"PiPetDesktop","state");if(n==="darwin")return P(r,"Library","Application Support","Pi Pet Desktop","state");return P(t.XDG_STATE_HOME||P(r,".local","state"),"pi-pet-desktop")}function jt(t){if(typeof t!=="string"||t.length>2048)throw Error("gippityUrl must be a bounded HTTPS URL.");let n;try{n=new URL(t)}catch{throw Error("gippityUrl must be a valid HTTPS URL.")}if(n.protocol!=="https:"||n.username||n.password||n.search||n.hash)throw Error("gippityUrl must be an HTTPS origin without credentials, query, or fragment.");if(n.pathname!=="/")throw Error("gippityUrl must not contain a path.");return n.origin}function v(t){if(!t||typeof t!=="object"||Array.isArray(t))throw Error("Pi Pet desktop config must be an object.");let n=t,r=Object.keys(n).find((o)=>!Ft.has(o));if(r)throw Error(`Pi Pet desktop config has unknown field: ${r}.`);if(n.schemaVersion!==1)throw Error("Unsupported Pi Pet desktop config schemaVersion.");return{schemaVersion:1,gippityUrl:jt(n.gippityUrl)}}function Jt(t){let n=t.PI_PET_GIPPITY_URL;return n?v({schemaVersion:1,gippityUrl:n}):void 0}async function Lt(t){try{return await Ot(t,M.O_RDONLY|(M.O_NOFOLLOW||0))}catch(n){if(n.code==="ENOENT")throw Error(`Pi Pet desktop is not configured: ${t}`);if(n.code==="ELOOP")throw Error(`Pi Pet desktop config must not be a symbolic link: ${t}`);throw n}}async function Vt(t,n){let r=await t.stat();if(!r.isFile()||r.size>Z)throw Error(`Pi Pet desktop config must be a regular file no larger than ${Z} bytes: ${n}`);if(process.platform==="win32")return;if((r.mode&63)!==0)throw Error(`Pi Pet desktop config must have mode 0600: ${n}`);if(process.getuid&&r.uid!==process.getuid())throw Error(`Pi Pet desktop config must be owned by the current user: ${n}`)}async function d(t=$t(),n=process.env){let r=Jt(n);if(r)return r;let o=await Lt(t);try{await Vt(o,t);let e=await o.readFile("utf8");return v(JSON.parse(e))}catch(e){throw Error(`Invalid Pi Pet desktop config at ${t}: ${e.message}`)}finally{await o.close()}}function q(t,n="normal"){let r=new URL(t.gippityUrl);if(r.pathname="/_gippity/apps/pi-pet/",r.searchParams.set("shell","desktop"),n==="quiet")r.searchParams.set("attention","quiet");return r.toString()}import{createConnection as zt}from"node:net";import{join as Rt}from"node:path";var Tt=/^[a-zA-Z0-9._-]{1,200}$/,Xt=1024,Kt=1048576,Yt=200;function w(t){let{XDG_RUNTIME_DIR:n,HYPRLAND_INSTANCE_SIGNATURE:r}=t;if(!(n&&r&&n.startsWith("/")&&Tt.test(r)))return;return Rt(n,"hypr",r,".socket.sock")}function Gt(t){let n=G(JSON.parse(t));if(!n)throw Error("Hyprland returned an empty cursor position.");return n}function W(t,n){if(!Array.isArray(t)||t.length!==2||typeof t[0]!=="number"||!Number.isFinite(t[0])||typeof t[1]!=="number"||!Number.isFinite(t[1]))throw Error(`Hyprland ${n} is invalid.`);return[t[0],t[1]]}function Mt(t,n){let r=JSON.parse(t);if(!Array.isArray(r))throw Error("Hyprland clients response must be an array.");let o=r.find((c)=>c!==null&&typeof c==="object"&&!Array.isArray(c)&&c.pid===n);if(!o)throw Error(`Hyprland did not report the Pi Pet window for process ${n}.`);let e=o,[u,_]=W(e.at,"window position"),[s,p]=W(e.size,"window size");if(s<1||p<1||s>4096||p>4096)throw Error("Hyprland window size is invalid.");return{x:u,y:_,width:s,height:p}}async function tt(t,n,r,o,e){return await new Promise((u,_)=>{let s=zt(t),p="",c=!1,l=(f)=>{if(c)return;c=!0,s.destroy(),_(f)};s.setEncoding("utf8"),s.setTimeout(Yt),s.on("connect",()=>s.end(n)),s.on("data",(f)=>{if(p+=f,Buffer.byteLength(p)>r){l(Error("Hyprland returned an oversized IPC response."));return}if(!o(p.trimEnd()))return;try{let m=e(p);c=!0,s.destroy(),u(m)}catch(m){l(m instanceof Error?m:Error("Hyprland returned an invalid IPC response."))}}),s.on("timeout",()=>l(Error("Hyprland cursor IPC timed out."))),s.on("error",(f)=>l(f)),s.on("end",()=>{if(c)return;try{let f=e(p);c=!0,u(f)}catch(f){l(f instanceof Error?f:Error("Hyprland returned an invalid IPC response."))}})})}async function Zt(t){return await tt(t,"j/cursorpos",Xt,(n)=>n.endsWith("}"),Gt)}async function Bt(t,n){return await tt(t,"j/clients",Kt,(r)=>r.endsWith("]"),(r)=>Mt(r,n))}function nt(t,n){let r=w(t);if(r)return async()=>await Zt(r);return async()=>n()}function rt(t,n){let r=w(t);if(!r)return;return async()=>await Bt(r,n)}var ot=20,wt=3000,tn=1500,nn=100,rn=220,et=500,on=1e4,en=1000,ut=Object.freeze({small:{width:180,height:195},medium:{width:240,height:260},large:{width:320,height:347}}),D,h,E,S,U,y,a=O(),j=Promise.resolve(),st=!1,N,at=!1;i.setName("Pi Pet Desktop");i.setPath("userData",Q());if(process.platform==="linux")i.disableHardwareAcceleration(),i.commandLine.appendSwitch("disable-gpu"),i.commandLine.appendSwitch("disable-gpu-compositing");function x(t){let n=process.platform==="darwin"?"floating":"screen-saver";t.setAlwaysOnTop(!0,n,1),t.moveTop(),t.setVisibleOnAllWorkspaces(!0,{visibleOnFullScreen:!0})}function mt(){return ct(i.getPath("userData"),"attention.json")}function sn(){let t=Number(process.env.PI_PET_OWNER_FD);if(!Number.isSafeInteger(t)||t<3||t>9)return;let n=()=>{if(!at)i.quit()};N=Qt("",{fd:t,autoClose:!1}),N.once("end",n),N.once("close",n),N.once("error",n),N.resume()}function Pt(){if(!y)throw Error("Pi Pet desktop configuration is not loaded.");return q(y,a.mode)}function C(t){let n=j.then(async()=>{let r=t(a);await K(mt(),r),a=r});return j=n.catch(()=>{return}),n}function g(t){process.stderr.write(`Pi Pet desktop: ${t instanceof Error?t.message:String(t)}
3
- `)}function b(t){if(!(y&&t))return!1;try{return new URL(t).origin===new URL(y.gippityUrl).origin}catch{return!1}}function cn(){$.defaultSession.setPermissionCheckHandler((t,n,r,o)=>{return n==="media"&&o.isMainFrame&&o.mediaType==="audio"&&t!==null&&b(t.getURL())&&b(r)}),$.defaultSession.setPermissionRequestHandler((t,n,r,o)=>{let e="mediaTypes"in o?o.mediaTypes:void 0;r(n==="media"&&o.isMainFrame&&e?.length===1&&e[0]==="audio"&&b(t.getURL())&&b(o.requestingUrl))})}function fn(){$.defaultSession.setCertificateVerifyProc((t,n)=>{let r=y?new URL(y.gippityUrl).hostname:void 0,o=t.errorCode===-202||t.verificationResult==="CERT_AUTHORITY_INVALID"||t.verificationResult==="net::ERR_CERT_AUTHORITY_INVALID";n(t.hostname===r&&o?0:t.errorCode)})}i.on("certificate-error",(t,n,r,o,e,u)=>{try{if(y&&o==="net::ERR_CERT_AUTHORITY_INVALID"&&new URL(r).origin===new URL(y.gippityUrl).origin){t.preventDefault(),u(!0);return}}catch{}u(!1)});function J(t){if(k(a)>0){t.hide();return}x(t),t.show()}function Et(t){if(E)clearTimeout(E);E=void 0;let n=k(a);if(n===0){J(t);return}t.hide(),E=setTimeout(()=>{E=void 0,C((r)=>({...r,snoozedUntil:null})).then(()=>J(t)).catch(g)},n),E.unref()}async function F(t,n){await C((r)=>({...r,snoozedUntil:n.toISOString()})),Et(t)}async function pn(t,n){await C((r)=>({...r,mode:n?"quiet":"normal"})),await L(t,Pt())}async function un(t,n){await C((e)=>({...e,petSize:n}));let r=t.getBounds(),o=ut[n];t.setBounds({x:r.x+r.width-o.width,y:r.y+r.height-o.height,...o}),x(t)}function an(t){let n=pt.getPrimaryDisplay().workArea,r=ut[t];return{x:n.x+n.width-r.width-ot,y:n.y+n.height-r.height-ot,...r}}function mn(t){let n=!1,r=!1,o=!1,e=et,u=0,_=nt(process.env,()=>pt.getCursorScreenPoint()),s=rt(process.env,process.pid),p,c=!1,l=async()=>{if(!s)return;try{p=await s(),c=!1}catch(m){if(!c)c=!0,g(m)}},f=async()=>{if(r||Date.now()<u||t.isDestroyed()||k(a)>0)return;r=!0;let m;try{m=await _(),o=!1,e=et,u=0}catch(I){if(!o)o=!0,g(I);u=Date.now()+e,e=Math.min(e*2,on),r=!1;return}let A=p||t.getBounds(),V=m.x-A.x,z=m.y-A.y,yt=V-A.width/2,lt=z-A.height/2,gt=Math.max(A.width,A.height)/2+rn,R=Math.hypot(yt,lt)<=gt;if(R){let I={x:V,y:z};t.webContents.send(H,I)}else if(n)t.webContents.send(H,null);n=R,r=!1};S=setInterval(()=>void f(),nn),S.unref(),U=setInterval(()=>void l(),en),U.unref(),l().then(f)}function Pn(t,n){t.webContents.setWindowOpenHandler(()=>({action:"deny"}));let r=(o,e)=>{try{if(new URL(e).origin!==n)o.preventDefault()}catch{o.preventDefault()}};t.webContents.on("will-navigate",r),t.webContents.on("will-redirect",r),t.webContents.on("context-menu",()=>{ft.buildFromTemplate([{label:"Use arrow keys and Enter",enabled:!1},{type:"separator"},{label:"Reload pet",click:()=>t.webContents.reload()},{label:"Quiet mode",type:"checkbox",checked:a.mode==="quiet",click:(o)=>void pn(t,o.checked).catch(g)},{label:"Snooze",submenu:[{label:"For 15 minutes",click:()=>void F(t,new Date(Date.now()+900000)).catch(g)},{label:"For 1 hour",click:()=>void F(t,new Date(Date.now()+3600000)).catch(g)},{label:"Until tomorrow",click:()=>void F(t,Y()).catch(g)}]},{label:"Pet size",submenu:["small","medium","large"].map((o)=>({label:o[0]?.toUpperCase()+o.slice(1),type:"radio",checked:a.petSize===o,click:()=>void un(t,o).catch(g)}))},{type:"separator"},{label:"Quit for this Pi session",click:()=>i.quit()}]).popup({window:t})})}async function En(t,n){await new Promise((o)=>setTimeout(o,tn));let r=await t.capturePage();await vt(n,r.toPNG()),i.quit()}function hn(t,n){h=setTimeout(()=>{h=void 0,L(t,n)},wt),h.unref()}async function L(t,n){if(h)clearTimeout(h);h=void 0;try{if(await t.loadURL(n),t.isDestroyed())return;J(t);let r=process.env.PI_PET_DESKTOP_SCREENSHOT;if(r)await En(t,r)}catch(r){if(t.isDestroyed())return;process.stderr.write(`Pi Pet desktop could not reach GipPity: ${r instanceof Error?r.message:String(r)}
4
- `),hn(t,n)}}async function it(){y=await d(),a=await X(mt());let t=Pt(),n=new URL(t).origin,r=new Wt({...an(a.petSize),frame:!1,transparent:!0,hasShadow:!1,backgroundColor:"#00000000",alwaysOnTop:!0,maximizable:!1,minimizable:!1,movable:!0,resizable:!1,show:!1,skipTaskbar:!0,title:"Pi Pet",webPreferences:{contextIsolation:!0,devTools:process.env.PI_PET_DESKTOP_DEVTOOLS==="1",nodeIntegration:!1,preload:ct(dt(qt(import.meta.url)),"preload.cjs"),sandbox:!0,webSecurity:!0}});if(Pn(r,n),x(r),r.setFullScreenable(!1),r.on("blur",()=>x(r)),r.on("show",()=>x(r)),r.on("closed",()=>{if(h)clearTimeout(h);if(E)clearTimeout(E);if(S)clearInterval(S);if(U)clearInterval(U);if(h=void 0,E=void 0,S=void 0,U=void 0,D===r)D=void 0}),L(r,t),mn(r),k(a)>0)Et(r);return r}i.whenReady().then(async()=>{ft.setApplicationMenu(null),sn(),cn(),fn(),D=await it(),i.on("activate",()=>{if(!D)it().then((t)=>{D=t})})}).catch((t)=>{process.stderr.write(`${t instanceof Error?t.message:String(t)}
5
- `),i.quit()});i.on("window-all-closed",()=>{if(process.platform!=="darwin")i.quit()});i.on("will-quit",()=>{at=!0,N?.destroy(),N=void 0});async function ht(){if(st)return;st=!0,setTimeout(()=>process.exit(0),1500).unref(),await j,i.quit()}process.once("SIGINT",()=>void ht());process.once("SIGTERM",()=>void ht());
1
+ import{createReadStream as qe}from"node:fs";import{writeFile as Xe}from"node:fs/promises";import{dirname as Ke,join as ce}from"node:path";import{fileURLToPath as Qe}from"node:url";import{app as s,BrowserWindow as Ze,Menu as ue,screen as de,session as v}from"electron";import{randomUUID as Ee}from"node:crypto";import{mkdir as Se,readFile as ke,rename as be,unlink as De,writeFile as _e}from"node:fs/promises";import{dirname as Te}from"node:path";var Ce=new Set(["schemaVersion","mode","petSize","snoozedUntil"]),Re=4096,Ae=604800000;function N(){return{schemaVersion:1,mode:"normal",petSize:"medium",snoozedUntil:null}}function Ne(e){let t=e??"medium";if(t!=="small"&&t!=="medium"&&t!=="large")throw Error("Desktop petSize must be small, medium, or large.");return t}function H(e){if(!e||typeof e!=="object"||Array.isArray(e))throw Error("Desktop attention preferences must be an object.");let t=e,n=Object.keys(t).find((o)=>!Ce.has(o));if(n)throw Error(`Desktop attention preferences have unknown field: ${n}.`);if(t.schemaVersion!==1)throw Error("Unsupported desktop attention schemaVersion.");if(t.mode!=="normal"&&t.mode!=="quiet")throw Error("Desktop attention mode must be normal or quiet.");let r=Ne(t.petSize);if(t.snoozedUntil!==null&&typeof t.snoozedUntil!=="string")throw Error("Desktop snoozedUntil must be an ISO timestamp or null.");if(typeof t.snoozedUntil==="string"){let o=Date.parse(t.snoozedUntil);if(!Number.isFinite(o))throw Error("Desktop snoozedUntil must be a valid ISO timestamp.");if(o-Date.now()>Ae)throw Error("Desktop snoozedUntil cannot be more than seven days away.")}return{schemaVersion:1,mode:t.mode,petSize:r,snoozedUntil:t.snoozedUntil}}async function B(e){try{let t=await ke(e,"utf8");if(Buffer.byteLength(t)>Re)throw Error("Attention preferences are too large.");return H(JSON.parse(t))}catch(t){if(t.code==="ENOENT")return N();throw Error(`Invalid desktop attention preferences at ${e}: ${t.message}`)}}async function F(e,t){let n=H(t);await Se(Te(e),{recursive:!0,mode:448});let r=`${e}.${process.pid}.${Ee()}.tmp`;try{await _e(r,`${JSON.stringify(n)}
2
+ `,{encoding:"utf8",flag:"wx",mode:384}),await be(r,e)}catch(o){throw await De(r).catch(()=>{return}),o}}function V(e=new Date){return new Date(e.getFullYear(),e.getMonth(),e.getDate()+1,8)}function k(e,t=Date.now()){if(!e.snoozedUntil)return 0;return Math.max(0,Date.parse(e.snoozedUntil)-t)}var U="pi-pet:cursor-position";function j(e){if(e===null)return null;if(!e||typeof e!=="object"||Array.isArray(e))throw Error("Cursor position must be an object.");let t=e;if(Object.keys(t).some((n)=>n!=="x"&&n!=="y"))throw Error("Cursor position has an unknown field.");if(typeof t.x!=="number"||!Number.isFinite(t.x)||Math.abs(t.x)>1e5||typeof t.y!=="number"||!Number.isFinite(t.y)||Math.abs(t.y)>1e5)throw Error("Cursor position is invalid.");return{x:t.x,y:t.y}}import{constants as J}from"node:fs";import{open as Ue}from"node:fs/promises";import{homedir as Y}from"node:os";import{join as l,resolve as Oe}from"node:path";var ve=new Set(["schemaVersion","gippityUrl"]),G=16384;function Ie(e=process.env,t=process.platform,n=Y()){if(e.PI_PET_DESKTOP_CONFIG)return Oe(e.PI_PET_DESKTOP_CONFIG);if(t==="win32")return l(e.APPDATA||l(n,"AppData","Roaming"),"PiPetDesktop","config.json");if(t==="darwin")return l(n,"Library","Application Support","Pi Pet Desktop","config.json");return l(e.XDG_CONFIG_HOME||l(n,".config"),"pi-pet-desktop","config.json")}function q(e=process.env,t=process.platform,n=Y()){if(t==="win32")return l(e.LOCALAPPDATA||l(n,"AppData","Local"),"PiPetDesktop","state");if(t==="darwin")return l(n,"Library","Application Support","Pi Pet Desktop","state");return l(e.XDG_STATE_HOME||l(n,".local","state"),"pi-pet-desktop")}function xe(e){if(typeof e!=="string"||e.length>2048)throw Error("gippityUrl must be a bounded HTTPS URL.");let t;try{t=new URL(e)}catch{throw Error("gippityUrl must be a valid HTTPS URL.")}if(t.protocol!=="https:"||t.username||t.password||t.search||t.hash)throw Error("gippityUrl must be an HTTPS origin without credentials, query, or fragment.");if(t.pathname!=="/")throw Error("gippityUrl must not contain a path.");return t.origin}function X(e){if(!e||typeof e!=="object"||Array.isArray(e))throw Error("Pi Pet desktop config must be an object.");let t=e,n=Object.keys(t).find((r)=>!ve.has(r));if(n)throw Error(`Pi Pet desktop config has unknown field: ${n}.`);if(t.schemaVersion!==1)throw Error("Unsupported Pi Pet desktop config schemaVersion.");return{schemaVersion:1,gippityUrl:xe(t.gippityUrl)}}function ze(e){let t=e.PI_PET_GIPPITY_URL;return t?X({schemaVersion:1,gippityUrl:t}):void 0}async function Le(e){try{return await Ue(e,J.O_RDONLY|(J.O_NOFOLLOW||0))}catch(t){if(t.code==="ENOENT")throw Error(`Pi Pet desktop is not configured: ${e}`);if(t.code==="ELOOP")throw Error(`Pi Pet desktop config must not be a symbolic link: ${e}`);throw t}}async function Me(e,t){let n=await e.stat();if(!n.isFile()||n.size>G)throw Error(`Pi Pet desktop config must be a regular file no larger than ${G} bytes: ${t}`);if(process.platform==="win32")return;if((n.mode&63)!==0)throw Error(`Pi Pet desktop config must have mode 0600: ${t}`);if(process.getuid&&n.uid!==process.getuid())throw Error(`Pi Pet desktop config must be owned by the current user: ${t}`)}async function K(e=Ie(),t=process.env){let n=ze(t);if(n)return n;let r=await Le(e);try{await Me(r,e);let o=await r.readFile("utf8");return X(JSON.parse(o))}catch(o){throw Error(`Invalid Pi Pet desktop config at ${e}: ${o.message}`)}finally{await r.close()}}function Q(e,t="normal"){let n=new URL(e.gippityUrl);if(n.pathname="/_gippity/apps/pi-pet/",n.searchParams.set("shell","desktop"),t==="quiet")n.searchParams.set("attention","quiet");return n.toString()}import{createConnection as We}from"node:net";import{join as He}from"node:path";var Be=/^[a-zA-Z0-9._-]{1,200}$/,Fe=1024,Ve=1048576,$e=200;function ee(e){let{XDG_RUNTIME_DIR:t,HYPRLAND_INSTANCE_SIGNATURE:n}=e;if(!(t&&n&&t.startsWith("/")&&Be.test(n)))return;return He(t,"hypr",n,".socket.sock")}function je(e){let t=j(JSON.parse(e));if(!t)throw Error("Hyprland returned an empty cursor position.");return t}function Z(e,t){if(!Array.isArray(e)||e.length!==2||typeof e[0]!=="number"||!Number.isFinite(e[0])||typeof e[1]!=="number"||!Number.isFinite(e[1]))throw Error(`Hyprland ${t} is invalid.`);return[e[0],e[1]]}function Je(e,t){let n=JSON.parse(e);if(!Array.isArray(n))throw Error("Hyprland clients response must be an array.");let r=n.find((a)=>a!==null&&typeof a==="object"&&!Array.isArray(a)&&a.pid===t);if(!r)throw Error(`Hyprland did not report the Pi Pet window for process ${t}.`);let o=r,[d,S]=Z(o.at,"window position"),[i,u]=Z(o.size,"window size");if(i<1||u<1||i>4096||u>4096)throw Error("Hyprland window size is invalid.");return{x:d,y:S,width:i,height:u}}async function te(e,t,n,r,o){return await new Promise((d,S)=>{let i=We(e),u="",a=!1,P=(c)=>{if(a)return;a=!0,i.destroy(),S(c)};i.setEncoding("utf8"),i.setTimeout($e),i.on("connect",()=>i.end(t)),i.on("data",(c)=>{if(u+=c,Buffer.byteLength(u)>n){P(Error("Hyprland returned an oversized IPC response."));return}if(!r(u.trimEnd()))return;try{let f=o(u);a=!0,i.destroy(),d(f)}catch(f){P(f instanceof Error?f:Error("Hyprland returned an invalid IPC response."))}}),i.on("timeout",()=>P(Error("Hyprland cursor IPC timed out."))),i.on("error",(c)=>P(c)),i.on("end",()=>{if(a)return;try{let c=o(u);a=!0,d(c)}catch(c){P(c instanceof Error?c:Error("Hyprland returned an invalid IPC response."))}})})}async function Ge(e){return await te(e,"j/cursorpos",Fe,(t)=>t.endsWith("}"),je)}async function Ye(e,t){return await te(e,"j/clients",Ve,(n)=>n.endsWith("]"),(n)=>Je(n,t))}function ne(e,t){let n=ee(e);if(n)return async()=>await Ge(n);return async()=>t()}function re(e,t){let n=ee(e);if(!n)return;return async()=>await Ye(n,t)}var oe=20,et=3000,tt=1500,nt=100,rt=220,ie=500,ot=1e4,it=1000,pe=Object.freeze({small:{width:180,height:195},medium:{width:240,height:260},large:{width:320,height:347}}),b,w,m,D,_,h,p=N(),I=Promise.resolve(),se=!1,y,fe=!1;s.setName("Pi Pet Desktop");s.setPath("userData",q());if(process.platform==="linux")s.disableHardwareAcceleration(),s.commandLine.appendSwitch("disable-gpu"),s.commandLine.appendSwitch("disable-gpu-compositing");function T(e){let t=process.platform==="darwin"?"floating":"screen-saver";e.setAlwaysOnTop(!0,t,1),e.moveTop(),e.setVisibleOnAllWorkspaces(!0,{visibleOnFullScreen:!0})}function le(){return ce(s.getPath("userData"),"attention.json")}function st(){let e=Number(process.env.PI_PET_OWNER_FD);if(!Number.isSafeInteger(e)||e<3||e>9)return;let t=()=>{if(!fe)s.quit()};y=qe("",{fd:e,autoClose:!1}),y.once("end",t),y.once("close",t),y.once("error",t),y.resume()}function me(){if(!h)throw Error("Pi Pet desktop configuration is not loaded.");return Q(h,p.mode)}function R(e){let t=I.then(async()=>{let n=e(p);await F(le(),n),p=n});return I=t.catch(()=>{return}),t}function g(e){process.stderr.write(`Pi Pet desktop: ${e instanceof Error?e.message:String(e)}
3
+ `)}function C(e){if(!(h&&e))return!1;try{return new URL(e).origin===new URL(h.gippityUrl).origin}catch{return!1}}function at(){v.defaultSession.setPermissionCheckHandler((e,t,n,r)=>t==="media"&&r.isMainFrame&&r.mediaType==="audio"&&e!==null&&C(e.getURL())&&C(n)),v.defaultSession.setPermissionRequestHandler((e,t,n,r)=>{let o="mediaTypes"in r?r.mediaTypes:void 0;n(t==="media"&&r.isMainFrame&&o?.length===1&&o[0]==="audio"&&C(e.getURL())&&C(r.requestingUrl))})}function ct(){v.defaultSession.setCertificateVerifyProc((e,t)=>{let n=h?new URL(h.gippityUrl).hostname:void 0,r=e.errorCode===-202||e.verificationResult==="CERT_AUTHORITY_INVALID"||e.verificationResult==="net::ERR_CERT_AUTHORITY_INVALID";t(e.hostname===n&&r?0:e.errorCode)})}s.on("certificate-error",(e,t,n,r,o,d)=>{try{if(h&&r==="net::ERR_CERT_AUTHORITY_INVALID"&&new URL(n).origin===new URL(h.gippityUrl).origin){e.preventDefault(),d(!0);return}}catch{}d(!1)});function x(e){if(k(p)>0){e.hide();return}T(e),e.show()}function we(e){if(m)clearTimeout(m);m=void 0;let t=k(p);if(t===0){x(e);return}e.hide(),m=setTimeout(()=>{m=void 0,R((n)=>({...n,snoozedUntil:null})).then(()=>x(e)).catch(g)},t),m.unref()}async function O(e,t){await R((n)=>({...n,snoozedUntil:t.toISOString()})),we(e)}async function ut(e,t){await R((n)=>({...n,mode:t?"quiet":"normal"})),await z(e,me())}async function dt(e,t){await R((o)=>({...o,petSize:t}));let n=e.getBounds(),r=pe[t];e.setBounds({x:n.x+n.width-r.width,y:n.y+n.height-r.height,...r}),T(e)}function pt(e){let t=de.getPrimaryDisplay().workArea,n=pe[e];return{x:t.x+t.width-n.width-oe,y:t.y+t.height-n.height-oe,...n}}function ft(e){let t=!1,n=!1,r=!1,o=ie,d=0,S=ne(process.env,()=>de.getCursorScreenPoint()),i=re(process.env,process.pid),u,a=!1,P=async()=>{if(!i)return;try{u=await i(),a=!1}catch(f){if(!a)a=!0,g(f)}},c=async()=>{if(n||Date.now()<d||e.isDestroyed()||k(p)>0)return;n=!0;let f;try{f=await S(),r=!1,o=ie,d=0}catch(A){if(!r)r=!0,g(A);d=Date.now()+o,o=Math.min(o*2,ot),n=!1;return}let E=u||e.getBounds(),L=f.x-E.x,M=f.y-E.y,Pe=L-E.width/2,ge=M-E.height/2,ye=Math.max(E.width,E.height)/2+rt,W=Math.hypot(Pe,ge)<=ye;if(W){let A={x:L,y:M};e.webContents.send(U,A)}else if(t)e.webContents.send(U,null);t=W,n=!1};D=setInterval(()=>void c(),nt),D.unref(),_=setInterval(()=>void P(),it),_.unref(),P().then(c)}function lt(e,t){e.webContents.setWindowOpenHandler(()=>({action:"deny"}));let n=(r,o)=>{try{if(new URL(o).origin!==t)r.preventDefault()}catch{r.preventDefault()}};e.webContents.on("will-navigate",n),e.webContents.on("will-redirect",n),e.webContents.on("context-menu",()=>{ue.buildFromTemplate([{label:"Use arrow keys and Enter",enabled:!1},{type:"separator"},{label:"Reload pet",click:()=>e.webContents.reload()},{label:"Quiet mode",type:"checkbox",checked:p.mode==="quiet",click:(r)=>void ut(e,r.checked).catch(g)},{label:"Snooze",submenu:[{label:"For 15 minutes",click:()=>void O(e,new Date(Date.now()+900000)).catch(g)},{label:"For 1 hour",click:()=>void O(e,new Date(Date.now()+3600000)).catch(g)},{label:"Until tomorrow",click:()=>void O(e,V()).catch(g)}]},{label:"Pet size",submenu:["small","medium","large"].map((r)=>({label:r[0]?.toUpperCase()+r.slice(1),type:"radio",checked:p.petSize===r,click:()=>void dt(e,r).catch(g)}))},{type:"separator"},{label:"Quit for this Pi session",click:()=>s.quit()}]).popup({window:e})})}async function mt(e,t){await new Promise((r)=>setTimeout(r,tt));let n=await e.capturePage();await Xe(t,n.toPNG()),s.quit()}function wt(e,t){w=setTimeout(()=>{w=void 0,z(e,t)},et),w.unref()}async function z(e,t){if(w)clearTimeout(w);w=void 0;try{if(await e.loadURL(t),e.isDestroyed())return;x(e);let n=process.env.PI_PET_DESKTOP_SCREENSHOT;if(n)await mt(e,n)}catch(n){if(e.isDestroyed())return;process.stderr.write(`Pi Pet desktop could not reach GipPity: ${n instanceof Error?n.message:String(n)}
4
+ `),wt(e,t)}}async function ae(){h=await K(),p=await B(le());let e=me(),t=new URL(e).origin,n=new Ze({...pt(p.petSize),frame:!1,transparent:!0,hasShadow:!1,backgroundColor:"#00000000",alwaysOnTop:!0,maximizable:!1,minimizable:!1,movable:!0,resizable:!1,show:!1,skipTaskbar:!0,title:"Pi Pet",webPreferences:{contextIsolation:!0,devTools:process.env.PI_PET_DESKTOP_DEVTOOLS==="1",nodeIntegration:!1,preload:ce(Ke(Qe(import.meta.url)),"preload.cjs"),sandbox:!0,webSecurity:!0}});if(lt(n,t),T(n),n.setFullScreenable(!1),n.on("blur",()=>T(n)),n.on("show",()=>T(n)),n.on("closed",()=>{if(w)clearTimeout(w);if(m)clearTimeout(m);if(D)clearInterval(D);if(_)clearInterval(_);if(w=void 0,m=void 0,D=void 0,_=void 0,b===n)b=void 0}),z(n,e),ft(n),k(p)>0)we(n);return n}s.whenReady().then(async()=>{ue.setApplicationMenu(null),st(),at(),ct(),b=await ae(),s.on("activate",()=>{if(!b)ae().then((e)=>{b=e})})}).catch((e)=>{process.stderr.write(`${e instanceof Error?e.message:String(e)}
5
+ `),s.quit()});s.on("window-all-closed",()=>{if(process.platform!=="darwin")s.quit()});s.on("will-quit",()=>{fe=!0,y?.destroy(),y=void 0});async function he(){if(se)return;se=!0,setTimeout(()=>process.exit(0),1500).unref(),await I,s.quit()}process.once("SIGINT",()=>void he());process.once("SIGTERM",()=>void he());
package/dist/web/app.js CHANGED
@@ -1 +1 @@
1
- var V=/^look-/,x=new URLSearchParams(location.search).get("shell")==="desktop",j=x&&new URLSearchParams(location.search).get("attention")==="quiet";document.documentElement.classList.toggle("desktop-shell",x);var e={actionName:n("action-name"),actionNote:n("action-note"),actionSelect:n("action-select"),agentState:n("agent-state"),bubble:n("bubble"),canvas:n("pet-canvas"),connectionDot:n("connection-dot"),connectionLabel:n("connection-label"),description:n("pet-description"),desktopPromptClose:n("desktop-prompt-close"),desktopPromptFeedback:n("desktop-prompt-feedback"),desktopPromptForm:n("desktop-prompt-form"),desktopPromptLabel:n("desktop-prompt-label"),desktopPromptSubmit:n("desktop-prompt-submit"),desktopPromptText:n("desktop-prompt-text"),desktopDictation:n("desktop-dictation-button"),desktopVoice:n("desktop-voice-button"),name:n("pet-name"),promptFeedback:n("prompt-feedback"),promptForm:n("prompt-form"),promptSubmit:n("prompt-submit"),promptText:n("prompt-text"),pointerHint:n("pointer-hint"),quickActions:n("quick-actions"),restoreAction:n("restore-action"),stage:n("stage"),voice:n("voice-button"),voiceState:n("voice-state"),wave:n("wave-button")};function n(t){let i=document.getElementById(t);if(!i)throw Error(`Missing element: ${t}`);return i}function k(t){return t.replace(V,"look ").replaceAll("_5",".5°").replaceAll("-"," ")}class H{#t;#e;#a=new Map;#s;#i;#o;#c=performance.now();#n;constructor(t){this.#t=t;let i=t.getContext("2d",{alpha:!0});if(!i)throw Error("Canvas rendering is unavailable.");this.#e=i,new ResizeObserver(()=>this.#d()).observe(t),this.#d()}async load(t){this.#s=t,this.#a.clear();let i=new Set([...Object.values(t.actions),...Object.values(t.directions)].map((o)=>o.asset));await Promise.all([...i].map(async(o)=>{let a=o.split("/").map(encodeURIComponent).join("/"),s=await fetch(new URL(a,location.href),{cache:"no-store"});if(!s.ok)throw Error(`Could not load pet asset ${o}: HTTP ${s.status}`);let g=URL.createObjectURL(await s.blob());try{let u=new Image;if(u.src=g,await u.decode(),u.naturalWidth*u.naturalHeight>16000000)throw Error(`Pet asset ${o} exceeds the decoded-pixel limit.`);this.#a.set(o,u)}finally{URL.revokeObjectURL(g)}})),this.show(t.defaultAction)}show(t){let i=this.#s?.actions[t]||this.#s?.directions[t];if(!i)return;if(this.#i=i,this.#c=performance.now(),this.#o=void 0,this.#n)clearTimeout(this.#n);this.#n=void 0,this.#l()}#d(){let t=this.#t.getBoundingClientRect(),i=Math.min(devicePixelRatio||1,2),o=Math.max(1,Math.round(t.width*i)),a=Math.max(1,Math.round(t.height*i));if(this.#t.width!==o||this.#t.height!==a){this.#t.width=o,this.#t.height=a;let s=this.#o;this.#o=void 0,this.#r(s||this.#i?.frames[0])}}#r(t){let i=this.#i;if(!(t&&i))return;let o=this.#a.get(i.asset);if(!o)return;let{width:a,height:s}=this.#t;this.#e.clearRect(0,0,a,s),this.#e.imageSmoothingEnabled=!0,this.#e.imageSmoothingQuality="high",this.#e.drawImage(o,t.x,t.y,t.width,t.height,0,0,a,s),this.#o=t}#l=()=>{let t=this.#i;if(!t)return;let i=t.frames.reduce((u,E)=>u+E.durationMs,0),o=performance.now()-this.#c;if(t.loop)o%=i;else if(o>=i){if(this.#r(t.frames.at(-1)),t.next)this.show(t.next);return}let a=0,s=t.frames[0]?.durationMs||0;while(a<t.frames.length-1&&o>=s)a+=1,s+=t.frames[a]?.durationMs||0;let g=t.frames[a];if(g!==this.#o)this.#r(g);this.#n=setTimeout(this.#l,Math.max(16,s-o))}}var h=new H(e.canvas),F={feedback:e.promptFeedback,submit:e.promptSubmit,text:e.promptText},y={feedback:e.desktopPromptFeedback,submit:e.desktopPromptSubmit,text:e.desktopPromptText},v=new Map,c,r,b=!1,f=!1,d="idle",l="idle",P="Following the active Pi session",S=-1,w,C,m=!1;function N(){if(w)clearTimeout(w);w=void 0}function M(t,i){if(b=t,e.connectionDot.classList.toggle("online",t),e.connectionLabel.textContent=i,e.agentState.textContent=t?"Session online":"Reconnecting",e.promptSubmit.disabled=!t,e.desktopPromptSubmit.disabled=!t,e.voice.disabled=!t,e.desktopVoice.disabled=!t,e.desktopDictation.disabled=!t,r)$(r.audio.state)}function p(t,i,o){if(N(),d=o,l=j&&o==="working"?c.defaultAction:t,P=i,!f)h.show(l);e.actionName.textContent=f?`preview · ${e.actionSelect.value}`:k(l),e.actionNote.textContent=f?"Local preview":P}function q(t){let i=t.replace(/\s+/g," ").trim().slice(0,280);if(!i)return;if(C)clearTimeout(C);e.bubble.textContent=i,e.bubble.hidden=!1,C=setTimeout(()=>{e.bubble.hidden=!0},Math.min(12000,Math.max(3500,i.length*55)))}function O(t){if(p("review","Ready","settled"),t)q(t);let i=c.actions.review?.frames.reduce((o,a)=>o+a.durationMs,0)||1000;w=setTimeout(()=>{w=void 0,p(c.defaultAction,"Following the active Pi session","idle")},i)}function G(t){if(t.state==="working"){if(d="working",!m)p("running","Pi is working","working")}else if(t.state==="settled")v.clear(),m=!1,O(t.text);else if(t.state==="idle")v.clear(),m=!1,p(c.defaultAction,"Following the active Pi session","idle")}function W(t){if(!(t.toolCallId&&t.toolName)||t.toolName==="pet_show")return;if(v.set(t.toolCallId,t.toolName),t.toolName==="ask"){if(d="waiting",!m)p("waiting","Waiting for your answer","waiting")}else if(d="working",!m)p("running",`Using ${t.toolName}`,"working")}function K(t){if(!(t.toolCallId&&v.delete(t.toolCallId)))return;if(t.isError){if(d="failed",!m)p("failed","A tool failed","failed")}else if([...v.values()].includes("ask")){if(d="waiting",!m)p("waiting","Waiting for your answer","waiting")}else if(d="working",!m)p("running","Pi is working","working")}function z(t){if(t.app!=="pi-pet"||t.data?.schemaVersion!==1||t.data.pet!==c.id||t.data.revision<=S)return;let i=t.data;if(!c.actions[i.action])return;if(S=i.revision,i.action===c.defaultAction){if(m=!1,d==="idle")p(c.defaultAction,"Following the active Pi session","idle");else if(d==="working")p("running","Pi is working","working");else if(d==="waiting")p("waiting","Waiting for your answer","waiting");else if(d==="failed")p("failed","A tool failed","failed");return}if(m=!0,N(),l=i.action,P=i.note||"Requested by Pi",!f)h.show(l),e.actionName.textContent=k(l),e.actionNote.textContent=P}function X(t){if(c=t,l=t.defaultAction,e.name.textContent=t.displayName,e.description.textContent=t.description,e.desktopPromptLabel.textContent=`Talk to ${t.displayName}`,e.pointerHint.textContent=`Move nearby. ${t.displayName} is watching.`,e.promptText.placeholder=`Send a thought back through ${t.displayName}…`,x)e.wave.setAttribute("aria-label",`Talk to ${t.displayName}`);document.title=`${t.displayName} · Pi Pet`,e.actionSelect.replaceChildren();for(let i of Object.keys(t.actions).sort()){let o=document.createElement("option");o.value=i,o.textContent=k(i),e.actionSelect.append(o)}e.actionSelect.value=t.defaultAction,e.quickActions.replaceChildren();for(let i of["idle","running","waiting","review","failed","waving","jumping"]){if(!t.actions[i])continue;let o=document.createElement("button");o.type="button",o.textContent=k(i),o.addEventListener("click",()=>B(i)),e.quickActions.append(o)}}function B(t){if(!c.actions[t])return;f=!0,e.actionSelect.value=t,h.show(t),e.actionName.textContent=`preview · ${k(t)}`,e.actionNote.textContent="Local preview"}function Q(){f=!1,p(l,P,d)}function Y(){if(f)return!1;if(d==="idle")return l===c.defaultAction;if(d==="working")return l==="running"||l===c.defaultAction;return d==="settled"&&(l==="review"||l===c.defaultAction)}function D(t,i){if(!Y())return;let o=e.stage.getBoundingClientRect(),a=t-(o.left+o.width/2),s=i-(o.top+o.height/2),g=Math.hypot(a,s),u=Math.max(45,Math.min(o.width,o.height)*0.3);if(g<u){h.show(c.defaultAction);return}let E=(Math.atan2(a,-s)*180/Math.PI+360)%360,U=Math.round(E/22.5)%16*22.5;h.show(`look-${String(U).padStart(3,"0").replace(".5","_5")}`)}function A(){if(!f)h.show(l)}function J(){e.desktopPromptForm.hidden=!1,document.documentElement.classList.add("conversation-open"),e.desktopPromptFeedback.textContent=b?"":"Pi is reconnecting",e.desktopPromptText.focus()}function T(){e.desktopPromptForm.hidden=!0,document.documentElement.classList.remove("conversation-open"),e.desktopPromptFeedback.textContent=""}async function _(t){let i=t.text.value.trim();if(!(i&&b))return;t.submit.disabled=!0,t.feedback.textContent="Sending…";try{if(await r.send(i),t.text.value="",t.feedback.textContent="Delivered",t===y)setTimeout(T,650)}catch(o){t.feedback.textContent=o instanceof Error?o.message:"Prompt failed"}finally{t.submit.disabled=!b}}function L(t,i,o){let a=o.mode||"conversation",s=i==="conversation"?"Voice":"Dictate";if(t.disabled=!b||(o.active===!0||o.busy===!0)&&a!==i,a!==i){t.textContent=s;return}if(o.active){t.textContent=i==="dictation"?"Finish":o.muted?"Muted":"Stop voice";return}t.textContent=o.busy?"Cancel":s}function $(t){let i=t.active===!0;if(L(e.voice,"conversation",t),L(e.desktopVoice,"conversation",t),L(e.desktopDictation,"dictation",t),e.voiceState.textContent=t.detail||(i?t.state||"Voice active":"Voice idle"),t.state==="error"){let o=t.detail||"Voice failed";e.promptFeedback.textContent=o,e.desktopPromptFeedback.textContent=o}}function Z(t){if(typeof t.text!=="string")return;if(e.promptText.value!==t.text)e.promptText.value=t.text;if(e.desktopPromptText.value!==t.text)e.desktopPromptText.value=t.text}function I(t){if(!r)return;r.setDraft(t.text.value)}async function R(t,i,o){try{let a=r.audio.state;if(a.active||a.busy){if(a.mode!==t)return;if(t==="dictation"&&o)r.audio.stop({text:o.text.value,revision:r.draft.revision,selectionStart:o.text.selectionStart,selectionEnd:o.text.selectionEnd});else r.audio.stop()}else await r.audio.start(t)}catch(a){i.textContent=a instanceof Error?a.message:"Voice failed"}}async function tt(){let t=await fetch(new URL("catalog.json",location.href),{cache:"no-store"});if(!t.ok)throw Error(`Could not load pet catalog: HTTP ${t.status}`);if(X(await t.json()),await h.load(c),document.documentElement.dataset.petReady="true",!window.GippityRemote)throw Error("GipPity Remote SDK is unavailable");r=window.GippityRemote.connect(),r.on("connection",(i)=>{let o=i.state==="connected";if(o)S=-1,v.clear();M(o,o?"Live":"Reconnecting")}),r.on("activity",G),r.on("app.state",z),r.on("pi:tool_execution_start",W),r.on("pi:tool_execution_end",K),r.on("draft",Z),r.on("audio",$),M(!1,"Connecting")}e.actionSelect.addEventListener("change",()=>B(e.actionSelect.value));e.restoreAction.addEventListener("click",Q);e.wave.addEventListener("click",()=>{if(x){if(e.desktopPromptForm.hidden)J();else T();return}h.show("waving"),setTimeout(A,900)});e.promptForm.addEventListener("submit",(t)=>{t.preventDefault(),_(F)});e.desktopPromptForm.addEventListener("submit",(t)=>{t.preventDefault(),_(y)});e.desktopPromptClose.addEventListener("click",T);e.desktopPromptText.addEventListener("keydown",(t)=>{if(t.key==="Escape")t.preventDefault(),T();else if(t.key==="Enter"&&!t.shiftKey)t.preventDefault(),e.desktopPromptForm.requestSubmit()});e.promptText.addEventListener("input",()=>I(F));e.desktopPromptText.addEventListener("input",()=>I(y));e.voice.addEventListener("click",()=>void R("conversation",e.promptFeedback));e.desktopVoice.addEventListener("click",()=>void R("conversation",e.desktopPromptFeedback));e.desktopDictation.addEventListener("click",()=>void R("dictation",e.desktopPromptFeedback,y));if(window.piPetDesktop)window.piPetDesktop.onCursorPosition((t)=>{if(t)D(t.x,t.y);else A()});else e.stage.addEventListener("pointermove",(t)=>D(t.clientX,t.clientY)),e.stage.addEventListener("pointerleave",A);tt().catch((t)=>{M(!1,"Unavailable"),e.actionNote.textContent=t instanceof Error?t.message:"Pet failed to start"});
1
+ var O=/^look-/,y=new URLSearchParams(location.search).get("shell")==="desktop",G=y&&new URLSearchParams(location.search).get("attention")==="quiet";document.documentElement.classList.toggle("desktop-shell",y);var e={actionName:n("action-name"),actionNote:n("action-note"),actionSelect:n("action-select"),agentState:n("agent-state"),bubble:n("bubble"),canvas:n("pet-canvas"),connectionDot:n("connection-dot"),connectionLabel:n("connection-label"),description:n("pet-description"),desktopPromptClose:n("desktop-prompt-close"),desktopPromptFeedback:n("desktop-prompt-feedback"),desktopPromptForm:n("desktop-prompt-form"),desktopPromptLabel:n("desktop-prompt-label"),desktopPromptSubmit:n("desktop-prompt-submit"),desktopPromptText:n("desktop-prompt-text"),desktopDictation:n("desktop-dictation-button"),desktopVoice:n("desktop-voice-button"),name:n("pet-name"),promptFeedback:n("prompt-feedback"),promptForm:n("prompt-form"),promptSubmit:n("prompt-submit"),promptText:n("prompt-text"),pointerHint:n("pointer-hint"),quickActions:n("quick-actions"),restoreAction:n("restore-action"),stage:n("stage"),voice:n("voice-button"),voiceState:n("voice-state"),wave:n("wave-button")};function n(t){let o=document.getElementById(t);if(!o)throw Error(`Missing element: ${t}`);return o}function b(t){return t.replace(O,"look ").replaceAll("_5",".5°").replaceAll("-"," ")}class F{#t;#e;#a=new Map;#s;#i;#o;#c=performance.now();#n;constructor(t){this.#t=t;let o=t.getContext("2d",{alpha:!0});if(!o)throw Error("Canvas rendering is unavailable.");this.#e=o,new ResizeObserver(()=>this.#d()).observe(t),this.#d()}async load(t){this.#s=t,this.#a.clear();let o=new Set([...Object.values(t.actions),...Object.values(t.directions)].map((i)=>i.asset));await Promise.all([...o].map(async(i)=>{let a=i.split("/").map(encodeURIComponent).join("/"),s=await fetch(new URL(a,location.href),{cache:"no-store"});if(!s.ok)throw Error(`Could not load pet asset ${i}: HTTP ${s.status}`);let h=URL.createObjectURL(await s.blob());try{let u=new Image;if(u.src=h,await u.decode(),u.naturalWidth*u.naturalHeight>16000000)throw Error(`Pet asset ${i} exceeds the decoded-pixel limit.`);this.#a.set(i,u)}finally{URL.revokeObjectURL(h)}})),this.show(t.defaultAction)}show(t){let o=this.#s?.actions[t]||this.#s?.directions[t];if(!o)return;if(this.#i=o,this.#c=performance.now(),this.#o=void 0,this.#n)clearTimeout(this.#n);this.#n=void 0,this.#l()}#d(){let t=this.#t.getBoundingClientRect(),o=Math.min(devicePixelRatio||1,2),i=Math.max(1,Math.round(t.width*o)),a=Math.max(1,Math.round(t.height*o));if(this.#t.width!==i||this.#t.height!==a){this.#t.width=i,this.#t.height=a;let s=this.#o;this.#o=void 0,this.#r(s||this.#i?.frames[0])}}#r(t){let o=this.#i;if(!(t&&o))return;let i=this.#a.get(o.asset);if(!i)return;let{width:a,height:s}=this.#t;this.#e.clearRect(0,0,a,s),this.#e.imageSmoothingEnabled=!0,this.#e.imageSmoothingQuality="high",this.#e.drawImage(i,t.x,t.y,t.width,t.height,0,0,a,s),this.#o=t}#l=()=>{let t=this.#i;if(!t)return;let o=t.frames.reduce((u,C)=>u+C.durationMs,0),i=performance.now()-this.#c;if(t.loop)i%=o;else if(i>=o){if(this.#r(t.frames.at(-1)),t.next)this.show(t.next);return}let a=0,s=t.frames[0]?.durationMs||0;while(a<t.frames.length-1&&i>=s)a+=1,s+=t.frames[a]?.durationMs||0;let h=t.frames[a];if(h!==this.#o)this.#r(h);this.#n=setTimeout(this.#l,Math.max(16,s-i))}}var g=new F(e.canvas),N={feedback:e.promptFeedback,submit:e.promptSubmit,text:e.promptText},T={feedback:e.desktopPromptFeedback,submit:e.desktopPromptSubmit,text:e.desktopPromptText},v=new Map,d,r,P=!1,f=!1,c="idle",p="idle",x="Following the active Pi session",A=-1,k,L,m=!1,w=!1;function B(){if(k)clearTimeout(k);k=void 0}function M(t,o){if(P=t,e.connectionDot.classList.toggle("online",t),e.connectionLabel.textContent=o,e.agentState.textContent=t?"Session online":"Reconnecting",e.promptSubmit.disabled=!t,e.desktopPromptSubmit.disabled=!t,e.voice.disabled=!t,e.desktopVoice.disabled=!t,e.desktopDictation.disabled=!t,r)V(r.audio.state)}function l(t,o,i){if(B(),c=i,p=G&&i==="working"?d.defaultAction:t,x=o,!f)g.show(p);e.actionName.textContent=f?`preview · ${e.actionSelect.value}`:b(p),e.actionNote.textContent=f?"Local preview":x}function W(t){let o=t.replace(/\s+/g," ").trim().slice(0,280);if(!o)return;if(L)clearTimeout(L);e.bubble.textContent=o,e.bubble.hidden=!1,L=setTimeout(()=>{e.bubble.hidden=!0},Math.min(12000,Math.max(3500,o.length*55)))}function K(t){if(l("review","Ready","settled"),t)W(t);let o=d.actions.review?.frames.reduce((i,a)=>i+a.durationMs,0)||1000;k=setTimeout(()=>{k=void 0,l(d.defaultAction,"Following the active Pi session","idle")},o)}function z(t){if(t.state==="working"){if(w=!1,c="working",!m)l("running","Pi is working","working")}else if(t.state==="waiting"){let o=t.text?.replace(/\s+/g," ").trim().slice(0,280);if(w=!0,c="waiting",!m)l("waiting",o||"Waiting for your answer","waiting")}else if(t.state==="settled")w=!1,v.clear(),m=!1,K(t.text);else if(t.state==="idle")w=!1,v.clear(),m=!1,l(d.defaultAction,"Following the active Pi session","idle")}function _(){return[...v.values()].includes("ask")}function X(t){if(!(t.toolCallId&&t.toolName)||t.toolName==="pet_show")return;if(v.set(t.toolCallId,t.toolName),w)return;if(_()){if(c="waiting",!m)l("waiting","Waiting for your answer","waiting");return}if(c="working",!m)l("running",`Using ${t.toolName}`,"working")}function Q(t){if(!(t.toolCallId&&v.delete(t.toolCallId)))return;if(w)return;if(t.isError){if(c="failed",!m)l("failed","A tool failed","failed")}else if(_()){if(c="waiting",!m)l("waiting","Waiting for your answer","waiting")}else if(c="working",!m)l("running","Pi is working","working")}function Y(t){if(t.app!=="pi-pet"||t.data?.schemaVersion!==1||t.data.pet!==d.id||t.data.revision<=A)return;let o=t.data;if(!d.actions[o.action])return;if(A=o.revision,o.action===d.defaultAction){if(m=!1,c==="idle")l(d.defaultAction,"Following the active Pi session","idle");else if(c==="working")l("running","Pi is working","working");else if(c==="waiting")l("waiting","Waiting for your answer","waiting");else if(c==="failed")l("failed","A tool failed","failed");return}if(m=!0,B(),p=o.action,x=o.note||"Requested by Pi",!f)g.show(p),e.actionName.textContent=b(p),e.actionNote.textContent=x}function J(t){if(d=t,p=t.defaultAction,e.name.textContent=t.displayName,e.description.textContent=t.description,e.desktopPromptLabel.textContent=`Talk to ${t.displayName}`,e.pointerHint.textContent=`Move nearby. ${t.displayName} is watching.`,e.promptText.placeholder=`Send a thought back through ${t.displayName}…`,y)e.wave.setAttribute("aria-label",`Talk to ${t.displayName}`);document.title=`${t.displayName} · Pi Pet`,e.actionSelect.replaceChildren();for(let o of Object.keys(t.actions).sort()){let i=document.createElement("option");i.value=o,i.textContent=b(o),e.actionSelect.append(i)}e.actionSelect.value=t.defaultAction,e.quickActions.replaceChildren();for(let o of["idle","running","waiting","review","failed","waving","jumping"]){if(!t.actions[o])continue;let i=document.createElement("button");i.type="button",i.textContent=b(o),i.addEventListener("click",()=>I(o)),e.quickActions.append(i)}}function I(t){if(!d.actions[t])return;f=!0,e.actionSelect.value=t,g.show(t),e.actionName.textContent=`preview · ${b(t)}`,e.actionNote.textContent="Local preview"}function Z(){f=!1,l(p,x,c)}function tt(){if(f)return!1;if(c==="idle")return p===d.defaultAction;if(c==="working")return p==="running"||p===d.defaultAction;return c==="settled"&&(p==="review"||p===d.defaultAction)}function H(t,o){if(!tt())return;let i=e.stage.getBoundingClientRect(),a=t-(i.left+i.width/2),s=o-(i.top+i.height/2),h=Math.hypot(a,s),u=Math.max(45,Math.min(i.width,i.height)*0.3);if(h<u){g.show(d.defaultAction);return}let C=(Math.atan2(a,-s)*180/Math.PI+360)%360,q=Math.round(C/22.5)%16*22.5;g.show(`look-${String(q).padStart(3,"0").replace(".5","_5")}`)}function R(){if(!f)g.show(p)}function et(){e.desktopPromptForm.hidden=!1,document.documentElement.classList.add("conversation-open"),e.desktopPromptFeedback.textContent=P?"":"Pi is reconnecting",e.desktopPromptText.focus()}function E(){e.desktopPromptForm.hidden=!0,document.documentElement.classList.remove("conversation-open"),e.desktopPromptFeedback.textContent=""}async function U(t){let o=t.text.value.trim();if(!(o&&P))return;t.submit.disabled=!0,t.feedback.textContent="Sending…";try{if(await r.send(o),t.text.value="",t.feedback.textContent="Delivered",t===T)setTimeout(E,650)}catch(i){t.feedback.textContent=i instanceof Error?i.message:"Prompt failed"}finally{t.submit.disabled=!P}}function S(t,o,i){let a=i.mode||"conversation",s=o==="conversation"?"Voice":"Dictate";if(t.disabled=!P||(i.active===!0||i.busy===!0)&&a!==o,a!==o){t.textContent=s;return}if(i.active){t.textContent=o==="dictation"?"Finish":i.muted?"Muted":"Stop voice";return}t.textContent=i.busy?"Cancel":s}function V(t){let o=t.active===!0;if(S(e.voice,"conversation",t),S(e.desktopVoice,"conversation",t),S(e.desktopDictation,"dictation",t),e.voiceState.textContent=t.detail||(o?t.state||"Voice active":"Voice idle"),t.state==="error"){let i=t.detail||"Voice failed";e.promptFeedback.textContent=i,e.desktopPromptFeedback.textContent=i}}function ot(t){if(typeof t.text!=="string")return;if(e.promptText.value!==t.text)e.promptText.value=t.text;if(e.desktopPromptText.value!==t.text)e.desktopPromptText.value=t.text}function j(t){if(!r)return;r.setDraft(t.text.value)}async function D(t,o,i){try{let a=r.audio.state;if(a.active||a.busy){if(a.mode!==t)return;if(t==="dictation"&&i)r.audio.stop({text:i.text.value,revision:r.draft.revision,selectionStart:i.text.selectionStart,selectionEnd:i.text.selectionEnd});else r.audio.stop()}else await r.audio.start(t)}catch(a){o.textContent=a instanceof Error?a.message:"Voice failed"}}async function it(){let t=await fetch(new URL("catalog.json",location.href),{cache:"no-store"});if(!t.ok)throw Error(`Could not load pet catalog: HTTP ${t.status}`);if(J(await t.json()),await g.load(d),document.documentElement.dataset.petReady="true",!window.GippityRemote)throw Error("GipPity Remote SDK is unavailable");r=window.GippityRemote.connect(),r.on("connection",(o)=>{let i=o.state==="connected";if(i)A=-1,v.clear();M(i,i?"Live":"Reconnecting")}),r.on("activity",z),r.on("app.state",Y),r.on("pi:tool_execution_start",X),r.on("pi:tool_execution_end",Q),r.on("draft",ot),r.on("audio",V),M(!1,"Connecting")}e.actionSelect.addEventListener("change",()=>I(e.actionSelect.value));e.restoreAction.addEventListener("click",Z);e.wave.addEventListener("click",()=>{if(y){if(e.desktopPromptForm.hidden)et();else E();return}g.show("waving"),setTimeout(R,900)});e.promptForm.addEventListener("submit",(t)=>{t.preventDefault(),U(N)});e.desktopPromptForm.addEventListener("submit",(t)=>{t.preventDefault(),U(T)});e.desktopPromptClose.addEventListener("click",E);e.desktopPromptText.addEventListener("keydown",(t)=>{if(t.key==="Escape")t.preventDefault(),E();else if(t.key==="Enter"&&!t.shiftKey)t.preventDefault(),e.desktopPromptForm.requestSubmit()});e.promptText.addEventListener("input",()=>j(N));e.desktopPromptText.addEventListener("input",()=>j(T));e.voice.addEventListener("click",()=>void D("conversation",e.promptFeedback));e.desktopVoice.addEventListener("click",()=>void D("conversation",e.desktopPromptFeedback));e.desktopDictation.addEventListener("click",()=>void D("dictation",e.desktopPromptFeedback,T));if(window.piPetDesktop)window.piPetDesktop.onCursorPosition((t)=>{if(t)H(t.x,t.y);else R()});else e.stage.addEventListener("pointermove",(t)=>H(t.clientX,t.clientY)),e.stage.addEventListener("pointerleave",R);it().catch((t)=>{M(!1,"Unavailable"),e.actionNote.textContent=t instanceof Error?t.message:"Pet failed to start"});
@@ -3,6 +3,7 @@ import { fileURLToPath } from "node:url";
3
3
  import { CONFIG_DIR_NAME, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
4
  import { Container, Text } from "@earendil-works/pi-tui";
5
5
  import { Type } from "typebox";
6
+ import registerPackageChangelog from "../changelog.js";
6
7
  import { type PetRuntime, resolvePetRuntime } from "../src/pet-storage.ts";
7
8
  import { type PetCatalog, type PetState, parseActionName, parseNote } from "../src/protocol/index.ts";
8
9
  import { registerPetDevices } from "./desktop-command.ts";
@@ -98,6 +99,7 @@ function registerPetRuntime(pi: ExtensionAPI, runtime: PetRuntime): boolean {
98
99
  }
99
100
 
100
101
  export default function piPetExtension(pi: ExtensionAPI): void {
102
+ registerPackageChangelog(pi);
101
103
  registerPetDevices(pi);
102
104
  pi.on("session_start", (_event, ctx) => {
103
105
  const resolution = resolvePetRuntime(packageRoot, join(ctx.cwd, CONFIG_DIR_NAME, "pi-pet.json"));
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@howaboua/pi-pet",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Animated companions for GipPity Remote.",
5
+ "license": "MIT",
5
6
  "repository": {
6
7
  "type": "git",
7
8
  "url": "git+https://github.com/IgorWarzocha/howaboua-pi-stuff.git",
@@ -12,6 +13,8 @@
12
13
  "url": "https://github.com/IgorWarzocha/howaboua-pi-stuff/issues"
13
14
  },
14
15
  "type": "module",
16
+ "main": "./extensions/index.ts",
17
+ "exports": "./extensions/index.ts",
15
18
  "keywords": [
16
19
  "pi-package",
17
20
  "pi-extension",
@@ -45,7 +48,6 @@
45
48
  ],
46
49
  "pi": {
47
50
  "extensions": [
48
- "./changelog.ts",
49
51
  "./extensions/index.ts"
50
52
  ]
51
53
  },
@@ -72,11 +74,11 @@
72
74
  "engines": {
73
75
  "node": ">=22"
74
76
  },
75
- "packageManager": "bun@1.3.14",
77
+ "packageManager": "bun@1.4.0",
76
78
  "peerDependencies": {
77
- "@howaboua/pi-gippity-control": ">=0.0.13",
78
- "@earendil-works/pi-coding-agent": "*",
79
- "@earendil-works/pi-tui": "*",
79
+ "@howaboua/pi-gippity-control": ">=0.0.17",
80
+ "@earendil-works/pi-coding-agent": ">=0.84.3",
81
+ "@earendil-works/pi-tui": ">=0.84.3",
80
82
  "typebox": "*"
81
83
  },
82
84
  "peerDependenciesMeta": {
@@ -85,15 +87,15 @@
85
87
  }
86
88
  },
87
89
  "devDependencies": {
88
- "@biomejs/biome": "2.5.3",
89
- "@earendil-works/pi-coding-agent": "0.84.1",
90
- "@earendil-works/pi-tui": "0.84.1",
91
- "@types/node": "^24.10.0",
92
- "electron": "41.0.2",
93
- "knip": "^6.29.0",
94
- "typebox": "1.3.7",
95
- "@typescript/native-preview": "^7.0.0-dev.20260519.1",
96
- "typescript": "^6.0.3"
90
+ "@biomejs/biome": "2.5.10",
91
+ "@earendil-works/pi-coding-agent": "0.85.0",
92
+ "@earendil-works/pi-tui": "0.85.0",
93
+ "@types/node": "^26.2.0",
94
+ "electron": "43.4.1",
95
+ "knip": "^6.32.2",
96
+ "typebox": "1.3.18",
97
+ "@typescript/native-preview": "^7.0.0-dev.20260707.2",
98
+ "typescript": "^7.0.2"
97
99
  },
98
100
  "publishConfig": {
99
101
  "access": "public"