quoxcli 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -0
  3. package/bin/quox.js +99 -126
  4. package/lib/api/endpoints/actions.js +15 -0
  5. package/lib/api/endpoints/admin.js +46 -0
  6. package/lib/api/endpoints/agent-schedules.js +26 -0
  7. package/lib/api/endpoints/agents.js +7 -0
  8. package/lib/api/endpoints/assistants.js +12 -0
  9. package/lib/api/endpoints/away.js +15 -0
  10. package/lib/api/endpoints/blog.js +16 -0
  11. package/lib/api/endpoints/chat.js +14 -0
  12. package/lib/api/endpoints/contactsq.js +6 -0
  13. package/lib/api/endpoints/conversations.js +14 -0
  14. package/lib/api/endpoints/devServerRegistry.js +25 -0
  15. package/lib/api/endpoints/engagement.js +26 -0
  16. package/lib/api/endpoints/evidence.js +20 -0
  17. package/lib/api/endpoints/executions.js +13 -0
  18. package/lib/api/endpoints/fetchAllowlist.js +27 -0
  19. package/lib/api/endpoints/files.js +14 -0
  20. package/lib/api/endpoints/hat.js +44 -0
  21. package/lib/api/endpoints/inbox.js +20 -0
  22. package/lib/api/endpoints/index.js +26 -0
  23. package/lib/api/endpoints/loops.js +26 -0
  24. package/lib/api/endpoints/memory.js +19 -0
  25. package/lib/api/endpoints/mind.js +13 -0
  26. package/lib/api/endpoints/mon.js +179 -0
  27. package/lib/api/endpoints/objectives.js +12 -0
  28. package/lib/api/endpoints/plugins.js +18 -0
  29. package/lib/api/endpoints/presence.js +33 -0
  30. package/lib/api/endpoints/quoxplan.js +7 -0
  31. package/lib/api/endpoints/quoxseo.js +12 -0
  32. package/lib/api/endpoints/router.js +17 -0
  33. package/lib/api/endpoints/safety.js +7 -0
  34. package/lib/api/endpoints/server.js +21 -0
  35. package/lib/api/endpoints/setup.js +6 -0
  36. package/lib/api/endpoints/skills.js +20 -0
  37. package/lib/api/endpoints/stepup.js +20 -0
  38. package/lib/api/endpoints/teams.js +2 -2
  39. package/lib/api/endpoints/triggers.js +26 -0
  40. package/lib/api/endpoints/v1.js +120 -0
  41. package/lib/api/endpoints/ward.js +7 -0
  42. package/lib/api/endpoints/workflows.js +7 -0
  43. package/lib/api/endpoints/x.js +8 -0
  44. package/lib/commands/actions.js +131 -0
  45. package/lib/commands/admin.js +16 -0
  46. package/lib/commands/agent.js +170 -27
  47. package/lib/commands/agentAttach.js +140 -13
  48. package/lib/commands/approvals.js +78 -1
  49. package/lib/commands/aside.js +24 -0
  50. package/lib/commands/ask.js +146 -0
  51. package/lib/commands/assistant.js +59 -4
  52. package/lib/commands/auth.js +8 -2
  53. package/lib/commands/backup.js +8 -4
  54. package/lib/commands/blog.js +110 -0
  55. package/lib/commands/brain.js +105 -14
  56. package/lib/commands/build.js +19 -5
  57. package/lib/commands/chat.js +180 -26
  58. package/lib/commands/chatCompanion.js +27 -0
  59. package/lib/commands/chatState.js +45 -0
  60. package/lib/commands/claude.js +35 -38
  61. package/lib/commands/contacts.js +134 -4
  62. package/lib/commands/contactsSendAt.js +21 -0
  63. package/lib/commands/conversation.js +35 -4
  64. package/lib/commands/dashboard.js +43 -0
  65. package/lib/commands/department.js +28 -7
  66. package/lib/commands/devResident.js +15 -3
  67. package/lib/commands/devSupervisor.js +13 -1
  68. package/lib/commands/discord.js +75 -39
  69. package/lib/commands/done.js +98 -28
  70. package/lib/commands/engagement.js +107 -6
  71. package/lib/commands/evidence.js +154 -0
  72. package/lib/commands/ext.js +23 -0
  73. package/lib/commands/fetchAllowlist.js +71 -0
  74. package/lib/commands/file.js +38 -1
  75. package/lib/commands/fleet.js +336 -4
  76. package/lib/commands/governance.js +12 -3
  77. package/lib/commands/gripe.js +44 -13
  78. package/lib/commands/hat.js +254 -9
  79. package/lib/commands/helpers.js +18 -1
  80. package/lib/commands/hyperion.js +58 -0
  81. package/lib/commands/image.js +68 -0
  82. package/lib/commands/inbox.js +74 -23
  83. package/lib/commands/kuma.js +1050 -0
  84. package/lib/commands/kumaSla.js +174 -0
  85. package/lib/commands/licence.js +52 -155
  86. package/lib/commands/loop.js +291 -0
  87. package/lib/commands/lucid.js +10 -4
  88. package/lib/commands/memory.js +85 -8
  89. package/lib/commands/mind.js +71 -0
  90. package/lib/commands/mon.js +69 -2
  91. package/lib/commands/monitoring.js +1 -1
  92. package/lib/commands/motion.js +2 -2
  93. package/lib/commands/nucleus.js +48 -17
  94. package/lib/commands/objective.js +59 -1
  95. package/lib/commands/offload.js +89 -0
  96. package/lib/commands/onboard.js +2 -1
  97. package/lib/commands/org.js +30 -3
  98. package/lib/commands/peek.js +28 -0
  99. package/lib/commands/plan.js +38 -0
  100. package/lib/commands/plugin.js +110 -0
  101. package/lib/commands/presence.js +89 -0
  102. package/lib/commands/provider.js +217 -0
  103. package/lib/commands/qlarity.js +19 -3
  104. package/lib/commands/quoxchat.js +13 -4
  105. package/lib/commands/quoxseo.js +69 -0
  106. package/lib/commands/reach.js +2 -1
  107. package/lib/commands/research.js +79 -0
  108. package/lib/commands/router.js +77 -0
  109. package/lib/commands/rows.js +367 -0
  110. package/lib/commands/run.js +82 -10
  111. package/lib/commands/scope.js +2 -2
  112. package/lib/commands/server.js +405 -0
  113. package/lib/commands/setup.js +33 -7
  114. package/lib/commands/shortcut.js +41 -0
  115. package/lib/commands/skills.js +68 -9
  116. package/lib/commands/slot.js +260 -7
  117. package/lib/commands/status.js +1 -1
  118. package/lib/commands/team.js +3 -1
  119. package/lib/commands/tips.js +23 -0
  120. package/lib/commands/tool.js +133 -45
  121. package/lib/commands/trace.js +12 -7
  122. package/lib/commands/trigger.js +95 -0
  123. package/lib/commands/update.js +46 -0
  124. package/lib/commands/vault.js +31 -8
  125. package/lib/commands/vm.js +237 -0
  126. package/lib/commands/voice.js +34 -3
  127. package/lib/commands/volt.js +45 -3
  128. package/lib/commands/walkthrough.js +32 -0
  129. package/lib/commands/watch.js +73 -21
  130. package/lib/commands/welcome.js +14 -0
  131. package/lib/commands/workflow.js +189 -32
  132. package/lib/commands/x.js +13 -4
  133. package/lib/commands/youtube.js +172 -0
  134. package/lib/core/actingAs.js +2 -2
  135. package/lib/core/actionItems.js +115 -0
  136. package/lib/core/activeSummary.js +186 -0
  137. package/lib/core/agentAlias.js +2 -0
  138. package/lib/core/agentFamilies.js +7 -0
  139. package/lib/core/aside.js +54 -0
  140. package/lib/core/askDecision.js +60 -0
  141. package/lib/core/auth.js +164 -13
  142. package/lib/core/callerSource.js +1 -1
  143. package/lib/core/cliBuild.js +62 -0
  144. package/lib/core/client.js +152 -35
  145. package/lib/core/commandRegistrars.js +130 -0
  146. package/lib/core/config.js +143 -25
  147. package/lib/core/customHats.js +168 -0
  148. package/lib/core/doneEngineFetch.js +8 -0
  149. package/lib/core/errors.js +329 -20
  150. package/lib/core/findingReconcile.js +90 -0
  151. package/lib/core/global-flags.js +105 -5
  152. package/lib/core/governedSession.js +1 -3
  153. package/lib/core/hyperion.js +102 -0
  154. package/lib/core/inboxResolution.js +65 -0
  155. package/lib/core/lazyCommands.js +4 -1
  156. package/lib/core/liveSessionsReport.js +1 -1
  157. package/lib/core/loopGlyph.js +97 -0
  158. package/lib/core/offload.js +533 -0
  159. package/lib/core/org-override.js +1 -1
  160. package/lib/core/orgResolve.js +70 -0
  161. package/lib/core/output.js +11 -11
  162. package/lib/core/peek.js +126 -0
  163. package/lib/core/profileUsers.js +51 -0
  164. package/lib/core/promptCancel.js +18 -0
  165. package/lib/core/repoPath.js +11 -0
  166. package/lib/core/residentSeed.js +3 -1
  167. package/lib/core/rootOptions.js +39 -0
  168. package/lib/core/router.js +168 -0
  169. package/lib/core/selfUpdate.js +188 -0
  170. package/lib/core/sessionHeartbeat.js +12 -8
  171. package/lib/core/sessionsOverview.js +64 -0
  172. package/lib/core/shortcut.js +83 -0
  173. package/lib/core/slotKeeper.js +248 -3
  174. package/lib/core/slotWake.js +120 -0
  175. package/lib/core/supervisorEngine.js +71 -2
  176. package/lib/core/version.js +1 -1
  177. package/lib/core/vm.js +366 -0
  178. package/lib/core/vmTui.js +86 -0
  179. package/lib/core/walkthrough.js +35 -0
  180. package/lib/dragon/companionView.js +34 -0
  181. package/lib/dragon/frames.js +118 -0
  182. package/lib/dragon/jobs.js +123 -0
  183. package/lib/dragon/renderWorker.js +15 -0
  184. package/lib/dragon/select.js +72 -0
  185. package/lib/evidence/bundleExport.js +47 -0
  186. package/lib/evidence/bundleVerify.js +77 -0
  187. package/lib/hyperion/deck.js +29 -0
  188. package/lib/lucid/LucidCard.jsx +68 -0
  189. package/lib/lucid/LucidIntro.jsx +45 -58
  190. package/lib/lucid/cards.js +86 -0
  191. package/lib/lucid/markdown.js +81 -0
  192. package/lib/lucid/motion.js +56 -0
  193. package/lib/quoxon/EXTENSIONS.md +23 -0
  194. package/lib/quoxon/activity.js +14 -2
  195. package/lib/quoxon/aocl.js +117 -0
  196. package/lib/quoxon/api.js +690 -25
  197. package/lib/quoxon/app.js +3586 -250
  198. package/lib/quoxon/approvalFacts.js +96 -0
  199. package/lib/quoxon/approvalSentence.js +44 -0
  200. package/lib/quoxon/approvalsView.js +384 -0
  201. package/lib/quoxon/asideView.js +40 -0
  202. package/lib/quoxon/ask.js +121 -0
  203. package/lib/quoxon/away.js +52 -0
  204. package/lib/quoxon/bang.js +224 -0
  205. package/lib/quoxon/bulkRisk.js +17 -0
  206. package/lib/quoxon/capabilities.js +13 -6
  207. package/lib/quoxon/cells.js +53 -0
  208. package/lib/quoxon/commandShort.js +124 -0
  209. package/lib/quoxon/commands.js +429 -39
  210. package/lib/quoxon/connect.js +210 -23
  211. package/lib/quoxon/connectCard.js +19 -2
  212. package/lib/quoxon/consent.js +86 -0
  213. package/lib/quoxon/copy.js +4 -3
  214. package/lib/quoxon/countdownLine.js +20 -0
  215. package/lib/quoxon/deck.js +30 -0
  216. package/lib/quoxon/demo.autoplay.js +29 -0
  217. package/lib/quoxon/demo.fixtures.js +1 -1
  218. package/lib/quoxon/demo.js +46 -3
  219. package/lib/quoxon/devObserver.js +20 -0
  220. package/lib/quoxon/dividerAocl.js +192 -0
  221. package/lib/quoxon/dividerRule.js +57 -0
  222. package/lib/quoxon/dragonCompanion.js +255 -0
  223. package/lib/quoxon/dragonModes.js +151 -0
  224. package/lib/quoxon/dragonMoments.js +82 -0
  225. package/lib/quoxon/dragonReads.js +68 -0
  226. package/lib/quoxon/dragonView.js +41 -0
  227. package/lib/quoxon/earlyInput.js +40 -0
  228. package/lib/quoxon/echo.js +15 -0
  229. package/lib/quoxon/egg.js +2 -2
  230. package/lib/quoxon/events.js +266 -26
  231. package/lib/quoxon/extensions.js +276 -0
  232. package/lib/quoxon/feedback.js +50 -0
  233. package/lib/quoxon/gallery.js +21 -7
  234. package/lib/quoxon/handoffOffer.js +78 -0
  235. package/lib/quoxon/hatSwitch.js +37 -0
  236. package/lib/quoxon/headless.js +161 -0
  237. package/lib/quoxon/hostUrls.js +41 -0
  238. package/lib/quoxon/inboxView.js +79 -0
  239. package/lib/quoxon/indicator.js +6 -2
  240. package/lib/quoxon/installCli.js +79 -0
  241. package/lib/quoxon/intent.js +12 -1
  242. package/lib/quoxon/keys.js +51 -8
  243. package/lib/quoxon/launcher.js +103 -0
  244. package/lib/quoxon/loopCard.js +456 -0
  245. package/lib/quoxon/loopCards.js +180 -0
  246. package/lib/quoxon/loopStream.js +87 -0
  247. package/lib/quoxon/main.js +101 -160
  248. package/lib/quoxon/markdown.js +45 -21
  249. package/lib/quoxon/markdownSettle.js +19 -0
  250. package/lib/quoxon/matrixDomains.json +46 -0
  251. package/lib/quoxon/mentions.js +90 -0
  252. package/lib/quoxon/mind.js +103 -0
  253. package/lib/quoxon/mode.js +46 -0
  254. package/lib/quoxon/model.js +132 -26
  255. package/lib/quoxon/modelPrefs.js +345 -0
  256. package/lib/quoxon/motion/clock.js +3 -2
  257. package/lib/quoxon/motion/level.js +91 -0
  258. package/lib/quoxon/mouse.js +222 -0
  259. package/lib/quoxon/noai.js +57 -0
  260. package/lib/quoxon/objectiveOffer.js +82 -0
  261. package/lib/quoxon/objects/away.js +72 -0
  262. package/lib/quoxon/objects/fetch.js +17 -5
  263. package/lib/quoxon/objects/fleet.js +149 -0
  264. package/lib/quoxon/objects/health.js +186 -0
  265. package/lib/quoxon/objects/index.js +46 -7
  266. package/lib/quoxon/objects/mirror.js +137 -0
  267. package/lib/quoxon/objects/model.js +33 -0
  268. package/lib/quoxon/objects/peek.js +17 -0
  269. package/lib/quoxon/objects/proof.js +12 -4
  270. package/lib/quoxon/objects/prove.js +130 -0
  271. package/lib/quoxon/objects/replay.js +125 -0
  272. package/lib/quoxon/objects/sitrep.js +58 -0
  273. package/lib/quoxon/objects/view.js +11 -0
  274. package/lib/quoxon/onboarding/copy.js +32 -4
  275. package/lib/quoxon/onboarding/credential.js +17 -4
  276. package/lib/quoxon/onboarding/dockerOffer.js +45 -0
  277. package/lib/quoxon/onboarding/failureCause.js +33 -0
  278. package/lib/quoxon/onboarding/finish.js +15 -0
  279. package/lib/quoxon/onboarding/index.js +34 -7
  280. package/lib/quoxon/onboarding/installRunner.js +64 -5
  281. package/lib/quoxon/onboarding/preflight.js +17 -3
  282. package/lib/quoxon/onboarding/probe.js +65 -0
  283. package/lib/quoxon/onboarding/shortcutOffer.js +28 -0
  284. package/lib/quoxon/onboarding/view.js +76 -19
  285. package/lib/quoxon/orgLabel.js +10 -0
  286. package/lib/quoxon/orgSwitch.js +59 -0
  287. package/lib/quoxon/ownBroadcast.js +22 -0
  288. package/lib/quoxon/palette.js +43 -0
  289. package/lib/quoxon/planSteps.js +51 -0
  290. package/lib/quoxon/print.js +12 -5
  291. package/lib/quoxon/profileSwitch.js +53 -0
  292. package/lib/quoxon/promptHistory.js +41 -0
  293. package/lib/quoxon/providers.js +48 -0
  294. package/lib/quoxon/qart.js +20 -51
  295. package/lib/quoxon/qdragon.js +410 -0
  296. package/lib/quoxon/qdragonFly.js +62 -0
  297. package/lib/quoxon/qdragonScenes.js +282 -0
  298. package/lib/quoxon/qeyes.js +213 -0
  299. package/lib/quoxon/qmotion.js +75 -37
  300. package/lib/quoxon/render.js +740 -124
  301. package/lib/quoxon/resend.js +72 -0
  302. package/lib/quoxon/roster.js +117 -17
  303. package/lib/quoxon/route.js +6 -5
  304. package/lib/quoxon/rows.js +954 -0
  305. package/lib/quoxon/rowsStore.js +80 -0
  306. package/lib/quoxon/screenContext.js +150 -0
  307. package/lib/quoxon/screenFrames.js +254 -0
  308. package/lib/quoxon/serverChip.js +42 -0
  309. package/lib/quoxon/session.js +182 -2
  310. package/lib/quoxon/sessionDead.js +17 -0
  311. package/lib/quoxon/signin.js +125 -0
  312. package/lib/quoxon/slash/agents.js +112 -0
  313. package/lib/quoxon/slash/argSources.js +65 -0
  314. package/lib/quoxon/slash/args.js +65 -0
  315. package/lib/quoxon/slash/automate.js +89 -0
  316. package/lib/quoxon/slash/broadcast.js +140 -0
  317. package/lib/quoxon/slash/broadcastRun.js +108 -0
  318. package/lib/quoxon/slash/card.js +30 -0
  319. package/lib/quoxon/slash/challenge.js +114 -0
  320. package/lib/quoxon/slash/cliCommand.js +94 -0
  321. package/lib/quoxon/slash/cliLeaves.json +1023 -0
  322. package/lib/quoxon/slash/compact.js +1 -1
  323. package/lib/quoxon/slash/confirm.js +30 -0
  324. package/lib/quoxon/slash/crystallize.js +69 -0
  325. package/lib/quoxon/slash/dashboard.js +136 -0
  326. package/lib/quoxon/slash/draft.js +54 -0
  327. package/lib/quoxon/slash/evidenceCmd.js +34 -0
  328. package/lib/quoxon/slash/hatWalk.js +206 -0
  329. package/lib/quoxon/slash/inverses.js +77 -0
  330. package/lib/quoxon/slash/killer.js +24 -0
  331. package/lib/quoxon/slash/killerApi.js +16 -0
  332. package/lib/quoxon/slash/ledger.js +13 -8
  333. package/lib/quoxon/slash/listView.js +3 -1
  334. package/lib/quoxon/slash/modelCmd.js +261 -0
  335. package/lib/quoxon/slash/palette.js +22 -8
  336. package/lib/quoxon/slash/paletteController.js +117 -7
  337. package/lib/quoxon/slash/panels.js +21 -6
  338. package/lib/quoxon/slash/picker.js +20 -2
  339. package/lib/quoxon/slash/qpeCmd.js +35 -0
  340. package/lib/quoxon/slash/restart.js +95 -13
  341. package/lib/quoxon/slash/resume.js +18 -1
  342. package/lib/quoxon/slash/rowCmd.js +157 -0
  343. package/lib/quoxon/slash/rowWalk.js +175 -0
  344. package/lib/quoxon/slash/settings.js +16 -6
  345. package/lib/quoxon/slash/spawn.js +177 -0
  346. package/lib/quoxon/slash/stepUp.js +274 -0
  347. package/lib/quoxon/slash/stepUpCard.js +60 -0
  348. package/lib/quoxon/slash/stepUpCmds.js +78 -0
  349. package/lib/quoxon/slash/todoCmd.js +122 -0
  350. package/lib/quoxon/slash/undoApi.js +38 -0
  351. package/lib/quoxon/slash/undoCmd.js +49 -0
  352. package/lib/quoxon/slash/undoWish.js +15 -0
  353. package/lib/quoxon/slash/watch.js +164 -0
  354. package/lib/quoxon/slash/wish.js +23 -0
  355. package/lib/quoxon/slash/wishCmd.js +32 -0
  356. package/lib/quoxon/slotChat.js +300 -0
  357. package/lib/quoxon/slotFormat.js +195 -0
  358. package/lib/quoxon/slotRun.js +131 -0
  359. package/lib/quoxon/sound/mapping.js +2 -1
  360. package/lib/quoxon/stray.js +22 -0
  361. package/lib/quoxon/suggestCommand.js +90 -0
  362. package/lib/quoxon/term.js +46 -11
  363. package/lib/quoxon/textBuffer.js +176 -2
  364. package/lib/quoxon/theme.js +58 -25
  365. package/lib/quoxon/tips.js +433 -0
  366. package/lib/quoxon/toolCards.js +30 -0
  367. package/lib/quoxon/toolError.js +41 -0
  368. package/lib/quoxon/transcript.js +42 -4
  369. package/lib/quoxon/web/qface.js +18 -10
  370. package/lib/quoxon/welcome.js +1 -1
  371. package/lib/quoxon/whatsnew.js +136 -0
  372. package/lib/quoxon/whatsnew.jsonl +134 -0
  373. package/lib/quoxon/windowOrg.js +123 -0
  374. package/lib/server/devServers.js +137 -0
  375. package/lib/server/diag.js +129 -0
  376. package/lib/server/doctor.js +71 -0
  377. package/lib/server/install.js +149 -0
  378. package/lib/server/pluginEnable.js +161 -0
  379. package/lib/server/skew.js +56 -0
  380. package/lib/server/snapshot.js +131 -0
  381. package/lib/server/status.js +128 -0
  382. package/lib/server/sys.js +65 -0
  383. package/lib/server/uninstall.js +63 -0
  384. package/lib/server/update.js +234 -0
  385. package/lib/toolOutputShape.js +315 -0
  386. package/lib/tui/views/HyperionDeck.jsx +79 -0
  387. package/lib/tui/views/LucidDeck.jsx +209 -217
  388. package/package.json +5 -1
  389. package/vendor/dragonlab/SOURCE +4 -0
  390. package/vendor/dragonlab/design/cipher/v1/cipher-master.svg +46 -0
  391. package/vendor/dragonlab/design/cipher/v2/character.mjs +49 -0
  392. package/vendor/dragonlab/design/cipher/v2/review.mjs +33 -0
  393. package/vendor/dragonlab/design/cipher/v2/watch.mjs +56 -0
  394. package/vendor/dragonlab/design/drago/v1/character.mjs +19 -0
  395. package/vendor/dragonlab/design/drago/v1/drago-master.svg +56 -0
  396. package/vendor/dragonlab/design/drago/v2/character.mjs +87 -0
  397. package/vendor/dragonlab/design/drago/v4/character.mjs +67 -0
  398. package/vendor/dragonlab/design/drago/v4/profile.mjs +35 -0
  399. package/vendor/dragonlab/design/drago/v4/review.mjs +33 -0
  400. package/vendor/dragonlab/design/drago/v4/watch.mjs +56 -0
  401. package/vendor/dragonlab/design/hyperion/v1/hyperion-master.svg +56 -0
  402. package/vendor/dragonlab/design/hyperion/v2/character.mjs +79 -0
  403. package/vendor/dragonlab/design/hyperion/v2/review.mjs +33 -0
  404. package/vendor/dragonlab/design/hyperion/v2/watch.mjs +56 -0
  405. package/vendor/dragonlab/design/lucid/recovery/arrival.mjs +22 -0
  406. package/vendor/dragonlab/design/lucid/recovery/character.mjs +106 -0
  407. package/vendor/dragonlab/design/lucid/recovery/layout.mjs +50 -0
  408. package/vendor/dragonlab/design/lucid/recovery/lucid-master.svg +78 -0
  409. package/vendor/dragonlab/design/lucid/recovery/motion.mjs +42 -0
  410. package/vendor/dragonlab/design/lucid/recovery/render.mjs +121 -0
  411. package/vendor/dragonlab/design/lucid/recovery/review.mjs +68 -0
  412. package/vendor/dragonlab/design/lucid/recovery/verify.mjs +84 -0
  413. package/vendor/dragonlab/design/lucid/recovery/watch.mjs +56 -0
  414. package/vendor/dragonlab/design/seer/v1/seer-master.svg +53 -0
  415. package/vendor/dragonlab/design/seer/v2/character.mjs +88 -0
  416. package/vendor/dragonlab/design/seer/v3/character.mjs +33 -0
  417. package/vendor/dragonlab/design/seer/v4/character.mjs +66 -0
  418. package/vendor/dragonlab/design/seer/v4/review.mjs +33 -0
  419. package/vendor/dragonlab/design/seer/v4/watch.mjs +56 -0
  420. package/vendor/dragonlab/design/stage3/companion-review.mjs +28 -0
  421. package/vendor/dragonlab/design/stage3/sentinel-art.js +39 -0
  422. package/vendor/dragonlab/design/stage3/sentinel-braille.js +216 -0
  423. package/vendor/dragonlab/design/stage3/sentinel-character.js +257 -0
  424. package/vendor/dragonlab/design/stage3/sentinel-icon.js +103 -0
  425. package/vendor/dragonlab/design/stage3/sentinel-warden-eyes.js +95 -0
  426. package/vendor/dragonlab/design/stage3/svg-coverage.js +148 -0
  427. package/vendor/dragonlab/factory/svg-rig.mjs +63 -0
  428. package/vendor/dragonlab/integration/companions-v1/characters.mjs +88 -0
  429. package/vendor/dragonlab/integration/companions-v1/cipher.mjs +2 -0
  430. package/vendor/dragonlab/integration/companions-v1/companion.mjs +63 -0
  431. package/vendor/dragonlab/integration/companions-v1/drago.mjs +2 -0
  432. package/vendor/dragonlab/integration/companions-v1/hyperion.mjs +2 -0
  433. package/vendor/dragonlab/integration/companions-v1/lucid.mjs +2 -0
  434. package/vendor/dragonlab/integration/companions-v1/seer.mjs +2 -0
  435. package/vendor/dragonlab/integration/companions-v1/sentinel.mjs +2 -0
  436. package/vendor/dragonlab/integration/companions-v1/view.mjs +28 -0
  437. package/vendor/dragonlab/integration/hyperion-v3/activity.mjs +76 -0
  438. package/vendor/dragonlab/lib/power-effects.js +294 -0
  439. package/vendor/dragonlab/lib/power-registry.js +74 -0
  440. package/vendor/dragonlab/vendor/quox-dragon/qart.js +348 -0
  441. package/vendor/dragonlab/vendor/quox-dragon/qeyes.js +213 -0
  442. package/lib/core/websiteSession.js +0 -148
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Loop commands (QLOOP-LIVE): observe a QLoop loop as it runs.
3
+ *
4
+ * quox loop watch <id> [--spool|--quiet] [--since n] live, one QuoxGlyph line per event
5
+ * quox loop status <id> snapshot
6
+ * quox loop list running + recent loops (org)
7
+ *
8
+ * `watch` reconnects with ?since=<last event_seq> when the stream drops, and exits with the loop's
9
+ * outcome: 0 completed, 1 failed, 2 halted, 3 interrupted, 4 connection lost for good.
10
+ * Events come from GET /api/v1/loops/:id/events (design: ~/quoxon-work/qloop-live-design.md).
11
+ */
12
+
13
+ import { formatOutput, getOutputFormat } from '../core/output.js';
14
+ import { resolveEndpoint } from '../api/endpoints.js';
15
+ import { loopLine, renderEvent, exitCodeFor, elapsedLabel } from '../core/loopGlyph.js';
16
+ import { QuoxApiError, QuoxAuthError, QuoxUserError } from '../core/errors.js';
17
+
18
+ const MAX_SILENT_RETRIES = 10;
19
+ const BACKOFF_BASE_MS = 1000;
20
+ const BACKOFF_CAP_MS = 10000;
21
+
22
+ const sleepReal = (ms) => new Promise((r) => setTimeout(r, ms));
23
+
24
+ /** SQLite "YYYY-MM-DD HH:MM:SS" (UTC, no zone) or ISO to epoch ms; null if unreadable. */
25
+ function toMs(ts) {
26
+ if (!ts) return null;
27
+ const iso = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(ts) ? `${ts.replace(' ', 'T')}Z` : ts;
28
+ const n = Date.parse(iso);
29
+ return Number.isNaN(n) ? null : n;
30
+ }
31
+
32
+ const SETTLE_MAX_MS = 90000;
33
+ const SETTLE_POLL_MS = 4000;
34
+
35
+ /**
36
+ * After loop.finished: wait (bounded) for the loop's receipts to be WITNESSED by WARD, asking the snapshot
37
+ * (which re-checks WARD and records what it finds), printing each receipt as it flips. Returns the finished
38
+ * event with the final witnessed count. A snapshot that cannot be read ends the wait and says so; it never
39
+ * reads as "0 witnessed".
40
+ */
41
+ async function settleReceipts({ client, id, finished, write, sleep, ctx, maxMs, signal, seen = new Set() }) {
42
+ const ep = resolveEndpoint('loops.get', { id: encodeURIComponent(id) });
43
+ let witnessed = finished.witnessed;
44
+ write(loopLine('receipts', `${witnessed}/${finished.receipts} witnessed, waiting for WARD to sign the covering tip`, 'busy'));
45
+ let waited = 0;
46
+ while (witnessed < finished.receipts && waited < maxMs && !(signal && signal.aborted)) {
47
+ await sleep(SETTLE_POLL_MS);
48
+ waited += SETTLE_POLL_MS;
49
+ let snap;
50
+ try { snap = await client.get(ep.path, { service: ep.service }); } catch (err) {
51
+ write(loopLine('receipts', `could not re-check WARD (${oneLineErr(err)}); \`quox loop status ${id}\` will`, 'warn'));
52
+ break;
53
+ }
54
+ const items = (snap && snap.receipts && snap.receipts.items) || [];
55
+ for (const r of items) {
56
+ if (r.status === 'WITNESSED' && !seen.has(r.envelope_id)) {
57
+ seen.add(r.envelope_id);
58
+ write(loopLine('receipt', `${String(r.envelope_id)} WITNESSED`, 'ok'));
59
+ }
60
+ }
61
+ witnessed = snap && snap.receipts ? snap.receipts.witnessed : witnessed;
62
+ }
63
+ if (witnessed < finished.receipts) write(loopLine('receipts', `${finished.receipts - witnessed} still pending after ${Math.round(waited / 1000)}s; they are witnessed when the next tip signs (quox loop status ${id})`, 'warn'));
64
+ return { ...finished, witnessed };
65
+ }
66
+
67
+ /**
68
+ * Follow one loop to its end. Returns the process exit code. Exported for tests.
69
+ * @param {object} p
70
+ * @param {object} p.client cli client (stream)
71
+ * @param {string} p.id
72
+ * @param {object} p.snapshot loops.get result (title, max_turns, started_at)
73
+ * @param {{spool?: boolean, quiet?: boolean, since?: number, json?: boolean, noSettle?: boolean, settleMs?: number}} p.opts
74
+ * @param {(line: string) => void} p.write
75
+ * @param {(ms: number) => Promise<void>} [p.sleep]
76
+ * @param {AbortSignal} [p.signal]
77
+ */
78
+ export async function watchLoop({ client, id, snapshot, opts = {}, write, sleep = sleepReal, signal }) {
79
+ const ep = resolveEndpoint('loops.events', { id: encodeURIComponent(id) });
80
+ let lastSeq = Number(opts.since) || 0;
81
+ let startedMs = toMs(snapshot && snapshot.started_at);
82
+ const ctx = { title: snapshot && snapshot.title, maxTurns: snapshot && snapshot.max_turns, startedMs, spool: !!opts.spool };
83
+ const showQuiet = new Set(['loop.started', 'loop.observe', 'loop.approval', 'loop.finished']);
84
+ let failures = 0;
85
+ let backoff = BACKOFF_BASE_MS;
86
+ const witnessedShown = new Set(); // receipts already printed from loop.ward events, so the settle phase does not repeat them
87
+
88
+ while (!signal || !signal.aborted) {
89
+ let gotEvent = false;
90
+ let ended = null;
91
+ try {
92
+ for await (const ev of client.stream(ep.path, { service: ep.service, query: { since: lastSeq }, signal })) {
93
+ // `event: end` frame (an old loop that never logged a finish): no type, a reason
94
+ if (ev && !ev.type && ev.reason) { ended = ev; break; }
95
+ if (!ev || typeof ev.type !== 'string' || !ev.type.startsWith('loop.')) continue;
96
+ gotEvent = true;
97
+ if (typeof ev.event_seq === 'number') {
98
+ if (ev.event_seq <= lastSeq) continue; // replay overlap after a reconnect
99
+ lastSeq = ev.event_seq;
100
+ }
101
+ if (ev.type === 'loop.ward' && ev.status === 'WITNESSED') witnessedShown.add(ev.envelope_id);
102
+ if (!ctx.startedMs && ev.type === 'loop.started') ctx.startedMs = Date.parse(ev.at);
103
+ if (opts.json) write(JSON.stringify(ev));
104
+ else if (ev.type !== 'loop.finished' && !(opts.quiet && !showQuiet.has(ev.type))) for (const l of renderEvent(ev, ctx)) write(l);
105
+ if (ev.type === 'loop.finished') {
106
+ // the loop is over; its receipts are WITNESSED when WARD's next signed tip covers them (about a minute),
107
+ // so say so and follow them in rather than printing a final count that is about to be stale
108
+ const last = (!opts.json && !opts.noSettle && ev.receipts > ev.witnessed)
109
+ ? await settleReceipts({ client, id, finished: ev, write, sleep, ctx, maxMs: opts.settleMs ?? SETTLE_MAX_MS, signal, seen: witnessedShown })
110
+ : ev;
111
+ if (!opts.json) for (const l of renderEvent(last, ctx).slice(-1)) write(l);
112
+ if (!opts.json && !opts.quiet) write(` quox loop status ${id} # receipts and proof`);
113
+ return exitCodeFor(ev.state);
114
+ }
115
+ }
116
+ } catch (err) {
117
+ // a refusal is not a dropped connection: 401/403/404 and friends end the watch now with the server's words
118
+ if (err instanceof QuoxAuthError || (err instanceof QuoxApiError && err.status >= 400 && err.status < 500 && err.status !== 429)) throw err;
119
+ if (signal && signal.aborted) return 130;
120
+ failures += 1;
121
+ if (failures > MAX_SILENT_RETRIES) {
122
+ write(loopLine('connection lost', `could not reach the loop stream after ${MAX_SILENT_RETRIES} tries; resume with: quox loop watch ${id} --since ${lastSeq}`, 'fail'));
123
+ return 4;
124
+ }
125
+ if (!opts.quiet && !opts.json) write(loopLine('reconnecting', `stream dropped (${oneLineErr(err)}); resuming after event ${lastSeq}`, 'warn'));
126
+ await sleep(backoff);
127
+ backoff = Math.min(BACKOFF_CAP_MS, backoff * 2);
128
+ continue;
129
+ }
130
+ if (ended) {
131
+ write(loopLine(ctx.title || 'loop', `ended without a recorded finish (${ended.status || 'unknown'}): ${ended.reason}`, 'warn'));
132
+ return ended.status === 'completed' ? 0 : 1;
133
+ }
134
+ // stream closed by the server (restart, proxy idle limit) before loop.finished: resume, quietly counting
135
+ if (gotEvent) { failures = 0; backoff = BACKOFF_BASE_MS; } else failures += 1;
136
+ if (failures > MAX_SILENT_RETRIES) {
137
+ write(loopLine('connection lost', `the loop stream keeps closing with nothing new; resume with: quox loop watch ${id} --since ${lastSeq}`, 'fail'));
138
+ return 4;
139
+ }
140
+ await sleep(gotEvent ? 250 : backoff);
141
+ if (!gotEvent) backoff = Math.min(BACKOFF_CAP_MS, backoff * 2);
142
+ }
143
+ return 130;
144
+ }
145
+
146
+ function oneLineErr(err) {
147
+ return String((err && err.message) || err).replace(/\s+/g, ' ').slice(0, 80);
148
+ }
149
+
150
+ export function statusLines(s, { diagnostics = false } = {}) {
151
+ const lines = [];
152
+ const turns = `turn ${s.turn}${s.max_turns ? `/${s.max_turns}` : ''}`;
153
+ lines.push(loopLine(s.title || s.objective_id, `${s.state}, ${turns}`, s.state === 'running' ? 'busy' : (s.state === 'completed' ? 'ok' : (s.state === 'completed_with_errors' || s.state === 'halted' || s.state === 'interrupted' ? 'warn' : (s.finished ? 'fail' : 'busy')))));
154
+ lines.push(` id ${s.objective_id}`);
155
+ lines.push(` host ${s.host || '-'} backend ${s.backend || '-'} scope ${(s.scope || []).join(',') || '-'}`);
156
+ if (s.until) lines.push(` until ${s.until}${s.every_seconds ? ` (a turn every ${s.every_seconds}s)` : ''}`);
157
+ else if (s.every_seconds) lines.push(` every ${s.every_seconds}s`);
158
+ if (s.workspace) lines.push(` workspace ${s.workspace}`);
159
+ const r = s.receipts || { total: 0, witnessed: 0 };
160
+ lines.push(` receipts ${r.witnessed}/${r.total} witnessed${s.ward_check_error ? ` (WARD could not be asked just now: ${s.ward_check_error})` : ''}`);
161
+ if (s.observe) lines.push(` observe ${JSON.stringify(s.observe)}`);
162
+ if (s.reason && s.finished) lines.push(` reason ${s.reason}`);
163
+ if (diagnostics && s.diagnostics) lines.push(` diagnostics ${s.diagnostics}`);
164
+ if ((s.last_turns || []).length) {
165
+ lines.push(' last turns');
166
+ for (const t of s.last_turns) {
167
+ const tools = (t.tools || []).map((x) => `${x.tool}${x.denied ? ' denied' : (x.ok ? '' : ' FAILED')}`).join(', ');
168
+ lines.push(` ${t.turn} ${t.summary}${tools ? ` [${tools}]` : ''} ${t.envelope_id ? `${String(t.envelope_id)} ${t.ward}` : ''}`.trimEnd());
169
+ }
170
+ }
171
+ if (s.started_at) {
172
+ const st = toMs(s.started_at);
173
+ const en = toMs(s.completed_at) || Date.now();
174
+ if (st) lines.push(` elapsed ${elapsedLabel(en - st)}`);
175
+ }
176
+ return lines;
177
+ }
178
+
179
+ export function registerLoopCommands(program, ctx) {
180
+ const { client, auth } = ctx;
181
+
182
+ const loop = program
183
+ .command('loop')
184
+ .description('Observe QLoop loops as they run (watch, status, list)');
185
+
186
+ loop
187
+ .command('watch')
188
+ .description('Follow a loop live: one QuoxGlyph line per event, reconnecting on drop. Exits 0 completed, 1 failed, 2 halted, 3 interrupted, 4 connection lost')
189
+ .argument('<id>', 'loop (objective) ID')
190
+ .option('--spool', 'also stream the loop\'s output text under each turn line')
191
+ .option('--quiet', 'only the headline lines: started, observe points (milestones, errors, approvals, stalls), the final line')
192
+ .option('--since <n>', 'resume after this event_seq (printed by a previous watch; default: from the start)')
193
+ .option('--no-wait-receipts', 'print the final line as soon as the loop ends instead of waiting (up to 90s) for its receipts to be witnessed')
194
+ .action(async (id, options) => {
195
+ auth.requireAuth();
196
+ if (options.spool && options.quiet) throw new QuoxUserError('--spool and --quiet cancel each other out; pick one.');
197
+ const since = options.since !== undefined ? Number(options.since) : 0;
198
+ if (!Number.isInteger(since) || since < 0) throw new QuoxUserError('--since needs a whole number event_seq (0 or more).');
199
+ const snap = resolveEndpoint('loops.get', { id: encodeURIComponent(id) });
200
+ const snapshot = await client.get(snap.path, { service: snap.service });
201
+ const ac = new AbortController();
202
+ const onSigint = () => ac.abort();
203
+ process.once('SIGINT', onSigint);
204
+ let code;
205
+ try {
206
+ code = await watchLoop({
207
+ client, id, snapshot,
208
+ opts: { spool: options.spool, quiet: options.quiet, since, json: getOutputFormat() === 'json', noSettle: options.waitReceipts === false },
209
+ write: (l) => console.log(l),
210
+ signal: ac.signal,
211
+ });
212
+ } finally {
213
+ process.removeListener('SIGINT', onSigint);
214
+ }
215
+ if (code === 130) console.log(` stopped watching; the loop keeps running. Resume: quox loop watch ${id}`);
216
+ if (code) process.exitCode = code;
217
+ });
218
+
219
+ loop
220
+ .command('status')
221
+ .description('Snapshot of a loop: state, turn n/max, host, backend, scope, last turns, receipts')
222
+ .argument('<id>', 'loop (objective) ID')
223
+ .option('--diagnostics', 'also show the raw diagnostics behind the finish reason (hidden by default)')
224
+ .action(async (id, options = {}) => {
225
+ auth.requireAuth();
226
+ const ep = resolveEndpoint('loops.get', { id: encodeURIComponent(id) });
227
+ const data = await client.get(ep.path, { service: ep.service });
228
+ if (getOutputFormat() === 'json') {
229
+ const out = options.diagnostics || !data || typeof data !== 'object' ? data : { ...data, diagnostics: undefined };
230
+ console.log(JSON.stringify(out, null, 2)); return;
231
+ }
232
+ for (const l of statusLines(data, { diagnostics: !!options.diagnostics })) console.log(l);
233
+ });
234
+
235
+ loop
236
+ .command('list')
237
+ .description('Running and recent loops in this org')
238
+ .option('--limit <n>', 'max results', '20')
239
+ .action(async (options) => {
240
+ auth.requireAuth();
241
+ const ep = resolveEndpoint('loops.list');
242
+ const data = await client.get(ep.path, { service: ep.service, query: { limit: options.limit } });
243
+ formatOutput({
244
+ columns: ['objective_id', 'title', 'state', 'turn', 'backend', 'created_at'],
245
+ headers: ['ID', 'Title', 'State', 'Turn', 'Backend', 'Created'],
246
+ rows: (data.loops || []).map((l) => ({ ...l, turn: l.max_turns ? `${l.turn}/${l.max_turns}` : String(l.turn) })),
247
+ }, { format: program.opts().output, emptyMessage: 'No loops in this org yet.' });
248
+ });
249
+
250
+ // SECURITY containment (2026-10-26): which orgs may run loops in-server (core-local) while a security review is open.
251
+ // Instance-admin only (the server enforces it; a non-admin gets a 403). Stored server-side in instance_config.
252
+ const coreLocal = loop
253
+ .command('core-local')
254
+ .description('Instance admin: which workspaces may run loops inside the server (limited while a security review is open)');
255
+
256
+ coreLocal
257
+ .command('list')
258
+ .description('Show the workspaces allowed to run loops inside the server')
259
+ .action(async () => {
260
+ auth.requireAuth();
261
+ const ep = resolveEndpoint('admin.instance.core_local_orgs.list');
262
+ const r = await client.get(ep.path, { service: ep.service });
263
+ if (getOutputFormat() === 'json') { console.log(JSON.stringify(r, null, 2)); return; }
264
+ if (!(r.orgs || []).length) { console.log('No workspace may run loops inside the server (loops need a dev server).'); return; }
265
+ for (const o of r.orgs) console.log(o);
266
+ });
267
+
268
+ coreLocal
269
+ .command('add')
270
+ .description('Allow a workspace to run loops inside the server (instance admin)')
271
+ .argument('<orgId>', 'workspace (org) id')
272
+ .action(async (orgId) => {
273
+ auth.requireAuth();
274
+ const ep = resolveEndpoint('admin.instance.core_local_orgs.add');
275
+ const r = await client.post(ep.path, { service: ep.service, body: { org_id: orgId } });
276
+ if (getOutputFormat() === 'json') { console.log(JSON.stringify(r, null, 2)); return; }
277
+ console.log(`Added ${orgId}. ${(r.orgs || []).length} workspace(s) may now run loops inside the server.`);
278
+ });
279
+
280
+ coreLocal
281
+ .command('remove')
282
+ .description('Stop a workspace running loops inside the server (instance admin)')
283
+ .argument('<orgId>', 'workspace (org) id')
284
+ .action(async (orgId) => {
285
+ auth.requireAuth();
286
+ const ep = resolveEndpoint('admin.instance.core_local_orgs.remove', { orgId: encodeURIComponent(orgId) });
287
+ const r = await client.delete(ep.path, { service: ep.service });
288
+ if (getOutputFormat() === 'json') { console.log(JSON.stringify(r, null, 2)); return; }
289
+ console.log(`Removed ${orgId}. ${(r.orgs || []).length} workspace(s) may still run loops inside the server.`);
290
+ });
291
+ }
@@ -102,6 +102,7 @@ export function registerLucidCommands(program, ctx) {
102
102
  if (o.json) { console.log(JSON.stringify(data, null, 2)); return; }
103
103
  if (o.agent) { console.log(data.text_agent ?? data.text ?? ''); return; }
104
104
  if (o.spoken) { console.log(data.spoken ?? data.text ?? ''); return; }
105
+ if (o.markdown) { const { renderSitrepMarkdown } = await import('../lucid/markdown.js'); console.log(renderSitrepMarkdown(data)); return; }
105
106
  const { renderSitrep } = await import('../lucid/sitrep.js');
106
107
  const tty = Boolean(process.stdout.isTTY);
107
108
  process.stdout.write(renderSitrep(data, {
@@ -118,14 +119,15 @@ export function registerLucidCommands(program, ctx) {
118
119
  const sitrepOpts = (c) => c
119
120
  .option('--agent', 'compact, id-rich form for agents')
120
121
  .option('--json', 'the full structured situation')
121
- .option('--spoken', 'sentences only, as read on the phone');
122
+ .option('--spoken', 'sentences only, as read on the phone')
123
+ .option('--markdown', 'chat-ready Markdown for Discord, Matrix and slots (fits one message)');
122
124
 
123
125
  sitrepOpts(lu
124
126
  .command('sitrep')
125
127
  .description('One-shot formatted snapshot (areas, what needs you, what changed, what I cannot see)'))
126
128
  .action(async (o) => showSitrep(o));
127
129
 
128
- // Top-level shortcut so any human or agent can ask for a sitrep: `quox sitrep [--agent|--json|--spoken]`.
130
+ // Top-level shortcut so any human or agent can ask for a sitrep: `quox sitrep [--agent|--json|--spoken|--markdown]`.
129
131
  sitrepOpts(program
130
132
  .command('sitrep')
131
133
  .description('LUCID situation report: what needs you, every area, what changed, what LUCID cannot see'))
@@ -393,9 +395,13 @@ export function registerLucidCommands(program, ctx) {
393
395
  if (wantJson(cmd)) { console.log(JSON.stringify(data, null, 2)); return; }
394
396
  if (data.status === 'filed') {
395
397
  printSuccess(`Filed${data.approval_id ? ` (approval ${data.approval_id})` : ''}. Approve it in Approvals.`);
396
- } else if (data.status === 'manual') {
398
+ } else if (data.status === 'manual' && data.command) {
397
399
  console.log('This one is manual. Run this yourself:');
398
- console.log(` ${data.command ?? ''}`);
400
+ console.log(` ${data.command}`);
401
+ } else if (data.status === 'manual') {
402
+ // #992: a 'say' measure has no command; print what it says and how to ask, never a blank instruction.
403
+ console.log(data.say || (m && m.label) || 'There is nothing to run for this one.');
404
+ console.log(` quox lucid ask "${((m && m.label) || 'Look into it').replace(/"/g, "'")} (${ref})"`);
399
405
  } else if (data.status === 'opened') {
400
406
  console.log(`Opened: ${data.url ?? ''}`);
401
407
  } else {
@@ -26,6 +26,7 @@ import { resolveEndpoint } from '../api/endpoints.js';
26
26
  import { QuoxError } from '../core/errors.js';
27
27
  import { resolveOrgId } from './helpers.js';
28
28
  import { resolveMyAttach } from './agentAttach.js';
29
+ import { hasExplicitProfile } from '../core/actingAs.js';
29
30
 
30
31
  // QUOXIDENTITY P9: which agent this session acts as is resolved session-scoped
31
32
  // (QUOX_T subtree / QUOX_ATTACH_AGENT override / single-attach fallback), so
@@ -46,7 +47,12 @@ async function callMemoryTool({ client, auth, program }, toolName, input) {
46
47
  const body = { tool_name: toolName, input };
47
48
  const requestOptions = { service: ep.service, body };
48
49
 
49
- const lease = detectSoleAttachLease();
50
+ // An explicit -p/--profile (or QUOX_PROFILE) is the caller saying "as me":
51
+ // it wins over the session's attach lease, same precedence as
52
+ // core/actingAs. Before this, `quox -p moo memory search` inside an attached
53
+ // session went out on the lease token and 401'd (proof engine 2026-10-14).
54
+ const argv = (program.rawArgs || process.argv).slice(2);
55
+ const lease = hasExplicitProfile(argv) ? null : detectSoleAttachLease();
50
56
  if (lease) {
51
57
  requestOptions.noAuth = true;
52
58
  requestOptions.headers = { Authorization: `Bearer ${lease.token}` };
@@ -260,16 +266,87 @@ export function registerMemoryCommands(program, ctx) {
260
266
  }, { format: program.opts().output });
261
267
  });
262
268
 
269
+ // QUOXMIND #1111: the old view counted only rows under YOUR user id, so an
270
+ // org holding hundreds of agent-written memories looked nearly empty. Default
271
+ // is now the org view (per agent, per writer), falling back honestly.
263
272
  memory
264
273
  .command('stats')
265
- .description('Show memory statistics')
266
- .action(async () => {
274
+ .description('Show memory statistics: the whole org by agent and writer (org admins), else your own')
275
+ .option('--mine', 'only memories written under your own login')
276
+ .action(async (options) => {
277
+ auth.requireAuth();
278
+ const ep = resolveEndpoint('memory.stats.list');
279
+ let data;
280
+ let note = '';
281
+ if (options.mine) {
282
+ data = await client.get(ep.path, { service: ep.service, query: {} });
283
+ } else {
284
+ try {
285
+ data = await client.get(ep.path, { service: ep.service, query: { scope: 'org' } });
286
+ } catch (err) {
287
+ if (err?.status !== 403) throw err;
288
+ data = await client.get(ep.path, { service: ep.service, query: {} });
289
+ note = 'Org-wide counts need an org admin or owner. Showing only memories written under your own login; agents write under their own identities, so the org holds more.';
290
+ }
291
+ }
292
+ const format = program.opts().output;
293
+ if (format === 'json') { console.log(JSON.stringify(note ? { ...data, note } : data, null, 2)); return; }
294
+ if (data?.scope !== 'org') {
295
+ formatDetail(data.stats || data, { format, title: 'Memory Stats (your login only)' });
296
+ console.log(note || 'Counts only memories written under your own login. `quox memory stats` without --mine shows the whole org (org admins).');
297
+ return;
298
+ }
299
+ const m = data.memories || {};
300
+ console.log(`Memory Stats (org): ${m.total ?? 0} valid memories, ${m.not_valid ?? 0} superseded or deleted (kept for history), ${data.pinned ?? 0} pinned, last write ${data.last_write_at || 'never'}`);
301
+ if (m.by_type) console.log(`By type: ${Object.entries(m.by_type).map(([k, v]) => `${k} ${v}`).join(', ')}`);
302
+ formatOutput({
303
+ columns: ['agent', 'count'], headers: ['Agent', 'Memories'],
304
+ rows: Object.entries(data.by_agent || {}).map(([agent, count]) => ({ agent, count })),
305
+ }, { format });
306
+ formatOutput({
307
+ columns: ['user_id', 'kind', 'count', 'agents'], headers: ['Writer', 'Kind', 'Memories', 'Agents'],
308
+ rows: (data.by_user || []).map((u) => ({ ...u, agents: (u.agents || []).join(',') })),
309
+ }, { format });
310
+ console.log('Valid = not deleted and not superseded. A writer is the login a memory was saved under: your own, an agent identity, service/system (internal agents), or org-shared beliefs.');
311
+ });
312
+
313
+ memory
314
+ .command('core')
315
+ .description('Show the core set: memories loaded into every chat turn (pinned, plus auto-promoted decisions and facts rated high or critical)')
316
+ .option('--limit <n>', 'max memories', '20')
317
+ .action(async (options) => {
267
318
  auth.requireAuth();
268
- const data = await client.get(resolveEndpoint('memory.stats.list').path, { service: resolveEndpoint('memory.stats.list').service });
269
- formatDetail(data.stats || data, {
270
- format: program.opts().output,
271
- title: 'Memory Stats',
272
- });
319
+ const ep = resolveEndpoint('memory.core-set');
320
+ const data = await client.get(ep.path, { service: ep.service, query: { limit: options.limit } });
321
+ formatOutput({
322
+ columns: ['id', 'core_reason', 'agent_id', 'memory_type', 'content'],
323
+ headers: ['ID', 'Why', 'Agent', 'Type', 'Content'],
324
+ rows: (data.memories || []).map((m) => ({ ...m, content: (m.content || '').slice(0, 80) })),
325
+ }, { format: program.opts().output, raw: data });
326
+ });
327
+
328
+ memory
329
+ .command('pin')
330
+ .description('Pin a memory into the core set so every chat turn carries it')
331
+ .argument('<id>', 'memory ID')
332
+ .action(async (id) => {
333
+ auth.requireAuth();
334
+ const ep = resolveEndpoint('memory.pin', { id });
335
+ const data = await client.post(ep.path, { service: ep.service, body: {} });
336
+ if (program.opts().output === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
337
+ printSuccess(`Pinned ${id} into the core set.`);
338
+ });
339
+
340
+ memory
341
+ .command('unpin')
342
+ .description('Take a memory out of the core set (also stops it being auto-promoted)')
343
+ .argument('<id>', 'memory ID')
344
+ .action(async (id) => {
345
+ auth.requireAuth();
346
+ const ep = resolveEndpoint('memory.unpin', { id });
347
+ const data = await client.delete(ep.path, { service: ep.service });
348
+ if (program.opts().output === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
349
+ printSuccess(`Unpinned ${id}: it stays a memory, but is no longer in the core set.`);
273
350
  });
274
351
 
275
352
  memory
@@ -0,0 +1,71 @@
1
+ /**
2
+ * `quox mind implants|implant <id>|health`: QuoxMind cognitive implants (read-only).
3
+ * Collector routes under /api/v1/mind (org-scoped). Implants with no recorded
4
+ * activity say so; nothing is invented.
5
+ */
6
+ import { formatOutput } from '../core/output.js';
7
+ import { resolveEndpoint } from '../api/endpoints.js';
8
+ import { resolveOrgId } from './helpers.js';
9
+
10
+ export function registerMindCommands(program, ctx) {
11
+ const { client, auth } = ctx;
12
+ const get = async (name, params) => {
13
+ auth.requireAuth();
14
+ const ep = resolveEndpoint(name, params);
15
+ return client.get(ep.path, { service: ep.service, query: { org_id: resolveOrgId(auth, program) } });
16
+ };
17
+ const isJson = () => program.opts().output === 'json';
18
+
19
+ const mind = program.command('mind').description('QuoxMind: the cognitive implants (read-only)');
20
+
21
+ mind
22
+ .command('implants')
23
+ .description('List the cognitive implants: purpose, status, last activity')
24
+ .action(async () => {
25
+ const data = await get('mind.implants');
26
+ const rows = data.implants || [];
27
+ formatOutput(rows.map((i) => ({
28
+ ID: i.short,
29
+ NAME: i.name,
30
+ PURPOSE: i.purpose,
31
+ STATUS: i.status,
32
+ 'LAST ACTIVITY': i.last_activity || 'no activity recorded',
33
+ // QUOXMIND-PROOF: firing evidence from the implant's own table (null = not tracked).
34
+ 'LAST FIRED': i.activity_error ? 'unknown' : (typeof i.fired_7d === 'number' ? (i.last_fired || 'never') : 'not tracked'),
35
+ '7D': typeof i.fired_7d === 'number' && !i.activity_error ? i.fired_7d : '-',
36
+ })), {
37
+ format: program.opts().output,
38
+ columns: ['ID', 'NAME', 'STATUS', 'LAST FIRED', '7D', 'PURPOSE'],
39
+ emptyMessage: 'No implants registered.',
40
+ raw: rows,
41
+ });
42
+ });
43
+
44
+ mind
45
+ .command('implant <id>')
46
+ .description('One implant in detail (e.g. predict, reflect, repobrain, qortex)')
47
+ .action(async (id) => {
48
+ const d = await get('mind.implant', { id });
49
+ if (isJson()) { console.log(JSON.stringify(d, null, 2)); return; }
50
+ console.log(`${d.name} (${d.short}) · ${d.status}${d.tier ? ` · ${d.tier}` : ''}`);
51
+ console.log(d.description || d.purpose);
52
+ if (d.inspired_by) console.log(`Inspired by: ${d.inspired_by}`);
53
+ if (d.summary) console.log(`Now: ${d.summary}`);
54
+ console.log(d.activity);
55
+ if (typeof d.fired_7d === 'number' && !d.activity_error) console.log(`Fired: ${d.last_fired || 'never'} · ${d.fired_7d} in the last 7 days${d.activity_source ? ` (from ${d.activity_source})` : ''}`);
56
+ else if (d.activity_error) console.log(`Fired: unknown (${d.activity_error})`);
57
+ for (const [k, v] of Object.entries(d.metrics || {})) if (v !== null && typeof v !== 'object') console.log(` ${k}: ${v}`);
58
+ });
59
+
60
+ mind
61
+ .command('health')
62
+ .description('Summary of QuoxMind health across all implants')
63
+ .action(async () => {
64
+ const d = await get('mind.health');
65
+ if (isJson()) { console.log(JSON.stringify(d, null, 2)); return; }
66
+ console.log(`${d.total} implants: ${Object.entries(d.by_status).map(([k, v]) => `${v} ${k}`).join(', ')}`);
67
+ console.log(`${d.with_activity} with recorded activity, ${d.no_activity} with no activity recorded`);
68
+ if (typeof d.fired_7d_total === 'number') console.log(`Last 7 days: ${d.fired_7d_implants} implants fired (${d.fired_7d_total} firings), ${d.silent_7d} silent, ${d.activity_unknown} not tracked`);
69
+ console.log(d.average_health === null ? 'Average health: not enough data' : `Average health: ${d.average_health} (over ${d.scored} scored)`);
70
+ });
71
+ }
@@ -2,7 +2,7 @@
2
2
  * Monitoring commands: Docker fleet, Wazuh security monitoring, AGH-chain
3
3
  * agent honesty leaderboard, Prometheus metrics, and monitoring config.
4
4
  *
5
- * quox mon docker fleet|containers|container|logs|stats|stacks|df|action
5
+ * quox mon docker fleet|containers|container|inspect|logs|stats|stacks|df|action
6
6
  * quox mon wazuh status|agents|alerts|vulns|deploy-history|deploy-agent|deploy-bulk|active-response
7
7
  * quox mon agh-chain leaderboard|agent|events
8
8
  * quox mon metrics
@@ -36,6 +36,47 @@ async function confirmOrSkip(program, options, message) {
36
36
  return ans.confirm;
37
37
  }
38
38
 
39
+
40
+ /** One readable line per fact from the inspect health block (null when the container has no healthcheck). */
41
+ function healthRows(h) {
42
+ if (!h) return null;
43
+ const rows = [];
44
+ const streak = h.failing_streak ? ` (failing ${h.failing_streak} times)` : '';
45
+ rows.push(`health: ${h.status || 'unknown'}${streak}`);
46
+ if (h.test) {
47
+ const parts = [h.interval && `every ${h.interval}`, h.timeout && `timeout ${h.timeout}`, h.retries != null && `retries ${h.retries}`].filter(Boolean);
48
+ rows.push(`probe: ${h.test}${parts.length ? ' ' + parts.join(', ') : ''}`);
49
+ }
50
+ for (const p of h.last_probes || []) {
51
+ const o = String(p.output || '').replace(/\s+/g, ' ').trim();
52
+ rows.push(`last: exit ${p.exit_code}${o ? ` '${o}'` : ''}${p.end ? ` at ${p.end}` : ''}`);
53
+ }
54
+ return rows;
55
+ }
56
+
57
+ /**
58
+ * DEVFLEET-DOCKER: an error body is not data. If the collector reports a refused/failed Docker
59
+ * read, say so (stderr) and set a non-zero exit instead of printing an empty list. Partial fleets
60
+ * print the failed hosts as warnings. Returns true when the whole read failed (caller stops).
61
+ */
62
+ function reportDockerReadFailures(data) {
63
+ if (!data) return false;
64
+ const failedHosts = Array.isArray(data.hosts_failed) ? data.hosts_failed
65
+ : (data.success === false && Array.isArray(data.hosts) ? data.hosts.filter((h) => h && h.ok === false) : []);
66
+ if (data.success === false) {
67
+ const tag = data.refused ? ' (refused, error_code ' + (data.error_code || 'approval_required') + ')' : '';
68
+ console.error('Docker read failed' + tag + ': ' + (data.error || 'unknown error'));
69
+ for (const h of failedHosts) console.error(' ' + h.host + ': ' + (h.error || 'failed'));
70
+ process.exitCode = 1;
71
+ return true;
72
+ }
73
+ if (data.partial) {
74
+ console.error('Partial result: ' + failedHosts.length + ' host(s) could not be read; the list below is incomplete.');
75
+ for (const h of failedHosts) console.error(' ' + h.host + ': ' + (h.error || 'failed'));
76
+ }
77
+ return false;
78
+ }
79
+
39
80
  export function registerMonCommands(program, ctx) {
40
81
  const { client, auth } = ctx;
41
82
 
@@ -59,6 +100,7 @@ export function registerMonCommands(program, ctx) {
59
100
  auth.requireAuth();
60
101
  const ep = resolveEndpoint('mon.docker.fleet.list');
61
102
  const data = await client.get(ep.path, { service: ep.service, query: options.refresh ? { refresh: 'true' } : undefined });
103
+ if (reportDockerReadFailures(data)) return;
62
104
  const hosts = data.hosts || [];
63
105
  formatOutput({
64
106
  // fleet #392: docker fleet rows are id/containers_total/docker_version; ip and status don't exist.
@@ -82,6 +124,7 @@ export function registerMonCommands(program, ctx) {
82
124
  if (options.all === false) query.all = 'false';
83
125
  if (options.refresh) query.refresh = 'true';
84
126
  const data = await client.get(ep.path, { service: ep.service, query });
127
+ if (reportDockerReadFailures(data)) return;
85
128
  formatOutput({
86
129
  columns: ['host_id', 'id', 'name', 'image', 'state', 'status'],
87
130
  headers: ['Host', 'ID', 'Name', 'Image', 'State', 'Status'],
@@ -98,6 +141,9 @@ export function registerMonCommands(program, ctx) {
98
141
  auth.requireAuth();
99
142
  const ep = resolveEndpoint('mon.docker.containers.get', { id });
100
143
  const data = await client.get(ep.path, { service: ep.service, query: { host: options.host } });
144
+ // quox#1001: a 200 with success:false (e.g. "Bastion not configured") is a failure, not an
145
+ // empty container. Surface the error on stderr + exit non-zero instead of rendering a blank card.
146
+ if (reportDockerReadFailures(data)) return;
101
147
  formatDetail(data, {
102
148
  format: program.opts().output,
103
149
  title: `Container: ${id} (${options.host})`,
@@ -106,6 +152,24 @@ export function registerMonCommands(program, ctx) {
106
152
  });
107
153
  });
108
154
 
155
+ docker
156
+ .command('inspect')
157
+ .description('Show container state and health (failing probe command and last results)')
158
+ .argument('<id>', 'container ID')
159
+ .requiredOption('--host <hostId>', 'host the container lives on')
160
+ .action(async (id, options) => {
161
+ auth.requireAuth();
162
+ const ep = resolveEndpoint('mon.docker.containers.inspect', { id });
163
+ const data = await client.get(ep.path, { service: ep.service, query: { host: options.host } });
164
+ if (reportDockerReadFailures(data)) return;
165
+ if (program.opts().output === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
166
+ const d = data.details || {};
167
+ console.log(`Container: ${id} (${options.host})`);
168
+ console.log(`state: ${d.status || 'unknown'} · image: ${d.image || 'unknown'}`);
169
+ const rows = healthRows(d.health);
170
+ console.log(rows ? rows.join('\n') : 'health: no healthcheck configured');
171
+ });
172
+
109
173
  docker
110
174
  .command('logs')
111
175
  .description('Show container logs')
@@ -143,6 +207,7 @@ export function registerMonCommands(program, ctx) {
143
207
  auth.requireAuth();
144
208
  const ep = resolveEndpoint('mon.docker.stacks.list');
145
209
  const data = await client.get(ep.path, { service: ep.service, query: options.host ? { host: options.host } : undefined });
210
+ if (reportDockerReadFailures(data)) return;
146
211
  formatOutput({
147
212
  columns: ['host_id', 'name', 'status', 'services'],
148
213
  headers: ['Host', 'Stack', 'Status', 'Services'],
@@ -181,7 +246,9 @@ export function registerMonCommands(program, ctx) {
181
246
  const ep = resolveEndpoint('mon.docker.containers.action', { id, action });
182
247
  const data = await client.post(ep.path, { service: ep.service, body: { host: options.host } });
183
248
  if (data.success === false) {
184
- printWarning(`Action failed: ${data.error || 'unknown error'}`);
249
+ // quox#1001: surface the real server error and exit non-zero (was exit 0 = success on failure).
250
+ printWarning(`Action failed: ${data.error || data.message || 'unknown error'}`);
251
+ process.exitCode = 1;
185
252
  return;
186
253
  }
187
254
  printSuccess(`Container ${id}: ${action} succeeded.`);
@@ -35,7 +35,7 @@ export function registerMonitoringCommands(program, ctx) {
35
35
  // nothing is stored); logged-in now actually sends the token.
36
36
  const data = await client.get(resolveEndpoint('v1.health.aggregate.list').path, {
37
37
  service: resolveEndpoint('v1.health.aggregate.list').service,
38
- timeout: 5000,
38
+ timeout: 15000, // #930: aggregate fan-out, 5s too tight under load
39
39
  });
40
40
  formatDetail(data, { format: program.opts().output, title: 'Platform Health' });
41
41
  });