cozyclay 2.1.1 → 3.0.0

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 (577) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +12 -30
  3. package/bin/agent/agent-routes.mjs +40 -113
  4. package/bin/agent/agent-runner.mjs +9 -10
  5. package/bin/agent/cliproxy-provider.mjs +135 -0
  6. package/bin/agent/credential-store.mjs +30 -7
  7. package/bin/agent/pi-tools.mjs +4 -1
  8. package/bin/agent/provider-keys.mjs +32 -0
  9. package/bin/agent/provider-login.mjs +110 -0
  10. package/bin/agent/provider-routes.mjs +99 -0
  11. package/bin/agent/providers.mjs +19 -97
  12. package/bin/agent/script-tool.mjs +17 -4
  13. package/bin/agent/studio-motion-reference.mjs +23 -0
  14. package/bin/agent/studio-prompt.mjs +1 -1
  15. package/bin/agent/studio-rebase-text.mjs +32 -0
  16. package/bin/agent/studio-rebase.mjs +26 -39
  17. package/bin/agent/studio-tools.mjs +9 -28
  18. package/bin/agent/tool-arguments.mjs +49 -0
  19. package/bin/agent/turn-frames.mjs +66 -0
  20. package/bin/codex-auth.mjs +9 -2
  21. package/bin/cozyclay.mjs +10 -116
  22. package/bin/live/cli.mjs +3 -3
  23. package/bin/mcp-runtime.mjs +2 -12
  24. package/bin/package-signature.mjs +0 -4
  25. package/dist/app/index.html +7 -6
  26. package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
  27. package/dist/assets/analytics-C9sUnndd.js +1 -0
  28. package/dist/assets/app-CCh--1Hc.js +5087 -0
  29. package/dist/assets/app-f-fkV5uP.css +1 -0
  30. package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
  31. package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
  32. package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-CC4xe4HM.js} +1 -1
  33. package/dist/assets/{landing-ChUvB_zL.js → landing-DjHD8u7k.js} +1 -1
  34. package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
  35. package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
  36. package/dist/assets/workflow-C4yKH1p4.js +179 -0
  37. package/dist/cozyclay-package.json +1 -1
  38. package/dist/index.html +3 -3
  39. package/dist/sw.js +3 -6
  40. package/dist/workflow/index.html +5 -4
  41. package/mcp/LIVE-PROTOCOL.md +7 -47
  42. package/mcp/README.md +16 -26
  43. package/mcp/live-hub.mjs +6 -10
  44. package/mcp/package.json +1 -3
  45. package/mcp/tool-handlers.mjs +4 -168
  46. package/package.json +3 -22
  47. package/src/App.jsx +150 -2624
  48. package/src/agent-progress.js +79 -0
  49. package/src/agent-turn-mark.js +30 -0
  50. package/src/analytics.js +1 -19
  51. package/src/app-context.js +5 -0
  52. package/src/app-stage.jsx +2 -34
  53. package/src/asset-pane.jsx +6 -5
  54. package/src/camera-block.js +21 -0
  55. package/src/camera-direction.js +120 -0
  56. package/src/character-kind.js +3 -9
  57. package/src/command-bus.js +8 -6
  58. package/src/commands/cast.js +5 -34
  59. package/src/commands/elements/character.js +1 -1
  60. package/src/commands/elements/motion.js +1 -6
  61. package/src/commands/elements/object.js +6 -2
  62. package/src/commands/elements/shot.js +4 -1
  63. package/src/commands/elements.js +38 -14
  64. package/src/commands/index.js +3 -1
  65. package/src/commands/kits.js +37 -0
  66. package/src/commands/motion-timing.js +57 -0
  67. package/src/commands/motion.js +9 -152
  68. package/src/commands/motion3-compile.js +65 -0
  69. package/src/commands/motion3.js +207 -0
  70. package/src/commands/objects.js +6 -4
  71. package/src/commands/shot.js +49 -11
  72. package/src/commands/stage.js +14 -1
  73. package/src/commands/view.js +4 -1
  74. package/src/document-mirrors.js +42 -0
  75. package/src/document-store.js +4 -0
  76. package/src/domains/cast.js +19 -194
  77. package/src/domains/motion/motion-range-pin-rules.js +1 -1
  78. package/src/domains/motion/take-source.js +51 -0
  79. package/src/domains/motion.js +60 -1838
  80. package/src/domains/scenes.js +12 -55
  81. package/src/domains/shots.js +2 -2
  82. package/src/domains/stage.js +4 -2
  83. package/src/draft-ghost.js +46 -0
  84. package/src/draft-ghost.jsx +129 -0
  85. package/src/dualview.jsx +4 -3
  86. package/src/effects/EffectsLayer.jsx +112 -0
  87. package/src/effects/effect-meshes.js +232 -0
  88. package/src/effects/effect-record.js +103 -0
  89. package/src/effects/emitter.js +59 -0
  90. package/src/effects/simulate.js +261 -0
  91. package/src/effects/state.js +59 -0
  92. package/src/execution-telemetry.js +2 -3
  93. package/src/first-success-guide.jsx +5 -5
  94. package/src/ghost-set.js +257 -0
  95. package/src/held-object-edit.jsx +113 -0
  96. package/src/held-object-pose.js +175 -0
  97. package/src/hierarchy-model.js +1 -1
  98. package/src/hierarchy-panel.jsx +1 -1
  99. package/src/imported-mesh.jsx +103 -0
  100. package/src/kits/held.js +84 -0
  101. package/src/kits/index.js +70 -0
  102. package/src/kits/smoking.js +57 -0
  103. package/src/light-blockers.js +93 -0
  104. package/src/motion/beat-board-model.js +84 -0
  105. package/src/motion/beat-board.css +124 -0
  106. package/src/motion/beat-board.jsx +59 -0
  107. package/src/motion/center-of-mass.js +66 -0
  108. package/src/motion/fix-collisions.js +850 -0
  109. package/src/motion/physics-review.js +118 -0
  110. package/src/{ardy → motion}/physics-support.js +1 -76
  111. package/src/motion/rig-snapshot.js +22 -0
  112. package/src/motion/take-segments.jsx +140 -0
  113. package/src/{ardy → motion}/timeline-coordinates.js +0 -21
  114. package/src/{ardy → motion}/timeline.css +1 -2
  115. package/src/{ardy → motion}/timeline.jsx +42 -413
  116. package/src/motion/waypoints.js +76 -0
  117. package/src/motion3/beats.js +83 -0
  118. package/src/motion3/body.js +158 -0
  119. package/src/motion3/bones.js +47 -0
  120. package/src/motion3/carried.js +47 -0
  121. package/src/motion3/compile-job.js +75 -0
  122. package/src/motion3/compile-worker.js +10 -0
  123. package/src/motion3/compiler/anchors.js +105 -0
  124. package/src/motion3/compiler/arm-orient.js +128 -0
  125. package/src/motion3/compiler/channels.js +125 -0
  126. package/src/motion3/compiler/compile.js +205 -0
  127. package/src/motion3/compiler/gait-rhythm.js +97 -0
  128. package/src/motion3/compiler/grip.js +171 -0
  129. package/src/motion3/compiler/head-steady.js +48 -0
  130. package/src/motion3/compiler/hold.js +134 -0
  131. package/src/motion3/compiler/idle.js +113 -0
  132. package/src/motion3/compiler/limbs.js +170 -0
  133. package/src/motion3/compiler/math.js +72 -0
  134. package/src/motion3/compiler/pose.js +162 -0
  135. package/src/motion3/compiler/quat.js +49 -0
  136. package/src/motion3/compiler/skeleton.js +74 -0
  137. package/src/motion3/compiler/validate-common.js +32 -0
  138. package/src/motion3/compiler/validate-goals.js +118 -0
  139. package/src/motion3/compiler/validate-objects.js +143 -0
  140. package/src/motion3/compiler/validate-parts.js +116 -0
  141. package/src/motion3/compiler/validate.js +122 -0
  142. package/src/motion3/compiler/vocab.js +73 -0
  143. package/src/motion3/compiler/walk-pelvis.js +275 -0
  144. package/src/motion3/compiler/walk.js +207 -0
  145. package/src/motion3/compiler/wrist-clamp.js +32 -0
  146. package/src/motion3/delta.js +47 -0
  147. package/src/motion3/describe.js +174 -0
  148. package/src/motion3/docs.js +27 -0
  149. package/src/motion3/edit-carry.js +21 -0
  150. package/src/motion3/fix/ease.js +16 -0
  151. package/src/motion3/fix/ground.js +37 -0
  152. package/src/motion3/fix/index.js +52 -0
  153. package/src/motion3/fix/push.js +71 -0
  154. package/src/motion3/fix/reach.js +46 -0
  155. package/src/motion3/fix/retime.js +79 -0
  156. package/src/motion3/fix/self.js +211 -0
  157. package/src/motion3/fix/slide.js +70 -0
  158. package/src/motion3/fix/turn.js +51 -0
  159. package/src/motion3/follow.js +39 -0
  160. package/src/motion3/free-motion.js +173 -0
  161. package/src/motion3/grip-choice.js +130 -0
  162. package/src/motion3/hold-plan.js +157 -0
  163. package/src/motion3/json-patch.js +77 -0
  164. package/src/motion3/key-aim.js +105 -0
  165. package/src/motion3/key-edit.js +82 -0
  166. package/src/motion3/lenient.js +127 -0
  167. package/src/motion3/object-keys.js +236 -0
  168. package/src/motion3/object-pose.js +182 -0
  169. package/src/motion3/object-tracks.js +12 -0
  170. package/src/motion3/pipeline.js +102 -0
  171. package/src/motion3/playback.js +51 -0
  172. package/src/motion3/probes.js +91 -0
  173. package/src/motion3/qa/body-checks.js +34 -0
  174. package/src/motion3/qa/check-names.js +14 -0
  175. package/src/motion3/qa/checks.js +121 -0
  176. package/src/motion3/qa/contact-checks.js +51 -0
  177. package/src/motion3/qa/explain.js +80 -0
  178. package/src/motion3/qa/fmt.js +5 -0
  179. package/src/motion3/qa/foot-checks.js +72 -0
  180. package/src/motion3/qa/geometry.js +77 -0
  181. package/src/motion3/qa/goals.js +191 -0
  182. package/src/motion3/qa/ground.js +60 -0
  183. package/src/motion3/qa/hints-contact.js +179 -0
  184. package/src/motion3/qa/hints-goal.js +160 -0
  185. package/src/motion3/qa/hints-knee.js +24 -0
  186. package/src/motion3/qa/hints-motion.js +209 -0
  187. package/src/motion3/qa/hints-object.js +91 -0
  188. package/src/motion3/qa/index.js +42 -0
  189. package/src/motion3/qa/key-checks.js +82 -0
  190. package/src/motion3/qa/near.js +96 -0
  191. package/src/motion3/qa/object-checks.js +135 -0
  192. package/src/motion3/qa/patch.js +161 -0
  193. package/src/motion3/qa/severity.js +38 -0
  194. package/src/motion3/qa/spec-checks.js +65 -0
  195. package/src/motion3/qa/style.js +218 -0
  196. package/src/motion3/qa/wrist-checks.js +69 -0
  197. package/src/motion3/qa/wrist-obs.js +60 -0
  198. package/src/motion3/refs.js +120 -0
  199. package/src/motion3/repair-tune.js +121 -0
  200. package/src/motion3/repair.js +83 -0
  201. package/src/motion3/report.js +67 -0
  202. package/src/motion3/rigs/index.js +14 -0
  203. package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
  204. package/src/motion3/rigs/x-bot-tpose.json +1 -0
  205. package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
  206. package/src/motion3/rigs/y-bot-tpose.json +1 -0
  207. package/src/motion3/scene.js +60 -0
  208. package/src/motion3/schema/brief.js +89 -0
  209. package/src/motion3/schema/fields.js +44 -0
  210. package/src/motion3/schema/spec-examples.js +46 -0
  211. package/src/motion3/schema/topics.js +111 -0
  212. package/src/motion3/schema/types.js +147 -0
  213. package/src/motion3/search.js +117 -0
  214. package/src/motion3/second-hand.js +112 -0
  215. package/src/motion3/skills/basic.js +82 -0
  216. package/src/motion3/skills/book.js +76 -0
  217. package/src/motion3/skills/car.js +145 -0
  218. package/src/motion3/skills/frames-auto.js +29 -0
  219. package/src/motion3/skills/index.js +83 -0
  220. package/src/motion3/skills/info.js +65 -0
  221. package/src/motion3/skills/smoke-kit.js +109 -0
  222. package/src/motion3/skills/smoke.js +160 -0
  223. package/src/motion3/spec-store.js +59 -0
  224. package/src/motion3/take-edit.js +155 -0
  225. package/src/motion3/take.js +63 -0
  226. package/src/motion3/timeline.js +127 -0
  227. package/src/motion3/views.js +121 -0
  228. package/src/motion3/walk-plan.js +119 -0
  229. package/src/object-catalog.jsx +7 -1
  230. package/src/object-colors.js +98 -0
  231. package/src/object-gizmo-parts.js +115 -0
  232. package/src/object-gizmo.jsx +37 -122
  233. package/src/object-glow.js +139 -0
  234. package/src/object-hidden.js +33 -0
  235. package/src/object-look.js +196 -0
  236. package/src/object-size.js +10 -0
  237. package/src/object-travel.js +71 -6
  238. package/src/panels/CameraPanel.jsx +7 -7
  239. package/src/panels/HdrEnvironmentControls.jsx +59 -0
  240. package/src/panels/LightPanel.jsx +25 -4
  241. package/src/panels/ObjectLookPanel.jsx +88 -0
  242. package/src/panels/PosePanel.jsx +2 -17
  243. package/src/panels/RigControlPanel.jsx +5 -53
  244. package/src/panels/SubjectBox.jsx +4 -1
  245. package/src/panels/details.css +14 -0
  246. package/src/posestudio.jsx +4 -27
  247. package/src/project-browser.jsx +3 -4
  248. package/src/project-resources.js +18 -31
  249. package/src/project.js +13 -104
  250. package/src/props.jsx +25 -280
  251. package/src/rail-dot-drag.js +23 -0
  252. package/src/render-passes.js +10 -0
  253. package/src/resource-status.jsx +1 -0
  254. package/src/result-modal.jsx +4 -19
  255. package/src/room.jsx +20 -12
  256. package/src/scene-assets.js +8 -6
  257. package/src/scene-light-place.js +72 -0
  258. package/src/scene-lights.jsx +148 -0
  259. package/src/scene-objects.js +31 -139
  260. package/src/scenes.js +15 -105
  261. package/src/semantic-edit.js +2 -5
  262. package/src/set-pieces.jsx +183 -0
  263. package/src/settings-menu.jsx +3 -11
  264. package/src/shell/BottomDock.jsx +9 -51
  265. package/src/shell/DetailsSlot.jsx +16 -200
  266. package/src/shell/GhostsMenuGroup.jsx +74 -0
  267. package/src/shell/LibrarySlot.jsx +1 -3
  268. package/src/shell/MenuBar.jsx +3 -3
  269. package/src/shell/PreferencesDialog.jsx +15 -264
  270. package/src/shell/PreferencesSlot.jsx +2 -8
  271. package/src/shell/TopBar.jsx +5 -84
  272. package/src/shell/ViewportToolbar.jsx +10 -70
  273. package/src/shell/agent-glass.css +19 -6
  274. package/src/shell/details-selection.js +42 -0
  275. package/src/shell/ghosts-menu.css +22 -0
  276. package/src/shell/glass-regions.css +29 -3
  277. package/src/shell/preferences.css +6 -1
  278. package/src/shot-authoring.js +4 -7
  279. package/src/soft-shadows.js +37 -0
  280. package/src/stage-attach-frames.js +12 -12
  281. package/src/stage-canvas-rigs.jsx +3 -3
  282. package/src/stage-capture.jsx +42 -8
  283. package/src/stage-characters.jsx +22 -216
  284. package/src/stage-editor-gizmos.jsx +6 -6
  285. package/src/stage-environment.js +93 -0
  286. package/src/stage-hdr-builtins.js +107 -0
  287. package/src/stage-hdr-image.js +116 -0
  288. package/src/stage-hdr-library.js +70 -0
  289. package/src/stage-hdr-map.js +94 -0
  290. package/src/stage-hdr.js +130 -0
  291. package/src/stage-lighting.js +149 -0
  292. package/src/stage-occlusion.js +210 -0
  293. package/src/stage-sky.jsx +159 -0
  294. package/src/stage-startup-defaults.js +5 -9
  295. package/src/studio-actions.js +12 -14
  296. package/src/studio-agent-commands.js +19 -16
  297. package/src/studio-agent-motion.js +9 -9
  298. package/src/studio-agent-protocol.js +41 -71
  299. package/src/studio-app-binding.js +25 -45
  300. package/src/studio-asset-list.js +17 -0
  301. package/src/studio-contact-sheet.js +37 -5
  302. package/src/studio-elements.js +27 -7
  303. package/src/studio-entity-bounds.js +18 -0
  304. package/src/studio-error-codes.js +8 -0
  305. package/src/studio-export-framing.js +152 -0
  306. package/src/studio-lighting-check.js +103 -0
  307. package/src/studio-motion-visual.js +150 -0
  308. package/src/studio-schema-errors.js +80 -0
  309. package/src/studio-support-gap.js +74 -0
  310. package/src/styles.css +1 -19
  311. package/src/timeline-defaults.js +5 -0
  312. package/src/timeline-extent.js +8 -20
  313. package/src/tutorial-walk.js +17 -0
  314. package/src/ui-scrub.js +1 -1
  315. package/src/use-shot-camera-tracks.js +12 -0
  316. package/src/view-ghosts.js +97 -0
  317. package/src/view-ghosts.jsx +49 -0
  318. package/src/workflow/AgentPanel.jsx +47 -166
  319. package/src/workflow/AgentProgressCard.jsx +107 -0
  320. package/src/workflow/AgentReplyText.jsx +37 -0
  321. package/src/workflow/AgentTurnSteps.jsx +84 -0
  322. package/src/workflow/LoginPanel.jsx +140 -0
  323. package/src/workflow/WorkflowBuilder.jsx +7 -23
  324. package/src/workflow/agent-client.js +54 -51
  325. package/src/workflow/agent-panel.css +71 -1
  326. package/src/workflow/agent-progress-card.css +167 -0
  327. package/src/workflow/agent-progress-view.js +47 -0
  328. package/src/workflow/agent-turn-steps.css +113 -0
  329. package/src/workflow/agent-turn.js +191 -0
  330. package/src/workflow/login-client.js +34 -0
  331. package/src/workflow/motion-input.js +18 -31
  332. package/src/workflow/node-schema.js +1 -1
  333. package/src/workflow/reply-markdown.js +69 -0
  334. package/src/workflow/scene-asset-sync.js +8 -8
  335. package/tools/agent-session-stats.mjs +61 -0
  336. package/tools/dev-full.mjs +5 -56
  337. package/tools/eslint-baseline.json +12 -13
  338. package/tools/knip-baseline.json +0 -179
  339. package/tools/loc-baseline.json +31 -81
  340. package/tools/motion3/extract-body.mjs +103 -0
  341. package/tools/motion3/extract-rig.mjs +85 -0
  342. package/tools/process-supervisor.mjs +0 -88
  343. package/tools/run-tests.mjs +8 -124
  344. package/bin/agent/motion-runtime.mjs +0 -76
  345. package/dist/ardy/cskel27-rest.json +0 -557
  346. package/dist/assets/analytics-C9pMpW7s.js +0 -1
  347. package/dist/assets/app-B_2XB9rw.css +0 -1
  348. package/dist/assets/app-DAuvKPP8.js +0 -4920
  349. package/dist/assets/config-Bx7pYsD9.js +0 -1
  350. package/dist/assets/config-DeoFTYzb.css +0 -1
  351. package/dist/assets/demo-CvKuBgy5.js +0 -1
  352. package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
  353. package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
  354. package/dist/assets/ticket-BPk2Ni95.js +0 -1
  355. package/dist/assets/workflow-jJRBR94Z.js +0 -179
  356. package/dist/demo/walk-then-stop.npz +0 -0
  357. package/mcp/ardy-prompts.mjs +0 -218
  358. package/src/ardy/auto-fix-panel.css +0 -108
  359. package/src/ardy/auto-physics.js +0 -1194
  360. package/src/ardy/client.js +0 -153
  361. package/src/ardy/convert.js +0 -313
  362. package/src/ardy/cskel27-neutral.js +0 -39
  363. package/src/ardy/cskel27.js +0 -77
  364. package/src/ardy/export.js +0 -165
  365. package/src/ardy/fix-collisions.js +0 -2257
  366. package/src/ardy/foot-lock.js +0 -230
  367. package/src/ardy/motion-calibration.js +0 -118
  368. package/src/ardy/motion-edit.js +0 -213
  369. package/src/ardy/motion-url.js +0 -55
  370. package/src/ardy/mp4-duration.js +0 -102
  371. package/src/ardy/npz.js +0 -595
  372. package/src/ardy/physics-panel.css +0 -21
  373. package/src/ardy/physics-panel.jsx +0 -89
  374. package/src/ardy/physics-refine.js +0 -111
  375. package/src/ardy/physics-review.js +0 -567
  376. package/src/ardy/physics-temporal.js +0 -54
  377. package/src/ardy/platform-fit-panel.css +0 -64
  378. package/src/ardy/platform-fit-panel.jsx +0 -41
  379. package/src/ardy/platform-fit.js +0 -326
  380. package/src/ardy/playback.js +0 -711
  381. package/src/ardy/pose-pin.js +0 -97
  382. package/src/ardy/prompt-clips.js +0 -30
  383. package/src/ardy/retime.js +0 -211
  384. package/src/ardy/root-drop.js +0 -369
  385. package/src/ardy/timeline-resize.js +0 -13
  386. package/src/ardy/to-cskel27.js +0 -225
  387. package/src/ardy/trim.js +0 -33
  388. package/src/ardy/waypoints.js +0 -369
  389. package/src/ardy-bridge-contract.js +0 -281
  390. package/src/domains/motion/motion-video-capture.js +0 -664
  391. package/src/i2v-motion-client.js +0 -77
  392. package/src/i2v-motion-studio.jsx +0 -180
  393. package/src/line-edit.js +0 -1348
  394. package/src/motion/generation.js +0 -105
  395. package/src/motion-readiness-ui.jsx +0 -67
  396. package/src/motion-readiness.js +0 -40
  397. package/src/motion-resources.js +0 -177
  398. package/src/motion-store.js +0 -65
  399. package/src/motion-trail.js +0 -601
  400. package/src/multimodel-ingest.js +0 -394
  401. package/src/panels/PromptBlocksPanel.jsx +0 -444
  402. package/src/panels/VideoCapturePanel.jsx +0 -181
  403. package/src/pose-extract/browser.js +0 -42
  404. package/src/pose-extract/detector.js +0 -100
  405. package/src/pose-extract/fit.js +0 -227
  406. package/src/pose-extract/image-frame.js +0 -51
  407. package/src/pose-extract/index.js +0 -58
  408. package/src/pose-extract/keyframes.js +0 -74
  409. package/src/pose-extract/landmarks.js +0 -162
  410. package/src/pose-extract/mirror.js +0 -153
  411. package/src/pose-extract/take.js +0 -131
  412. package/src/pose-extract/video-frames.js +0 -91
  413. package/src/take-recipe.js +0 -165
  414. package/src/trail-key-conflicts.js +0 -39
  415. package/src/trail-pick.js +0 -59
  416. package/src/use-i2v-capture-modal.js +0 -15
  417. package/tools/ardy/BRIDGE.md +0 -37
  418. package/tools/ardy/GVHMR-NPZ.md +0 -68
  419. package/tools/ardy/README.md +0 -124
  420. package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
  421. package/tools/ardy/artifacts.mjs +0 -34
  422. package/tools/ardy/bridge.mjs +0 -1732
  423. package/tools/ardy/bvh-cskel27.mjs +0 -1265
  424. package/tools/ardy/cclay_constrained_generate.py +0 -561
  425. package/tools/ardy/cclay_gvhmr_worker.py +0 -264
  426. package/tools/ardy/cclay_motion_edit.py +0 -510
  427. package/tools/ardy/cclay_sequence_generate.py +0 -705
  428. package/tools/ardy/cclay_worker.py +0 -304
  429. package/tools/ardy/dump-npz.py +0 -205
  430. package/tools/ardy/extract-rest.mjs +0 -299
  431. package/tools/ardy/extract.mjs +0 -507
  432. package/tools/ardy/footage.mjs +0 -467
  433. package/tools/ardy/gvhmr-floor.mjs +0 -102
  434. package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
  435. package/tools/ardy/gvhmr_fastpath.py +0 -168
  436. package/tools/ardy/gvhmr_trajectory.py +0 -240
  437. package/tools/ardy/merge-text-encoder.py +0 -160
  438. package/tools/ardy/mocap-metrics.mjs +0 -200
  439. package/tools/ardy/mocap-quality-gate.mjs +0 -110
  440. package/tools/ardy/motion-stabilize.mjs +0 -348
  441. package/tools/ardy/npz.mjs +0 -414
  442. package/tools/ardy/pose-to-npz.mjs +0 -106
  443. package/tools/ardy/prompt-limits.mjs +0 -5
  444. package/tools/ardy/run-edit-on-box.sh +0 -73
  445. package/tools/ardy/run-local.mjs +0 -233
  446. package/tools/ardy/run-on-box.sh +0 -593
  447. package/tools/ardy/run-sequence-on-box.sh +0 -177
  448. package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
  449. package/tools/ardy/runners/index.mjs +0 -18
  450. package/tools/ardy/runners/local.mjs +0 -455
  451. package/tools/ardy/runners/proc.mjs +0 -125
  452. package/tools/ardy/runners/remote.mjs +0 -252
  453. package/tools/ardy/setup-local.mjs +0 -189
  454. package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
  455. package/tools/ardy/setup-text-encoder.py +0 -306
  456. package/tools/ardy/smpl-cskel27.mjs +0 -178
  457. package/tools/ardy/visual-qa.mjs +0 -196
  458. package/tools/ardy/vq-car.mjs +0 -52
  459. package/tools/bench/EXP3.md +0 -61
  460. package/tools/bench/cclay_bench_extract_incam.py +0 -125
  461. package/tools/bench/cclay_bench_extract_obs.py +0 -204
  462. package/tools/bench/cclay_bench_runner.py +0 -42
  463. package/tools/bench/cube-contact.mjs +0 -162
  464. package/tools/bench/exp3.mjs +0 -152
  465. package/tools/bench/extract-bench-lib.mjs +0 -216
  466. package/tools/bench/extract-bench.mjs +0 -302
  467. package/tools/bench/fal-generate.mjs +0 -88
  468. package/tools/bench/fit/README.md +0 -189
  469. package/tools/bench/fit/camera.mjs +0 -59
  470. package/tools/bench/fit/contact.mjs +0 -419
  471. package/tools/bench/fit/footlock.mjs +0 -234
  472. package/tools/bench/fit/motion.mjs +0 -85
  473. package/tools/bench/fit/pin.mjs +0 -27
  474. package/tools/bench/fit/remote.mjs +0 -105
  475. package/tools/bench/fit-bench.mjs +0 -126
  476. package/tools/bench/fit-sanity.mjs +0 -45
  477. package/tools/bench/metrics.mjs +0 -313
  478. package/tools/bench/obs/depth.mjs +0 -258
  479. package/tools/bench/obs/extrinsics.mjs +0 -175
  480. package/tools/bench/obs/fit_mannequin_betas.py +0 -340
  481. package/tools/bench/obs/ground.mjs +0 -294
  482. package/tools/bench/obs/heading.mjs +0 -151
  483. package/tools/bench/obs/ladder.mjs +0 -555
  484. package/tools/bench/obs/mannequin-betas.json +0 -124
  485. package/tools/bench/obs/remote.mjs +0 -83
  486. package/tools/bench/obs/rest_joints.py +0 -52
  487. package/tools/bench/obs/ybot-targets.mjs +0 -49
  488. package/tools/bench/obs-bench.mjs +0 -520
  489. package/tools/bench/score.mjs +0 -343
  490. package/tools/bench/summarize.mjs +0 -86
  491. package/tools/demo-worker/README.md +0 -137
  492. package/tools/demo-worker/api-client.mjs +0 -233
  493. package/tools/demo-worker/generate.mjs +0 -301
  494. package/tools/demo-worker/index.mjs +0 -466
  495. package/tools/demo-worker/sign-core.mjs +0 -112
  496. package/tools/demo-worker/sign.mjs +0 -161
  497. package/tools/gt-render/browser.mjs +0 -182
  498. package/tools/gt-render/camera-math.mjs +0 -276
  499. package/tools/gt-render/page.mjs +0 -267
  500. package/tools/gt-render/render.mjs +0 -514
  501. package/tools/gt-render/scene-box.mjs +0 -31
  502. package/tools/gt-render/take-transform.mjs +0 -87
  503. package/tools/kimodo/compare-seam.mjs +0 -108
  504. package/tools/kimodo/constraints.mjs +0 -244
  505. package/tools/kimodo/edit.mjs +0 -259
  506. package/tools/kimodo/effector-constraints.mjs +0 -200
  507. package/tools/kimodo/generate.mjs +0 -646
  508. package/tools/kimodo/local-output.mjs +0 -265
  509. package/tools/kimodo/measure-pose.mjs +0 -65
  510. package/tools/kimodo/measure-preserve.mjs +0 -899
  511. package/tools/kimodo/measure-waypoints.mjs +0 -55
  512. package/tools/kimodo/mlx-generate.py +0 -111
  513. package/tools/kimodo/pose-constraints.mjs +0 -248
  514. package/tools/kimodo/preserve-mask.mjs +0 -543
  515. package/tools/kimodo/read-npz.mjs +0 -189
  516. package/tools/kimodo/run-edit-on-box.mjs +0 -155
  517. package/tools/kimodo/run-sequence-on-box.mjs +0 -178
  518. package/tools/kimodo/runner.mjs +0 -284
  519. package/tools/kimodo/seam-batch.mjs +0 -77
  520. package/tools/kimodo/setup-local.mjs +0 -30
  521. package/tools/kimodo/setup-on-box.sh +0 -339
  522. package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
  523. package/tools/morphgs/assets/gen_truth.py +0 -39
  524. package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
  525. package/tools/morphgs/assets/playback-check.mjs +0 -30
  526. package/tools/morphgs/demo-gate.sh +0 -26
  527. package/tools/morphgs/fbx2morphgs.mjs +0 -68
  528. package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
  529. package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
  530. package/tools/morphgs/setup-on-cluster.sh +0 -126
  531. package/tools/projflow/driver.py +0 -1069
  532. package/tools/projflow/generate.mjs +0 -805
  533. package/tools/projflow/hml22-to-cskel27.mjs +0 -735
  534. package/tools/projflow/line-edit-job.mjs +0 -547
  535. package/tools/projflow/replay.mjs +0 -452
  536. package/tools/projflow/runner.mjs +0 -269
  537. package/tools/projflow/service.mjs +0 -742
  538. package/tools/track/DESIGN.md +0 -513
  539. package/tools/track/backfill-provenance.mjs +0 -116
  540. package/tools/track/budget.mjs +0 -8
  541. package/tools/track/check-rig.mjs +0 -209
  542. package/tools/track/diagnostics.schema.json +0 -60
  543. package/tools/track/export-rig.mjs +0 -256
  544. package/tools/track/fallback.mjs +0 -7
  545. package/tools/track/fk-parity-fixture.mjs +0 -161
  546. package/tools/track/gate.mjs +0 -349
  547. package/tools/track/masks.mjs +0 -318
  548. package/tools/track/metrics.mjs +0 -201
  549. package/tools/track/publish-obs.mjs +0 -108
  550. package/tools/track/py/check_env.py +0 -61
  551. package/tools/track/py/eval_lr.py +0 -229
  552. package/tools/track/py/lr_viterbi.py +0 -294
  553. package/tools/track/py/masks.py +0 -405
  554. package/tools/track/py/objective.py +0 -382
  555. package/tools/track/py/rig.py +0 -508
  556. package/tools/track/py/scene.py +0 -326
  557. package/tools/track/py/test_joint_indices.py +0 -123
  558. package/tools/track/py/test_lr_viterbi.py +0 -195
  559. package/tools/track/py/test_masks.py +0 -229
  560. package/tools/track/py/test_rig.py +0 -288
  561. package/tools/track/py/test_scene.py +0 -354
  562. package/tools/track/py/test_track.py +0 -328
  563. package/tools/track/py/track.py +0 -411
  564. package/tools/track/remote.mjs +0 -140
  565. package/tools/track/rig-dump.mjs +0 -227
  566. package/tools/track/run-box-tests.mjs +0 -14
  567. package/tools/track/run-box.mjs +0 -168
  568. package/tools/track/setup-box.sh +0 -59
  569. package/tools/track/study-2d.mjs +0 -728
  570. /package/src/{ardy → motion}/collision-blockers.js +0 -0
  571. /package/src/{ardy → motion}/ground.js +0 -0
  572. /package/src/{ardy → motion}/ik-drag.js +0 -0
  573. /package/src/{ardy → motion}/ik-key-json.js +0 -0
  574. /package/src/{ardy → motion}/ik.js +0 -0
  575. /package/src/{ardy → motion}/physics-surface.js +0 -0
  576. /package/src/{ardy → motion}/playback-clock.js +0 -0
  577. /package/src/{ardy → motion}/range-pin.js +0 -0
