quoxcli 0.2.0 → 0.4.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 (442) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -0
  3. package/bin/quox.js +99 -126
  4. package/lib/api/endpoints/actions.js +15 -0
  5. package/lib/api/endpoints/admin.js +46 -0
  6. package/lib/api/endpoints/agent-schedules.js +26 -0
  7. package/lib/api/endpoints/agents.js +7 -0
  8. package/lib/api/endpoints/assistants.js +12 -0
  9. package/lib/api/endpoints/away.js +15 -0
  10. package/lib/api/endpoints/blog.js +16 -0
  11. package/lib/api/endpoints/chat.js +14 -0
  12. package/lib/api/endpoints/contactsq.js +6 -0
  13. package/lib/api/endpoints/conversations.js +14 -0
  14. package/lib/api/endpoints/devServerRegistry.js +25 -0
  15. package/lib/api/endpoints/engagement.js +26 -0
  16. package/lib/api/endpoints/evidence.js +20 -0
  17. package/lib/api/endpoints/executions.js +13 -0
  18. package/lib/api/endpoints/fetchAllowlist.js +27 -0
  19. package/lib/api/endpoints/files.js +14 -0
  20. package/lib/api/endpoints/hat.js +44 -0
  21. package/lib/api/endpoints/inbox.js +20 -0
  22. package/lib/api/endpoints/index.js +26 -0
  23. package/lib/api/endpoints/loops.js +26 -0
  24. package/lib/api/endpoints/memory.js +19 -0
  25. package/lib/api/endpoints/mind.js +13 -0
  26. package/lib/api/endpoints/mon.js +179 -0
  27. package/lib/api/endpoints/objectives.js +12 -0
  28. package/lib/api/endpoints/plugins.js +18 -0
  29. package/lib/api/endpoints/presence.js +33 -0
  30. package/lib/api/endpoints/quoxplan.js +7 -0
  31. package/lib/api/endpoints/quoxseo.js +12 -0
  32. package/lib/api/endpoints/router.js +17 -0
  33. package/lib/api/endpoints/safety.js +7 -0
  34. package/lib/api/endpoints/server.js +21 -0
  35. package/lib/api/endpoints/setup.js +6 -0
  36. package/lib/api/endpoints/skills.js +20 -0
  37. package/lib/api/endpoints/stepup.js +20 -0
  38. package/lib/api/endpoints/teams.js +2 -2
  39. package/lib/api/endpoints/triggers.js +26 -0
  40. package/lib/api/endpoints/v1.js +120 -0
  41. package/lib/api/endpoints/ward.js +7 -0
  42. package/lib/api/endpoints/workflows.js +7 -0
  43. package/lib/api/endpoints/x.js +8 -0
  44. package/lib/commands/actions.js +131 -0
  45. package/lib/commands/admin.js +16 -0
  46. package/lib/commands/agent.js +170 -27
  47. package/lib/commands/agentAttach.js +140 -13
  48. package/lib/commands/approvals.js +78 -1
  49. package/lib/commands/aside.js +24 -0
  50. package/lib/commands/ask.js +146 -0
  51. package/lib/commands/assistant.js +59 -4
  52. package/lib/commands/auth.js +8 -2
  53. package/lib/commands/backup.js +8 -4
  54. package/lib/commands/blog.js +110 -0
  55. package/lib/commands/brain.js +105 -14
  56. package/lib/commands/build.js +19 -5
  57. package/lib/commands/chat.js +180 -26
  58. package/lib/commands/chatCompanion.js +27 -0
  59. package/lib/commands/chatState.js +45 -0
  60. package/lib/commands/claude.js +35 -38
  61. package/lib/commands/contacts.js +134 -4
  62. package/lib/commands/contactsSendAt.js +21 -0
  63. package/lib/commands/conversation.js +35 -4
  64. package/lib/commands/dashboard.js +43 -0
  65. package/lib/commands/department.js +28 -7
  66. package/lib/commands/devResident.js +15 -3
  67. package/lib/commands/devSupervisor.js +13 -1
  68. package/lib/commands/discord.js +75 -39
  69. package/lib/commands/done.js +98 -28
  70. package/lib/commands/engagement.js +107 -6
  71. package/lib/commands/evidence.js +154 -0
  72. package/lib/commands/ext.js +23 -0
  73. package/lib/commands/fetchAllowlist.js +71 -0
  74. package/lib/commands/file.js +38 -1
  75. package/lib/commands/fleet.js +336 -4
  76. package/lib/commands/governance.js +12 -3
  77. package/lib/commands/gripe.js +44 -13
  78. package/lib/commands/hat.js +254 -9
  79. package/lib/commands/helpers.js +18 -1
  80. package/lib/commands/hyperion.js +58 -0
  81. package/lib/commands/image.js +68 -0
  82. package/lib/commands/inbox.js +74 -23
  83. package/lib/commands/kuma.js +1050 -0
  84. package/lib/commands/kumaSla.js +174 -0
  85. package/lib/commands/licence.js +52 -155
  86. package/lib/commands/loop.js +291 -0
  87. package/lib/commands/lucid.js +10 -4
  88. package/lib/commands/memory.js +85 -8
  89. package/lib/commands/mind.js +71 -0
  90. package/lib/commands/mon.js +69 -2
  91. package/lib/commands/monitoring.js +1 -1
  92. package/lib/commands/motion.js +2 -2
  93. package/lib/commands/nucleus.js +48 -17
  94. package/lib/commands/objective.js +59 -1
  95. package/lib/commands/offload.js +89 -0
  96. package/lib/commands/onboard.js +2 -1
  97. package/lib/commands/org.js +30 -3
  98. package/lib/commands/peek.js +28 -0
  99. package/lib/commands/plan.js +38 -0
  100. package/lib/commands/plugin.js +110 -0
  101. package/lib/commands/presence.js +89 -0
  102. package/lib/commands/provider.js +217 -0
  103. package/lib/commands/qlarity.js +19 -3
  104. package/lib/commands/quoxchat.js +13 -4
  105. package/lib/commands/quoxseo.js +69 -0
  106. package/lib/commands/reach.js +2 -1
  107. package/lib/commands/research.js +79 -0
  108. package/lib/commands/router.js +77 -0
  109. package/lib/commands/rows.js +367 -0
  110. package/lib/commands/run.js +82 -10
  111. package/lib/commands/scope.js +2 -2
  112. package/lib/commands/server.js +405 -0
  113. package/lib/commands/setup.js +33 -7
  114. package/lib/commands/shortcut.js +41 -0
  115. package/lib/commands/skills.js +68 -9
  116. package/lib/commands/slot.js +260 -7
  117. package/lib/commands/status.js +1 -1
  118. package/lib/commands/team.js +3 -1
  119. package/lib/commands/tips.js +23 -0
  120. package/lib/commands/tool.js +133 -45
  121. package/lib/commands/trace.js +12 -7
  122. package/lib/commands/trigger.js +95 -0
  123. package/lib/commands/update.js +46 -0
  124. package/lib/commands/vault.js +31 -8
  125. package/lib/commands/vm.js +237 -0
  126. package/lib/commands/voice.js +34 -3
  127. package/lib/commands/volt.js +45 -3
  128. package/lib/commands/walkthrough.js +32 -0
  129. package/lib/commands/watch.js +73 -21
  130. package/lib/commands/welcome.js +14 -0
  131. package/lib/commands/workflow.js +189 -32
  132. package/lib/commands/x.js +13 -4
  133. package/lib/commands/youtube.js +172 -0
  134. package/lib/core/actingAs.js +2 -2
  135. package/lib/core/actionItems.js +115 -0
  136. package/lib/core/activeSummary.js +186 -0
  137. package/lib/core/agentAlias.js +2 -0
  138. package/lib/core/agentFamilies.js +7 -0
  139. package/lib/core/aside.js +54 -0
  140. package/lib/core/askDecision.js +60 -0
  141. package/lib/core/auth.js +164 -13
  142. package/lib/core/callerSource.js +1 -1
  143. package/lib/core/cliBuild.js +62 -0
  144. package/lib/core/client.js +152 -35
  145. package/lib/core/commandRegistrars.js +130 -0
  146. package/lib/core/config.js +143 -25
  147. package/lib/core/customHats.js +168 -0
  148. package/lib/core/doneEngineFetch.js +8 -0
  149. package/lib/core/errors.js +329 -20
  150. package/lib/core/findingReconcile.js +90 -0
  151. package/lib/core/global-flags.js +105 -5
  152. package/lib/core/governedSession.js +1 -3
  153. package/lib/core/hyperion.js +102 -0
  154. package/lib/core/inboxResolution.js +65 -0
  155. package/lib/core/lazyCommands.js +4 -1
  156. package/lib/core/liveSessionsReport.js +1 -1
  157. package/lib/core/loopGlyph.js +97 -0
  158. package/lib/core/offload.js +533 -0
  159. package/lib/core/org-override.js +1 -1
  160. package/lib/core/orgResolve.js +70 -0
  161. package/lib/core/output.js +11 -11
  162. package/lib/core/peek.js +126 -0
  163. package/lib/core/profileUsers.js +51 -0
  164. package/lib/core/promptCancel.js +18 -0
  165. package/lib/core/repoPath.js +11 -0
  166. package/lib/core/residentSeed.js +3 -1
  167. package/lib/core/rootOptions.js +39 -0
  168. package/lib/core/router.js +168 -0
  169. package/lib/core/selfUpdate.js +188 -0
  170. package/lib/core/sessionHeartbeat.js +12 -8
  171. package/lib/core/sessionsOverview.js +64 -0
  172. package/lib/core/shortcut.js +83 -0
  173. package/lib/core/slotKeeper.js +248 -3
  174. package/lib/core/slotWake.js +120 -0
  175. package/lib/core/supervisorEngine.js +71 -2
  176. package/lib/core/version.js +1 -1
  177. package/lib/core/vm.js +366 -0
  178. package/lib/core/vmTui.js +86 -0
  179. package/lib/core/walkthrough.js +35 -0
  180. package/lib/dragon/companionView.js +34 -0
  181. package/lib/dragon/frames.js +118 -0
  182. package/lib/dragon/jobs.js +123 -0
  183. package/lib/dragon/renderWorker.js +15 -0
  184. package/lib/dragon/select.js +72 -0
  185. package/lib/evidence/bundleExport.js +47 -0
  186. package/lib/evidence/bundleVerify.js +77 -0
  187. package/lib/hyperion/deck.js +29 -0
  188. package/lib/lucid/LucidCard.jsx +68 -0
  189. package/lib/lucid/LucidIntro.jsx +45 -58
  190. package/lib/lucid/cards.js +86 -0
  191. package/lib/lucid/markdown.js +81 -0
  192. package/lib/lucid/motion.js +56 -0
  193. package/lib/quoxon/EXTENSIONS.md +23 -0
  194. package/lib/quoxon/activity.js +14 -2
  195. package/lib/quoxon/aocl.js +117 -0
  196. package/lib/quoxon/api.js +690 -25
  197. package/lib/quoxon/app.js +3586 -250
  198. package/lib/quoxon/approvalFacts.js +96 -0
  199. package/lib/quoxon/approvalSentence.js +44 -0
  200. package/lib/quoxon/approvalsView.js +384 -0
  201. package/lib/quoxon/asideView.js +40 -0
  202. package/lib/quoxon/ask.js +121 -0
  203. package/lib/quoxon/away.js +52 -0
  204. package/lib/quoxon/bang.js +224 -0
  205. package/lib/quoxon/bulkRisk.js +17 -0
  206. package/lib/quoxon/capabilities.js +13 -6
  207. package/lib/quoxon/cells.js +53 -0
  208. package/lib/quoxon/commandShort.js +124 -0
  209. package/lib/quoxon/commands.js +429 -39
  210. package/lib/quoxon/connect.js +210 -23
  211. package/lib/quoxon/connectCard.js +19 -2
  212. package/lib/quoxon/consent.js +86 -0
  213. package/lib/quoxon/copy.js +4 -3
  214. package/lib/quoxon/countdownLine.js +20 -0
  215. package/lib/quoxon/deck.js +30 -0
  216. package/lib/quoxon/demo.autoplay.js +29 -0
  217. package/lib/quoxon/demo.fixtures.js +1 -1
  218. package/lib/quoxon/demo.js +46 -3
  219. package/lib/quoxon/devObserver.js +20 -0
  220. package/lib/quoxon/dividerAocl.js +192 -0
  221. package/lib/quoxon/dividerRule.js +57 -0
  222. package/lib/quoxon/dragonCompanion.js +255 -0
  223. package/lib/quoxon/dragonModes.js +151 -0
  224. package/lib/quoxon/dragonMoments.js +82 -0
  225. package/lib/quoxon/dragonReads.js +68 -0
  226. package/lib/quoxon/dragonView.js +41 -0
  227. package/lib/quoxon/earlyInput.js +40 -0
  228. package/lib/quoxon/echo.js +15 -0
  229. package/lib/quoxon/egg.js +2 -2
  230. package/lib/quoxon/events.js +266 -26
  231. package/lib/quoxon/extensions.js +276 -0
  232. package/lib/quoxon/feedback.js +50 -0
  233. package/lib/quoxon/gallery.js +21 -7
  234. package/lib/quoxon/handoffOffer.js +78 -0
  235. package/lib/quoxon/hatSwitch.js +37 -0
  236. package/lib/quoxon/headless.js +161 -0
  237. package/lib/quoxon/hostUrls.js +41 -0
  238. package/lib/quoxon/inboxView.js +79 -0
  239. package/lib/quoxon/indicator.js +6 -2
  240. package/lib/quoxon/installCli.js +79 -0
  241. package/lib/quoxon/intent.js +12 -1
  242. package/lib/quoxon/keys.js +51 -8
  243. package/lib/quoxon/launcher.js +103 -0
  244. package/lib/quoxon/loopCard.js +456 -0
  245. package/lib/quoxon/loopCards.js +180 -0
  246. package/lib/quoxon/loopStream.js +87 -0
  247. package/lib/quoxon/main.js +101 -160
  248. package/lib/quoxon/markdown.js +45 -21
  249. package/lib/quoxon/markdownSettle.js +19 -0
  250. package/lib/quoxon/matrixDomains.json +46 -0
  251. package/lib/quoxon/mentions.js +90 -0
  252. package/lib/quoxon/mind.js +103 -0
  253. package/lib/quoxon/mode.js +46 -0
  254. package/lib/quoxon/model.js +132 -26
  255. package/lib/quoxon/modelPrefs.js +345 -0
  256. package/lib/quoxon/motion/clock.js +3 -2
  257. package/lib/quoxon/motion/level.js +91 -0
  258. package/lib/quoxon/mouse.js +222 -0
  259. package/lib/quoxon/noai.js +57 -0
  260. package/lib/quoxon/objectiveOffer.js +82 -0
  261. package/lib/quoxon/objects/away.js +72 -0
  262. package/lib/quoxon/objects/fetch.js +17 -5
  263. package/lib/quoxon/objects/fleet.js +149 -0
  264. package/lib/quoxon/objects/health.js +186 -0
  265. package/lib/quoxon/objects/index.js +46 -7
  266. package/lib/quoxon/objects/mirror.js +137 -0
  267. package/lib/quoxon/objects/model.js +33 -0
  268. package/lib/quoxon/objects/peek.js +17 -0
  269. package/lib/quoxon/objects/proof.js +12 -4
  270. package/lib/quoxon/objects/prove.js +130 -0
  271. package/lib/quoxon/objects/replay.js +125 -0
  272. package/lib/quoxon/objects/sitrep.js +58 -0
  273. package/lib/quoxon/objects/view.js +11 -0
  274. package/lib/quoxon/onboarding/copy.js +32 -4
  275. package/lib/quoxon/onboarding/credential.js +17 -4
  276. package/lib/quoxon/onboarding/dockerOffer.js +45 -0
  277. package/lib/quoxon/onboarding/failureCause.js +33 -0
  278. package/lib/quoxon/onboarding/finish.js +15 -0
  279. package/lib/quoxon/onboarding/index.js +34 -7
  280. package/lib/quoxon/onboarding/installRunner.js +64 -5
  281. package/lib/quoxon/onboarding/preflight.js +17 -3
  282. package/lib/quoxon/onboarding/probe.js +65 -0
  283. package/lib/quoxon/onboarding/shortcutOffer.js +28 -0
  284. package/lib/quoxon/onboarding/view.js +76 -19
  285. package/lib/quoxon/orgLabel.js +10 -0
  286. package/lib/quoxon/orgSwitch.js +59 -0
  287. package/lib/quoxon/ownBroadcast.js +22 -0
  288. package/lib/quoxon/palette.js +43 -0
  289. package/lib/quoxon/planSteps.js +51 -0
  290. package/lib/quoxon/print.js +12 -5
  291. package/lib/quoxon/profileSwitch.js +53 -0
  292. package/lib/quoxon/promptHistory.js +41 -0
  293. package/lib/quoxon/providers.js +48 -0
  294. package/lib/quoxon/qart.js +20 -51
  295. package/lib/quoxon/qdragon.js +410 -0
  296. package/lib/quoxon/qdragonFly.js +62 -0
  297. package/lib/quoxon/qdragonScenes.js +282 -0
  298. package/lib/quoxon/qeyes.js +213 -0
  299. package/lib/quoxon/qmotion.js +75 -37
  300. package/lib/quoxon/render.js +740 -124
  301. package/lib/quoxon/resend.js +72 -0
  302. package/lib/quoxon/roster.js +117 -17
  303. package/lib/quoxon/route.js +6 -5
  304. package/lib/quoxon/rows.js +954 -0
  305. package/lib/quoxon/rowsStore.js +80 -0
  306. package/lib/quoxon/screenContext.js +150 -0
  307. package/lib/quoxon/screenFrames.js +254 -0
  308. package/lib/quoxon/serverChip.js +42 -0
  309. package/lib/quoxon/session.js +182 -2
  310. package/lib/quoxon/sessionDead.js +17 -0
  311. package/lib/quoxon/signin.js +125 -0
  312. package/lib/quoxon/slash/agents.js +112 -0
  313. package/lib/quoxon/slash/argSources.js +65 -0
  314. package/lib/quoxon/slash/args.js +65 -0
  315. package/lib/quoxon/slash/automate.js +89 -0
  316. package/lib/quoxon/slash/broadcast.js +140 -0
  317. package/lib/quoxon/slash/broadcastRun.js +108 -0
  318. package/lib/quoxon/slash/card.js +30 -0
  319. package/lib/quoxon/slash/challenge.js +114 -0
  320. package/lib/quoxon/slash/cliCommand.js +94 -0
  321. package/lib/quoxon/slash/cliLeaves.json +1023 -0
  322. package/lib/quoxon/slash/compact.js +1 -1
  323. package/lib/quoxon/slash/confirm.js +30 -0
  324. package/lib/quoxon/slash/crystallize.js +69 -0
  325. package/lib/quoxon/slash/dashboard.js +136 -0
  326. package/lib/quoxon/slash/draft.js +54 -0
  327. package/lib/quoxon/slash/evidenceCmd.js +34 -0
  328. package/lib/quoxon/slash/hatWalk.js +206 -0
  329. package/lib/quoxon/slash/inverses.js +77 -0
  330. package/lib/quoxon/slash/killer.js +24 -0
  331. package/lib/quoxon/slash/killerApi.js +16 -0
  332. package/lib/quoxon/slash/ledger.js +13 -8
  333. package/lib/quoxon/slash/listView.js +3 -1
  334. package/lib/quoxon/slash/modelCmd.js +261 -0
  335. package/lib/quoxon/slash/palette.js +22 -8
  336. package/lib/quoxon/slash/paletteController.js +117 -7
  337. package/lib/quoxon/slash/panels.js +21 -6
  338. package/lib/quoxon/slash/picker.js +20 -2
  339. package/lib/quoxon/slash/qpeCmd.js +35 -0
  340. package/lib/quoxon/slash/restart.js +95 -13
  341. package/lib/quoxon/slash/resume.js +18 -1
  342. package/lib/quoxon/slash/rowCmd.js +157 -0
  343. package/lib/quoxon/slash/rowWalk.js +175 -0
  344. package/lib/quoxon/slash/settings.js +16 -6
  345. package/lib/quoxon/slash/spawn.js +177 -0
  346. package/lib/quoxon/slash/stepUp.js +274 -0
  347. package/lib/quoxon/slash/stepUpCard.js +60 -0
  348. package/lib/quoxon/slash/stepUpCmds.js +78 -0
  349. package/lib/quoxon/slash/todoCmd.js +122 -0
  350. package/lib/quoxon/slash/undoApi.js +38 -0
  351. package/lib/quoxon/slash/undoCmd.js +49 -0
  352. package/lib/quoxon/slash/undoWish.js +15 -0
  353. package/lib/quoxon/slash/watch.js +164 -0
  354. package/lib/quoxon/slash/wish.js +23 -0
  355. package/lib/quoxon/slash/wishCmd.js +32 -0
  356. package/lib/quoxon/slotChat.js +300 -0
  357. package/lib/quoxon/slotFormat.js +195 -0
  358. package/lib/quoxon/slotRun.js +131 -0
  359. package/lib/quoxon/sound/mapping.js +2 -1
  360. package/lib/quoxon/stray.js +22 -0
  361. package/lib/quoxon/suggestCommand.js +90 -0
  362. package/lib/quoxon/term.js +46 -11
  363. package/lib/quoxon/textBuffer.js +176 -2
  364. package/lib/quoxon/theme.js +58 -25
  365. package/lib/quoxon/tips.js +433 -0
  366. package/lib/quoxon/toolCards.js +30 -0
  367. package/lib/quoxon/toolError.js +41 -0
  368. package/lib/quoxon/transcript.js +42 -4
  369. package/lib/quoxon/web/qface.js +18 -10
  370. package/lib/quoxon/welcome.js +1 -1
  371. package/lib/quoxon/whatsnew.js +136 -0
  372. package/lib/quoxon/whatsnew.jsonl +134 -0
  373. package/lib/quoxon/windowOrg.js +123 -0
  374. package/lib/server/devServers.js +137 -0
  375. package/lib/server/diag.js +129 -0
  376. package/lib/server/doctor.js +71 -0
  377. package/lib/server/install.js +149 -0
  378. package/lib/server/pluginEnable.js +161 -0
  379. package/lib/server/skew.js +56 -0
  380. package/lib/server/snapshot.js +131 -0
  381. package/lib/server/status.js +128 -0
  382. package/lib/server/sys.js +65 -0
  383. package/lib/server/uninstall.js +63 -0
  384. package/lib/server/update.js +234 -0
  385. package/lib/toolOutputShape.js +315 -0
  386. package/lib/tui/views/HyperionDeck.jsx +79 -0
  387. package/lib/tui/views/LucidDeck.jsx +209 -217
  388. package/package.json +5 -1
  389. package/vendor/dragonlab/SOURCE +4 -0
  390. package/vendor/dragonlab/design/cipher/v1/cipher-master.svg +46 -0
  391. package/vendor/dragonlab/design/cipher/v2/character.mjs +49 -0
  392. package/vendor/dragonlab/design/cipher/v2/review.mjs +33 -0
  393. package/vendor/dragonlab/design/cipher/v2/watch.mjs +56 -0
  394. package/vendor/dragonlab/design/drago/v1/character.mjs +19 -0
  395. package/vendor/dragonlab/design/drago/v1/drago-master.svg +56 -0
  396. package/vendor/dragonlab/design/drago/v2/character.mjs +87 -0
  397. package/vendor/dragonlab/design/drago/v4/character.mjs +67 -0
  398. package/vendor/dragonlab/design/drago/v4/profile.mjs +35 -0
  399. package/vendor/dragonlab/design/drago/v4/review.mjs +33 -0
  400. package/vendor/dragonlab/design/drago/v4/watch.mjs +56 -0
  401. package/vendor/dragonlab/design/hyperion/v1/hyperion-master.svg +56 -0
  402. package/vendor/dragonlab/design/hyperion/v2/character.mjs +79 -0
  403. package/vendor/dragonlab/design/hyperion/v2/review.mjs +33 -0
  404. package/vendor/dragonlab/design/hyperion/v2/watch.mjs +56 -0
  405. package/vendor/dragonlab/design/lucid/recovery/arrival.mjs +22 -0
  406. package/vendor/dragonlab/design/lucid/recovery/character.mjs +106 -0
  407. package/vendor/dragonlab/design/lucid/recovery/layout.mjs +50 -0
  408. package/vendor/dragonlab/design/lucid/recovery/lucid-master.svg +78 -0
  409. package/vendor/dragonlab/design/lucid/recovery/motion.mjs +42 -0
  410. package/vendor/dragonlab/design/lucid/recovery/render.mjs +121 -0
  411. package/vendor/dragonlab/design/lucid/recovery/review.mjs +68 -0
  412. package/vendor/dragonlab/design/lucid/recovery/verify.mjs +84 -0
  413. package/vendor/dragonlab/design/lucid/recovery/watch.mjs +56 -0
  414. package/vendor/dragonlab/design/seer/v1/seer-master.svg +53 -0
  415. package/vendor/dragonlab/design/seer/v2/character.mjs +88 -0
  416. package/vendor/dragonlab/design/seer/v3/character.mjs +33 -0
  417. package/vendor/dragonlab/design/seer/v4/character.mjs +66 -0
  418. package/vendor/dragonlab/design/seer/v4/review.mjs +33 -0
  419. package/vendor/dragonlab/design/seer/v4/watch.mjs +56 -0
  420. package/vendor/dragonlab/design/stage3/companion-review.mjs +28 -0
  421. package/vendor/dragonlab/design/stage3/sentinel-art.js +39 -0
  422. package/vendor/dragonlab/design/stage3/sentinel-braille.js +216 -0
  423. package/vendor/dragonlab/design/stage3/sentinel-character.js +257 -0
  424. package/vendor/dragonlab/design/stage3/sentinel-icon.js +103 -0
  425. package/vendor/dragonlab/design/stage3/sentinel-warden-eyes.js +95 -0
  426. package/vendor/dragonlab/design/stage3/svg-coverage.js +148 -0
  427. package/vendor/dragonlab/factory/svg-rig.mjs +63 -0
  428. package/vendor/dragonlab/integration/companions-v1/characters.mjs +88 -0
  429. package/vendor/dragonlab/integration/companions-v1/cipher.mjs +2 -0
  430. package/vendor/dragonlab/integration/companions-v1/companion.mjs +63 -0
  431. package/vendor/dragonlab/integration/companions-v1/drago.mjs +2 -0
  432. package/vendor/dragonlab/integration/companions-v1/hyperion.mjs +2 -0
  433. package/vendor/dragonlab/integration/companions-v1/lucid.mjs +2 -0
  434. package/vendor/dragonlab/integration/companions-v1/seer.mjs +2 -0
  435. package/vendor/dragonlab/integration/companions-v1/sentinel.mjs +2 -0
  436. package/vendor/dragonlab/integration/companions-v1/view.mjs +28 -0
  437. package/vendor/dragonlab/integration/hyperion-v3/activity.mjs +76 -0
  438. package/vendor/dragonlab/lib/power-effects.js +294 -0
  439. package/vendor/dragonlab/lib/power-registry.js +74 -0
  440. package/vendor/dragonlab/vendor/quox-dragon/qart.js +348 -0
  441. package/vendor/dragonlab/vendor/quox-dragon/qeyes.js +213 -0
  442. package/lib/core/websiteSession.js +0 -148
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quoxcli",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Quox: the conversational front door to your self-hosted AI agent control plane",
5
5
  "type": "module",
