@bitefoundry/retrieviewer 0.0.0-stage → 0.0.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.
Files changed (292) hide show
  1. package/ASSET-PROVENANCE.md +43 -0
  2. package/ASSETS-LICENSE.md +58 -0
  3. package/ATTRIBUTION.md +54 -0
  4. package/EMBEDDING.md +96 -0
  5. package/INSTALLING.md +107 -0
  6. package/LICENSE +16 -0
  7. package/LICENSE-CODE +21 -0
  8. package/PLUGIN.md +110 -0
  9. package/PUPPY-MEMORY.md +57 -0
  10. package/PUPPY-Q5-COMPARISON.md +73 -0
  11. package/README.md +61 -2
  12. package/RELEASING.md +61 -0
  13. package/THIRD-PARTY-NOTICES.md +17 -0
  14. package/TRADEMARKS.md +21 -0
  15. package/braille.mjs +45 -0
  16. package/examples/bridge.py +62 -0
  17. package/examples/embed.mjs +15 -0
  18. package/examples/live.mjs +40 -0
  19. package/examples/plugin.mjs +21 -0
  20. package/integrations/opencode/runtime.mjs +195 -0
  21. package/integrations/opencode/tui.tsx +59 -0
  22. package/locomotion-core.mjs +21 -0
  23. package/locomotion-puppy.animal +0 -0
  24. package/package.json +125 -4
  25. package/pet-q5-manifest.json +118 -0
  26. package/puppy-clean.mjs +51 -0
  27. package/puppy-companion-interactions-v3-q5.bin.gz +0 -0
  28. package/puppy-companion-interactions-v3.json +1 -0
  29. package/puppy-companion-life.mjs +182 -0
  30. package/puppy-companion-objects.mjs +30 -0
  31. package/puppy-companion-sprites.mjs +14 -0
  32. package/puppy-continuous.mjs +57 -0
  33. package/puppy-face.mjs +46 -0
  34. package/puppy-frame-predictive.mjs +147 -0
  35. package/puppy-frame-store.mjs +108 -0
  36. package/puppy-mouth-anchor.json +1 -0
  37. package/puppy-mouth-anchor.mjs +33 -0
  38. package/puppy-paper-actions-v9-q5.bin.gz +0 -0
  39. package/puppy-paper-actions-v9.json +1 -0
  40. package/puppy-paper-actions.mjs +48 -0
  41. package/puppy-paper-approach-q5.bin.gz +0 -0
  42. package/puppy-paper-approach.json +1 -0
  43. package/puppy-paper-area.mjs +15 -0
  44. package/puppy-paper-arrival-seat-q5.bin.gz +0 -0
  45. package/puppy-paper-arrival-seat.json +1 -0
  46. package/puppy-paper-behaviors-q5.bin.gz +0 -0
  47. package/puppy-paper-behaviors.json +1 -0
  48. package/puppy-paper-behaviors.mjs +8 -0
  49. package/puppy-paper-contact.mjs +103 -0
  50. package/puppy-paper-director.mjs +21 -0
  51. package/puppy-paper-paw-transport.mjs +76 -0
  52. package/puppy-paper-rear-gallop-q5.bin.gz +0 -0
  53. package/puppy-paper-rear-gallop.json +1 -0
  54. package/puppy-paper-seat-q5.bin.gz +0 -0
  55. package/puppy-paper-seat.json +14 -0
  56. package/puppy-paper-seat.mjs +13 -0
  57. package/puppy-paper-trick-timing.mjs +58 -0
  58. package/puppy-paper-world.mjs +108 -0
  59. package/puppy-paper.mjs +271 -0
  60. package/puppy-stable-meta.json +1606 -0
  61. package/puppy-stable.mjs +72 -0
  62. package/puppy-stride-meta.json +343 -0
  63. package/puppy-stride-tweens-q5.bin.gz +0 -0
  64. package/puppy-zoomies.mjs +27 -0
  65. package/puppy-zoomies30.mjs +12 -0
  66. package/retrieviewer-cli.mjs +81 -0
  67. package/retrieviewer-config.mjs +49 -0
  68. package/retrieviewer-install.mjs +157 -0
  69. package/retrieviewer-plugin.d.mts +44 -0
  70. package/retrieviewer-plugin.mjs +97 -0
  71. package/retrieviewer-stdio.mjs +38 -0
  72. package/retrieviewer.d.mts +85 -0
  73. package/retrieviewer.mjs +142 -0
  74. package/vendor/procedural-animals/LICENSE +21 -0
  75. package/vendor/procedural-animals/package.json +40 -0
  76. package/vendor/procedural-animals/src/animal.js +146 -0
  77. package/vendor/procedural-animals/src/bake.js +51 -0
  78. package/vendor/procedural-animals/src/build.js +81 -0
  79. package/vendor/procedural-animals/src/core/build/coatKit.js +121 -0
  80. package/vendor/procedural-animals/src/core/build/featherCards.js +189 -0
  81. package/vendor/procedural-animals/src/core/build/finEdges.js +85 -0
  82. package/vendor/procedural-animals/src/core/build/furSlope.js +115 -0
  83. package/vendor/procedural-animals/src/core/build/mesh.js +67 -0
  84. package/vendor/procedural-animals/src/core/build/pipeline.js +272 -0
  85. package/vendor/procedural-animals/src/core/build/seams.js +183 -0
  86. package/vendor/procedural-animals/src/core/build/warp.js +112 -0
  87. package/vendor/procedural-animals/src/core/build/weights.js +393 -0
  88. package/vendor/procedural-animals/src/core/math/vec.js +81 -0
  89. package/vendor/procedural-animals/src/core/motion/actions.js +800 -0
  90. package/vendor/procedural-animals/src/core/motion/bird.js +3063 -0
  91. package/vendor/procedural-animals/src/core/motion/birdActions.js +1059 -0
  92. package/vendor/procedural-animals/src/core/motion/common.js +158 -0
  93. package/vendor/procedural-animals/src/core/motion/index.js +22 -0
  94. package/vendor/procedural-animals/src/core/motion/quadruped.js +2875 -0
  95. package/vendor/procedural-animals/src/core/motion/snake.js +1594 -0
  96. package/vendor/procedural-animals/src/core/motion/spider.js +1810 -0
  97. package/vendor/procedural-animals/src/core/motion/swimmer.js +1047 -0
  98. package/vendor/procedural-animals/src/core/motion/swimmerActions.js +613 -0
  99. package/vendor/procedural-animals/src/core/render/animalObject.js +262 -0
  100. package/vendor/procedural-animals/src/core/render/coatMaterial.js +1301 -0
  101. package/vendor/procedural-animals/src/core/render/dqs.js +159 -0
  102. package/vendor/procedural-animals/src/core/render/eyes.js +676 -0
  103. package/vendor/procedural-animals/src/core/render/membranes.js +77 -0
  104. package/vendor/procedural-animals/src/core/render/skeleton.js +41 -0
  105. package/vendor/procedural-animals/src/core/rig/bird.js +327 -0
  106. package/vendor/procedural-animals/src/core/rig/quadruped.js +76 -0
  107. package/vendor/procedural-animals/src/core/rig/rig.js +79 -0
  108. package/vendor/procedural-animals/src/core/rig/snake.js +76 -0
  109. package/vendor/procedural-animals/src/core/rig/spider.js +110 -0
  110. package/vendor/procedural-animals/src/core/rig/swimmer.js +181 -0
  111. package/vendor/procedural-animals/src/core/sdf/eyeSocket.js +53 -0
  112. package/vendor/procedural-animals/src/core/sdf/mesher.js +238 -0
  113. package/vendor/procedural-animals/src/core/sdf/sdf.js +263 -0
  114. package/vendor/procedural-animals/src/index.d.ts +98 -0
  115. package/vendor/procedural-animals/src/index.js +29 -0
  116. package/vendor/procedural-animals/src/registry.js +48 -0
  117. package/vendor/procedural-animals/src/species/bear/behaviour.js +463 -0
  118. package/vendor/procedural-animals/src/species/bear/claws.js +147 -0
  119. package/vendor/procedural-animals/src/species/bear/coat.js +726 -0
  120. package/vendor/procedural-animals/src/species/bear/index.js +139 -0
  121. package/vendor/procedural-animals/src/species/bear/motion.js +112 -0
  122. package/vendor/procedural-animals/src/species/bear/regions.js +78 -0
  123. package/vendor/procedural-animals/src/species/bear/rig.js +112 -0
  124. package/vendor/procedural-animals/src/species/bear/sculpt.js +272 -0
  125. package/vendor/procedural-animals/src/species/boar/behaviour.js +335 -0
  126. package/vendor/procedural-animals/src/species/boar/coat.js +372 -0
  127. package/vendor/procedural-animals/src/species/boar/index.js +110 -0
  128. package/vendor/procedural-animals/src/species/boar/motion.js +85 -0
  129. package/vendor/procedural-animals/src/species/boar/regions.js +88 -0
  130. package/vendor/procedural-animals/src/species/boar/rig.js +111 -0
  131. package/vendor/procedural-animals/src/species/boar/sculpt.js +285 -0
  132. package/vendor/procedural-animals/src/species/cat/behaviour.js +559 -0
  133. package/vendor/procedural-animals/src/species/cat/coat.js +841 -0
  134. package/vendor/procedural-animals/src/species/cat/index.js +211 -0
  135. package/vendor/procedural-animals/src/species/cat/motion.js +101 -0
  136. package/vendor/procedural-animals/src/species/cat/regions.js +82 -0
  137. package/vendor/procedural-animals/src/species/cat/rig.js +100 -0
  138. package/vendor/procedural-animals/src/species/cat/sculpt.js +266 -0
  139. package/vendor/procedural-animals/src/species/cat/whiskers.js +110 -0
  140. package/vendor/procedural-animals/src/species/cheetah/coat.js +427 -0
  141. package/vendor/procedural-animals/src/species/cheetah/index.js +51 -0
  142. package/vendor/procedural-animals/src/species/cheetah/joints.js +3 -0
  143. package/vendor/procedural-animals/src/species/cheetah/motion.js +77 -0
  144. package/vendor/procedural-animals/src/species/cheetah/regions.js +95 -0
  145. package/vendor/procedural-animals/src/species/cheetah/rig.js +66 -0
  146. package/vendor/procedural-animals/src/species/cheetah/sculpt.js +158 -0
  147. package/vendor/procedural-animals/src/species/chicken/coat.js +313 -0
  148. package/vendor/procedural-animals/src/species/chicken/coverts.js +320 -0
  149. package/vendor/procedural-animals/src/species/chicken/index.js +110 -0
  150. package/vendor/procedural-animals/src/species/chicken/motion.js +177 -0
  151. package/vendor/procedural-animals/src/species/chicken/regions.js +66 -0
  152. package/vendor/procedural-animals/src/species/chicken/rig.js +144 -0
  153. package/vendor/procedural-animals/src/species/chicken/sculpt.js +389 -0
  154. package/vendor/procedural-animals/src/species/chicken/wingLook.js +66 -0
  155. package/vendor/procedural-animals/src/species/chicken/wingStack.js +124 -0
  156. package/vendor/procedural-animals/src/species/cow/behaviour.js +521 -0
  157. package/vendor/procedural-animals/src/species/cow/coat.js +465 -0
  158. package/vendor/procedural-animals/src/species/cow/index.js +118 -0
  159. package/vendor/procedural-animals/src/species/cow/motion.js +81 -0
  160. package/vendor/procedural-animals/src/species/cow/regions.js +98 -0
  161. package/vendor/procedural-animals/src/species/cow/rig.js +116 -0
  162. package/vendor/procedural-animals/src/species/cow/sculpt.js +339 -0
  163. package/vendor/procedural-animals/src/species/crow/coat.js +145 -0
  164. package/vendor/procedural-animals/src/species/crow/index.js +70 -0
  165. package/vendor/procedural-animals/src/species/crow/joints.js +3 -0
  166. package/vendor/procedural-animals/src/species/crow/motion.js +101 -0
  167. package/vendor/procedural-animals/src/species/crow/regions.js +50 -0
  168. package/vendor/procedural-animals/src/species/crow/rig.js +83 -0
  169. package/vendor/procedural-animals/src/species/crow/sculpt.js +118 -0
  170. package/vendor/procedural-animals/src/species/deer/behaviour.js +513 -0
  171. package/vendor/procedural-animals/src/species/deer/coat.js +391 -0
  172. package/vendor/procedural-animals/src/species/deer/index.js +106 -0
  173. package/vendor/procedural-animals/src/species/deer/motion.js +97 -0
  174. package/vendor/procedural-animals/src/species/deer/regions.js +60 -0
  175. package/vendor/procedural-animals/src/species/deer/rig.js +124 -0
  176. package/vendor/procedural-animals/src/species/deer/sculpt.js +297 -0
  177. package/vendor/procedural-animals/src/species/dog/behaviour.js +521 -0
  178. package/vendor/procedural-animals/src/species/dog/coat.js +495 -0
  179. package/vendor/procedural-animals/src/species/dog/index.js +144 -0
  180. package/vendor/procedural-animals/src/species/dog/motion.js +118 -0
  181. package/vendor/procedural-animals/src/species/dog/regions.js +61 -0
  182. package/vendor/procedural-animals/src/species/dog/rig.js +129 -0
  183. package/vendor/procedural-animals/src/species/dog/sculpt.js +246 -0
  184. package/vendor/procedural-animals/src/species/eagle/coat.js +250 -0
  185. package/vendor/procedural-animals/src/species/eagle/index.js +76 -0
  186. package/vendor/procedural-animals/src/species/eagle/joints.js +3 -0
  187. package/vendor/procedural-animals/src/species/eagle/motion.js +137 -0
  188. package/vendor/procedural-animals/src/species/eagle/regions.js +57 -0
  189. package/vendor/procedural-animals/src/species/eagle/rig.js +107 -0
  190. package/vendor/procedural-animals/src/species/eagle/sculpt.js +137 -0
  191. package/vendor/procedural-animals/src/species/fish/coat.js +271 -0
  192. package/vendor/procedural-animals/src/species/fish/index.js +88 -0
  193. package/vendor/procedural-animals/src/species/fish/motion.js +92 -0
  194. package/vendor/procedural-animals/src/species/fish/regions.js +27 -0
  195. package/vendor/procedural-animals/src/species/fish/rig.js +143 -0
  196. package/vendor/procedural-animals/src/species/fish/sculpt.js +214 -0
  197. package/vendor/procedural-animals/src/species/fish/variants.js +126 -0
  198. package/vendor/procedural-animals/src/species/fox/behaviour.js +314 -0
  199. package/vendor/procedural-animals/src/species/fox/coat.js +444 -0
  200. package/vendor/procedural-animals/src/species/fox/index.js +120 -0
  201. package/vendor/procedural-animals/src/species/fox/motion.js +81 -0
  202. package/vendor/procedural-animals/src/species/fox/regions.js +59 -0
  203. package/vendor/procedural-animals/src/species/fox/rig.js +91 -0
  204. package/vendor/procedural-animals/src/species/fox/sculpt.js +202 -0
  205. package/vendor/procedural-animals/src/species/frog/behaviour.js +590 -0
  206. package/vendor/procedural-animals/src/species/frog/coat.js +305 -0
  207. package/vendor/procedural-animals/src/species/frog/index.js +97 -0
  208. package/vendor/procedural-animals/src/species/frog/motion.js +69 -0
  209. package/vendor/procedural-animals/src/species/frog/regions.js +46 -0
  210. package/vendor/procedural-animals/src/species/frog/rig.js +156 -0
  211. package/vendor/procedural-animals/src/species/frog/sculpt.js +240 -0
  212. package/vendor/procedural-animals/src/species/goat/behaviour.js +509 -0
  213. package/vendor/procedural-animals/src/species/goat/coat.js +590 -0
  214. package/vendor/procedural-animals/src/species/goat/index.js +149 -0
  215. package/vendor/procedural-animals/src/species/goat/motion.js +95 -0
  216. package/vendor/procedural-animals/src/species/goat/regions.js +98 -0
  217. package/vendor/procedural-animals/src/species/goat/rig.js +181 -0
  218. package/vendor/procedural-animals/src/species/goat/sculpt.js +547 -0
  219. package/vendor/procedural-animals/src/species/horse/coat.js +361 -0
  220. package/vendor/procedural-animals/src/species/horse/coatSeam.js +122 -0
  221. package/vendor/procedural-animals/src/species/horse/index.js +80 -0
  222. package/vendor/procedural-animals/src/species/horse/motion.js +245 -0
  223. package/vendor/procedural-animals/src/species/horse/regions.js +60 -0
  224. package/vendor/procedural-animals/src/species/horse/rig.js +117 -0
  225. package/vendor/procedural-animals/src/species/horse/sculpt.js +316 -0
  226. package/vendor/procedural-animals/src/species/horse/seamWeights.js +272 -0
  227. package/vendor/procedural-animals/src/species/lion/behaviour.js +509 -0
  228. package/vendor/procedural-animals/src/species/lion/coat.js +1016 -0
  229. package/vendor/procedural-animals/src/species/lion/index.js +111 -0
  230. package/vendor/procedural-animals/src/species/lion/motion.js +87 -0
  231. package/vendor/procedural-animals/src/species/lion/regions.js +106 -0
  232. package/vendor/procedural-animals/src/species/lion/rig.js +110 -0
  233. package/vendor/procedural-animals/src/species/lion/sculpt.js +384 -0
  234. package/vendor/procedural-animals/src/species/lion/whiskers.js +132 -0
  235. package/vendor/procedural-animals/src/species/pig/behaviour.js +575 -0
  236. package/vendor/procedural-animals/src/species/pig/coat.js +730 -0
  237. package/vendor/procedural-animals/src/species/pig/ears.js +242 -0
  238. package/vendor/procedural-animals/src/species/pig/index.js +161 -0
  239. package/vendor/procedural-animals/src/species/pig/motion.js +105 -0
  240. package/vendor/procedural-animals/src/species/pig/regions.js +100 -0
  241. package/vendor/procedural-animals/src/species/pig/rig.js +152 -0
  242. package/vendor/procedural-animals/src/species/pig/sculpt.js +382 -0
  243. package/vendor/procedural-animals/src/species/rabbit/behaviour.js +605 -0
  244. package/vendor/procedural-animals/src/species/rabbit/coat.js +285 -0
  245. package/vendor/procedural-animals/src/species/rabbit/index.js +85 -0
  246. package/vendor/procedural-animals/src/species/rabbit/motion.js +85 -0
  247. package/vendor/procedural-animals/src/species/rabbit/regions.js +48 -0
  248. package/vendor/procedural-animals/src/species/rabbit/rig.js +99 -0
  249. package/vendor/procedural-animals/src/species/rabbit/sculpt.js +148 -0
  250. package/vendor/procedural-animals/src/species/rat/behaviour.js +539 -0
  251. package/vendor/procedural-animals/src/species/rat/coat.js +341 -0
  252. package/vendor/procedural-animals/src/species/rat/index.js +111 -0
  253. package/vendor/procedural-animals/src/species/rat/motion.js +90 -0
  254. package/vendor/procedural-animals/src/species/rat/regions.js +92 -0
  255. package/vendor/procedural-animals/src/species/rat/rig.js +112 -0
  256. package/vendor/procedural-animals/src/species/rat/sculpt.js +163 -0
  257. package/vendor/procedural-animals/src/species/rat/whiskers.js +119 -0
  258. package/vendor/procedural-animals/src/species/shark/coat.js +383 -0
  259. package/vendor/procedural-animals/src/species/shark/index.js +104 -0
  260. package/vendor/procedural-animals/src/species/shark/motion.js +69 -0
  261. package/vendor/procedural-animals/src/species/shark/regions.js +34 -0
  262. package/vendor/procedural-animals/src/species/shark/rig.js +194 -0
  263. package/vendor/procedural-animals/src/species/shark/sculpt.js +343 -0
  264. package/vendor/procedural-animals/src/species/shark/variants.js +105 -0
  265. package/vendor/procedural-animals/src/species/sheep/behaviour.js +522 -0
  266. package/vendor/procedural-animals/src/species/sheep/coat.js +529 -0
  267. package/vendor/procedural-animals/src/species/sheep/index.js +152 -0
  268. package/vendor/procedural-animals/src/species/sheep/motion.js +86 -0
  269. package/vendor/procedural-animals/src/species/sheep/neck.js +41 -0
  270. package/vendor/procedural-animals/src/species/sheep/regions.js +62 -0
  271. package/vendor/procedural-animals/src/species/sheep/rig.js +163 -0
  272. package/vendor/procedural-animals/src/species/sheep/sculpt.js +542 -0
  273. package/vendor/procedural-animals/src/species/snake/coat.js +315 -0
  274. package/vendor/procedural-animals/src/species/snake/index.js +73 -0
  275. package/vendor/procedural-animals/src/species/snake/motion.js +51 -0
  276. package/vendor/procedural-animals/src/species/snake/regions.js +51 -0
  277. package/vendor/procedural-animals/src/species/snake/rig.js +108 -0
  278. package/vendor/procedural-animals/src/species/snake/sculpt.js +122 -0
  279. package/vendor/procedural-animals/src/species/spider/coat.js +279 -0
  280. package/vendor/procedural-animals/src/species/spider/index.js +78 -0
  281. package/vendor/procedural-animals/src/species/spider/motion.js +72 -0
  282. package/vendor/procedural-animals/src/species/spider/regions.js +87 -0
  283. package/vendor/procedural-animals/src/species/spider/rig.js +118 -0
  284. package/vendor/procedural-animals/src/species/spider/sculpt.js +182 -0
  285. package/vendor/procedural-animals/src/species/wolf/behaviour.js +160 -0
  286. package/vendor/procedural-animals/src/species/wolf/coat.js +749 -0
  287. package/vendor/procedural-animals/src/species/wolf/index.js +107 -0
  288. package/vendor/procedural-animals/src/species/wolf/motion.js +80 -0
  289. package/vendor/procedural-animals/src/species/wolf/regions.js +57 -0
  290. package/vendor/procedural-animals/src/species/wolf/rig.js +87 -0
  291. package/vendor/procedural-animals/src/species/wolf/sculpt.js +339 -0
  292. package/vendor/procedural-animals/src/worker.js +29 -0
