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
@@ -25,14 +25,10 @@ const QDP = process.env.QDP_CLI_PATH
25
25
 
26
26
  /**
27
27
  * Per-verb usage, mirrored from qdp.mjs's own header doc block (bin/qdp.mjs
28
- * in the quox-discord-pro repo). qdp itself has no --help handling of its
29
- * own, and this command hand-rolls arg dispatch (allowUnknownOption +
30
- * helpOption(false), see below) rather than commander subcommands, so
31
- * `--help`/`-h` was previously just another unrecognised arg forwarded
32
- * straight through to qdp — which silently ran the verb instead of showing
33
- * help. Worst case for a mutating verb (bind/unbind/provision): "--help"
34
- * would have actually bound/unbound/provisioned a channel. This map is
35
- * checked BEFORE qdp is ever spawned so that can't happen (quox#603).
28
+ * in the quox-discord-pro repo), shown in each subcommand's --help. qdp has no
29
+ * --help handling of its own; before quox#1077 this command hand-rolled arg
30
+ * dispatch and had to intercept --help itself (quox#603). Commander
31
+ * subcommands now answer --help before any action runs.
36
32
  */
37
33
  const VERB_USAGE = {
38
34
  status: 'qdp status daemon liveness, instances, binding count',
@@ -56,41 +52,81 @@ const VERB_USAGE = {
56
52
  + ' --dry-run print exactly what it WOULD do, change nothing',
57
53
  };
58
54
 
59
- const GENERAL_USAGE = 'Usage: quox discord <verb> [args...]\n\n'
60
- + Object.values(VERB_USAGE).map((u) => ` ${u}`).join('\n')
55
+ /**
56
+ * qdp verbs that only READ (quox#1077). Each was checked against bin/qdp.mjs:
57
+ * status/instances/bindings print config.json, the pid file and the systemd
58
+ * state; gaps GETs the open support-gap inbox items; health reads the pid and
59
+ * systemd state; doctor GETs the control API /health and Discord's channel,
60
+ * webhook and @me endpoints. None issues PUT/POST/PATCH/DELETE or writes a
61
+ * file. bind, unbind and provision PUT/DELETE bindings and create channels.
62
+ */
63
+ export const DISCORD_READ_VERBS = ['status', 'instances', 'bindings', 'gaps', 'health', 'doctor'];
64
+ export const DISCORD_WRITE_VERBS = ['bind', 'unbind', 'provision'];
65
+
66
+ // No "Usage:" line here: the matrix's --help walker (scripts/matrix/cli-tree.mjs) reads one as the command's usage.
67
+ const GENERAL_USAGE = Object.values(VERB_USAGE).map((u) => ` ${u}`).join('\n')
61
68
  + '\n\nRun `quox discord <verb> --help` for a single verb\'s usage.';
62
69
 
63
- function printVerbHelp(verb) {
64
- if (verb && VERB_USAGE[verb]) {
65
- process.stdout.write(`Usage:\n ${VERB_USAGE[verb]}\n`);
66
- } else {
67
- process.stdout.write(`${GENERAL_USAGE}\n`);
70
+ function runQdp(verb, args = []) {
71
+ if (!fs.existsSync(QDP)) {
72
+ process.stderr.write(
73
+ `Discord Pro control CLI (qdp) not found at ${QDP}\n` +
74
+ 'Set QDP_CLI_PATH if it lives elsewhere, or install the quox-discord-pro daemon on this host.\n',
75
+ );
76
+ process.exit(2);
68
77
  }
78
+ const r = spawnSync(process.execPath, [QDP, verb, ...args], { stdio: 'inherit' });
79
+ if (r.error) {
80
+ process.stderr.write(`qdp failed to run: ${r.error.message}\n`);
81
+ process.exit(2);
82
+ }
83
+ process.exit(r.status ?? 2);
69
84
  }
70
85
 
86
+ const VERB_DESC = { unbind: 'remove a channel binding', doctor: 'per-binding view/webhook/liveness check (exits non-zero if any binding is broken)' };
87
+ const firstLine = (verb) => VERB_DESC[verb] || VERB_USAGE[verb].split('\n')[0].replace(/^qdp \S+(?: [<[][^\]>]*[>\]])*\s+/, '').trim();
88
+
89
+ /**
90
+ * One commander subcommand per qdp verb (quox#1077). It used to be a single
91
+ * `discord [args...]` leaf, so the CLI leaf policy could only class the whole
92
+ * group, and it classed it write: a read like `quox discord bindings` was held
93
+ * for approval. Per-verb leaves let reads be read and keep bind/unbind/provision
94
+ * write. Commander's own --help runs before any action, so `--help` can never
95
+ * reach qdp and run a mutating verb (quox#603).
96
+ */
71
97
  export function registerDiscordCommands(program) {
72
- program
73
- .command('discord [args...]')
74
- .description('Quox Discord Pro daemon: status | instances | bindings | bind | unbind | gaps | health')
75
- .allowUnknownOption(true)
76
- .helpOption(false)
77
- .action((args = []) => {
78
- if (args.includes('--help') || args.includes('-h')) {
79
- printVerbHelp(args[0]);
80
- process.exit(0);
81
- }
82
- if (!fs.existsSync(QDP)) {
83
- process.stderr.write(
84
- `Discord Pro control CLI (qdp) not found at ${QDP}\n` +
85
- 'Set QDP_CLI_PATH if it lives elsewhere, or install the quox-discord-pro daemon on this host.\n',
86
- );
87
- process.exit(2);
88
- }
89
- const r = spawnSync(process.execPath, [QDP, ...args], { stdio: 'inherit' });
90
- if (r.error) {
91
- process.stderr.write(`qdp failed to run: ${r.error.message}\n`);
92
- process.exit(2);
93
- }
94
- process.exit(r.status ?? 2);
95
- });
98
+ const discord = program
99
+ .command('discord')
100
+ .description('Quox Discord Pro daemon: status | instances | bindings | gaps | health | doctor | bind | unbind | provision')
101
+ .addHelpText('after', `\nPer-verb usage (each verb shells straight through to qdp):\n${GENERAL_USAGE}`);
102
+
103
+ for (const verb of DISCORD_READ_VERBS) {
104
+ discord
105
+ .command(verb)
106
+ .description(firstLine(verb) || verb)
107
+ .addHelpText('after', `\nqdp usage:\n ${VERB_USAGE[verb]}`)
108
+ .action(() => runQdp(verb));
109
+ }
110
+
111
+ const writeArgs = { bind: '<channelId> <mode> [pairs...]', unbind: '<channelId>', provision: '<agentId> [args...]' };
112
+ for (const verb of DISCORD_WRITE_VERBS) {
113
+ const leaf = discord
114
+ .command(`${verb} ${writeArgs[verb]}`);
115
+ // Only provision has a dry run in qdp. Declaring it here keeps it in --help and lets the
116
+ // root guard refuse `discord bind|unbind --dry-run` instead of binding for real.
117
+ if (verb === 'provision') leaf.option('--dry-run', 'print exactly what it WOULD do, change nothing');
118
+ leaf
119
+ .description(firstLine(verb) || verb)
120
+ .addHelpText('after', `\nqdp usage:\n ${VERB_USAGE[verb]}`)
121
+ // qdp parses its own options (provision --mode/--host/...), so they pass through unchanged.
122
+ .allowUnknownOption(true)
123
+ .action((...a) => {
124
+ const cmd = a[a.length - 1];
125
+ const args = [...cmd.args];
126
+ // --dry-run is also a root quox flag, so commander hands it to the root and it never reaches
127
+ // cmd.args: a requested dry run of provision used to provision for real. Forward it explicitly.
128
+ if (cmd.optsWithGlobals().dryRun && !args.includes('--dry-run')) args.push('--dry-run');
129
+ runQdp(verb, args);
130
+ });
131
+ }
96
132
  }
@@ -55,6 +55,7 @@ import path from 'node:path';
55
55
  import {
56
56
  resolveFetchDestination,
57
57
  fetchEngine,
58
+ inspectInstall,
58
59
  OPT_GAUGE_DIR,
59
60
  userLocalGaugeDir,
60
61
  legacyGaugeDir,
@@ -88,19 +89,21 @@ export function resolveEnginePath({ env = process.env, existsSync = fs.existsSyn
88
89
 
89
90
  const ENGINE = resolveEnginePath();
90
91
 
91
- function runEngine(args) {
92
- if (!fs.existsSync(ENGINE)) {
93
- process.stderr.write(
94
- `DoneEngine not found at ${ENGINE}\n` +
95
- 'Self-heal: `quox done fetch` pulls the engine bundle straight from ' +
96
- 'the collector, no human in the loop. Otherwise `quox dev enable` ' +
97
- 'delivers it to /opt/quoxgauge as part of full dev-server ' +
98
- 'provisioning. Or set QUOX_DONEENGINE_PATH to a checkout.\n',
99
- );
92
+ function engineMissingMessage(engine) {
93
+ return `DoneEngine not found at ${engine}\n`
94
+ + 'Self-heal: `quox done fetch` pulls the engine bundle straight from '
95
+ + 'the collector, no human in the loop. Otherwise `quox dev enable` '
96
+ + 'delivers it to /opt/quoxgauge as part of full dev-server '
97
+ + 'provisioning. Or set QUOX_DONEENGINE_PATH to a checkout.\n';
98
+ }
99
+
100
+ function runEngine(args, engine = ENGINE) {
101
+ if (!fs.existsSync(engine)) {
102
+ process.stderr.write(engineMissingMessage(engine));
100
103
  // 2, not 1: a missing engine is "cannot tell", not "refused".
101
104
  return 2;
102
105
  }
103
- const r = spawnSync(process.execPath, [ENGINE, ...args], { stdio: 'inherit' });
106
+ const r = spawnSync(process.execPath, [engine, ...args], { stdio: 'inherit' });
104
107
  if (r.error) {
105
108
  process.stderr.write(`DoneEngine failed to run: ${r.error.message}\n`);
106
109
  return 2;
@@ -108,6 +111,23 @@ function runEngine(args) {
108
111
  return r.status ?? 2;
109
112
  }
110
113
 
114
+ /**
115
+ * VISIBILITY GATE (#988 follow-on). Public installs do not ship DoneEngine
116
+ * (its bundle is built from the private quox-gauge repo and delivered to dev
117
+ * servers by `quox dev enable`), so listing `quox done` in their help
118
+ * advertises a command that can only fail. It is listed only when an engine
119
+ * is installed locally, decided from the filesystem alone with the same
120
+ * resolution the runner uses: building help never makes a network call.
121
+ * QUOX_HELP_INCLUDE_GATED=1 lists it regardless; the generators that scrape
122
+ * `--help` into the command trees set it so those trees do not depend on
123
+ * which host ran them.
124
+ */
125
+ export function isDoneEngineInstalled({ env = process.env, home = os.homedir(), fsOps } = {}) {
126
+ const ops = fsOps || { existsSync: fs.existsSync, readdirSync: fs.readdirSync };
127
+ const engine = resolveEnginePath({ env, existsSync: ops.existsSync, home });
128
+ return inspectInstall(path.dirname(engine), ops).healthy;
129
+ }
130
+
111
131
  function defaultExecFn(cmd, args) {
112
132
  return spawnSync(cmd, args, { encoding: 'utf8' });
113
133
  }
@@ -140,8 +160,56 @@ function defaultFsOps() {
140
160
  }
141
161
 
142
162
  export function registerDoneCommands(program, ctx = {}) {
163
+ const env = ctx.env || process.env;
164
+ const home = ctx.home || os.homedir();
165
+ // Tests inject fsOps/env/home; the real CLI keeps the import-time ENGINE.
166
+ const injected = !!(ctx.fsOps || ctx.env || ctx.home);
167
+ const currentEngine = () => (injected
168
+ ? resolveEnginePath({ env, existsSync: (ctx.fsOps || fs).existsSync, home })
169
+ : ENGINE);
170
+ const visible = env.QUOX_HELP_INCLUDE_GATED === '1'
171
+ || isDoneEngineInstalled({ env, home, fsOps: ctx.fsOps });
172
+
173
+ const fetchDeps = () => ({
174
+ execFn: ctx.execFn || defaultExecFn,
175
+ isWritable: ctx.isWritable || defaultIsWritable,
176
+ fsOps: ctx.fsOps || defaultFsOps(),
177
+ });
178
+
179
+ /**
180
+ * Resolve a runnable engine for a subcommand. Present: use it, no network.
181
+ * Absent: try the existing fetch path once. A collector 404
182
+ * (DONEENGINE_NOT_SHIPPED) means this is a public install: refuse with that
183
+ * message and exit 2, having changed nothing. A bundle that IS available
184
+ * (a dev collector) installs and the freshly fetched engine runs.
185
+ */
186
+ async function ensureEngine() {
187
+ const engine = currentEngine();
188
+ const exists = (ctx.fsOps || fs).existsSync(engine);
189
+ if (exists || !ctx.client || env.QUOX_DONEENGINE_PATH) return { engine };
190
+ const { execFn, isWritable, fsOps } = fetchDeps();
191
+ const { dest, needsSudo } = resolveFetchDestination({ env, isWritable, execFn });
192
+ try {
193
+ await fetchEngine({ dest, needsSudo, force: false, client: ctx.client, execFn, fsOps });
194
+ return { engine: path.join(dest, 'gauge.mjs') };
195
+ } catch (err) {
196
+ if (err.code === 'DONEENGINE_NOT_SHIPPED') {
197
+ process.stderr.write(`quox done: ${err.message}\n`);
198
+ return { code: 2 };
199
+ }
200
+ process.stderr.write(`quox done: engine fetch failed: ${err.message}\n`);
201
+ return { engine };
202
+ }
203
+ }
204
+
205
+ async function runGuarded(args) {
206
+ const r = await ensureEngine();
207
+ if (r.code !== undefined) return r.code;
208
+ return runEngine(args, r.engine);
209
+ }
210
+
143
211
  const done = program
144
- .command('done')
212
+ .command('done', { hidden: !visible })
145
213
  .description('Quox DoneEngine: is this actually done, and what is the evidence?');
146
214
 
147
215
  done
@@ -150,7 +218,7 @@ export function registerDoneCommands(program, ctx = {}) {
150
218
  .option('--pack <id>', 'rule pack to judge against', 'launch-spine')
151
219
  .option('--json', 'structured verdict for agents')
152
220
  .option('--root <path>', 'judge <subject> in this repo, reading its own STATUS map with the selected pack (e.g. a plugin with --pack plugin)')
153
- .action((subject, options) => {
221
+ .action(async (subject, options) => {
154
222
  const args = ['--can-ship', subject || 'all', '--pack', options.pack];
155
223
  if (options.root) {
156
224
  if (!subject) {
@@ -161,17 +229,17 @@ export function registerDoneCommands(program, ctx = {}) {
161
229
  args.push('--subject', subject, '--root', path.resolve(options.root));
162
230
  }
163
231
  if (options.json) args.push('--json');
164
- process.exitCode = runEngine(args);
232
+ process.exitCode = await runGuarded(args);
165
233
  });
166
234
 
167
235
  done
168
236
  .command('checklist [pack]')
169
237
  .description('The builder checklist GENERATED from a pack (every gate the engine judges); --check verifies a doc that embeds it')
170
238
  .option('--check <file>', 'exit 1 if the checklist block in this file has drifted from the pack')
171
- .action((pack, options) => {
239
+ .action(async (pack, options) => {
172
240
  const args = ['--checklist', pack || 'plugin'];
173
241
  if (options.check) args.push('--check', path.resolve(options.check));
174
- process.exitCode = runEngine(args);
242
+ process.exitCode = await runGuarded(args);
175
243
  });
176
244
 
177
245
  done
@@ -180,12 +248,12 @@ export function registerDoneCommands(program, ctx = {}) {
180
248
  .option('--json', 'machine-readable, includes raw deploy state')
181
249
  .option('--dod', 'per-surface gates, split by evidence class')
182
250
  .option('--no-deploy', 'skip docker/git; Deployed Ready becomes unknown, which is not a pass')
183
- .action((options) => {
251
+ .action(async (options) => {
184
252
  const args = [];
185
253
  if (options.json) args.push('--json');
186
254
  if (options.dod) args.push('--dod');
187
255
  if (options.deploy === false) args.push('--no-deploy');
188
- process.exitCode = runEngine(args);
256
+ process.exitCode = await runGuarded(args);
189
257
  });
190
258
 
191
259
  done
@@ -193,25 +261,27 @@ export function registerDoneCommands(program, ctx = {}) {
193
261
  .description('One rule pack against its subjects: BOTH finish lines, build and deployed')
194
262
  .option('--snapshot', 'append this reading to the snapshot history')
195
263
  .option('--org <id>', 'measure FOR this org: plan evidence reads as it, the snapshot is stamped with it (default: the pack\'s own org field)')
196
- .action((pack, options) => {
264
+ .action(async (pack, options) => {
197
265
  const args = pack ? ['--pack', pack] : ['--launch'];
198
266
  if (options.snapshot) args.push('--snapshot');
199
267
  if (options.org) args.push('--org', options.org);
200
- process.exitCode = runEngine(args);
268
+ process.exitCode = await runGuarded(args);
201
269
  });
202
270
 
203
271
  done
204
272
  .command('history [pack]')
205
273
  .description('Movement between snapshots, regressions listed first')
206
- .action((pack) => {
207
- process.exitCode = runEngine(pack ? ['--history', '--pack', pack] : ['--history']);
274
+ .action(async (pack) => {
275
+ process.exitCode = await runGuarded(pack ? ['--history', '--pack', pack] : ['--history']);
208
276
  });
209
277
 
210
278
  done
211
279
  .command('packs')
212
280
  .description('List available rule packs')
213
- .action(() => {
214
- const dir = path.join(path.dirname(ENGINE), 'packs');
281
+ .action(async () => {
282
+ const r = await ensureEngine();
283
+ if (r.code !== undefined) { process.exitCode = r.code; return; }
284
+ const dir = path.join(path.dirname(r.engine), 'packs');
215
285
  if (!fs.existsSync(dir)) {
216
286
  process.stderr.write(
217
287
  `No packs directory at ${dir}\n` +
@@ -246,13 +316,11 @@ export function registerDoneCommands(program, ctx = {}) {
246
316
  process.exitCode = 2;
247
317
  return;
248
318
  }
249
- const execFn = ctx.execFn || defaultExecFn;
250
- const isWritable = ctx.isWritable || defaultIsWritable;
251
- const fsOps = ctx.fsOps || defaultFsOps();
319
+ const { execFn, isWritable, fsOps } = fetchDeps();
252
320
 
253
321
  const { dest, reason, needsSudo } = resolveFetchDestination({
254
322
  destOption: options.dest,
255
- env: process.env,
323
+ env,
256
324
  isWritable,
257
325
  execFn,
258
326
  });
@@ -267,7 +335,9 @@ export function registerDoneCommands(program, ctx = {}) {
267
335
  }
268
336
  } catch (err) {
269
337
  process.stderr.write(`quox done fetch failed: ${err.message}\n`);
270
- process.exitCode = 1;
338
+ // A public install's 404 is "this install has no DoneEngine", not a
339
+ // transient failure: exit 2 like every other "cannot tell" here.
340
+ process.exitCode = err.code === 'DONEENGINE_NOT_SHIPPED' ? 2 : 1;
271
341
  }
272
342
  });
273
343
 
@@ -4,6 +4,7 @@
4
4
  * quox engagement create <name> --environment-tier <tier> [--risk-ceiling <tier>]
5
5
  * quox engagement list
6
6
  * quox engagement show <id>
7
+ * quox engagement verify <id>
7
8
  * quox engagement add-target <id> <type> <value> [--verification-method <method>]
8
9
  * quox engagement verify-target <id> <targetId>
9
10
  *
@@ -20,6 +21,7 @@ import { writeFileSync } from 'node:fs';
20
21
  import { formatOutput, formatDetail, printSuccess, printInfo } from '../core/output.js';
21
22
  import { events, printEvent, line as glyphLine } from '../core/quoxglyph.js';
22
23
  import { resolveEndpoint } from '../api/endpoints.js';
24
+ import { gatherEvidence, evidenceChanged } from '../core/findingReconcile.js';
23
25
 
24
26
  export function registerEngagementCommands(program, ctx) {
25
27
  const { client, auth } = ctx;
@@ -80,6 +82,31 @@ export function registerEngagementCommands(program, ctx) {
80
82
  }, { format: program.opts().output, emptyMessage: 'No targets added yet.' });
81
83
  });
82
84
 
85
+ engagement
86
+ .command('verify <id>')
87
+ .description('Verify the WARD witness for an engagement (recompute receipt + Ed25519 tip signature)')
88
+ .action(async (id) => {
89
+ auth.requireAuth();
90
+ const ep = resolveEndpoint('engagement.verify', { id });
91
+ const data = await client.get(ep.path, { service: ep.service });
92
+ if (program.opts().output === 'json') {
93
+ console.log(JSON.stringify(data, null, 2));
94
+ return;
95
+ }
96
+ const ok = data.pass === true;
97
+ console.log(ok ? '✓ WITNESS VERIFIED' : '✗ NOT VERIFIED');
98
+ console.log(` engagement ${data.engagement_id || id}`);
99
+ console.log(` witnessed ${data.witnessed ? 'yes' : 'no'}`);
100
+ console.log(` chain ${data.chainId || '-'}`);
101
+ if (data.seq != null) console.log(` entry #${data.seq}`);
102
+ if (data.chainHash) console.log(` chain hash ${String(data.chainHash).slice(0, 40)}`);
103
+ console.log(` chain recompute ${data.chainHashRecomputed ? 'ok' : 'FAILED'}`);
104
+ const sig = data.signature || {};
105
+ console.log(` tip signature ${sig.status}${sig.keyId ? ` (key ${String(sig.keyId).slice(0, 16)})` : ''}`);
106
+ if (data.signedTip && data.signedTip.tipId) console.log(` signed tip ${data.signedTip.tipId} (seq ${data.signedTip.tipSeq})`);
107
+ if (!ok && data.reason) console.log(` reason ${data.reason}`);
108
+ });
109
+
83
110
  engagement
84
111
  .command('set-gripe-target <id>')
85
112
  .description('Set which repo a finding filed from this engagement goes to, and which org GitHub connector token files it. Lets a customer-site engagement file into the customer repo with the customer org token. Pass --clear (or --repo "") to fall back to the platform default (quoxai/quox) and the engagement own org.')
@@ -121,10 +148,13 @@ export function registerEngagementCommands(program, ctx) {
121
148
  const data = await client.post(ep.path, { service: ep.service, body });
122
149
  printSuccess(`Target "${value}" added to engagement ${id}.`);
123
150
  if (data.target.verification_token) {
151
+ // #1007: a url target is challenged on its hostname (dns_txt) or at its own origin (well_known_file)
152
+ const stored = data.target.value || value;
153
+ const url = data.target.type === 'url' ? (() => { try { return new URL(stored); } catch { return null; } })() : null;
124
154
  if (data.target.verification_method === 'dns_txt') {
125
- console.log(` Add a DNS TXT record on ${value}: quox-verify=${data.target.verification_token}`);
155
+ console.log(` Add a DNS TXT record on ${url ? url.hostname : stored}: quox-verify=${data.target.verification_token}`);
126
156
  } else if (data.target.verification_method === 'well_known_file') {
127
- console.log(` Serve this content at https://${value}/.well-known/quox-verification: ${data.target.verification_token}`);
157
+ console.log(` Serve this content at ${url ? url.origin : `https://${stored}`}/.well-known/quox-verification: ${data.target.verification_token}`);
128
158
  }
129
159
  console.log(` Then run: quox engagement verify-target ${id} ${data.target.id}`);
130
160
  }
@@ -172,6 +202,7 @@ export function registerEngagementCommands(program, ctx) {
172
202
  console.log(JSON.stringify(data.output, null, 2));
173
203
  } else {
174
204
  console.log(` Failed (allowed: ${data.allowed}): ${data.error}`);
205
+ process.exitCode = 1; // #1007: a refusal is not success, for scripts as much as people
175
206
  }
176
207
  });
177
208
 
@@ -185,6 +216,7 @@ export function registerEngagementCommands(program, ctx) {
185
216
  const data = await client.post(ep.path, { service: ep.service, body: { target: hostTarget, add: options.add === true } });
186
217
  if (!data.success) {
187
218
  console.log(` Failed (allowed: ${data.allowed}): ${data.error}`);
219
+ process.exitCode = 1; // #1007: a refusal is not success, for scripts as much as people
188
220
  return;
189
221
  }
190
222
  const discovered = data.discovered || [];
@@ -234,6 +266,7 @@ export function registerEngagementCommands(program, ctx) {
234
266
  console.log(` Poll: quox engagement job ${data.job_id}`);
235
267
  } else {
236
268
  console.log(` Failed (allowed: ${data.allowed}): ${data.error}`);
269
+ process.exitCode = 1; // #1007: a refusal is not success, for scripts as much as people
237
270
  }
238
271
  });
239
272
 
@@ -372,11 +405,16 @@ export function registerEngagementCommands(program, ctx) {
372
405
  }
373
406
  const buf = Buffer.from(await res.arrayBuffer());
374
407
  const rootOutput = program.opts().output;
375
- const outFile = options.output
376
- || (rootOutput && !KNOWN_OUTPUT_FORMATS.has(rootOutput) ? rootOutput : null)
377
- || `quox-security-${id}.pdf`;
408
+ // quox#919: the global --json reaches options.output as 'json' via
409
+ // mergeShadowedGlobals; an output-format name is never a file path.
410
+ const asPath = (v) => (v && !KNOWN_OUTPUT_FORMATS.has(v) ? v : null);
411
+ const outFile = asPath(options.output) || asPath(rootOutput) || `quox-security-${id}.pdf`;
378
412
  writeFileSync(outFile, buf);
379
- printSuccess(`Report saved to ${outFile} (${buf.length} bytes).`);
413
+ if (rootOutput === 'json') {
414
+ console.log(JSON.stringify({ success: true, file: outFile, bytes: buf.length }));
415
+ } else {
416
+ printSuccess(`Report saved to ${outFile} (${buf.length} bytes).`);
417
+ }
380
418
  });
381
419
 
382
420
  async function raiseIssue(findingId) {
@@ -564,6 +602,69 @@ export function registerEngagementCommands(program, ctx) {
564
602
  printSuccess(`Schedule "${scheduleId}" deleted.`);
565
603
  });
566
604
 
605
+ // SITREP-FINDINGS: reconcile open findings against their issue and fix. Evidence is gathered
606
+ // here (git + gh on this host); the collector decides and keeps the audit trail. Resolves only
607
+ // on a closed issue AND a fix in the running build; a critical is never auto-resolved.
608
+ engagement
609
+ .command('reconcile [findingId]')
610
+ .description('Reconcile open findings against their linked issue and fix commit (landed on main, deployed in the running build). Resolves only when the issue is closed AND the fix is deployed; a critical never auto-resolves. Every change is audited')
611
+ .option('--link <ref...>', 'link evidence: <findingId>=<owner/repo#N | #N | commit-sha> (repeatable)')
612
+ .option('--repo-dir <dir>', 'git checkout to read origin/main from (default: ~/quox-dashboard)')
613
+ .option('--build-dir <dir>', 'the tree the running build was built from (default: ~/.quox-build/quox-dashboard)')
614
+ .option('--issue-repo <owner/repo>', 'repo a bare #N refers to', 'quoxai/quox')
615
+ .option('--no-fetch', 'do not fetch origin/main first')
616
+ .option('--dry-run', 'show the evidence without recording anything')
617
+ .action(async (findingId, options) => {
618
+ auth.requireAuth();
619
+ const { spawnSync } = await import('node:child_process');
620
+ const run = (cmd, args) => {
621
+ const r = spawnSync(cmd, args, { encoding: 'utf8', timeout: 30000 });
622
+ return { ok: r.status === 0, out: String(r.stdout || '') };
623
+ };
624
+ const home = (await import('node:os')).homedir();
625
+ options.repoDir = options.repoDir || `${home}/quox-dashboard`;
626
+ options.buildDir = options.buildDir || `${home}/.quox-build/quox-dashboard`;
627
+ const links = {};
628
+ for (const l of options.link || []) {
629
+ const m = /^(FND_[0-9A-Z]+)=(.+)$/.exec(String(l));
630
+ if (!m) throw new Error(`--link must look like FND_...=<issue or sha>, got "${l}"`);
631
+ const v = m[2].trim();
632
+ links[m[1]] = { ...(links[m[1]] || {}), ...(/^[0-9a-f]{7,40}$/i.test(v) ? { commit: v } : { issue: v }) };
633
+ }
634
+ if (options.fetch !== false) run('git', ['-C', options.repoDir, 'fetch', '--quiet', 'origin', 'main']);
635
+ const ep = resolveEndpoint('engagement.findings.open', {});
636
+ const all = (await client.get(ep.path, { service: ep.service })).findings || [];
637
+ const findings = findingId ? all.filter((f) => f.id === findingId) : all;
638
+ if (findingId && !findings.length) throw new Error(`Finding "${findingId}" is not open in this org`);
639
+ const rows = [];
640
+ for (const f of findings) {
641
+ const ev = gatherEvidence(f, links[f.id] || {}, { repoDir: options.repoDir, buildDir: options.buildDir, issueRepo: options.issueRepo, run });
642
+ const row = { id: f.id, severity: f.severity, status: f.status, issue: ev.issue_ref ? `${ev.issue_ref}${ev.issue_state ? ` (${ev.issue_state})` : ''}` : '',
643
+ fix: ev.fix_commit ? `${ev.fix_commit.slice(0, 9)} ${ev.fix_state}` : '', outcome: '' };
644
+ if (!Object.keys(ev).length) { row.outcome = 'no evidence'; rows.push(row); continue; }
645
+ if (!evidenceChanged(f, ev)) { row.outcome = 'unchanged'; rows.push(row); continue; }
646
+ if (options.dryRun) { row.outcome = 'would record'; rows.push(row); continue; }
647
+ const rep = resolveEndpoint('engagement.findings.reconcile', { findingId: f.id });
648
+ const data = await client.post(rep.path, { service: rep.service, body: ev });
649
+ row.outcome = `${data.outcome}: ${data.reason}`;
650
+ row.status = data.finding?.status || row.status;
651
+ rows.push(row);
652
+ }
653
+ formatOutput({ columns: ['id', 'severity', 'status', 'issue', 'fix', 'outcome'], headers: ['Finding', 'Severity', 'Status', 'Issue', 'Fix', 'Outcome'], rows },
654
+ { format: program.opts().output, emptyMessage: 'No open findings.' });
655
+ });
656
+
657
+ engagement
658
+ .command('reconciliations <findingId>')
659
+ .description('Show the reconciliation audit trail of one finding (what evidence was recorded, when, by whom, and the outcome)')
660
+ .action(async (findingId) => {
661
+ auth.requireAuth();
662
+ const ep = resolveEndpoint('engagement.findings.reconciliations', { findingId });
663
+ const data = await client.get(ep.path, { service: ep.service });
664
+ formatOutput({ columns: ['created_at', 'outcome', 'reason', 'actor', 'evidence'], headers: ['When', 'Outcome', 'Reason', 'Actor', 'Evidence'], rows: data.reconciliations || [] },
665
+ { format: program.opts().output, emptyMessage: 'No reconciliations recorded.' });
666
+ });
667
+
567
668
  engagement
568
669
  .command('sync-issues <id>')
569
670
  .description('Reconcile this engagement\'s filed findings against their GitHub issue state. A closed issue auto-resolves its finding. Honest no-op if GitHub is not configured')