6
6
  "bin": {
@@ -53,9 +53,13 @@
53
53
  "files": [
54
54
  "bin/",
55
55
  "lib/",
56
+ "vendor/",
56
57
  "!lib/**/*.test.js",
57
58
  "!lib/**/*.test.jsx",
58
59
  "!lib/**/__tests__/",
60
+ "!lib/vendor/",
61
+ "!lib/core/websiteSession.js",
62
+ "!lib/quoxon/whatsnew.md",
59
63
  "README.md",
60
64
  "LICENSE"
61
65
  ],
@@ -0,0 +1,4 @@
1
+ repo: quoxai/dragonlab
2
+ commit: 5b478a813554eb80793b848d14da1468ea3bdcef
3
+ branch: HEAD
4
+ files: 52
@@ -0,0 +1,46 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 480">
2
+ <title>Cipher — network operations, electric-blue listening dragon</title>
3
+ <g id="wings">
4
+ <path id="far-sail" fill="#185a9b" d="M 352 288 Q 363 205 445 105 L 430 222 L 484 181 L 451 285 Z"/>
5
+ <path id="near-sail" fill="#297cc4" d="M 398 302 Q 450 189 564 144 L 531 227 L 579 209 L 537 272 L 572 272 L 483 329 Z"/>
6
+ <path id="sail-ribs" fill="none" stroke="#81ddff" stroke-width="8" d="M 398 302 L 564 144 L 481 272 L 537 272 M 352 288 L 445 105 L 398 246"/>
7
+ </g>
8
+ <g id="tail">
9
+ <path id="tail-ribbon" fill="#2988da" d="M 475 305 C 538 278 625 287 658 327 C 696 378 630 409 589 387 C 547 366 564 328 613 313 C 673 294 711 263 695 218 Q 683 189 654 197 Q 691 201 677 234 C 663 262 600 266 559 295 C 502 337 525 398 576 419 C 644 446 731 395 696 332 C 663 266 548 253 469 275 Z"/>
10
+ <path id="tail-edge" fill="none" stroke="#9aefff" stroke-width="7" d="M 491 294 C 554 274 641 289 665 335 C 686 374 637 404 594 384 M 613 313 C 673 294 711 263 695 218"/>
11
+ <path id="tail-fin" fill="#76dbfb" d="M 688 226 L 641 221 L 658 202 L 635 180 Q 688 178 699 215 Z"/>
12
+ </g>
13
+ <g id="body">
14
+ <path id="torso" fill="#287fc3" d="M 288 246 C 328 225 374 246 402 266 C 445 267 494 287 510 317 Q 524 356 476 367 L 331 354 Q 282 335 278 291 Z"/>
15
+ <path id="silver-belly" fill="#bddceb" d="M 291 265 Q 346 301 426 308 L 480 340 Q 405 365 336 337 Q 295 319 291 265 Z"/>
16
+ <path id="belly-plates" fill="none" stroke="#477b9d" stroke-width="5" d="M 320 295 L 315 325 M 348 306 L 344 339 M 379 311 L 376 345 M 410 315 L 408 348"/>
17
+ <path id="rear-legs" fill="#2367a8" d="M 455 314 Q 496 299 505 338 L 495 386 L 526 399 L 525 412 L 468 412 L 469 362 L 443 347 Z M 388 329 L 405 376 L 435 391 L 429 405 L 382 402 L 366 345 Z"/>
18
+ <path id="front-legs" fill="#409bde" d="M 298 290 Q 323 283 332 307 L 322 378 L 349 400 L 345 416 L 293 415 Q 283 409 290 390 L 291 330 Z M 265 288 L 281 312 L 265 378 L 280 393 L 270 408 L 231 405 L 243 371 Z"/>
19
+ <path id="talons" fill="#e8f7ff" d="M 296 403 L 289 417 L 305 412 L 313 401 L 311 417 L 327 412 L 335 404 L 337 417 L 350 411 M 474 400 L 466 413 L 483 410 L 490 399 L 490 414 L 507 410 L 515 400 L 519 413 L 531 408 M 235 393 L 230 406 L 247 402 L 255 392 L 255 408 L 272 401"/>
20
+ <path id="dorsal-fins" fill="#7ae6ff" d="M 330 249 L 341 224 L 356 258 L 374 238 L 386 269 L 409 251 L 421 276 L 445 260 L 457 289"/>
21
+ </g>
22
+ <g id="neck">
23
+ <path id="neck-mass" fill="#358bd1" d="M 230 180 Q 281 156 310 193 Q 316 231 349 273 Q 305 315 263 281 Q 271 239 230 180 Z"/>
24
+ <path id="neck-silver" fill="#d1e8f4" d="M 237 201 Q 259 214 274 233 Q 281 259 274 282 Q 295 297 314 284 Q 294 249 288 221 Z"/>
25
+ </g>
26
+ <g id="head">
27
+ <path id="antenna-far" fill="none" stroke="#9eeeff" stroke-width="9" d="M 233 127 Q 225 82 263 46 L 283 22"/>
28
+ <path id="antenna-near" fill="none" stroke="#d5f5ff" stroke-width="10" d="M 278 130 Q 286 74 332 55 L 366 33"/>
29
+ <path id="ear-far" fill="#5dc8f5" d="M 216 146 L 166 106 L 185 161 L 165 172 L 218 179 Z"/>
30
+ <path id="head-mass" fill="#469ee0" d="M 203 143 Q 217 112 263 116 Q 301 117 312 152 Q 324 189 295 217 Q 255 245 216 218 L 192 188 Z"/>
31
+ <path id="ear-near" fill="#70d8fa" d="M 291 153 L 354 113 L 332 157 L 362 150 L 334 185 L 347 189 L 302 205 Z"/>
32
+ <path id="ear-cut" fill="none" stroke="#2978bb" stroke-width="5" d="M 305 169 L 337 139 M 309 182 L 336 168"/>
33
+ <path id="muzzle" fill="#9bd7ed" d="M 208 181 Q 187 177 178 190 Q 166 208 191 220 Q 229 238 266 218 L 270 204 Q 237 215 208 181 Z"/>
34
+ <path id="nose" fill="#080d19" d="M 185 194 Q 195 186 202 194 Q 197 202 185 199 Z"/>
35
+ <path id="smile" fill="none" stroke="#164668" stroke-width="5" d="M 185 211 Q 218 231 255 214"/>
36
+ <path id="brow" fill="none" stroke="#a6edff" stroke-width="6" d="M 213 143 Q 230 130 245 143 M 259 140 Q 280 125 295 141"/>
37
+ </g>
38
+ <g id="eyes">
39
+ <path id="far-eye-socket" fill="#080d19" d="M 210 154 Q 223 138 240 149 Q 253 164 243 184 Q 222 197 211 177 Z"/>
40
+ <path id="far-eye-white" fill="#ffffff" d="M 216 156 Q 227 145 237 155 Q 246 166 238 179 Q 224 188 216 174 Z"/>
41
+ <path id="far-pupil" fill="#080d19" d="M 225 166 C 225 157 234 157 234 166 C 234 175 225 175 225 166 Z"/>
42
+ <path id="near-eye-socket" fill="#080d19" d="M 253 153 Q 272 134 293 149 Q 310 168 295 190 Q 271 204 254 183 Z"/>
43
+ <path id="near-eye-white" fill="#ffffff" d="M 260 155 Q 275 142 289 154 Q 301 169 290 184 Q 274 194 261 179 Z"/>
44
+ <path id="near-pupil" fill="#080d19" d="M 272 168 C 272 157 283 157 283 168 C 283 179 272 179 272 168 Z"/>
45
+ </g>
46
+ </svg>
@@ -0,0 +1,49 @@
1
+ import {renderSvgRig} from '../../../factory/svg-rig.mjs';
2
+ const pulse=(t,a,b)=>t<=a||t>=b?0:Math.sin(Math.PI*(t-a)/(b-a))**2;
3
+ const ease=x=>{x=Math.max(0,Math.min(1,x));return x*x*(3-2*x);};
4
+ export const CIPHER_CLIPS=[{id:'idle',title:'Listening',duration:12000},{id:'attach',title:'Link Up',duration:3200},{id:'inspect',title:'Following Traffic',duration:3600},{id:'trace',title:'Trace Route',duration:4800},{id:'delay',title:'Delayed Hop',duration:4800},{id:'broken',title:'Broken Link',duration:4800},{id:'restored',title:'Route Restored',duration:4800}];
5
+ export const STORY=[['attach',3200],['idle',1600],['inspect',3600],['trace',4800],['delay',4800],['broken',4800],['restored',4800],['idle',2400]];
6
+ export const STORY_DURATION=STORY.reduce((s,b)=>s+b[1],0);
7
+ export function poseRig(rig,t,scene){
8
+ const cycle=t%12000,arrival=scene==='attach'&&t<3200;
9
+ const attention=arrival?pulse(t,0,2800):pulse(cycle,600,3400);
10
+ const fin=arrival?pulse(t,500,2900):pulse(cycle,2600,4300);
11
+ const tail=arrival?pulse(t,1000,3200):pulse(cycle,6200,10500);
12
+ const paths=rig.paths.map(p=>({...p,polygons:p.polygons.map(poly=>poly.map(([x,y])=>{
13
+ if(p.id.endsWith('-pupil'))x+=(scene==='inspect'?Math.sin(t/700):attention)*2;
14
+ if(p.group==='head'||p.group==='eyes'){x+=attention*3;y-=attention*3;if(p.id.startsWith('ear-'))y-=fin*4*ease((x-290)/65);if(p.id.startsWith('antenna-'))x+=fin*4*ease((130-y)/100);}
15
+ if(p.group==='neck'){const w=ease((290-y)/100);x+=attention*3*w;y-=attention*3*w;}
16
+ if(p.group==='wings'){const w=ease((300-y)/170);x-=fin*3*w;y+=fin*3*w;}
17
+ if(p.group==='tail'){const w=ease((x-490)/150);x-=tail*4*w;y+=tail*4*w*Math.sin((x-490)/70);}
18
+ return [x,y];
19
+ }))}));
20
+ const byId=new Map(paths.map(p=>[p.id,p]));
21
+ return {...rig,paths,bounds:{left:rig.bounds.left-7,top:rig.bounds.top-7,right:rig.bounds.right+7,bottom:rig.bounds.bottom+7},eyes:rig.eyes.map(e=>({...e,socket:byId.get(e.socket.id),white:byId.get(e.white.id),pupil:byId.get(e.pupil.id)}))};
22
+ }
23
+ function route(frame,t,scene,mono){
24
+ const cols=frame[0].length,rows=frame.length,y=1;
25
+ const nodes=[.53,.69,.85].map(f=>Math.round(f*(cols-1)));
26
+ let effectCells=0;
27
+ const put=(x,ch,rgb)=>{if(x<1||x>=cols-1||y>=rows-1||frame[y][x].ch!==' ')return;frame[y][x]={ch,rgb:mono?null:rgb,bg:null};effectCells++;};
28
+ const cyan=[103,226,255],amber=[255,194,76],red=[255,100,103];
29
+ const p=Math.min(1,t/4000),blocked=scene==='broken',delayed=scene==='delay';
30
+ nodes.forEach((x,i)=>put(x,'⠿',(i===1&&(blocked||delayed))?(blocked?red:amber):cyan));
31
+ for(let x=nodes[0];x<=nodes[2];x++){if(blocked&&x>nodes[1]&&x<nodes[1]+3)continue;put(x,'⠒',cyan);}
32
+ // Replace our route cells only; never overwrite original art.
33
+ const set=(x,ch,rgb)=>{if(x>=nodes[0]&&x<=nodes[2]&&['⠒','⠿'].includes(frame[y][x].ch))frame[y][x]={ch,rgb:mono?null:rgb,bg:null};};
34
+ let progress=p;if(delayed)progress=p<.3?p/.6:p<.7?.5:.5+(p-.7)/.6;if(blocked)progress=Math.min(.5,p);
35
+ const x=Math.round(nodes[0]+(nodes[2]-nodes[0])*Math.min(1,progress));
36
+ set(x,'⣿',blocked?red:delayed&&progress===.5?amber:[255,255,255]);
37
+ return effectCells;
38
+ }
39
+ export function renderCipher(rig,scene='idle',ms=0,options={}){
40
+ const clip=CIPHER_CLIPS.find(c=>c.id===scene);if(!clip)return {ok:false,code:'UNKNOWN_CLIP'};
41
+ if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
42
+ const t=options.still?0:ms;
43
+ const idleT=scene==='attach'&&t>=3200?t-3200:t;
44
+ const posed=poseRig(rig,options.still?0:(options.motionTime??idleT),scene==='attach'&&t>=3200?'idle':scene);
45
+ const r=renderSvgRig(posed,options),power=['trace','delay','broken','restored'].includes(scene);
46
+ const effectCells=power?route(r.frame,options.still?2400:t,scene,options.mono):0;
47
+ return {...r,scene,clip:clip.title,caption:power?clip.title+' · simulated network state':'Listening to the network.',effectCells};
48
+ }
49
+ export function renderStory(rig,ms,options={}){let t=options.still?0:Math.min(ms,STORY_DURATION-1);for(const [scene,duration]of STORY){if(t<duration)return renderCipher(rig,scene,t,{...options});t-=duration;}}
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs/promises';
3
+ import {parseSvgRig} from '../../../factory/svg-rig.mjs';
4
+ import {renderCipher,CIPHER_CLIPS} from './character.mjs';
5
+ import {artSlot,composeMessage} from '../../lucid/recovery/layout.mjs';
6
+ import {cellsToAnsi} from '../../../vendor/quox-dragon/qart.js';
7
+ const args=process.argv.slice(2),values=new Set(['--cols','--rows','--terminal-cols','--terminal-rows','--message','--scene','--at']);
8
+ for(let i=0;i<args.length;i++){
9
+ if(values.has(args[i])){if(!args[i+1]||args[i+1].startsWith('--'))throw Error('Missing option value');i++;}
10
+ else if(!['--large','--mono','--help','--watch','--attach','--intro','--still','--list'].includes(args[i]))throw Error('Unknown option: '+args[i]);
11
+ }
12
+ const value=(name,fallback)=>args.includes(name)?args[args.indexOf(name)+1]:fallback;
13
+ if(args.includes('--help')){console.log('Cipher V2 acting studio. --watch --attach --intro --still --scene ID --at MS --list --large --mono --message TEXT --cols N --rows N --terminal-cols N --terminal-rows N');process.exit(0);}
14
+ if(args.includes('--list')){console.log(CIPHER_CLIPS.map(c=>c.id+' · '+c.title).join('\n'));process.exit(0);}
15
+ const w=Number(value('--terminal-cols',process.stdout.columns||80)),h=Number(value('--terminal-rows',process.stdout.rows||24));
16
+ if(!Number.isSafeInteger(w)||!Number.isSafeInteger(h)||w<1||h<1)throw Error('Invalid terminal size');
17
+ const slot=artSlot(w,h),large=args.includes('--large')||args.includes('--intro'),explicit=large||args.includes('--cols')||args.includes('--rows');
18
+ const cols=Number(value('--cols',large?Math.min(120,w-4):Math.min(39,slot.cols))),rows=Number(value('--rows',large?Math.min(36,h-4):Math.min(11,slot.rows)));
19
+ const message=value('--message','CIPHER\nOn guard.');
20
+ if(!slot.showArt&&!explicit){console.log(composeMessage(null,message,{terminalCols:w}).join('\n'));}
21
+ else{
22
+ if(cols>w-4||rows>h-2)throw Error('Requested art exceeds terminal margins');
23
+ const rig=parseSvgRig(await fs.readFile(new URL('../v1/cipher-master.svg',import.meta.url),'utf8'));
24
+ const opts={cols,rows,mono:args.includes('--mono'),still:args.includes('--still'),deck:large};
25
+ const scene=value('--scene',args.includes('--attach')?'attach':'idle');
26
+ const r=renderCipher(rig,scene,Number(value('--at',0)),opts);
27
+ if(!r.ok)throw Error(r.code);
28
+ if(args.includes('--watch')||args.includes('--attach')||args.includes('--intro')){
29
+ const {watch}=await import('./watch.mjs');
30
+ await watch(rig,opts,{terminalCols:w,terminalRows:h,large,scene:args.includes('--scene')||args.includes('--attach')?scene:null,arrival:args.includes('--attach'),message:args.includes('--message')?message:null});
31
+ }else
32
+ console.log((large?[...cellsToAnsi(r.frame),...composeMessage(null,message,{terminalCols:w})]:composeMessage(r.frame,message,{terminalCols:w})).join('\n'));
33
+ }
@@ -0,0 +1,56 @@
1
+ import {performance} from 'node:perf_hooks';
2
+ import {CIPHER_CLIPS,renderCipher,renderStory,STORY_DURATION} from './character.mjs';
3
+ import {composeMessage} from '../../lucid/recovery/layout.mjs';
4
+ import {cellsToAnsi} from '../../../vendor/quox-dragon/qart.js';
5
+
6
+ /** One bounded performance. No alternate screen or full-screen clearing. */
7
+ export async function watch(paths,options,config){
8
+ if(!process.stdout.isTTY||!process.stdin.isTTY)throw new Error('--watch/--intro needs an interactive terminal. Use --scene and --at for snapshots.');
9
+ const {terminalCols,terminalRows,scene,large,message}=config;
10
+ const clip=scene?CIPHER_CLIPS.find(c=>c.id===scene):null;
11
+ const duration=(config.arrival&&options.deck?3200:clip?.duration??STORY_DURATION)+(config.arrival?6000:0);
12
+ const realCols=process.stdout.columns,realRows=process.stdout.rows;
13
+ const start=performance.now();
14
+ const render=t=>{
15
+ const r=scene?renderCipher(paths,scene,t,options):renderStory(paths,t,options);
16
+ if(!r?.ok)throw new Error(r?.code??'Invalid story frame');
17
+ const caption=message??'CIPHER · '+r.clip+'\n'+r.caption;
18
+ return large?[...cellsToAnsi(r.frame),...caption.split('\n')]:composeMessage(r.frame,caption,{terminalCols});
19
+ };
20
+ // Reserve enough lines for captions too; keep within the real terminal.
21
+ const height=options.rows+3;
22
+ if(height>=terminalRows)throw new Error('Animation needs room for its caption; reduce --rows.');
23
+ let ended=false,drawn=false,timer,resolveDone,rejectDone;
24
+ const wasRaw=Boolean(process.stdin.isRaw);
25
+ const done=new Promise((resolve,reject)=>{resolveDone=resolve;rejectDone=reject;});
26
+ const cleanup=(error)=>{
27
+ if(ended)return;ended=true;clearInterval(timer);
28
+ process.stdin.off('data',key);process.stdout.off('resize',resized);
29
+ process.off('SIGINT',sigint);process.off('SIGTERM',sigterm);
30
+ process.stdin.setRawMode(wasRaw);process.stdin.pause();
31
+ if(drawn)process.stdout.write(`\x1b[${height}B\r`);
32
+ process.stdout.write('\x1b[0m\x1b[?25h');
33
+ error?rejectDone(error):resolveDone();
34
+ };
35
+ const key=data=>{if(data.includes(3)||data.toString().includes('q'))cleanup();};
36
+ const sigint=()=>cleanup(),sigterm=()=>cleanup();
37
+ const resized=()=>cleanup(new Error('Terminal resized; rerun for the new art slot.'));
38
+ try{
39
+ process.stdin.setRawMode(true);process.stdin.resume();process.stdin.on('data',key);
40
+ process.stdout.on('resize',resized);process.on('SIGINT',sigint);process.on('SIGTERM',sigterm);
41
+ process.stdout.write('\x1b[?25l');
42
+ // Allocate the block once; each frame replaces only these lines.
43
+ process.stdout.write('\n'.repeat(height)+`\x1b[${height}A`);drawn=true;
44
+ const tick=()=>{
45
+ try{
46
+ if(process.stdout.columns!==realCols||process.stdout.rows!==realRows)return resized();
47
+ const t=Math.min(performance.now()-start,duration-1),lines=render(t);
48
+ if(lines.length>height)throw new Error('Message exceeds the animation block; use a shorter message or a static preview.');
49
+ process.stdout.write(Array.from({length:height},(_,i)=>'\r\x1b[2K'+(lines[i]??'')+'\n').join('')+`\x1b[${height}A`);
50
+ if(performance.now()-start>=duration)cleanup();
51
+ }catch(e){cleanup(e);}
52
+ };
53
+ tick();if(!ended)timer=setInterval(tick,1000/6);
54
+ await done;
55
+ }catch(e){cleanup(e);await done;}
56
+ }
@@ -0,0 +1,19 @@
1
+ import {renderSvgRig} from '../../../factory/svg-rig.mjs';
2
+ import {flattenPath} from '../../stage3/svg-coverage.js';
3
+ export const DRAGO_CLIPS=Object.freeze(['idle','active']);
4
+ const glare={
5
+ 'far-eye-white':'M 325 113 L 358 128 L 357 141 Q 336 138 325 124 Z',
6
+ 'near-eye-white':'M 382 128 L 424 111 L 422 126 Q 405 139 383 141 Z',
7
+ 'far-pupil':'M 341 128 C 341 122 348 122 348 128 C 348 134 341 134 341 128 Z',
8
+ 'near-pupil':'M 398 128 C 398 122 405 122 405 128 C 405 134 398 134 398 128 Z'
9
+ };
10
+ export function poseDrago(rig,scene='idle'){
11
+ if(!DRAGO_CLIPS.includes(scene))throw RangeError('Unknown Drago scene: '+scene);
12
+ const paths=rig.paths.map(p=>scene==='active'&&glare[p.id]?{...p,polygons:flattenPath(glare[p.id])}:p);
13
+ const byId=new Map(paths.map(p=>[p.id,p]));
14
+ return {...rig,paths,eyes:rig.eyes.map(e=>({...e,socket:byId.get(e.socket.id),white:byId.get(e.white.id),pupil:byId.get(e.pupil.id)}))};
15
+ }
16
+ export function renderDrago(rig,scene='idle',ms=0,options={}){
17
+ if(!Number.isFinite(ms)||ms<0)throw RangeError('Invalid time');
18
+ return {...renderSvgRig(poseDrago(rig,scene),options),scene,staticStudy:true,caption:'DRAGO\n'+(scene==='active'?'Active glare study.':'On guard.')};
19
+ }
@@ -0,0 +1,56 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 480">
2
+ <title>Drago — crimson engagement guardian, shield-bearing terminal master</title>
3
+ <g id="wings">
4
+ <path id="left-wing" fill="#922837" d="M 349 289 C 286 246 246 199 224 146 Q 208 115 183 103 C 197 184 158 282 109 365 Q 156 334 181 348 Q 218 308 246 340 Q 275 308 302 350 Z"/>
5
+ <path id="left-wing-ribs" fill="none" stroke="#ef5350" stroke-width="8" d="M 349 289 Q 240 207 183 103 M 183 103 Q 201 242 109 365 M 204 178 Q 235 272 181 348 M 232 217 L 246 340"/>
6
+ <path id="right-wing" fill="#6d2433" d="M 445 266 Q 520 206 566 108 Q 585 80 605 83 C 585 165 628 273 687 361 Q 647 333 623 349 Q 589 305 563 332 Q 529 301 501 347 Z"/>
7
+ <path id="right-wing-ribs" fill="none" stroke="#d34243" stroke-width="9" d="M 445 266 Q 546 167 605 83 M 605 83 Q 583 203 687 361 M 584 153 Q 554 234 623 349 M 556 194 L 563 332"/>
8
+ <path id="wing-claws" fill="#ffc28e" d="M 180 121 Q 162 93 192 86 L 183 103 Z M 593 100 Q 600 69 621 74 L 605 91 Z"/>
9
+ </g>
10
+ <g id="body">
11
+ <path id="haunches" fill="#b53b43" d="M 346 250 C 293 266 267 326 278 380 Q 291 422 342 420 L 476 420 Q 536 416 543 372 C 544 311 493 259 450 242 Z"/>
12
+ <path id="neck" fill="#c34143" d="M 348 146 C 335 205 327 243 310 294 Q 382 324 473 285 C 438 245 442 195 432 146 Z"/>
13
+ <path id="throat" fill="#e9ac8a" d="M 361 166 Q 358 233 339 280 Q 388 307 435 278 Q 414 224 410 166 Z"/>
14
+ <path id="throat-plates" fill="none" stroke="#79303b" stroke-width="6" d="M 359 199 Q 386 211 415 197 M 356 221 Q 387 235 420 221 M 350 245 Q 387 258 426 245 M 344 267 Q 388 281 433 267"/>
15
+ <path id="spine" fill="#ff6048" d="M 433 156 L 473 160 L 447 180 L 485 197 L 448 206 L 481 230 L 451 232 L 491 262 L 464 260 L 510 287 L 481 288 Z"/>
16
+ <path id="feet" fill="#bd4249" d="M 301 357 Q 278 379 244 398 Q 230 420 262 428 L 329 428 L 344 383 Z M 481 362 Q 518 375 548 407 Q 552 428 523 429 L 463 429 L 450 384 Z"/>
17
+ <path id="foot-talons" fill="#f5c59d" d="M 251 410 L 243 430 L 267 422 L 278 409 L 274 431 L 295 421 L 306 409 L 307 430 L 326 421 M 477 410 L 467 431 L 489 423 L 500 411 L 500 433 L 520 423 L 531 414 L 535 431 L 548 424"/>
18
+ </g>
19
+ <g id="head">
20
+ <path id="far-horn" fill="#f5b47f" d="M 343 107 C 309 93 296 63 290 27 Q 320 65 365 70 Z"/>
21
+ <path id="near-horn" fill="#ffc18b" d="M 410 91 C 459 81 482 50 502 18 C 499 64 462 104 430 121 Z"/>
22
+ <path id="skull" fill="#d84a49" d="M 323 112 Q 320 79 362 75 Q 412 65 437 101 L 444 145 Q 425 184 380 188 Q 329 183 312 149 Z"/>
23
+ <path id="brow-armour" fill="#f06956" d="M 324 104 L 349 78 L 366 86 L 386 62 L 401 84 L 426 79 L 438 106 L 395 121 L 365 118 Z"/>
24
+ <path id="cheek-blades" fill="#ff7458" d="M 434 119 L 478 100 L 453 132 L 480 134 L 447 152 L 461 168 L 425 169 Z M 321 119 L 294 105 L 303 132 L 288 137 L 315 153 Z"/>
25
+ <path id="muzzle" fill="#e0715b" d="M 328 145 Q 357 135 379 150 Q 401 142 422 151 L 414 174 Q 379 196 332 178 L 313 164 Z"/>
26
+ <path id="nose" fill="#080d19" d="M 329 153 L 341 151 L 336 158 Z M 400 153 L 411 151 L 404 159 Z"/>
27
+ <path id="mouth" fill="none" stroke="#080d19" stroke-width="5" d="M 327 172 Q 370 184 414 168"/>
28
+ <path id="fangs" fill="#fff0d4" d="M 337 176 L 346 193 L 350 179 Z M 398 176 L 402 188 L 409 172 Z"/>
29
+ </g>
30
+ <g id="shield">
31
+ <path id="shield-outer" fill="#ff7058" d="M 386 260 Q 430 289 492 294 L 486 350 Q 471 401 390 440 Q 312 401 296 350 L 290 294 Q 347 290 386 260 Z"/>
32
+ <path id="shield-dark" fill="#080d19" d="M 386 273 Q 435 301 478 303 L 473 348 Q 456 391 390 425 Q 327 391 309 348 L 304 303 Q 352 298 386 273 Z"/>
33
+ <path id="shield-left" fill="#bd3841" d="M 385 285 L 385 411 Q 333 381 321 346 L 317 314 Q 355 307 385 285 Z"/>
34
+ <path id="shield-right" fill="#842d3d" d="M 394 286 Q 431 309 466 314 L 461 345 Q 448 382 394 411 Z"/>
35
+ <path id="shield-rune" fill="none" stroke="#ffc999" stroke-width="8" d="M 360 334 L 389 354 L 418 334 M 389 354 L 389 386"/>
36
+ </g>
37
+ <g id="hands">
38
+ <path id="left-arm" fill="#d74b4a" d="M 315 264 Q 279 270 283 298 L 317 321 L 337 304 L 312 288 Z"/>
39
+ <path id="right-arm" fill="#ba3c43" d="M 454 260 Q 501 265 501 296 L 464 320 L 442 305 L 475 286 Z"/>
40
+ <path id="gripping-talons" fill="#ffe0b2" d="M 311 297 Q 322 286 333 301 L 330 321 L 323 305 L 318 323 Z M 329 297 Q 342 291 348 306 L 343 323 L 338 307 Z M 445 297 Q 457 290 462 303 L 456 324 L 451 308 Z M 464 297 Q 478 288 482 302 L 473 322 L 471 305 Z"/>
41
+ </g>
42
+ <g id="tail">
43
+ <path id="tail-coil" fill="#aa3541" d="M 501 350 C 554 351 608 371 605 405 C 601 440 507 464 392 460 C 271 458 190 439 180 406 Q 169 376 199 359 Q 178 392 232 407 C 326 438 473 431 535 411 Q 564 393 502 384 Z"/>
44
+ <path id="tail-rim" fill="none" stroke="#ed5a50" stroke-width="9" d="M 199 359 Q 178 392 232 407 C 326 438 473 431 535 411"/>
45
+ <path id="tail-tip" fill="#ff815e" d="M 201 359 L 169 346 L 180 379 L 162 390 L 195 395 Q 181 376 201 359 Z"/>
46
+ <path id="tail-scutes" fill="none" stroke="#752939" stroke-width="6" d="M 226 410 L 218 434 M 264 420 L 257 444 M 305 428 L 302 453 M 351 434 L 350 458 M 400 435 L 402 459 M 451 432 L 459 457 M 500 425 L 514 449 M 543 410 L 563 432"/>
47
+ </g>
48
+ <g id="eyes">
49
+ <path id="far-eye-socket" fill="#080d19" d="M 321 111 Q 340 100 362 114 L 363 145 Q 336 154 320 134 Z"/>
50
+ <path id="far-eye-white" fill="#ffffff" d="M 326 115 Q 342 109 357 118 L 357 139 Q 338 147 326 131 Z"/>
51
+ <path id="far-pupil" fill="#080d19" d="M 338 127 C 338 117 348 117 348 127 C 348 137 338 137 338 127 Z"/>
52
+ <path id="near-eye-socket" fill="#080d19" d="M 377 115 Q 405 99 429 109 L 429 135 Q 410 153 379 144 Z"/>
53
+ <path id="near-eye-white" fill="#ffffff" d="M 383 119 Q 405 108 423 113 L 423 132 Q 407 146 383 139 Z"/>
54
+ <path id="near-pupil" fill="#080d19" d="M 397 127 C 397 116 408 116 408 127 C 408 138 397 138 397 127 Z"/>
55
+ </g>
56
+ </svg>
@@ -0,0 +1,87 @@
1
+ import {renderSvgRig} from '../../../factory/svg-rig.mjs';
2
+ import {poseDrago} from '../v1/character.mjs';
3
+ const clamp=x=>Math.max(0,Math.min(1,x));
4
+ const ease=x=>{x=clamp(x);return x*x*(3-2*x);};
5
+ const pulse=(t,a,b)=>t<=a||t>=b?0:Math.sin(Math.PI*(t-a)/(b-a))**2;
6
+ export const DRAGO_CLIPS=Object.freeze([
7
+ {id:'idle',title:'Keeping Watch',duration:14000},
8
+ {id:'attach',title:'The Guardian Arrives',duration:3800},
9
+ {id:'active',title:'Locked On',duration:3600},
10
+ {id:'inspect',title:'Closer Inspection',duration:3800},
11
+ {id:'guard',title:'Hold the Line',duration:4200,power:true},
12
+ {id:'concerned',title:'Needs Attention',duration:3200},
13
+ {id:'acknowledge',title:'Understood',duration:2800},
14
+ {id:'standby',title:'Awaiting You',duration:3200}
15
+ ]);
16
+ export const STORY=[['attach',3800],['idle',2400],['inspect',3800],['active',3600],['guard',4200],['concerned',3200],['acknowledge',2800],['standby',3200],['idle',2500]];
17
+ export const STORY_DURATION=STORY.reduce((n,b)=>n+b[1],0);
18
+ const captions={idle:'On guard.',attach:'At your side.',active:'Active expression study.',inspect:'Inspection expression study.',guard:'Shield-forward visual study.',concerned:'Concern expression study.',acknowledge:'Acknowledgement expression study.',standby:'Waiting expression study.'};
19
+ export function bodyPose(ms){const t=ms%14000;return {
20
+ breath:1.8*pulse(t,500,6500),wing:.025*pulse(t,3000,6500),far:.019*pulse(t,3500,7000),tail:1.1*pulse(t,7500,12000),
21
+ turn:.009*pulse(t,900,3800),nod:1.8*pulse(t,900,3800),shield:0,grip:0,gaze:0,glare:false,eye:1
22
+ };}
23
+ function performance(scene,ms,options){
24
+ const t=options.still?0:ms;
25
+ let pose=bodyPose(options.still?0:(options.motionTime??t));
26
+ if(scene==='attach'){
27
+ const duration=options.deck?4800:3800;
28
+ if(t>=duration)return bodyPose(t-duration);
29
+ const u=t/duration;
30
+ pose={...bodyPose(0),gaze:.6*pulse(u,0,.55),turn:-.015*pulse(u,.1,.7),nod:5*pulse(u,.32,.9),
31
+ wing:.06*pulse(u,.16,.8),far:.045*pulse(u,.23,.88),shield:.65*pulse(u,.3,.86),grip:pulse(u,.3,.86),tail:pulse(u,.45,1),glare:u>.32&&u<.67};
32
+ }
33
+ if(scene==='active')pose={...pose,glare:true,nod:pose.nod+3,grip:.7,gaze:.2};
34
+ if(scene==='inspect')pose={...pose,gaze:.8*Math.sin(t/1100),turn:pose.turn+.018*Math.sin(t/1100),nod:pose.nod+1};
35
+ if(scene==='guard'){const p=options.still?1:pulse(t,0,4200);pose={...pose,glare:true,shield:p,grip:p,wing:pose.wing+.045*p,far:pose.far+.035*p,nod:pose.nod+5*p};}
36
+ if(scene==='concerned')pose={...pose,turn:pose.turn-.022,nod:pose.nod+2,gaze:-.5,eye:1.08};
37
+ if(scene==='acknowledge')pose={...pose,nod:pose.nod+6*pulse(t,300,2100),eye:1.05};
38
+ if(scene==='standby')pose={...pose,gaze:-.65,turn:pose.turn-.012,eye:.9};
39
+ if(options.still)pose={...pose,breath:0,wing:0,far:0,tail:0,nod:0,turn:0,gaze:0};
40
+ return pose;
41
+ }
42
+ const rotate=(x,y,cx,cy,a)=>[cx+(x-cx)*Math.cos(a)-(y-cy)*Math.sin(a),cy+(x-cx)*Math.sin(a)+(y-cy)*Math.cos(a)];
43
+ export function poseRig(rig,pose){
44
+ const source=poseDrago(rig,pose.glare?'active':'idle');
45
+ const paths=source.paths.map(p=>({...p,polygons:p.polygons.map(poly=>poly.map(([x,y])=>{
46
+ if(p.id.endsWith('-eye-white')||p.id.endsWith('-pupil')){
47
+ y=127+(y-127)*pose.eye;
48
+ if(p.id.endsWith('-pupil'))x+=pose.gaze*3;
49
+ }
50
+ if(p.group==='head'||p.group==='eyes'){
51
+ const v=rotate(x,y,385,190,pose.turn);return [v[0],v[1]+pose.nod-pose.breath];
52
+ }
53
+ if(['neck','throat','throat-plates','spine'].includes(p.id)){
54
+ const w=ease((290-y)/100),v=rotate(x,y,385,190,pose.turn*w);return [v[0],v[1]+(pose.nod-pose.breath)*w];
55
+ }
56
+ if(p.group==='wings'){
57
+ const right=x>400,a=right?-pose.far:pose.wing;return rotate(x,y,right?455:335,290,a);
58
+ }
59
+ if(p.group==='tail'){
60
+ // Pin the upper-right attachment. Distal coil gathers inward with a small settling recoil.
61
+ const w=ease((y-360)/55)*ease((610-x)/160),recoil=pose.tail-.24*pose.tail*pose.tail;
62
+ return [x+(390-x)*.025*w*recoil,y-5*w*recoil];
63
+ }
64
+ if(p.group==='shield')return [390+(x-390)*(1+.06*pose.shield),350+(y-350)*(1+.06*pose.shield)-9*pose.shield-pose.breath*.3];
65
+ if(p.group==='hands'){
66
+ const w=ease((y-270)/40);return [x+(x<390?2:-2)*pose.grip*w+(x-390)*.06*pose.shield*w,y-(9*pose.shield+pose.breath*.3)*w];
67
+ }
68
+ return [x,y];
69
+ }))}));
70
+ const byId=new Map(paths.map(p=>[p.id,p]));
71
+ return {...source,paths,bounds:{left:rig.bounds.left-10,top:rig.bounds.top-10,right:rig.bounds.right+10,bottom:rig.bounds.bottom+6},
72
+ eyes:source.eyes.map(e=>({...e,socket:byId.get(e.socket.id),white:byId.get(e.white.id),pupil:byId.get(e.pupil.id)}))};
73
+ }
74
+ export function renderDrago(rig,scene='idle',ms=0,options={}){
75
+ const clip=DRAGO_CLIPS.find(c=>c.id===scene);
76
+ if(!clip)return {ok:false,code:'UNKNOWN_CLIP'};
77
+ if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
78
+ const pose=performance(scene,ms,options),posed=poseRig(rig,pose),r=renderSvgRig(posed,options);
79
+ // A local shield glow, never a claim about security posture or real authorisation.
80
+ if(scene==='guard'&&!options.mono){const brightness=options.still?.9:pulse(ms,0,4200);for(const row of r.frame)for(const cell of row)if(cell.rgb&&cell.rgb[0]===255&&cell.rgb[1]===112){cell.rgb=[255,Math.round(112+70*brightness),Math.round(88+50*brightness)];}}
81
+ return {...r,scene,clip:clip.title,caption:captions[scene],pose,staticStudy:false};
82
+ }
83
+ export function renderStory(rig,ms,options={}){
84
+ if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
85
+ let t=options.still?0:Math.min(ms,STORY_DURATION-1);
86
+ for(const [scene,duration]of STORY){if(t<duration)return renderDrago(rig,scene,t,{...options,deck:false,motionTime:Math.max(0,ms-3800)});t-=duration;}
87
+ }
@@ -0,0 +1,67 @@
1
+ import {turnHead} from './profile.mjs';
2
+ import {renderDrago as renderV2,poseRig,bodyPose,DRAGO_CLIPS as V2_CLIPS} from '../v2/character.mjs';
3
+ import {renderSvgRig} from '../../../factory/svg-rig.mjs';
4
+ import {flattenPath} from '../../stage3/svg-coverage.js';
5
+ const clamp=x=>Math.max(0,Math.min(1,x));
6
+ const ease=x=>{x=clamp(x);return x*x*(3-2*x);};
7
+ const envelope=(t,a,b,c,d)=>ease((t-a)/(b-a))*(1-ease((t-c)/(d-c)));
8
+ export const FIRE_DURATION=6800;
9
+ export const DRAGO_CLIPS=Object.freeze([...V2_CLIPS,{id:'fire',title:'Scoped Inferno',duration:FIRE_DURATION,power:true}]);
10
+ export const STORY=[['attach',3800],['idle',1400],['fire',FIRE_DURATION],['idle',2400]];
11
+ export const STORY_DURATION=STORY.reduce((n,b)=>n+b[1],0);
12
+ const path=(id,d,rgb,stroke=0)=>({id,group:'fire-effect',polygons:flattenPath(d),rgb,stroke,erase:rgb.every((v,i)=>v===[8,13,25][i])});
13
+ const point=([x,y])=>`${x.toFixed(3)} ${y.toFixed(3)}`;
14
+ export function fireRig(rig,ms,options={}){
15
+ const t=options.still?3000:ms;
16
+ const aim=envelope(t,0,900,5600,FIRE_DURATION),blast=envelope(t,1200,1700,4300,4800),embers=envelope(t,4400,4700,5200,5600);
17
+ const pose={...bodyPose(0),glare:aim>.2,grip:aim,shield:.25*aim,nod:2*aim,gaze:.5*aim,turn:.018*aim,wing:.012*aim,far:0};
18
+ const posed=poseRig(rig,pose);
19
+ // Lower the right sail so the line of fire clears its claw and membrane.
20
+ let paths=posed.paths.map(p=>({...p,polygons:p.polygons.map(poly=>poly.map(([x,y])=>p.group==='wings'&&x>400?[x,y+28*aim*ease((300-y)/160)]:[x,y]))}));
21
+ const turn=ease(Math.max(0,Math.min(1,(t-180)/720)))*(1-ease((t-5600)/1200));
22
+ paths=turnHead(paths,turn);
23
+ const mouth=[419+(502-419)*turn,169+(124-169)*turn],target=[650,57],dx=target[0]-mouth[0],dy=target[1]-mouth[1],length=Math.hypot(dx,dy),normal=[-dy/length,dx/length];
24
+ if(aim>.02){
25
+ // A visible target box describes the animation's scope; it is not an approval badge.
26
+ const c=blast>.1?[255,116,56]:[210,148,92];
27
+ paths.push(path('scope-brackets','M 634 24 L 619 24 L 619 39 M 665 24 L 681 24 L 681 39 M 619 75 L 619 91 L 635 91 M 665 91 L 681 91 L 681 75',c,5));
28
+ paths.push(path('scope-target','M 650 44 L 663 57 L 650 70 L 637 57 Z',[255,185,94],3));
29
+ }
30
+ if(blast>.01){
31
+ const reach=ease((t-1200)/500),tip=[mouth[0]+dx*reach,mouth[1]+dy*reach];
32
+ const flame=(id,width,rgb,phase)=>{
33
+ const sides=[[],[]];
34
+ for(let i=0;i<=24;i++){
35
+ const u=i/24,x=mouth[0]+(tip[0]-mouth[0])*u,y=mouth[1]+(tip[1]-mouth[1])*u;
36
+ const rip=.70+.30*Math.sin(u*33-t*.025+phase),w=(2+width*Math.sin(Math.PI*u*.76))*blast*rip;
37
+ for(let side=0;side<2;side++){const s=side?1:-1;sides[side].push([x+normal[0]*w*s,y+normal[1]*w*s]);}
38
+ }
39
+ paths.push(path(id,'M '+[...sides[0],...sides[1].reverse()].map(point).join(' L ')+' Z',rgb));
40
+ };
41
+ flame('fire-outer',20,[235,48,30],0);flame('fire-orange',13,[255,116,30],.5);flame('fire-gold',7,[255,211,70],1);flame('fire-core',2.5,[255,249,215],1.4);
42
+ if(reach>.95){
43
+ // Impact burst remains wholly within the target's fixed box.
44
+ const ring=[];for(let j=0;j<20;j++){const a=j*Math.PI/10,r=(j%2?9:19+4*Math.sin(t*.03+j))*blast;ring.push([650+Math.cos(a)*r,57+Math.sin(a)*r]);}
45
+ paths.push(path('fire-impact','M '+ring.map(point).join(' L ')+' Z',[255,176,45]));
46
+ paths.push(path('fire-impact-core','M 650 48 L 658 57 L 650 66 L 642 57 Z',[255,249,215]));
47
+ }
48
+ }
49
+ if(embers>.01)for(let i=0;i<7;i++){
50
+ const age=clamp((t-4400)/1200),x=632+i*5,y=45+((i*13)%22)+age*10;
51
+ paths.push(path('fire-ember-'+i,`M ${x} ${y} L ${x+2} ${y} L ${x+2} ${y+3} L ${x} ${y+3} Z`,[255,Math.round(110+80*embers),35]));
52
+ }
53
+ const byId=new Map(paths.map(p=>[p.id,p]));
54
+ return {rig:{...posed,paths,eyes:posed.eyes.filter(e=>turn<.95||e.name!=='far').map(e=>({...e,socket:byId.get(e.socket.id),white:byId.get(e.white.id),pupil:byId.get(e.pupil.id)}))},pose,phase:t<1200?'Aim':t<4800?'Directed fire':t<5600?'Embers':'Settle',blast,aim,mouth,target};
55
+ }
56
+ export function renderDrago(rig,scene='idle',ms=0,options={}){
57
+ if(scene!=='fire')return renderV2(rig,scene,ms,options);
58
+ if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
59
+ if(!options.still&&ms>=FIRE_DURATION)return renderV2(rig,'idle',ms-FIRE_DURATION,options);
60
+ const f=fireRig(rig,ms,options),r=renderSvgRig(f.rig,options);
61
+ return {...r,scene,clip:'Scoped Inferno',caption:`${f.phase} · visual rehearsal`,pose:f.pose,phase:f.phase,blast:f.blast,mouth:f.mouth,target:f.target,staticStudy:false};
62
+ }
63
+ export function renderStory(rig,ms,options={}){
64
+ if(!Number.isFinite(ms)||ms<0)return {ok:false,code:'INVALID_TIME'};
65
+ let t=options.still?0:Math.min(ms,STORY_DURATION-1);
66
+ for(const [scene,duration]of STORY){if(t<duration)return renderDrago(rig,scene,t,{...options,deck:false,motionTime:Math.max(0,ms-3800)});t-=duration;}
67
+ }
@@ -0,0 +1,35 @@
1
+ import {flattenPath} from '../../stage3/svg-coverage.js';
2
+ // Authored right-facing head: swept-back horns, one visible eye, elongated muzzle and open jaw.
3
+ const shapes={
4
+ 'far-horn':'M 380 101 Q 330 87 313 51 Q 353 77 405 77 Z',
5
+ 'near-horn':'M 403 92 Q 362 59 346 23 Q 391 59 435 72 Z',
6
+ 'skull':'M 365 123 Q 356 91 398 84 Q 434 78 456 99 L 486 98 L 505 110 L 500 120 L 446 139 Q 422 171 383 167 Z',
7
+ 'brow-armour':'M 376 100 L 393 79 L 413 84 L 430 71 L 448 95 L 430 109 Z',
8
+ 'cheek-blades':'M 386 118 L 344 105 L 362 131 L 339 142 L 376 152 L 357 173 L 400 161 Z',
9
+ 'muzzle':'M 427 120 L 491 99 L 506 109 L 502 118 L 443 139 Z M 402 145 L 443 145 L 493 130 L 503 137 Q 458 164 420 170 L 393 159 Z',
10
+ 'nose':'M 483 107 L 496 106 L 492 113 L 484 114 Z',
11
+ 'mouth-line':'M 425 144 L 500 119',
12
+ 'mouth':'M 425 144 L 500 119',
13
+ 'fangs':'M 465 129 L 474 140 L 477 125 Z M 487 120 L 493 132 L 498 117 Z',
14
+ 'near-eye-socket':'M 397 110 L 439 103 L 440 126 Q 421 143 400 131 Z',
15
+ 'near-eye-white':'M 403 115 L 434 109 L 434 124 Q 422 135 405 128 Z',
16
+ 'near-pupil':'M 422 121 C 422 112 430 112 430 121 C 430 130 422 130 422 121 Z'
17
+ };
18
+ function samples(polys,n=72){
19
+ const p=polys[0],edges=p.map((v,i)=>{const q=p[(i+1)%p.length];return Math.hypot(q[0]-v[0],q[1]-v[1]);}),total=edges.reduce((a,b)=>a+b,0);
20
+ return Array.from({length:n},(_,i)=>{let s=i*total/n,j=0;while(j<edges.length-1&&s>edges[j])s-=edges[j++];const a=p[j],b=p[(j+1)%p.length],f=edges[j]?s/edges[j]:0;return [a[0]+(b[0]-a[0])*f,a[1]+(b[1]-a[1])*f];});
21
+ }
22
+ export function turnHead(paths,amount){
23
+ return paths.map(p=>{
24
+ if(p.group!=='head'&&p.group!=='eyes'&&!['neck','throat','throat-plates','spine'].includes(p.id))return p;
25
+ if(p.group!=='head'&&p.group!=='eyes')return {...p,polygons:p.polygons.map(poly=>poly.map(([x,y])=>[x+22*amount*Math.max(0,Math.min(1,(290-y)/110)),y]))};
26
+ if(p.id.startsWith('far-eye')||p.id==='far-pupil'){
27
+ return {...p,polygons:p.polygons.map(poly=>poly.map(([x,y])=>[390+(x-390)*(1-amount),122+(y-122)*(1-amount)]))};
28
+ }
29
+ const d=shapes[p.id];if(!d)return p;
30
+ const target=flattenPath(d);if(amount>=.999)return {...p,polygons:target};
31
+ if(amount<=0)return p;
32
+ const a=samples(p.polygons),b=samples(target);
33
+ return {...p,polygons:[a.map(([x,y],i)=>[x+(b[i][0]-x)*amount,y+(b[i][1]-y)*amount])]};
34
+ });
35
+ }
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs/promises';
3
+ import {parseSvgRig} from '../../../factory/svg-rig.mjs';
4
+ import {renderDrago,DRAGO_CLIPS} from './character.mjs';
5
+ import {artSlot,composeMessage} from '../../lucid/recovery/layout.mjs';
6
+ import {cellsToAnsi} from '../../../vendor/quox-dragon/qart.js';
7
+ const args=process.argv.slice(2),values=new Set(['--cols','--rows','--terminal-cols','--terminal-rows','--message','--scene','--at']);
8
+ for(let i=0;i<args.length;i++){
9
+ if(values.has(args[i])){if(!args[i+1]||args[i+1].startsWith('--'))throw Error('Missing option value');i++;}
10
+ else if(!['--large','--mono','--help','--watch','--attach','--intro','--still','--list'].includes(args[i]))throw Error('Unknown option: '+args[i]);
11
+ }
12
+ const value=(name,fallback)=>args.includes(name)?args[args.indexOf(name)+1]:fallback;
13
+ if(args.includes('--help')){console.log('Drago V4 scoped fire studio. --watch --attach --intro --still --scene ID --at MS --list --large --mono --message TEXT --cols N --rows N --terminal-cols N --terminal-rows N');process.exit(0);}
14
+ if(args.includes('--list')){console.log(DRAGO_CLIPS.map(c=>c.id+' · '+c.title).join('\n'));process.exit(0);}
15
+ const w=Number(value('--terminal-cols',process.stdout.columns||80)),h=Number(value('--terminal-rows',process.stdout.rows||24));
16
+ if(!Number.isSafeInteger(w)||!Number.isSafeInteger(h)||w<1||h<1)throw Error('Invalid terminal size');
17
+ const slot=artSlot(w,h),large=args.includes('--large')||args.includes('--intro'),explicit=large||args.includes('--cols')||args.includes('--rows');
18
+ const cols=Number(value('--cols',large?Math.min(120,w-4):Math.min(39,slot.cols))),rows=Number(value('--rows',large?Math.min(36,h-4):Math.min(11,slot.rows)));
19
+ const message=value('--message','DRAGO\nOn guard.');
20
+ if(!slot.showArt&&!explicit){console.log(composeMessage(null,message,{terminalCols:w}).join('\n'));}
21
+ else{
22
+ if(cols>w-4||rows>h-2)throw Error('Requested art exceeds terminal margins');
23
+ const rig=parseSvgRig(await fs.readFile(new URL('../v1/drago-master.svg',import.meta.url),'utf8'));
24
+ const opts={cols,rows,mono:args.includes('--mono'),still:args.includes('--still'),deck:large};
25
+ const scene=value('--scene',args.includes('--attach')?'attach':'idle');
26
+ const r=renderDrago(rig,scene,Number(value('--at',0)),opts);
27
+ if(!r.ok)throw Error(r.code);
28
+ if(args.includes('--watch')||args.includes('--attach')||args.includes('--intro')){
29
+ const {watch}=await import('./watch.mjs');
30
+ await watch(rig,opts,{terminalCols:w,terminalRows:h,large,scene:args.includes('--scene')||args.includes('--attach')?scene:null,arrival:args.includes('--attach'),message:args.includes('--message')?message:null});
31
+ }else
32
+ console.log((large?[...cellsToAnsi(r.frame),...composeMessage(null,message,{terminalCols:w})]:composeMessage(r.frame,message,{terminalCols:w})).join('\n'));
33
+ }
@@ -0,0 +1,56 @@
1
+ import {performance} from 'node:perf_hooks';
2
+ import {DRAGO_CLIPS,renderDrago,renderStory,STORY_DURATION} from './character.mjs';
3
+ import {composeMessage} from '../../lucid/recovery/layout.mjs';
4
+ import {cellsToAnsi} from '../../../vendor/quox-dragon/qart.js';
5
+
6
+ /** One bounded performance. No alternate screen or full-screen clearing. */
7
+ export async function watch(paths,options,config){
8
+ if(!process.stdout.isTTY||!process.stdin.isTTY)throw new Error('--watch/--intro needs an interactive terminal. Use --scene and --at for snapshots.');
9
+ const {terminalCols,terminalRows,scene,large,message}=config;
10
+ const clip=scene?DRAGO_CLIPS.find(c=>c.id===scene):null;
11
+ const duration=(config.arrival&&options.deck?4800:clip?.duration??STORY_DURATION)+(config.arrival?6000:0);
12
+ const realCols=process.stdout.columns,realRows=process.stdout.rows;
13
+ const start=performance.now();
14
+ const render=t=>{
15
+ const r=scene?renderDrago(paths,scene,t,options):renderStory(paths,t,options);
16
+ if(!r?.ok)throw new Error(r?.code??'Invalid story frame');
17
+ const caption=message??'DRAGO · '+r.clip+'\n'+r.caption;
18
+ return large?[...cellsToAnsi(r.frame),...caption.split('\n')]:composeMessage(r.frame,caption,{terminalCols});
19
+ };
20
+ // Reserve enough lines for captions too; keep within the real terminal.
21
+ const height=options.rows+3;
22
+ if(height>=terminalRows)throw new Error('Animation needs room for its caption; reduce --rows.');
23
+ let ended=false,drawn=false,timer,resolveDone,rejectDone;
24
+ const wasRaw=Boolean(process.stdin.isRaw);
25
+ const done=new Promise((resolve,reject)=>{resolveDone=resolve;rejectDone=reject;});
26
+ const cleanup=(error)=>{
27
+ if(ended)return;ended=true;clearInterval(timer);
28
+ process.stdin.off('data',key);process.stdout.off('resize',resized);
29
+ process.off('SIGINT',sigint);process.off('SIGTERM',sigterm);
30
+ process.stdin.setRawMode(wasRaw);process.stdin.pause();
31
+ if(drawn)process.stdout.write(`\x1b[${height}B\r`);
32
+ process.stdout.write('\x1b[0m\x1b[?25h');
33
+ error?rejectDone(error):resolveDone();
34
+ };
35
+ const key=data=>{if(data.includes(3)||data.toString().includes('q'))cleanup();};
36
+ const sigint=()=>cleanup(),sigterm=()=>cleanup();
37
+ const resized=()=>cleanup(new Error('Terminal resized; rerun for the new art slot.'));
38
+ try{
39
+ process.stdin.setRawMode(true);process.stdin.resume();process.stdin.on('data',key);
40
+ process.stdout.on('resize',resized);process.on('SIGINT',sigint);process.on('SIGTERM',sigterm);
41
+ process.stdout.write('\x1b[?25l');
42
+ // Allocate the block once; each frame replaces only these lines.
43
+ process.stdout.write('\n'.repeat(height)+`\x1b[${height}A`);drawn=true;
44
+ const tick=()=>{
45
+ try{
46
+ if(process.stdout.columns!==realCols||process.stdout.rows!==realRows)return resized();
47
+ const t=Math.min(performance.now()-start,duration-1),lines=render(t);
48
+ if(lines.length>height)throw new Error('Message exceeds the animation block; use a shorter message or a static preview.');
49
+ process.stdout.write(Array.from({length:height},(_,i)=>'\r\x1b[2K'+(lines[i]??'')+'\n').join('')+`\x1b[${height}A`);
50
+ if(performance.now()-start>=duration)cleanup();
51
+ }catch(e){cleanup(e);}
52
+ };
53
+ tick();if(!ended)timer=setInterval(tick,1000/6);
54
+ await done;
55
+ }catch(e){cleanup(e);await done;}
56
+ }