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
@@ -3,8 +3,113 @@
3
3
  * as active here is wired in app.js; planned entries are listed dim under
4
4
  * "Coming" and refuse to run. Add a command here first, or it does not exist.
5
5
  */
6
+ import { topicList } from './tips.js';
7
+ import { devObserverOn } from './devObserver.js';
8
+ import { SHORT } from './commandShort.js';
9
+
6
10
  export const GROUPS = ['Talk', 'Approvals', 'Skills', 'Conversation', 'Connect', 'Verbose', 'Context', 'Awareness'];
7
11
 
12
+ // Argument suggestions after `/command ` (slash/args.js): levels in order, first match wins.
13
+ const ARGS = {
14
+ model: [
15
+ { after: [], src: 'model.connected', choices: [
16
+ { value: 'fav', desc: 'favourites: add, rm, or a number' },
17
+ { value: 'default', desc: 'the model new sessions start on' },
18
+ { value: 'auto', desc: 'server policy picks' },
19
+ { value: 'list', desc: 'what is connected' }] },
20
+ { after: ['fav'], src: 'model.favs', choices: [
21
+ { value: 'add', desc: 'add <provider> <model>' },
22
+ { value: 'rm', desc: 'remove by number or model' }] },
23
+ { after: ['fav', 'add'], src: 'model.connected' },
24
+ { after: ['fav', 'add', '*'], src: 'model.models', of: 2 },
25
+ { after: ['fav', 'rm'], src: 'model.favs' },
26
+ { after: ['fav', '*'], choices: [] },
27
+ { after: ['default'], src: 'model.connected',
28
+ choices: [{ value: 'clear', desc: 'no default' }] },
29
+ { after: ['default', '*'], src: 'model.models', of: 1 },
30
+ { after: ['*'], src: 'model.models', of: 0 },
31
+ { after: ['*', '*'], choices: ['compact', 'full'] }
32
+ ],
33
+ connect: [{ after: [], src: 'providers' }],
34
+ fleet: [{ after: [], src: 'fleetHosts' }],
35
+ replay: [{ after: [], src: 'conversations' }],
36
+ org: [
37
+ { after: [], src: 'orgs', choices: [
38
+ { value: 'switch', desc: 'move this session to another org' },
39
+ { value: 'back', desc: 'return to the org you were in before' },
40
+ { value: 'list', desc: 'your orgs' }] },
41
+ { after: ['switch'], src: 'orgs' }
42
+ ],
43
+ hat: [
44
+ { after: [], choices: [
45
+ { value: 'new', desc: 'make a hat of your own' },
46
+ { value: 'edit', desc: 'change one of your hats' },
47
+ { value: 'delete', desc: 'remove one of your hats' }] }
48
+ ],
49
+ attach: [{ after: [], src: 'agents' }],
50
+ agents: [
51
+ { after: [], src: 'agents', choices: [{ value: 'list', desc: 'pick from all agents (list sec narrows it)' }] },
52
+ { after: ['list'], choices: [] },
53
+ { after: ['*'], choices: [{ value: 'tools', desc: 'the tools this agent may use' }] }
54
+ ],
55
+ which: [{ after: [], choices: [
56
+ { value: 'profile', desc: 'login name, human or agent' }, { value: 'org', desc: 'the org this window is in' },
57
+ { value: 'agent', desc: 'who your turns go to' }, { value: 'hat', desc: 'the hat on your last turn' },
58
+ { value: 'model', desc: 'provider and model' }, { value: 'slot', desc: 'slot and its live stream' },
59
+ { value: 'conversation', desc: 'conversation id and name' }] }],
60
+ slot: [{ after: [], src: 'slots', choices: [{ value: 'log', desc: 'recent messages in the held slot' }] }],
61
+ mode: [{ after: [], choices: [{ value: 'ask', desc: 'ask first (the default)' }, { value: 'plan', desc: 'plan only: investigates and proposes, changes nothing' }] }],
62
+ say: [{ after: [], src: 'slots' }],
63
+ broadcast: [{ after: [], choices: [{ value: '--slots', desc: 'only these slots, comma separated' }, { value: '--priority', desc: 'urgent shows red' }, { value: '--ack', desc: 'ask recipients to /ack' }] }],
64
+ send: [{ after: [], src: 'slots' }, { after: ['*'], src: 'files', of: 'partial' }],
65
+ dragon: [
66
+ { after: [], choices: ['explain', 'ask', 'sleep', 'fly', 'hero', 'fire', 'classic', { value: 'companion', desc: 'the dragon beside an attached Lucid, Hyperion, Seer, Sentinel, Drago or Cipher' }] },
67
+ { after: ['companion'], choices: [{ value: 'on', desc: 'on attach and key moments, then leaves' }, { value: 'greeting', desc: 'only on attach, then leaves' }, { value: 'off', desc: 'never on its own (/dragon shows it)' }, { value: 'status', desc: 'what is set and whether the art loads' }] }
68
+ ],
69
+ inbox: [{ after: [], choices: [{ value: 'read', desc: 'read N | all: mark items read' }, { value: 'open', desc: 'open N: take an approval to /approvals' }, { value: 'latest', desc: 'the latest 10' }] }],
70
+ tips: [{ after: [], choices: topicList().map(t => ({ value: t.topic, desc: `${t.count} tip${t.count === 1 ? "" : "s"}` })) }],
71
+ whatsnew: [{ after: [], choices: [{ value: 'all', desc: 'every command and entry, regardless of seen' }, { value: 'on', desc: 'show the card at startup when something is new' }, { value: 'off', desc: 'stop the startup card' }] }],
72
+ 'tip-settings': [{ after: [], choices: [
73
+ { value: 'off', desc: 'no tips' },
74
+ { value: 'core', desc: 'free features only' },
75
+ { value: 'all', desc: 'everything shipped' }] }],
76
+ hosts: [{ after: [], choices: [{ value: 'add', desc: 'print the one install command' }] }],
77
+ row: [
78
+ { after: [], choices: [
79
+ { value: 'new', desc: 'make a row, step by step (y/N cards)' },
80
+ { value: 'add', desc: 'a live line above the status bar' },
81
+ { value: 'edit', desc: 'change a row, step by step' },
82
+ { value: 'reload', desc: 'pick up rows changed by quox rows' },
83
+ { value: 'list', desc: 'your rows and what they show now' },
84
+ { value: 'rm', desc: 'remove a row' },
85
+ { value: 'test', desc: 'show a row\'s raw value' },
86
+ { value: 'on', desc: 'show rows' },
87
+ { value: 'off', desc: 'hide and pause all rows' }] },
88
+ { after: ['rm'], src: 'rows' },
89
+ { after: ['test'], src: 'rows' },
90
+ { after: ['edit'], src: 'rows' },
91
+ { after: ['add', '*'], choices: [
92
+ { value: 'url', desc: 'fetch JSON from a URL' },
93
+ { value: 'cmd', desc: 'run a shell command (this machine only)' }] },
94
+ { after: ['add', '*', 'url'], choices: [] },
95
+ { after: ['add', '*', 'cmd'], choices: [] },
96
+ { after: ['add'], choices: [] }
97
+ ]
98
+ };
99
+
100
+ // Dev-only entries (dev: true) are vendor tooling: they live in lib/vendor/devCommands.js, which package.json
101
+ // "files" keeps out of the published quoxcli (same mechanism as the owner-only licence verbs, quox#1134).
102
+ // Present in the dev tree, absent for every npm user.
103
+ let DEV_ENTRIES = [];
104
+ // TUI-QPE 2026-10-20 (owner: "/qpe is extension right?"): owner tooling that is not dev-observer gated also lives in
105
+ // lib/vendor (VENDOR_ENTRIES), so the public build has no /qpe; customers have no proof engine.
106
+ let VENDOR_ENTRIES = [];
107
+ try {
108
+ const v = await import('../vendor/devCommands.js');
109
+ DEV_ENTRIES = v.DEV_ENTRIES || [];
110
+ VENDOR_ENTRIES = v.VENDOR_ENTRIES || [];
111
+ } catch (e) { if (e?.code !== 'ERR_MODULE_NOT_FOUND') throw e; }
112
+
8
113
  export const REGISTRY = [
9
114
  // Talk (keys)
10
115
  { id: 'type', kind: 'key', group: 'Talk', usage: 'just type', desc: 'ask Quox anything; CommanderQ routes it to the right agent', status: 'active' },
@@ -17,105 +122,311 @@ export const REGISTRY = [
17
122
  { id: 'newline', kind: 'key', group: 'Talk', usage: 'Shift+Enter / Ctrl+J', desc: 'new line in the composer (a trailing \\ then Enter also works)', status: 'active' },
18
123
  { id: 'editor', kind: 'key', group: 'Talk', usage: 'Ctrl+G', desc: 'edit the draft in $EDITOR', status: 'active' },
19
124
  { id: 'stash', kind: 'key', group: 'Talk', usage: 'Ctrl+S', desc: 'stash the draft; on an empty composer, restore it', status: 'active' },
20
- { id: 'undo', kind: 'key', group: 'Talk', usage: 'Ctrl+_', desc: 'undo in the composer', status: 'active' },
125
+ { id: 'undo-key', kind: 'key', group: 'Talk', usage: 'Ctrl+_', desc: 'undo in the composer', status: 'active' },
21
126
  { id: 'paste', kind: 'key', group: 'Talk', usage: 'paste', desc: 'long pastes collapse to [Pasted text #N +L lines] and expand on send', status: 'active' },
22
127
  { id: 'ctrlc', kind: 'key', group: 'Talk', usage: 'Ctrl+C', desc: 'clear the draft; during a turn interrupt; twice when idle quits', status: 'active' },
23
128
  // Approvals
24
- { id: 'approvals', kind: 'command', group: 'Approvals', usage: '/approvals', desc: 'list what is waiting for you', server: true, cap: 'approvals', status: 'active' },
25
- { id: 'approve', kind: 'command', group: 'Approvals', usage: '/approve N', desc: 'approve item N from the list', server: true, cap: 'approvals', status: 'active',
129
+ { id: 'approvals', kind: 'command', group: 'Approvals', usage: '/approvals [mine|agents|old|<agent>|N]', desc: 'the questions waiting for you, one at a time', server: true, cap: 'approvals', status: 'active' },
130
+ { id: 'inbox', kind: 'command', new: true, group: 'Approvals', usage: '/inbox [N | read N|all | open N]', desc: 'your Quox inbox: the latest 10 as a table, N shows one in full, read marks read, open takes an approval to /approvals', needs: ['inboxItems'], cap: 'approvals', status: 'active', args: ARGS.inbox },
131
+ { id: 'walkthrough', kind: 'command', new: true, group: 'Approvals', usage: '/walkthrough', desc: 'walk through what is waiting for you one question at a time: what it does, why, my advice, then your choice (same as /approvals walkthrough)', server: true, cap: 'approvals', status: 'active' },
132
+ { id: 'yestoall', kind: 'command', new: true, group: 'Approvals', usage: '/yestoall', desc: 'approve everything waiting for you (same as /approve all; privileged ones still ask)', server: true, cap: 'approvals', status: 'active' },
133
+ { id: 'approve', kind: 'command', group: 'Approvals', usage: '/approve N | 1 y 2 n | agent <name>', desc: 'approve item N from the list, or everything one agent asked', server: true, cap: 'approvals', status: 'active',
26
134
  detail: 'Run /approvals first. Mid-turn approval cards also take a (approve), d (deny), o (open); they arm after 300 ms and never default to approve.' },
27
- { id: 'deny', kind: 'command', group: 'Approvals', usage: '/deny N', desc: 'deny item N from the list', server: true, cap: 'approvals', status: 'active' },
135
+ { id: 'deny', kind: 'command', group: 'Approvals', usage: '/deny N [reason] | agent <name> [reason]', desc: 'deny item N (or all of one agent) with an optional reason the requester sees', server: true, cap: 'approvals', status: 'active' },
136
+ { id: 'answer', kind: 'command', group: 'Approvals', usage: '/answer 3 b ["note"] | 1 y 2 n', desc: 'answer a decision a session asked (quox ask): pick its option, with an optional note the asker gets', server: true, cap: 'approvals', status: 'active',
137
+ detail: 'Run /approvals first: decisions are listed first, one line each, with who asked, the options (a) (b) (c), what is recommended and how long is left. /answer 3 b picks option b of item 3; /answer 3 b "after Friday" adds a note. After /approvals 3 you can leave the number off: /answer b. y/n questions also take /approve 3, and /deny 3 <reason> declines with a reason. /approve all never answers a decision for you.' },
138
+ { id: 'authenticate', kind: 'command', group: 'Approvals', usage: '/authenticate', desc: 'enter your PIN once: a person is present for 10 minutes', server: true, cap: 'approvals', status: 'active',
139
+ detail: 'A typed y cannot prove a person typed it, so approvals an agent raised (or high-risk ones) and /override need your step-up PIN. /authenticate asks for it once and keeps a "human present" window open for 10 minutes (shown in gold above the status line), so those do not ask again. Set the PIN in the dashboard (Settings > Account > Approval PIN); it cannot be set from here. Agent logins cannot use this: a person approves from their phone or a human session.' },
140
+ { id: 'authorise', kind: 'command', group: 'Approvals', usage: '/authorise N', desc: 'approve item N with your PIN', server: true, cap: 'approvals', status: 'active', aliases: ['authorize'],
141
+ detail: 'Like /approve N, but asks for your step-up PIN first when there is no live human-present window. The digits are never shown, saved, or sent anywhere except the PIN check.' },
142
+ { id: 'override', kind: 'command', group: 'Approvals', usage: '/override <block id|last> <reason>', desc: 'override ONE blocked action (PIN, recorded and witnessed)', server: true, cap: 'approvals', status: 'active',
143
+ detail: 'When a policy block refuses an action (safety level, budget cap, AOCL or WARD deny), a person with a fresh PIN can override that one action. The reason, your name and the block are recorded and witnessed. Today only safety blocks (E-STOP, DEFCON RED dispatch refusals) are honoured by a gate; other block types are recorded but stay blocked, and the answer says so. "last" is the most recent block this window saw.' },
28
144
  // Skills and rules
29
145
  { id: 'skills', kind: 'command', group: 'Skills', usage: '/skills [name]', desc: 'list your org\'s governed skills, or show one as a card', needs: ['skills'], cap: 'skills', status: 'active',
30
146
  detail: 'Lists your org skills (reusable procedures an agent follows; not Crystals, which are deterministic replays) with edition and one-line description. /skills <name> shows its card: purpose, version and review state, tests (results only when run-tests recorded them), the agents it is for, where it came from, recorded use, and how to run it.' },
31
147
  { id: 'skill', kind: 'command', group: 'Skills', usage: '/skill <name> [args]', desc: 'ask Quox to run a skill (governed: approvals still apply)', server: true, cap: 'skills', status: 'active',
32
148
  detail: 'Sends "Run the skill <name> with: <args>" as a normal turn, so CommanderQ runs it through the usual governed path. Nothing is executed from here, and approval cards work as always.' },
149
+ { id: 'plugins', kind: 'command', group: 'Skills', usage: '/plugins [name]', desc: 'plugins (extensions, add-ons) your org has installed: enabled state and version; /plugins <name> shows one', needs: ['pluginList'], status: 'active', aliases: ['plugin', 'extensions', 'extension', 'addons', 'apps'],
150
+ detail: 'Read-only. Lists the plugins installed for the active org (the same call as quox plugin list) with enabled or disabled and the version. /plugins <name> shows one plugin (as quox plugin get). Also answers to /extensions, /extension, /addons and /apps; "/extension list" works too.' },
151
+ { id: 'todo', kind: 'command', group: 'Awareness', usage: '/todo <text>', desc: 'add an Action Tracker item; /todo move do2 done moves one; /todo alone lists', needs: ['todoAdd', 'todoList', 'todoMove'], status: 'active',
152
+ detail: 'Moves: /todo move do5 done, /todo do5 done, /todo mark do2 done, /todo td3 doing (td = to do, do = doing, dn = done; the numbers are the ones /todos last showed, and if the list changed since, it says so and still moves the item you saw). Adds one item to your Action Tracker (status todo) as you, the same as quox actions add "<text>" or adding it in the dashboard, then confirms with the item id. No model call and no approval: you typing it is the authorization. /todo with no text lists your open items, the same as /todos. Mark one done with quox actions done <id>.' },
153
+ { id: 'done', kind: 'command', group: 'Awareness', usage: '/done <ids>', desc: 'mark Action Tracker items done: /done td1,td2', needs: ['todoList', 'todoMove'], status: 'active',
154
+ detail: 'Marks Action Tracker items done by the short ids /todos last showed: /done td1,td2 or /done td1 td2. Each one is confirmed by title. Same as /todo move td1,td2 done. No model call and no approval: you typing it is the authorization.' },
155
+ { id: 'todos', kind: 'command', group: 'Awareness', usage: '/todos', desc: 'your Action Tracker by column, numbered td1 do1 dn1 for /todo move', needs: ['todoList'], status: 'active',
156
+ detail: 'Read-only. Lists your whole Action Tracker (every page) by column: To do td1.., Doing do1.., Done dn1.. (the 10 newest; the rest via quox actions list --status done), newest first. Use the numbers with /todo move do2 done. /todo <text> adds one.' },
33
157
  { id: 'rules', kind: 'command', group: 'Skills', usage: '/rules [files|off|on|N]', desc: 'which rules win: advisory QUOX.md, the dev pack, enforced server policy', status: 'active',
34
158
  detail: '/rules shows the precedence: QUOX.md and the dev pack only guide; armed governance policies on the server are enforced and win. /rules files shows the QUOX.md text. QUOX.md in this directory and parents up to the git root, plus ~/.quox/QUOX.md, are sent with each turn as your rules. World-writable files and symlinks leaving your home or repo are ignored. /rules N shows page N.' },
35
159
  // Conversation
36
160
  { id: 'clear', kind: 'command', group: 'Conversation', usage: '/clear', desc: 'fresh conversation and a clear screen; rules and memory kept, /resume goes back', status: 'active', aliases: ['new', 'reset'],
37
161
  detail: 'Clears the screen and starts a new conversation. Your QUOX.md rules, attached agent, model choice and Quox memory stay as they are. The previous conversation is kept on the server: /resume lists it.' },
162
+ { id: 'stop', kind: 'command', group: 'Conversation', usage: '/stop', desc: 'stop the current turn, same as Esc', status: 'active', aliases: ['cancel'],
163
+ detail: 'Stops the turn that is running, exactly like Esc: the stream is cancelled, the working animation ends and the status line goes back to normal. /cancel does the same. With nothing running it says so.' },
38
164
  { id: 'resume', kind: 'command', group: 'Conversation', usage: '/resume [id|name]', desc: 'pick a recent conversation and carry on where it left off', status: 'active', needs: ['conversations', 'conversationTurns'],
39
165
  detail: 'Alone: a picker of your recent conversations (title, agent, age); Up/Down and Enter resume, Esc cancels. With an id (or the start of one) or a /rename name it resumes directly. The last turns are shown as history and your next message continues that conversation. Also at launch: quox --resume [id|name] or quox --conversation <id>.' },
40
166
  { id: 'compact', kind: 'command', group: 'Conversation', usage: '/compact [show]', desc: 'summarise the conversation on the server, keeping decisions', status: 'active', needs: ['compactConversation'],
41
167
  detail: 'The server summarises this conversation with your AI provider, keeping decisions, and the summary becomes the start of the conversation for later turns. Shows the token change and the first lines of the summary; /compact show prints all of it. Refused while a turn runs, and when the org has not given AI processing consent. Asking "compact this conversation" in words only suggests it.' },
42
- { id: 'help', kind: 'command', group: 'Conversation', usage: '/help [command]', desc: 'this reference, or detail for one command or key', status: 'active' },
43
- { id: 'note', kind: 'command', group: 'Conversation', usage: '/note <text>', desc: 'jot an observation for the dev observer (also: // text); not sent to Quox', status: 'active',
44
- detail: 'Records your text in the dev transcript only: not sent to the model or the server, no turn, not in chat history. Needs dev transcripts on (touch ~/.config/quox/dev-transcripts). Up to 2000 characters.' },
168
+ { id: 'challenge', kind: 'command', group: 'Conversation', usage: '/challenge [focus]', desc: 'an adversarial review of the last answer by a different provider (costs one extra model turn)', status: 'active', needs: ['connections'],
169
+ detail: 'Brutaloop on demand. Sends the last answer and that turn\'s tool calls and receipts to ONE review turn on a different connected provider (else a different model on the same provider; it says which). The reviewer lists factual errors, unsupported claims, missed failures and unsafe actions, each with a severity and the evidence it rests on, and does not rewrite anything. Read-only, but it costs one extra model turn. Afterwards "Resolve these? y/N": y sends the findings back to the agent that answered, as a normal turn, to fix or rebut each one. The optional focus narrows the review, e.g. /challenge security.' },
170
+ { id: 'spawn', kind: 'command', group: 'Conversation', usage: '/spawn <cores>c <ram>G <disk>G <lifespan> for <objective> | list | done <vmid>', desc: 'ask HYPERION for a disposable VM (posts to slot provisioner; lifespan required, max 24h)', status: 'active', needs: ['slotSay', 'slotStream'], cap: 'slots',
171
+ detail: 'Order is loose and plain words work: /spawn 4 cores 8G 20G for 2h to test the installer. Lifespan is required (max 24h); at most 3 live VMs per session. You see one card (resources, lifespan, objective, and that it posts to HYPERION); y posts the request to slot provisioner signed by this session, waits for HYPERION\'s lease reply and prints the vmid, IP, user and how to connect. The lease is stored locally with its expiry and released with "done <vmid>" at expiry, or on /spawn done <vmid>. There is no server-side slot reminder, so expiry runs in this window: if it is closed, the VM is released on the next start, and /spawn list shows overdue leases in red.' },
172
+ { id: 'consent', kind: 'command', group: 'Conversation', usage: '/consent [grant [provider]]', desc: 'AI data sharing consent: see where you stand, or give your OK and carry on', status: 'active', needs: ['consentStatus'],
173
+ detail: 'Cloud AI providers (OpenAI and other hosted ones) receive your messages and tool results, so Quox needs your OK first; it is personal, not an admin setting. /consent shows where you stand. /consent grant [provider] records your OK (the same as quox consent grant --provider <name>, or Settings > Privacy in the dashboard) and, if a message just failed for lack of it, sends that message again. A local model (Ollama) never needs it.' },
174
+ { id: 'help', kind: 'command', group: 'Conversation', usage: '/help [command]', desc: 'this reference, or detail for one command or key', status: 'active',
175
+ detail:
176
+ 'Type / to open the command list. After a command and a space, the next word is ' +
177
+ 'suggested too (providers, live model ids, orgs, agents, slots, options): Up and Down' +
178
+ ' choose, Tab completes, Esc closes, and keep typing to narrow. Suggestions that ' +
179
+ 'cannot be fetched are simply left out.' },
180
+ { id: 'note', kind: 'command', group: 'Conversation', usage: '/note <text>', desc: 'save a NotesQ note (first line is the title); not sent to Quox. /notes lists them', status: 'active',
181
+ detail: 'Creates a NotesQ note through the same backend as quox notes new: the first line (or first 60 characters) becomes the title and your text is the body. It confirms with the title and id. If NotesQ is not licensed or enabled for your org it says so and how to enable it. /notes lists recent notes (quox notes ls).' },
182
+ { id: 'notes', kind: 'command', group: 'Conversation', usage: '/notes', desc: 'list your recent NotesQ notes (same as quox notes ls)', status: 'active',
183
+ detail: 'Runs quox notes list in this window. /note <text> saves a new one.' },
184
+ { id: 'contact', kind: 'command', group: 'Conversation', usage: '/contact new <name> [--phone N] [--email E] [--relationship R]', desc: 'add a contact (same as quox contacts add); /contact list pages them', status: 'active',
185
+ detail: 'Builds a quox contacts add command from what you type: --mobile, --tel and --number become --phone, a multi-word name is quoted for you, and a flag the command does not have is refused with the list it does take. It goes through the same approval rules as the CLI.' },
186
+ { id: 'bang', kind: 'key', group: 'Talk', usage: '!<command>', desc: 'run a shell command here without leaving (!quox <args> runs this quox as you); output stays on your screen', status: 'active',
187
+ detail: 'A line starting with ! runs on this machine in the current directory via your shell and prints inline: "$ <command>", the output, then "exit N". It is never sent to Quox, starts no turn and needs no approval, because it is your own command. !quox <args> (or !q <args>) runs THIS quox binary with your current profile, so !quox inbox list works as you; bare quox and tui need the terminal and are refused. Ctrl+C stops it (SIGTERM, then SIGKILL after 2 s) without quitting; one command at a time; stdin is closed, so a command that waits for input gets end-of-file; 120 s limit (QUOX_BANG_TIMEOUT_MS); output is capped at 2000 lines / 256 KB. !! is reserved. Only the command line and exit code go in the dev transcript, never the output.' },
188
+ { id: 'share', kind: 'command', group: 'Talk', usage: '/share [N]', desc: 'attach the output of your last ! command (or the Nth-last) to your next message', status: 'active',
189
+ detail: 'Command output is not shared with Quox by default, because it can contain secrets. /share attaches the latest ! result to your next message (up to 16 KB, and it says when it was cut); /share 2 picks the one before. A line "shared with Quox: output of `<command>`" appears when it is sent.' },
45
190
  { id: 'copy', kind: 'command', group: 'Conversation', usage: '/copy [N|code]', desc: 'copy the last answer (raw markdown) to the clipboard; N = Nth-last, code = last code block', status: 'active',
46
191
  detail: 'Uses the OSC 52 terminal escape (works over SSH in most terminals; wrapped for tmux). Needs a terminal that allows OSC 52. Mouse selection is untouched: select and copy with your terminal as usual.' },
47
- { id: 'eye', kind: 'command', group: 'Conversation', usage: '/eye', desc: 'the Q opens its eye and blinks (the Dragon\'s Eye)', status: 'active',
48
- detail: 'A small Easter egg. Under reduced motion (QUOX_REDUCED_MOTION=1) it shows a still open eye instead.' },
192
+ { id: 'dragon', kind: 'command', group: 'Conversation', usage: '/dragon [explain|ask [question]|sleep|fly|hero|fire|classic|companion on|greeting|off|status]', desc: 'the Q comes alive; /dragon explain gives the simplest possible explanation of the last output, /dragon ask [question] answers one question just as simply, /dragon sleep is a katakana screensaver (any key wakes)', status: 'active', args: ARGS.dragon,
193
+ detail: 'Plain /dragon plays the dragon (googly eyes, a talking smile, tail sway and whip, then fire from its mouth); classic and hero are the same show, /dragon fire is the breath alone. explain and ask are ordinary turns on whichever AI you have selected (tools and approvals still apply); the answer stays in the conversation as "Dragon explains" or "Dragon answers". With no AI connected you get the usual connect card. /dragon ask with no question waits for your next line. /dragon sleep hides the screen under katakana with the dragon asleep; any key wakes it and is not typed, an approval wakes it, Ctrl+C quits. It only changes what is drawn, never the conversation. Quiet motion or NO_COLOR shows a still screen.' },
194
+ { id: 'dragon-fly', kind: 'command', group: 'Conversation', usage: '/dragon-fly', desc: 'the dragon flies across the screen, wings beating (same as /dragon fly)', status: 'active',
195
+ detail: 'One 9 second pass of the dragon in flight, wings beating; Esc stops it early. It only changes what is drawn, never the conversation. Quiet motion or NO_COLOR shows a single still frame instead.' },
49
196
  { id: 'sound', kind: 'command', group: 'Conversation', usage: '/sound [profile|test]', desc: 'earcons for approvals, failures and long turns: Silent | Minimal | Operational | Full', status: 'active',
50
197
  detail: 'Same setting as the dashboard (your account\'s soundProfile). Minimal (default) sounds for approvals, critical alerts and failures; Operational adds a short tick when a turn over 20 s finishes or the terminal is in the background. Over SSH only the terminal bell rings, for approvals and critical alerts. QUOX_SOUND=off mutes this terminal; --print never makes sound.' },
198
+ { id: 'mouse', kind: 'command', new: true, group: 'Conversation', usage: '/mouse [on|off]', desc: 'clicks: a ◆ sender to reply, a card\'s y / n / c to answer, select text in your draft (off by default)', status: 'active',
199
+ detail: 'Saved for this profile. On: click a ◆ sender line to start /say to that slot, click y approve, n deny or c chat on a card (or y/N/c on an ask) to answer exactly as the key would, and click, drag or double-click in your draft to place the cursor or select (Backspace deletes, typing replaces). A click never runs a command: a run request still needs y or typing run. While it is on the terminal sends clicks and the wheel to Quox, so hold Shift (Option on macOS) to select or copy text, and Shift+wheel or the scrollbar to scroll back.' },
200
+ { id: 'tip', kind: 'command', group: 'Conversation', usage: '/tip', desc: 'a "did you know" tip about something Quox can do on this install', status: 'active',
201
+ detail: 'Shows one tip at a time and rotates through all of them before repeating. Only features that are shipped (stable or beta) and ready on this server are offered, and things you already use are skipped. One tip also shows under the greeting on launch. /tip-settings off stops them.' },
202
+ { id: 'tips', kind: 'command', group: 'Conversation', usage: '/tips [topic or words]', desc: 'search the tips: /tips social media, /tips integrations; alone lists the topics', status: 'active', args: ARGS.tips,
203
+ detail: 'Bare /tips lists the topics with how many tips each has. /tips <words> ranks tips by topic, title, text and command and shows up to 5; add "more" at the end for the rest. It respects /tip-settings: tips your setting hides are counted, not shown. /tip still gives one random tip.' },
204
+ { id: 'tip-settings', kind: 'command', group: 'Conversation', usage: '/tip-settings [off|core|all]', desc: 'tips: off = none, core = free features only, all = everything shipped (default); saved on this profile', status: 'active', args: ARGS['tip-settings'],
205
+ detail: 'Alone shows the current setting. Saved to your CLI profile, so it survives restarts. core limits tips to the free tier; all includes plugin features your install can use.' },
206
+ { id: 'whatsnew', kind: 'command', group: 'Conversation', usage: '/whatsnew [all|on|off]', desc: 'new commands since you last looked, plus release notes; on/off controls the startup card', status: 'active', args: ARGS.whatsnew,
207
+ detail: 'Quox shows a short "What\'s new" box once when you start a newer version than last time. /whatsnew lists the latest 10 entries with a command to try; /whatsnew all lists every entry.' },
208
+ { id: 'welcome', kind: 'command', group: 'Conversation', usage: '/welcome', desc: 'Show the welcome screen again', status: 'active' },
51
209
  { id: 'about', kind: 'command', group: 'Conversation', usage: '/about', desc: 'who we are, terms, privacy, licence and docs links', status: 'active' },
52
210
  { id: 'quit', kind: 'command', group: 'Conversation', usage: '/quit', desc: 'leave; finished work is kept and /resume picks the conversation up', status: 'active', aliases: ['exit'],
53
211
  detail: 'Idle: leaves at once; the conversation stays on the server for /resume (or quox --resume). During a turn it asks first, because leaving disconnects the turn and the server stops it before its next step (a tool already running finishes and is recorded). /quit again within 5 s leaves anyway.' },
54
- { id: 'restart', kind: 'command', group: 'Conversation', usage: '/restart', desc: 'restart into the latest installed quox and resume this conversation', status: 'active',
55
- detail: 'Re-runs the quox on your PATH in this terminal with the same arguments and profile, plus --resume for this conversation. During a turn it asks first: restarting disconnects the turn and the server stops it before its next step (this server cannot reattach a running turn yet); /restart again within 5 s goes ahead. When a newer build is installed than the one running, a dim line says so (checked at most once a minute, no network).' },
212
+ { id: 'restart', kind: 'command', group: 'Conversation', usage: '/restart [auto [on|off]]', desc: 'restart into the latest installed quox and resume this conversation', status: 'active',
213
+ detail: 'Re-runs the quox on your PATH in this terminal with the same arguments and profile, plus --resume for this conversation. During a turn it asks first: restarting disconnects the turn and the server stops it before its next step (this server cannot reattach a running turn yet); /restart again within 5 s goes ahead. When a newer build is installed than the one running, a dim line says so (checked at most once a minute, no network). /restart auto on restarts for you when that happens and you are idle (5 s countdown, any key cancels); bare /restart auto toggles. Saved per profile, off by default.' },
214
+ { id: 'update', kind: 'command', new: true, group: 'Conversation', usage: '/update', desc: 'update quox to the latest version, then restart', status: 'active',
215
+ detail: 'Checks how this quox was installed (npm, a git checkout, or this box\'s shared CLI tree) and the latest version, then shows what is new and asks y/N/c. y hands the terminal to the update (so npm prompts or sudo work), shows its exit status and, on success, restarts into this conversation like /restart (attach and slot carry over). A failed update keeps you in this conversation and says the command to run yourself. A git checkout updates only when clean and on main (git pull --ff-only, never stash or reset); this box\'s shared tree runs quox-cli-refresh, which updates quox for every session here. A self-hosted install updates with quox server update. Same as quox update in a shell.' },
216
+ { id: 'demo', kind: 'command', group: 'Conversation', usage: '/demo', desc: 'play the simulated demo journey on its own (same as quox --demo --autoplay); nothing real runs', status: 'active',
217
+ detail: 'Hands the terminal to the autoplay demo: a scripted journey with a simulated server, no input needed, nothing real runs and nothing is sent. Quit it to come back to your normal client; this conversation stays on the server for /resume. During a turn it asks first, like /restart.' },
218
+ { id: 'feedback', kind: 'command', new: true, group: 'Conversation', usage: '/feedback <text>', desc: 'tell the Quox team something: sent to the quox.ai feedback API after you see it and say y', needs: ['feedback'], status: 'active',
219
+ detail: '/feedback <text> shows exactly what will be sent (type feedback, your text, and the quox CLI version; no name, email, org or token) to https://api.quox.ai/api/feedback, the Quox team rather than your own Quox server, and asks y/N. y sends it; anything else leaves it unsent. The Quox site takes 5 a hour per address. /gripe is different: it files a GitHub issue through your org.' },
56
220
  { id: 'gripe', kind: 'command', group: 'Conversation', usage: '/gripe <text>', desc: 'file a GitHub issue through the GRIPE agent; shows it first, /gripe! files at once', needs: ['gripe'], status: 'active', aliases: ['gripe!'],
57
221
  detail: 'The same governed path as `quox gripe`: the GRIPE agent files the issue through your org\'s GitHub connector (default repo quoxai/quox). /gripe <text> shows what would be filed and asks; /gripe! (alone, within 2 minutes) files it, or /gripe! <text> files straight away. The issue link is shown when one comes back; with no link it says so and treats it as not filed.' },
58
- { id: 'sessions', kind: 'command', group: 'Conversation', usage: '/sessions [all]', desc: 'your recent conversations; /sessions all shows everything live in your org', needs: ['conversations'], status: 'active',
59
- detail: '/sessions all is the same overview as `quox sessions`: every live tmux session, conversation, held slot and running objective, loop or workflow in your org, grouped by agent, with how to attach. Bare /sessions lists recent conversations from the server. Names set with /rename are kept on this machine (~/.config/quox/sessions.json) because the server has no rename route yet.' },
222
+ { id: 'sessions', kind: 'command', group: 'Conversation', usage: '/sessions [all|refs]', desc: 'everything live in your org by name and where it runs; refs adds ids, all adds every chat', needs: ['sessionsIndex'], status: 'active',
223
+ detail: 'Each row leads with the session name and where it runs (host, or "host unknown" when the server does not know), then how long it has been up and when it was last active. Sections: terminal sessions, slots, work (objectives, loops, workflows) and chats. /sessions refs adds ids and attach commands under each row; /sessions all includes every chat from the last 24 h. The same data as `quox sessions`. Names set with /rename are kept on this machine (~/.config/quox/sessions.json).' },
224
+ { id: 'active', kind: 'command', new: true, group: 'Conversation', usage: '/active [sessions|slots|agents]', desc: 'who is doing what: one line per active worker, grouped working / waiting on you / idle (same as `quox active`)', needs: ['sessionsIndex'], status: 'active',
225
+ detail: 'One short summary over the live session index: each line is a name, what it is doing (its state reason, slot or agent, never invented), its state (working, waiting on you, idle Nm, asleep) and since when. Top 10 per group; /active all shows every one. /active sessions, /active slots and /active agents show one kind. A source the server could not read is named in one line. The same data as `quox active`.' },
226
+ { id: 'dashboard', kind: 'command', group: 'Conversation', usage: '/dashboard [page]', desc: 'open the web dashboard already signed in (one-time link); pages: sessions approvals agents lucid inbox seo settings', needs: ['dashboardLink'], status: 'active',
227
+ detail: 'Creates a sign-in link for this profile that is valid for 10 minutes and works once, then opens it in your default browser. Over SSH it only prints the link (the browser would open on the wrong machine); terminals that support hyperlinks make it clickable. The link is a credential: it is shown on screen only and never written to the dev transcript or history. An older server without one-time links gets the plain dashboard URL and you sign in as usual. Also: quox dashboard [page].' },
60
228
  { id: 'rename', kind: 'command', group: 'Conversation', usage: '/rename [name]', desc: 'name this session (shown in the top rule); alone shows the current name', status: 'active',
61
229
  detail: 'Also settable at launch with quox -n <name>. The name is kept on this machine, keyed by conversation, and shown in /sessions.' },
62
- { id: 'slot', kind: 'command', group: 'Conversation', usage: '/slot <name>', desc: 'give this session a slot address (checks out the slot)', needs: ['checkoutSlot'], cap: 'slots', status: 'active',
63
- detail: 'Checks out the slot, creating it if needed, as `quox slot checkout <name> --create --session <session>` does; typing it is your consent. Discord replies are not relayed into this window yet (no slot wait loop here).' },
64
- { id: 'attach', kind: 'command', group: 'Conversation', usage: '/attach [agent]', desc: 'send your turns to a specific agent; alone shows the current one', needs: ['agents'], cap: 'agents', status: 'active',
230
+ { id: 'slot', kind: 'command', group: 'Conversation', usage: '/slot <name>', desc: 'give this session a slot address (checks out the slot)', needs: ['checkoutSlot'], cap: 'slots', status: 'active', args: ARGS.slot,
231
+ detail: 'Checks out the slot, creating it if needed, as `quox slot checkout <name> --create --session <session>` does; typing it is your consent. While you hold it, every message to the slot (other windows, Discord, Matrix) shows here live, above the composer. /slot log [n] shows the last n messages. A start in the same folder offers to reattach the slot you last held. /config sets a bell for incoming lines.' },
232
+ { id: 'say', kind: 'command', group: 'Conversation', usage: '/say <slot>[,<slot>...] <text>', desc: 'message one or more slots directly (no model turn); a,b,c sends each a copy naming everyone it went to', needs: ['slotSay'], cap: 'slots', status: 'active', aliases: ['ask'], args: ARGS.say,
233
+ detail: 'Posts straight into the slot, signed as this window (your slot and name), and prints the line with ✔ once it is in the slot. Starting a line with @<slot> does the same.' },
234
+ { id: 'join', kind: 'command', new: true, group: 'Conversation', usage: '/join <slot>', desc: 'join a slot as a group room (read and post without holding it)', needs: ['slotSay'], cap: 'slots', status: 'active', args: ARGS.say,
235
+ detail: 'Subscribes this window to the slot\'s live stream; the last ~10 messages show first. Holding (/slot) stays separate and exclusive. Post with #<slot> <text> or /say; #<slot> alone makes it the default target (a bare # undoes it). /<slot> join, /<slot> leave and /join #<slot> work too. Your lines are signed with this session\'s name and person, never as a human profile.' },
236
+ { id: 'leave', kind: 'command', group: 'Conversation', usage: '/leave <slot>', desc: 'leave a slot room you joined (does not release a hold)', needs: ['slotSay'], cap: 'slots', status: 'active', args: ARGS.say },
237
+ { id: 'rooms', kind: 'command', new: true, group: 'Conversation', usage: '/rooms', desc: 'rooms (groups) this window has joined, with unread counts; also on the status line', needs: ['slotSay'], cap: 'slots', status: 'active', aliases: ['groups'] },
238
+ { id: 'broadcast', kind: 'command', new: true, group: 'Conversation', usage: '/broadcast [--slots a,b] [--priority urgent] [--ack] <text>', desc: 'tell every held slot in this org at once, signed as you', needs: ['slotBroadcast'], cap: 'slots', status: 'active', aliases: ['roar'], args: ARGS.broadcast,
239
+ detail: 'One server call delivers to every slot held in this org (never another org), or only --slots a,b. --priority urgent marks it red for recipients. --ack asks them to reply /ack <id>; you then see "Broadcast delivery: delivered N / acknowledged M / awaiting K" update. You get a framed card: how many sessions have it (stored in the slot, listening sessions see it at once) and a separate chat-rooms count (sent / sending / failed). /broadcast status <id> redraws it. Typing it is your consent. Not yet: --at (schedule), --fleet, --org, --voice.' },
240
+ { id: 'page', kind: 'command', new: true, group: 'Conversation', usage: '/page <slot> <text>', desc: 'urgent message to one slot, acknowledgement requested', needs: ['slotBroadcast'], cap: 'slots', status: 'active', args: ARGS.say,
241
+ detail: 'Like /broadcast to a single slot with --priority urgent --ack: it shows red there, asks them to /ack, and you see when they have.' },
242
+ { id: 'ack', kind: 'command', new: true, group: 'Conversation', usage: '/ack <broadcast id>', desc: 'acknowledge a broadcast, as the slot this window holds', needs: ['slotAck'], cap: 'slots', status: 'active',
243
+ detail: 'The id is in the broadcast line ("ack with /ack bc_...").' },
244
+ { id: 'send', kind: 'command', group: 'Conversation', usage: '/send <slot> <file>', desc: 'send a local text file into a slot (64 KB cap)', needs: ['slotSay'], cap: 'slots', status: 'active', args: ARGS.send,
245
+ detail: 'Text files up to 64 KB go inline; the receiving window shows the name and the first lines, /slot log shows the rest. Secret-shaped values (keys, tokens, passwords) are found first: you are asked y/N, and a yes sends them redacted. Binary and larger files are refused (slots have no attachment store yet). Tab completes the path.' },
246
+ { id: 'attach', kind: 'command', group: 'Conversation', usage: '/attach [agent]', desc: 'send your turns to a specific agent; alone shows the current one', needs: ['agents'], cap: 'agents', status: 'active', aliases: ['summon'], args: ARGS.attach,
65
247
  detail: 'The agent name is checked against your org\'s agent list. Later turns go to that agent (the chat stream assistant field) until /detach.' },
66
248
  { id: 'detach', kind: 'command', group: 'Conversation', usage: '/detach', desc: 'back to CommanderQ', status: 'active' },
67
- { id: 'peek', kind: 'command', group: 'Conversation', usage: '/peek <id>', desc: 'look up an envelope, task or agent by id', status: 'planned' },
68
- { id: 'aside', kind: 'command', group: 'Conversation', usage: '/aside', desc: 'a read-only side question', status: 'planned' },
249
+ { id: 'peek', kind: 'command', new: true, group: 'Conversation', usage: '/peek <id>', desc: 'look up an envelope, inbox item, objective, run, WARD receipt or agent by id', status: 'active',
250
+ detail: 'Works out what the id is from its shape and asks only the read endpoints that fit: a ULID is tried as an envelope, inbox item, objective and workflow run at once; a UUID as a WARD receipt; a name as an agent. Shows a compact card and the quox command for the full record. Nothing found says what was tried and what each answered. Read only, in this window\'s org. Also: quox peek <id>.' },
251
+ { id: 'aside', kind: 'command', new: true, group: 'Conversation', usage: '/aside <question>', desc: 'a read-only side question: no tools, not added to this conversation', status: 'active',
252
+ detail: 'Asks the model with no tools at all and no conversation id, so it cannot change anything and the answer never enters this conversation\'s history: your next message carries on as if the aside never happened. The last few exchanges go along as read-only context, so you can ask about what was just said. The answer shows in its own violet block marked read only. Also: quox aside "<question>" (no context).' },
69
253
  // Connect
70
- { id: 'connect', kind: 'command', group: 'Connect', usage: '/connect [provider]', desc: 'see what is connected; /connect openai adds a key safely', needs: ['connections'], cap: 'vault', status: 'active',
254
+ { id: 'login', kind: 'command', group: 'Connect', usage: '/login', desc: 'sign in again without leaving quox (email, masked password, MFA code if asked)', status: 'active',
255
+ detail: 'Opens a card here: your email (prefilled with the last one this profile used), then a masked password, then your authenticator code if the server asks for one. It is the same sign-in as quox login, so status, approvals and the digest come back without a restart. The password is never echoed, logged or saved. CLI sign-ins last 30 days by default; an org session policy can set a shorter limit.' },
256
+ { id: 'profile', kind: 'command', new: true, group: 'Connect', usage: '/profile [name]', desc: 'switch profile or org without leaving: a chooser of your profiles and this login\'s orgs, for this window only', status: 'active',
257
+ detail: '/profile alone names this window\'s profile and opens a chooser: the signed-in profiles (this window\'s marked *), profiles on this machine that are not signed in, then this login\'s orgs. Up/Down and Enter switch; an org asks y/N first, like /org. Every switch is for this window only: the shared default profile and org in your quox config stay as they were, so other windows and scripts are untouched. /profile <name> also switches straight away. /profile <name> switches this window to that profile in place (or restarts into it carrying the conversation, attach, slot and org). /login <name> does the same when that profile is already signed in. A profile that is not signed in gets a run card for quox login -p <name>. An agent-login window that hits approvals or a step-up offers the switch as a y/N card; it never switches on its own.' },
258
+ { id: 'connect', kind: 'command', group: 'Connect', usage: '/connect [provider]', desc: 'see what is connected; /connect openai adds a key safely', needs: ['connections'], cap: 'vault', status: 'active', args: ARGS.connect,
71
259
  detail: 'Alone: AI providers (OpenAI, Claude API, Ollama) and the integrations in your org vault, with real status from the server; anything it cannot read shows ? unknown. /connect openai (or anthropic) opens a secure card: paste the key, Enter tests it with the provider and saves it to your org vault as the default; Esc cancels. Keys typed there never reach chat, history or transcripts. A key pasted into the composer is never sent.' },
260
+ { id: 'hosts', kind: 'command', group: 'Connect', usage: '/hosts add', desc: 'add a host to your fleet (works without an AI); /hosts lists them', status: 'active', args: ARGS.hosts,
261
+ detail: '/hosts add prints the one command to run on the machine you want to add (the QuoxAgent installer served by your Quox, with a --host-id of your choice). /hosts lists the hosts your Quox knows, the same as quox fleet hosts. Neither needs an AI connected.' },
72
262
  { id: 'capabilities', kind: 'command', group: 'Connect', usage: '/capabilities [name]', desc: 'what this Quox can do here: ready, needs setup, not available, planned', status: 'active',
73
263
  detail: 'Asks the server (GET /api/v1/capabilities), which checks real service health, your plan, plugin install state and connected providers. Each gap says why and how to set it up; /capabilities <name> gives the one-line offer for one. If the server cannot answer, nothing is assumed.' },
74
264
  // Verbose
75
- { id: 'verbose', kind: 'command', group: 'Verbose', usage: '/verbose off|on|full', desc: 'protocol lines: off = receipts only, on = compact, full = adds thinking', status: 'active',
76
- detail: 'on (default) shows compact AEE / AOCL / WARD lines as they happen. off collapses to the receipt line. full adds the thinking stream.' },
265
+ { id: 'verbose', kind: 'command', group: 'Verbose', usage: '/verbose off|on|full', desc: 'protocol lines (AEE envelopes, VOLT tool offers, WARD): off (default) hides them, on = compact, full = adds thinking; saved', status: 'active',
266
+ detail: 'off (default) keeps the screen to your conversation, the tool result lines and ONE AOCL footer per turn (layers, tool count, time). on shows the compact AEE / VOLT / AOCL / WARD protocol lines as they happen. full adds the thinking stream. Saved to your profile. The full list of tools offered to the model for the last turn is in /aocl.' },
77
267
  { id: 'proof', kind: 'command', group: 'Verbose', usage: '/proof [envelope]', desc: 'the evidence chain for the last tool call: AEE, AOCL, tool, WARD', server: true, cap: 'receipts', status: 'active',
78
268
  detail: 'Each link is read back from the server and drawn confirmed only when the read says so. WARD is the server-verified inclusion receipt; it can be slow on a large chain and is bounded, so a link may stay open.' },
269
+ { id: 'evidence', kind: 'command', new: true, group: 'Verbose', usage: '/evidence', desc: 'signed evidence bundle for this conversation', server: true, cap: 'receipts', status: 'active', aliases: ['envelope', 'envelopes', 'aee', 'volt', 'ward'],
270
+ detail: 'The same export as `quox evidence bundle`: a zip with envelopes.json/csv, ward-receipts.json, volt.json, a manifest of file hashes and an Ed25519 signature, scoped to your org and this conversation. It prints the path, the envelope count, the receipt count and the command to check it offline (`quox evidence verify <file>`). /ward does the same and lists the WARD receipts; /envelope, /envelopes and /aee show the envelope ids (same as /proof); /volt points at the VOLT run inside the bundle.' },
271
+ { id: 'prove', kind: 'command', new: true, group: 'Verbose', usage: '/prove', desc: 'independently check that the last turn\'s tool calls really did what they said: receipt chain plus a read-back of the effect', server: true, cap: 'receipts', status: 'active',
272
+ detail: 'For each tool call of the last turn: the receipt chain is re-read from the server (as in /proof), and where the tool is a write we know how to read back (today: slot_create, memory_save) the effect is read from the system of record. ✔ proven = the chain agrees and the effect is there (or it was a read and the chain agrees). ⚠ unproven = it cannot be shown from here (no read mapping for that write, a read failed, no receipt). ✖ contradicts = the call failed, the server recorded a failure, or the effect is not there. Nothing is assumed: an unmapped write is never called proven. /proof is unchanged and shows one call\'s chain.' },
273
+ { id: 'replay', kind: 'command', new: true, group: 'Verbose', usage: '/replay [run or conversation id] [page]', desc: 'rebuild an earlier run from the audit trail: decisions, tools with args, outputs and receipts, in order, paged', server: true, cap: 'receipts', status: 'active', args: ARGS.replay,
274
+ detail: 'Alone: the last turn of this conversation (your question, each tool with its args summary, output and receipt id, then the answer). With a conversation id: its saved messages (the server does not link tool envelopes to a conversation). With an envelope or correlation id: that run\'s tool calls and guard decisions straight from the AEE audit trail. Six steps a page; add a page number for the next. Read-only: it reconstructs what was recorded and never re-executes anything, and sandbox re-execution is not available.' },
275
+ { id: 'aocl', kind: 'command', group: 'Verbose', usage: '/aocl', desc: 'every AOCL layer of the last turn: outcome, time, reason', status: 'active',
276
+ detail: 'Each turn shows ONE AOCL line (layers accounted, passed, bypassed, time; red with the layer name on a reject). /aocl expands the last turn into all 11 layers, L0 ingress to L10 audit, as the server reported them.' },
79
277
  { id: 'reconcile', kind: 'command', group: 'Verbose', usage: '/reconcile', desc: 'check tool calls that ended with effect unknown against the server\'s record', server: true, cap: 'receipts', status: 'active' },
80
278
  // Awareness
81
279
  { id: 'roster', kind: 'command', group: 'Awareness', usage: '/roster [all]', desc: 'show or hide the live roster under the status line; all includes background work', needs: ['roster'], status: 'active',
82
280
  detail: 'The roster lists what needs you: pending approvals, then work you started (objectives, loops, manual workflows). Scheduled and event-driven workflows are hidden; background failures show once per workflow in the bar below. /roster all shows everything. Refreshed every 15 s from the server. Down on an empty composer focuses it; Enter peeks, s asks to stop (y confirms), Esc returns. A source the server cannot answer shows nothing and is named here.' },
281
+ { id: 'loop', kind: 'command', group: 'Awareness', usage: '/loop [list|watch <id>|peek <id>|focus]', desc: 'follow a loop live in a card that updates in place', server: true, status: 'active',
282
+ detail: 'A loop you start from chat gets a live card: the turn strip (✔ ok, ⚠ waiting or denied, ✖ error, ◐ running), the receipts counter, host and scope, and the last lines of what it is writing. Milestones and errors land as normal chat lines beneath it, and a finished loop prints one line with the proof command. /loop list shows loops, /loop watch <id> follows one that is already running (and gives it the keys), /loop peek <id> prints one snapshot, /loop focus gives the keys back. Keys: Enter expands the output, s asks to halt (y confirms, never immediate), Esc collapses and leaves. Under 60 columns the card is one line. Needs a server with the loop event routes; one without them says so.' },
83
283
  { id: 'workflow', kind: 'command', group: 'Skills', usage: '/workflow <name|id|run>', desc: 'the step flow of a workflow\'s latest run (or one run): state, timings, human gates', server: true, cap: 'workflows', status: 'active',
84
284
  detail: 'Steps come from the workflow definition and each state and duration from that run\'s recorded envelopes. A reached human gate shows amber. Enter on a workflow row in the roster shows the same flow.' },
85
285
  { id: 'crystal', kind: 'command', group: 'Skills', usage: '/crystal <workflow>', desc: 'is this workflow eligible to become a Crystal? checked against the real gate', server: true, cap: 'crystals', status: 'active',
86
- detail: 'Computed from the workflow\'s recorded runs with quoxflow\'s crystalliser rule: at least 3 runs, at least 80% successful, one action sequence. Says how many more successful runs are needed.' },
87
- { id: 'agents', kind: 'command', group: 'Awareness', usage: '/agents <name>', desc: 'an agent\'s card: role, tools, slot, last action and its WARD witness', server: true, cap: 'agents', status: 'active' },
286
+ detail: 'Computed from the workflow\'s recorded runs with quoxflow\'s crystalliser rule: at least 3 runs, at least 80% successful, one action sequence. Says how many more successful runs are needed. Related: /crystallize turns what you just did in this conversation into a new skill.' },
287
+ { id: 'undo', kind: 'command', group: 'Awareness', usage: '/undo [last]', desc: 'reverse the last thing Quox did, if Quox knows how (one card, y/n)', status: 'active', server: true, cap: 'receipts',
288
+ detail: 'Finds the last state-changing tool call of this conversation that has a receipt, reads the receipt back, says what it did and what reversing it means, and on y runs the registered inverse and prints the outcome. Known inverses today: slot_checkout (release the slot), inbox_move (mail undo), notesq_create (delete the note). The target is bound from the call itself or from the server\'s own record of it, and only when exactly one record fits; otherwise nothing runs and you get the by-hand route. Anything else says plainly that Quox can\'t reverse it automatically. Ctrl+_ still undoes composer typing.' },
289
+ { id: 'wish', kind: 'command', group: 'Skills', usage: '/wish <description>', desc: 'describe something Quox cannot do yet; Quox drafts a governed spec, you decide (one card, y/n)', status: 'active',
290
+ detail: 'Quox drafts a short spec through a normal turn: what it does, which existing tools or APIs it would use, the hat and permissions, what tests prove it, and which shape fits (skill, workflow, power or code). On y, a skill is created through skill_manage (experimental, queued for human review) and a workflow through the workflow tool, inactive; a power or anything that needs code is filed as a feature request with the spec. Nothing installs code: that goes through the normal build and review path.' },
291
+ { id: 'agents', kind: 'command', group: 'Awareness', usage: '/agents [list [text] | <name> [tools]]', desc: 'pick an agent from the list, or show one\'s card: role, tools, slot, last action and its WARD witness', server: true, cap: 'agents', status: 'active', aliases: ['agent'], args: ARGS.agents,
292
+ detail: '/agents or /agents list opens a picker (name, role, status); Enter shows that agent\'s card; /agents list sec narrows it. /agents <name> shows the card, /agents <name> tools lists the tools it may use. /agent is the same command.' },
293
+ { id: 'which', kind: 'command', new: true, group: 'Context', usage: '/which [profile|org|agent|hat|model|slot|conversation]', desc: 'what this window is using right now: login, org, agent, hat, model, slot, conversation', status: 'active', args: ARGS.which,
294
+ detail: 'Alone prints one block; with a word prints just that line. Reads what this window already holds (the hat shows after your first turn), no model call.' },
295
+ { id: 'crystallize', kind: 'command', group: 'Skills', usage: '/crystallize [what]', desc: 'turn what was just accomplished into a permanent reusable skill (rules, tools, tests)', status: 'active',
296
+ detail: 'One model call drafts a skill from the conversation: rules, the tools used, and tests. The server skill firewall checks it first, then one preview card; y saves it to your skills and prints /skills <name>. Related: /crystal checks whether an existing workflow is eligible to become a Crystal; /crystallize writes a new skill from this conversation.' },
297
+ { id: 'automate', kind: 'command', group: 'Skills', usage: '/automate [task]', desc: 'turn this conversation (or a task you name) into a persistent workflow', status: 'active',
298
+ detail: 'One model call drafts a workflow from the conversation: trigger, schedule, steps, approval gates and failure handling. You get one preview card; y creates it in the workflow store and activates its trigger, then prints the id and /workflow <id>. Nothing is created before y.' },
299
+ { id: 'watch', kind: 'command', new: true, group: 'Awareness', usage: '/watch <what> [for <condition>] | list | stop <id>', desc: 'turn a site, repo, server, page, metric or condition into an ongoing observer', status: 'active',
300
+ detail: '/watch quox.ai for downtime, /watch gitea health, /watch competitor pricing page https://... Shows one preview card (what, how often, what triggers it, where it notifies); y creates a scheduled agent check (default every 15 min, min 5) that stays quiet until the condition is met and then raises it in your Approvals inbox. /watch list shows your watches, /watch stop <id> ends one.' },
301
+ { id: 'fleet', kind: 'command', new: true, group: 'Awareness', usage: '/fleet [host]', desc: 'computers, agents, missions and SubFleets in one block, with health; /fleet <host> shows one', server: true, cap: 'fleet', status: 'active', aliases: ['soar'], args: ARGS.fleet,
302
+ detail: 'Read-only, no model call. Computers merge the QuoxAgent heartbeats with the Bastion host registry (the same reads as quox fleet agents and quox fleet hosts) and count healthy, degraded, unhealthy and offline, naming anything that is not healthy. Agents are your org agents, missions are running or waiting objectives and loops. SubFleets are designed but have no API in this Quox yet, so that row says so. A source that cannot be read is named on the "Can\'t see" line, never shown as zero. Moving work is not a command: ask in plain words.' },
88
303
  { id: 'memory', kind: 'command', group: 'Awareness', usage: '/memory <query>', desc: 'facts Quox remembers, each with where it came from, age, trust and conflicts', server: true, cap: 'memory', status: 'active' },
89
- { id: 'observers', kind: 'command', group: 'Awareness', usage: '/observers', desc: 'LUCID situation, what changed since you last looked, what Quox records about its own learning', needs: ['observers'], cap: 'lucid', status: 'active',
304
+ { id: 'sitrep', kind: 'command', group: 'Awareness', usage: '/sitrep', desc: 'the situation now: approvals waiting, what needs you, running work, what Quox cannot see', server: true, cap: 'lucid', status: 'active',
305
+ detail: 'Read from the server, no model call: approvals waiting (your Approvals inbox), what needs you from the LUCID situation (by urgency), work in motion, and the sources LUCID cannot see (silent, failing or not connected). A part the server cannot answer is named on the "Can\'t see" line, never shown as empty. Does not move your LUCID cursor. /away is the "since you were last here" digest. Asking "give me a sitrep" suggests it.' },
306
+ { id: 'away', kind: 'command', group: 'Awareness', usage: '/away [since]', desc: 'what happened while you were away, and what Quox cannot see', server: true, cap: 'lucid', status: 'active',
307
+ detail: 'A factual digest from the server since you last used quox on this profile (or since 90m, 3h, 2d, or an ISO time): approvals waiting and resolved, objectives and workflows finished or failed, LUCID alerts, new Qortex decisions, failed turns and agent activity, each with its source and time. Sources that could not be read or went silent are named on a "Can\'t see" line. Nothing is summarised by a model. At launch, after more than 2 h away, a short card shows the top 5. Asking "what did I miss" suggests it.' },
308
+ { id: 'vm', kind: 'command', group: 'Awareness', usage: '/vm <verb> <target> [args]', desc: 'Proxmox VMs via the HYPERION agent: list, info, doctor, start|stop|reboot|shutdown, snapshot, rehearse, pool', server: true, status: 'active',
309
+ detail: 'Same verbs as quox vm. /vm list [--node n] [--type qemu|lxc]; /vm info <name|vmid>; /vm doctor <name|vmid> (why is it slow or unhealthy: verdict and ranked findings with evidence and a suggested fix, read only); /vm start|stop|reboot|shutdown <name|vmid>; /vm snapshot <name|vmid> [--name s]; /vm rehearse <name|vmid> [--check url:..|unit:..|cmd:..] [--keep-twin] [--no-wait] -- <command> (runs the command on a network-isolated twin and waits for pass/fail, minutes); /vm pool status|lease|done <vmid>|config. Every action goes through the governed tool route, so a held call prints its approval id and quox approvals result <id> to read the outcome; it is never re-submitted. Names match exactly; an ambiguous name lists the candidates.' },
310
+ { id: 'hyperion', kind: 'command', new: true, group: 'Awareness', usage: '/hyperion', desc: 'which VMs HYPERION has out and who holds each one, one line per VM', server: true, status: 'active',
311
+ detail: 'Read-only, no model call. One line per held VM: vmid, name, node, status, who holds it (pool lease or standing-grant lane), purpose and expiry or age. Same data as quox hyperion, which also shows node load, pool counts and the last 5 operations; quox hyperion deck is the live full-screen view. A VM nobody holds is not listed; a part the server cannot read is named, never shown as empty.' },
312
+ { id: 'observers', kind: 'command', group: 'Awareness', usage: '/observers', desc: 'LUCID situation, what changed since you last looked, what Quox records about its own learning', needs: ['observers'], cap: 'lucid', status: 'active', aliases: ['observer', 'lucid'],
90
313
  detail: 'Facts only, read from persisted data: the LUCID situation (without moving your cursor), recent changes, lessons taught, and the reflect / learn / insight counters. Nothing is summarised by a model.' },
314
+ { id: 'mind', kind: 'command', new: true, group: 'Awareness', usage: '/mind [implant]', desc: 'QuoxMind cognitive implants: status and last activity; /mind <id> for one', needs: ['mind'], status: 'active',
315
+ detail: 'Read-only, no model call. /mind lists the 14 cognitive implants (sense, repobrain, memory, temporal, focus, value, guard, qortex, insight, executive, predict, reflect, dream, learn) with status and last activity; /mind predict shows one. An implant with nothing recorded says "no activity recorded". Same data as quox mind implants and quox mind implant <id>.' },
91
316
  // Context
317
+ { id: 'hat', kind: 'command', new: true, group: 'Context', usage: '/hat [name|new|edit <hat>|delete <hat>]', desc: 'list the hats, change hat for this conversation, or make, edit and delete hats of your own', needs: ['hats', 'bindHat'], status: 'active', args: ARGS.hat,
318
+ detail: '/hat lists the org hats with the one this conversation wears marked. /hat <name> takes the key or the name you see (/hat DevOps, /hat the devops hat, or a unique start of it) and switches this conversation now: you typed it, so there is no approval card. A privileged hat asks y/N first (or /hat yes). The switch is recorded on the server (quoxhat.binding.selected) and your next message runs on the new hat. Same as approving an agent\'s hat proposal, without asking a model. Custom hats: /hat new walks you through a hat of your own (name, a starting hat, the agents it keeps and adds); a custom hat offers a turn only its own tools and agents. /hat edit <hat> keeps or drops agents, adds some, and can rename it; /hat delete <hat> removes it and sends conversations wearing it back to the default hat. Built-in hats are not edited or deleted here. Esc cancels a walk and nothing is sent.' },
319
+ { id: 'org', kind: 'command', group: 'Context', usage: '/org [switch <name|id> | back]', desc: 'list your orgs (current marked), switch this session to another one, or go back to the last one', needs: ['orgs'], status: 'active', args: ARGS.org,
320
+ detail: '/org lists the orgs your login belongs to. /org switch <name or id> matches loosely, asks y/N (or /org yes), then re-scopes the login (the same exchange as quox org switch) and starts a new conversation in that org; the status line shows the org. /org back (or /org -) returns to the org you were in before, with the same single y/N. A switch only succeeds for orgs you are a member of; the server decides.' },
92
321
  { id: 'statusline', kind: 'key', group: 'Context', usage: 'status line', desc: 'profile · org · agent · model · mode, under the composer', status: 'active' },
93
- { id: 'status', kind: 'command', group: 'Context', usage: '/status', desc: 'profile, org, server and its health, model, agent, slot, session, version', status: 'active',
322
+ { id: 'r', kind: 'command', new: true, group: 'Conversation', usage: '/r <text>', desc: 'reply to whoever last messaged you (slot or room); also /reply', needs: ['slotSay'], cap: 'slots', status: 'active', aliases: ['reply'] },
323
+ { id: 'poke', kind: 'command', new: true, group: 'Conversation', usage: '/poke <slot or session> [text]', desc: 'wake a session: an urgent page asking it to acknowledge (also /wake, /nudge, /ping)', needs: ['slotBroadcast'], cap: 'slots', status: 'active', aliases: ['wake', 'nudge', 'ping', 'slap', 'awaken'] },
324
+ { id: 'afk', kind: 'command', new: true, group: 'Context', usage: '/afk [note]', desc: 'tell sessions you are away (they stop waiting on approvals, which still queue); /back then shows everything since, grouped', status: 'active',
325
+ detail: 'Sets your presence on the server (per user and org): sessions saying into hitl, triage-q or this window\'s slot see "owner away since 15:54 UTC (note): approvals will queue"; `quox presence`, `quox slot list`, quox ask and agents see it too. Nothing is approved while you are away. /back or any typed message marks you present.' },
326
+ { id: 'back', kind: 'command', new: true, group: 'Context', usage: '/back', desc: 'mark yourself present and show what happened since /afk (same as /away)', status: 'active' },
327
+ { id: 'huh', kind: 'command', new: true, group: 'Conversation', usage: '/huh [question]', desc: 'explain the last output in plain words, using one turn on your model (same as /dragon explain)', status: 'active' },
328
+ { id: 'sleep', kind: 'command', new: true, group: 'Conversation', usage: '/sleep', desc: 'tell other sessions you are away sleeping so they stop waiting on your approvals (they still queue), then the dragon curls up (same as /dragon sleep)', status: 'active' },
329
+ { id: 'mode', kind: 'command', new: true, group: 'Context', usage: '/mode [ask|plan]', desc: 'this window\'s mode: ask first (default) or plan only', status: 'active', args: ARGS.mode,
330
+ detail: 'Alone it shows the current mode and the options. /mode plan: Quox investigates with read tools and proposes the steps and the exact commands, and runs nothing that changes state; the server enforces it, and the answer ends with "Run this plan now? y/N/c" (y switches back to ask and sends the go-ahead). /mode ask goes back. For this window only: kept across /restart, never saved to your profile. Auto mode is not available yet: it needs standing grants (planned).' },
331
+ { id: 'devservers', kind: 'command', new: true, group: 'Connect', usage: '/devservers [name]', desc: 'your registered dev servers; /devservers <name> shows its go command', status: 'active' },
332
+ { id: 'slots', kind: 'command', new: true, group: 'Conversation', usage: '/slots [all]', desc: 'held slots: who holds them and where; free ones are summarised, /slots all lists them (same as /slot list)', needs: ['slots'], cap: 'slots', status: 'active' },
333
+ { id: 'mirror', kind: 'command', new: true, group: 'Context', usage: '/mirror [agents]', desc: 'the Mirror: what CommanderQ sees about this org right now', needs: ['mirror'], status: 'active',
334
+ detail: 'Read-only, no model call. The org-scoped Mirror the server builds from each service and injects into CommanderQ on every turn: identity, health, implants, capabilities, plugins, memory, codebase, and every value it could not read with the reason. /mirror agents lists the agents. For the QuoxMind implants alone use /mind.' },
335
+ { id: 'health', kind: 'command', new: true, group: 'Context', usage: '/health', desc: 'Core dashboard summary: services, computers, agents, approvals, missions, failures, licence (also /core)', status: 'active', server: true, aliases: ['core'],
336
+ detail: 'Read-only, no model call. The same server reads the Core dashboard overview uses: service health (GET /api/v1/health/aggregate), computers and agents (as /fleet), approvals and inbox (inbox stats), running objectives and loops, failed workflow runs in the last 24h, and the licence. A section that could not be read says "unavailable" and why, never zero. /status is the session panel; /collector-health still shows it.' },
337
+ { id: 'status', kind: 'command', group: 'Context', usage: '/status', desc: 'profile, org, server and its health, model, agent, slot, session, version', status: 'active', aliases: ['collector-health'],
94
338
  detail: 'The provider and model shown are what the server last reported running (turn.started), not what was asked for. Server health is checked live. Asking "what is my status" in words prints the same panel as a tip.' },
95
339
  { id: 'cost', kind: 'command', group: 'Context', usage: '/cost', desc: 'tokens and cost per model for this session, from real usage', status: 'active', aliases: ['usage'],
96
340
  detail: 'Counts only what the server reported on turn.finished (input, output and cache tokens, cost when the provider gives one). Cost stays blank when the server did not report it, and says so; nothing is estimated. Asking "what is this costing" prints the same panel.' },
97
- { id: 'context', kind: 'command', group: 'Context', usage: '/context', desc: 'model, conversation tokens, rules, skills, memory and the attached agent', status: 'active',
98
- detail: 'Conversation tokens are the sum the server reported for this conversation in this session. Skills and memory are real calls to your server; anything it cannot answer shows ? with the reason. Asking "how much context have we used" prints the same panel.' },
341
+ { id: 'context', kind: 'command', new: true, group: 'Context', usage: '/context [off|on]', desc: 'model, tokens, rules, skills, memory, agent, and what the terminal block sent last turn', status: 'active',
342
+ detail: 'Every message carries a small block (2 KB max, secrets masked) saying what this screen shows: your org, slot and model, the roster rows with their ids, waiting approvals, the last commands and their outcome, and the slash command names. /context shows what was sent last turn; /context off stops it for this session, /context on resumes. It goes to the same provider under the same AI-sharing consent as the chat itself. Conversation tokens are the sum the server reported for this conversation in this session. Skills and memory are real calls to your server; anything it cannot answer shows ? with the reason. Asking "how much context have we used" prints the same panel.' },
99
343
  { id: 'doctor', kind: 'command', group: 'Context', usage: '/doctor', desc: 'the quox doctor checks plus chat checks, as rows', status: 'active', needs: ['doctorChecks'],
100
344
  detail: 'Runs the same checks as `quox doctor` (services, auth token, CLI version) in this process, then chat checks: the chat routes answer, a provider is connected, the capability contract.' },
345
+ { id: 'server', kind: 'command', group: 'Context', usage: '/server', desc: 'your self-hosted install: version, health per service, disk, ports, last update', status: 'active', needs: ['serverStatus'],
346
+ detail: 'The same read-only panel as `quox server status`, for the install Getting started recorded (or the one the running containers belong to). Changing the server is a terminal verb, never a chat action: quox server update (backup first, rolls back on failure), quox server rollback, quox server uninstall, quox server doctor, quox server diag. Each takes --dry-run to show the plan first.' },
101
347
  { id: 'config', kind: 'command', group: 'Context', usage: '/config', desc: 'settings: theme, motion, verbose, model; saved on this profile', status: 'active', aliases: ['settings'],
102
348
  detail: 'An interactive card: Up/Down pick a setting, Left/Right or Space change it (saved at once to this CLI profile), Enter on Model opens /model, Esc closes. Theme replaces the env-only QUOX_THEME; if QUOX_THEME is set in your shell it still wins and the card says so.' },
103
- { id: 'profile', kind: 'command', group: 'Context', usage: '/profile', desc: 'switch profile or org without leaving', status: 'planned' },
104
- { id: 'model', kind: 'command', group: 'Context', usage: '/model [provider [model] [compact|full]|auto]', desc: 'see what is connected and choose the provider/model for your next turns', needs: ['connections'], status: 'active',
105
- detail: 'Alone: auto, OpenAI, Claude API and Ollama with real status from your org vault (the same source as /connect). /model openai, /model openai gpt-5.4-mini, /model anthropic, /model auto (server policy). The choice is saved for this profile (no keys) and sent with each turn; the server decides, and the status line shows what it actually ran. A provider that is not connected is refused with a /connect offer; if the server runs something else, you are told once. quox --provider openai does the same for one run. A trailing compact or full picks the prompt profile: compact is a short system prompt for small or local models (the default on Ollama, LM Studio and vLLM), full is the default elsewhere; --prompt-profile does the same for one --print run.' },
106
- { id: 'mentions', kind: 'key', group: 'Context', usage: '@mention', desc: 'complete agents, hosts, tasks and receipts', status: 'planned' }
349
+ { id: 'model', kind: 'command', group: 'Context', usage: '/model [provider [model] [compact|full]|auto|<n>|fav|default|list]', desc: 'choose the provider/model for your next turns: a chooser, numbered favourites, a saved default', needs: ['connections'], status: 'active', args: ARGS.model,
350
+ detail:
351
+ 'Alone: opens a chooser (arrow keys, Enter selects for this session). Favourites are ' +
352
+ 'listed first, numbered, then the live models of each CONNECTED provider (listed by ' +
353
+ 'the provider, never from a fixed list). In the chooser f toggles a favourite, d ' +
354
+ 'makes the highlighted model your default, Esc closes; current and default are ' +
355
+ 'marked. /model list prints what is connected with real status from your org vault ' +
356
+ '(the same source as /connect). /model openai, /model openai gpt-5.4-mini, /model ' +
357
+ 'anthropic, /model auto (server policy) pick directly. Favourites: /model fav lists ' +
358
+ 'them, /model fav add <provider> <model>, /model fav rm <n|model>, /model <n> or ' +
359
+ '/model fav <n> switch at once. Default: /model default shows it, /model default ' +
360
+ '<provider> <model> sets it, /model default clear removes it; new sessions start on ' +
361
+ 'it when its provider is connected (otherwise you are told once and the usual rules ' +
362
+ 'apply). A model id missing from the live list is kept with a warning, because new ' +
363
+ 'models appear before lists update. Favourites and the default are saved to your ' +
364
+ 'account on the server (your user preferences), so they follow you across machines; ' +
365
+ 'this profile keeps a copy for offline use. Same for every provider. The choice is ' +
366
+ 'sent with each turn; the server decides, and the status line shows what it actually ' +
367
+ 'ran. A provider that is not connected is refused with a /connect offer. quox ' +
368
+ '--provider openai does the same for one run. A trailing compact or full picks the ' +
369
+ 'prompt profile: compact is a short system prompt for small or local models (the ' +
370
+ 'default on Ollama, LM Studio and vLLM), full is the default elsewhere; --prompt-' +
371
+ 'profile does the same for one --print run.' },
372
+ { id: 'deck', ext: 'quoxdeck', kind: 'command', group: 'Context', usage: '/deck on|off|status', desc: 'QuoxDeck: an optional MIDI pad surface; pads open an approval or preselect an answer, you confirm here', status: 'active',
373
+ detail: 'Provided by the optional quoxdeck extension, not by core: npm i -g quoxdeck, then quox ext add quoxdeck (until then /deck says so). quoxon pushes approvals, health and loops to the deck at most once a second. A pad only opens an approval view or fills the composer with the answer; it never answers for you, and high risk still needs the step-up PIN. The setting is saved, so the deck comes back on at the next start.' },
374
+ { id: 'ext', kind: 'command', group: 'Context', usage: '/ext list|on|off <name>', desc: 'optional extensions (npm packages) that add surfaces to this window', status: 'active',
375
+ detail: 'Extensions load only when installed (npm i -g <pkg>) and listed (quox ext add <pkg>). /ext list shows each one and where it was found, on|off switches one for the next start and now. An extension can open an approval or preselect an answer; it cannot answer for you. Contract: cli/lib/quoxon/EXTENSIONS.md.' },
376
+ { id: 'row', kind: 'command', group: 'Context', usage: '/row [new|add|list|edit <id>|rm <id>|test <id>|reload|on|off]', desc: 'your own live lines above the status bar: stats from a URL, your Quox, a plugin or a command', status: 'active', args: ARGS.row,
377
+ detail:
378
+ '/row new walks you through one row with y/N cards and shows what it will say before saving; /row edit <id> changes one. ' +
379
+ 'Without a template a row reads the status-row contract: JSON {"text", "value", "tone", "trend", "link"} or plain text ' +
380
+ '(quox rows contract prints it). quox rows add|edit|remove|test|share|import does the same from a shell; /row reload picks it up at once (open windows re-read every minute). ' +
381
+ 'Rows never carry secrets, and a row from a share bundle never runs a command. ' +
382
+ '/row add <id> url <url> "<template>" [every 60s] [label X] [colour ok] puts one line above the status bar, ' +
383
+ 'refreshed on a timer. /row add <id> cmd "<shell cmd>" "<template>" does the same from a command. The template ' +
384
+ 'fills {{path}} from the JSON (or {{text}} from plain output), plus count(path) and count(path[field=value]); ' +
385
+ 'numbers are shown compactly, for example {{count(runs[state=ok])}}/{{count(runs)}} ok. At most 3 rows, one line each. ' +
386
+ 'url rows follow your account (fetched from THIS machine, http or https only, no credentials sent unless the URL is ' +
387
+ 'your own Quox server). cmd rows stay on this machine and are never synced. A failing row shows a short red ' +
388
+ '"warning" line and retries with backoff; it never blocks typing. /row test <id> shows the raw value; /row off hides and pauses all rows.' },
389
+ { id: 'mentions', kind: 'key', new: true, group: 'Context', usage: '@mention', desc: 'complete agents, hosts, tasks and receipts as you type @', status: 'active',
390
+ detail: 'Type @ at the start of a word: a list under the composer suggests agents, fleet hosts, tasks (your newest objectives) and receipts (the envelope ids of this session\'s tool calls), filtered as you type by id or name. Tab or Enter puts it in, Esc closes the list. The lists are fetched once per session on your first @ and then filtered locally, so typing never sends a request; receipts come from this session and need no request at all. /peek <id> shows what an id is.' }
107
391
  ];
108
392
 
393
+ // Splice dev entries back next to their neighbours (after /notes and /back); absent in the public build.
394
+ for (const e of DEV_ENTRIES) {
395
+ const after = e.id === 'devnote' ? 'notes' : 'back';
396
+ const i = REGISTRY.findIndex((x) => x.id === after);
397
+ REGISTRY.splice(i < 0 ? REGISTRY.length : i + 1, 0, e);
398
+ }
399
+ // Vendor (owner tooling) entries go just before /row; absent in the public build.
400
+ for (const e of VENDOR_ENTRIES) {
401
+ const i = REGISTRY.findIndex((x) => x.id === 'row');
402
+ REGISTRY.splice(i < 0 ? REGISTRY.length : i, 0, e);
403
+ }
404
+
405
+ // The palette row's one-line wording (commandShort.js); /help keeps the long desc.
406
+ for (const e of REGISTRY) if (e.kind === 'command' && !e.short && SHORT[e.id]) e.short = SHORT[e.id];
407
+
109
408
  const norm = (s) => String(s || '').trim().toLowerCase().replace(/^\//, '');
110
409
 
111
- export function findEntry(name) {
410
+ /** Dev-only entries (/observe, /devnote) are invisible unless a dev observer is configured. */
411
+ export const entryVisible = (e) => !e.dev || devObserverOn();
412
+ /** REGISTRY minus entries hidden on this install. */
413
+ export const visibleRegistry = () => REGISTRY.filter(entryVisible);
414
+
415
+ export function findEntry(name, registry = visibleRegistry()) {
112
416
  const n = norm(name);
113
417
  if (!n) return null;
114
- return REGISTRY.find(e => e.id === n || e.aliases?.includes(n) || norm(e.usage.split(/\s/)[0]) === n || e.usage.toLowerCase() === n) || null;
418
+ return registry.find(e => e.id === n || e.aliases?.includes(n) || norm(e.usage.split(/\s/)[0]) === n || e.usage.toLowerCase() === n) || null;
419
+ }
420
+
421
+ /** Like findEntry but only a slash command: a key or topic that shares the first word (the "status line" key vs /status) never shadows the command. */
422
+ export function findCommandEntry(name) {
423
+ const n = norm(name);
424
+ if (!n) return null;
425
+ return visibleRegistry().find(e => e.kind === 'command' && (e.id === n || e.aliases?.includes(n) || norm(e.usage.split(/\s/)[0]) === n)) || null;
115
426
  }
116
427
 
117
428
  /** Slash commands only. */
118
- export const commandNames = () => REGISTRY.filter(e => e.kind === 'command' && e.status === 'active').flatMap(e => ['/' + e.id, ...(e.aliases || []).map(a => '/' + a)]);
429
+ export const commandNames = () => visibleRegistry().filter(e => e.kind === 'command' && e.status === 'active').flatMap(e => ['/' + e.id, ...(e.aliases || []).map(a => '/' + a)]);
119
430
 
120
431
  /** Pure help model: [{ kind: 'head'|'row'|'dim'|'gap'|'plain', key?, text? }]. */
121
432
  const capView = (e, caps = {}) => {
@@ -124,31 +435,110 @@ const capView = (e, caps = {}) => {
124
435
  return e;
125
436
  };
126
437
 
127
- export function helpModel(topic, caps = {}) {
438
+ /** /help status: the status rows under the composer, one line per item (there is no hover in a terminal). */
439
+ const STATUS_LINE_HELP = [
440
+ { kind: 'gap' }, { kind: 'head', text: 'The status line, item by item' },
441
+ { kind: 'row', key: '● agent X', text: 'the agent your turns go to (/attach); ○ agent: none when you talk to Quox itself' },
442
+ { kind: 'row', key: '⬢ slot X', text: 'this window\'s own address, held by this session (/slot); others reach you with /say X' },
443
+ { kind: 'row', key: '⬢ groups a 2, b', text: 'other slots joined as group rooms (/join, /leave, /groups); a number is unread lines' },
444
+ { kind: 'row', key: 'profile X', text: 'the quox login this window uses (/profile)' },
445
+ { kind: 'row', key: 'org X', text: 'the organisation everything here acts in' },
446
+ { kind: 'row', key: 'mode ask first', text: 'risky actions wait for your approval; auto acts without asking, plan only plans' },
447
+ { kind: 'row', key: 'rules: QUOX.md', text: 'project rules sent with every turn (/rules off drops them)' },
448
+ { kind: 'row', key: '↻ auto-restart', text: 'restarts into a newer installed quox when you are idle (/restart auto)' },
449
+ { kind: 'row', key: '── model ─', text: 'the divider under the composer names the provider and model (/model)' }
450
+ ];
451
+
452
+ /** QUOXON-DELIGHT: the short "Try these" block at the end of /help (the full verified set is the collector's tryThese.json). */
453
+ export const TRY_THESE = [
454
+ { id: 'challenge', usage: '/challenge', hook: 'a different AI provider argues with the answer you just got' },
455
+ { id: 'spawn', usage: '/spawn 2c 4G 20G 1h for X', hook: 'a disposable VM in seconds, gone when the timer ends' },
456
+ { id: 'crystallize', usage: '/crystallize', hook: 'turn what you just did into a reusable skill' },
457
+ { id: 'roar', usage: '/roar <text>', hook: 'shout to every session you hold at once' }
458
+ ];
459
+
460
+ /** registry: the entries to list (default: this install's visible registry; tests pass a fixture). */
461
+ export function helpModel(topic, caps = {}, registry = visibleRegistry()) {
128
462
  if (topic) {
129
- const e0 = findEntry(topic);
463
+ const e0 = findEntry(topic, registry);
130
464
  const e = e0 && capView(e0, caps);
131
465
  if (!e) return [{ kind: 'plain', text: `No help for "${topic}". Try /help` }];
466
+ const statusLine = norm(topic) === 'status' ? STATUS_LINE_HELP : [];
132
467
  return [
133
468
  { kind: 'head', text: e.usage },
134
469
  { kind: 'plain', text: e.desc },
135
470
  ...(e.detail ? [{ kind: 'plain', text: e.detail }] : []),
136
- ...(e.status === 'planned' ? [{ kind: 'dim', text: 'Planned, not built yet.' }] : [])
471
+ ...(e.status === 'planned' ? [{ kind: 'dim', text: 'Planned, not built yet.' }] : []),
472
+ ...statusLine
137
473
  ];
138
474
  }
139
475
  const out = [];
140
476
  for (const g of GROUPS) {
141
- const active = REGISTRY.filter(e => e.group === g && e.status === 'active').map(e => capView(e, caps));
477
+ const active = registry.filter(e => e.group === g && e.status === 'active').map(e => capView(e, caps));
142
478
  if (!active.length) continue;
143
479
  if (out.length) out.push({ kind: 'gap' });
144
480
  out.push({ kind: 'head', text: g });
145
481
  for (const e of active) out.push({ kind: 'row', key: e.usage, text: e.desc });
146
482
  }
147
- const planned = REGISTRY.filter(e => e.status === 'planned');
483
+ const planned = registry.filter(e => e.status === 'planned');
148
484
  if (planned.length) {
149
485
  out.push({ kind: 'gap' }, { kind: 'head', text: 'Coming', dim: true });
150
486
  for (const e of planned) out.push({ kind: 'dim', key: e.usage, text: e.desc });
151
487
  }
488
+ const tries = TRY_THESE.filter(t => registry.some(e => e.status === 'active' && (e.id === t.id || (e.aliases || []).includes(t.id))));
489
+ if (tries.length) {
490
+ out.push({ kind: 'gap' }, { kind: 'subhead', text: 'Try these' });
491
+ for (const t of tries) out.push({ kind: 'row', key: t.usage, text: t.hook });
492
+ }
152
493
  out.push({ kind: 'gap' }, { kind: 'dim', text: '/help <command> for detail, e.g. /help esc' });
153
494
  return out;
154
495
  }
496
+
497
+ /** Nearest real slash commands for a mistyped one (prefix, substring, then edit distance; a typo in the first letters of a longer command counts, so /drsg finds /dragon; a one-letter name like /r is not "inside" every word containing that letter). */
498
+ export function suggestCommands(input, max = 3) {
499
+ const q = norm(input);
500
+ if (!q) return [];
501
+ const names = [...new Set(commandNames())];
502
+ const lev = (a, b) => { const dp = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)]); for (let j = 1; j <= b.length; j++) dp[0][j] = j; for (let i = 1; i <= a.length; i++) for (let j = 1; j <= b.length; j++) dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)); return dp[a.length][b.length]; };
503
+ return names.map(n => { const k = n.slice(1); const rel = q.startsWith(k) || k.startsWith(q) || (k.length >= 2 && q.includes(k)) || (q.length >= 2 && k.includes(q)); const head = q.length >= 3 && k.length > q.length ? lev(q, k.slice(0, q.length)) + 0.5 : Infinity; return { n, d: rel ? 0 : Math.min(lev(q, k), head), len: Math.abs(k.length - q.length) }; })
504
+ .filter(x => x.d <= Math.max(2, Math.floor(q.length / 3)))
505
+ .sort((a, b) => a.d - b.d || a.len - b.len || a.n.localeCompare(b.n)).slice(0, max).map(x => x.n);
506
+ }
507
+
508
+ const HINT_SYNONYMS = { active: ['live', 'working', 'running', 'who'], working: ['live', 'active'], who: ['sessions', 'live'], online: ['live'], busy: ['live', 'working'], list: ['show'], quit: ['leave'], logout: ['leave'], help: ['commands'] };
509
+ const HINT_STOP = new Set(['the', 'and', 'for', 'with', 'your', 'you', 'this', 'that', 'what', 'new']);
510
+ /**
511
+ * Hints for an unknown /word: the edit-distance neighbours (suggestCommands) first, else commands whose id, aliases or
512
+ * description share a keyword with it (so /active finds /sessions, "everything live in your org"). Max 3.
513
+ * -> [{ name: '/sessions', desc, kw }] (kw: matched by keyword, not spelling); a single result is a "strong" match the TUI offers as a y/N card.
514
+ */
515
+ export function nearestCommands(input, max = 3) {
516
+ const q = norm(input);
517
+ const out = suggestCommands(input, max).map(n => ({ n, score: 100 }));
518
+ const seen = new Set(out.map(x => findEntry(x.n)?.id));
519
+ const toks = new Set(q.split(/[^a-z0-9]+/).filter(t => t.length >= 3 && !HINT_STOP.has(t)));
520
+ for (const t of [...toks]) for (const syn of HINT_SYNONYMS[t] || []) toks.add(syn);
521
+ if (toks.size && !out.length) { // keyword hints only when spelling found nothing (the spelling line is pinned by FW1)
522
+ const stem = (w) => w.slice(0, Math.max(4, w.length - 3));
523
+ for (const e of visibleRegistry()) {
524
+ if (e.kind !== 'command' || e.status !== 'active' || seen.has(e.id)) continue;
525
+ const words = new Set(`${e.id} ${(e.aliases || []).join(' ')} ${e.desc || ''}`.toLowerCase().split(/[^a-z0-9]+/).filter(w => w.length >= 3 && !HINT_STOP.has(w)));
526
+ let score = 0;
527
+ for (const t of toks) for (const w of words) if (w === t || (t.length >= 5 && w.length >= 5 && stem(t) === stem(w))) { score++; break; }
528
+ if (score) out.push({ n: '/' + e.id, score, kw: true });
529
+ }
530
+ }
531
+ return out.sort((a, b) => b.score - a.score).slice(0, max).map(x => ({ name: x.n, kw: !!x.kw, desc: (findEntry(x.n)?.desc || '').split(/[;,(]/)[0].trim() }));
532
+ }
533
+
534
+ // Commands whose last argument is free text: a pasted line that the terminal wrapped is still one command.
535
+ const FREE_TEXT_CMDS = new Set(['ask', 'aside', 'afk', 'observe', 'roar', 'awaken', 'poke', 'wake', 'nudge', 'ping', 'slap', 'feedback', 'huh', 'r', 'reply', 'say', 'page', 'broadcast', 'gripe', 'gripe!', 'note', 'devnote', 'dragon', 'deny', 'wish', 'challenge']);
536
+ /** "/say a long\n text" -> { text: '/say a long text', lines: 2 }; null when it is not a wrapped free-text command. */
537
+ export function joinWrappedSlash(text) {
538
+ const t = String(text || '');
539
+ if (!t.startsWith('/') || !t.includes('\n')) return null;
540
+ const lines = t.split('\n').map(l => l.trim()).filter(Boolean);
541
+ if (lines.length < 2 || !FREE_TEXT_CMDS.has(lines[0].slice(1).split(/\s+/)[0].toLowerCase())) return null;
542
+ if (lines.slice(1).some(l => l.startsWith('/'))) return null;
543
+ return { text: lines.join(' '), lines: lines.length };
544
+ }