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
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Dragon jobs: each companion dragon has ONE truthful job, and its caption comes from a real read.
3
+ * sentinel WARD state of the last answer's envelope (POST /ward/verify-receipt, the route /proof uses)
4
+ * cipher route health: kuma monitors up/down (the `quox kuma` read)
5
+ * seer the plan: QuoxPlan focus stream and x of y phases
6
+ * hyperion fleet: hosts up of total (the Bastion host read)
7
+ * drago open security findings (the engagement store)
8
+ * lucid recovery: the last failure this session saw, and whether a later turn completed (no network read)
9
+ * Rules: every read is bounded (timeoutMs); a failed or silent read says "Couldn't check <thing> (<why>)" and never a number;
10
+ * results (failures too) are cached for JOB_TTL_MS so a dragon that appears often does not hammer the server; nothing here
11
+ * polls, run() is only called when the dragon is about to be shown; read-only. Pure apart from the injected reads.
12
+ * A result is { caption, mood } where mood is 'ok' | 'concerned' | null (null = unknown or neutral). The host may pick a
13
+ * concerned scene for 'concerned'; it never celebrates on null.
14
+ */
15
+ export const JOB_FOR = Object.freeze({ sentinel: 'ward', cipher: 'routes', seer: 'plan', hyperion: 'fleet', drago: 'findings', lucid: 'recovery' });
16
+ export const JOB_TTL_MS = 30000;
17
+ export const JOB_TIMEOUT_MS = 6000;
18
+ const LABEL = { ward: 'WARD', routes: 'routes', plan: 'plan', fleet: 'fleet', findings: 'findings' };
19
+ const READ_FOR = { ward: 'verifyReceipt', routes: 'routes', plan: 'plan', fleet: 'fleet', findings: 'findings' };
20
+
21
+ const why = (e) => (e?.status ? `HTTP ${e.status}` : /timed out/i.test(e?.message || '') ? 'timed out' : String(e?.message || 'failed').slice(0, 60));
22
+ const ago = (ms) => { const m = Math.floor(ms / 60000); return m < 1 ? 'under a minute ago' : m < 60 ? `${m} min ago` : m < 1440 ? `${Math.floor(m / 60)} h ago` : `${Math.floor(m / 1440)} d ago`; };
23
+ const failed = (job, reason) => ({ caption: `Couldn't check ${LABEL[job]} (${reason}).`, mood: null });
24
+
25
+ /** Pure: a read's data -> { caption, mood }. */
26
+ export function captionFor(job, d) {
27
+ if (job === 'ward') {
28
+ const r = d?.result || {}, seq = r.entry?.seq;
29
+ if (r.status === 'WITNESSED') return { caption: `Last answer witnessed by WARD (seq ${seq}, signed tip).`, mood: 'ok' };
30
+ if (r.status === 'UNTIPPED') return { caption: `Last answer recorded (seq ${seq}) but no signed tip yet.`, mood: null };
31
+ if (r.status === 'NOT_WITNESSED') return { caption: 'Last answer not witnessed yet.', mood: null };
32
+ if (r.status) return { caption: `WARD check failed: ${r.status}${r.reason ? ` ${r.reason}` : ''}.`, mood: 'concerned' };
33
+ return failed('ward', 'no status in the answer');
34
+ }
35
+ if (job === 'routes') {
36
+ if (!d.total) return { caption: 'No monitors to read.', mood: null };
37
+ const stale = d.ageSeconds > 600 ? ` (snapshot ${Math.round(d.ageSeconds / 60)} min old)` : '';
38
+ if (d.down > 0) return { caption: `Routes: ${d.up} of ${d.total} up. Down: ${d.downNames.slice(0, 3).join(', ')}${d.downNames.length > 3 ? ` and ${d.downNames.length - 3} more` : ''}.`, mood: 'concerned' };
39
+ if (d.up < d.total) return { caption: `Routes: ${d.up} of ${d.total} up${stale}.`, mood: null }; // pending, maintenance, unknown: not counted up
40
+ const ping = Number.isFinite(d.avgPing) && !stale ? `, average ${Math.round(d.avgPing)} ms` : '';
41
+ return { caption: `Routes: ${d.up} of ${d.total} up${stale || ping}.`, mood: stale ? null : 'ok' };
42
+ }
43
+ if (job === 'plan') {
44
+ if (!d) return { caption: 'Nothing in focus on the plan.', mood: null };
45
+ if (d.total === 0) return { caption: `Plan: ${d.id} (no phases listed).`, mood: null };
46
+ return { caption: Number.isFinite(d.total) ? `Plan: ${d.id}, ${d.done} of ${d.total} phases.` : `Plan: ${d.id} (phases not read).`, mood: null };
47
+ }
48
+ if (job === 'fleet') {
49
+ if (!d.total) return { caption: 'No hosts to read.', mood: null };
50
+ const bits = ['degraded', 'unhealthy', 'offline', 'unknown'].filter(k => d[k]).map(k => `${d[k]} ${k}`);
51
+ return { caption: `Fleet: ${d.healthy} of ${d.total} hosts up${bits.length ? ` (${bits.join(', ')})` : ''}.`, mood: d.healthy === d.total ? 'ok' : 'concerned' };
52
+ }
53
+ if (job === 'findings') {
54
+ if (!d.open) return { caption: 'No open security findings recorded.', mood: 'ok' };
55
+ return { caption: `Open findings: ${d.open}${d.severe ? ` (${d.severe} critical or high)` : ''}.`, mood: d.severe ? 'concerned' : null };
56
+ }
57
+ return null;
58
+ }
59
+
60
+ /**
61
+ * reads: { verifyReceipt(envelopeId), routes(), plan(), fleet(), findings() }, each resolving to data or throwing (verifyReceipt may
62
+ * resolve { ok:false, error }). context(): { envelopeId } of the last answer. observe(event): the session's own events (lucid).
63
+ */
64
+ export function createDragonJobs({ reads = {}, context = () => ({}), now = Date.now, ttlMs = JOB_TTL_MS, timeoutMs = JOB_TIMEOUT_MS } = {}) {
65
+ const cache = new Map(), inflight = new Map();
66
+ let lastFailure = null, recoveredAt = null;
67
+ const keyOf = (job) => (job === 'ward' ? `ward:${context()?.envelopeId || ''}` : job);
68
+
69
+ function recovery() {
70
+ if (!lastFailure) return { caption: 'No failures seen in this session.', mood: null };
71
+ const when = ago(now() - lastFailure.at);
72
+ return recoveredAt != null
73
+ ? { caption: `Recovered: ${lastFailure.what} ${when}, a later turn completed.`, mood: 'ok' }
74
+ : { caption: `Last failure: ${lastFailure.what} ${when}, not recovered yet.`, mood: 'concerned' };
75
+ }
76
+ async function fetchOne(job) {
77
+ const read = reads[READ_FOR[job]];
78
+ if (typeof read !== 'function') return null;
79
+ let timer;
80
+ try {
81
+ const arg = job === 'ward' ? context()?.envelopeId : undefined;
82
+ const data = await Promise.race([
83
+ Promise.resolve().then(() => read(arg)),
84
+ new Promise((_, rej) => { timer = setTimeout(() => rej(new Error('timed out')), timeoutMs); timer.unref?.(); }),
85
+ ]);
86
+ if (job === 'ward' && data && data.ok === false) return failed(job, data.error || 'failed');
87
+ return captionFor(job, data);
88
+ } catch (e) { return failed(job, why(e)); }
89
+ finally { clearTimeout(timer); }
90
+ }
91
+
92
+ return {
93
+ /** Feed the session's events (turn.finished, error): the only input lucid's job has. */
94
+ observe(ev) {
95
+ if (!ev) return;
96
+ if (ev.type === 'error') { lastFailure = { at: now(), what: 'stream error' }; recoveredAt = null; }
97
+ else if (ev.type === 'turn.finished') {
98
+ if (ev.status === 'failed' || ev.status === 'unconfirmed') { lastFailure = { at: now(), what: `turn ${ev.status}` }; recoveredAt = null; }
99
+ else if (ev.status === 'completed' && lastFailure && recoveredAt == null) recoveredAt = now();
100
+ }
101
+ },
102
+ peek(dragonId) {
103
+ const job = JOB_FOR[dragonId];
104
+ if (!job) return null;
105
+ if (job === 'recovery') return recovery();
106
+ const hit = cache.get(keyOf(job));
107
+ return hit && now() - hit.at < ttlMs ? hit.value : null;
108
+ },
109
+ /** The dragon's job result, from cache (30 s) or one bounded read. null = this dragon has no job here. */
110
+ async run(dragonId) {
111
+ const job = JOB_FOR[dragonId];
112
+ if (!job) return null;
113
+ if (job === 'recovery') return recovery();
114
+ if (job === 'ward' && !context()?.envelopeId) return { caption: 'No receipt to check yet: nothing from this session is witnessed.', mood: null };
115
+ const key = keyOf(job), hit = cache.get(key);
116
+ if (hit && now() - hit.at < ttlMs) return hit.value;
117
+ if (inflight.has(key)) return inflight.get(key);
118
+ const p = fetchOne(job).then((value) => { if (value) cache.set(key, { at: now(), value }); return value; }).finally(() => inflight.delete(key));
119
+ inflight.set(key, p);
120
+ return p;
121
+ },
122
+ };
123
+ }
@@ -0,0 +1,15 @@
1
+ /** Worker thread: loads one dragonlab character and renders frames on request, so the TUI thread never does. See frames.js. */
2
+ import { parentPort, workerData } from 'node:worker_threads';
3
+ import { pathToFileURL } from 'node:url';
4
+
5
+ const { root, id } = workerData;
6
+ const mod = await import(pathToFileURL(`${root}/integration/companions-v1/characters.mjs`).href);
7
+ const actor = await mod.loadCharacter(id);
8
+ parentPort.on('message', ({ seq, req }) => {
9
+ try {
10
+ const r = actor.render(req.scene, req.time, { cols: req.cols, rows: req.rows, still: req.still, mono: req.mono, motionTime: req.motionTime });
11
+ if (!r || !r.ok) parentPort.postMessage({ type: 'error', seq, code: r?.code || 'render failed' });
12
+ else parentPort.postMessage({ type: 'frame', seq, frame: r.frame });
13
+ } catch (e) { parentPort.postMessage({ type: 'error', seq, code: e.message }); }
14
+ });
15
+ parentPort.postMessage({ type: 'ready' });
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Dragon companions (Lucid, Hyperion, Seer, Sentinel, Drago, Cipher): which one, whether to show it, and where its art lives.
3
+ * Pure. The art, animation and activity reducer stay in quoxai/dragonlab (integration/companions-v1);
4
+ * Quox only chooses and loads. See docs/dragons/COMPANION-UX.md for the decisions behind each rule.
5
+ */
6
+ import { resolve, join } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ /** The dragonlab art bundled with the CLI (cli/vendor/dragonlab, refreshed by scripts/sync-dragonlab.sh). */
10
+ export const VENDORED_DRAGONLAB = fileURLToPath(new URL('../../vendor/dragonlab', import.meta.url));
11
+
12
+ /** Registry agent ids (and display names) to approved dragon. Others, including quox/commanderq, get none. */
13
+ export const AGENT_DRAGON = Object.freeze({ lucid: 'lucid', hyperion: 'hyperion', atlas: 'hyperion', seer: 'seer', sentinel: 'sentinel', warden: 'sentinel', security_lead: 'drago', drago: 'drago', cipher: 'cipher' });
14
+ export const COMPANION_MODES = ['on', 'greeting', 'off'];
15
+ /** One-line change: the owner decides this after his Mac check (docs/dragons/COMPANION-UX.md section 4). */
16
+ export const DEFAULT_COMPANION_MODE = 'on';
17
+
18
+ export function dragonForAgent(agent) {
19
+ const k = String(agent ?? '').trim().toLowerCase();
20
+ return Object.hasOwn(AGENT_DRAGON, k) ? AGENT_DRAGON[k] : null;
21
+ }
22
+
23
+ export function normaliseMode(v) {
24
+ const s = String(v ?? '').trim().toLowerCase();
25
+ return COMPANION_MODES.includes(s) ? s : null;
26
+ }
27
+
28
+ /** env QUOX_DRAGON_COMPANION beats the saved profile setting beats the default. */
29
+ export function companionMode(settings = {}, env = process.env) {
30
+ return normaliseMode(env.QUOX_DRAGON_COMPANION) || normaliseMode(settings?.dragonCompanion) || DEFAULT_COMPANION_MODE;
31
+ }
32
+
33
+ /**
34
+ * Motion policy from the existing Quox switches. lite or a dumb terminal = off; reduced motion = still
35
+ * (no entrance, every channel frozen, captions still update); NO_COLOR = mono render.
36
+ */
37
+ export function motionPolicy(env = process.env) {
38
+ const lite = Boolean(env.QUOX_TUI_LITE) || env.TERM === 'dumb';
39
+ const quiet = env.QUOX_REDUCED_MOTION === '1' || String(env.QUOX_MOTION || '').trim().toLowerCase() === 'quiet';
40
+ return { off: lite, still: quiet, mono: 'NO_COLOR' in env };
41
+ }
42
+
43
+ /**
44
+ * Where dragonlab lives: QUOX_DRAGONLAB beats the saved quoxon.dragonLab beats `bundled` (hosts pass
45
+ * VENDORED_DRAGONLAB). With no bundled path and no override, null = art not available.
46
+ */
47
+ export function dragonLabRoot(settings = {}, env = process.env, bundled = null) {
48
+ const p = env.QUOX_DRAGONLAB || settings?.dragonLab;
49
+ if (typeof p === 'string' && p.trim()) return resolve(p.trim());
50
+ return bundled ? resolve(bundled) : null;
51
+ }
52
+ export const companionModuleFile = (root, id) => join(root, 'integration', 'companions-v1', `${id}.mjs`);
53
+ export const viewModuleFile = (root) => join(root, 'integration', 'companions-v1', 'view.mjs');
54
+
55
+ /**
56
+ * Everything a host needs, or { enabled:false, why } (the reason is what `/dragon companion status` prints).
57
+ * `exists` is injected so this stays pure.
58
+ */
59
+ export function companionPlan({ agent, settings = {}, env = process.env, isTTY = true, exists = () => true, bundled = null }) {
60
+ const mode = companionMode(settings, env), policy = motionPolicy(env);
61
+ const id = dragonForAgent(agent);
62
+ const no = (why) => ({ enabled: false, why, mode, id });
63
+ if (mode === 'off') return no('companion is off');
64
+ if (!isTTY) return no('output is not a terminal');
65
+ if (policy.off) return no('QUOX_TUI_LITE or a dumb terminal turns decoration off');
66
+ if (!id) return no(agent ? `${agent} has no approved dragon` : 'no agent attached');
67
+ const root = dragonLabRoot(settings, env, bundled);
68
+ if (!root) return no('dragon art not found (the bundled copy is missing; set QUOX_DRAGONLAB or quoxon.dragonLab to a dragonlab checkout)');
69
+ const file = companionModuleFile(root, id);
70
+ if (!exists(file) || !exists(viewModuleFile(root))) return no(`dragon art not found at ${root}`);
71
+ return { enabled: true, mode, id, root, file, viewFile: viewModuleFile(root), still: policy.still, mono: policy.mono, persist: mode === 'on' };
72
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Fetch one conversation's signed evidence zip, write it, verify what was written. One implementation for
3
+ * `quox evidence bundle` and the TUI's /evidence. The server scopes by the caller's org and refuses anything else.
4
+ */
5
+ import fs from 'node:fs';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import { resolveEndpoint } from '../api/endpoints.js';
9
+ import { verifyEvidenceBundle } from './bundleVerify.js';
10
+
11
+ export function defaultBundlePath(corr, now = new Date()) {
12
+ const ts = now.toISOString().replace(/[:.]/g, '-').slice(0, 19);
13
+ const safe = String(corr).replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 60);
14
+ return path.join(os.homedir(), 'quox-evidence', `${safe}-${ts}.zip`);
15
+ }
16
+
17
+ /** -> the id of the caller's newest chat conversation, or null. */
18
+ export async function newestConversationId(client) {
19
+ const ep = resolveEndpoint('conversations.chat.list');
20
+ const data = await client.get(ep.path, { service: ep.service, query: { limit: 1 } });
21
+ const c = (data && data.conversations && data.conversations[0]) || null;
22
+ return c ? c.id : null;
23
+ }
24
+
25
+ /**
26
+ * @returns {Promise<{ path: string, corr: string, bytes: number, envelopes: number, receipts: number, verify: object }>}
27
+ * @throws Error with the HTTP status named when the server refuses (404 = not your conversation or no evidence).
28
+ */
29
+ export async function exportConversationBundle({ client, corr, out, now = new Date() }) {
30
+ const ep = resolveEndpoint('evidence.bundle', { corr });
31
+ const res = await client.get(ep.path, { service: ep.service, query: { format: 'zip' }, raw: true, timeout: 120000 });
32
+ if (!res.ok) {
33
+ let detail = res.statusText;
34
+ try { const j = await res.json(); detail = j.error || JSON.stringify(j); } catch { /* keep statusText */ }
35
+ const e = new Error(res.status === 404
36
+ ? `No evidence for conversation ${corr} in your org (HTTP 404: ${detail}). Tool calls made before this fix are filed under per-command ids and are not part of the conversation.`
37
+ : `Evidence bundle failed: HTTP ${res.status}: ${detail}`);
38
+ e.status = res.status;
39
+ throw e;
40
+ }
41
+ const buf = Buffer.from(await res.arrayBuffer());
42
+ const file = path.resolve(out || defaultBundlePath(corr, now));
43
+ fs.mkdirSync(path.dirname(file), { recursive: true });
44
+ fs.writeFileSync(file, buf);
45
+ const verify = await verifyEvidenceBundle(fs.readFileSync(file)); // what was written, not what was sent
46
+ return { path: file, corr, bytes: buf.length, envelopes: verify.manifest?.counts?.envelopes ?? 0, receipts: verify.manifest?.counts?.ward_receipts ?? 0, receiptRows: verify.receipts || [], verify };
47
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Offline verifier for a conversation evidence bundle (collector lib/conversationEvidenceBundle.js).
3
+ * Needs nothing but the zip: file hashes against manifest.json, the envelope list hash, the Ed25519 signature
4
+ * over the canonical manifest, and the counts. Never reaches the network. The collector builds; this only checks.
5
+ */
6
+ import crypto from 'node:crypto';
7
+ import JSZip from 'jszip';
8
+
9
+ const sha256 = (buf) => crypto.createHash('sha256').update(buf).digest('hex');
10
+
11
+ // Same canonical form as services/collector/lib/bundleSigner.js (sorted keys at every level, no whitespace).
12
+ export function canonicalize(val) {
13
+ if (val === null || val === undefined) return JSON.stringify(val);
14
+ if (typeof val !== 'object') return JSON.stringify(val);
15
+ if (Array.isArray(val)) return `[${val.map((v) => canonicalize(v)).join(',')}]`;
16
+ const keys = Object.keys(val).filter((k) => val[k] !== undefined).sort();
17
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalize(val[k])}`).join(',')}}`;
18
+ }
19
+
20
+ const SPKI_ED25519_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
21
+
22
+ export function envelopesHash(envelopes) {
23
+ return sha256(Buffer.from(JSON.stringify(
24
+ envelopes.map((e) => ({ id: e.id ?? null, ts: e.ts ?? null, intent: e.intent ?? null, corr: e.corr ?? null, reply_to: e.reply_to ?? null, from: e.from ?? null, to: e.to ?? null })),
25
+ ), 'utf8'));
26
+ }
27
+
28
+ /**
29
+ * @param {Buffer|Uint8Array} zipBytes
30
+ * @returns {Promise<{ ok: boolean, checks: {name:string, ok:boolean, detail?:string}[], manifest: object|null, signature: object|null }>}
31
+ */
32
+ export async function verifyEvidenceBundle(zipBytes) {
33
+ const checks = [];
34
+ const add = (name, ok, detail) => { checks.push({ name, ok: !!ok, ...(detail ? { detail } : {}) }); return !!ok; };
35
+ let zip;
36
+ try { zip = await JSZip.loadAsync(zipBytes); } catch (e) {
37
+ add('zip readable', false, e.message);
38
+ return { ok: false, checks, manifest: null, signature: null };
39
+ }
40
+ const read = async (name) => { const f = zip.file(name); return f ? Buffer.from(await f.async('nodebuffer')) : null; };
41
+ const manifestBuf = await read('manifest.json');
42
+ const sigBuf = await read('signature.json');
43
+ if (!add('manifest.json present', manifestBuf)) return { ok: false, checks, manifest: null, signature: null };
44
+ if (!add('signature.json present', sigBuf)) return { ok: false, checks, manifest: null, signature: null };
45
+ let manifest; let signature;
46
+ try { manifest = JSON.parse(manifestBuf.toString('utf8')); signature = JSON.parse(sigBuf.toString('utf8')); } catch (e) {
47
+ add('manifest and signature parse', false, e.message);
48
+ return { ok: false, checks, manifest: null, signature: null };
49
+ }
50
+
51
+ // 1. signature over the canonical manifest
52
+ try {
53
+ const signed = Buffer.from(canonicalize(manifest), 'utf8');
54
+ const pub = crypto.createPublicKey({ key: Buffer.concat([SPKI_ED25519_PREFIX, Buffer.from(signature.public_key, 'base64')]), format: 'der', type: 'spki' });
55
+ const sigOk = signature.alg === 'ed25519' && crypto.verify(null, signed, pub, Buffer.from(signature.signature, 'base64'));
56
+ add('signature (Ed25519 over the manifest)', sigOk, sigOk ? `key ${signature.key_id}` : 'signature does not match the manifest');
57
+ add('signed manifest hash', signature.manifest_sha256 === sha256(signed), signature.manifest_sha256 === sha256(signed) ? undefined : 'manifest_sha256 differs');
58
+ } catch (e) { add('signature (Ed25519 over the manifest)', false, e.message); }
59
+
60
+ // 2. every listed file hashes to what the (signed) manifest says
61
+ const bufs = {};
62
+ for (const [name, want] of Object.entries(manifest.files || {})) {
63
+ const b = await read(name);
64
+ bufs[name] = b;
65
+ add(`file ${name}`, b && sha256(b) === want, !b ? 'missing from the zip' : (sha256(b) === want ? undefined : 'hash differs from the manifest'));
66
+ }
67
+
68
+ // 3. envelope list hash + counts
69
+ let envelopes = [];
70
+ try { envelopes = JSON.parse((bufs['envelopes.json'] || Buffer.from('[]')).toString('utf8')); } catch { envelopes = []; }
71
+ add('envelope list hash', envelopesHash(envelopes) === manifest.envelopes_sha256, envelopesHash(envelopes) === manifest.envelopes_sha256 ? undefined : 'recomputed hash differs');
72
+ add('envelope count', envelopes.length === manifest.counts?.envelopes, `${envelopes.length} in envelopes.json, ${manifest.counts?.envelopes} in the manifest`);
73
+ let receipts = [];
74
+ try { receipts = JSON.parse((bufs['ward-receipts.json'] || Buffer.from('[]')).toString('utf8')); } catch { receipts = []; }
75
+ add('receipt count', receipts.length === manifest.counts?.ward_receipts, `${receipts.length} in ward-receipts.json, ${manifest.counts?.ward_receipts} in the manifest`);
76
+ return { ok: checks.every((c) => c.ok), checks, manifest, signature, receipts };
77
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Launcher for the HYPERION deck TUI (`quox hyperion deck`). Same shape as lucid/deck.js: alt screen via
3
+ * fullscreen-ink when the terminal supports raw mode; the terminal is restored on every exit path.
4
+ */
5
+ export async function launchHyperionDeck(api, { intervalMs = 10000 } = {}) {
6
+ if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== 'function' || !process.stdout.isTTY) {
7
+ console.error('quox hyperion deck needs an interactive terminal. Use `quox hyperion` for a one-shot view.');
8
+ process.exitCode = 1;
9
+ return;
10
+ }
11
+ // A long-running TUI must use React's production build (the development build grows the heap per render).
12
+ if (!process.env.NODE_ENV) process.env.NODE_ENV = 'production';
13
+ await import('../tui/index.js'); // registers the .jsx loader hook
14
+ const React = (await import('react')).default;
15
+ const { HyperionDeck } = await import('../tui/views/HyperionDeck.jsx');
16
+ const { withFullScreen } = await import('fullscreen-ink');
17
+ const app = withFullScreen(React.createElement(HyperionDeck, { api, intervalMs }), { exitOnCtrlC: true });
18
+ const restore = () => { try { process.stdout.write('\x1b[?1049l\x1b[?25h'); } catch { /* best effort */ } };
19
+ process.once('SIGTERM', () => { restore(); process.exit(143); });
20
+ process.once('SIGHUP', () => { restore(); process.exit(129); });
21
+ process.once('exit', restore);
22
+ process.once('uncaughtException', (e) => { restore(); console.error(e); process.exit(1); });
23
+ try {
24
+ await app.start();
25
+ await app.waitUntilExit();
26
+ } finally {
27
+ restore();
28
+ }
29
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * <LucidCard/>: draws a Quoxon object-card view ({ box, rows }, see cards.js) for the deck.
3
+ *
4
+ * Same geometry and roles as quoxon/objects/view.js, plus what a navigable list needs:
5
+ * box.sel 'focus' | 'dim' the title sits on a bar (and inverse under NO_COLOR) so the selected card is unmistakable
6
+ * role 'sel' the highlighted action bar
7
+ * t (seconds) drop-in reveal: rows decode through half-width katakana, then a short colour
8
+ * afterglow on the real characters. Infinity = settled (what idle always shows).
9
+ * Colour comes from the theme roles (light/dark, 256-colour, NO_COLOR); the characters never change.
10
+ */
11
+ import React from 'react';
12
+ import { Text } from 'ink';
13
+ import { c, mono } from '../quoxon/theme.js';
14
+ import { agentColor } from '../quoxon/theme.js';
15
+ import { decodeRow, padRow, fit, len } from '../quoxon/objects/model.js';
16
+ import { rowStart } from '../quoxon/objects/view.js';
17
+ import { hexRgb, mix, rgbStr } from '../quoxon/motion/color.js';
18
+
19
+ const h = React.createElement;
20
+ export const BAR = { focus: '#1E3A8A', dim: '#27272A' }; // selection bars (white text on both)
21
+
22
+ function roleColor(role) {
23
+ if (mono()) return undefined;
24
+ if (typeof role === 'string' && role.startsWith('agent:')) return agentColor(role.slice(6));
25
+ return c(role || 'value');
26
+ }
27
+ /** A role colour lifted towards white by the afterglow (hex colours only; anything else stays as is). */
28
+ function glowColor(role, g) {
29
+ const base = roleColor(role);
30
+ return typeof base === 'string' && base[0] === '#' ? rgbStr(mix(hexRgb(base), [255, 255, 255], 0.65 * g)) : base;
31
+ }
32
+
33
+ function runs(cells, key) {
34
+ const out = [];
35
+ let run = '', col, bold = false, bg, k = 0;
36
+ const flush = () => { if (run) out.push(h(Text, { key: `${key}-${k++}`, color: col, bold, backgroundColor: bg }, run)); run = ''; };
37
+ for (const cl of cells) {
38
+ let color, back;
39
+ if (cl.role === 'sel') { color = mono() ? undefined : '#FFFFFF'; back = mono() ? undefined : BAR.focus; }
40
+ else if (cl.glyph) color = roleColor('teal'); // the katakana flicker
41
+ else color = cl.glow > 0.05 ? glowColor(cl.role, cl.glow) : roleColor(cl.role);
42
+ const b = Boolean(cl.bold);
43
+ if (color !== col || b !== bold || back !== bg) { flush(); col = color; bold = b; bg = back; }
44
+ run += cl.ch;
45
+ }
46
+ flush();
47
+ return out;
48
+ }
49
+
50
+ export function LucidCard({ view, t = Infinity }) {
51
+ const b = view.box, w = b.width, inner = w - 4;
52
+ const rows = view.rows || [];
53
+ const bc = roleColor(b.border || 'faint');
54
+ const right = b.right ? ` ${b.right} ` : '';
55
+ const title = b.title ? ` ${fit(b.title, Math.max(4, w - 6 - (right ? len(right) + 1 : 0)))} ` : '';
56
+ const fill = Math.max(0, w - 3 - len(title) - len(right) - 1);
57
+ const bar = b.sel ? BAR[b.sel] : null;
58
+ const top = h(Text, { key: 'top', wrap: 'truncate' },
59
+ h(Text, { color: bc }, '╭─'),
60
+ h(Text, { color: bar && !mono() ? '#FFFFFF' : roleColor(b.titleRole || 'value'), backgroundColor: bar && !mono() ? bar : undefined, inverse: Boolean(bar) && mono(), bold: true }, title),
61
+ h(Text, { color: bc }, '─'.repeat(fill)),
62
+ h(Text, { color: roleColor(b.rightRole || 'muted') }, right),
63
+ h(Text, { color: bc }, '─╮'));
64
+ const body = rows.map((r, i) => h(Text, { key: `b${i}`, wrap: 'truncate' },
65
+ h(Text, { color: bc }, '│ '), ...runs(decodeRow(padRow(r, inner), t, rowStart(i, rows.length), 0.25, 70 + i), `b${i}`), h(Text, { color: bc }, ' │')));
66
+ const bottom = h(Text, { key: 'bot', color: bc }, '╰' + '─'.repeat(w - 2) + '╯');
67
+ return h(React.Fragment, null, top, ...body, bottom);
68
+ }
@@ -1,8 +1,8 @@
1
1
  /**
2
- * LucidIntro: the ~1.2s startup for `quox lucid deck`.
2
+ * LucidIntro: the ~1.6s startup for `quox lucid deck`.
3
3
  *
4
- * Frames (50ms): the QUOX block wordmark is revealed by a gradient sweep, the
5
- * ◉ LUCID mark fades in under it, then "tuning in…" resolves into
4
+ * Frames (50ms): the Quoxon particle collapse (particles swarm in and lock into the horned Q, tail last),
5
+ * the ◉ LUCID mark fades in under it, then "tuning in…" resolves into
6
6
  * "◉ LUCID │ N sources live · M need you" once the first fetch has landed.
7
7
  * The fetch is owned by the deck and runs in parallel, never gated on this.
8
8
  * onDone fires once: at the natural end, on a hard 2.5s cap, or on a key
@@ -10,38 +10,25 @@
10
10
  */
11
11
  import React, { useState, useEffect, useRef } from 'react';
12
12
  import { Box, Text } from 'ink';
13
- import chalk from 'chalk';
14
- import { BLOCK_FONT, gradientColor } from '../tui/intro/Intro.jsx';
15
- import { PALETTE, STATE_HEX } from './fmt.js';
13
+ import { c, gradient, mono } from '../quoxon/theme.js';
14
+ import { QGlyph } from '../quoxon/qmotion.js';
15
+ import { Clock } from '../quoxon/motion/clock.js';
16
+ import { particleCellsAt, qCells, cellsToAnsi, labSize } from '../quoxon/qart.js';
16
17
 
17
18
  export const FRAME_MS = 50;
18
- const SWEEP_FRAMES = 12;
19
- const MIN_FRAMES = 24; // 1.2s
20
- const MAX_FRAMES = 50; // 2.5s cap if the fetch never lands
19
+ const PARTICLE_MS = 1100; // the collapse; the Q is fully settled ~470ms later
20
+ const MIN_FRAMES = 32; // 1.6s: the Q settles, then the live line shows
21
+ const MAX_FRAMES = 50; // 2.5s cap if the fetch never lands
21
22
 
22
- const LETTERS = ['Q', 'U', 'O', 'X'];
23
- const rgb = (c, s) => chalk.rgb(c[0], c[1], c[2])(s);
24
-
25
- function titleRow(row, frame, colsRevealed) {
26
- let out = ''; let col = 0;
27
- const total = LETTERS.reduce((n, ch) => n + BLOCK_FONT[ch][0].length + 1, 0);
28
- for (const ch of LETTERS) {
29
- const g = BLOCK_FONT[ch][row];
30
- for (let i = 0; i < g.length; i++) {
31
- const x = col + i;
32
- const lit = g[i] !== ' ';
33
- if (x > colsRevealed) { out += ' '; continue; }
34
- if (!lit) { out += ' '; continue; }
35
- const edge = colsRevealed - x < 2 && colsRevealed < total;
36
- out += edge ? chalk.white('██') : rgb(gradientColor(x / total + frame * 0.02), '██');
37
- }
38
- out += ' ';
39
- col += g.length + 1;
40
- }
41
- return out;
23
+ /** Rows of the particle Q at frame `frame` (motion off: the settled Q). Under 12 rows the compact Q. */
24
+ export function introQ(cols, rows, frame, motion = true) {
25
+ const size = labSize(cols, Math.max(0, rows - 8));
26
+ const color = !mono();
27
+ const t = frame * FRAME_MS;
28
+ return cellsToAnsi(motion ? particleCellsAt(size, t, { dur: PARTICLE_MS, color }) : qCells(size, { color }));
42
29
  }
43
30
 
44
- export function LucidIntro({ data, err, onDone, rows = 24 }) {
31
+ export function LucidIntro({ data, err, onDone, rows = 24, cols = 100, motion = true }) {
45
32
  const [frame, setFrame] = useState(0);
46
33
  const doneRef = useRef(false);
47
34
  const readyAt = useRef(null);
@@ -49,10 +36,11 @@ export function LucidIntro({ data, err, onDone, rows = 24 }) {
49
36
 
50
37
  if (sit && readyAt.current == null) readyAt.current = frame;
51
38
 
52
- useEffect(() => {
53
- const id = setInterval(() => setFrame((f) => f + 1), FRAME_MS);
54
- return () => clearInterval(id);
55
- }, []);
39
+ // The deck's shared clock drives the intro too: one timer, armed only while the intro still has frames to play.
40
+ const clockRef = useRef(null);
41
+ if (!clockRef.current) clockRef.current = new Clock(() => setFrame((f) => f + 1));
42
+ useEffect(() => { if (!doneRef.current) clockRef.current.schedule([{ every: FRAME_MS }]); });
43
+ useEffect(() => () => clockRef.current.stop(), []);
56
44
 
57
45
  const finishAt = readyAt.current != null
58
46
  ? Math.max(MIN_FRAMES, readyAt.current + 6)
@@ -62,44 +50,43 @@ export function LucidIntro({ data, err, onDone, rows = 24 }) {
62
50
  if (frame >= finishAt && !doneRef.current) { doneRef.current = true; onDone(); }
63
51
  }, [frame, finishAt, onDone]);
64
52
 
65
- const tall = rows >= 16;
66
- const total = LETTERS.reduce((n, ch) => n + BLOCK_FONT[ch][0].length + 1, 0);
67
- const revealed = Math.round((Math.min(frame, SWEEP_FRAMES) / SWEEP_FRAMES) * (total + 2)) - 1;
68
- const markOn = frame >= 6;
69
- const resolved = sit && frame >= 14 && frame >= (readyAt.current ?? 0) + 2;
53
+ const qRows = introQ(cols, rows, frame, motion);
54
+ const markOn = frame >= 14;
55
+ const resolved = sit && frame >= 22 && frame >= (readyAt.current ?? 0) + 2;
70
56
  const cov = (sit && sit.cannot_see && sit.cannot_see.coverage) || { live: 0, expected: 0 };
71
57
  const dots = '.'.repeat((frame % 3) + 1).padEnd(3, ' ');
72
58
 
73
- const mark = [...'◉ LUCID'].map((ch, i) => (
74
- <Text key={i} bold color={'#' + gradientColor((i / 7) + frame * 0.03).map((v) => v.toString(16).padStart(2, '0')).join('')}>{ch}</Text>
75
- ));
59
+ const mark = (
60
+ <Text>
61
+ <QGlyph state="working" ms={Date.now()} quiet={!motion} />
62
+ <Text> </Text>
63
+ {gradient('LUCID').map((g, i) => <Text key={i} bold color={g.color}>{g.ch}</Text>)}
64
+ </Text>
65
+ );
76
66
 
77
67
  return (
78
68
  <Box flexDirection="column" alignItems="center" justifyContent="center" flexGrow={1} paddingY={1}>
79
- {tall ? (
80
- <Box flexDirection="column" alignItems="center">
81
- {Array.from({ length: 5 }, (_, r) => <Text key={r}>{titleRow(r, frame, revealed)}</Text>)}
82
- </Box>
83
- ) : (
84
- <Text bold color={PALETTE.brand}>Q U O X</Text>
85
- )}
69
+ <Box flexDirection="column" alignItems="center">
70
+ {qRows.map((l, r) => <Text key={r} wrap="truncate">{l}</Text>)}
71
+ </Box>
86
72
  <Box marginTop={1}>{markOn ? mark : <Text> </Text>}</Box>
87
73
  <Box marginTop={1}>
88
- {frame < 10 ? <Text> </Text> : resolved ? (
74
+ {frame < 16 ? <Text> </Text> : resolved ? (
89
75
  <Text>
90
- <Text bold color={PALETTE.brand}>◉ LUCID</Text>
91
- <Text color={PALETTE.dim}> │ </Text>
92
- <Text color={STATE_HEX.ok}>{cov.live} sources live</Text>
93
- <Text color={PALETTE.dim}> · </Text>
94
- <Text bold color={(sit.needs_you || []).length ? STATE_HEX.attention : STATE_HEX.ok}>{(sit.needs_you || []).length} need you</Text>
76
+ <QGlyph state={(sit.needs_you || []).length ? 'waiting' : 'resting'} ms={Date.now()} quiet />
77
+ <Text bold color={c('sky')}> LUCID</Text>
78
+ <Text color={c('muted')}> │ </Text>
79
+ <Text color={c('ok')}>{cov.live} sources live</Text>
80
+ <Text color={c('muted')}> · </Text>
81
+ <Text bold color={(sit.needs_you || []).length ? c('warn') : c('ok')}>{(sit.needs_you || []).length} need you</Text>
95
82
  </Text>
96
83
  ) : err && !sit ? (
97
- <Text color={STATE_HEX.blind}>LUCID unreachable, still trying</Text>
84
+ <Text color={c('error')}>LUCID unreachable, still trying</Text>
98
85
  ) : (
99
- <Text color={PALETTE.muted}>tuning in{dots}</Text>
86
+ <Text color={c('muted')}>tuning in{dots}</Text>
100
87
  )}
101
88
  </Box>
102
- <Box marginTop={1}><Text color={PALETTE.dim}>any key to skip</Text></Box>
89
+ <Box marginTop={1}><Text color={c('muted')}>any key to skip</Text></Box>
103
90
  </Box>
104
91
  );
105
92
  }