@howaboua/pi-pet 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +92 -0
- package/SECURITY.md +24 -0
- package/authoring/PET-GUIDE.md +65 -0
- package/authoring/hatch/HATCH-GUIDE.md +136 -0
- package/authoring/hatch/LICENSE.txt +201 -0
- package/authoring/hatch/NOTICE.md +5 -0
- package/authoring/hatch/pyproject.toml +8 -0
- package/authoring/hatch/references/animation-rows.md +29 -0
- package/authoring/hatch/references/final-assembly.md +67 -0
- package/authoring/hatch/references/look-directions.md +35 -0
- package/authoring/hatch/references/pet-contract.md +40 -0
- package/authoring/hatch/references/qa-rubric.md +34 -0
- package/authoring/hatch/references/reviewer-protocol.md +72 -0
- package/authoring/hatch/references/standard-assembly.md +21 -0
- package/authoring/hatch/references/visual-generation.md +59 -0
- package/authoring/hatch/scripts/approve_cardinals.py +110 -0
- package/authoring/hatch/scripts/assemble_extended_atlas.py +684 -0
- package/authoring/hatch/scripts/combine_direction_blind_verdicts.py +59 -0
- package/authoring/hatch/scripts/compose_atlas.py +171 -0
- package/authoring/hatch/scripts/compose_cardinal_anchor_strip.py +45 -0
- package/authoring/hatch/scripts/derive_running_left_from_running_right.py +164 -0
- package/authoring/hatch/scripts/despill_chroma_edges.py +312 -0
- package/authoring/hatch/scripts/extract_cardinal_anchors.py +153 -0
- package/authoring/hatch/scripts/extract_strip_frames.py +409 -0
- package/authoring/hatch/scripts/inspect_frames.py +278 -0
- package/authoring/hatch/scripts/make_contact_sheet.py +116 -0
- package/authoring/hatch/scripts/make_direction_blind_qa_sheet.py +179 -0
- package/authoring/hatch/scripts/make_direction_qa_sheet.py +172 -0
- package/authoring/hatch/scripts/measure_direction_continuity.py +201 -0
- package/authoring/hatch/scripts/prepare_pet_run.py +1207 -0
- package/authoring/hatch/scripts/render_animation_previews.py +80 -0
- package/authoring/hatch/scripts/validate_atlas.py +299 -0
- package/authoring/hatch/scripts/validate_direction_blind_verdicts.py +100 -0
- package/authoring/hatch/uv.lock +99 -0
- package/authoring/references/import-existing-pet.md +47 -0
- package/authoring/references/pet-format.md +88 -0
- package/changelog.ts +327 -0
- package/deploy/pi-pet-hyprland.conf +10 -0
- package/desktop/build.mjs +194 -0
- package/desktop/package-lock.json +887 -0
- package/desktop/package.json +16 -0
- package/dist/authoring/prepare.mjs +3 -0
- package/dist/authoring/rebuild.mjs +4 -0
- package/dist/authoring/validate.mjs +2 -0
- package/dist/desktop/main.mjs +5 -0
- package/dist/desktop/preload.cjs +1 -0
- package/dist/web/app.js +1 -0
- package/dist/web/catalog.json +1 -0
- package/dist/web/index.html +100 -0
- package/dist/web/manifest.webmanifest +16 -0
- package/dist/web/pet-icon.svg +6 -0
- package/dist/web/spritesheet.webp +0 -0
- package/dist/web/styles.css +592 -0
- package/docs/architecture.md +41 -0
- package/docs/pi-pet.png +0 -0
- package/docs/research.md +17 -0
- package/extensions/desktop-command.ts +249 -0
- package/extensions/desktop-config.ts +152 -0
- package/extensions/desktop-launcher.ts +380 -0
- package/extensions/gippity.ts +24 -0
- package/extensions/index.ts +110 -0
- package/extensions/remote-app.ts +61 -0
- package/package.json +101 -0
- package/pets/clawa/PET.md +27 -0
- package/pets/clawa/pet.json +7 -0
- package/pets/clawa/spritesheet.webp +0 -0
- package/pets/clawa/validation.json +808 -0
- package/src/desktop/attention.ts +87 -0
- package/src/desktop/bridge.ts +31 -0
- package/src/desktop/config.ts +145 -0
- package/src/desktop/cursor-provider.ts +150 -0
- package/src/desktop/main.ts +459 -0
- package/src/desktop/preload.ts +19 -0
- package/src/pet-image-size.ts +72 -0
- package/src/pet-loader.ts +278 -0
- package/src/pet-storage.ts +240 -0
- package/src/protocol/index.ts +81 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@howaboua/pi-pet-desktop-source",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "node build.mjs",
|
|
7
|
+
"start": "electron dist/app"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"electron": "41.0.2",
|
|
11
|
+
"typescript": "^6.0.3"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=22"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +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)}
|
|
3
|
+
`);
|
|
@@ -0,0 +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
|
|
4
|
+
`);
|
|
@@ -0,0 +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
|
|
2
|
+
`);
|
|
@@ -0,0 +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());
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=require("electron");var i="pi-pet:cursor-position";function n(r){if(r===null)return null;if(!r||typeof r!=="object"||Array.isArray(r))throw Error("Cursor position must be an object.");let o=r;if(Object.keys(o).some((t)=>t!=="x"&&t!=="y"))throw Error("Cursor position has an unknown field.");if(typeof o.x!=="number"||!Number.isFinite(o.x)||Math.abs(o.x)>1e5||typeof o.y!=="number"||!Number.isFinite(o.y)||Math.abs(o.y)>1e5)throw Error("Cursor position is invalid.");return{x:o.x,y:o.y}}var p=Object.freeze({onCursorPosition(r){let o=(t,s)=>{r(n(s))};return e.ipcRenderer.on(i,o),()=>e.ipcRenderer.removeListener(i,o)}});e.contextBridge.exposeInMainWorld("piPetDesktop",p);
|
package/dist/web/app.js
ADDED
|
@@ -0,0 +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"});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schemaVersion":1,"id":"clawa","displayName":"Clawa","description":"A cheerful orange compile-spirit fox with a cream face and belly, lightning tail, and teal codefire paws.","defaultAction":"idle","canvas":{"width":192,"height":208},"actions":{"idle":{"name":"idle","asset":"spritesheet.webp","frames":[{"x":0,"y":0,"width":192,"height":208,"durationMs":280},{"x":192,"y":0,"width":192,"height":208,"durationMs":110},{"x":384,"y":0,"width":192,"height":208,"durationMs":110},{"x":576,"y":0,"width":192,"height":208,"durationMs":140},{"x":768,"y":0,"width":192,"height":208,"durationMs":140},{"x":960,"y":0,"width":192,"height":208,"durationMs":320}],"loop":true},"running-right":{"name":"running-right","asset":"spritesheet.webp","frames":[{"x":0,"y":208,"width":192,"height":208,"durationMs":120},{"x":192,"y":208,"width":192,"height":208,"durationMs":120},{"x":384,"y":208,"width":192,"height":208,"durationMs":120},{"x":576,"y":208,"width":192,"height":208,"durationMs":120},{"x":768,"y":208,"width":192,"height":208,"durationMs":120},{"x":960,"y":208,"width":192,"height":208,"durationMs":120},{"x":1152,"y":208,"width":192,"height":208,"durationMs":120},{"x":1344,"y":208,"width":192,"height":208,"durationMs":220}],"loop":true},"running-left":{"name":"running-left","asset":"spritesheet.webp","frames":[{"x":0,"y":416,"width":192,"height":208,"durationMs":120},{"x":192,"y":416,"width":192,"height":208,"durationMs":120},{"x":384,"y":416,"width":192,"height":208,"durationMs":120},{"x":576,"y":416,"width":192,"height":208,"durationMs":120},{"x":768,"y":416,"width":192,"height":208,"durationMs":120},{"x":960,"y":416,"width":192,"height":208,"durationMs":120},{"x":1152,"y":416,"width":192,"height":208,"durationMs":120},{"x":1344,"y":416,"width":192,"height":208,"durationMs":220}],"loop":true},"waving":{"name":"waving","asset":"spritesheet.webp","frames":[{"x":0,"y":624,"width":192,"height":208,"durationMs":140},{"x":192,"y":624,"width":192,"height":208,"durationMs":140},{"x":384,"y":624,"width":192,"height":208,"durationMs":140},{"x":576,"y":624,"width":192,"height":208,"durationMs":280}],"loop":true},"jumping":{"name":"jumping","asset":"spritesheet.webp","frames":[{"x":0,"y":832,"width":192,"height":208,"durationMs":140},{"x":192,"y":832,"width":192,"height":208,"durationMs":140},{"x":384,"y":832,"width":192,"height":208,"durationMs":140},{"x":576,"y":832,"width":192,"height":208,"durationMs":140},{"x":768,"y":832,"width":192,"height":208,"durationMs":280}],"loop":true},"failed":{"name":"failed","asset":"spritesheet.webp","frames":[{"x":0,"y":1040,"width":192,"height":208,"durationMs":140},{"x":192,"y":1040,"width":192,"height":208,"durationMs":140},{"x":384,"y":1040,"width":192,"height":208,"durationMs":140},{"x":576,"y":1040,"width":192,"height":208,"durationMs":140},{"x":768,"y":1040,"width":192,"height":208,"durationMs":140},{"x":960,"y":1040,"width":192,"height":208,"durationMs":140},{"x":1152,"y":1040,"width":192,"height":208,"durationMs":140},{"x":1344,"y":1040,"width":192,"height":208,"durationMs":240}],"loop":true},"waiting":{"name":"waiting","asset":"spritesheet.webp","frames":[{"x":0,"y":1248,"width":192,"height":208,"durationMs":150},{"x":192,"y":1248,"width":192,"height":208,"durationMs":150},{"x":384,"y":1248,"width":192,"height":208,"durationMs":150},{"x":576,"y":1248,"width":192,"height":208,"durationMs":150},{"x":768,"y":1248,"width":192,"height":208,"durationMs":150},{"x":960,"y":1248,"width":192,"height":208,"durationMs":260}],"loop":true},"running":{"name":"running","asset":"spritesheet.webp","frames":[{"x":0,"y":1456,"width":192,"height":208,"durationMs":120},{"x":192,"y":1456,"width":192,"height":208,"durationMs":120},{"x":384,"y":1456,"width":192,"height":208,"durationMs":120},{"x":576,"y":1456,"width":192,"height":208,"durationMs":120},{"x":768,"y":1456,"width":192,"height":208,"durationMs":120},{"x":960,"y":1456,"width":192,"height":208,"durationMs":220}],"loop":true},"review":{"name":"review","asset":"spritesheet.webp","frames":[{"x":0,"y":1664,"width":192,"height":208,"durationMs":150},{"x":192,"y":1664,"width":192,"height":208,"durationMs":150},{"x":384,"y":1664,"width":192,"height":208,"durationMs":150},{"x":576,"y":1664,"width":192,"height":208,"durationMs":150},{"x":768,"y":1664,"width":192,"height":208,"durationMs":150},{"x":960,"y":1664,"width":192,"height":208,"durationMs":280}],"loop":true}},"aliases":{"active":"running","error":"failed","hello":"waving","settled":"review","success":"jumping","thinking":"running","working":"running"},"directions":{"look-000":{"name":"look-000","asset":"spritesheet.webp","frames":[{"x":0,"y":1872,"width":192,"height":208,"durationMs":1000}],"loop":true},"look-22_5":{"name":"look-22_5","asset":"spritesheet.webp","frames":[{"x":192,"y":1872,"width":192,"height":208,"durationMs":1000}],"loop":true},"look-045":{"name":"look-045","asset":"spritesheet.webp","frames":[{"x":384,"y":1872,"width":192,"height":208,"durationMs":1000}],"loop":true},"look-67_5":{"name":"look-67_5","asset":"spritesheet.webp","frames":[{"x":576,"y":1872,"width":192,"height":208,"durationMs":1000}],"loop":true},"look-090":{"name":"look-090","asset":"spritesheet.webp","frames":[{"x":768,"y":1872,"width":192,"height":208,"durationMs":1000}],"loop":true},"look-112_5":{"name":"look-112_5","asset":"spritesheet.webp","frames":[{"x":960,"y":1872,"width":192,"height":208,"durationMs":1000}],"loop":true},"look-135":{"name":"look-135","asset":"spritesheet.webp","frames":[{"x":1152,"y":1872,"width":192,"height":208,"durationMs":1000}],"loop":true},"look-157_5":{"name":"look-157_5","asset":"spritesheet.webp","frames":[{"x":1344,"y":1872,"width":192,"height":208,"durationMs":1000}],"loop":true},"look-180":{"name":"look-180","asset":"spritesheet.webp","frames":[{"x":0,"y":2080,"width":192,"height":208,"durationMs":1000}],"loop":true},"look-202_5":{"name":"look-202_5","asset":"spritesheet.webp","frames":[{"x":192,"y":2080,"width":192,"height":208,"durationMs":1000}],"loop":true},"look-225":{"name":"look-225","asset":"spritesheet.webp","frames":[{"x":384,"y":2080,"width":192,"height":208,"durationMs":1000}],"loop":true},"look-247_5":{"name":"look-247_5","asset":"spritesheet.webp","frames":[{"x":576,"y":2080,"width":192,"height":208,"durationMs":1000}],"loop":true},"look-270":{"name":"look-270","asset":"spritesheet.webp","frames":[{"x":768,"y":2080,"width":192,"height":208,"durationMs":1000}],"loop":true},"look-292_5":{"name":"look-292_5","asset":"spritesheet.webp","frames":[{"x":960,"y":2080,"width":192,"height":208,"durationMs":1000}],"loop":true},"look-315":{"name":"look-315","asset":"spritesheet.webp","frames":[{"x":1152,"y":2080,"width":192,"height":208,"durationMs":1000}],"loop":true},"look-337_5":{"name":"look-337_5","asset":"spritesheet.webp","frames":[{"x":1344,"y":2080,"width":192,"height":208,"durationMs":1000}],"loop":true}}}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="theme-color" content="#171815" />
|
|
7
|
+
<meta name="description" content="An animated companion for GipPity Remote." />
|
|
8
|
+
<link rel="manifest" href="./manifest.webmanifest" />
|
|
9
|
+
<link rel="icon" href="./pet-icon.svg" type="image/svg+xml" />
|
|
10
|
+
<link rel="stylesheet" href="./styles.css" />
|
|
11
|
+
<title>Pi Pet</title>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<main class="shell">
|
|
15
|
+
<header class="masthead">
|
|
16
|
+
<a class="wordmark" href="./" aria-label="Pi Pet home">
|
|
17
|
+
<span class="wordmark-mark">π</span>
|
|
18
|
+
<span>pet</span>
|
|
19
|
+
</a>
|
|
20
|
+
<div class="connection" aria-live="polite">
|
|
21
|
+
<span id="connection-dot" class="connection-dot"></span>
|
|
22
|
+
<span id="connection-label">Connecting</span>
|
|
23
|
+
</div>
|
|
24
|
+
</header>
|
|
25
|
+
|
|
26
|
+
<section class="companion-layout" aria-label="Pi Pet companion">
|
|
27
|
+
<div id="stage" class="stage">
|
|
28
|
+
<div id="bubble" class="bubble" hidden></div>
|
|
29
|
+
<canvas id="pet-canvas" width="384" height="416" aria-label="Animated Pi Pet"></canvas>
|
|
30
|
+
<button id="wave-button" class="pet-hit-area" type="button" aria-label="Wave to the pet"></button>
|
|
31
|
+
<form id="desktop-prompt-form" class="desktop-prompt" hidden>
|
|
32
|
+
<div class="desktop-prompt-heading">
|
|
33
|
+
<label id="desktop-prompt-label" for="desktop-prompt-text">Talk to your pet</label>
|
|
34
|
+
<button id="desktop-prompt-close" type="button" aria-label="Close prompt">×</button>
|
|
35
|
+
</div>
|
|
36
|
+
<textarea id="desktop-prompt-text" maxlength="8000" rows="2" placeholder="What should we do?"></textarea>
|
|
37
|
+
<div class="desktop-prompt-footer">
|
|
38
|
+
<span id="desktop-prompt-feedback" aria-live="polite"></span>
|
|
39
|
+
<div class="prompt-buttons">
|
|
40
|
+
<button id="desktop-voice-button" class="voice-button" type="button">Voice</button>
|
|
41
|
+
<button id="desktop-dictation-button" class="voice-button" type="button">Dictate</button>
|
|
42
|
+
<button id="desktop-prompt-submit" type="submit">Send</button>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</form>
|
|
46
|
+
<p id="pointer-hint" class="pointer-hint">Move nearby. Your pet is watching.</p>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<aside class="console">
|
|
50
|
+
<div class="identity">
|
|
51
|
+
<h1 id="pet-name">Pi Pet</h1>
|
|
52
|
+
<p id="pet-description" class="description">Connecting through GipPity Remote.</p>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<section aria-labelledby="now-title">
|
|
56
|
+
<div class="section-heading">
|
|
57
|
+
<h2 id="now-title">Now</h2>
|
|
58
|
+
<span id="voice-state" class="quiet">Voice idle</span>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="current-state">
|
|
61
|
+
<span id="action-name" class="action-name">idle</span>
|
|
62
|
+
<span id="action-note" class="action-note">Following the active Pi session</span>
|
|
63
|
+
</div>
|
|
64
|
+
</section>
|
|
65
|
+
|
|
66
|
+
<section aria-labelledby="preview-title">
|
|
67
|
+
<div class="section-heading">
|
|
68
|
+
<h2 id="preview-title">Preview</h2>
|
|
69
|
+
<button id="restore-action" class="text-button" type="button">Follow Pi</button>
|
|
70
|
+
</div>
|
|
71
|
+
<div id="quick-actions" class="quick-actions"></div>
|
|
72
|
+
<label class="select-label" for="action-select">Every action</label>
|
|
73
|
+
<select id="action-select"><option>Loading actions</option></select>
|
|
74
|
+
</section>
|
|
75
|
+
|
|
76
|
+
<section class="prompt-section" aria-labelledby="prompt-title">
|
|
77
|
+
<div class="section-heading">
|
|
78
|
+
<h2 id="prompt-title">Talk to Pi</h2>
|
|
79
|
+
<span id="agent-state" class="quiet">Connecting</span>
|
|
80
|
+
</div>
|
|
81
|
+
<form id="prompt-form">
|
|
82
|
+
<label class="sr-only" for="prompt-text">Prompt for the connected Pi session</label>
|
|
83
|
+
<textarea id="prompt-text" maxlength="8000" rows="3" placeholder="Send a thought back through your pet…"></textarea>
|
|
84
|
+
<div class="prompt-footer">
|
|
85
|
+
<span id="prompt-feedback" class="prompt-feedback" aria-live="polite"></span>
|
|
86
|
+
<div class="prompt-buttons">
|
|
87
|
+
<button id="voice-button" class="voice-button" type="button">Voice</button>
|
|
88
|
+
<button id="prompt-submit" class="send-button" type="submit">Send</button>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</form>
|
|
92
|
+
</section>
|
|
93
|
+
</aside>
|
|
94
|
+
</section>
|
|
95
|
+
</main>
|
|
96
|
+
|
|
97
|
+
<script src="/_gippity/client.js"></script>
|
|
98
|
+
<script type="module" src="./app.js"></script>
|
|
99
|
+
</body>
|
|
100
|
+
</html>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Pi Pet",
|
|
3
|
+
"short_name": "Pi Pet",
|
|
4
|
+
"description": "An animated companion for GipPity Remote.",
|
|
5
|
+
"start_url": "./",
|
|
6
|
+
"display": "standalone",
|
|
7
|
+
"background_color": "#171815",
|
|
8
|
+
"theme_color": "#171815",
|
|
9
|
+
"icons": [
|
|
10
|
+
{
|
|
11
|
+
"src": "./pet-icon.svg",
|
|
12
|
+
"sizes": "any",
|
|
13
|
+
"type": "image/svg+xml"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
|
|
2
|
+
<title>Pi Pet</title>
|
|
3
|
+
<rect width="128" height="128" rx="30" fill="#20211d"/>
|
|
4
|
+
<circle cx="64" cy="64" r="43" fill="#ff941f"/>
|
|
5
|
+
<path d="M42 49h48M50 49v35M78 49v35M45 84h42" fill="none" stroke="#20211d" stroke-width="10" stroke-linecap="round"/>
|
|
6
|
+
</svg>
|
|
Binary file
|