quoxcli 0.0.0-stage → 0.3.1

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 (516) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +144 -2
  3. package/bin/quox.js +265 -0
  4. package/bin/quoxon.js +8 -0
  5. package/lib/api/endpoints/admin.js +151 -0
  6. package/lib/api/endpoints/aee.js +38 -0
  7. package/lib/api/endpoints/agent-attach.js +126 -0
  8. package/lib/api/endpoints/agent-schedules.js +26 -0
  9. package/lib/api/endpoints/agents.js +63 -0
  10. package/lib/api/endpoints/assistants.js +62 -0
  11. package/lib/api/endpoints/audit.js +14 -0
  12. package/lib/api/endpoints/auth.js +50 -0
  13. package/lib/api/endpoints/awareness.js +34 -0
  14. package/lib/api/endpoints/away.js +15 -0
  15. package/lib/api/endpoints/backup-schedules.js +20 -0
  16. package/lib/api/endpoints/backups.js +86 -0
  17. package/lib/api/endpoints/brain.js +140 -0
  18. package/lib/api/endpoints/budgets.js +62 -0
  19. package/lib/api/endpoints/calendarq.js +71 -0
  20. package/lib/api/endpoints/catalog.js +14 -0
  21. package/lib/api/endpoints/chat.js +61 -0
  22. package/lib/api/endpoints/consent.js +26 -0
  23. package/lib/api/endpoints/contactsq.js +68 -0
  24. package/lib/api/endpoints/conversations.js +46 -0
  25. package/lib/api/endpoints/custom-roles.js +92 -0
  26. package/lib/api/endpoints/divisions.js +56 -0
  27. package/lib/api/endpoints/domains.js +38 -0
  28. package/lib/api/endpoints/engagement.js +231 -0
  29. package/lib/api/endpoints/engine.js +11 -0
  30. package/lib/api/endpoints/entity.js +50 -0
  31. package/lib/api/endpoints/executions.js +45 -0
  32. package/lib/api/endpoints/fetchAllowlist.js +27 -0
  33. package/lib/api/endpoints/file.js +20 -0
  34. package/lib/api/endpoints/files.js +40 -0
  35. package/lib/api/endpoints/github.js +21 -0
  36. package/lib/api/endpoints/governance.js +43 -0
  37. package/lib/api/endpoints/hat.js +66 -0
  38. package/lib/api/endpoints/inbox.js +52 -0
  39. package/lib/api/endpoints/inboxq.js +120 -0
  40. package/lib/api/endpoints/index.js +242 -0
  41. package/lib/api/endpoints/invites.js +19 -0
  42. package/lib/api/endpoints/llm-policy.js +43 -0
  43. package/lib/api/endpoints/lucid.js +89 -0
  44. package/lib/api/endpoints/members.js +32 -0
  45. package/lib/api/endpoints/memory.js +94 -0
  46. package/lib/api/endpoints/mfa.js +32 -0
  47. package/lib/api/endpoints/mind.js +13 -0
  48. package/lib/api/endpoints/mon.js +184 -0
  49. package/lib/api/endpoints/notesq.js +43 -0
  50. package/lib/api/endpoints/objectives.js +68 -0
  51. package/lib/api/endpoints/orgs.js +38 -0
  52. package/lib/api/endpoints/plugins.js +152 -0
  53. package/lib/api/endpoints/powers.js +75 -0
  54. package/lib/api/endpoints/presence.js +33 -0
  55. package/lib/api/endpoints/prompts.js +69 -0
  56. package/lib/api/endpoints/qlarity.js +122 -0
  57. package/lib/api/endpoints/quoxbox.js +55 -0
  58. package/lib/api/endpoints/quoxchat.js +54 -0
  59. package/lib/api/endpoints/quoxflow.js +32 -0
  60. package/lib/api/endpoints/quoxplan.js +121 -0
  61. package/lib/api/endpoints/quoxseo.js +114 -0
  62. package/lib/api/endpoints/reach.js +47 -0
  63. package/lib/api/endpoints/router.js +17 -0
  64. package/lib/api/endpoints/runs.js +38 -0
  65. package/lib/api/endpoints/safety.js +53 -0
  66. package/lib/api/endpoints/scim.js +46 -0
  67. package/lib/api/endpoints/scopes.js +14 -0
  68. package/lib/api/endpoints/security.js +69 -0
  69. package/lib/api/endpoints/setup.js +90 -0
  70. package/lib/api/endpoints/skills.js +200 -0
  71. package/lib/api/endpoints/sso.js +67 -0
  72. package/lib/api/endpoints/stepup.js +20 -0
  73. package/lib/api/endpoints/tags.js +32 -0
  74. package/lib/api/endpoints/teams.js +56 -0
  75. package/lib/api/endpoints/telephony.js +46 -0
  76. package/lib/api/endpoints/tg.js +56 -0
  77. package/lib/api/endpoints/tools.js +50 -0
  78. package/lib/api/endpoints/traces.js +157 -0
  79. package/lib/api/endpoints/triggers.js +96 -0
  80. package/lib/api/endpoints/user-library.js +41 -0
  81. package/lib/api/endpoints/users.js +20 -0
  82. package/lib/api/endpoints/v1.js +585 -0
  83. package/lib/api/endpoints/vault.js +192 -0
  84. package/lib/api/endpoints/voice.js +22 -0
  85. package/lib/api/endpoints/volt.js +84 -0
  86. package/lib/api/endpoints/ward.js +47 -0
  87. package/lib/api/endpoints/webhooks.js +46 -0
  88. package/lib/api/endpoints/workflows.js +169 -0
  89. package/lib/api/endpoints/x.js +32 -0
  90. package/lib/api/endpoints.js +15 -0
  91. package/lib/commands/actions.js +61 -0
  92. package/lib/commands/admin.js +375 -0
  93. package/lib/commands/aee.js +99 -0
  94. package/lib/commands/agent.js +337 -0
  95. package/lib/commands/agentAttach.js +1528 -0
  96. package/lib/commands/approvals.js +119 -0
  97. package/lib/commands/aside.js +24 -0
  98. package/lib/commands/ask.js +144 -0
  99. package/lib/commands/assistant.js +169 -0
  100. package/lib/commands/auth.js +464 -0
  101. package/lib/commands/awareness.js +88 -0
  102. package/lib/commands/backup.js +377 -0
  103. package/lib/commands/brain.js +1085 -0
  104. package/lib/commands/budget.js +241 -0
  105. package/lib/commands/build.js +170 -0
  106. package/lib/commands/calendar.js +216 -0
  107. package/lib/commands/capabilities.js +34 -0
  108. package/lib/commands/chat.js +295 -0
  109. package/lib/commands/claude.js +380 -0
  110. package/lib/commands/config-cmd.js +106 -0
  111. package/lib/commands/consent.js +84 -0
  112. package/lib/commands/contacts.js +1016 -0
  113. package/lib/commands/contactsSelftest.js +132 -0
  114. package/lib/commands/conversation.js +105 -0
  115. package/lib/commands/dashboard.js +43 -0
  116. package/lib/commands/department.js +96 -0
  117. package/lib/commands/dev.js +620 -0
  118. package/lib/commands/devResident.js +326 -0
  119. package/lib/commands/devSupervisor.js +1062 -0
  120. package/lib/commands/discord.js +132 -0
  121. package/lib/commands/doctor.js +181 -0
  122. package/lib/commands/domain.js +114 -0
  123. package/lib/commands/done.js +347 -0
  124. package/lib/commands/engagement.js +597 -0
  125. package/lib/commands/entity.js +119 -0
  126. package/lib/commands/facebook.js +212 -0
  127. package/lib/commands/fetchAllowlist.js +71 -0
  128. package/lib/commands/file.js +165 -0
  129. package/lib/commands/fleet.js +1189 -0
  130. package/lib/commands/governance.js +647 -0
  131. package/lib/commands/gripe.js +355 -0
  132. package/lib/commands/hat.js +374 -0
  133. package/lib/commands/helpers.js +96 -0
  134. package/lib/commands/hyperion.js +58 -0
  135. package/lib/commands/inbox.js +173 -0
  136. package/lib/commands/instagram.js +264 -0
  137. package/lib/commands/integration.js +100 -0
  138. package/lib/commands/inventory.js +115 -0
  139. package/lib/commands/invites.js +45 -0
  140. package/lib/commands/licence.js +139 -0
  141. package/lib/commands/linkedin.js +178 -0
  142. package/lib/commands/llm-policy.js +117 -0
  143. package/lib/commands/llm.js +45 -0
  144. package/lib/commands/lucid.js +597 -0
  145. package/lib/commands/mail.js +531 -0
  146. package/lib/commands/memory.js +361 -0
  147. package/lib/commands/mind.js +71 -0
  148. package/lib/commands/mon.js +581 -0
  149. package/lib/commands/monitoring.js +86 -0
  150. package/lib/commands/motion.js +22 -0
  151. package/lib/commands/notes.js +1246 -0
  152. package/lib/commands/notesBatch.js +160 -0
  153. package/lib/commands/notesBridges.js +56 -0
  154. package/lib/commands/notesLinks.js +60 -0
  155. package/lib/commands/notification.js +57 -0
  156. package/lib/commands/nucleus.js +123 -0
  157. package/lib/commands/objective.js +240 -0
  158. package/lib/commands/offload.js +89 -0
  159. package/lib/commands/onboard.js +329 -0
  160. package/lib/commands/open.js +56 -0
  161. package/lib/commands/org.js +276 -0
  162. package/lib/commands/peek.js +28 -0
  163. package/lib/commands/plan.js +944 -0
  164. package/lib/commands/plugin-doctor.js +1091 -0
  165. package/lib/commands/plugin.js +649 -0
  166. package/lib/commands/policy.js +78 -0
  167. package/lib/commands/powers.js +296 -0
  168. package/lib/commands/presence.js +89 -0
  169. package/lib/commands/prompt.js +350 -0
  170. package/lib/commands/provider.js +159 -0
  171. package/lib/commands/qlarity.js +706 -0
  172. package/lib/commands/quoxbox.js +270 -0
  173. package/lib/commands/quoxchat.js +275 -0
  174. package/lib/commands/quoxseo.js +378 -0
  175. package/lib/commands/reach.js +159 -0
  176. package/lib/commands/role.js +286 -0
  177. package/lib/commands/router.js +77 -0
  178. package/lib/commands/rows.js +358 -0
  179. package/lib/commands/run.js +187 -0
  180. package/lib/commands/safety.js +107 -0
  181. package/lib/commands/scim.js +88 -0
  182. package/lib/commands/scope.js +58 -0
  183. package/lib/commands/security.js +157 -0
  184. package/lib/commands/server.js +292 -0
  185. package/lib/commands/service.js +89 -0
  186. package/lib/commands/settings.js +99 -0
  187. package/lib/commands/setup.js +569 -0
  188. package/lib/commands/skillManifest.js +113 -0
  189. package/lib/commands/skillRightsFormat.js +38 -0
  190. package/lib/commands/skills.js +687 -0
  191. package/lib/commands/slot.js +1451 -0
  192. package/lib/commands/sso.js +229 -0
  193. package/lib/commands/status.js +137 -0
  194. package/lib/commands/tag.js +91 -0
  195. package/lib/commands/team.js +142 -0
  196. package/lib/commands/tg.js +240 -0
  197. package/lib/commands/tiktok.js +218 -0
  198. package/lib/commands/tips.js +23 -0
  199. package/lib/commands/tool.js +304 -0
  200. package/lib/commands/trace.js +530 -0
  201. package/lib/commands/trigger.js +223 -0
  202. package/lib/commands/update.js +46 -0
  203. package/lib/commands/vault.js +1447 -0
  204. package/lib/commands/vm.js +237 -0
  205. package/lib/commands/voice.js +213 -0
  206. package/lib/commands/volt.js +570 -0
  207. package/lib/commands/walkthrough.js +32 -0
  208. package/lib/commands/watch.js +167 -0
  209. package/lib/commands/webhook.js +137 -0
  210. package/lib/commands/welcome.js +14 -0
  211. package/lib/commands/workflow.js +660 -0
  212. package/lib/commands/x.js +448 -0
  213. package/lib/core/actingAs.js +136 -0
  214. package/lib/core/activeSummary.js +186 -0
  215. package/lib/core/aee.js +53 -0
  216. package/lib/core/agentAlias.js +163 -0
  217. package/lib/core/approvalContext.js +61 -0
  218. package/lib/core/aside.js +54 -0
  219. package/lib/core/askDecision.js +60 -0
  220. package/lib/core/auth.js +501 -0
  221. package/lib/core/banner.js +143 -0
  222. package/lib/core/brainPrime.js +90 -0
  223. package/lib/core/callerSource.js +129 -0
  224. package/lib/core/claudeLs.js +94 -0
  225. package/lib/core/cliBuild.js +62 -0
  226. package/lib/core/client.js +693 -0
  227. package/lib/core/commandRegistrars.js +121 -0
  228. package/lib/core/completions.js +163 -0
  229. package/lib/core/config.js +669 -0
  230. package/lib/core/confirm.js +37 -0
  231. package/lib/core/customHats.js +168 -0
  232. package/lib/core/deeplinks.js +76 -0
  233. package/lib/core/doneEngineFetch.js +227 -0
  234. package/lib/core/errors.js +441 -0
  235. package/lib/core/global-flags.js +190 -0
  236. package/lib/core/governedSession.js +402 -0
  237. package/lib/core/hyperion.js +102 -0
  238. package/lib/core/inboxResolution.js +65 -0
  239. package/lib/core/lazyCommands.js +122 -0
  240. package/lib/core/liveSessionsReport.js +112 -0
  241. package/lib/core/offload.js +504 -0
  242. package/lib/core/org-override.js +52 -0
  243. package/lib/core/orgResolve.js +70 -0
  244. package/lib/core/output.js +239 -0
  245. package/lib/core/peek.js +126 -0
  246. package/lib/core/prodEnv.js +7 -0
  247. package/lib/core/profileUsers.js +51 -0
  248. package/lib/core/promptCancel.js +18 -0
  249. package/lib/core/quoxglyph.js +158 -0
  250. package/lib/core/repoPath.js +11 -0
  251. package/lib/core/residentAlarm.js +282 -0
  252. package/lib/core/residentAuth.js +243 -0
  253. package/lib/core/residentPump.js +229 -0
  254. package/lib/core/residentSeed.js +106 -0
  255. package/lib/core/rootOptions.js +39 -0
  256. package/lib/core/router.js +168 -0
  257. package/lib/core/selfUpdate.js +188 -0
  258. package/lib/core/sessionHeartbeat.js +192 -0
  259. package/lib/core/sessionsOverview.js +224 -0
  260. package/lib/core/slotKeeper.js +527 -0
  261. package/lib/core/slotWake.js +120 -0
  262. package/lib/core/supervisorEngine.js +933 -0
  263. package/lib/core/systemdInstaller.js +256 -0
  264. package/lib/core/version.js +87 -0
  265. package/lib/core/vm.js +361 -0
  266. package/lib/core/vmTui.js +86 -0
  267. package/lib/core/walkthrough.js +35 -0
  268. package/lib/hyperion/deck.js +29 -0
  269. package/lib/lucid/LucidCard.jsx +68 -0
  270. package/lib/lucid/LucidIntro.jsx +92 -0
  271. package/lib/lucid/blind.js +71 -0
  272. package/lib/lucid/cards.js +86 -0
  273. package/lib/lucid/deck.js +32 -0
  274. package/lib/lucid/fmt.js +139 -0
  275. package/lib/lucid/markdown.js +81 -0
  276. package/lib/lucid/motion.js +56 -0
  277. package/lib/lucid/sitrep.js +173 -0
  278. package/lib/plugins/loader.js +93 -0
  279. package/lib/quoxon/activity.js +116 -0
  280. package/lib/quoxon/aocl.js +117 -0
  281. package/lib/quoxon/api.js +952 -0
  282. package/lib/quoxon/app.js +4032 -0
  283. package/lib/quoxon/approvalFacts.js +94 -0
  284. package/lib/quoxon/approvalsView.js +325 -0
  285. package/lib/quoxon/asideView.js +40 -0
  286. package/lib/quoxon/ask.js +121 -0
  287. package/lib/quoxon/away.js +52 -0
  288. package/lib/quoxon/bang.js +224 -0
  289. package/lib/quoxon/bulkRisk.js +17 -0
  290. package/lib/quoxon/capabilities.js +82 -0
  291. package/lib/quoxon/cells.js +53 -0
  292. package/lib/quoxon/commandShort.js +112 -0
  293. package/lib/quoxon/commands.js +479 -0
  294. package/lib/quoxon/connect.js +654 -0
  295. package/lib/quoxon/connectCard.js +45 -0
  296. package/lib/quoxon/consent.js +86 -0
  297. package/lib/quoxon/context.js +68 -0
  298. package/lib/quoxon/copy.js +34 -0
  299. package/lib/quoxon/countdownLine.js +11 -0
  300. package/lib/quoxon/demo.autoplay.js +29 -0
  301. package/lib/quoxon/demo.fixtures.js +24 -0
  302. package/lib/quoxon/demo.js +76 -0
  303. package/lib/quoxon/devObserver.js +20 -0
  304. package/lib/quoxon/dividerAocl.js +191 -0
  305. package/lib/quoxon/dividerRule.js +57 -0
  306. package/lib/quoxon/dragonModes.js +150 -0
  307. package/lib/quoxon/dragonView.js +41 -0
  308. package/lib/quoxon/earlyInput.js +40 -0
  309. package/lib/quoxon/echo.js +15 -0
  310. package/lib/quoxon/egg.js +36 -0
  311. package/lib/quoxon/events.js +677 -0
  312. package/lib/quoxon/feedback.js +50 -0
  313. package/lib/quoxon/gallery.js +237 -0
  314. package/lib/quoxon/handoffOffer.js +78 -0
  315. package/lib/quoxon/hatSwitch.js +37 -0
  316. package/lib/quoxon/headless.js +161 -0
  317. package/lib/quoxon/hostUrls.js +41 -0
  318. package/lib/quoxon/indicator.js +197 -0
  319. package/lib/quoxon/intent.js +52 -0
  320. package/lib/quoxon/keys.js +164 -0
  321. package/lib/quoxon/launcher.js +103 -0
  322. package/lib/quoxon/main.js +176 -0
  323. package/lib/quoxon/markdown.js +341 -0
  324. package/lib/quoxon/markdownSettle.js +19 -0
  325. package/lib/quoxon/matrixDomains.json +46 -0
  326. package/lib/quoxon/mentions.js +90 -0
  327. package/lib/quoxon/mind.js +103 -0
  328. package/lib/quoxon/mode.js +46 -0
  329. package/lib/quoxon/model.js +311 -0
  330. package/lib/quoxon/modelPrefs.js +345 -0
  331. package/lib/quoxon/motion/clock.js +70 -0
  332. package/lib/quoxon/motion/color.js +35 -0
  333. package/lib/quoxon/motion/frame.js +14 -0
  334. package/lib/quoxon/motion/level.js +91 -0
  335. package/lib/quoxon/motion/reveal.js +54 -0
  336. package/lib/quoxon/motion/ring.js +79 -0
  337. package/lib/quoxon/mouse.js +211 -0
  338. package/lib/quoxon/noai.js +57 -0
  339. package/lib/quoxon/objects/agent.js +61 -0
  340. package/lib/quoxon/objects/away.js +72 -0
  341. package/lib/quoxon/objects/crystal.js +100 -0
  342. package/lib/quoxon/objects/fetch.js +182 -0
  343. package/lib/quoxon/objects/fleet.js +149 -0
  344. package/lib/quoxon/objects/health.js +186 -0
  345. package/lib/quoxon/objects/index.js +106 -0
  346. package/lib/quoxon/objects/ladder.js +71 -0
  347. package/lib/quoxon/objects/memory.js +44 -0
  348. package/lib/quoxon/objects/mirror.js +137 -0
  349. package/lib/quoxon/objects/model.js +118 -0
  350. package/lib/quoxon/objects/peek.js +17 -0
  351. package/lib/quoxon/objects/proof.js +110 -0
  352. package/lib/quoxon/objects/prove.js +130 -0
  353. package/lib/quoxon/objects/replay.js +125 -0
  354. package/lib/quoxon/objects/sitrep.js +58 -0
  355. package/lib/quoxon/objects/skill.js +105 -0
  356. package/lib/quoxon/objects/view.js +87 -0
  357. package/lib/quoxon/objects/workflow.js +119 -0
  358. package/lib/quoxon/observers.js +42 -0
  359. package/lib/quoxon/onboarding/copy.js +263 -0
  360. package/lib/quoxon/onboarding/credential.js +108 -0
  361. package/lib/quoxon/onboarding/detect.js +22 -0
  362. package/lib/quoxon/onboarding/dockerOffer.js +45 -0
  363. package/lib/quoxon/onboarding/failureCause.js +33 -0
  364. package/lib/quoxon/onboarding/finish.js +134 -0
  365. package/lib/quoxon/onboarding/index.js +94 -0
  366. package/lib/quoxon/onboarding/installRunner.js +284 -0
  367. package/lib/quoxon/onboarding/intent.js +53 -0
  368. package/lib/quoxon/onboarding/journey.js +73 -0
  369. package/lib/quoxon/onboarding/preflight.js +156 -0
  370. package/lib/quoxon/onboarding/probe.js +65 -0
  371. package/lib/quoxon/onboarding/runner.js +58 -0
  372. package/lib/quoxon/onboarding/view.js +682 -0
  373. package/lib/quoxon/orgLabel.js +10 -0
  374. package/lib/quoxon/orgSwitch.js +33 -0
  375. package/lib/quoxon/ownBroadcast.js +22 -0
  376. package/lib/quoxon/palette.js +43 -0
  377. package/lib/quoxon/print.js +98 -0
  378. package/lib/quoxon/profileSwitch.js +53 -0
  379. package/lib/quoxon/promptHistory.js +41 -0
  380. package/lib/quoxon/providers.js +48 -0
  381. package/lib/quoxon/qart.js +348 -0
  382. package/lib/quoxon/qdragon.js +410 -0
  383. package/lib/quoxon/qdragonFly.js +61 -0
  384. package/lib/quoxon/qdragonScenes.js +282 -0
  385. package/lib/quoxon/qeyes.js +213 -0
  386. package/lib/quoxon/qglyph.js +143 -0
  387. package/lib/quoxon/qhello.js +58 -0
  388. package/lib/quoxon/qmotion.js +132 -0
  389. package/lib/quoxon/render.js +1167 -0
  390. package/lib/quoxon/resend.js +72 -0
  391. package/lib/quoxon/roster.js +307 -0
  392. package/lib/quoxon/route.js +39 -0
  393. package/lib/quoxon/rows.js +711 -0
  394. package/lib/quoxon/rowsStore.js +80 -0
  395. package/lib/quoxon/rules.js +130 -0
  396. package/lib/quoxon/screenContext.js +150 -0
  397. package/lib/quoxon/screenFrames.js +254 -0
  398. package/lib/quoxon/session.js +238 -0
  399. package/lib/quoxon/sessionDead.js +17 -0
  400. package/lib/quoxon/signin.js +125 -0
  401. package/lib/quoxon/skills.js +30 -0
  402. package/lib/quoxon/slash/agents.js +112 -0
  403. package/lib/quoxon/slash/argSources.js +65 -0
  404. package/lib/quoxon/slash/args.js +65 -0
  405. package/lib/quoxon/slash/automate.js +89 -0
  406. package/lib/quoxon/slash/broadcast.js +140 -0
  407. package/lib/quoxon/slash/broadcastRun.js +108 -0
  408. package/lib/quoxon/slash/card.js +30 -0
  409. package/lib/quoxon/slash/challenge.js +114 -0
  410. package/lib/quoxon/slash/cliCommand.js +53 -0
  411. package/lib/quoxon/slash/cliLeaves.json +948 -0
  412. package/lib/quoxon/slash/compact.js +90 -0
  413. package/lib/quoxon/slash/confirm.js +30 -0
  414. package/lib/quoxon/slash/crystallize.js +69 -0
  415. package/lib/quoxon/slash/dashboard.js +136 -0
  416. package/lib/quoxon/slash/draft.js +54 -0
  417. package/lib/quoxon/slash/hatWalk.js +206 -0
  418. package/lib/quoxon/slash/inverses.js +77 -0
  419. package/lib/quoxon/slash/killer.js +24 -0
  420. package/lib/quoxon/slash/killerApi.js +16 -0
  421. package/lib/quoxon/slash/ledger.js +88 -0
  422. package/lib/quoxon/slash/listView.js +30 -0
  423. package/lib/quoxon/slash/modelCmd.js +261 -0
  424. package/lib/quoxon/slash/palette.js +118 -0
  425. package/lib/quoxon/slash/paletteController.js +169 -0
  426. package/lib/quoxon/slash/panels.js +116 -0
  427. package/lib/quoxon/slash/picker.js +56 -0
  428. package/lib/quoxon/slash/restart.js +202 -0
  429. package/lib/quoxon/slash/resume.js +59 -0
  430. package/lib/quoxon/slash/rowCmd.js +157 -0
  431. package/lib/quoxon/slash/rowWalk.js +175 -0
  432. package/lib/quoxon/slash/settings.js +60 -0
  433. package/lib/quoxon/slash/spawn.js +177 -0
  434. package/lib/quoxon/slash/stepUp.js +274 -0
  435. package/lib/quoxon/slash/stepUpCard.js +60 -0
  436. package/lib/quoxon/slash/stepUpCmds.js +78 -0
  437. package/lib/quoxon/slash/undoApi.js +38 -0
  438. package/lib/quoxon/slash/undoCmd.js +49 -0
  439. package/lib/quoxon/slash/undoWish.js +15 -0
  440. package/lib/quoxon/slash/watch.js +164 -0
  441. package/lib/quoxon/slash/wish.js +23 -0
  442. package/lib/quoxon/slash/wishCmd.js +32 -0
  443. package/lib/quoxon/slotChat.js +300 -0
  444. package/lib/quoxon/slotFormat.js +195 -0
  445. package/lib/quoxon/slotRun.js +131 -0
  446. package/lib/quoxon/sound/index.js +186 -0
  447. package/lib/quoxon/sound/mapping.js +120 -0
  448. package/lib/quoxon/sound/output.js +52 -0
  449. package/lib/quoxon/sound/synth.js +122 -0
  450. package/lib/quoxon/stray.js +22 -0
  451. package/lib/quoxon/suggestCommand.js +59 -0
  452. package/lib/quoxon/term.js +141 -0
  453. package/lib/quoxon/textBuffer.js +416 -0
  454. package/lib/quoxon/theme.js +286 -0
  455. package/lib/quoxon/tips.js +433 -0
  456. package/lib/quoxon/toolError.js +41 -0
  457. package/lib/quoxon/transcript.js +188 -0
  458. package/lib/quoxon/web/qface.js +89 -0
  459. package/lib/quoxon/welcome.js +52 -0
  460. package/lib/quoxon/whatsnew.js +136 -0
  461. package/lib/quoxon/whatsnew.jsonl +89 -0
  462. package/lib/quoxon/windowOrg.js +113 -0
  463. package/lib/server/diag.js +129 -0
  464. package/lib/server/doctor.js +71 -0
  465. package/lib/server/install.js +149 -0
  466. package/lib/server/pluginEnable.js +161 -0
  467. package/lib/server/skew.js +56 -0
  468. package/lib/server/snapshot.js +131 -0
  469. package/lib/server/status.js +128 -0
  470. package/lib/server/sys.js +65 -0
  471. package/lib/server/uninstall.js +63 -0
  472. package/lib/server/update.js +234 -0
  473. package/lib/toolOutputShape.js +315 -0
  474. package/lib/tui/app.jsx +289 -0
  475. package/lib/tui/components/Breadcrumb.jsx +42 -0
  476. package/lib/tui/components/ChatInput.jsx +68 -0
  477. package/lib/tui/components/Confirm.jsx +32 -0
  478. package/lib/tui/components/DataTable.jsx +270 -0
  479. package/lib/tui/components/DetailPanel.jsx +241 -0
  480. package/lib/tui/components/HelpOverlay.jsx +67 -0
  481. package/lib/tui/components/Layout.jsx +129 -0
  482. package/lib/tui/components/Palette.jsx +141 -0
  483. package/lib/tui/components/SearchInput.jsx +125 -0
  484. package/lib/tui/components/Sparkline.jsx +55 -0
  485. package/lib/tui/components/StatusBar.jsx +133 -0
  486. package/lib/tui/components/Toast.jsx +52 -0
  487. package/lib/tui/hooks/useApi.js +44 -0
  488. package/lib/tui/hooks/useHealth.js +90 -0
  489. package/lib/tui/hooks/usePolledApi.js +86 -0
  490. package/lib/tui/hooks/useTerminalSize.js +46 -0
  491. package/lib/tui/index.js +56 -0
  492. package/lib/tui/intro/Intro.jsx +332 -0
  493. package/lib/tui/jsx-loader.js +32 -0
  494. package/lib/tui/keys.js +87 -0
  495. package/lib/tui/registry.js +49 -0
  496. package/lib/tui/theme.js +103 -0
  497. package/lib/tui/views/AdminView.jsx +398 -0
  498. package/lib/tui/views/AgentView.jsx +204 -0
  499. package/lib/tui/views/BackupView.jsx +225 -0
  500. package/lib/tui/views/BrainView.jsx +223 -0
  501. package/lib/tui/views/ChatView.jsx +468 -0
  502. package/lib/tui/views/FilesView.jsx +218 -0
  503. package/lib/tui/views/FleetView.jsx +234 -0
  504. package/lib/tui/views/GovernanceView.jsx +453 -0
  505. package/lib/tui/views/HealthView.jsx +216 -0
  506. package/lib/tui/views/HyperionDeck.jsx +79 -0
  507. package/lib/tui/views/InboxView.jsx +228 -0
  508. package/lib/tui/views/LucidDeck.jsx +719 -0
  509. package/lib/tui/views/MainMenu.jsx +123 -0
  510. package/lib/tui/views/MemoryView.jsx +377 -0
  511. package/lib/tui/views/ObjectivesView.jsx +230 -0
  512. package/lib/tui/views/OrgView.jsx +229 -0
  513. package/lib/tui/views/ToolView.jsx +221 -0
  514. package/lib/tui/views/VaultView.jsx +422 -0
  515. package/lib/tui/views/WorkflowView.jsx +329 -0
  516. package/package.json +75 -4