@@ -0,0 +1,135 @@
1
+ // CLIProxyAPI, the one provider CozyClay adds to pi's built-ins (moved out of providers.mjs for #pi-login).
2
+ // Its login is pi's own api-key login shape: the flow asks for the server address and then the key, and
3
+ // returns one api-key credential that carries the address as provider env (CLIPROXY_BASE_URL), so an app
4
+ // opened from the Dock needs no shell variable. The address is read when the registry is built: the
5
+ // environment wins, then the stored credential's env, then the default local port.
6
+
7
+ const CLIPROXY_DEFAULT_BASE = "http://127.0.0.1:8317";
8
+ const BASE_ENV = "CLIPROXY_BASE_URL";
9
+ const KEY_ENV = "CLIPROXY_API_KEY";
10
+
11
+ /** A server address as typed, cleaned: http(s), no trailing slash, no trailing /v1 (the models add it). */
12
+ function cleanBase(value) {
13
+ const url = new URL(String(value ?? "").trim());
14
+ if (!["http:", "https:"].includes(url.protocol)) throw new Error("The server address must start with http:// or https://.");
15
+ return url.href.replace(/\/+$/, "").replace(/\/v1$/, "");
16
+ }
17
+
18
+ /** The address the registry builds CLIProxyAPI's models on: environment, then the stored credential, then the default. */
19
+ export function cliproxyBase({ env = process.env, credential } = {}) {
20
+ const fromEnv = typeof env?.[BASE_ENV] === "string" ? env[BASE_ENV].trim() : "";
21
+ return (fromEnv || credential?.env?.[BASE_ENV] || CLIPROXY_DEFAULT_BASE).replace(/\/$/, "");
22
+ }
23
+
24
+ /** pi ApiKeyAuth: login asks for the address, then the key; resolve prefers the stored key, then CLIPROXY_API_KEY. */
25
+ function cliproxyAuth() {
26
+ return {
27
+ name: "CLIProxyAPI server and key",
28
+ login: async (interaction) => {
29
+ interaction.signal.throwIfAborted();
30
+ const address = await interaction.prompt({ type: "text", message: "CLIProxyAPI server address", placeholder: CLIPROXY_DEFAULT_BASE });
31
+ const base = cleanBase(address.trim() || CLIPROXY_DEFAULT_BASE);
32
+ interaction.signal.throwIfAborted();
33
+ const key = await interaction.prompt({ type: "secret", message: "CLIProxyAPI key" });
34
+ interaction.signal.throwIfAborted();
35
+ return { type: "api_key", key, env: { [BASE_ENV]: base } };
36
+ },
37
+ resolve: async ({ ctx, credential, signal }) => {
38
+ signal.throwIfAborted();
39
+ if (credential?.key) return { auth: { apiKey: credential.key }, env: credential.env, source: "stored credential" };
40
+ const key = await ctx.env(KEY_ENV);
41
+ return key ? { auth: { apiKey: key }, source: KEY_ENV } : undefined;
42
+ },
43
+ };
44
+ }
45
+
46
+ export async function cliproxyProvider({ baseUrl, env = process.env } = {}) {
47
+ const base = (baseUrl ?? cliproxyBase({ env })).replace(/\/$/, "");
48
+ const [{ createProvider }, { openaiProvider }, { anthropicProvider }, openaiResponsesApi, anthropicMessagesApi] = await Promise.all([
49
+ import("@earendil-works/pi-ai"),
50
+ import("@earendil-works/pi-ai/providers/openai"),
51
+ import("@earendil-works/pi-ai/providers/anthropic"),
52
+ import("@earendil-works/pi-ai/api/openai-responses"),
53
+ import("@earendil-works/pi-ai/api/anthropic-messages"),
54
+ ]);
55
+ const ids = new Set();
56
+ const models = [
57
+ ...openaiProvider().getModels().map((model) => ({ ...model, provider: "cliproxy", baseUrl: `${base}/v1` })),
58
+ ...anthropicProvider().getModels().map((model) => ({
59
+ ...model,
60
+ provider: "cliproxy",
61
+ baseUrl: base,
62
+ // CLIProxyAPI's /v1/messages rejects messages[].output_config and thinking.block_binding, so keep
63
+ // adaptive thinking (forceAdaptiveThinking) but drop the mid-conversation output_config shape.
64
+ compat: { ...model.compat, supportsMidConvoEffort: false },
65
+ })),
66
+ ].filter((model) => !ids.has(model.id) && ids.add(model.id));
67
+ return createProvider({
68
+ id: "cliproxy",
69
+ name: "CLIProxyAPI",
70
+ baseUrl: base,
71
+ auth: { apiKey: cliproxyAuth() },
72
+ models,
73
+ api: { "openai-responses": openaiResponsesApi, "anthropic-messages": anthropicMessagesApi },
74
+ });
75
+ }
76
+
77
+ /** The proxy's own /v1/models (id -> owner), fetched once per registry; a failed fetch is asked again next time. */
78
+ export async function registryLiveCliproxy(models, apiKey, state) {
79
+ if (!state.cliproxyFetch) {
80
+ state.cliproxyFetch = Promise.resolve().then(async () => {
81
+ const provider = models?.getProvider?.("cliproxy");
82
+ const model = provider?.getModels?.().find((entry) => entry.api === "openai-responses");
83
+ if (!model?.baseUrl || !apiKey) throw new Error("CLIProxyAPI is not configured");
84
+ const response = await fetch(`${model.baseUrl}/models`, {
85
+ headers: { Authorization: `Bearer ${apiKey}` },
86
+ signal: AbortSignal.timeout(5000),
87
+ });
88
+ if (!response.ok) throw new Error(`CLIProxyAPI models request failed: ${response.status}`);
89
+ const body = await response.json();
90
+ // id -> owned_by: the owner is how CLIProxyAPI routes a model (openai, anthropic, or an
91
+ // openai-compatibility upstream name), so it picks the wire format for models pi does not know.
92
+ return new Map((Array.isArray(body?.data) ? body.data : [])
93
+ .map((entry) => typeof entry === "string" ? [entry, undefined] : [entry?.id, typeof entry?.owned_by === "string" ? entry.owned_by : undefined])
94
+ .filter(([id]) => id));
95
+ }).catch(() => { state.cliproxyFetch = null; return null; });
96
+ }
97
+ return state.cliproxyFetch;
98
+ }
99
+
100
+ const CLIPROXY_NON_CHAT = /^gpt-image-|-auto-review$/;
101
+
102
+ /** A live CLIProxyAPI model pi's catalogue does not know yet (#400 follow-up), built on the newest
103
+ * catalogue model of the same family and wire format: `owned_by: openai` rides openai-responses,
104
+ * `owned_by: anthropic` rides anthropic-messages. Other owners (openai-compatibility upstreams such as
105
+ * opencode-go) are left out: they need upstream-specific headers the proxy does not pass through. */
106
+ export function cliproxyLiveModel(id, owner, knownModels) {
107
+ if (CLIPROXY_NON_CHAT.test(id)) return null;
108
+ const api = owner === "openai" ? "openai-responses" : owner === "anthropic" ? "anthropic-messages" : null;
109
+ if (!api) return null;
110
+ const candidates = knownModels.filter((model) => model.api === api);
111
+ const family = owner === "anthropic" ? id.match(/^claude-[a-z]+/)?.[0] : id.match(/^gpt-\d+/)?.[0];
112
+ const sameFamily = family ? candidates.filter((model) => model.id.startsWith(family)) : [];
113
+ const template = [...(sameFamily.length ? sameFamily : candidates)].sort((a, b) => b.id.localeCompare(a.id, undefined, { numeric: true }))[0];
114
+ if (!template) return null;
115
+ // The 5-5 claude generation rejects thinking {type:"disabled"}; a null "off" makes pi-ai send no thinking block (#723).
116
+ const generation = owner === "anthropic" ? id.match(/-(\d+)-(\d+)$/) : null;
117
+ if (generation && Number(generation[1]) >= 5) return { ...template, id, name: id, thinkingLevelMap: { ...template.thinkingLevelMap, off: null } };
118
+ return { ...template, id, name: id };
119
+ }
120
+
121
+ export function extendCliproxyProvider(provider, live, state) {
122
+ if (state.extendedProviders.has(provider)) return false;
123
+ const getModels = provider.getModels.bind(provider);
124
+ const knownModels = getModels();
125
+ const known = new Set(knownModels.map((model) => model.id));
126
+ const additions = [...live]
127
+ .filter(([id]) => !known.has(id))
128
+ .map(([id, owner]) => cliproxyLiveModel(id, owner, knownModels))
129
+ .filter(Boolean)
130
+ .sort((a, b) => b.id.localeCompare(a.id, undefined, { numeric: true }));
131
+ if (!additions.length) return false;
132
+ provider.getModels = () => [...getModels(), ...additions];
133
+ state.extendedProviders.add(provider);
134
+ return true;
135
+ }
@@ -1,4 +1,5 @@
1
1
  import { PROVIDERS } from "./providers.mjs";