@@ -0,0 +1,157 @@
1
+ // Safe, local native-plugin registration. Basic MCP support is NOT a renderer.
2
+ import {homedir} from 'node:os';
3
+ import {delimiter,join,resolve,dirname,parse as parsePath} from 'node:path';
4
+ import {readFile,lstat,mkdir,open,rename,unlink,access} from 'node:fs/promises';
5
+ import {execFile} from 'node:child_process';
6
+ import {promisify} from 'node:util';
7
+ import {fileURLToPath,pathToFileURL} from 'node:url';
8
+ import {randomUUID} from 'node:crypto';
9
+ import {parse,modify,applyEdits} from 'jsonc-parser';
10
+ import {getConfigPaths,readSettings,withSettingsLock,writeAtomicJson,setAgentEnabled} from './retrieviewer-config.mjs';
11
+
12
+ export const AGENTS=Object.freeze([
13
+ ['codex','Codex',['codex']],['claude','Claude Code',['claude']],['cursor-agent','Cursor Agent',['cursor-agent']],
14
+ ['grok','Grok',['grok']],['antigravity','Antigravity',['agy','antigravity']],['opencode','OpenCode',['opencode']],
15
+ ['pi','Pi',['pi']],['gemini','Gemini CLI',['gemini']],['copilot','GitHub Copilot CLI',['copilot']],
16
+ ['aider','Aider',['aider']],['goose','Goose',['goose']],['qwen','Qwen Code',['qwen']],
17
+ ['amp','Amp',['amp']],['droid','Droid',['droid']],['crush','Crush',['crush']],
18
+ ].map(([id,label,commands])=>Object.freeze({id,label,commands:Object.freeze(commands)})));
19
+ const run=promisify(execFile),PLUGIN_URL=new URL('./integrations/opencode/',import.meta.url).href;
20
+ const ADAPTER_FILE=fileURLToPath(new URL('./integrations/opencode/tui.tsx',import.meta.url));
21
+ const definitions=new Map(AGENTS.map(a=>[a.id,a]));
22
+ const definition=id=>{const a=definitions.get(id);if(!a)throw Error('Unknown agent: '+id);return a;};
23
+ const processAlive=pid=>{if(!Number.isInteger(pid)||pid<=0||pid>0x7fffffff)return false;try{process.kill(pid,0);return true;}catch{return false;}};
24
+ const exists=async path=>{try{await access(path);return true;}catch(e){if(e.code==='ENOENT')return false;throw e;}};
25
+ async function safePath(path){
26
+ for(let p=resolve(path);;p=dirname(p)){
27
+ try{if((await lstat(p)).isSymbolicLink())throw Error('Refusing symlink/junction: '+p);}catch(e){if(e.code!=='ENOENT')throw e;}
28
+ if(p===parsePath(p).root)break;
29
+ }
30
+ }
31
+ async function textFile(path){await safePath(path);try{const s=await readFile(path,'utf8');if(s.length>1024*1024)throw Error('Config exceeds 1 MiB: '+path);return s;}catch(e){if(e.code==='ENOENT')return null;throw e;}}
32
+ function documentOf(text){
33
+ if(text===null)return {};
34
+ const errors=[],data=parse(text,errors,{allowTrailingComma:true,disallowComments:false});
35
+ if(errors.length||!data||typeof data!=='object'||Array.isArray(data))throw Error('Malformed/non-object agent JSONC configuration; refusing to overwrite');
36
+ if(data.plugins!==undefined&&!Array.isArray(data.plugins))throw Error('Agent plugins must be an array; refusing to overwrite');
37
+ return data;
38
+ }
39
+ async function atomicText(path,text){
40
+ await safePath(path);await mkdir(dirname(path),{recursive:true});const temp=path+'.retrieviewer-'+randomUUID()+'.tmp';let owned=false;
41
+ try{const f=await open(temp,'wx',0o600);owned=true;try{await f.writeFile(text);await f.sync();}finally{await f.close();}await rename(temp,path);owned=false;}
42
+ finally{if(owned)await unlink(temp).catch(()=>{});}
43
+ }
44
+
45
+ export async function probeCommand(command,{env=process.env}={}){
46
+ const extensions=process.platform==='win32'?['.exe','.ps1','.cmd','']:[''];
47
+ for(const directory of (env.PATH??env.Path??'').split(delimiter).filter(Boolean))for(const ext of extensions){
48
+ const executable=join(directory,command+ext);if(!await exists(executable))continue;
49
+ // Never interpolate a command in a shell string. .cmd needs a shell, so prefer
50
+ // npm's equivalent PowerShell shim; unsupported shims are detected, not executed.
51
+ if(ext==='.cmd')continue;
52
+ try{
53
+ const binary=ext==='.ps1'?'pwsh.exe':executable;
54
+ const args=ext==='.ps1'?['-NoProfile','-NonInteractive','-File',executable,'--version']:['--version'];
55
+ // npm's PowerShell shims treat an open pipe as pipeline input. Close stdin so
56
+ // --version cannot hang waiting for EOF (spawnSync implicitly did this).
57
+ let pending=run(binary,args,{env,timeout:4000,maxBuffer:8192,windowsHide:true});pending.child?.stdin?.end();
58
+ let output;
59
+ try{output=await pending;}catch(error){
60
+ if(ext!=='.ps1'||error.code!=='ENOENT')throw error;
61
+ pending=run('powershell.exe',args,{env,timeout:4000,maxBuffer:8192,windowsHide:true});pending.child?.stdin?.end();output=await pending;
62
+ }
63
+ const {stdout,stderr}=output;
64
+ return {executable,version:(stdout+' '+stderr).match(/\d+\.\d+\.\d+/)?.[0]??'unknown'};
65
+ }catch{return {executable,version:'unknown'};}
66
+ }
67
+ return null;
68
+ }
69
+
70
+ export function createInstaller({home=homedir(),env=process.env,stateDir=env.RETRIEVIEWER_HOME??join(home,'.config','retrieviewer'),probe,beforeCommit}={}){
71
+ const options={home,env,stateDir},paths=getConfigPaths(options),probeCache=new Map();
72
+ const configPath=join(env.XDG_CONFIG_HOME??join(home,'.config'),'opencode','cli.json');
73
+ const probeOne=command=>{if(!probeCache.has(command))probeCache.set(command,Promise.resolve().then(()=>probe?probe(command):probeCommand(command,{env})));return probeCache.get(command);};
74
+ async function executableFor(agent){for(const cmd of agent.commands){const found=await probeOne(cmd);if(found)return found;}return null;}
75
+ async function inspect(id){
76
+ const def=definition(id),found=await executableFor(def),state=await readSettings(options),saved=state.agents[id];
77
+ let renderer={status:'unsupported',reason:'No native TUI renderer implemented; MCP support alone is insufficient.'};
78
+ if(id==='opencode')renderer=found?.version==='2.1.2'
79
+ ?{status:'available',reason:'Native OpenCode 2.1.2 adapter candidate; registration and runtime evidence are separate.'}
80
+ :{status:'version-unsupported',reason:'Native adapter currently targets OpenCode 2.1.2 exactly.'};
81
+ let connected=false,configError=null;
82
+ if(id==='opencode')try{const data=documentOf(await textFile(saved?.configPath??configPath));connected=data.plugins?.includes(PLUGIN_URL)??false;}catch(e){configError=e.message;}
83
+ let runtime=null;
84
+ try{const raw=await textFile(join(paths.root,'runtime-'+id+'.json'));if(raw)runtime=JSON.parse(raw);}catch(e){runtime={error:e.message};}
85
+ const age=runtime?.updatedAt?Date.now()-Date.parse(runtime.updatedAt):Infinity;
86
+ const recent=runtime?.version===1&&runtime?.agent===id&&runtime?.hostVersion===found?.version&&runtime?.phase==='rendering'&&age>=0&&age<15000&&Number.isFinite(runtime?.frames)&&runtime.frames>0&&processAlive(runtime?.pid);
87
+ return {id,label:def.label,detected:!!found,executable:found?.executable??null,version:found?.version??null,enabled:saved?.enabled===true,connected,renderer,configError,runtime,runtimeRecent:!!recent,visualApproval:false};
88
+ }
89
+ async function plan(id){
90
+ const row=await inspect(id);let supported=row.detected&&row.renderer.status==='available'&&!row.configError;
91
+ let reason=row.configError??(!row.detected?'Agent executable not found.':row.renderer.reason);
92
+ if(supported&&!await exists(ADAPTER_FILE)){supported=false;reason='Native adapter file is missing from the installation.';}
93
+ if(supported&&env.OPENCODE_CLI_CONFIG_CONTENT){supported=false;reason='An inline OpenCode CLI configuration override is active; refuse ambiguous installation.';}
94
+ if(id==='opencode')await safePath(configPath);
95
+ return {agent:id,supported,reason,pluginUrl:id==='opencode'?PLUGIN_URL:null,changes:id==='opencode'?[{path:configPath,description:'Merge the native TUI plugin URL into plugins; preserve JSONC and other entries.'},{path:paths.settings,description:'Record ownership and enabled state.'}]:[]};
96
+ }
97
+ async function backup(path,previous){
98
+ if(previous===null)return null;
99
+ const dir=join(paths.root,'backups');await safePath(dir);await mkdir(dir,{recursive:true,mode:0o700});
100
+ const destination=join(dir,'opencode-cli-'+randomUUID()+'.jsonc');const f=await open(destination,'wx',0o600);
101
+ try{await f.writeFile(previous);await f.sync();}finally{await f.close();}return destination;
102
+ }
103
+ async function transact(previous,next,state,{path=configPath}={}){
104
+ const backupPath=previous!==next?await backup(path,previous):null;
105
+ await beforeCommit?.();
106
+ if(await textFile(path)!==previous)throw Error('Agent configuration changed concurrently; nothing overwritten. Retry after reviewing it.');
107
+ let changed=false;
108
+ try{
109
+ if(previous!==next){await atomicText(path,next);changed=true;}
110
+ await writeAtomicJson(paths.settings,state);
111
+ }catch(error){
112
+ if(changed&&await textFile(path)===next){if(previous===null)await unlink(path);else await atomicText(path,previous);}
113
+ throw error;
114
+ }
115
+ return backupPath;
116
+ }
117
+ async function enable(id,{confirm=false}={}){
118
+ definition(id);if(!confirm)throw Error('Explicit confirmation required before changing agent configuration');
119
+ const proposed=await plan(id);if(!proposed.supported)throw Error(proposed.reason);
120
+ return withSettingsLock(async()=>{
121
+ const state=await readSettings(options),prior=state.agents[id],old=await textFile(configPath),data=documentOf(old),already=data.plugins?.includes(PLUGIN_URL)??false;
122
+ const formattingOptions={insertSpaces:true,tabSize:2,eol:old?.includes('\r\n')?'\r\n':'\n'};
123
+ const next=already?old:applyEdits(old??'{}\n',data.plugins
124
+ ?modify(old,['plugins',data.plugins.length],PLUGIN_URL,{formattingOptions,isArrayInsertion:true})
125
+ :modify(old??'{}\n',['plugins'],[PLUGIN_URL],{formattingOptions}));
126
+ // Preserve ownership only for the same exact registration; adoption is not deletion authority.
127
+ const addedByUs=already?!!(prior?.addedByUs&&prior.pluginUrl===PLUGIN_URL&&prior.configPath===configPath):true;
128
+ state.agents[id]={...prior,enabled:true,configPath,pluginUrl:PLUGIN_URL,addedByUs};
129
+ const backupPath=await transact(old,next,state);
130
+ return {agent:id,enabled:true,connected:true,rendering:false,restartRequired:true,backupPath};
131
+ },options);
132
+ }
133
+ async function disconnect(id,{confirm=false}={}){
134
+ definition(id);if(!confirm)throw Error('Explicit confirmation required before removing a registration');
135
+ const initial=await readSettings(options);if(!initial.agents[id])return {agent:id,connected:false,changed:false};
136
+ return withSettingsLock(async()=>{
137
+ const state=await readSettings(options),saved=state.agents[id];if(!saved)return {agent:id,changed:false};
138
+ // Never use a path from an editable settings file as deletion authority.
139
+ if(id!=='opencode'||saved.configPath!==configPath||saved.pluginUrl!==PLUGIN_URL)throw Error('Registration ownership/path does not match this installation; refusing removal.');
140
+ const old=await textFile(configPath),data=documentOf(old),index=data.plugins?.indexOf(PLUGIN_URL)??-1;
141
+ const remove=saved.addedByUs===true&&index>=0;
142
+ const next=remove?applyEdits(old,modify(old,['plugins',index],undefined,{formattingOptions:{insertSpaces:true,tabSize:2}})):old;
143
+ delete state.agents[id];const backupPath=await transact(old,next,state);
144
+ return {agent:id,connected:documentOf(next).plugins?.includes(PLUGIN_URL)??false,changed:true,removedOwnedEntry:remove,preservedPreexisting:index>=0&&!remove,backupPath};
145
+ },options);
146
+ }
147
+ return {
148
+ list:()=>Promise.all(AGENTS.map(a=>inspect(a.id))),plan,enable,disconnect,
149
+ async disable(id){definition(id);await setAgentEnabled(id,false,options);return {agent:id,enabled:false,registrationPreserved:true};},
150
+ status:id=>id?inspect(id):Promise.all(AGENTS.map(a=>inspect(a.id))),
151
+ async doctor(id){
152
+ const rows=id?[await inspect(id)]:await Promise.all(AGENTS.map(a=>inspect(a.id)));
153
+ const result=await Promise.all(rows.map(async row=>({...row,adapterFilePresent:row.id==='opencode'&&await exists(ADAPTER_FILE),checks:{executable:row.detected,compatibleVersion:row.renderer.status==='available',registration:row.connected,frameProducedRecently:row.runtimeRecent,humanVisualApproval:false},note:'A recent runtime frame receipt is not human visual verification. MCP is not required for the native OpenCode plugin.'})));
154
+ return id?result[0]:result;
155
+ },
156
+ };
157
+ }
@@ -0,0 +1,44 @@
1
+ import type {FrameOptions, PetFrame, PetOptions} from './retrieviewer.mjs';
2
+
3
+ /** The host owns size and output; the plugin never touches DOM, stdio or signals. */
4
+ export interface PetPluginHost {
5
+ /** Called before every frame. Null or a zero-size pane means hidden: no render, no time. */
6
+ getSize(): FrameOptions | null;
7
+ /** `frame.pixels` is borrowed; copy anything kept past this call. */
8
+ render(frame: PetFrame): void;
9
+ /** Called once by dispose(). */
10
+ clear?(): void;
11
+ /** Errors from scheduled frames. The plugin is already paused (not disposed). */
12
+ onError?(error: unknown): void;
13
+ }
14
+ export interface PetPluginScheduler {
15
+ /** Milliseconds, monotonic. */
16
+ now(): number;
17
+ /** Must call back later, never synchronously. */
18
+ schedule(callback: () => void, delayMs: number): unknown;
19
+ cancel(handle: unknown): void;
20
+ }
21
+ export interface PetPluginOptions {
22
+ host: PetPluginHost;
23
+ petOptions?: PetOptions;
24
+ /** Scheduled frame rate, default 30. */
25
+ fps?: number;
26
+ /** Default: performance.now() with setTimeout/clearTimeout, used only after start(). */
27
+ scheduler?: PetPluginScheduler;
28
+ }
29
+ export interface PetPlugin {
30
+ /** Idempotent. Renders immediately, then once per 1/fps s. */
31
+ start(): void;
32
+ /** Cancels the pending frame; paused time is never caught up. */
33
+ pause(): void;
34
+ /** Renders without advancing time. False while hidden. */
35
+ render(): boolean;
36
+ /** Host-owned loop: advance seconds then render. False while hidden. Throws while running. */
37
+ tick(dtSeconds: number): boolean;
38
+ /** False when busy or cooling down. */
39
+ interact(kind: 'ball' | 'treat'): boolean;
40
+ readonly running: boolean;
41
+ readonly disposed: boolean;
42
+ dispose(): void;
43
+ }
44
+ export function createPetPlugin(options: PetPluginOptions): Promise<PetPlugin>;
@@ -0,0 +1,97 @@
1
+ import {createPet} from './retrieviewer.mjs';
2
+
3
+ // Portable pet plugin: the host supplies size and output, the plugin owns only its
4
+ // own clock. Importing or creating it starts no timer, renders nothing and touches
5
+ // no DOM, stdin/stdout, signals or global listeners; start() or tick() does the work.
6
+ const fn=(value,name)=>{if(typeof value!=='function')throw TypeError(`${name} must be a function`);};
7
+ const defaultScheduler=()=>({now:()=>performance.now(),schedule:(callback,delayMs)=>setTimeout(callback,delayMs),cancel:handle=>clearTimeout(handle)});
8
+
9
+ export async function createPetPlugin({host,petOptions,fps=30,scheduler}={}){
10
+ if(!host||typeof host!=='object')throw TypeError('host must be an object');
11
+ fn(host.getSize,'host.getSize');fn(host.render,'host.render');
12
+ if(host.clear!==undefined)fn(host.clear,'host.clear');
13
+ if(host.onError!==undefined)fn(host.onError,'host.onError');
14
+ if(typeof fps!=='number'||!Number.isFinite(fps)||fps<=0||fps>120)throw RangeError('fps must be within (0, 120]');
15
+ if(scheduler!==undefined){
16
+ if(!scheduler||typeof scheduler!=='object')throw TypeError('scheduler must be an object');
17
+ fn(scheduler.now,'scheduler.now');fn(scheduler.schedule,'scheduler.schedule');fn(scheduler.cancel,'scheduler.cancel');
18
+ }
19
+ const clock=scheduler??defaultScheduler(),delay=1000/fps;
20
+ let pet=await createPet(petOptions);
21
+ // One pending callback at most; `token` makes a late or uncancelled callback inert.
22
+ let running=false,disposed=false,busy=false,armed=false,handle=null,token=0,baseline=null;
23
+ const alive=()=>{if(disposed)throw Error('Pet plugin is disposed');};
24
+ const idle=()=>{if(busy)throw Error('Pet plugin cannot render or tick from inside host.render');};
25
+ const cancel=()=>{if(!armed)return;const h=handle;armed=false;handle=null;token++;clock.cancel(h);};
26
+ const halt=()=>{running=false;baseline=null;cancel();};
27
+
28
+ // Fresh size every frame, so resizes need no listener. Null or zero-size panes are
29
+ // hidden: no render and no simulated time. Other invalid sizes throw from frame().
30
+ function draw(dt){
31
+ busy=true;
32
+ try{
33
+ const size=host.getSize();
34
+ if(size===null||size===undefined)return false;
35
+ if(typeof size!=='object')throw TypeError('host.getSize() must return FrameOptions or null');
36
+ const {columns,rows}=size;
37
+ if((columns===0&&Number.isInteger(rows)&&rows>=0)||(rows===0&&Number.isInteger(columns)&&columns>=0))return false;
38
+ if(dt>0)pet.step(dt);
39
+ host.render(pet.frame(size));
40
+ return true;
41
+ }finally{busy=false;}
42
+ }
43
+ // Scheduled frames advance by real elapsed time since the last visible frame. A
44
+ // resume or a reveal after hiding starts a new baseline, so nothing is caught up.
45
+ function frameAt(now){
46
+ const dt=baseline===null?0:Math.max(0,(now-baseline)/1000);
47
+ const shown=draw(dt);
48
+ if(running)baseline=shown?now:null;
49
+ }
50
+ function arm(){
51
+ if(!running||disposed||armed)return;
52
+ const mine=++token;armed=true;
53
+ handle=clock.schedule(()=>{if(mine!==token)return;armed=false;handle=null;token++;run();},delay);
54
+ }
55
+ // Asynchronous failures have no caller to throw to: the plugin pauses (it is not
56
+ // disposed) and hands the error to host.onError, or stays paused silently without
57
+ // one. If onError itself throws, that error escapes this callback and the plugin
58
+ // stays paused; it is never rescheduled.
59
+ function run(){
60
+ if(!running||disposed)return;
61
+ try{frameAt(clock.now());arm();}
62
+ catch(error){halt();if(!disposed&&host.onError)host.onError(error);}
63
+ }
64
+
65
+ return {
66
+ // Idempotent. Renders the first frame immediately (no time advance), then once
67
+ // per 1/fps s. Errors from that first frame stop the plugin and propagate.
68
+ start(){
69
+ alive();idle();if(running)return;
70
+ running=true;baseline=null;
71
+ try{frameAt(clock.now());arm();}catch(error){halt();throw error;}
72
+ },
73
+ // Cancels the pending callback; time while paused is never simulated.
74
+ pause(){if(!disposed)halt();},
75
+ // Draws the current state without advancing time. False while hidden.
76
+ render(){alive();idle();try{return draw(0);}catch(error){halt();throw error;}},
77
+ // Host-owned loop: advance by explicit seconds, then render. False while hidden
78
+ // (time does not advance). Use instead of start(), not alongside it.
79
+ tick(dtSeconds){
80
+ alive();idle();
81
+ if(running)throw Error('tick() is for host-owned loops; pause() the scheduled loop first');
82
+ if(typeof dtSeconds!=='number'||!Number.isFinite(dtSeconds)||dtSeconds<0)throw RangeError('dtSeconds must be finite non-negative seconds');
83
+ try{return draw(dtSeconds);}catch(error){halt();throw error;}
84
+ },
85
+ // 'ball' | 'treat'. False when busy or cooling down; shows on the next frame.
86
+ interact(kind){alive();return pet.interact(kind);},
87
+ get running(){return running;},
88
+ get disposed(){return disposed;},
89
+ // Idempotent: stops the clock, disposes the pet and calls host.clear once, even
90
+ // if pet disposal throws. A throwing clear() propagates once; disposal still holds.
91
+ dispose(){
92
+ if(disposed)return;disposed=true;halt();
93
+ const core=pet;pet=null;
94
+ try{core.dispose();}finally{host.clear?.();}
95
+ },
96
+ };
97
+ }
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ // Explicit language-neutral host bridge. Stdout is JSONL only; no autonomous
3
+ // timers or terminal ownership. Close stdin or send dispose to release the pet.
4
+ import {createPet} from './retrieviewer.mjs';
5
+ import {createInterface} from 'node:readline';
6
+ import {once} from 'node:events';
7
+
8
+ let pet,lines;
9
+ const stop=()=>lines?.close();
10
+ const send=async value=>{if(!process.stdout.write(JSON.stringify(value)+'\n'))await once(process.stdout,'drain');};
11
+ try{
12
+ pet=await createPet();
13
+ lines=createInterface({input:process.stdin,crlfDelay:Infinity});
14
+ process.on('SIGINT',stop);process.on('SIGTERM',stop);
15
+ await send({event:'ready',protocol:1});
16
+ for await(const line of lines){
17
+ if(!line.trim())continue;
18
+ let id=null;
19
+ try{
20
+ const request=JSON.parse(line);
21
+ if(!request||typeof request!=='object'||Array.isArray(request))throw TypeError('Expected a request object');
22
+ if(request.id!==undefined&&request.id!==null&&typeof request.id!=='string'&&typeof request.id!=='number')throw TypeError('id must be a string, number or null');
23
+ id=request.id??null;
24
+ if(request.op==='frame'||request.op==='tick'){
25
+ // One round trip per host frame; existing step + frame stays compatible.
26
+ if(request.op==='tick')pet.step(request.dt);
27
+ const {text,columns,rows,report,artProfile}=pet.frame(request);
28
+ await send({id,ok:true,text,columns,rows,report,artProfile});
29
+ }else if(request.op==='step')await send({id,ok:true,steps:pet.step(request.dt)});
30
+ else if(request.op==='interact')await send({id,ok:true,accepted:pet.interact(request.kind)});
31
+ else if(request.op==='dispose'){pet.dispose();await send({id,ok:true});break;}
32
+ else throw TypeError('Unknown op: use frame, tick, step, interact or dispose');
33
+ }catch(error){await send({id,ok:false,error:error.message});}
34
+ }
35
+ }catch(error){process.stderr.write('Retrieviewer bridge: '+error.message+'\n');process.exitCode=1;}
36
+ finally{
37
+ lines?.close();pet?.dispose();process.off('SIGINT',stop);process.off('SIGTERM',stop);
38
+ }
@@ -0,0 +1,85 @@
1
+ /** A headless pet: the host owns input, time and terminal rendering. */
2
+ export interface PetOptions {
3
+ /** Asset names are relative; return original file bytes (keep .gz files compressed). */
4
+ loadAsset?: (name: string) => Promise<ArrayBuffer | ArrayBufferView>;
5
+ seed?: number;
6
+ energy?: number;
7
+ /** Keep true for the current paw correction; false selects the legacy comparison. */
8
+ pawTransport?: boolean;
9
+ /** Default q5 (32 levels). Original requires separately supplied original assets. */
10
+ artProfile?: 'original' | 'q5';
11
+ }
12
+ export interface FrameOptions {
13
+ columns: number;
14
+ rows: number;
15
+ /** Character width / line height. Default .5; measure your terminal font if needed. */
16
+ cellAspect?: number;
17
+ poseFps?: 12 | 24 | 30;
18
+ contactEnabled?: boolean;
19
+ camera?: {
20
+ viewportWidth: number;
21
+ viewportHeight: number;
22
+ rect: {left: number; top: number; width: number; height: number};
23
+ };
24
+ }
25
+ export interface PetState {
26
+ state: string;
27
+ x: number;
28
+ z: number;
29
+ speed: number;
30
+ view: string;
31
+ flip: boolean;
32
+ seatProgress: number | null;
33
+ behavior: string | null;
34
+ actionName: string | null;
35
+ [key: string]: unknown;
36
+ }
37
+ export interface LifeReport {
38
+ intent: string;
39
+ label: string;
40
+ interaction: 'ball' | 'treat' | null;
41
+ busy: boolean;
42
+ energy: number;
43
+ decisions: number;
44
+ cooldowns: {ball: number; treat: number};
45
+ gesture: {clip: string; progress: number} | null;
46
+ ball: {x: number; z: number; height: number; carried: boolean} | null;
47
+ treat: {x: number; z: number; height: number} | null;
48
+ }
49
+ export interface PetFrame {
50
+ artProfile: 'original' | 'q5';
51
+ /** Plain Unicode Braille: rows lines of exactly columns cells, no ANSI. */
52
+ text: string;
53
+ columns: number;
54
+ rows: number;
55
+ /** Borrowed RGBA buffer; copy it before retaining across frame() calls. */
56
+ pixels: Uint8ClampedArray;
57
+ state: PetState;
58
+ report: LifeReport;
59
+ mouth: {x: number; y: number; rx: number; ry: number; family: string; frame: number} | null;
60
+ placement: {x: number; y: number; size: number; pixelAspect: number; depth: number};
61
+ projection: {
62
+ project(x: number, y: number, z: number): {x: number; y: number; depth: number};
63
+ ground(u: number, v: number): {x: number; z: number} | null;
64
+ focalPixels: number;
65
+ };
66
+ contacts: {samples: number; max: number; limited: number; releaseEvents: number; releasedMax: number};
67
+ }
68
+ export interface Pet {
69
+ /** Seconds. Fixed 60 Hz simulation, at most .1 s catch-up per call. */
70
+ step(dt: number): number;
71
+ frame(options: FrameOptions): PetFrame;
72
+ /** False when busy or cooling down. */
73
+ interact(kind: 'ball' | 'treat'): boolean;
74
+ readonly disposed: boolean;
75
+ dispose(): void;
76
+ }
77
+ export function createPet(options?: PetOptions): Promise<Pet>;
78
+ export function defaultLoadAsset(name: string): Promise<ArrayBuffer | Uint8Array>;
79
+ export const STEP_SECONDS: number;
80
+ export const MAX_CATCH_UP_SECONDS: number;
81
+ export const INTERACTIONS: readonly ['ball', 'treat'];
82
+ export const ART_PROFILES: readonly ['original', 'q5'];
83
+ export const DEFAULT_ART_PROFILE: 'q5';
84
+ export function getPetAssetFiles(artProfile?: 'original' | 'q5'): readonly string[];
85
+ export const PET_ASSETS: Readonly<Record<string, string>>;
@@ -0,0 +1,142 @@
1
+ import {makeLabAnimal} from './locomotion-core.mjs';
2
+ import {createPaperActor,createPaperRenderer} from './puppy-paper.mjs';
3
+ import {createPaperProjection} from './puppy-paper-world.mjs';
4
+ import {rgbaToBraille} from './braille.mjs';
5
+ import {createSnappyTrickTimings} from './puppy-paper-trick-timing.mjs';
6
+ import {createCompanionLife} from './puppy-companion-life.mjs';
7
+ import {paintCompanionObjects} from './puppy-companion-objects.mjs';
8
+ import {readPackedFrames} from './puppy-frame-store.mjs';
9
+
10
+ // Embeddable autonomous puppy. Importing or creating has no DOM, timer, stdin/stdout
11
+ // or listener side effects: the host owns the loop and calls step() then frame().
12
+ export const STEP_SECONDS=1/60;
13
+ export const MAX_CATCH_UP_SECONDS=.1;
14
+ export const INTERACTIONS=Object.freeze(['ball','treat']);
15
+ // Approved 32-level art ships by default; preserved original art is optional.
16
+ export const DEFAULT_ART_PROFILE='q5';
17
+ export const ART_PROFILES=Object.freeze(['original','q5']);
18
+ // Current companion runtime: v9 tricks + interactions-v3, same as puppy-paper.html.
19
+ export const PET_ASSETS=Object.freeze({
20
+ animal:'locomotion-puppy.animal',
21
+ stride:'puppy-stride-tweens-q5.bin.gz',strideInfo:'puppy-stride-meta.json',stableInfo:'puppy-stable-meta.json',
22
+ rear:'puppy-paper-rear-gallop',approach:'puppy-paper-approach',seat:'puppy-paper-seat',arrivalSeat:'puppy-paper-arrival-seat',
23
+ behaviors:'puppy-paper-behaviors',actions:'puppy-paper-actions-v9',interactions:'puppy-companion-interactions-v3',
24
+ });
25
+ const BUNDLES=['rear','approach','seat','arrivalSeat','behaviors','actions','interactions'];
26
+ const frameFile=(base,profile)=>base+(profile==='q5'?'-q5.bin.gz':'-compact.bin.gz');
27
+ /** Exact files a host/bundler must copy, including metadata. Never loads assets. */
28
+ export function getPetAssetFiles(artProfile=DEFAULT_ART_PROFILE){
29
+ if(!ART_PROFILES.includes(artProfile))throw RangeError('artProfile must be original or q5');
30
+ return Object.freeze([PET_ASSETS.strideInfo,PET_ASSETS.stableInfo,frameFile('puppy-stride-tweens',artProfile),
31
+ ...BUNDLES.flatMap(key=>[PET_ASSETS[key]+'.json',frameFile(PET_ASSETS[key],artProfile)]),PET_ASSETS.animal]);
32
+ }
33
+
34
+ // Relative to this module, never the process cwd: file: URLs in Node, HTTP in browsers.
35
+ export async function defaultLoadAsset(name){
36
+ const url=new URL(name,import.meta.url);
37
+ if(url.protocol==='file:'){
38
+ const {readFile}=await import('node:fs/promises');
39
+ try{return await readFile(url);}catch(error){
40
+ if(error.code==='ENOENT'&&name.endsWith('-compact.bin.gz'))throw Error('Original art is optional and not bundled. Supply the preserved original assets with loadAsset, or use the default q5 profile.',{cause:error});
41
+ throw error;
42
+ }
43
+ }
44
+ const r=await fetch(url);if(!r.ok)throw Error(`${name} HTTP ${r.status}`);return r.arrayBuffer();
45
+ }
46
+
47
+ const bytesOf=(name,value)=>{
48
+ if(value instanceof ArrayBuffer)return new Uint8Array(value);
49
+ if(ArrayBuffer.isView(value))return new Uint8Array(value.buffer,value.byteOffset,value.byteLength);
50
+ throw TypeError(`loadAsset(${name}) must return an ArrayBuffer or typed bytes`);
51
+ };
52
+ async function gunzip(bytes){
53
+ // Native Node gzip avoids Blob/Response/stream copies of every large cache.
54
+ if(typeof process!=='undefined'&&process.versions?.node){const {gunzipSync}=await import('node:zlib');return gunzipSync(bytes);}
55
+ if(typeof DecompressionStream!=='undefined')return new Uint8Array(await new Response(new Blob([bytes]).stream().pipeThrough(new DecompressionStream('gzip'))).arrayBuffer());
56
+ throw Error('This browser needs DecompressionStream for pet assets');
57
+ }
58
+
59
+ // Only immutable art/metadata is shared. Each pet still owns its animal, actor,
60
+ // life, renderer and output. Last dispose drops the shared lease, not other pets.
61
+ const sharedAssets=new Map();
62
+ async function loadPetAssets(loadAsset,artProfile){
63
+ const load=async name=>bytesOf(name,await loadAsset(name));
64
+ const json=async name=>JSON.parse(new TextDecoder().decode(await load(name)));
65
+ const frames=async name=>readPackedFrames(await gunzip(await load(name)));
66
+ // One family at a time: never inflate all eight legacy-sized buffers together.
67
+ const data={assets:{}};
68
+ data.info=await json(PET_ASSETS.strideInfo);data.meta=await json(PET_ASSETS.stableInfo);
69
+ data.cache=await frames(frameFile('puppy-stride-tweens',artProfile));
70
+ for(const key of BUNDLES){const base=PET_ASSETS[key];data.assets[key]={info:await json(base+'.json'),bytes:await frames(frameFile(base,artProfile))};}
71
+ data.baked=await load(PET_ASSETS.animal);
72
+ return data;
73
+ }
74
+ async function acquireAssets(loadAsset,artProfile){
75
+ let entry;
76
+ if(loadAsset===defaultLoadAsset){entry=sharedAssets.get(artProfile);if(!entry){entry={users:0,promise:loadPetAssets(loadAsset,artProfile)};sharedAssets.set(artProfile,entry);}}
77
+ else entry={users:0,promise:loadPetAssets(loadAsset,artProfile)};
78
+ entry.users++;
79
+ let released=false;
80
+ const release=()=>{if(released)return;released=true;const previous=entry;entry=null;if(--previous.users===0&&sharedAssets.get(artProfile)===previous)sharedAssets.delete(artProfile);};
81
+ try{return{data:await entry.promise,release};}catch(error){release();throw error;}
82
+ }
83
+
84
+ export async function createPet({loadAsset=defaultLoadAsset,seed,energy,pawTransport=true,artProfile=DEFAULT_ART_PROFILE}={}){
85
+ if(typeof loadAsset!=='function')throw TypeError('loadAsset must be a function');
86
+ if(seed===undefined)seed=globalThis.crypto?.getRandomValues?globalThis.crypto.getRandomValues(new Uint32Array(1))[0]:731;
87
+ if(!Number.isInteger(seed)||seed<0||seed>0xffffffff)throw RangeError('seed must be a uint32');
88
+ if(energy!==undefined&&(!Number.isFinite(energy)||energy<0||energy>1))throw RangeError('energy must be within 0..1');
89
+ if(typeof pawTransport!=='boolean')throw TypeError('pawTransport must be boolean');
90
+ if(!ART_PROFILES.includes(artProfile))throw RangeError('artProfile must be original or q5');
91
+ let lease=await acquireAssets(loadAsset,artProfile),data=lease.data;
92
+ const release=lease.release;lease=null;
93
+ let {cache,info,meta,baked}=data;const assets={...data.assets};data=null;
94
+ let animal,actor,life;
95
+ try{
96
+ if(!assets.actions.info.clips?.['lie-down']||!assets.actions.info.clips?.['lie-up'])throw Error('v9 action cache lacks lie-down/lie-up clips');
97
+ animal=await makeLabAnimal({baked:baked.buffer.slice(baked.byteOffset,baked.byteOffset+baked.byteLength)});baked=null;
98
+ actor=createPaperActor(animal,info,{manualTricks:true,trickTimings:createSnappyTrickTimings(assets.actions.info),walkTransitions:true});
99
+ life=createCompanionLife(actor,{seed,...energy===undefined?{}:{energy}});
100
+ }catch(e){animal?.dispose?.();release();throw e;}
101
+ let paint=null,paintKey='',time=0,acc=0,disposed=false;
102
+ const alive=()=>{if(disposed)throw Error('Pet is disposed');};
103
+ return {
104
+ // Fixed 1/60 s simulation; host deltas are clamped to 0.1 s so a stalled host
105
+ // catches up at most six steps. Returns the number of fixed steps taken.
106
+ step(dt){
107
+ alive();if(typeof dt!=='number'||!Number.isFinite(dt)||dt<0)throw RangeError('dt must be finite non-negative seconds');
108
+ acc+=Math.min(dt,MAX_CATCH_UP_SECONDS);let n=0;
109
+ while(acc>=STEP_SECONDS-1e-9){actor.update(STEP_SECONDS);life.update(STEP_SECONDS);time+=STEP_SECONDS;acc=Math.max(0,acc-STEP_SECONDS);n++;}
110
+ return n;
111
+ },
112
+ // Plain Braille text sized exactly columns x rows (2x4 dots per cell). `pixels`
113
+ // is the renderer's buffer, valid only until the next frame()/dispose().
114
+ frame({columns,rows,cellAspect=.5,camera,poseFps=24,contactEnabled=true}={}){
115
+ alive();
116
+ if(!Number.isInteger(columns)||!Number.isInteger(rows)||columns<1||rows<1||columns*rows*8>1_000_000)throw RangeError('columns/rows must be positive integers within 125000 cells');
117
+ if(typeof cellAspect!=='number'||!Number.isFinite(cellAspect)||cellAspect<=0)throw RangeError('cellAspect must be a positive number');
118
+ if(![12,24,30].includes(poseFps))throw RangeError('poseFps must be 12, 24 or 30');
119
+ const width=columns*2,height=rows*4,key=`${width}x${height}`;
120
+ if(paintKey!==key){paint=createPaperRenderer(cache,info,meta,width,height,{...assets,pawTransport});paintKey=key;}
121
+ const viewportWidth=camera?.viewportWidth??columns*cellAspect,viewportHeight=camera?.viewportHeight??rows;
122
+ const projection=createPaperProjection({viewportWidth,viewportHeight,rect:camera?.rect??{left:0,top:0,width:viewportWidth,height:viewportHeight},width,height});
123
+ const state=actor.sample(time,{poseFps}),report=life.report;
124
+ if(report.gesture)state.interactionPose=report.gesture;
125
+ const placement=projection.placement(state.x,state.z),pixels=paint(state,{...placement,contactEnabled:!!contactEnabled});
126
+ paintCompanionObjects(pixels,width,height,projection,report,state,paint.lastMouth);
127
+ const {text}=rgbaToBraille(pixels,width,height,{method:'stable',brightness:1.18,contrast:1.1});
128
+ return {text,columns,rows,pixels,state,report,placement,projection,artProfile,mouth:paint.lastMouth,contacts:paint.report()};
129
+ },
130
+ // 'ball' | 'treat'. False when busy or cooling down; unknown kinds throw.
131
+ interact(kind){alive();if(!INTERACTIONS.includes(kind))throw TypeError(`Unknown interaction: ${kind}`);return life.interact(kind);},
132
+ get disposed(){return disposed;},
133
+ dispose(){
134
+ if(disposed)return;disposed=true;
135
+ try{animal.dispose?.();}finally{
136
+ animal=actor=life=paint=cache=info=meta=null;paintKey='';
137
+ for(const k of BUNDLES)assets[k]=null;
138
+ release();
139
+ }
140
+ },
141
+ };
142
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Majid Manzarpour and procedural-animals contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "procedural-animals",
3
+ "version": "0.1.0",
4
+ "description": "24 fully procedural animals for three.js: SDF-sculpted bodies, dual-quaternion skinning, GPU fur, feathers and scales, IK locomotion and actions. No models, no textures, no downloads.",
5
+ "license": "MIT",
6
+ "author": "Majid Manzarpour",
7
+ "homepage": "https://majidmanzarpour.github.io/threejs-procedural-animals/",
8
+ "repository": { "type": "git", "url": "git+https://github.com/majidmanzarpour/threejs-procedural-animals.git" },
9
+ "bugs": { "url": "https://github.com/majidmanzarpour/threejs-procedural-animals/issues" },
10
+ "type": "module",
11
+ "main": "./src/index.js",
12
+ "module": "./src/index.js",
13
+ "types": "./src/index.d.ts",
14
+ "exports": {
15
+ ".": { "types": "./src/index.d.ts", "default": "./src/index.js" },
16
+ "./species/*": "./src/species/*/index.js",
17
+ "./worker": "./src/worker.js",
18
+ "./bake": "./src/bake.js",
19
+ "./package.json": "./package.json"
20
+ },
21
+ "bin": { "procedural-animals-bake": "./tools/bake.mjs" },
22
+ "sideEffects": ["./src/worker.js"],
23
+ "files": [
24
+ "src/**/*.js",
25
+ "src/index.d.ts",
26
+ "tools/bake.mjs",
27
+ "README.md",
28
+ "LICENSE",
29
+ "llms.txt"
30
+ ],
31
+ "engines": { "node": ">=18" },
32
+ "peerDependencies": { "three": ">=0.160.0" },
33
+ "devDependencies": { "esbuild": "^0.28.2", "playwright-core": "1.56", "three": "^0.186.0" },
34
+ "scripts": {
35
+ "build:showcase": "node showcase/build.mjs",
36
+ "build:pages": "node showcase/build.mjs --pages",
37
+ "test": "node tools/test.mjs"
38
+ },
39
+ "keywords": ["three", "threejs", "procedural", "animals", "animation", "ik", "fur", "sdf", "creatures", "gamedev", "webgl"]
40
+ }