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
@@ -7,11 +7,14 @@
7
7
  * dumped wholesale, and every string passes a token scrubber as a second line of defence.
8
8
  */
9
9
  import { mkdirSync, openSync, writeSync, closeSync, existsSync, chmodSync } from 'node:fs';
10
+ import { stripMarks } from './palette.js';
11
+ import { textGate } from './events.js';
10
12
  import { join } from 'node:path';
11
13
  import { homedir } from 'node:os';
12
14
 
13
15
  export const MAX_FIELD = 4000;
14
16
  export const SILENT_GAP_MS = 10000;
17
+ export const MAX_ANSWER = 8192;
15
18
 
16
19
  export function transcriptEnabled({ env = process.env, home = homedir(), exists = existsSync } = {}) {
17
20
  if (env.QUOX_DEV_TRANSCRIPT === '0') return false;
@@ -37,7 +40,7 @@ export function scrub(s) {
37
40
  export function clean(v) {
38
41
  if (v == null || typeof v === 'number' || typeof v === 'boolean') return v;
39
42
  let s = typeof v === 'string' ? v : (() => { try { return JSON.stringify(v); } catch { return String(v); } })();
40
- s = scrub(s);
43
+ s = stripMarks(scrub(s)); // ⟦⟧ colour markup never reaches a record
41
44
  return s.length > MAX_FIELD ? `${s.slice(0, MAX_FIELD)}…[truncated ${s.length - MAX_FIELD} chars]` : s;
42
45
  }
43
46
 
@@ -72,21 +75,27 @@ export function pickSse(ev) {
72
75
  export function recordFor(a) {
73
76
  switch (a?.type) {
74
77
  case 'event': return { t: 'sse', ...pickSse(a.ev) };
75
- case 'user': return { t: 'user', mode: a.queued ? 'queue-drain' : 'send', text: clean(a.text) };
78
+ case 'user': return { t: 'user', mode: a.echo ? 'command' : a.queued ? 'queue-drain' : 'send', text: clean(a.full ?? a.text) };
76
79
  case 'steer.add': return { t: 'user', mode: 'steer', id: clean(a.id), text: clean(a.text) };
77
80
  case 'queue.add': return { t: 'user', mode: 'queue', text: clean(a.text) };
81
+ case 'walk.reply': return { t: 'user', mode: 'walk', id: clean(a.id), text: clean(a.text) }; // WALK-FIXES: what was typed at each walkthrough card
78
82
  case 'steer.failed': case 'steer.converted': return { t: 'notice', kind: a.type, id: clean(a.id), text: clean(a.reason) };
79
83
  case 'note': return { t: 'notice', kind: 'note', tone: clean(a.tone), text: clean(a.text) };
84
+ case 'ask.open': return { t: 'notice', kind: 'note', tone: 'ask', text: clean(a.text) }; // ASK-SETTLE: same record as before
85
+ case 'ask.settle': case 'slotmsg.settle': return { t: 'notice', kind: 'ask.settled', tone: clean(a.tone), text: clean(a.text) };
86
+ case 'restart.countdown': return { t: 'restart.countdown', phase: clean(a.phase), seconds: Number.isFinite(a.seconds) ? a.seconds : null, build: clean(a.build ?? null) }; // started | cancelled | restart-now | timed-out | held: lets a reviewer verify the owner was shown the countdown
80
87
  case 'error': return { t: 'error', text: clean(a.text) };
81
88
  case 'notify': return { t: 'notify', approvals: a.patch?.approvals ?? null, error: clean(a.patch?.error ?? null) };
82
89
  case 'turn.begin': return { t: 'turn.begin', turnId: clean(a.turnId) };
83
90
  case 'approval.sent': return { t: 'approval.sent', action: clean(a.action), callId: clean(a.callId), tool: clean(a.tool), target: clean(a.target) };
84
91
  case 'approval.result': return { t: 'approval.result', callId: clean(a.callId), outcome: clean(a.outcome), text: clean(a.text ?? null) };
92
+ case 'screen.sent': return { t: 'screen.sent', summary: clean(a.text) }; // SCREEN-CONTEXT: size and sections only, never the block itself
85
93
  case 'verbose': return { t: 'verbose', mode: clean(a.mode) };
86
94
  case 'ctx': return { t: 'ctx', conversationId: clean(a.ctx?.conversationId ?? null) };
87
95
  // /connect: provider and last 4 only, by construction (the key is never in an action).
88
96
  case 'connect.started': return { t: 'connect.started', provider: clean(a.provider) };
89
97
  case 'connect.saved': return { t: 'connect.saved', provider: clean(a.provider), last4: clean(a.last4) };
98
+ case 'help': return { t: 'action', type: 'help', rows: (Array.isArray(a.model) ? a.model : []).map(m => clean([m.key, m.text].filter(Boolean).join(' '))).slice(0, 80) };
90
99
  case 'inbox.sync': return null; // every 30 s, ids only: noise
91
100
  default: return { t: 'action', type: clean(a?.type) };
92
101
  }
@@ -108,7 +117,7 @@ export function createTranscript({ env = process.env, home = homedir(), now = Da
108
117
  fd = openSync(path, 'a', 0o600);
109
118
  chmodSync(path, 0o600);
110
119
  } catch { return null; }
111
- const turn = { id: null, t0: 0, firstText: false, lastSse: 0 };
120
+ const turn = { id: null, t0: 0, firstText: false, lastSse: 0, text: '', provider: null, model: null, offered: null, tokens: null, answered: false };
112
121
  const write = (rec) => {
113
122
  if (fd == null || !rec) return;
114
123
  try { writeSync(fd, JSON.stringify({ ts: new Date(now()).toISOString(), ...rec }) + '\n'); }
@@ -120,7 +129,7 @@ export function createTranscript({ env = process.env, home = homedir(), now = Da
120
129
  const action = (a) => {
121
130
  const t = now();
122
131
  const rec = recordFor(a);
123
- if (a?.type === 'turn.begin') Object.assign(turn, { id: a.turnId, t0: t, firstText: false, lastSse: t });
132
+ if (a?.type === 'turn.begin') Object.assign(turn, { id: a.turnId, t0: t, firstText: false, lastSse: t, text: '', gate: null, provider: null, model: null, offered: null, tokens: null, answered: false });
124
133
  if (a?.type === 'event' && turn.id) {
125
134
  if (t - turn.lastSse > SILENT_GAP_MS) write({ t: 'gap', turnId: turn.id, ms: t - turn.lastSse, before: clean(a.ev?.type) });
126
135
  turn.lastSse = t;
@@ -128,11 +137,36 @@ export function createTranscript({ env = process.env, home = homedir(), now = Da
128
137
  write(rec);
129
138
  if (a?.type === 'event' && turn.id) {
130
139
  const ty = a.ev?.type;
140
+ const ev = a.ev;
141
+ if (ty === 'turn.started') {
142
+ turn.provider = ev.provider ?? turn.provider; turn.model = ev.model ?? turn.model;
143
+ const n = [ev.tools_offered, ev.tool_count, ev.tools_available].find(num);
144
+ if (n !== undefined) turn.offered = n;
145
+ }
146
+ // QUOXON-FW19: the server mirrors every delta as a full-text chunk; the same gate the screen uses decides what is answer text.
147
+ if (ty === 'turn.started' || ty === 'chunk' || ty === 'text.delta') {
148
+ const g = textGate(turn.gate, ev);
149
+ turn.gate = g.st;
150
+ if (g.append && typeof ev.content === 'string' && turn.text.length <= MAX_ANSWER) turn.text += ev.content;
151
+ }
152
+ // a final full text is authoritative: it replaces what accumulated, never adds to it
153
+ if (ty === 'done' && typeof ev.full_response === 'string' && ev.full_response) turn.text = ev.full_response;
154
+ if (ty === 'tool.finished') {
155
+ const failed = ev.status === 'failed' || ev.status === 'denied';
156
+ write({ t: 'tool', turnId: turn.id, name: clean(ev.tool ?? null), ok: ev.status === 'ok', ...(failed || ev.error ? { error: clean(ev.error ?? ev.result_summary ?? ev.status) } : {}), ms: num(ev.duration_ms) ? ev.duration_ms : null });
157
+ }
158
+ if (ty === 'error') write({ t: 'turn.error', turnId: turn.id, message: clean(ev.error ?? ev.message ?? 'stream error'), provider: clean(turn.provider), model: clean(turn.model) });
131
159
  if (!turn.firstText && (ty === 'chunk' || ty === 'text.delta') && a.ev?.content) {
132
160
  turn.firstText = true;
133
161
  write({ t: 'timing', turnId: turn.id, first_text_ms: t - turn.t0 });
134
162
  }
135
163
  if (ty === 'turn.finished') {
164
+ if (ev.usage && typeof ev.usage === 'object') turn.tokens = { input: num(ev.usage.input_tokens) ? ev.usage.input_tokens : null, output: num(ev.usage.output_tokens) ? ev.usage.output_tokens : null };
165
+ if (turn.text) {
166
+ const raw = scrub(turn.text);
167
+ const text = raw.length > MAX_ANSWER ? `${raw.slice(0, MAX_ANSWER)}…[truncated ${raw.length - MAX_ANSWER} chars]` : raw;
168
+ write({ t: 'answer', turnId: turn.id, provider: clean(turn.provider), model: clean(turn.model), tokens: turn.tokens, duration_ms: t - turn.t0, tools_offered: turn.offered, text });
169
+ }
136
170
  write({ t: 'timing', turnId: turn.id, finish_ms: t - turn.t0, status: clean(a.ev?.status ?? null), saw_text: turn.firstText });
137
171
  turn.id = null;
138
172
  }
@@ -144,7 +178,11 @@ export function createTranscript({ env = process.env, home = homedir(), now = Da
144
178
  command: (line) => { const [cmd, ...rest] = String(line).trim().split(/\s+/); write({ t: 'command', cmd: clean(cmd), arg: clean(rest.join(' ') || null) }); },
145
179
  // Owner observation (/note): recorded verbatim (scrubbed, 2000 chars), never sent anywhere else.
146
180
  note: (text) => write({ t: 'note', text: clean(String(text).slice(0, 2000)), ts: new Date(now()).toISOString() }),
181
+ // ! commands (QUOXON-FW8): the command line and exit code only, never the output.
182
+ bang: (cmd, code) => write({ t: 'bang', cmd: clean(String(cmd).slice(0, 2000)), exit: clean(code) }),
147
183
  resize: (cols, rows) => write({ t: 'resize', cols: cols ?? null, rows: rows ?? null }),
184
+ // SCREEN-FRAMES: the visible screen as plain text (screenFrames.js caps it at 16 KB), scrubbed, local file only.
185
+ screen: ({ cols = null, rows = null, text = '', colors = {}, truncated } = {}) => write({ t: 'screen', cols, rows, text: scrub(String(text)), colors: colors && typeof colors === 'object' ? colors : {}, ...(truncated ? { truncated } : {}) }),
148
186
  end: (reason = 'exit') => { write({ t: 'end', reason: clean(reason), duration_ms: now() - start }); if (fd != null) { try { closeSync(fd); } catch { /* ignore */ } fd = null; } }
149
187
  };
150
188
  }
@@ -2,13 +2,14 @@
2
2
  * The Q face for the web (cli.quox.ai): the SAME pure frames the CLI draws (qart.js, qglyph.js, qhello.js),
3
3
  * rendered into <pre> elements as coloured spans. Plays Orbit & Lock, then the Q says hello (the line
4
4
  * decodes in from katakana), then idles: the mark breathes, the status Q breathes beside "Ready", and now
5
- * and then the Dragon's Eye opens and blinks. prefers-reduced-motion: the settled frame only.
5
+ * and then the dragon's googly eyes open and blink. prefers-reduced-motion: the settled frame only.
6
6
  *
7
7
  * Browser ESM, no dependencies, no build: import it next to ../qart.js, ../qglyph.js, ../qhello.js.
8
8
  * import { mountQFace } from './qface.js';
9
9
  * mountQFace(document.getElementById('face'), { hello: "Hi, I'm Quox!" });
10
10
  */
11
- import { introPlan, restCellsAt, eyeCellsAt, INTRO_MS, EYE_MS } from '../qart.js';
11
+ import { introPlan, restCellsAt, INTRO_MS } from '../qart.js';
12
+ import { eggCells, EGG_MS } from '../qdragonScenes.js';
12
13
  import { qGlyph } from '../qglyph.js';
13
14
  import { helloCells, helloGradient, helloMs } from '../qhello.js';
14
15
 
@@ -19,7 +20,8 @@ export function rowToHTML(row) {
19
20
  let html = '', run = '', key = null;
20
21
  const flush = () => { if (!run) return; html += key ? `<span style="${key}">${esc(run)}</span>` : esc(run); run = ''; };
21
22
  for (const c of row) {
22
- const k = c.rgb ? `color:rgb(${c.rgb.join(',')})${c.bold ? ';font-weight:700' : ''}` : (c.bold ? 'font-weight:700' : null);
23
+ const parts = [c.rgb ? `color:rgb(${c.rgb.join(',')})` : null, c.bg ? `background-color:rgb(${c.bg.join(',')})` : null, c.bold ? 'font-weight:700' : null].filter(Boolean);
24
+ const k = parts.length ? parts.join(';') : null; // bg: the dragon's quadrant-block eyes (solid whites, dark pupil)
23
25
  if (k !== key) { flush(); key = k; }
24
26
  run += c.ch;
25
27
  }
@@ -45,9 +47,9 @@ export function faceFrame(t, { size, hello, eyeAt = [], color = true }) {
45
47
  const plan = introPlan(size, { color, quant: false });
46
48
  const hT = t - INTRO_MS, settledAt = INTRO_MS + helloMs(hello);
47
49
  let q;
48
- const eye = eyeAt.find((s) => t >= s && t < s + EYE_MS + 400);
50
+ const eye = eyeAt.find((s) => t >= s && t < s + EGG_MS + 400);
49
51
  if (t < INTRO_MS) q = plan.at(t);
50
- else if (eye != null) q = eyeCellsAt(size, t - eye, { color, quant: false });
52
+ else if (eye != null) q = eggCells(size, t - eye, { color });
51
53
  else q = restCellsAt(size, Math.max(0, t - settledAt), { color, quant: false });
52
54
  const line = helloCells(hello, hT, { fg: helloGradient(hello), mono: !color });
53
55
  const glyph = qGlyph('resting', Math.max(0, t - settledAt), { mono: !color });
@@ -65,7 +67,7 @@ export function mountQFace(root, { hello = "Hi, I'm Quox!", status = 'Ready', se
65
67
  let r = seed; const rand = () => { r = (r * 16807) % 2147483647; return r / 2147483647; };
66
68
  const eyeAt = [];
67
69
  let next = INTRO_MS + helloMs(hello) + 2500; // the first blink comes soon after hello, then rarely
68
- for (let i = 0; i < 40; i++) { eyeAt.push(next); next += EYE_MS + eyeEveryMs[0] + rand() * (eyeEveryMs[1] - eyeEveryMs[0]); }
70
+ for (let i = 0; i < 40; i++) { eyeAt.push(next); next += EGG_MS + eyeEveryMs[0] + rand() * (eyeEveryMs[1] - eyeEveryMs[0]); }
69
71
  const size = () => sizeForWidth(Math.min(root.clientWidth || 600, win.innerWidth || 600) * (root.clientWidth > 520 ? 0.55 : 1));
70
72
  const draw = (t) => {
71
73
  const f = faceFrame(t, { size: size(), hello, eyeAt });
@@ -73,9 +75,15 @@ export function mountQFace(root, { hello = "Hi, I'm Quox!", status = 'Ready', se
73
75
  hEl.innerHTML = rowToHTML(f.line);
74
76
  sEl.innerHTML = f.showStatus ? `${rowToHTML(f.glyph.cells)} <span class="qface-word">${esc(status)}</span>` : '&nbsp;';
75
77
  };
76
- if (still) { draw(INTRO_MS + helloMs(hello) + 10); return { stop() {} }; }
77
- let raf = 0, t0 = null, stopped = false;
78
- const tick = (now) => { if (stopped) return; t0 ??= now; draw(now - t0); raf = win.requestAnimationFrame(tick); };
78
+ if (still) { draw(INTRO_MS + helloMs(hello) + 10); return { stop() {}, pause() {}, resume() {}, seek: draw }; }
79
+ // pause()/resume() keep the animation's own clock: time spent paused (offscreen, hidden tab) is not skipped.
80
+ let raf = 0, t0 = null, stopped = false, paused = false, pausedAt = null, last = 0;
81
+ const tick = (now) => { if (stopped || paused) return; t0 ??= now; last = now - t0; draw(last); raf = win.requestAnimationFrame(tick); };
79
82
  raf = win.requestAnimationFrame(tick);
80
- return { stop() { stopped = true; win.cancelAnimationFrame(raf); }, seek: draw };
83
+ return {
84
+ stop() { stopped = true; win.cancelAnimationFrame(raf); },
85
+ pause() { if (paused || stopped) return; paused = true; pausedAt = last; win.cancelAnimationFrame(raf); },
86
+ resume() { if (!paused || stopped) return; paused = false; t0 = null; const base = pausedAt; raf = win.requestAnimationFrame((now) => { t0 = now - base; tick(now); }); },
87
+ seek: draw,
88
+ };
81
89
  }
@@ -6,7 +6,7 @@ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
6
6
  import { homedir } from 'node:os';
7
7
  import { join } from 'node:path';
8
8
 
9
- export const DOCS_URL = 'https://github.com/quoxai/quoxcore'; // will become https://cli.quox.ai
9
+ export const DOCS_URL = 'https://cli.quox.ai/docs/start/'; // #1100: CLI docs "Start here" (cli.quox.ai/docs/ itself has no index page)
10
10
  export const TERMS_URL = 'https://quox.ai/terms';
11
11
  export const PRIVACY_URL = 'https://quox.ai/privacy';
12
12
  export const LICENCE = 'Quox Community Source Licence 1.0 (source-available)';
@@ -0,0 +1,136 @@
1
+ /**
2
+ * "What's new" (QUOXON-FW16, derived in QUOXON-WHATSNEW3). Curated entries live in whatsnew.jsonl: append-only, one
3
+ * JSON object per line, added by the commit that ships the change (see whatsnew.md). ENTRIES is sorted newest first.
4
+ * With no curated source (a dev checkout without the file) entries are derived from git: commits on the CLI tree with
5
+ * a `Whatsnew: <title> | <command>` trailer or a `[whatsnew]` subject tag.
6
+ * Last-seen state is kept on the CLI profile (quoxon.whatsNew = { build, id, date }): the build the user last ran and
7
+ * the newest entry they have been shown.
8
+ */
9
+ import { readFileSync } from 'node:fs';
10
+ import { execFileSync } from 'node:child_process';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ const byDateDesc = (a, b) => (Date.parse(b.date) || 0) - (Date.parse(a.date) || 0);
14
+
15
+ /** whatsnew.jsonl text -> entries newest first. Blank or unparsable lines are skipped. */
16
+ export function parseEntries(text) {
17
+ const out = [];
18
+ for (const line of String(text || '').split('\n')) {
19
+ const t = line.trim(); if (!t) continue;
20
+ try { const e = JSON.parse(t); if (e && e.id && e.title) out.push(e); } catch { /* skip a broken line */ }
21
+ }
22
+ return out.sort(byDateDesc);
23
+ }
24
+
25
+ // %x1f between fields, %x1e between commits; the trailer value is "title | command" (command optional).
26
+ export const GIT_LOG_FORMAT = '%h%x1f%cI%x1f%s%x1f%(trailers:key=Whatsnew,valueonly,separator=%x20)%x1e';
27
+
28
+ /** git log output (GIT_LOG_FORMAT) -> entries, keeping only commits marked user-facing. */
29
+ export function gitEntries(log) {
30
+ const out = [];
31
+ for (const rec of String(log || '').split('\x1e')) {
32
+ const [sha, date, subject = '', trailer = ''] = rec.replace(/^\s+/, '').split('\x1f');
33
+ if (!sha) continue;
34
+ const tv = trailer.trim();
35
+ let title = '', cmd = '';
36
+ if (tv && tv.toLowerCase() !== 'none') [title, cmd = ''] = tv.split('|').map(x => x.trim());
37
+ else if (/\[whatsnew\]/i.test(subject)) title = subject.replace(/\[[^\]]*\]\s*/g, '').trim();
38
+ if (!title) continue;
39
+ out.push({ id: `git-${sha}`, date: (date || '').trim(), title, text: title, ...(cmd ? (cmd.startsWith('/') ? { try: cmd } : { shell: cmd }) : {}), sha });
40
+ }
41
+ return out.sort(byDateDesc);
42
+ }
43
+
44
+ /** Curated file first; git only when the file is missing or empty. Never throws. */
45
+ export function loadEntries({ readCurated, readGit }) {
46
+ let curated = [];
47
+ try { curated = parseEntries(readCurated()); } catch { /* no curated source */ }
48
+ if (curated.length) return curated;
49
+ try { return gitEntries(readGit()); } catch { return []; }
50
+ }
51
+
52
+ /**
53
+ * Curated text: whatsnew.jsonl in this repo; the public mirror export ships the same entries as a whatsnew.json array
54
+ * (scripts/release/public-mirror/export.js), which is turned back into one JSON object per line here.
55
+ */
56
+ export function readCuratedText(read) {
57
+ try { return read('./whatsnew.jsonl'); } catch { /* not here: try the exported array */ }
58
+ const arr = JSON.parse(read('./whatsnew.json'));
59
+ return Array.isArray(arr) ? arr.map(e => JSON.stringify(e)).join('\n') : '';
60
+ }
61
+
62
+ const CLI_ROOT = fileURLToPath(new URL('../..', import.meta.url));
63
+ export const ENTRIES = loadEntries({
64
+ readCurated: () => readCuratedText(f => readFileSync(new URL(f, import.meta.url), 'utf8')),
65
+ readGit: () => execFileSync('git', ['-C', CLI_ROOT, 'log', '-n', '300', `--format=${GIT_LOG_FORMAT}`, '--', '.'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 3000 }),
66
+ });
67
+
68
+ /** Entries newer than the last one shown: by id, else by date (the id is unknown after a source switch). */
69
+ export function unseenEntries(last, entries = ENTRIES) {
70
+ if (!last || typeof last !== 'object' || (!last.id && !last.date)) return entries;
71
+ const at = last.id ? entries.findIndex(e => e.id === last.id) : -1;
72
+ if (at >= 0) return entries.slice(0, at);
73
+ const t = Date.parse(last.date);
74
+ return Number.isFinite(t) ? entries.filter(e => Date.parse(e.date) > t) : entries;
75
+ }
76
+
77
+ /** The profile record after showing: build + newest entry. */
78
+ export const seenRecord = (build, entries = ENTRIES) => ({ build: String(build || ''), id: entries[0]?.id, date: entries[0]?.date });
79
+
80
+ /** The command to try for an entry: a slash command, else a shell command. */
81
+ export const tryText = (e) => e.try || e.shell || '';
82
+
83
+ export const BOX_MAX = 5;
84
+ export const LIST_DEFAULT = 10;
85
+
86
+ export const tryCommand = (e) => String(e.try || '').trim().split(/\s+/)[0];
87
+
88
+ /** Startup decision. -> { show: entries[], more: n, record: {build,id} | null }. Nothing recorded when there are no entries. */
89
+ export function startupWhatsNew({ build, last, entries = ENTRIES }) {
90
+ const newest = entries[0]?.id;
91
+ if (!newest) return { show: [], more: 0, record: null };
92
+ const record = { build: String(build || ''), id: newest };
93
+ if (!last || typeof last !== 'object' || !last.id) return { show: [], more: 0, record }; // first ever run: record silently
94
+ if (last.build === record.build) return { show: [], more: 0, record: last.id === newest ? null : record };
95
+ const at = entries.findIndex(e => e.id === last.id);
96
+ const unseen = at < 0 ? entries : entries.slice(0, at); // unknown last id (e.g. a rollback): treat all as unseen
97
+ return { show: unseen.slice(0, BOX_MAX), more: Math.max(0, unseen.length - BOX_MAX), record };
98
+ }
99
+
100
+ /** /whatsnew [all]. -> { entries, more } */
101
+ export function listWhatsNew(arg = '', entries = ENTRIES) {
102
+ const all = String(arg).trim().toLowerCase() === 'all';
103
+ const shown = all ? entries : entries.slice(0, LIST_DEFAULT);
104
+ return { entries: shown, more: entries.length - shown.length };
105
+ }
106
+
107
+ // ---- New commands (QUOXON-WHATSNEW2) -------------------------------------------------------------------------------
108
+ // Per-profile record of registry command ids the user has already been shown: quoxon.whatsNewSeen = [id...].
109
+ // Startup preference: quoxon.whatsNewStartup = 'on' | 'off' (default on). Both live in the synced profile settings.
110
+ export const startupEnabled = (settings) => String(settings?.whatsNewStartup || 'on').toLowerCase() !== 'off';
111
+
112
+ const activeCommands = (registry) => registry.filter(e => e.kind === 'command' && e.status === 'active');
113
+
114
+ /**
115
+ * Commands the user has not been shown, newest first (registry is oldest first, so reversed). With no record yet
116
+ * (first run, or an install that predates this) only entries flagged `new: true` count, so nobody gets a wall of
117
+ * every command. all=true ignores the record.
118
+ */
119
+ export function unseenCommands({ registry, seen, all = false }) {
120
+ const cmds = activeCommands(registry);
121
+ const pool = all ? cmds : Array.isArray(seen) ? cmds.filter(e => !seen.includes(e.id)) : cmds.filter(e => e.new);
122
+ return [...pool].reverse();
123
+ }
124
+
125
+ /** The ids to store after showing: everything currently active (so later additions show as new). */
126
+ export const seenAfter = (registry) => activeCommands(registry).map(e => e.id);
127
+
128
+ /** One compact startup line, or null. e.g. "6 new commands (/mind, /ask, /page, ...) · /whatsnew for more". */
129
+ export function startupCommandLine(list, max = 3) {
130
+ if (!list.length) return null;
131
+ const names = list.slice(0, max).map(e => '/' + e.id).join(', ') + (list.length > max ? ', ...' : '');
132
+ return `${list.length} new command${list.length === 1 ? '' : 's'} (${names}) \u00b7 /whatsnew for more`;
133
+ }
134
+
135
+ /** /whatsnew on|off. -> 'on' | 'off' | null */
136
+ export const toggleArg = (arg) => { const a = String(arg).trim().toLowerCase(); return a === 'on' || a === 'off' ? a : null; };
@@ -0,0 +1,134 @@
1
+ {"id":"bang-tty","date":"2026-10-16T16:00:00Z","title":"! hands over the terminal when a command needs it","text":"!quox lucid deck, !vim, !top, !less, !ssh host and other interactive commands now get the real terminal; quit them to come back. A ! command that fails for want of a terminal is re-run handed over. Everything else is still captured, so /share works.","since":"0.3.0","shell":"quox lucid deck"}
2
+ {"id":"fw12-demo","date":"2026-10-13T16:00:00Z","title":"Play the demo","text":"See a short guided run of Quox without touching your real setup.","try":"/demo","since":"0.3.0"}
3
+ {"id":"fw12-lan-ollama","date":"2026-10-13T16:30:00Z","title":"Connect a model on your network","text":"Connect an Ollama server on another machine on your network. Quox asks first, and you confirm with /connect yes.","try":"/connect","since":"0.3.0"}
4
+ {"id":"engine-chat-actions","date":"2026-10-13T17:00:00Z","title":"Chat can do things for you","text":"Ask in plain words and chat can create a slot or run a quox command for you. This is rolling out, so it may not be on your server yet.","since":"0.3.0"}
5
+ {"id":"fw13-attach-message","date":"2026-10-13T17:30:00Z","title":"Attach and speak in one go","text":"Attach to an agent by name and send it a message in the same line.","try":"/attach","since":"0.3.0"}
6
+ {"id":"fw13-one-dragon","date":"2026-10-13T18:00:00Z","title":"One dragon everywhere","text":"The dragon is the same everywhere in Quox now. The old eye is retired.","try":"/dragon","since":"0.3.0"}
7
+ {"id":"fw13-aocl-line","date":"2026-10-13T18:30:00Z","title":"One AOCL line per turn","text":"Each turn ends with a single AOCL line. Open the full table whenever you want it.","try":"/aocl","since":"0.3.0"}
8
+ {"id":"fw13-calmer-screen","date":"2026-10-13T19:00:00Z","title":"A calmer screen","text":"Each role gets one colour, history has more room, and protocol lines are hidden until you ask for them.","try":"/verbose on","since":"0.3.0"}
9
+ {"id":"fw14-slots-survive","date":"2026-10-13T19:30:00Z","title":"Your slot survives a restart","text":"Restarting keeps the slot you hold, and renaming the session renames the slot.","try":"/restart","since":"0.3.0"}
10
+ {"id":"fw12-login-in-place","date":"2026-10-13T20:00:00Z","title":"Sign in without leaving","text":"Sign in again right where you are. CLI sign-ins now last 30 days.","try":"/login","since":"0.3.0"}
11
+ {"id":"fw13-tips-search","date":"2026-10-13T20:30:00Z","title":"Search the tips","text":"Tips now come in a frame, and you can search them by topic or by what you want to do.","try":"/tips","since":"0.3.0"}
12
+ {"id":"fw14-shell-share","date":"2026-10-13T21:00:00Z","title":"Run shell commands, share the output","text":"Start a line with ! to run a shell command here, then attach its output to your next message.","try":"/share","since":"0.3.0"}
13
+ {"id":"fw14-tab-suggestions","date":"2026-10-13T21:30:00Z","title":"Tab suggests arguments","text":"After a command and a space, press Tab to see what can come next.","try":"/model","since":"0.3.0"}
14
+ {"id":"fw14-model-chooser","date":"2026-10-13T22:00:00Z","title":"Model chooser with favourites","text":"Pick a model from what is connected, star your favourites and set the one new sessions start on.","try":"/model","since":"0.3.0"}
15
+ {"id":"fw15-approve-many","date":"2026-10-13T22:30:00Z","title":"Approve several at once","text":"Approve more than one request in a single line, or all of them.","try":"/approve all","since":"0.3.0"}
16
+ {"id":"fw15-yn-approvals","date":"2026-10-13T23:00:00Z","title":"Answer approvals with y or n","text":"With an empty composer, y or n answers the approval card you can see. Privileged actions need y twice.","try":"/approvals","since":"0.3.0"}
17
+ {"id":"fw16-whatsnew","date":"2026-10-14T00:00:00Z","title":"What's new","text":"Quox now tells you what changed since you last ran it, once, when you start it.","try":"/whatsnew","since":"0.3.0"}
18
+ {"id":"history-across-runs","date":"2026-10-15T16:00:00Z","title":"Up recalls what you typed","text":"Up steps back through your earlier prompts and commands, most recent first, including ones from earlier runs. /say also takes a session name now: /say SITE-CLI reaches the slot that session holds.","try":"/say SITE-CLI hello","since":"0.3.0"}
19
+ {"id":"say-many-bell","date":"2026-10-15T16:30:00Z","title":"Message several sessions at once","text":"/say a,b,c <text> sends each one a copy that starts with who else it went to. Incoming slot messages now ring the terminal bell; /settings turns it off.","try":"/say cli-site,provisioner hello","since":"0.3.0"}
20
+ {"id":"reconnect-restart","date":"2026-10-15T19:46:21Z","title":"Calm reconnect when the server restarts","text":"If the Quox server restarts, the TUI says it is reconnecting and carries on; nothing is lost.","since":"0.3.0","sha":"716b979f7"}
21
+ {"id":"restart-keeps-agent","date":"2026-10-15T21:17:49Z","title":"/restart keeps your agent and slot","text":"Restart and you come back attached to the same agent, still holding the same slot.","try":"/restart","since":"0.3.0","sha":"0c69c2042"}
22
+ {"id":"poke-wake","date":"2026-10-15T22:33:32Z","title":"/poke and /wake really wake a session","text":"An idle session holding a slot is woken to read its messages, and a draft you are typing is never typed over.","try":"/wake <slot>","since":"0.3.0","sha":"7142e5679"}
23
+ {"id":"health-card","date":"2026-10-15T23:05:16Z","title":"/health shows a Core summary card","text":"Services, computers, agents, approvals and missions, all in one card.","try":"/health","since":"0.3.0","sha":"e6dfaa69e"}
24
+ {"id":"late-answers","date":"2026-10-16T02:19:16Z","title":"Late answers still reach you","text":"When a helper agent answers after the wait, its answer is delivered in the same turn instead of being lost.","since":"0.3.0","sha":"21a609308"}
25
+ {"id":"slots-columns","date":"2026-10-16T02:21:38Z","title":"/slots in tidy columns","text":"Every slot with who holds it and when it was last used, yours first.","try":"/slots","since":"0.3.0","sha":"90f4ccd83"}
26
+ {"id":"sleep-cmd","date":"2026-10-16T02:37:50Z","title":"/sleep","text":"The dragon curls up and sleeps.","try":"/sleep","since":"0.3.0","sha":"da9c1b188"}
27
+ {"id":"mirror-real","date":"2026-10-16T03:06:04Z","title":"/mirror shows the real Mirror","text":"See what CommanderQ sees about your org: identity, health, plugins and memory, with how old it is.","try":"/mirror","since":"0.3.0","sha":"2eb27da6e"}
28
+ {"id":"status-two-rows","date":"2026-10-16T03:08:06Z","title":"A two-row status line with labels","text":"Profile, org, mode, agent, slot and groups each have a label, and the model sits on the line above where you type.","try":"/groups","since":"0.3.0","sha":"6244dbc43"}
29
+ {"id":"update-cmd","date":"2026-10-16T03:11:38Z","title":"/update and quox update","text":"Update Quox to the latest version from inside the TUI or from your shell, then restart.","try":"/update","since":"0.3.0","sha":"30afdffbc"}
30
+ {"id":"run-requests","date":"2026-10-16T03:18:53Z","title":"Run requests in messages","text":"A slot message can ask you to run a command here; you see the whole command first and answer y, N or c.","shell":"quox slot say <slot> --run \"<command>\" \"<why>\"","since":"0.3.0","sha":"fa9b7f9d4"}
31
+ {"id":"triage-q-queue","date":"2026-10-16T04:35:00Z","title":"TRIAGE-Q works your approvals queue","text":"TRIAGE-Q pages through the whole queue, opens items, and snoozes, assigns or dismisses them one at a time.","try":"/attach triage-q","since":"0.3.0","sha":"7374985e2"}
32
+ {"id":"asks-are-cards","date":"2026-10-16T04:37:48Z","title":"Every question is an approval card","text":"Questions and approvals share one card that says what will happen and why; answer y, N, or c to talk it through.","try":"/approvals","since":"0.3.0","sha":"16a82f01d"}
33
+ {"id":"ask-session","date":"2026-10-16T05:11:59Z","title":"/ask a session a question","text":"/ask <slot> <question> sends your question straight to that session, like /say.","try":"/ask <slot> <question>","since":"0.3.0","sha":"2e064d05c"}
34
+ {"id":"org-window-back","date":"2026-10-16T05:38:46Z","title":"/org switch stays in this window, /org back returns","text":"Switching org in one window no longer moves your other windows, and /org back takes you to the org you were in.","try":"/org back","since":"0.3.0","sha":"dab35b8bd"}
35
+ {"id":"memory-core","date":"2026-10-16T05:44:08Z","title":"Memory core, pin and recall","text":"Pin the facts that matter into your org core memory, and chat recalls them with where they came from.","shell":"quox memory core","since":"0.3.0","sha":"be605c485"}
36
+ {"id":"mouse-cmd","date":"2026-10-16T05:59:04Z","title":"/mouse turns on clicks","text":"Click a sender to reply, or y, n or c on a card to answer; hold Shift to copy text.","try":"/mouse on","since":"0.3.0","sha":"dabc66f54"}
37
+ {"id":"org-slugs","date":"2026-10-16T07:05:41Z","title":"--org takes the org short name","text":"quox --org <slug> works with the org short name, not only its ID.","shell":"quox --org <slug> whoami","since":"0.3.0","sha":"9f85dec70"}
38
+ {"id":"mode-plan","date":"2026-10-16T07:21:11Z","title":"/mode plan: plan without changing anything","text":"In plan mode Quox investigates and proposes, the server refuses changes, and you say y to run the plan.","try":"/mode plan","since":"0.3.0","sha":"1351de192"}
39
+ {"id":"partial-says-partial","date":"2026-10-16T07:58:19Z","title":"Partial answers say partial","text":"When a turn could only finish part of the job, the answer says so instead of claiming it is complete.","since":"0.3.0","sha":"56e8a88a1"}
40
+ {"id":"current-focus","date":"2026-10-16T09:34:27Z","title":"Chat knows your current focus","text":"Each chat turn now carries your open objectives and recent asks, so answers stay on what you are working on.","since":"0.3.0","sha":"c62572590"}
41
+ {"id":"whatsnew-derived","date":"2026-10-16T15:15:00Z","title":"What's new shows only what changed","text":"/whatsnew lists what is new since you last looked, then marks it seen; when there is nothing new it says so.","try":"/whatsnew","since":"0.3.0"}
42
+ {"id":"profile-switch","date":"2026-10-16T15:10:00Z","title":"Switch profile without quitting","text":"An agent-login window that hits approvals or a step-up now offers your signed-in login as a y/N card: y switches this window, same conversation, attach, slot and groups. /profile <name> switches directly. A /slot name that does not exist asks to create it.","try":"/profile","since":"0.3.0","sha":"bb00994c8"}
43
+ {"id":"restore-tui-fixes","date":"2026-10-16T18:00:00Z","title":"Tab answers the approval card again","text":"With a draft in the composer, Tab focuses the approval card (your draft is kept, Esc returns to it) instead of sending the draft. /dragon-fly now fits short terminals, the background-agents line shows again, and streamed text is never cut.","try":"/approvals","since":"0.3.0"}
44
+ {"id":"presence-afk-sleep","date":"2026-10-16T16:40:00Z","title":"/afk and /sleep tell your sessions","text":"/afk [note] and /sleep mark you away or sleeping on the server, so sessions saying into hitl or your slot see \"owner away since 15:54 UTC: approvals will queue\" (quox ask and quox slot list too). Nothing is approved while you are away; /back or any message marks you present. quox presence shows who is away; /observe <text> sends a note to the observer.","try":"/afk lunch","since":"0.3.0","sha":"560262bc6"}
45
+ {"id":"answer-pairs-yestoall-pin","date":"2026-10-16T21:00:00Z","title":"Answer many at once: /approve 1 y 2 n","text":"/approve and /answer take pairs: 1 y 2 n, 1 y, 2 n or 1y 2n, mixed yes and no, all checked first, and the PIN is asked once if any yes is high risk (a no never needs it). /yestoall now shows one card for every yes/no question instead of one per item, and a yes on a high-risk ask needs your PIN.","try":"/approve 1 y 2 n","since":"0.3.0"}
46
+ {"id":"hat-command","date":"2026-10-16T19:20:00Z","title":"/hat changes hat without asking the AI","text":"/hat lists your hats with the current one marked. /hat DevOps (the key, the name, or a unique start of it) switches this conversation straight away, recorded on the server; a privileged hat asks y/N first. Works with any model, including small local ones.","try":"/hat","since":"0.3.0"}
47
+ {"id":"approval-wwwh","date":"2026-10-16T21:50:00Z","title":"Approval cards say what, where, who and why","text":"Every approval card, /approvals row and /approvals N detail now shows the exact command (verbatim, cut only with a visible count), the host count and names, the delegation chain and the human it acts for, the reason, your original request and the time, and whether the server judged it read-only. New items also keep your request text when the turn registry lost it.","try":"/approvals","since":"0.3.0"}
48
+ {"id":"slot-reply-path","date":"2026-10-17T00:45:00Z","title":"Replies to your /say now reach you","text":"A /say, @slot or /send from a window that holds a slot is now stamped with that slot by the server, so the other side replies to you. After you say into a slot, the window also shows that slot's holder's lines arriving in it for 30 minutes, and the \"no reply yet\" nudge no longer fires for a reply that showed.","try":"/say lucid sitrep","since":"0.3.0"}
49
+ {"id":"wake-real","date":"2026-10-17T01:15:00Z","title":"/wake really wakes","text":"/poke, /wake and /slap now make an idle session start a turn: the keeper checks the wake line left the prompt box and presses Enter again if Claude Code swallowed it. You are told whether the session was woken or, mid-turn, queued until its turn ends.","try":"/poke <slot>","since":"0.3.0"}
50
+ {"id":"ask-settle","date":"2026-10-17T01:15:00Z","title":"Answered questions go away","text":"A y/N/c question (Wake it now?, Run it?, Switch?, and the rest) now stays a live card only while it is open. Once you answer, it expires or it is no longer open, the card goes and one line stays, such as ✔ Wake it now? yes (00:44).","try":"/poke <slot>","since":"0.3.0"}
51
+ {"id":"active-and-hints","date":"2026-10-17T09:00:00Z","title":"/active: who is doing what","text":"/active (and quox active) is one short summary of every active worker, grouped Working, Waiting on you and Idle, newest first, with what each is doing and since when; /active sessions, slots and agents filter it. A mistyped command now names the 1-3 nearest real commands, by spelling and by what they do, and a single strong match is a y/N card that runs it.","try":"/active","since":"0.3.0"}
52
+ {"id":"restart-no-pileup","date":"2026-10-17T10:00:00Z","title":"/restart no longer piles up old windows","text":"/restart, /update and auto-restart now replace the running client instead of starting a new one inside it, so a terminal keeps exactly one Quox client however many times you restart, and old ones no longer sit in memory.","try":"/restart","since":"0.3.0"}
53
+ {"id":"decision-labels","date":"2026-10-17T10:00:00Z","title":"Questions one at a time, in words","text":"When several questions are waiting, /approvals now walks you through them one at a time: what it is, what I would do in plain words, then just say yes, no or the option. /answer and /deny notes keep their full text, even with an unclosed quote or a line break.","try":"/approvals","since":"0.3.0"}
54
+ {"id":"bulk-yes-never-risky","date":"2026-10-17T09:00:00Z","title":"A bulk yes never covers a risky ask","text":"/yestoall and number ranges now answer only the low-risk questions; high-risk, test-changing and restart asks come one at a time with their full text, and a typed number is refused if the list moved.","try":"/yestoall","since":"0.3.0"}
55
+ {"id":"walkthrough-esc-summary","date":"2026-10-17T10:30:00Z","title":"Walkthrough: Esc exits with a summary","text":"Esc now leaves /walkthrough with a summary. It shows one card at a time, keeps every answer, and answered questions never come back.","try":"/walkthrough","since":"0.3.0"}
56
+ {"id":"walkthrough-yesno-only","date":"2026-10-17T14:30:00Z","title":"Walkthrough: yes/no only answers yes/no questions","text":"On a numbered question, typing yes or no no longer picks anything. The walkthrough shows the choices in words and never picks the asker's favourite for you.","try":"/walkthrough","since":"0.3.0"}
57
+ {"id":"custom-hats","date":"2026-10-17T16:00:00Z","title":"Make your own hats","text":"/hat new walks you through a hat of your own: name it, pick a starting hat and the agents it includes. Under it a turn carries only those agents. /hat edit and /hat delete change or remove one.","try":"/hat new","since":"0.3.0"}
58
+ {"id":"recommended-first","date":"2026-10-17T17:00:00Z","title":"Recommended option shown first","text":"On a decision card, the option the asker recommends is listed first in bold with a star and who recommends it. Keys do not change, and a card with no recommendation shows none.","try":"/walkthrough","since":"0.3.0"}
59
+ {"id":"pin-card-big","date":"2026-10-17T18:30:00Z","title":"A bigger PIN card","text":"The approval PIN card now shows six large slots that light up as you type, a shimmer while it checks, a green tick when authorised and a red flash on a wrong PIN. The full decision title is shown, wrapped.","try":"/authenticate","since":"0.3.0"}
60
+ {"id":"tui-render-fixes","date":"2026-10-17T18:30:00Z","title":"Tables, /help and /slots fit your terminal","text":"Chat tables wrap long cells instead of cutting them with an ellipsis. /help keeps usage and description apart. /slots shows held slots first and folds free ones into +N free (/slots all lists them). Tall cards and the dragon no longer leave garbled frames in a short terminal.","try":"/slots","since":"0.3.0"}
61
+ {"id":"status-rows","date":"2026-10-17T19:00:00Z","title":"Make your own status rows","text":"/row new walks you through a live line above the status bar and shows what it will say before saving: approvals waiting, hosts up, a plugin status or your own script. quox rows does the same from a shell, and a row can now carry a label, a tone colour, a value and a trend.","try":"/row new","since":"0.3.0"}
62
+ {"id":"cli-errors-plain","date":"2026-10-17T19:05:00Z","title":"Plainer error messages","text":"Errors now lead with what happened and why, then one next step, instead of \"Request failed with status 404\". Approving something that was already approved says who approved it and when, and exits 0. Add --verbose to see the HTTP status and server code.","shell":"quox inbox respond <id> --action approve","since":"0.3.0"}
63
+ {"id":"delegation-progress","date":"2026-10-17T20:30:00Z","title":"See what a specialist is doing while you wait","text":"When Quox hands a job to a specialist, the waiting line now names each real step it takes, for example Delegating HYPERION · proxmox_read (vm), instead of sitting silent. Tool footers fold repeats (proxmox_read ×3).","shell":"quox chat \"which VMs use the most memory\"","since":"0.3.0"}
64
+ {"id":"ux-interaction-fixes","date":"2026-10-17T20:00:00Z","title":"Simpler cards and choosers","text":"Approval cards say each thing once and wrap cleanly. /hat, /org and /mode open the same arrow-key chooser as /model. A typo offers a Run /command? card. /sleep no longer swallows a slash command, and an offer you move on from closes itself.","try":"/hat","since":"0.3.0"}
65
+ {"id":"ask-single-key","date":"2026-10-17T21:00:00Z","title":"y, n or c answer a question straight away","text":"y, n or c now answer an open question straight away, and a stray letter no longer sticks to your next command. Pressing Enter right after is ignored, and sending the same queued message twice says it is already queued.","since":"0.3.0"}
66
+ {"id":"tidy-footer","date":"2026-10-17T20:30:00Z","title":"A tidier bottom bar","text":"The status under the input box is now one line that wraps onto a second or third row only when needed, at the same left edge, with no big gaps. It says agent: none when nothing is attached, and the key hints sit on their own row.","try":"/help status","since":"0.3.0"}
67
+ {"id":"handoff-offer","date":"2026-10-17T21:30:00Z","title":"Attached agents offer to hand a question to CommanderQ","text":"When the agent you are attached to has no tools for a question (TRIAGE-Q asked about VM CPU), a card asks: Hand this question to CommanderQ? y asks CommanderQ that one question and you stay attached; n does nothing. quox --print names the same one-turn command.","try":"/attach triage-q","since":"0.3.0"}
68
+ {"id":"divider-aocl","date":"2026-10-17T21:00:00Z","title":"Watch the governance layers light up","text":"After you send, the line under your message shows one mark per AOCL governance layer that lights up as the server reports it, then the model name shimmers while it thinks and turns green when the reply is done, or red with the blocking layer marked.","try":"/aocl","since":"0.3.0"}
69
+ {"id":"approvals-card-first","date":"2026-10-17T22:00:00Z","title":"Approvals open on the card","text":"A plain /approvals now opens straight on the one-at-a-time card. An answer with a note asks you to confirm it first, and a question that has expired says so and moves on.","try":"/approvals","since":"0.3.1"}
70
+ {"id":"welcome-cmd","date":"2026-10-17T21:45:00Z","title":"Replay the welcome screen","text":"/welcome shows the welcome screen again on demand, with the Q, the notice and the dragon greeting on a tall terminal. It changes no setup or config. quox welcome prints the same notice from the shell.","try":"/welcome","since":"0.3.0"}
71
+ {"id":"restart-countdown","date":"2026-10-17T22:30:00Z","title":"Auto-restart counts down and Esc cancels","text":"When /restart auto is on and a newer quox is installed, a line counts down 10 seconds before the restart. Esc stays on this version, Enter restarts now. It waits while a turn runs or you have a draft. QUOX_RESTART_COUNTDOWN sets the seconds (0 restarts at once).","try":"/restart auto","since":"0.3.1"}
72
+ {"id":"active-rows","date":"2026-10-17T22:45:00Z","title":"/active is one clean row per session","text":"Each session shows once, with a status glyph, a readable name, what it needs from you in plain words and its age. Long rows wrap instead of being cut off.","try":"/active","since":"0.3.0"}
73
+ {"id":"no-hint-row","date":"2026-10-17T22:30:00Z","title":"No more Enter send · /help row","text":"The permanent key-hint row under the status line is gone, so the status line ends the screen. While Quox is working you still see how to steer, queue or stop.","try":"/help","since":"0.3.0"}
74
+ {"id":"active-cli-rows","date":"2026-10-17T23:10:00Z","title":"quox active matches the window","text":"quox active now prints the same clean rows as /active, with the unread message count per session. --lines keeps the older layout.","shell":"quox active","since":"0.3.0"}
75
+ {"id":"composer-edit","date":"2026-10-17T22:30:00Z","title":"Edit your draft anywhere, like Claude Code","text":"Move into the middle of your draft and edit there: Ctrl+Left/Right or Alt+B/F jump by word, Ctrl+W or Alt+Backspace delete the word before the cursor, Alt+D the word after, Ctrl+U and Ctrl+K cut to the start or end of the line, and Up/Down move through wrapped lines. Shift with the arrows, Home or End selects text: typing replaces it, Backspace deletes it, and it is copied to your clipboard (OSC 52, works over SSH where the terminal allows it). Your terminal's own mouse selection and copy keep working as usual.","since":"0.3.1"}
76
+ {"id":"peek-id","date":"2026-10-17T22:30:00Z","title":"Look up any id with /peek","text":"/peek <id> works out whether an id is an envelope, inbox item, objective, workflow run, WARD receipt or agent and shows a short card; when nothing matches it says what it tried. Also quox peek <id>.","try":"/peek atlas","since":"0.3.0"}
77
+ {"id":"aside-q","date":"2026-10-17T22:31:00Z","title":"Ask a side question with /aside","text":"/aside <question> asks with no tools and outside the conversation, so it cannot change anything and your next message carries on as if it never happened. The answer shows in its own block marked read only. Also quox aside.","try":"/aside what does a WARD receipt prove?","since":"0.3.0"}
78
+ {"id":"profile-chooser","date":"2026-10-17T22:32:00Z","title":"Switch profile or org from one list","text":"/profile opens a list of your profiles and this login's orgs. Pick one with the arrows and Enter; it changes this window only, never the default your other windows use.","try":"/profile","since":"0.3.0"}
79
+ {"id":"at-mentions","date":"2026-10-17T22:33:00Z","title":"@ completes agents, hosts, tasks and receipts","text":"Type @ in a message to pick an agent, fleet host, task or a receipt from this session; Tab or Enter puts it in. The lists load once per session, so typing never waits on the server.","try":"/help @mention","since":"0.3.0"}
80
+ {"id":"guide-fixes-docker","date":"2026-10-19T06:15:00Z","title":"Getting started can install Docker for you","text":"On Linux, when Docker is missing or your user is not in the docker group, the setup guide offers to fix it (y/N, the exact command shown) and carries on without logging out. A failed install now says why in one line.","shell":"quox","since":"0.3.1","sha":"381c023a3"}
81
+ {"id": "roster-chat-started-runs", "date": "2026-10-19T07:20:00Z", "title": "Workflows you start from chat show as yours", "text": "When you ask Quox to run a workflow, the run is now recorded as started by you, so it appears in your roster and in the away digest instead of being hidden as background work. Scheduled and webhook runs still show as background.", "try": "/roster", "since": "0.3.1", "sha": "d684cdfff"}
82
+ {"id":"stale-signin-recover","date":"2026-10-19T05:30:00Z","title":"An open window picks up a new sign-in","text":"If you sign in again in another window or with quox login, this window now notices and carries on instead of saying it is signed out.","try":"/login","since":"0.3.1"}
83
+ {"id":"presence-auto-clear","date":"2026-10-19T05:30:00Z","title":"Away clears when you are back","text":"If you set /afk and forget /back, your first real action in the terminal (a message, any slash command, or a PIN approval) clears it and says how long you were away. Scripts, --print runs and agents never clear it.","try":"/afk","since":"0.3.1"}
84
+ {"id":"polish5-quiet","date":"2026-10-18T09:00:00Z","title":"Quieter, shorter, truer","text":"Palette descriptions fit one row and /luc finds LUCID first. Waiting approvals collapse to a single row. Your own broadcast no longer echoes back, the org shows by name, and a finished walkthrough leaves no stray line.","try":"/approvals","since":"0.3.0"}
85
+ {"id": "ollama-private-address", "date": "2026-10-19T08:00:00Z", "title": "Connect your own Ollama on your network", "text": "/connect ollama http://<lan-ip>:11434 now asks \"It's on your private network. Connect to it?\" y/N. Say yes (org admins) and Quox checks it answers as Ollama, then allows only that address. No server-wide switch needed.", "try": "/connect ollama", "since": "0.3.1"}
86
+ {"id":"restart-settle","date":"2026-10-19T09:00:00Z","since":"0.3.1","title":"One restart for back-to-back builds","text":"When several new builds land minutes apart, the restart offer now waits until the installed build has stayed unchanged for 2 minutes, so you get one restart instead of two."}
87
+ {"id":"footer-tool-count","date":"2026-10-19T12:00:00Z","since":"0.3.1","title":"Turn footer counts tools properly","text":"The line under an answer now reads 1 tool or 3 tools. Some builds printed [object Object]1 tools after a turn that used a tool."}
88
+ {"id":"cancelled-reads-cancelled","date":"2026-10-19T14:00:00Z","since":"0.3.1","title":"Cancelled reads cancelled","text":"A stopped objective in the activity list now says cancelled (a neutral circle), not failed. Expired and rejected say their own word too, and an empty question no longer draws a stray rule above the composer."}
89
+ {"id":"one-rule-above-composer","date":"2026-10-19T15:00:00Z","since":"0.3.1","title":"One rule above the composer","text":"Under an approval card the composer no longer draws a blank line and a second rule: the card's closing rule is the only one."}
90
+ {"id":"install-claude-code-codex","date":"2026-10-19T19:30:00Z","since":"0.3.1","title":"Install Claude Code and Codex from Quox","text":"Quox works best with both Claude and OpenAI models. If this server has no Claude Code or Codex, Quox offers to install it the moment you need it (a y/N card), or run quox provider install claude-code. It is the vendor's own npm package, downloaded to your server.","shell":"quox provider install claude-code","sha":"e802e90ce"}
91
+ {"id":"polish8-card-lists","date":"2026-10-19T19:40:00Z","since":"0.3.1","title":"Approval cards keep list lines, ask loosening takes --recommend","text":"A walk card now shows a file list one item per line under WHY, and text with HTML escapes shows as plain characters. A session can add its advice to a test-edit approval with --recommend y or n.","shell":"quox ask loosening --help"}
92
+ {"id":"polish8-exec-entities","date":"2026-10-19T20:00:00Z","since":"0.3.1","title":"Approval cards show commands as typed","text":"An approval for a command no longer shows HTML escapes like > in the command, reason or request lines.","shell":"quox ask loosening --help"}
93
+ {"id":"restart-min-gap","date":"2026-10-19T20:10:00Z","since":"0.3.1","title":"At most one auto-restart offer every 15 minutes","text":"The window no longer offers a restart within 15 minutes of starting; the newest settled build is offered once after that, and /restart still works any time.","try":"/restart"}
94
+ {"id":"named-offer-one-readonly","date":"2026-10-19T20:20:00Z","since":"0.3.1","title":"Only one complete read-only command is offered from an answer","text":"When an answer merely mentions a command, the window no longer offers a write like /broadcast or one missing its required text; it offers nothing unless there is exactly one safe command."}
95
+ {"id":"fun-commands-try-these","date":"2026-10-19T20:30:00Z","since":"0.3.1","title":"Ask Quox for fun commands and get a real list","text":"Ask for fun or cool commands, or what Quox can do, and the answer is a hand-picked set (the dragon verbs, /challenge, /spawn, /crystallize and more), each with a one-line reason to try it. /help ends with a short Try these block.","try":"/help"}
96
+ {"id":"q-shortcut","date":"2026-10-19T20:30:00Z","since":"0.3.1","title":"A short q command for quox","text":"Run quox shortcut add to make q start quox. It never replaces a q you already have, and quox shortcut remove undoes it.","shell":"quox shortcut add --help"}
97
+ {"id":"note-notesq","date":"2026-10-19T20:30:00Z","title":"/note saves a NotesQ note","text":"/note <text> now creates a NotesQ note and /notes lists them. The developer observer note moved to /devnote (// text still works).","try":"/note buy milk","since":"0.3.0"}
98
+ {"id":"findings-reconcile","date":"2026-10-19T21:15:00Z","since":"0.3.1","title":"Fixed security findings stop raising alarms","text":"quox engagement reconcile checks each open finding against its linked issue and fix commit. A finding whose fix is on main or in the running build now reads \"fix deployed, awaiting verification\" in /sitrep instead of an alarm. It is closed only when the issue is closed AND the fix is deployed, a critical never closes on its own, and quox engagement reconciliations shows the audit trail.","shell":"quox engagement reconcile --dry-run"}
99
+ {"id":"walk-recheck","date":"2026-10-19T22:40:00Z","since":"0.3.1","title":"The walkthrough ends by re-checking the server","text":"After your last answer the approvals walkthrough fetches the list fresh and ends with \"Nothing else is currently waiting for you.\", or tells you how many new ones arrived, or that it could not re-check. No need to run /approvals again.","try":"/walkthrough"}
100
+ {"id":"slash-plugins","date":"2026-10-19T23:55:00Z","since":"0.3.1","title":"/plugins lists your installed extensions","text":"/plugins (also /extensions, /addons, /apps) shows the plugins your org has installed with their state and version, and a mistyped slash command now suggests the nearest real one.","try":"/plugins"}
101
+ {"id":"qpe-rows","date":"2026-10-19T23:59:00Z","since":"0.3.1","title":"The proof engine rows are coloured, lined up and three deep","text":"The QPE rows now read as three rows on one grid: the score coloured by health with its sparkline, 24 h change and release-proven jobs; what got fixed and broke, the last run and who ran it, the next run; coverage coloured by ratio with the weakest area named. Narrow windows drop whole items rather than cutting words. Status rows can now send coloured cells (quox rows contract).","try":"/qpe rows 3"}
102
+ {"id":"qpe-content","date":"2026-10-20T03:30:00Z","since":"0.3.1","title":"A fourth QPE line: your cli.quox.ai content","text":"The proof engine lines now add, not just respread: the newest broken case by name, what the newest release is waiting on, the next run (and why today's was deferred), and a fourth line for cli.quox.ai content: pages backed by proof, guides, things to try, journeys and command pages. One call draws all four. /qpe 4 (or unset) shows them all.","try":"/qpe 4"}
103
+ {"id":"slash-todo","date":"2026-10-20T10:00:00Z","since":"0.3.1","title":"/todo adds Action Tracker items, /todos moves them","text":"/todo <text> adds an item and confirms with its id. /todos lists every item by column as td1 (to do), do1 (doing), dn1 (done); then /todo move do2 done, /todo td3 doing. From a shell: quox actions add \"<text>\" [--due tomorrow], quox actions move <id> done, quox actions list (every page).","try":"/todos"}
104
+ {"id":"approve-says-who","date":"2026-10-23T12:00:00Z","since":"0.3.1","title":"Approving or denying says who, what and where","text":"After you answer a card, the confirmation now reads: You approved VULCAN-SEC to run a shell command on web01. Denials use the same shape. Anything the card did not say is left out.","try":"/approvals"}
105
+ {"id":"bar-approvals","date":"2026-10-23T12:00:00Z","since":"0.3.1","title":"The line above the input says what is waiting","text":"The divider above the input box now always shows your approvals: ✔ nothing waiting, ⏸ 5 approvals waiting, or approvals ? when the inbox cannot be read or you are signed out (never a false zero). On narrow windows the model name goes first, the approvals note stays whole.","try":"/approvals"}
106
+ {"id":"done-cmd","date":"2026-10-23T14:00:00Z","since":"0.3.1","title":"/done marks Action Tracker items done","text":"Type /done td1,td2 (ids from /todos) to mark several items done at once; /todo move td1,td2 done takes lists too.","try":"/done"}
107
+ {"id":"server-chip","date":"2026-10-23T20:00:00Z","since":"0.3.1","title":"The status line names the server you are on","text":"If the machine Quox runs on is a registered server, the status line now shows it, for example server dev01 (fleet), and the remote instance when your profile talks to another one.","shell":"quox fleet hosts"}
108
+ {"id":"evidence-bundle","date":"2026-10-23T19:10:00Z","since":"0.3.1","title":"/evidence exports this conversation as a signed bundle","text":"/evidence writes every envelope, WARD receipt and VOLT run of this conversation to ~/quox-evidence/ as a signed zip and prints how to check it offline (quox evidence verify <file>). /proof now lists the envelope ids of the conversation, newest first. /ward, /envelope, /envelopes, /aee and /volt point at the same evidence.","try":"/evidence"}
109
+ {"id":"server-go","date":"2026-10-23T21:00:00Z","since":"0.3.1","title":"Open a Quox window on any dev server","text":"quox server list shows your registered dev servers and which are reachable; quox server go <name> opens Quox on one. The status line now shows the server purpose (for example main dev). /server lists them in the app.","try":"/server"}
110
+ {"id":"dragon-companion","date":"2026-10-23T21:00:00Z","since":"0.3.1","title":"Dragon companions: your attached agent's dragon greets you","text":"Dragon companions: your attached agent's dragon greets you. /dragon companion on|greeting|off.","try":"/dragon companion status"}
111
+ {"id":"stop-cmd","date":"2026-10-23T21:00:00Z","since":"0.3.1","title":"/stop and /cancel stop the running turn","text":"Type /stop (or /cancel) to stop the current turn, same as Esc. /clear now cancels a running turn too, and a stopped turn no longer leaves the status line animating.","try":"/stop"}
112
+ {"id":"approvals-ux","date":"2026-10-23T22:00:00Z","since":"0.3.1","title":"Approvals: readable rows, full content, walkthrough covers them","text":"/approvals rows now show what each approval says (the start of an X post), /approvals N shows the full content, a bare y after a list asks which one instead of going to chat, and /walkthrough walks plain approvals as cards.","try":"/walkthrough"}
113
+ {"id": "dragon-transient", "date": "2026-10-23T23:00:00Z", "since": "0.3.1", "title": "The dragon comes and goes", "text": "The dragon companion no longer stays on screen. It appears when you attach, at key moments (a new approval, a failed turn, a long task finishing) and when you type /dragon, then leaves after a few seconds or on your next keypress.", "try": "/dragon"}
114
+ {"id": "resume-attach-coherent", "date": "2026-10-24T01:00:00Z", "since": "0.3.1", "title": "Resume keeps the right agent", "text": "Resuming a conversation now attaches you to the agent it was with, so the footer and your next turn agree. After a restart, if the saved attachment and conversation differ, you get one warning and a fresh conversation. Resumed titles never show painted screen characters.", "try": "/resume"}
115
+ {"id": "dragon-smooth", "date": "2026-10-24T03:30:00Z", "since": "0.3.1", "title": "Companion dragons animate smoothly, and bigger", "text": "Lucid, Hyperion and Seer now play through frames drawn off the main thread, so they move as smoothly as the Quox dragon and no longer stutter the screen. They are larger, stay for 10 seconds (a keypress in the first 2 seconds will not send them away), every typed /attach greets, and /dragon companion calls the dragon too.", "try": "/dragon"}
116
+ {"id": "say-last-target", "date": "2026-10-24T19:00:00Z", "since": "0.3.1", "title": "/say remembers who you were talking to", "text": "If the first word of a /say is not a slot (a URL, a path, plain text), Quox offers to send the whole message to the slot you last said something to. A URL is never taken as a slot name.", "try": "/say"}
117
+ {"id": "full-envelope-ids", "date": "2026-10-25T08:00:00Z", "since": "0.3.1", "title": "/proof and tool lines show the whole envelope id", "text": "Envelope ids, WARD seqs and bundle paths now wrap instead of being cut off, even in a narrow window. /proof puts the full id on its own line to copy, with the quox aee get and quox evidence verify commands, and the model can quote the id too.", "try": "/proof"}
118
+ {"id": "contacts-from-chat", "date": "2026-10-25T09:00:00Z", "since": "0.3.1", "title": "Add and list contacts by just asking", "text": "Ask Quox to add Sam Lee to your contacts with a number and it does, through the same approval rules as the CLI; ask to list your contacts and it pages through all of them. /contact new now builds a valid quox contacts add command (--mobile and --tel become --phone, multi-word names are quoted) and refuses flags the command does not have. quox contacts list pages with --offset.", "try": "/contact new"}
119
+ {"id": "contacts-send-at", "date": "2026-10-25T09:00:00Z", "since": "0.3.1", "title": "Schedule an SMS for a time, and see why a send failed", "text": "quox contacts send now takes --at (10am, 14:30 or a full ISO time, read in Europe/London unless you pass --tz) and the server delivers it then, even if you close the terminal. The answer states the exact time, and quox contacts scheduled list or cancel manages the queue. A failed send now says why in plain words.", "shell": "quox contacts send <contact> --channel sms --at 10am \"hello\""}
120
+ {"id": "attach-agent-first", "date": "2026-10-25T12:00:00Z", "since": "0.3.1", "title": "/attach seer now picks the agent first", "text": "When a name is both an agent and a slot, /attach <name> attaches the agent and tells you the slot exists. Use /attach slot:<name> for the slot or /attach agent:<name> to be explicit. A name that is only a slot still attaches the slot.", "try": "/attach slot:seer"}
121
+ {"id": "dragons-sentinel-drago", "date": "2026-10-25T19:00:00Z", "since": "0.3.1", "title": "Two new dragons: Sentinel (Warden) and Drago", "text": "Two new dragons: Sentinel (Warden) and Drago. Attach Warden or the security lead and they greet you, wait with you at approvals and show when something needs attention. Sentinel shows its shield icon on smaller terminals.", "try": "/attach sentinel"}
122
+ {"id": "dragons-cipher", "date": "2026-10-25T21:00:00Z", "since": "0.3.1", "title": "New dragon: Cipher", "text": "New dragon: Cipher, the network operations dragon. Attach cipher and it greets you, listens while you work, waits with you at approvals and shows when something needs attention.", "try": "/attach cipher"}
123
+ {"id": "objective-offer", "date": "2026-10-25T22:00:00Z", "since": "0.3.1", "title": "Ask for something your agent cannot do, get an objective", "text": "Ask an attached agent for a multi-step job it has no tools for and Quox now offers (y or n) to make it an objective that runs until done. You approve the plan, then each step.", "try": "/attach seer"}
124
+ {"id": "dragon-jobs", "date": "2026-10-25T23:00:00Z", "since": "0.3.1", "title": "Dragons now report real state", "text": "Each companion dragon has one job and says it from a live read: Sentinel the WARD state of your last answer, Cipher route health, Seer the plan, Hyperion the fleet, Drago open security findings, Lucid recovery. If a read fails it says it could not check.", "try": "/attach cipher"}
125
+ {"id": "dragon-summons", "date": "2026-10-25T23:30:00Z", "since": "0.3.1", "title": "Dragons turn up when your work finishes", "text": "With the dragon companion on, a finished or failed objective or loop calls Seer for a few seconds with the real result, even with no agent attached. Never while you are typing, and each dragon waits a minute before it can be called again.", "try": "/dragon companion status"}
126
+ {"id": "loop-card", "date": "2026-10-26T00:00:00Z", "since": "0.3.1", "title": "Watch a loop as it runs", "text": "A loop you start from chat now shows a live card that updates in place: turns, receipts witnessed, and the last lines of its output. Milestones and errors land as chat lines under it, and a finished loop prints its proof command.", "try": "/loop list"}
127
+ {"id": "loop-offer", "date": "2026-10-26T01:00:00Z", "since": "0.3.1", "title": "Ask for a loop and Quox offers one", "text": "Say \"keep checking until it is green\" or \"run it as a loop\" and Quox offers to create a loop with a tool list worked out from your goal (read-only unless the goal writes a file or runs a command). You see exactly which tools it may use and approve it before it starts. A fixed cadence like \"every 15 minutes\" is offered as a schedule instead.", "try": "/loop list"}
128
+ {"id": "dragon-fit", "date": "2026-10-26T01:00:00Z", "since": "0.3.1", "title": "Dragons fit your window, or tell you why not", "text": "/dragon fire and /dragon-fly now size to your real window and say how many rows they need when it is too small. /detach brings the Q dragon back to greet you when dragons are on.", "try": "/dragon-fly"}
129
+ {"id":"restart-waits-server","date":"2026-10-26T02:10:00Z","title":"Auto restart waits for Quox to come back","text":"When a new version lands while the Quox server is restarting, the window now waits for the server before it restarts, and picks its slot back up once the server answers.","try":"/restart","since":"0.3.1"}
130
+ {"id":"keep-going-plain-words","date":"2026-10-26T04:00:00Z","title":"Ask for a watch in plain words","text":"Say \"keep an eye on quox.ai for the next 10 minutes\" or \"watch the build until it is green\" and Quox offers to keep going for you, no need to say loop. One key to start it, one to just answer once.","try":"/watch quox.ai for downtime","since":"0.3.1"}
131
+ {"id": "objective-offer-reports-back", "date": "2026-10-26T05:30:00Z", "title": "Objectives report back in the chat", "text": "Say a long multi-step ask to an advisory agent and Quox offers to run it as an objective. The plan routes each step to an agent that holds the tool, a fallback like \"if something goes wrong, save the draft\" is honoured, and when it ends the chat tells you what happened and where things are.", "try": "/attach seer", "since": "0.3.1"}
132
+ {"id": "loop-card-polish", "date": "2026-10-26T13:00:00Z", "since": "0.3.1", "title": "The loop card keeps its own count", "text": "The card now counts the loop's own turns (turn 2 of 5, not the message count), shows the whole runway, fills a receipts bar as each one is witnessed, and says a milestone once. With an empty composer, press Down to reach its keys. A finished loop calls its dragon.", "try": "/loop list"}
133
+ {"id": "org-picker", "date": "2026-10-26T16:30:00Z", "since": "0.3.1", "title": "Switch org in one keystroke", "text": "Type /org for a list of your orgs, arrow to one and press Enter: this window moves there at once and the status bar follows. Press d instead to also make it your default. /org hampshire switches straight away when only one org matches, and quox org switch with no argument gives a numbered list.", "try": "/org"}
134
+ {"id":"quoxcli-0-4-0","date":"2026-10-26T18:00:00Z","since":"0.4.0","title":"quoxon extensions, an inbox bar, history and mouse editing","text":"Add and manage extensions with quox ext add, quox ext remove and quox ext list, or /ext in the window. /inbox and a notification bar show what is waiting. Up and Ctrl+R search your history. /mouse turns on optional mouse editing. Plan approval cards now list their steps, and live loop cards no longer freeze the window.","try":"/ext","shell":"quox ext list"}