2
+ import { readCredentials, removeCredential, setCredential } from "./provider-keys.mjs";
2
3
 
3
4
  const codexId = "openai-codex";
4
5
  const apiProvider = (id) => PROVIDERS.find((provider) => provider.id === id);
@@ -44,7 +45,12 @@ function rawOAuth(credential) {
44
45
  };
45
46
  }
46
47
 
47
- export function createCredentialStore({ auth, keys, env = process.env }) {
48
+ // pi's CredentialStore for every provider pi can log in to (#pi-login): ChatGPT stays in codex-auth's file (its
49
+ // id_token carries the plan and account the image routes need), a bare api key in providers.json, and what pi's
50
+ // login flows return beyond that (OAuth tokens, api keys with provider env) in auth.json. A credential that
51
+ // lands in one file is removed from the other, so one provider never has two.
52
+
53
+ export function createCredentialStore({ auth, keys, env = process.env, credentialFile = { read: readCredentials, set: setCredential, remove: removeCredential } }) {
48
54
  const configured = (id) => apiProvider(id);
49
55
  return {
50
56
  async read(providerId, { signal } = {}) {
@@ -54,8 +60,11 @@ export function createCredentialStore({ auth, keys, env = process.env }) {
54
60
  return mapOAuth(stored);
55
61
  }
56
62
  const provider = configured(providerId);
57
- if (!provider) return undefined;
58
- const key = envValue(provider, env) ?? keys.readKeys()[providerId];
63
+ const fromEnv = provider && envValue(provider, env);
64
+ if (fromEnv) return { type: "api_key", key: fromEnv };
65
+ const credential = credentialFile.read()[providerId];
66
+ if (credential) return credential;
67
+ const key = keys.readKeys()[providerId];
59
68
  return key ? { type: "api_key", key } : undefined;
60
69
  },
61
70
  async list({ signal } = {}) {
@@ -68,8 +77,13 @@ export function createCredentialStore({ auth, keys, env = process.env }) {
68
77
  if (stored) result.push({ providerId: provider.id, type: "oauth", source: "chatgpt" });
69
78
  continue;
70
79
  }
71
- const source = envValue(provider, env) ? "env" : saved[provider.id] ? "file" : null;
72
- if (source) result.push({ providerId: provider.id, type: "api_key", source });
80
+ const source = envValue(provider, env) ? "env" : saved[provider.id] || credentialFile.read()[provider.id] ? "file" : null;
81
+ if (source) result.push({ providerId: provider.id, type: credentialFile.read()[provider.id]?.type ?? "api_key", source });
82
+ }
83
+ // providers outside the curated list that a pi login stored
84
+ const listed = new Set(result.map((entry) => entry.providerId));
85
+ for (const [providerId, credential] of Object.entries({ ...Object.fromEntries(Object.keys(saved).map((id) => [id, { type: "api_key" }])), ...credentialFile.read() })) {
86
+ if (!listed.has(providerId)) result.push({ providerId, type: credential.type, source: "file" });
73
87
  }
74
88
  return result;
75
89
  },
@@ -79,14 +93,23 @@ export function createCredentialStore({ auth, keys, env = process.env }) {
79
93
  const next = await fn(current);
80
94
  if (next === undefined) return current;
81
95
  if (providerId === codexId) await auth.writeStored(rawOAuth(next));
82
- else keys.setKey(providerId, next.key);
96
+ else if (next.type === "api_key" && !next.env) {
97
+ keys.setKey(providerId, next.key);
98
+ credentialFile.remove(providerId);
99
+ } else {
100
+ credentialFile.set(providerId, next);
101
+ if (keys.readKeys()[providerId]) keys.removeKey(providerId);
102
+ }
83
103
  return next;
84
104
  }, signal);
85
105
  },