@@ -0,0 +1,479 @@
1
+ /**
2
+ * The single registry behind /help and slash-command dispatch. Anything shown
3
+ * as active here is wired in app.js; planned entries are listed dim under
4
+ * "Coming" and refuse to run. Add a command here first, or it does not exist.
5
+ */
6
+ import { topicList } from './tips.js';
7
+ import { devObserverOn } from './devObserver.js';
8
+ import { SHORT } from './commandShort.js';
9
+
10
+ export const GROUPS = ['Talk', 'Approvals', 'Skills', 'Conversation', 'Connect', 'Verbose', 'Context', 'Awareness'];
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: [], 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: [{ after: [], choices: ['explain', 'ask', 'sleep', 'fly', 'hero', 'fire', 'classic'] }],
66
+ tips: [{ after: [], choices: topicList().map(t => ({ value: t.topic, desc: `${t.count} tip${t.count === 1 ? "" : "s"}` })) }],
67
+ 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' }] }],
68
+ 'tip-settings': [{ after: [], choices: [
69
+ { value: 'off', desc: 'no tips' },
70
+ { value: 'core', desc: 'free features only' },
71
+ { value: 'all', desc: 'everything shipped' }] }],
72
+ hosts: [{ after: [], choices: [{ value: 'add', desc: 'print the one install command' }] }],
73
+ row: [
74
+ { after: [], choices: [
75
+ { value: 'new', desc: 'make a row, step by step (y/N cards)' },
76
+ { value: 'add', desc: 'a live line above the status bar' },
77
+ { value: 'edit', desc: 'change a row, step by step' },
78
+ { value: 'reload', desc: 'pick up rows changed by quox rows' },
79
+ { value: 'list', desc: 'your rows and what they show now' },
80
+ { value: 'rm', desc: 'remove a row' },
81
+ { value: 'test', desc: 'show a row\'s raw value' },
82
+ { value: 'on', desc: 'show rows' },
83
+ { value: 'off', desc: 'hide and pause all rows' }] },
84
+ { after: ['rm'], src: 'rows' },
85
+ { after: ['test'], src: 'rows' },
86
+ { after: ['edit'], src: 'rows' },
87
+ { after: ['add', '*'], choices: [
88
+ { value: 'url', desc: 'fetch JSON from a URL' },
89
+ { value: 'cmd', desc: 'run a shell command (this machine only)' }] },
90
+ { after: ['add', '*', 'url'], choices: [] },
91
+ { after: ['add', '*', 'cmd'], choices: [] },
92
+ { after: ['add'], choices: [] }
93
+ ]
94
+ };
95
+
96
+ export const REGISTRY = [
97
+ // Talk (keys)
98
+ { id: 'type', kind: 'key', group: 'Talk', usage: 'just type', desc: 'ask Quox anything; CommanderQ routes it to the right agent', status: 'active' },
99
+ { id: 'enter', kind: 'key', group: 'Talk', usage: 'Enter', desc: 'send; while Quox works, steer the running turn if this server supports it, else queue it', status: 'active',
100
+ detail: 'Idle: sends. During a turn: steers when the server supports it (shown here once known), delivered at the next model boundary (never kills a running tool). If the server cannot steer, or the turn already finished, it is queued instead and you are told.' },
101
+ { id: 'tab', kind: 'key', group: 'Talk', usage: 'Tab', desc: 'queue your message to run after this turn', status: 'active',
102
+ detail: 'Queued messages show above the composer. Up (empty composer) pulls the last one back to edit. After an interrupt they wait; press Enter on an empty composer to send the next one.' },
103
+ { id: 'esc', kind: 'key', group: 'Talk', usage: 'Esc', desc: 'ask the server to stop the turn (idle: Esc Esc clears the draft to history)', status: 'active',
104
+ detail: 'Asks the server to stop before the next model call or tool; you see "stopping" until the server confirms. A tool already dispatched finishes and is reported, never hidden. Until the server route exists the stream is aborted locally and you are told work already sent may still finish.' },
105
+ { 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' },
106
+ { id: 'editor', kind: 'key', group: 'Talk', usage: 'Ctrl+G', desc: 'edit the draft in $EDITOR', status: 'active' },
107
+ { id: 'stash', kind: 'key', group: 'Talk', usage: 'Ctrl+S', desc: 'stash the draft; on an empty composer, restore it', status: 'active' },
108
+ { id: 'undo-key', kind: 'key', group: 'Talk', usage: 'Ctrl+_', desc: 'undo in the composer', status: 'active' },
109
+ { id: 'paste', kind: 'key', group: 'Talk', usage: 'paste', desc: 'long pastes collapse to [Pasted text #N +L lines] and expand on send', status: 'active' },
110
+ { id: 'ctrlc', kind: 'key', group: 'Talk', usage: 'Ctrl+C', desc: 'clear the draft; during a turn interrupt; twice when idle quits', status: 'active' },
111
+ // Approvals
112
+ { 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' },
113
+ { 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' },
114
+ { 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' },
115
+ { 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',
116
+ 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.' },
117
+ { 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' },
118
+ { 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',
119
+ 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.' },
120
+ { 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',
121
+ 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.' },
122
+ { id: 'authorise', kind: 'command', group: 'Approvals', usage: '/authorise N', desc: 'approve item N with your PIN', server: true, cap: 'approvals', status: 'active', aliases: ['authorize'],
123
+ 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.' },
124
+ { 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',
125
+ 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.' },
126
+ // Skills and rules
127
+ { 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',
128
+ 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.' },
129
+ { 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',
130
+ 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.' },
131
+ { 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',
132
+ 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.' },
133
+ // Conversation
134
+ { 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'],
135
+ 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.' },
136
+ { 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'],
137
+ 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>.' },
138
+ { id: 'compact', kind: 'command', group: 'Conversation', usage: '/compact [show]', desc: 'summarise the conversation on the server, keeping decisions', status: 'active', needs: ['compactConversation'],
139
+ 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.' },
140
+ { 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'],
141
+ 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.' },
142
+ { 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',
143
+ 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.' },
144
+ { 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'],
145
+ 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.' },
146
+ { id: 'help', kind: 'command', group: 'Conversation', usage: '/help [command]', desc: 'this reference, or detail for one command or key', status: 'active',
147
+ detail:
148
+ 'Type / to open the command list. After a command and a space, the next word is ' +
149
+ 'suggested too (providers, live model ids, orgs, agents, slots, options): Up and Down' +
150
+ ' choose, Tab completes, Esc closes, and keep typing to narrow. Suggestions that ' +
151
+ 'cannot be fetched are simply left out.' },
152
+ { 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', dev: true,
153
+ 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.' },
154
+ { 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',
155
+ 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.' },
156
+ { 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',
157
+ 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.' },
158
+ { 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',
159
+ 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.' },
160
+ { id: 'dragon', kind: 'command', group: 'Conversation', usage: '/dragon [explain|ask [question]|sleep|fly|hero|fire|classic]', 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,
161
+ 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.' },
162
+ { 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',
163
+ 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.' },
164
+ { id: 'sound', kind: 'command', group: 'Conversation', usage: '/sound [profile|test]', desc: 'earcons for approvals, failures and long turns: Silent | Minimal | Operational | Full', status: 'active',
165
+ 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.' },
166
+ { 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',
167
+ 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.' },
168
+ { id: 'tip', kind: 'command', group: 'Conversation', usage: '/tip', desc: 'a "did you know" tip about something Quox can do on this install', status: 'active',
169
+ 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.' },
170
+ { 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,
171
+ 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.' },
172
+ { 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'],
173
+ 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.' },
174
+ { 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,
175
+ 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.' },
176
+ { id: 'welcome', kind: 'command', group: 'Conversation', usage: '/welcome', desc: 'Show the welcome screen again', status: 'active' },
177
+ { id: 'about', kind: 'command', group: 'Conversation', usage: '/about', desc: 'who we are, terms, privacy, licence and docs links', status: 'active' },
178
+ { id: 'quit', kind: 'command', group: 'Conversation', usage: '/quit', desc: 'leave; finished work is kept and /resume picks the conversation up', status: 'active', aliases: ['exit'],
179
+ 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.' },
180
+ { id: 'restart', kind: 'command', group: 'Conversation', usage: '/restart [auto [on|off]]', desc: 'restart into the latest installed quox and resume this conversation', status: 'active',
181
+ 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.' },
182
+ { id: 'update', kind: 'command', new: true, group: 'Conversation', usage: '/update', desc: 'update quox to the latest version, then restart', status: 'active',
183
+ 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.' },
184
+ { 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',
185
+ 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.' },
186
+ { 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',
187
+ 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.' },
188
+ { 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!'],
189
+ 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.' },
190
+ { 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',
191
+ 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).' },
192
+ { 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',
193
+ 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`.' },
194
+ { 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',
195
+ 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].' },
196
+ { 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',
197
+ detail: 'Also settable at launch with quox -n <name>. The name is kept on this machine, keyed by conversation, and shown in /sessions.' },
198
+ { 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,
199
+ 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.' },
200
+ { 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,
201
+ 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.' },
202
+ { 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,
203
+ 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.' },
204
+ { 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 },
205
+ { 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'] },
206
+ { 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,
207
+ 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.' },
208
+ { 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,
209
+ 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.' },
210
+ { 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',
211
+ detail: 'The id is in the broadcast line ("ack with /ack bc_...").' },
212
+ { 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,
213
+ 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.' },
214
+ { 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,
215
+ 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.' },
216
+ { id: 'detach', kind: 'command', group: 'Conversation', usage: '/detach', desc: 'back to CommanderQ', status: 'active' },
217
+ { 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',
218
+ 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>.' },
219
+ { 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',
220
+ 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).' },
221
+ // Connect
222
+ { id: 'login', kind: 'command', group: 'Connect', usage: '/login', desc: 'sign in again without leaving quox (email, masked password, MFA code if asked)', status: 'active',
223
+ 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.' },
224
+ { 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',
225
+ 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.' },
226
+ { 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,
227
+ 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.' },
228
+ { 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,
229
+ 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.' },
230
+ { id: 'capabilities', kind: 'command', group: 'Connect', usage: '/capabilities [name]', desc: 'what this Quox can do here: ready, needs setup, not available, planned', status: 'active',
231
+ 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.' },
232
+ // Verbose
233
+ { 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',
234
+ 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.' },
235
+ { 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',
236
+ 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.' },
237
+ { 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',
238
+ 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.' },
239
+ { 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,
240
+ 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.' },
241
+ { id: 'aocl', kind: 'command', group: 'Verbose', usage: '/aocl', desc: 'every AOCL layer of the last turn: outcome, time, reason', status: 'active',
242
+ 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.' },
243
+ { 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' },
244
+ // Awareness
245
+ { 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',
246
+ 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.' },
247
+ { 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',
248
+ 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.' },
249
+ { 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',
250
+ 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.' },
251
+ { 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',
252
+ 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.' },
253
+ { 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',
254
+ 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.' },
255
+ { 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,
256
+ 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.' },
257
+ { 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,
258
+ 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.' },
259
+ { 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',
260
+ 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.' },
261
+ { id: 'automate', kind: 'command', group: 'Skills', usage: '/automate [task]', desc: 'turn this conversation (or a task you name) into a persistent workflow', status: 'active',
262
+ 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.' },
263
+ { 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',
264
+ 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.' },
265
+ { 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,
266
+ 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.' },
267
+ { 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' },
268
+ { 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',
269
+ 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.' },
270
+ { 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',
271
+ 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.' },
272
+ { 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',
273
+ 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.' },
274
+ { 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',
275
+ 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.' },
276
+ { 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'],
277
+ 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.' },
278
+ { 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',
279
+ 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>.' },
280
+ // Context
281
+ { 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,
282
+ 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.' },
283
+ { 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,
284
+ 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.' },
285
+ { id: 'statusline', kind: 'key', group: 'Context', usage: 'status line', desc: 'profile · org · agent · model · mode, under the composer', status: 'active' },
286
+ { 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'] },
287
+ { 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'] },
288
+ { 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',
289
+ 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.' },
290
+ { 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' },
291
+ { id: 'observe', kind: 'command', new: true, group: 'Context', usage: '/observe <text>', desc: 'a note to the observer: posts your text to the session that watches your window', status: 'active', dev: true },
292
+ { id: 'huh', kind: 'command', new: true, group: 'Conversation', usage: '/huh [question]', desc: 'the dragon explains what just happened (same as /dragon explain)', status: 'active' },
293
+ { id: 'sleep', kind: 'command', new: true, group: 'Conversation', usage: '/sleep', desc: 'mark yourself sleeping (sessions stop waiting on approvals, which still queue) and the dragon curls up (/dragon sleep)', status: 'active' },
294
+ { 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,
295
+ 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).' },
296
+ { 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' },
297
+ { 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',
298
+ 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.' },
299
+ { 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'],
300
+ 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.' },
301
+ { 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'],
302
+ 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.' },
303
+ { id: 'cost', kind: 'command', group: 'Context', usage: '/cost', desc: 'tokens and cost per model for this session, from real usage', status: 'active', aliases: ['usage'],
304
+ 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.' },
305
+ { 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',
306
+ 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.' },
307
+ { id: 'doctor', kind: 'command', group: 'Context', usage: '/doctor', desc: 'the quox doctor checks plus chat checks, as rows', status: 'active', needs: ['doctorChecks'],
308
+ 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.' },
309
+ { 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'],
310
+ 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.' },
311
+ { id: 'config', kind: 'command', group: 'Context', usage: '/config', desc: 'settings: theme, motion, verbose, model; saved on this profile', status: 'active', aliases: ['settings'],
312
+ 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.' },
313
+ { 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,
314
+ detail:
315
+ 'Alone: opens a chooser (arrow keys, Enter selects for this session). Favourites are ' +
316
+ 'listed first, numbered, then the live models of each CONNECTED provider (listed by ' +
317
+ 'the provider, never from a fixed list). In the chooser f toggles a favourite, d ' +
318
+ 'makes the highlighted model your default, Esc closes; current and default are ' +
319
+ 'marked. /model list prints what is connected with real status from your org vault ' +
320
+ '(the same source as /connect). /model openai, /model openai gpt-5.4-mini, /model ' +
321
+ 'anthropic, /model auto (server policy) pick directly. Favourites: /model fav lists ' +
322
+ 'them, /model fav add <provider> <model>, /model fav rm <n|model>, /model <n> or ' +
323
+ '/model fav <n> switch at once. Default: /model default shows it, /model default ' +
324
+ '<provider> <model> sets it, /model default clear removes it; new sessions start on ' +
325
+ 'it when its provider is connected (otherwise you are told once and the usual rules ' +
326
+ 'apply). A model id missing from the live list is kept with a warning, because new ' +
327
+ 'models appear before lists update. Favourites and the default are saved to your ' +
328
+ 'account on the server (your user preferences), so they follow you across machines; ' +
329
+ 'this profile keeps a copy for offline use. Same for every provider. The choice is ' +
330
+ 'sent with each turn; the server decides, and the status line shows what it actually ' +
331
+ 'ran. A provider that is not connected is refused with a /connect offer. quox ' +
332
+ '--provider openai does the same for one run. A trailing compact or full picks the ' +
333
+ 'prompt profile: compact is a short system prompt for small or local models (the ' +
334
+ 'default on Ollama, LM Studio and vLLM), full is the default elsewhere; --prompt-' +
335
+ 'profile does the same for one --print run.' },
336
+ { 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,
337
+ detail:
338
+ '/row new walks you through one row with y/N cards and shows what it will say before saving; /row edit <id> changes one. ' +
339
+ 'Without a template a row reads the status-row contract: JSON {"text", "value", "tone", "trend", "link"} or plain text ' +
340
+ '(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). ' +
341
+ 'Rows never carry secrets, and a row from a share bundle never runs a command. ' +
342
+ '/row add <id> url <url> "<template>" [every 60s] [label X] [colour ok] puts one line above the status bar, ' +
343
+ 'refreshed on a timer. /row add <id> cmd "<shell cmd>" "<template>" does the same from a command. The template ' +
344
+ 'fills {{path}} from the JSON (or {{text}} from plain output), plus count(path) and count(path[field=value]); ' +
345
+ 'numbers are shown compactly, for example {{count(runs[state=ok])}}/{{count(runs)}} ok. At most 3 rows, one line each. ' +
346
+ 'url rows follow your account (fetched from THIS machine, http or https only, no credentials sent unless the URL is ' +
347
+ 'your own Quox server). cmd rows stay on this machine and are never synced. A failing row shows a short red ' +
348
+ '"warning" line and retries with backoff; it never blocks typing. /row test <id> shows the raw value; /row off hides and pauses all rows.' },
349
+ { id: 'mentions', kind: 'key', new: true, group: 'Context', usage: '@mention', desc: 'complete agents, hosts, tasks and receipts as you type @', status: 'active',
350
+ 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.' }
351
+ ];
352
+
353
+ // The palette row's one-line wording (commandShort.js); /help keeps the long desc.
354
+ for (const e of REGISTRY) if (e.kind === 'command' && !e.short && SHORT[e.id]) e.short = SHORT[e.id];
355
+
356
+ const norm = (s) => String(s || '').trim().toLowerCase().replace(/^\//, '');
357
+
358
+ /** Dev-only entries (/observe, /note) are invisible unless a dev observer is configured. */
359
+ export const entryVisible = (e) => !e.dev || devObserverOn();
360
+ /** REGISTRY minus entries hidden on this install. */
361
+ export const visibleRegistry = () => REGISTRY.filter(entryVisible);
362
+
363
+ export function findEntry(name, registry = visibleRegistry()) {
364
+ const n = norm(name);
365
+ if (!n) return null;
366
+ return registry.find(e => e.id === n || e.aliases?.includes(n) || norm(e.usage.split(/\s/)[0]) === n || e.usage.toLowerCase() === n) || null;
367
+ }
368
+
369
+ /** 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. */
370
+ export function findCommandEntry(name) {
371
+ const n = norm(name);
372
+ if (!n) return null;
373
+ return visibleRegistry().find(e => e.kind === 'command' && (e.id === n || e.aliases?.includes(n) || norm(e.usage.split(/\s/)[0]) === n)) || null;
374
+ }
375
+
376
+ /** Slash commands only. */
377
+ export const commandNames = () => visibleRegistry().filter(e => e.kind === 'command' && e.status === 'active').flatMap(e => ['/' + e.id, ...(e.aliases || []).map(a => '/' + a)]);
378
+
379
+ /** Pure help model: [{ kind: 'head'|'row'|'dim'|'gap'|'plain', key?, text? }]. */
380
+ const capView = (e, caps = {}) => {
381
+ if (e.id === 'enter' && caps.steer === false) return { ...e, desc: 'send; while Quox works, queue it (this server cannot steer a running turn)', detail: 'This server has no steer route: Enter during a turn queues your message as the next turn.' };
382
+ if (e.id === 'esc' && caps.interrupt === false) return { ...e, desc: 'disconnect from the turn (this server cannot stop it; idle: Esc Esc clears the draft)', detail: 'This server has no interrupt route: Esc aborts only your stream. Work already sent may still finish and be charged.' };
383
+ return e;
384
+ };
385
+
386
+ /** /help status: the status rows under the composer, one line per item (there is no hover in a terminal). */
387
+ const STATUS_LINE_HELP = [
388
+ { kind: 'gap' }, { kind: 'head', text: 'The status line, item by item' },
389
+ { kind: 'row', key: '● agent X', text: 'the agent your turns go to (/attach); ○ agent: none when you talk to Quox itself' },
390
+ { kind: 'row', key: '⬢ slot X', text: 'this window\'s own address, held by this session (/slot); others reach you with /say X' },
391
+ { kind: 'row', key: '⬢ groups a 2, b', text: 'other slots joined as group rooms (/join, /leave, /groups); a number is unread lines' },
392
+ { kind: 'row', key: 'profile X', text: 'the quox login this window uses (/profile)' },
393
+ { kind: 'row', key: 'org X', text: 'the organisation everything here acts in' },
394
+ { kind: 'row', key: 'mode ask first', text: 'risky actions wait for your approval; auto acts without asking, plan only plans' },
395
+ { kind: 'row', key: 'rules: QUOX.md', text: 'project rules sent with every turn (/rules off drops them)' },
396
+ { kind: 'row', key: '↻ auto-restart', text: 'restarts into a newer installed quox when you are idle (/restart auto)' },
397
+ { kind: 'row', key: '── model ─', text: 'the divider under the composer names the provider and model (/model)' }
398
+ ];
399
+
400
+ /** registry: the entries to list (default: this install's visible registry; tests pass a fixture). */
401
+ export function helpModel(topic, caps = {}, registry = visibleRegistry()) {
402
+ if (topic) {
403
+ const e0 = findEntry(topic, registry);
404
+ const e = e0 && capView(e0, caps);
405
+ if (!e) return [{ kind: 'plain', text: `No help for "${topic}". Try /help` }];
406
+ const statusLine = norm(topic) === 'status' ? STATUS_LINE_HELP : [];
407
+ return [
408
+ { kind: 'head', text: e.usage },
409
+ { kind: 'plain', text: e.desc },
410
+ ...(e.detail ? [{ kind: 'plain', text: e.detail }] : []),
411
+ ...(e.status === 'planned' ? [{ kind: 'dim', text: 'Planned, not built yet.' }] : []),
412
+ ...statusLine
413
+ ];
414
+ }
415
+ const out = [];
416
+ for (const g of GROUPS) {
417
+ const active = registry.filter(e => e.group === g && e.status === 'active').map(e => capView(e, caps));
418
+ if (!active.length) continue;
419
+ if (out.length) out.push({ kind: 'gap' });
420
+ out.push({ kind: 'head', text: g });
421
+ for (const e of active) out.push({ kind: 'row', key: e.usage, text: e.desc });
422
+ }
423
+ const planned = registry.filter(e => e.status === 'planned');
424
+ if (planned.length) {
425
+ out.push({ kind: 'gap' }, { kind: 'head', text: 'Coming', dim: true });
426
+ for (const e of planned) out.push({ kind: 'dim', key: e.usage, text: e.desc });
427
+ }
428
+ out.push({ kind: 'gap' }, { kind: 'dim', text: '/help <command> for detail, e.g. /help esc' });
429
+ return out;
430
+ }
431
+
432
+ /** 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). */
433
+ export function suggestCommands(input, max = 3) {
434
+ const q = norm(input);
435
+ if (!q) return [];
436
+ const names = [...new Set(commandNames())];
437
+ 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]; };
438
+ 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) }; })
439
+ .filter(x => x.d <= Math.max(2, Math.floor(q.length / 3)))
440
+ .sort((a, b) => a.d - b.d || a.len - b.len || a.n.localeCompare(b.n)).slice(0, max).map(x => x.n);
441
+ }
442
+
443
+ 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'] };
444
+ const HINT_STOP = new Set(['the', 'and', 'for', 'with', 'your', 'you', 'this', 'that', 'what', 'new']);
445
+ /**
446
+ * Hints for an unknown /word: the edit-distance neighbours (suggestCommands) first, else commands whose id, aliases or
447
+ * description share a keyword with it (so /active finds /sessions, "everything live in your org"). Max 3.
448
+ * -> [{ 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.
449
+ */
450
+ export function nearestCommands(input, max = 3) {
451
+ const q = norm(input);
452
+ const out = suggestCommands(input, max).map(n => ({ n, score: 100 }));
453
+ const seen = new Set(out.map(x => findEntry(x.n)?.id));
454
+ const toks = new Set(q.split(/[^a-z0-9]+/).filter(t => t.length >= 3 && !HINT_STOP.has(t)));
455
+ for (const t of [...toks]) for (const syn of HINT_SYNONYMS[t] || []) toks.add(syn);
456
+ if (toks.size && !out.length) { // keyword hints only when spelling found nothing (the spelling line is pinned by FW1)
457
+ const stem = (w) => w.slice(0, Math.max(4, w.length - 3));
458
+ for (const e of visibleRegistry()) {
459
+ if (e.kind !== 'command' || e.status !== 'active' || seen.has(e.id)) continue;
460
+ 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)));
461
+ let score = 0;
462
+ 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; }
463
+ if (score) out.push({ n: '/' + e.id, score, kw: true });
464
+ }
465
+ }
466
+ 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() }));
467
+ }
468
+
469
+ // Commands whose last argument is free text: a pasted line that the terminal wrapped is still one command.
470
+ 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', 'dragon', 'deny', 'wish', 'challenge']);
471
+ /** "/say a long\n text" -> { text: '/say a long text', lines: 2 }; null when it is not a wrapped free-text command. */
472
+ export function joinWrappedSlash(text) {
473
+ const t = String(text || '');
474
+ if (!t.startsWith('/') || !t.includes('\n')) return null;
475
+ const lines = t.split('\n').map(l => l.trim()).filter(Boolean);
476
+ if (lines.length < 2 || !FREE_TEXT_CMDS.has(lines[0].slice(1).split(/\s+/)[0].toLowerCase())) return null;
477
+ if (lines.slice(1).some(l => l.startsWith('/'))) return null;
478
+ return { text: lines.join(' '), lines: lines.length };
479
+ }