86
106
  async delete(providerId, { signal } = {}) {
87
107
  await serialized(providerId, async () => {
88
108
  if (providerId === codexId) await auth.logout();
89
- else if (configured(providerId)) keys.removeKey(providerId);
109
+ else {
110
+ if (keys.readKeys()[providerId]) keys.removeKey(providerId);
111
+ credentialFile.remove(providerId);
112
+ }
90
113
  }, signal);
91
114
  },
92
115
  };
@@ -1,6 +1,7 @@
1
1
  import { Value } from "typebox/value";
2
2
  import { STUDIO_TOOL_LABELS } from "../../src/studio-agent-protocol.js";
3
3
  import { toTypeBox } from "./schema-to-typebox.mjs";
4
+ import { toolArgumentPreparer } from "./tool-arguments.mjs";
4
5
 
5
6
 
6
7
  function dataUrlImage(dataUrl) {
@@ -28,12 +29,14 @@ function formatToolError(error) {
28
29
  /** Convert CozyClay's handler catalogue into pi AgentTools. */
29
30
  export function toAgentTools(tools, { emit, signal, labels = STUDIO_TOOL_LABELS } = {}) {
30
31
  return (Array.isArray(tools) ? tools : []).map((tool) => {
31
- const parameters = toTypeBox(tool.parameters || { type: "object", properties: {}, additionalProperties: false });
32
+ const schema = tool.parameters || { type: "object", properties: {}, additionalProperties: false }, parameters = toTypeBox(schema);
32
33
  return {
33
34
  name: tool.name,
34
35
  label: labels?.[tool.name] ?? tool.label ?? tool.name.replaceAll("_", " "),
35
36
  description: tool.description || tool.name,
36
37
  parameters,
38
+ // Before pi's own check: array texts parsed, unknown fields named (tool-arguments.mjs).
39
+ prepareArguments: toolArgumentPreparer(tool.name, schema),
37
40
  // pi's low-level `Agent` calls `execute(id, params, signal, onUpdate)`;
38
41
  // pi's `AgentHarness` calls `execute(id, params, onUpdate, toolContext,
39
42
  // invocation, context)` where `context.abortSignal` is the REAL abort
@@ -54,3 +54,35 @@ export function removeKey(id) {
54
54
  return next;
55
55
  }
56
56
 
57
+ // Credentials pi's login flows return that are more than a bare key (#pi-login): OAuth tokens (Claude Pro/Max,
58
+ // GitHub Copilot, xAI, Kimi, OpenRouter) and api-key credentials that carry provider env (CLIProxyAPI's server
59
+ // address). They are kept as pi's canonical Credential, one per provider id, in auth.json beside providers.json;
60
+ // a bare api key stays in providers.json as before. credential-store.mjs decides which file a write goes to.
61
+ function authPath() {
62
+ return join(configDir(), "auth.json");
63
+ }
64
+
65
+ /** { [providerId]: Credential } from auth.json; a missing or unreadable file is {}. */
66
+ export function readCredentials() {
67
+ try {
68
+ const value = JSON.parse(readFileSync(authPath(), "utf8"));
69
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
70
+ return Object.fromEntries(Object.entries(value).filter(([id, entry]) => id && entry && typeof entry === "object" && ["api_key", "oauth"].includes(entry.type)));
71
+ } catch {
72
+ return {};
73
+ }
74
+ }
75
+
76
+ export function setCredential(id, credential) {
77
+ const next = { ...readCredentials(), [id]: credential };
78
+ writeSecureJson(authPath(), next);
79
+ return next;
80
+ }
81
+
82
+ export function removeCredential(id) {
83
+ const current = readCredentials();
84
+ if (!(id in current)) return current;
85
+ delete current[id];
86
+ writeSecureJson(authPath(), current);
87
+ return current;
88
+ }
@@ -0,0 +1,110 @@
1
+ // pi's provider login (the coding agent's /login) over HTTP for the agent panel (#pi-login). The sidecar
2
+ // runs `models.login(providerId, type, interaction)` itself; the interaction is a queue the panel reads:
3
+ // { type: "prompt", promptId, prompt: { type: text|secret|select|manual_code, message, placeholder?, options? } }
4
+ // { type: "event", event: pi AuthEvent (info, auth_url, device_code, progress) }
5
+ // { type: "prompt_closed", promptId } pi withdrew a prompt (e.g. a callback beat the pasted code)
6
+ // { type: "done", ok, message? }
7
+ // and answers with the prompt's value. Nothing the user types is ever put in the queue. pi persists the
8
+ // credential through the registry's CredentialStore (credential-store.mjs); onSignedIn rebuilds the registry.
9
+ import { randomUUID } from "node:crypto";
10
+
11
+ const WAIT_MS = 25_000;
12
+ const KEEP_MS = 60_000;
13
+
14
+ /** The login methods pi offers for each provider, as the panel lists them (subscriptions first). */
15
+ export async function loginOptions(models, { chatgpt } = {}) {
16
+ const options = [];
17
+ for (const provider of models.getProviders()) {
18
+ const auth = provider.auth ?? {};
19
+ // ChatGPT keeps CozyClay's own sign-in: its id_token carries the plan and account the image routes read.
20
+ const methods = provider.id === "openai-codex"
21
+ ? [{ type: "chatgpt", label: "Sign in with ChatGPT (Plus/Pro)", subscription: true }]
22
+ : [
23
+ ...(auth.oauth?.login ? [{ type: "oauth", label: auth.oauth.loginLabel || auth.oauth.name, subscription: !!auth.oauth.isSubscription }] : []),
24
+ ...(auth.apiKey?.login ? [{ type: "api_key", label: auth.apiKey.name }] : []),
25
+ ];
26
+ if (!methods.length) continue;
27
+ const check = provider.id === "openai-codex"
28
+ ? (chatgpt?.() ? { type: "oauth", source: "ChatGPT" } : undefined)
29
+ : await models.checkAuth(provider.id).catch(() => undefined);
30
+ options.push({ id: provider.id, name: provider.name ?? provider.id, methods, connected: !!check, via: check?.type ?? null, source: check?.source ?? null });
31
+ }
32
+ const rank = (entry) => (entry.connected ? 0 : entry.methods.some((method) => method.subscription) ? 1 : 2);
33
+ return options.sort((a, b) => rank(a) - rank(b) || a.name.localeCompare(b.name));
34
+ }
35
+
36
+ export function createLoginFlows({ getModels, onSignedIn }) {
37
+ const flows = new Map();
38
+ const wake = (flow) => { for (const waiter of [...flow.waiters]) waiter(); };
39
+ const push = (flow, entry) => { flow.events.push(entry); wake(flow); };
40
+ const finish = (flow, entry) => {
41
+ flow.done = true;
42
+ for (const pending of flow.pending.values()) pending.reject(new Error("The login ended."));
43
+ flow.pending.clear();
44
+ push(flow, entry);
45
+ setTimeout(() => flows.delete(flow.id), KEEP_MS).unref?.();
46
+ };
47
+
48
+ function start(providerId, type) {
49
+ const flow = { id: randomUUID(), providerId, type, events: [], waiters: new Set(), pending: new Map(), controller: new AbortController(), done: false };
50
+ const interaction = {
51
+ signal: flow.controller.signal,
52
+ prompt(prompt) {
53
+ return new Promise((resolve, reject) => {
54
+ const promptId = randomUUID();
55
+ flow.pending.set(promptId, { resolve, reject });
56
+ const { signal, ...shown } = prompt;
57
+ push(flow, { type: "prompt", promptId, prompt: shown });
58
+ signal?.addEventListener("abort", () => {
59
+ if (!flow.pending.delete(promptId)) return;
60
+ push(flow, { type: "prompt_closed", promptId });
61
+ reject(signal.reason ?? new Error("The prompt was withdrawn."));
62
+ }, { once: true });
63
+ });
64
+ },
65
+ notify(event) { push(flow, { type: "event", event }); },
66
+ };
67
+ flows.set(flow.id, flow);
68
+ void (async () => {
69
+ try {
70
+ await (await getModels()).login(providerId, type, interaction);
71
+ await onSignedIn?.(providerId);
72
+ finish(flow, { type: "done", ok: true });
73
+ } catch (error) {
74
+ finish(flow, { type: "done", ok: false, message: flow.controller.signal.aborted ? "The login was cancelled." : String(error?.message || error || "The login failed.") });
75
+ }
76
+ })();
77
+ return flow.id;
78
+ }
79
+
80
+ /** Entries after `after`, waiting up to WAIT_MS for the first new one; null for an unknown flow. */
81
+ async function read(id, after = 0) {
82
+ const flow = flows.get(id);
83
+ if (!flow) return null;
84
+ if (flow.events.length <= after && !flow.done) {
85
+ await new Promise((resolve) => {
86
+ const done = () => { clearTimeout(timer); flow.waiters.delete(done); resolve(); };
87
+ const timer = setTimeout(done, WAIT_MS);
88
+ flow.waiters.add(done);
89
+ });
90
+ }
91
+ return { events: flow.events.slice(after), next: flow.events.length, done: flow.done };
92
+ }
93
+
94
+ function answer(id, promptId, value) {
95
+ const pending = flows.get(id)?.pending.get(promptId);
96
+ if (!pending) return false;
97
+ flows.get(id).pending.delete(promptId);
98
+ pending.resolve(String(value ?? ""));
99
+ return true;
100
+ }
101
+
102
+ function cancel(id) {
103
+ const flow = flows.get(id);
104
+ if (!flow || flow.done) return false;
105
+ flow.controller.abort();
106
+ return true;
107
+ }
108
+
109
+ return { start, read, answer, cancel };
110
+ }
@@ -0,0 +1,99 @@
1
+ // Provider routes (moved out of agent-routes.mjs):
2
+ // GET /agent/providers the curated providers' sign-in state (MCP and older panels)
3
+ // PUT /agent/providers/:id { key }: store a bare api key for a curated provider
4
+ // DELETE /agent/providers/:id forget a curated provider's key
5
+ // and pi's own login (#pi-login), which the panel uses for every provider pi ships:
6
+ // GET /agent/logins each provider's login methods and whether it is connected
7
+ // POST /agent/logins { providerId, type: "oauth" | "api_key" } -> { flowId }
8
+ // GET /agent/logins/:flowId?after=n the flow's prompts and events after n (long poll, provider-login.mjs)
9
+ // POST /agent/logins/:flowId/answer { promptId, value }
10
+ // DELETE /agent/logins/:flowId cancel the flow
11
+ // POST /agent/logout/:providerId pi's logout for that provider
12
+ // Every credential change calls onChange, so the model registry is rebuilt with what was just stored.
13
+ import { createLoginFlows, loginOptions } from "./provider-login.mjs";
14
+
15
+ const KEY_PATH = /^\/agent\/providers\/([^/]+)$/;
16
+ const FLOW_PATH = /^\/agent\/logins\/([^/]+)(\/answer)?$/;
17
+ const LOGOUT_PATH = /^\/agent\/logout\/([^/]+)$/;
18
+
19
+ /** Returns the route handler for one sidecar; `getModels` is its registry, `onChange` rebuilds it. */
20
+ export function createProviderRoutes({ auth, getModels, onChange, json, readBody }) {
21
+ const flows = createLoginFlows({ getModels, onSignedIn: () => onChange?.() });
22
+ return async function handleProviderRoute(req, res, path) {
23
+ if (path === "/agent/providers" && req.method === "GET") {
24
+ const { providerStatus } = await import("./providers.mjs");
25
+ try { json(res, 200, { providers: providerStatus({ auth }) }); }
26
+ catch { json(res, 500, { error: "provider credentials unavailable" }); }
27
+ return true;
28
+ }
29
+ const keyMatch = KEY_PATH.exec(path);
30
+ if (keyMatch && ["PUT", "DELETE"].includes(req.method)) return keyRoute(req, res, keyMatch[1]);
31
+ if (path === "/agent/logins" && req.method === "GET") {
32
+ try { json(res, 200, { providers: await loginOptions(await getModels(), { chatgpt: () => !!auth.status?.().signedIn }) }); }
33
+ catch (error) { json(res, 502, { error: String(error?.message || error) }); }
34
+ return true;
35
+ }
36
+ if (path === "/agent/logins" && req.method === "POST") {
37
+ let value;
38
+ try { value = await readBody(req); } catch { json(res, 400, { error: "invalid request" }); return true; }
39
+ const models = await getModels();
40
+ const provider = models.getProvider(String(value?.providerId ?? ""));
41
+ const method = value?.type === "oauth" ? provider?.auth?.oauth : value?.type === "api_key" ? provider?.auth?.apiKey : null;
42
+ if (!provider || !method?.login || provider.id === "openai-codex") { json(res, 400, { error: "That provider has no such login." }); return true; }
43
+ json(res, 200, { flowId: flows.start(provider.id, value.type) });
44
+ return true;
45
+ }
46
+ const flowMatch = FLOW_PATH.exec(path);
47
+ if (flowMatch && !flowMatch[2] && req.method === "GET") {
48
+ const after = Number(new URL(req.url, "http://127.0.0.1").searchParams.get("after")) || 0;
49
+ const page = await flows.read(flowMatch[1], after);
50
+ if (!page) json(res, 404, { error: "login not found" }); else json(res, 200, page);
51
+ return true;
52
+ }
53
+ if (flowMatch && flowMatch[2] && req.method === "POST") {
54
+ let value;
55
+ try { value = await readBody(req); } catch { json(res, 400, { error: "invalid request" }); return true; }
56
+ if (!flows.answer(flowMatch[1], String(value?.promptId ?? ""), value?.value)) { json(res, 409, { error: "That prompt is no longer open." }); return true; }
57
+ json(res, 200, { ok: true });
58
+ return true;
59
+ }
60
+ if (flowMatch && !flowMatch[2] && req.method === "DELETE") {
61
+ json(res, 200, { ok: flows.cancel(flowMatch[1]) });
62
+ return true;
63
+ }
64
+ const logoutMatch = LOGOUT_PATH.exec(path);
65
+ if (logoutMatch && req.method === "POST") {
66
+ if (logoutMatch[1] === "openai-codex") { json(res, 400, { error: "Use Sign out for ChatGPT." }); return true; }
67
+ try { await (await getModels()).logout(logoutMatch[1]); }
68
+ catch (error) { json(res, 500, { error: String(error?.message || error) }); return true; }
69
+ await onChange?.();
70
+ json(res, 200, { ok: true });
71
+ return true;
72
+ }
73
+ return false;
74
+ };
75
+
76
+ async function keyRoute(req, res, id) {
77
+ const { PROVIDERS } = await import("./providers.mjs");
78
+ const keys = await import("./provider-keys.mjs");
79
+ const { createCredentialStore } = await import("./credential-store.mjs");
80
+ if (!PROVIDERS.some((provider) => provider.id === id)) { json(res, 404, { error: "provider not found" }); return true; }
81
+ const credentials = createCredentialStore({ auth, keys });
82
+ let key;
83
+ if (req.method === "PUT") {
84
+ if (id === "openai-codex") { json(res, 400, { error: "Use ChatGPT sign-in for OpenAI Codex." }); return true; }
85
+ try {
86
+ const value = await readBody(req);
87
+ if (typeof value?.key !== "string" || !value.key.trim()) throw new Error("Invalid key.");
88
+ key = value.key.trim();
89
+ } catch { json(res, 400, { error: "a non-empty API key is required" }); return true; }
90
+ }
91
+ try {
92
+ if (req.method === "PUT") await credentials.modify(id, async () => ({ type: "api_key", key }));
93
+ else await credentials.delete(id);
94
+ } catch { json(res, 500, { error: "provider credentials could not be saved" }); return true; }
95
+ await onChange?.();
96
+ json(res, 200, { ok: true });
97
+ return true;
98
+ }
99
+ }
@@ -2,6 +2,9 @@ import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import * as defaultAuth from "../codex-auth.mjs";
3
3
  import * as defaultKeys from "./provider-keys.mjs";
4
4
  import { createCredentialStore } from "./credential-store.mjs";
5
+ import { readCredentials } from "./provider-keys.mjs";
6
+ import { cliproxyBase, cliproxyProvider, extendCliproxyProvider, registryLiveCliproxy } from "./cliproxy-provider.mjs";
7
+ export { cliproxyLiveModel } from "./cliproxy-provider.mjs";
5
8
 
6
9
  // #379 (F2 pass 5 finding 4): pi never forwards a per-request `fetch` to the
7
10
  // codex API module — `pi-agent-core`'s `createRequestOptions` (the options
@@ -69,37 +72,6 @@ const factories = {
69
72
  openrouter: "openrouterProvider",
70
73
  };
71
74
 
72
- async function cliproxyProvider({ baseUrl, env = process.env } = {}) {
73
- const base = (baseUrl ?? env.CLIPROXY_BASE_URL ?? "http://127.0.0.1:8317").replace(/\/$/, "");
74
- const [{ createProvider, envApiKeyAuth }, { openaiProvider }, { anthropicProvider }, openaiResponsesApi, anthropicMessagesApi] = await Promise.all([
75
- import("@earendil-works/pi-ai"),
76
- import("@earendil-works/pi-ai/providers/openai"),
77
- import("@earendil-works/pi-ai/providers/anthropic"),
78
- import("@earendil-works/pi-ai/api/openai-responses"),
79
- import("@earendil-works/pi-ai/api/anthropic-messages"),
80
- ]);
81
- const ids = new Set();
82
- const models = [
83
- ...openaiProvider().getModels().map((model) => ({ ...model, provider: "cliproxy", baseUrl: `${base}/v1` })),
84
- ...anthropicProvider().getModels().map((model) => ({
85
- ...model,
86
- provider: "cliproxy",
87
- baseUrl: base,
88
- // CLIProxyAPI's /v1/messages rejects messages[].output_config and thinking.block_binding, so keep
89
- // adaptive thinking (forceAdaptiveThinking) but drop the mid-conversation output_config shape.
90
- compat: { ...model.compat, supportsMidConvoEffort: false },
91
- })),
92
- ].filter((model) => !ids.has(model.id) && ids.add(model.id));
93
- return createProvider({
94
- id: "cliproxy",
95
- name: "CLIProxyAPI",
96
- baseUrl: base,
97
- auth: { apiKey: envApiKeyAuth("CLIProxyAPI key", ["CLIPROXY_API_KEY"]) },
98
- models,
99
- api: { "openai-responses": openaiResponsesApi, "anthropic-messages": anthropicMessagesApi },
100
- });
101
- }
102
-
103
75
  const providerConfig = (id) => PROVIDERS.find((provider) => provider.id === id);
104
76
 
105
77
  export async function loadProvider(id, options = {}) {
@@ -159,7 +131,7 @@ async function registryLiveCatalogue(models, codex) {
159
131
  state.fetch = Promise.resolve().then(() => codex.listModels()).then((result) => {
160
132
  state.models = liveModelsCodex(result);
161
133
  return state.models;
162
- }).catch(() => []);
134
+ }).catch(() => { state.fetch = null; return []; });
163
135
  }
164
136
  return state.fetch;
165
137
  }
@@ -176,75 +148,17 @@ async function registerLiveCodexModels(models, codex) {
176
148
  return live;
177
149
  }
178
150
 
179
- async function registryLiveCliproxy(models, apiKey) {
180
- const state = catalogueState(models);
181
- if (!state.cliproxyFetch) {
182
- state.cliproxyFetch = Promise.resolve().then(async () => {
183
- const provider = models?.getProvider?.("cliproxy");
184
- const model = provider?.getModels?.().find((entry) => entry.api === "openai-responses");
185
- if (!model?.baseUrl || !apiKey) throw new Error("CLIProxyAPI is not configured");
186
- const response = await fetch(`${model.baseUrl}/models`, {
187
- headers: { Authorization: `Bearer ${apiKey}` },
188
- signal: AbortSignal.timeout(5000),
189
- });
190
- if (!response.ok) throw new Error(`CLIProxyAPI models request failed: ${response.status}`);
191
- const body = await response.json();
192
- // id -> owned_by: the owner is how CLIProxyAPI routes a model (openai, anthropic, or an
193
- // openai-compatibility upstream name), so it picks the wire format for models pi does not know.
194
- return new Map((Array.isArray(body?.data) ? body.data : [])
195
- .map((entry) => typeof entry === "string" ? [entry, undefined] : [entry?.id, typeof entry?.owned_by === "string" ? entry.owned_by : undefined])
196
- .filter(([id]) => id));
197
- }).catch(() => null);
198
- }
199
- return state.cliproxyFetch;
200
- }
201
-
202
- const CLIPROXY_NON_CHAT = /^gpt-image-|-auto-review$/;
203
-
204
- /** A live CLIProxyAPI model pi's catalogue does not know yet (#400 follow-up), built on the newest
205
- * catalogue model of the same family and wire format: `owned_by: openai` rides openai-responses,
206
- * `owned_by: anthropic` rides anthropic-messages. Other owners (openai-compatibility upstreams such as
207
- * opencode-go) are left out: they need upstream-specific headers the proxy does not pass through. */
208
- export function cliproxyLiveModel(id, owner, knownModels) {
209
- if (CLIPROXY_NON_CHAT.test(id)) return null;
210
- const api = owner === "openai" ? "openai-responses" : owner === "anthropic" ? "anthropic-messages" : null;
211
- if (!api) return null;
212
- const candidates = knownModels.filter((model) => model.api === api);
213
- const family = owner === "anthropic" ? id.match(/^claude-[a-z]+/)?.[0] : id.match(/^gpt-\d+/)?.[0];
214
- const sameFamily = family ? candidates.filter((model) => model.id.startsWith(family)) : [];
215
- const template = [...(sameFamily.length ? sameFamily : candidates)].sort((a, b) => b.id.localeCompare(a.id, undefined, { numeric: true }))[0];
216
- if (!template) return null;
217
- // The 5-5 claude generation rejects thinking {type:"disabled"}; a null "off" makes pi-ai send no thinking block (#723).
218
- const generation = owner === "anthropic" ? id.match(/-(\d+)-(\d+)$/) : null;
219
- if (generation && Number(generation[1]) >= 5) return { ...template, id, name: id, thinkingLevelMap: { ...template.thinkingLevelMap, off: null } };
220
- return { ...template, id, name: id };
221
- }
222
-
223
- function extendCliproxyProvider(provider, live, state) {
224
- if (state.extendedProviders.has(provider)) return false;
225
- const getModels = provider.getModels.bind(provider);
226
- const knownModels = getModels();
227
- const known = new Set(knownModels.map((model) => model.id));
228
- const additions = [...live]
229
- .filter(([id]) => !known.has(id))
230
- .map(([id, owner]) => cliproxyLiveModel(id, owner, knownModels))
231
- .filter(Boolean)
232
- .sort((a, b) => b.id.localeCompare(a.id, undefined, { numeric: true }));
233
- if (!additions.length) return false;
234
- provider.getModels = () => [...getModels(), ...additions];
235
- state.extendedProviders.add(provider);
236
- return true;
237
- }
238
-
239
151
  export async function createModels({ credentials, auth = defaultAuth, keys = defaultKeys, env = process.env, codexBaseUrl, cliproxyBaseUrl } = {}) {
240
152
  const { createModels: createPiModels } = await import("@earendil-works/pi-ai");
241
153
  const store = credentials ?? createCredentialStore({ auth, keys, env });
242
154
  const models = createPiModels({ credentials: store });
243
155
  for (const provider of PROVIDERS) {
244
- const baseUrl = provider.id === "openai-codex" ? codexBaseUrl : provider.id === "cliproxy" ? cliproxyBaseUrl : undefined;
245
- const loaded = await loadProvider(provider.id, { baseUrl, env });
246
- models.setProvider(loaded);
156
+ const baseUrl = provider.id === "openai-codex" ? codexBaseUrl : provider.id === "cliproxy" ? cliproxyBaseUrl ?? cliproxyBase({ env, credential: await store.read("cliproxy").catch(() => undefined) }) : undefined;
157
+ models.setProvider(await loadProvider(provider.id, { baseUrl, env }));
247
158
  }
159
+ // every other provider pi ships, so each of pi's logins (subscriptions and api keys) has a provider to land on (#pi-login)
160
+ const { builtinProviders } = await import("@earendil-works/pi-ai/providers/all");
161
+ for (const provider of builtinProviders()) if (!models.getProvider(provider.id)) models.setProvider(provider);
248
162
  return models;
249
163
  }
250
164
 
@@ -254,8 +168,9 @@ export async function resolveModel(requested, options = {}) {
254
168
  const slash = value.indexOf("/");
255
169
  const provider = slash === -1 ? "openai-codex" : value.slice(0, slash);
256
170
  const modelId = slash === -1 ? value : value.slice(slash + 1);
257
- if (!providerConfig(provider) || !modelId) throw unknownModel(requested);
171
+ if (!modelId) throw unknownModel(requested);
258
172
  const models = options.models ?? await createModels(options);
173
+ if (!providerConfig(provider) && !models.getProvider?.(provider)) throw unknownModel(requested);
259
174
  const model = models.getModel(provider, modelId);
260
175
  if (!model) throw unknownModel(requested);
261
176
  return { provider, modelId, model };
@@ -483,7 +398,7 @@ export async function listAgentModels({ models, codex, auth = defaultAuth, keys
483
398
  list = [...list].sort(astraFirst);
484
399
  } else if (provider.id === "cliproxy") {
485
400
  if (signedIn) {
486
- const live = await registryLiveCliproxy(resolvedModels, authResult?.auth?.apiKey);
401
+ const live = await registryLiveCliproxy(resolvedModels, authResult?.auth?.apiKey, catalogueState(resolvedModels));
487
402
  if (live) {
488
403
  // The proxy's own /v1/models is the lineup: register what pi's catalogue has not caught up
489
404
  // with on the shared registry, so the turn route resolves the same ids the dropdown shows.
@@ -496,6 +411,13 @@ export async function listAgentModels({ models, codex, auth = defaultAuth, keys
496
411
  }
497
412
  providers.push({ id: provider.id, label: provider.label, signedIn, authSource, models: list });
498
413
  }
414
+ // a provider outside the curated list joins the picker once a pi login stored its credential here (#pi-login); ambient
415
+ // credentials (an AWS profile, cloud ADC files) alone do not list it, so the picker shows what the author connected
416
+ const stored = new Set([...Object.keys(readCredentials()), ...Object.keys(saved)]);
417
+ for (const provider of resolvedModels.getProviders?.() ?? []) {
418
+ const check = providerConfig(provider.id) || !stored.has(provider.id) ? undefined : await resolvedModels.checkAuth?.(provider.id).catch(() => undefined);
419
+ if (check) providers.push({ id: provider.id, label: provider.name ?? provider.id, signedIn: true, authSource: check.type === "oauth" || /stored/i.test(check.source ?? "") ? "file" : "env", models: catalogModels(resolvedModels, provider.id, getSupportedThinkingLevels) });
420
+ }
499
421
  // The flat union is what the panel's dropdown reads today (`models[].id`);
500
422
  // with six providers in play the id has to be the provider/id key so two
501
423
  // providers' same-named model never collide there, while each provider's