quoxcli 0.0.0-stage → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (516) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +144 -2
  3. package/bin/quox.js +265 -0
  4. package/bin/quoxon.js +8 -0
  5. package/lib/api/endpoints/admin.js +151 -0
  6. package/lib/api/endpoints/aee.js +38 -0
  7. package/lib/api/endpoints/agent-attach.js +126 -0
  8. package/lib/api/endpoints/agent-schedules.js +26 -0
  9. package/lib/api/endpoints/agents.js +63 -0
  10. package/lib/api/endpoints/assistants.js +62 -0
  11. package/lib/api/endpoints/audit.js +14 -0
  12. package/lib/api/endpoints/auth.js +50 -0
  13. package/lib/api/endpoints/awareness.js +34 -0
  14. package/lib/api/endpoints/away.js +15 -0
  15. package/lib/api/endpoints/backup-schedules.js +20 -0
  16. package/lib/api/endpoints/backups.js +86 -0
  17. package/lib/api/endpoints/brain.js +140 -0
  18. package/lib/api/endpoints/budgets.js +62 -0
  19. package/lib/api/endpoints/calendarq.js +71 -0
  20. package/lib/api/endpoints/catalog.js +14 -0
  21. package/lib/api/endpoints/chat.js +61 -0
  22. package/lib/api/endpoints/consent.js +26 -0
  23. package/lib/api/endpoints/contactsq.js +68 -0
  24. package/lib/api/endpoints/conversations.js +46 -0
  25. package/lib/api/endpoints/custom-roles.js +92 -0
  26. package/lib/api/endpoints/divisions.js +56 -0
  27. package/lib/api/endpoints/domains.js +38 -0
  28. package/lib/api/endpoints/engagement.js +231 -0
  29. package/lib/api/endpoints/engine.js +11 -0
  30. package/lib/api/endpoints/entity.js +50 -0
  31. package/lib/api/endpoints/executions.js +45 -0
  32. package/lib/api/endpoints/fetchAllowlist.js +27 -0
  33. package/lib/api/endpoints/file.js +20 -0
  34. package/lib/api/endpoints/files.js +40 -0
  35. package/lib/api/endpoints/github.js +21 -0
  36. package/lib/api/endpoints/governance.js +43 -0
  37. package/lib/api/endpoints/hat.js +66 -0
  38. package/lib/api/endpoints/inbox.js +52 -0
  39. package/lib/api/endpoints/inboxq.js +120 -0
  40. package/lib/api/endpoints/index.js +242 -0
  41. package/lib/api/endpoints/invites.js +19 -0
  42. package/lib/api/endpoints/llm-policy.js +43 -0
  43. package/lib/api/endpoints/lucid.js +89 -0
  44. package/lib/api/endpoints/members.js +32 -0
  45. package/lib/api/endpoints/memory.js +94 -0
  46. package/lib/api/endpoints/mfa.js +32 -0
  47. package/lib/api/endpoints/mind.js +13 -0
  48. package/lib/api/endpoints/mon.js +184 -0
  49. package/lib/api/endpoints/notesq.js +43 -0
  50. package/lib/api/endpoints/objectives.js +68 -0
  51. package/lib/api/endpoints/orgs.js +38 -0
  52. package/lib/api/endpoints/plugins.js +152 -0
  53. package/lib/api/endpoints/powers.js +75 -0
  54. package/lib/api/endpoints/presence.js +33 -0
  55. package/lib/api/endpoints/prompts.js +69 -0
  56. package/lib/api/endpoints/qlarity.js +122 -0
  57. package/lib/api/endpoints/quoxbox.js +55 -0
  58. package/lib/api/endpoints/quoxchat.js +54 -0
  59. package/lib/api/endpoints/quoxflow.js +32 -0
  60. package/lib/api/endpoints/quoxplan.js +121 -0
  61. package/lib/api/endpoints/quoxseo.js +114 -0
  62. package/lib/api/endpoints/reach.js +47 -0
  63. package/lib/api/endpoints/router.js +17 -0
  64. package/lib/api/endpoints/runs.js +38 -0
  65. package/lib/api/endpoints/safety.js +53 -0
  66. package/lib/api/endpoints/scim.js +46 -0
  67. package/lib/api/endpoints/scopes.js +14 -0
  68. package/lib/api/endpoints/security.js +69 -0
  69. package/lib/api/endpoints/setup.js +90 -0
  70. package/lib/api/endpoints/skills.js +200 -0
  71. package/lib/api/endpoints/sso.js +67 -0
  72. package/lib/api/endpoints/stepup.js +20 -0
  73. package/lib/api/endpoints/tags.js +32 -0
  74. package/lib/api/endpoints/teams.js +56 -0
  75. package/lib/api/endpoints/telephony.js +46 -0
  76. package/lib/api/endpoints/tg.js +56 -0
  77. package/lib/api/endpoints/tools.js +50 -0
  78. package/lib/api/endpoints/traces.js +157 -0
  79. package/lib/api/endpoints/triggers.js +96 -0
  80. package/lib/api/endpoints/user-library.js +41 -0
  81. package/lib/api/endpoints/users.js +20 -0
  82. package/lib/api/endpoints/v1.js +585 -0
  83. package/lib/api/endpoints/vault.js +192 -0
  84. package/lib/api/endpoints/voice.js +22 -0
  85. package/lib/api/endpoints/volt.js +84 -0
  86. package/lib/api/endpoints/ward.js +47 -0
  87. package/lib/api/endpoints/webhooks.js +46 -0
  88. package/lib/api/endpoints/workflows.js +169 -0
  89. package/lib/api/endpoints/x.js +32 -0
  90. package/lib/api/endpoints.js +15 -0
  91. package/lib/commands/actions.js +61 -0
  92. package/lib/commands/admin.js +375 -0
  93. package/lib/commands/aee.js +99 -0
  94. package/lib/commands/agent.js +337 -0
  95. package/lib/commands/agentAttach.js +1528 -0
  96. package/lib/commands/approvals.js +119 -0
  97. package/lib/commands/aside.js +24 -0
  98. package/lib/commands/ask.js +144 -0
  99. package/lib/commands/assistant.js +169 -0
  100. package/lib/commands/auth.js +464 -0
  101. package/lib/commands/awareness.js +88 -0
  102. package/lib/commands/backup.js +377 -0
  103. package/lib/commands/brain.js +1085 -0
  104. package/lib/commands/budget.js +241 -0
  105. package/lib/commands/build.js +170 -0
  106. package/lib/commands/calendar.js +216 -0
  107. package/lib/commands/capabilities.js +34 -0
  108. package/lib/commands/chat.js +295 -0
  109. package/lib/commands/claude.js +380 -0
  110. package/lib/commands/config-cmd.js +106 -0
  111. package/lib/commands/consent.js +84 -0
  112. package/lib/commands/contacts.js +1016 -0
  113. package/lib/commands/contactsSelftest.js +132 -0
  114. package/lib/commands/conversation.js +105 -0
  115. package/lib/commands/dashboard.js +43 -0
  116. package/lib/commands/department.js +96 -0
  117. package/lib/commands/dev.js +620 -0
  118. package/lib/commands/devResident.js +326 -0
  119. package/lib/commands/devSupervisor.js +1062 -0
  120. package/lib/commands/discord.js +132 -0
  121. package/lib/commands/doctor.js +181 -0
  122. package/lib/commands/domain.js +114 -0
  123. package/lib/commands/done.js +347 -0
  124. package/lib/commands/engagement.js +597 -0
  125. package/lib/commands/entity.js +119 -0
  126. package/lib/commands/facebook.js +212 -0
  127. package/lib/commands/fetchAllowlist.js +71 -0
  128. package/lib/commands/file.js +165 -0
  129. package/lib/commands/fleet.js +1189 -0
  130. package/lib/commands/governance.js +647 -0
  131. package/lib/commands/gripe.js +355 -0
  132. package/lib/commands/hat.js +374 -0
  133. package/lib/commands/helpers.js +96 -0
  134. package/lib/commands/hyperion.js +58 -0
  135. package/lib/commands/inbox.js +173 -0
  136. package/lib/commands/instagram.js +264 -0
  137. package/lib/commands/integration.js +100 -0
  138. package/lib/commands/inventory.js +115 -0
  139. package/lib/commands/invites.js +45 -0
  140. package/lib/commands/licence.js +139 -0
  141. package/lib/commands/linkedin.js +178 -0
  142. package/lib/commands/llm-policy.js +117 -0
  143. package/lib/commands/llm.js +45 -0
  144. package/lib/commands/lucid.js +597 -0
  145. package/lib/commands/mail.js +531 -0
  146. package/lib/commands/memory.js +361 -0
  147. package/lib/commands/mind.js +71 -0
  148. package/lib/commands/mon.js +581 -0
  149. package/lib/commands/monitoring.js +86 -0
  150. package/lib/commands/motion.js +22 -0
  151. package/lib/commands/notes.js +1246 -0
  152. package/lib/commands/notesBatch.js +160 -0
  153. package/lib/commands/notesBridges.js +56 -0
  154. package/lib/commands/notesLinks.js +60 -0
  155. package/lib/commands/notification.js +57 -0
  156. package/lib/commands/nucleus.js +123 -0
  157. package/lib/commands/objective.js +240 -0
  158. package/lib/commands/offload.js +89 -0
  159. package/lib/commands/onboard.js +329 -0
  160. package/lib/commands/open.js +56 -0
  161. package/lib/commands/org.js +276 -0
  162. package/lib/commands/peek.js +28 -0
  163. package/lib/commands/plan.js +944 -0
  164. package/lib/commands/plugin-doctor.js +1091 -0
  165. package/lib/commands/plugin.js +649 -0
  166. package/lib/commands/policy.js +78 -0
  167. package/lib/commands/powers.js +296 -0
  168. package/lib/commands/presence.js +89 -0
  169. package/lib/commands/prompt.js +350 -0
  170. package/lib/commands/provider.js +159 -0
  171. package/lib/commands/qlarity.js +706 -0
  172. package/lib/commands/quoxbox.js +270 -0
  173. package/lib/commands/quoxchat.js +275 -0
  174. package/lib/commands/quoxseo.js +378 -0
  175. package/lib/commands/reach.js +159 -0
  176. package/lib/commands/role.js +286 -0
  177. package/lib/commands/router.js +77 -0
  178. package/lib/commands/rows.js +358 -0
  179. package/lib/commands/run.js +187 -0
  180. package/lib/commands/safety.js +107 -0
  181. package/lib/commands/scim.js +88 -0
  182. package/lib/commands/scope.js +58 -0
  183. package/lib/commands/security.js +157 -0
  184. package/lib/commands/server.js +292 -0
  185. package/lib/commands/service.js +89 -0
  186. package/lib/commands/settings.js +99 -0
  187. package/lib/commands/setup.js +569 -0
  188. package/lib/commands/skillManifest.js +113 -0
  189. package/lib/commands/skillRightsFormat.js +38 -0
  190. package/lib/commands/skills.js +687 -0
  191. package/lib/commands/slot.js +1451 -0
  192. package/lib/commands/sso.js +229 -0
  193. package/lib/commands/status.js +137 -0
  194. package/lib/commands/tag.js +91 -0
  195. package/lib/commands/team.js +142 -0
  196. package/lib/commands/tg.js +240 -0
  197. package/lib/commands/tiktok.js +218 -0
  198. package/lib/commands/tips.js +23 -0
  199. package/lib/commands/tool.js +304 -0
  200. package/lib/commands/trace.js +530 -0
  201. package/lib/commands/trigger.js +223 -0
  202. package/lib/commands/update.js +46 -0
  203. package/lib/commands/vault.js +1447 -0
  204. package/lib/commands/vm.js +237 -0
  205. package/lib/commands/voice.js +213 -0
  206. package/lib/commands/volt.js +570 -0
  207. package/lib/commands/walkthrough.js +32 -0
  208. package/lib/commands/watch.js +167 -0
  209. package/lib/commands/webhook.js +137 -0
  210. package/lib/commands/welcome.js +14 -0
  211. package/lib/commands/workflow.js +660 -0
  212. package/lib/commands/x.js +448 -0
  213. package/lib/core/actingAs.js +136 -0
  214. package/lib/core/activeSummary.js +186 -0
  215. package/lib/core/aee.js +53 -0
  216. package/lib/core/agentAlias.js +163 -0
  217. package/lib/core/approvalContext.js +61 -0
  218. package/lib/core/aside.js +54 -0
  219. package/lib/core/askDecision.js +60 -0
  220. package/lib/core/auth.js +501 -0
  221. package/lib/core/banner.js +143 -0
  222. package/lib/core/brainPrime.js +90 -0
  223. package/lib/core/callerSource.js +129 -0
  224. package/lib/core/claudeLs.js +94 -0
  225. package/lib/core/cliBuild.js +62 -0
  226. package/lib/core/client.js +693 -0
  227. package/lib/core/commandRegistrars.js +121 -0
  228. package/lib/core/completions.js +163 -0
  229. package/lib/core/config.js +669 -0
  230. package/lib/core/confirm.js +37 -0
  231. package/lib/core/customHats.js +168 -0
  232. package/lib/core/deeplinks.js +76 -0
  233. package/lib/core/doneEngineFetch.js +227 -0
  234. package/lib/core/errors.js +441 -0
  235. package/lib/core/global-flags.js +190 -0
  236. package/lib/core/governedSession.js +402 -0
  237. package/lib/core/hyperion.js +102 -0
  238. package/lib/core/inboxResolution.js +65 -0
  239. package/lib/core/lazyCommands.js +122 -0
  240. package/lib/core/liveSessionsReport.js +112 -0
  241. package/lib/core/offload.js +504 -0
  242. package/lib/core/org-override.js +52 -0
  243. package/lib/core/orgResolve.js +70 -0
  244. package/lib/core/output.js +239 -0
  245. package/lib/core/peek.js +126 -0
  246. package/lib/core/prodEnv.js +7 -0
  247. package/lib/core/profileUsers.js +51 -0
  248. package/lib/core/promptCancel.js +18 -0
  249. package/lib/core/quoxglyph.js +158 -0
  250. package/lib/core/repoPath.js +11 -0
  251. package/lib/core/residentAlarm.js +282 -0
  252. package/lib/core/residentAuth.js +243 -0
  253. package/lib/core/residentPump.js +229 -0
  254. package/lib/core/residentSeed.js +106 -0
  255. package/lib/core/rootOptions.js +39 -0
  256. package/lib/core/router.js +168 -0
  257. package/lib/core/selfUpdate.js +188 -0
  258. package/lib/core/sessionHeartbeat.js +192 -0
  259. package/lib/core/sessionsOverview.js +224 -0
  260. package/lib/core/slotKeeper.js +527 -0
  261. package/lib/core/slotWake.js +120 -0
  262. package/lib/core/supervisorEngine.js +933 -0
  263. package/lib/core/systemdInstaller.js +256 -0
  264. package/lib/core/version.js +87 -0
  265. package/lib/core/vm.js +361 -0
  266. package/lib/core/vmTui.js +86 -0
  267. package/lib/core/walkthrough.js +35 -0
  268. package/lib/hyperion/deck.js +29 -0
  269. package/lib/lucid/LucidCard.jsx +68 -0
  270. package/lib/lucid/LucidIntro.jsx +92 -0
  271. package/lib/lucid/blind.js +71 -0
  272. package/lib/lucid/cards.js +86 -0
  273. package/lib/lucid/deck.js +32 -0
  274. package/lib/lucid/fmt.js +139 -0
  275. package/lib/lucid/markdown.js +81 -0
  276. package/lib/lucid/motion.js +56 -0
  277. package/lib/lucid/sitrep.js +173 -0
  278. package/lib/plugins/loader.js +93 -0
  279. package/lib/quoxon/activity.js +116 -0
  280. package/lib/quoxon/aocl.js +117 -0
  281. package/lib/quoxon/api.js +952 -0
  282. package/lib/quoxon/app.js +4032 -0
  283. package/lib/quoxon/approvalFacts.js +94 -0
  284. package/lib/quoxon/approvalsView.js +325 -0
  285. package/lib/quoxon/asideView.js +40 -0
  286. package/lib/quoxon/ask.js +121 -0
  287. package/lib/quoxon/away.js +52 -0
  288. package/lib/quoxon/bang.js +224 -0
  289. package/lib/quoxon/bulkRisk.js +17 -0
  290. package/lib/quoxon/capabilities.js +82 -0
  291. package/lib/quoxon/cells.js +53 -0
  292. package/lib/quoxon/commandShort.js +112 -0
  293. package/lib/quoxon/commands.js +479 -0
  294. package/lib/quoxon/connect.js +654 -0
  295. package/lib/quoxon/connectCard.js +45 -0
  296. package/lib/quoxon/consent.js +86 -0
  297. package/lib/quoxon/context.js +68 -0
  298. package/lib/quoxon/copy.js +34 -0
  299. package/lib/quoxon/countdownLine.js +11 -0
  300. package/lib/quoxon/demo.autoplay.js +29 -0
  301. package/lib/quoxon/demo.fixtures.js +24 -0
  302. package/lib/quoxon/demo.js +76 -0
  303. package/lib/quoxon/devObserver.js +20 -0
  304. package/lib/quoxon/dividerAocl.js +191 -0
  305. package/lib/quoxon/dividerRule.js +57 -0
  306. package/lib/quoxon/dragonModes.js +150 -0
  307. package/lib/quoxon/dragonView.js +41 -0
  308. package/lib/quoxon/earlyInput.js +40 -0
  309. package/lib/quoxon/echo.js +15 -0
  310. package/lib/quoxon/egg.js +36 -0
  311. package/lib/quoxon/events.js +677 -0
  312. package/lib/quoxon/feedback.js +50 -0
  313. package/lib/quoxon/gallery.js +237 -0
  314. package/lib/quoxon/handoffOffer.js +78 -0
  315. package/lib/quoxon/hatSwitch.js +37 -0
  316. package/lib/quoxon/headless.js +161 -0
  317. package/lib/quoxon/hostUrls.js +41 -0
  318. package/lib/quoxon/indicator.js +197 -0
  319. package/lib/quoxon/intent.js +52 -0
  320. package/lib/quoxon/keys.js +164 -0
  321. package/lib/quoxon/launcher.js +103 -0
  322. package/lib/quoxon/main.js +176 -0
  323. package/lib/quoxon/markdown.js +341 -0
  324. package/lib/quoxon/markdownSettle.js +19 -0
  325. package/lib/quoxon/matrixDomains.json +46 -0
  326. package/lib/quoxon/mentions.js +90 -0
  327. package/lib/quoxon/mind.js +103 -0
  328. package/lib/quoxon/mode.js +46 -0
  329. package/lib/quoxon/model.js +311 -0
  330. package/lib/quoxon/modelPrefs.js +345 -0
  331. package/lib/quoxon/motion/clock.js +70 -0
  332. package/lib/quoxon/motion/color.js +35 -0
  333. package/lib/quoxon/motion/frame.js +14 -0
  334. package/lib/quoxon/motion/level.js +91 -0
  335. package/lib/quoxon/motion/reveal.js +54 -0
  336. package/lib/quoxon/motion/ring.js +79 -0
  337. package/lib/quoxon/mouse.js +211 -0
  338. package/lib/quoxon/noai.js +57 -0
  339. package/lib/quoxon/objects/agent.js +61 -0
  340. package/lib/quoxon/objects/away.js +72 -0
  341. package/lib/quoxon/objects/crystal.js +100 -0
  342. package/lib/quoxon/objects/fetch.js +182 -0
  343. package/lib/quoxon/objects/fleet.js +149 -0
  344. package/lib/quoxon/objects/health.js +186 -0
  345. package/lib/quoxon/objects/index.js +106 -0
  346. package/lib/quoxon/objects/ladder.js +71 -0
  347. package/lib/quoxon/objects/memory.js +44 -0
  348. package/lib/quoxon/objects/mirror.js +137 -0
  349. package/lib/quoxon/objects/model.js +118 -0
  350. package/lib/quoxon/objects/peek.js +17 -0
  351. package/lib/quoxon/objects/proof.js +110 -0
  352. package/lib/quoxon/objects/prove.js +130 -0
  353. package/lib/quoxon/objects/replay.js +125 -0
  354. package/lib/quoxon/objects/sitrep.js +58 -0
  355. package/lib/quoxon/objects/skill.js +105 -0
  356. package/lib/quoxon/objects/view.js +87 -0
  357. package/lib/quoxon/objects/workflow.js +119 -0
  358. package/lib/quoxon/observers.js +42 -0
  359. package/lib/quoxon/onboarding/copy.js +263 -0
  360. package/lib/quoxon/onboarding/credential.js +108 -0
  361. package/lib/quoxon/onboarding/detect.js +22 -0
  362. package/lib/quoxon/onboarding/dockerOffer.js +45 -0
  363. package/lib/quoxon/onboarding/failureCause.js +33 -0
  364. package/lib/quoxon/onboarding/finish.js +134 -0
  365. package/lib/quoxon/onboarding/index.js +94 -0
  366. package/lib/quoxon/onboarding/installRunner.js +284 -0
  367. package/lib/quoxon/onboarding/intent.js +53 -0
  368. package/lib/quoxon/onboarding/journey.js +73 -0
  369. package/lib/quoxon/onboarding/preflight.js +156 -0
  370. package/lib/quoxon/onboarding/probe.js +65 -0
  371. package/lib/quoxon/onboarding/runner.js +58 -0
  372. package/lib/quoxon/onboarding/view.js +682 -0
  373. package/lib/quoxon/orgLabel.js +10 -0
  374. package/lib/quoxon/orgSwitch.js +33 -0
  375. package/lib/quoxon/ownBroadcast.js +22 -0
  376. package/lib/quoxon/palette.js +43 -0
  377. package/lib/quoxon/print.js +98 -0
  378. package/lib/quoxon/profileSwitch.js +53 -0
  379. package/lib/quoxon/promptHistory.js +41 -0
  380. package/lib/quoxon/providers.js +48 -0
  381. package/lib/quoxon/qart.js +348 -0
  382. package/lib/quoxon/qdragon.js +410 -0
  383. package/lib/quoxon/qdragonFly.js +61 -0
  384. package/lib/quoxon/qdragonScenes.js +282 -0
  385. package/lib/quoxon/qeyes.js +213 -0
  386. package/lib/quoxon/qglyph.js +143 -0
  387. package/lib/quoxon/qhello.js +58 -0
  388. package/lib/quoxon/qmotion.js +132 -0
  389. package/lib/quoxon/render.js +1167 -0
  390. package/lib/quoxon/resend.js +72 -0
  391. package/lib/quoxon/roster.js +307 -0
  392. package/lib/quoxon/route.js +39 -0
  393. package/lib/quoxon/rows.js +711 -0
  394. package/lib/quoxon/rowsStore.js +80 -0
  395. package/lib/quoxon/rules.js +130 -0
  396. package/lib/quoxon/screenContext.js +150 -0
  397. package/lib/quoxon/screenFrames.js +254 -0
  398. package/lib/quoxon/session.js +238 -0
  399. package/lib/quoxon/sessionDead.js +17 -0
  400. package/lib/quoxon/signin.js +125 -0
  401. package/lib/quoxon/skills.js +30 -0
  402. package/lib/quoxon/slash/agents.js +112 -0
  403. package/lib/quoxon/slash/argSources.js +65 -0
  404. package/lib/quoxon/slash/args.js +65 -0
  405. package/lib/quoxon/slash/automate.js +89 -0
  406. package/lib/quoxon/slash/broadcast.js +140 -0
  407. package/lib/quoxon/slash/broadcastRun.js +108 -0
  408. package/lib/quoxon/slash/card.js +30 -0
  409. package/lib/quoxon/slash/challenge.js +114 -0
  410. package/lib/quoxon/slash/cliCommand.js +53 -0
  411. package/lib/quoxon/slash/cliLeaves.json +948 -0
  412. package/lib/quoxon/slash/compact.js +90 -0
  413. package/lib/quoxon/slash/confirm.js +30 -0
  414. package/lib/quoxon/slash/crystallize.js +69 -0
  415. package/lib/quoxon/slash/dashboard.js +136 -0
  416. package/lib/quoxon/slash/draft.js +54 -0
  417. package/lib/quoxon/slash/hatWalk.js +206 -0
  418. package/lib/quoxon/slash/inverses.js +77 -0
  419. package/lib/quoxon/slash/killer.js +24 -0
  420. package/lib/quoxon/slash/killerApi.js +16 -0
  421. package/lib/quoxon/slash/ledger.js +88 -0
  422. package/lib/quoxon/slash/listView.js +30 -0
  423. package/lib/quoxon/slash/modelCmd.js +261 -0
  424. package/lib/quoxon/slash/palette.js +118 -0
  425. package/lib/quoxon/slash/paletteController.js +169 -0
  426. package/lib/quoxon/slash/panels.js +116 -0
  427. package/lib/quoxon/slash/picker.js +56 -0
  428. package/lib/quoxon/slash/restart.js +202 -0
  429. package/lib/quoxon/slash/resume.js +59 -0
  430. package/lib/quoxon/slash/rowCmd.js +157 -0
  431. package/lib/quoxon/slash/rowWalk.js +175 -0
  432. package/lib/quoxon/slash/settings.js +60 -0
  433. package/lib/quoxon/slash/spawn.js +177 -0
  434. package/lib/quoxon/slash/stepUp.js +274 -0
  435. package/lib/quoxon/slash/stepUpCard.js +60 -0
  436. package/lib/quoxon/slash/stepUpCmds.js +78 -0
  437. package/lib/quoxon/slash/undoApi.js +38 -0
  438. package/lib/quoxon/slash/undoCmd.js +49 -0
  439. package/lib/quoxon/slash/undoWish.js +15 -0
  440. package/lib/quoxon/slash/watch.js +164 -0
  441. package/lib/quoxon/slash/wish.js +23 -0
  442. package/lib/quoxon/slash/wishCmd.js +32 -0
  443. package/lib/quoxon/slotChat.js +300 -0
  444. package/lib/quoxon/slotFormat.js +195 -0
  445. package/lib/quoxon/slotRun.js +131 -0
  446. package/lib/quoxon/sound/index.js +186 -0
  447. package/lib/quoxon/sound/mapping.js +120 -0
  448. package/lib/quoxon/sound/output.js +52 -0
  449. package/lib/quoxon/sound/synth.js +122 -0
  450. package/lib/quoxon/stray.js +22 -0
  451. package/lib/quoxon/suggestCommand.js +59 -0
  452. package/lib/quoxon/term.js +141 -0
  453. package/lib/quoxon/textBuffer.js +416 -0
  454. package/lib/quoxon/theme.js +286 -0
  455. package/lib/quoxon/tips.js +433 -0
  456. package/lib/quoxon/toolError.js +41 -0
  457. package/lib/quoxon/transcript.js +188 -0
  458. package/lib/quoxon/web/qface.js +89 -0
  459. package/lib/quoxon/welcome.js +52 -0
  460. package/lib/quoxon/whatsnew.js +136 -0
  461. package/lib/quoxon/whatsnew.jsonl +89 -0
  462. package/lib/quoxon/windowOrg.js +113 -0
  463. package/lib/server/diag.js +129 -0
  464. package/lib/server/doctor.js +71 -0
  465. package/lib/server/install.js +149 -0
  466. package/lib/server/pluginEnable.js +161 -0
  467. package/lib/server/skew.js +56 -0
  468. package/lib/server/snapshot.js +131 -0
  469. package/lib/server/status.js +128 -0
  470. package/lib/server/sys.js +65 -0
  471. package/lib/server/uninstall.js +63 -0
  472. package/lib/server/update.js +234 -0
  473. package/lib/toolOutputShape.js +315 -0
  474. package/lib/tui/app.jsx +289 -0
  475. package/lib/tui/components/Breadcrumb.jsx +42 -0
  476. package/lib/tui/components/ChatInput.jsx +68 -0
  477. package/lib/tui/components/Confirm.jsx +32 -0
  478. package/lib/tui/components/DataTable.jsx +270 -0
  479. package/lib/tui/components/DetailPanel.jsx +241 -0
  480. package/lib/tui/components/HelpOverlay.jsx +67 -0
  481. package/lib/tui/components/Layout.jsx +129 -0
  482. package/lib/tui/components/Palette.jsx +141 -0
  483. package/lib/tui/components/SearchInput.jsx +125 -0
  484. package/lib/tui/components/Sparkline.jsx +55 -0
  485. package/lib/tui/components/StatusBar.jsx +133 -0
  486. package/lib/tui/components/Toast.jsx +52 -0
  487. package/lib/tui/hooks/useApi.js +44 -0
  488. package/lib/tui/hooks/useHealth.js +90 -0
  489. package/lib/tui/hooks/usePolledApi.js +86 -0
  490. package/lib/tui/hooks/useTerminalSize.js +46 -0
  491. package/lib/tui/index.js +56 -0
  492. package/lib/tui/intro/Intro.jsx +332 -0
  493. package/lib/tui/jsx-loader.js +32 -0
  494. package/lib/tui/keys.js +87 -0
  495. package/lib/tui/registry.js +49 -0
  496. package/lib/tui/theme.js +103 -0
  497. package/lib/tui/views/AdminView.jsx +398 -0
  498. package/lib/tui/views/AgentView.jsx +204 -0
  499. package/lib/tui/views/BackupView.jsx +225 -0
  500. package/lib/tui/views/BrainView.jsx +223 -0
  501. package/lib/tui/views/ChatView.jsx +468 -0
  502. package/lib/tui/views/FilesView.jsx +218 -0
  503. package/lib/tui/views/FleetView.jsx +234 -0
  504. package/lib/tui/views/GovernanceView.jsx +453 -0
  505. package/lib/tui/views/HealthView.jsx +216 -0
  506. package/lib/tui/views/HyperionDeck.jsx +79 -0
  507. package/lib/tui/views/InboxView.jsx +228 -0
  508. package/lib/tui/views/LucidDeck.jsx +719 -0
  509. package/lib/tui/views/MainMenu.jsx +123 -0
  510. package/lib/tui/views/MemoryView.jsx +377 -0
  511. package/lib/tui/views/ObjectivesView.jsx +230 -0
  512. package/lib/tui/views/OrgView.jsx +229 -0
  513. package/lib/tui/views/ToolView.jsx +221 -0
  514. package/lib/tui/views/VaultView.jsx +422 -0
  515. package/lib/tui/views/WorkflowView.jsx +329 -0
  516. package/package.json +75 -4
@@ -0,0 +1,597 @@
1
+ /**
2
+ * Engagement commands: QUOX-SECURITY Phase 0 engagement/scope object.
3
+ *
4
+ * quox engagement create <name> --environment-tier <tier> [--risk-ceiling <tier>]
5
+ * quox engagement list
6
+ * quox engagement show <id>
7
+ * quox engagement add-target <id> <type> <value> [--verification-method <method>]
8
+ * quox engagement verify-target <id> <targetId>
9
+ *
10
+ * Backend: services/collector/server.js's /api/v1/security/engagements
11
+ * routes (collector-native, own security.db, not a proxy). No agent
12
+ * capability exists yet, this is pure engagement/scope CRUD plus the
13
+ * domain-verification challenge that every future security capability
14
+ * call must pass. See lib/securityEngagement.js for the enforcement
15
+ * itself (environment_tier/risk_ceiling validation, verification, and the
16
+ * policy gate wired into approvalGate.js).
17
+ */
18
+
19
+ import { writeFileSync } from 'node:fs';
20
+ import { formatOutput, formatDetail, printSuccess, printInfo } from '../core/output.js';
21
+ import { events, printEvent, line as glyphLine } from '../core/quoxglyph.js';
22
+ import { resolveEndpoint } from '../api/endpoints.js';
23
+
24
+ export function registerEngagementCommands(program, ctx) {
25
+ const { client, auth } = ctx;
26
+
27
+ const engagement = program
28
+ .command('engagement')
29
+ .description('QUOX-SECURITY: engagements + scope targets, live capability scanners (run via scan/scan-async; see "engagement tools" for the list), and findings (report/verify/regrade/resolve)');
30
+
31
+ engagement
32
+ .command('create <name>')
33
+ .description('Create a new security engagement')
34
+ .requiredOption('--environment-tier <tier>', 'dev | staging | production')
35
+ .option('--risk-ceiling <tier>', 'passive | discovery | safe_active | intrusive | credential_sensitive | exploit_emulation (production caps at discovery in Phase 0)')
36
+ .option('--time-window-start <iso>', 'ISO 8601 timestamp')
37
+ .option('--time-window-end <iso>', 'ISO 8601 timestamp')
38
+ .option('--responsible-project <name>', 'the project/team this engagement belongs to')
39
+ .action(async (name, options) => {
40
+ auth.requireAuth();
41
+ const body = { name, environment_tier: options.environmentTier };
42
+ if (options.riskCeiling) body.risk_ceiling = options.riskCeiling;
43
+ if (options.timeWindowStart || options.timeWindowEnd) {
44
+ body.time_window = { start: options.timeWindowStart || null, end: options.timeWindowEnd || null };
45
+ }
46
+ if (options.responsibleProject) body.responsible_project = options.responsibleProject;
47
+
48
+ const ep = resolveEndpoint('engagement.create');
49
+ const data = await client.post(ep.path, { service: ep.service, body });
50
+ printEvent(events.created('quox', 'engagement', data.engagement.id));
51
+ formatDetail(data.engagement, { format: program.opts().output, title: 'Engagement' });
52
+ });
53
+
54
+ engagement
55
+ .command('list')
56
+ .description('List engagements for the current org')
57
+ .action(async () => {
58
+ auth.requireAuth();
59
+ const ep = resolveEndpoint('engagement.list');
60
+ const data = await client.get(ep.path, { service: ep.service });
61
+ formatOutput({
62
+ columns: ['id', 'name', 'status', 'environment_tier', 'risk_ceiling', 'created_at'],
63
+ headers: ['ID', 'Name', 'Status', 'Environment', 'Risk Ceiling', 'Created'],
64
+ rows: data.engagements || [],
65
+ }, { format: program.opts().output, emptyMessage: 'No engagements yet.' });
66
+ });
67
+
68
+ engagement
69
+ .command('show <id>')
70
+ .description('Show an engagement and its targets')
71
+ .action(async (id) => {
72
+ auth.requireAuth();
73
+ const ep = resolveEndpoint('engagement.get', { id });
74
+ const data = await client.get(ep.path, { service: ep.service });
75
+ formatDetail(data.engagement, { format: program.opts().output, title: 'Engagement' });
76
+ formatOutput({
77
+ columns: ['id', 'type', 'value', 'verification_status', 'verified_at'],
78
+ headers: ['ID', 'Type', 'Value', 'Verification', 'Verified At'],
79
+ rows: data.targets || [],
80
+ }, { format: program.opts().output, emptyMessage: 'No targets added yet.' });
81
+ });
82
+
83
+ engagement
84
+ .command('set-gripe-target <id>')
85
+ .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.')
86
+ .option('--repo <owner/name>', 'GitHub repo for findings from this engagement (owner/name). Pass an empty value to clear.')
87
+ .option('--connector-org <orgId>', 'org whose GitHub connector token files the issue')
88
+ .option('--clear', 'clear both overrides back to the platform default')
89
+ .action(async (id, options) => {
90
+ auth.requireAuth();
91
+ if (!options.clear && options.repo === undefined && options.connectorOrg === undefined) {
92
+ throw new Error('nothing to set: pass --repo <owner/name>, --connector-org <orgId>, or --clear');
93
+ }
94
+ let gripeRepo = null;
95
+ let connectorOrg = null;
96
+ if (!options.clear) {
97
+ gripeRepo = options.repo ? options.repo : null;
98
+ connectorOrg = options.connectorOrg ? options.connectorOrg : null;
99
+ }
100
+ const ep = resolveEndpoint('engagement.gripe_target.set', { id });
101
+ const data = await client.patch(ep.path, {
102
+ service: ep.service,
103
+ body: { gripe_repo: gripeRepo, gripe_connector_org_id: connectorOrg },
104
+ });
105
+ const eng = data.engagement;
106
+ printSuccess(`Gripe target updated for engagement ${eng.id}.`);
107
+ console.log(` Repo: ${eng.gripe_repo || 'platform default (quoxai/quox)'}`);
108
+ console.log(` Connector org: ${eng.gripe_connector_org_id || 'engagement own org'}`);
109
+ });
110
+
111
+ engagement
112
+ .command('add-target <id> <type> <value>')
113
+ .description('Add a target (type: domain | cidr | repo | cloud_account | url) to an engagement')
114
+ .option('--verification-method <method>', 'dns_txt | well_known_file | quoxagent_install (private-network targets, proven via real fleet membership; signed_attestation not implemented in Phase 0)')
115
+ .action(async (id, type, value, options) => {
116
+ auth.requireAuth();
117
+ const body = { type, value };
118
+ if (options.verificationMethod) body.verification_method = options.verificationMethod;
119
+
120
+ const ep = resolveEndpoint('engagement.target.create', { id });
121
+ const data = await client.post(ep.path, { service: ep.service, body });
122
+ printSuccess(`Target "${value}" added to engagement ${id}.`);
123
+ if (data.target.verification_token) {
124
+ // #1007: a url target is challenged on its hostname (dns_txt) or at its own origin (well_known_file)
125
+ const stored = data.target.value || value;
126
+ const url = data.target.type === 'url' ? (() => { try { return new URL(stored); } catch { return null; } })() : null;
127
+ if (data.target.verification_method === 'dns_txt') {
128
+ console.log(` Add a DNS TXT record on ${url ? url.hostname : stored}: quox-verify=${data.target.verification_token}`);
129
+ } else if (data.target.verification_method === 'well_known_file') {
130
+ console.log(` Serve this content at ${url ? url.origin : `https://${stored}`}/.well-known/quox-verification: ${data.target.verification_token}`);
131
+ }
132
+ console.log(` Then run: quox engagement verify-target ${id} ${data.target.id}`);
133
+ }
134
+ formatDetail(data.target, { format: program.opts().output, title: 'Target' });
135
+ });
136
+
137
+ engagement
138
+ .command('verify-target <id> <targetId>')
139
+ .description('Run the domain-verification challenge for a target')
140
+ .action(async (id, targetId) => {
141
+ auth.requireAuth();
142
+ const ep = resolveEndpoint('engagement.target.verify', { id, targetId });
143
+ const data = await client.post(ep.path, { service: ep.service, body: {} });
144
+ if (data.target.verification_status === 'verified') {
145
+ printEvent(glyphLine('quox', 'target verified', { result: data.target.value, status: 'ok' }));
146
+ } else {
147
+ console.log(` Verification status: ${data.target.verification_status} (challenge did not match yet).`);
148
+ }
149
+ formatDetail(data.target, { format: program.opts().output, title: 'Target' });
150
+ });
151
+
152
+ engagement
153
+ .command('check-policy <id> <targetValue> <capabilityRisk>')
154
+ .description('Check whether a capability of the given risk tier is currently allowed against a target (passive|discovery|safe_active|intrusive|credential_sensitive|exploit_emulation)')
155
+ .action(async (id, targetValue, capabilityRisk) => {
156
+ auth.requireAuth();
157
+ const ep = resolveEndpoint('engagement.check_policy', { id });
158
+ const data = await client.post(ep.path, { service: ep.service, body: { target_value: targetValue, capability_risk: capabilityRisk } });
159
+ if (data.allowed) {
160
+ printEvent(glyphLine('quox', 'policy check', { result: data.requiresApproval ? 'allowed · requires approval' : 'allowed', status: data.requiresApproval ? 'warn' : 'ok' }));
161
+ } else {
162
+ console.log(` Denied: ${data.reason}`);
163
+ }
164
+ });
165
+
166
+ engagement
167
+ .command('scan <id> <target> <tool>')
168
+ .description('Attempt a Phase 1 capability tool against a verified, in-scope target (tool: discover_hosts | probe_http | discover_app_ports | vuln_scan). Policy-gated; honestly fails if no scan host is configured.')
169
+ .action(async (id, target, tool) => {
170
+ auth.requireAuth();
171
+ const ep = resolveEndpoint('engagement.capability', { id, tool });
172
+ const data = await client.post(ep.path, { service: ep.service, body: { target } });
173
+ if (data.success) {
174
+ printEvent(glyphLine('quox', `${tool}`, { result: 'completed', status: 'ok' }));
175
+ console.log(JSON.stringify(data.output, null, 2));
176
+ } else {
177
+ console.log(` Failed (allowed: ${data.allowed}): ${data.error}`);
178
+ process.exitCode = 1; // #1007: a refusal is not success, for scripts as much as people
179
+ }
180
+ });
181
+
182
+ engagement
183
+ .command('discover-ports <id> <hostTarget>')
184
+ .description('Discover which app ports on a verified, in-scope host actually serve HTTP(S) (httpx over a curated port set), so a scan hits the real surface instead of only :80/:443. With --add, register each discovered endpoint as a verified url target (control re-proven via fleet membership).')
185
+ .option('--add', 'Add each discovered endpoint as a url target and verify it')
186
+ .action(async (id, hostTarget, options) => {
187
+ auth.requireAuth();
188
+ const ep = resolveEndpoint('engagement.discover_app_ports', { id });
189
+ const data = await client.post(ep.path, { service: ep.service, body: { target: hostTarget, add: options.add === true } });
190
+ if (!data.success) {
191
+ console.log(` Failed (allowed: ${data.allowed}): ${data.error}`);
192
+ process.exitCode = 1; // #1007: a refusal is not success, for scripts as much as people
193
+ return;
194
+ }
195
+ const discovered = data.discovered || [];
196
+ printSuccess(`Discovered ${discovered.length} serving endpoint(s) on ${hostTarget}.`);
197
+ formatOutput({
198
+ columns: ['url', 'status_code', 'title'],
199
+ headers: ['URL', 'Status', 'Title'],
200
+ rows: discovered,
201
+ }, { format: program.opts().output, emptyMessage: 'No serving app ports found.' });
202
+ if (options.add) {
203
+ const added = data.added || [];
204
+ const skipped = data.skipped || [];
205
+ printSuccess(`Added ${added.length} url target(s); skipped ${skipped.length} already in scope.`);
206
+ if (added.length) {
207
+ formatOutput({
208
+ columns: ['url', 'targetId', 'verified'],
209
+ headers: ['URL', 'Target', 'Verified'],
210
+ rows: added,
211
+ }, { format: program.opts().output, emptyMessage: 'None added.' });
212
+ }
213
+ }
214
+ });
215
+
216
+ engagement
217
+ .command('tools')
218
+ .description('List the real capability tools (name, risk tier, binary). Quoxcli-parity fix: this catalog backed the dashboard Tools tab but had no CLI verb.')
219
+ .action(async () => {
220
+ auth.requireAuth();
221
+ const ep = resolveEndpoint('engagement.capability_tools');
222
+ const data = await client.get(ep.path, { service: ep.service });
223
+ formatOutput({
224
+ columns: ['tool', 'capabilityRisk', 'binary', 'description'],
225
+ headers: ['Tool', 'Risk', 'Binary', 'Description'],
226
+ rows: data.tools || [],
227
+ }, { format: program.opts().output, emptyMessage: 'No capability tools registered.' });
228
+ });
229
+
230
+ engagement
231
+ .command('scan-async <id> <target> <tool>')
232
+ .description('Start a capability tool as a background job and return immediately (job_id). Use for tools that can outrun a single request, e.g. vuln_scan/nuclei or discover_app_ports over a wide host set. Poll with `quox engagement job <jobId>`.')
233
+ .action(async (id, target, tool) => {
234
+ auth.requireAuth();
235
+ const ep = resolveEndpoint('engagement.capability.start', { id, tool });
236
+ const data = await client.post(ep.path, { service: ep.service, body: { target } });
237
+ if (data.success && data.job_id) {
238
+ printSuccess(`${tool} started as job ${data.job_id} (status: ${data.status}).`);
239
+ console.log(` Poll: quox engagement job ${data.job_id}`);
240
+ } else {
241
+ console.log(` Failed (allowed: ${data.allowed}): ${data.error}`);
242
+ process.exitCode = 1; // #1007: a refusal is not success, for scripts as much as people
243
+ }
244
+ });
245
+
246
+ engagement
247
+ .command('job <jobId>')
248
+ .description('Check the status/result of an async scan job (queued | running | succeeded | failed)')
249
+ .action(async (jobId) => {
250
+ auth.requireAuth();
251
+ const ep = resolveEndpoint('engagement.job.get', { jobId });
252
+ const data = await client.get(ep.path, { service: ep.service });
253
+ formatDetail(data.job, { format: program.opts().output, title: 'Scan job' });
254
+ });
255
+
256
+ engagement
257
+ .command('jobs <id>')
258
+ .description('List async scan jobs for an engagement')
259
+ .action(async (id) => {
260
+ auth.requireAuth();
261
+ const ep = resolveEndpoint('engagement.jobs.list', { id });
262
+ const data = await client.get(ep.path, { service: ep.service });
263
+ formatOutput({
264
+ columns: ['id', 'tool', 'target', 'status', 'created_at', 'finished_at'],
265
+ headers: ['ID', 'Tool', 'Target', 'Status', 'Created', 'Finished'],
266
+ rows: data.jobs || [],
267
+ }, { format: program.opts().output, emptyMessage: 'No scan jobs yet.' });
268
+ });
269
+
270
+ engagement
271
+ .command('report-finding <id> <targetValue> <title> <description>')
272
+ .description('Report a candidate finding against a target within an engagement. Always starts as status "candidate"')
273
+ .option('--severity <level>', 'info | low | medium | high | critical', 'info')
274
+ .action(async (id, targetValue, title, description, options) => {
275
+ auth.requireAuth();
276
+ const ep = resolveEndpoint('engagement.findings.report', { id });
277
+ const data = await client.post(ep.path, {
278
+ service: ep.service,
279
+ body: { target_value: targetValue, title, description, severity: options.severity },
280
+ });
281
+ printSuccess(`Finding "${data.finding.id}" reported (status: candidate).`);
282
+ formatDetail(data.finding, { format: program.opts().output, title: 'Finding' });
283
+ // quox#601 report-time reminder: severity defaults to 'info', and a batch
284
+ // left there ships understated on the published /security projection
285
+ // (that is exactly what bit trust-proof cycle 3). Nudge, don't block: a
286
+ // genuinely-info finding is fine, but the default should be a choice.
287
+ if (data.finding?.severity === 'info') {
288
+ printInfo(`Severity is 'info' (the default). If that understates it, re-grade with a reason: quox engagement regrade-finding ${data.finding.id} <severity> --reason "<why>"`);
289
+ }
290
+ });
291
+
292
+ engagement
293
+ .command('findings <id>')
294
+ .description('List findings for an engagement')
295
+ .action(async (id) => {
296
+ auth.requireAuth();
297
+ const ep = resolveEndpoint('engagement.findings.list', { id });
298
+ const data = await client.get(ep.path, { service: ep.service });
299
+ const findings = data.findings || [];
300
+ // Surface gripe_url/quoxplan_ref columns only when at least one
301
+ // finding actually has a value — an all-empty column is just noise.
302
+ const hasGripe = findings.some((f) => f.gripe_url);
303
+ const hasQuoxplan = findings.some((f) => f.quoxplan_ref);
304
+ const columns = ['id', 'title', 'severity', 'status', 'target_value', 'created_at'];
305
+ const headers = ['ID', 'Title', 'Severity', 'Status', 'Target', 'Created'];
306
+ if (hasGripe) { columns.push('gripe_url'); headers.push('Issue'); }
307
+ if (hasQuoxplan) { columns.push('quoxplan_ref'); headers.push('QuoxPlan'); }
308
+ formatOutput({
309
+ columns,
310
+ headers,
311
+ rows: findings,
312
+ }, { format: program.opts().output, emptyMessage: 'No findings yet.' });
313
+ });
314
+
315
+ engagement
316
+ .command('verify-finding <findingId> <verdict>')
317
+ .description('Confirm or refute a candidate finding (verdict: confirmed | refuted). A reason is required, a bare verdict is refused')
318
+ .requiredOption('--reason <text>', 'why this verdict was reached')
319
+ .action(async (findingId, verdict, options) => {
320
+ auth.requireAuth();
321
+ const ep = resolveEndpoint('engagement.findings.verify', { findingId });
322
+ const data = await client.post(ep.path, { service: ep.service, body: { verdict, reason: options.reason } });
323
+ printSuccess(`Finding "${data.finding.id}" ${data.finding.status}.`);
324
+ formatDetail(data.finding, { format: program.opts().output, title: 'Finding' });
325
+ });
326
+
327
+ engagement
328
+ .command('resolve-finding <findingId>')
329
+ .description('Mark a finding remediated (status: resolved). Refuses a refuted finding')
330
+ .action(async (findingId) => {
331
+ auth.requireAuth();
332
+ const ep = resolveEndpoint('engagement.findings.resolve', { findingId });
333
+ const data = await client.post(ep.path, { service: ep.service, body: {} });
334
+ printSuccess(`Finding "${data.finding.id}" ${data.finding.status}.`);
335
+ formatDetail(data.finding, { format: program.opts().output, title: 'Finding' });
336
+ });
337
+
338
+ engagement
339
+ .command('regrade-finding <findingId> <severity>')
340
+ .description('Re-grade a reported finding\'s severity (info | low | medium | high | critical). A reason is required, the same discipline as verify-finding, because severity is a published field')
341
+ .requiredOption('--reason <text>', 'why the severity is being changed')
342
+ .action(async (findingId, severity, options) => {
343
+ auth.requireAuth();
344
+ const ep = resolveEndpoint('engagement.findings.regrade', { findingId });
345
+ const data = await client.post(ep.path, { service: ep.service, body: { severity, reason: options.reason } });
346
+ const f = data.finding;
347
+ printSuccess(`Finding "${f.id}" re-graded ${f.severity_from} -> ${f.severity}.`);
348
+ formatDetail(f, { format: program.opts().output, title: 'Finding' });
349
+ });
350
+
351
+ // Finding -> next-steps pipeline: lib/securityReportActions.js. Raise
352
+ // issue (GitHub issue) / Propose as QuoxPlan / Download PDF, mirroring the
353
+ // dashboard Findings tab's action bar so nothing ships dashboard-only
354
+ // (quoxcli-parity gate).
355
+
356
+ // Known output-format values understood by the global -o/--output flag
357
+ // (bin/quox.js). The `report` command below reuses -o/--output for a
358
+ // different purpose (destination file path), which collides with that
359
+ // global flag at the root parser — a real path passed after `report <id>`
360
+ // lands in `program.opts().output` rather than this command's own
361
+ // `options.output`. Anything that isn't a recognized format name is
362
+ // treated as the destination file rather than silently dropped.
363
+ const KNOWN_OUTPUT_FORMATS = new Set(['table', 'json', 'csv', 'quiet']);
364
+
365
+ engagement
366
+ .command('report <id>')
367
+ .description('Download the engagement report (targets, tools run, findings) as a PDF')
368
+ .option('--pdf', 'Format flag — PDF is the only format currently shipped, kept for forward compatibility')
369
+ .option('--output <file>', 'Output file path (default: quox-security-<id>.pdf)')
370
+ .action(async (id, options) => {
371
+ auth.requireAuth();
372
+ const ep = resolveEndpoint('engagement.report.pdf', { id });
373
+ const res = await client.get(ep.path, { service: ep.service, raw: true });
374
+ if (!res.ok) {
375
+ let data;
376
+ try { data = await res.json(); } catch { data = { error: res.statusText }; }
377
+ throw new Error(data.error || `Request failed with status ${res.status}`);
378
+ }
379
+ const buf = Buffer.from(await res.arrayBuffer());
380
+ const rootOutput = program.opts().output;
381
+ // quox#919: the global --json reaches options.output as 'json' via
382
+ // mergeShadowedGlobals; an output-format name is never a file path.
383
+ const asPath = (v) => (v && !KNOWN_OUTPUT_FORMATS.has(v) ? v : null);
384
+ const outFile = asPath(options.output) || asPath(rootOutput) || `quox-security-${id}.pdf`;
385
+ writeFileSync(outFile, buf);
386
+ if (rootOutput === 'json') {
387
+ console.log(JSON.stringify({ success: true, file: outFile, bytes: buf.length }));
388
+ } else {
389
+ printSuccess(`Report saved to ${outFile} (${buf.length} bytes).`);
390
+ }
391
+ });
392
+
393
+ async function raiseIssue(findingId) {
394
+ auth.requireAuth();
395
+ const ep = resolveEndpoint('engagement.findings.gripe', { findingId });
396
+ const data = await client.post(ep.path, { service: ep.service, body: {} });
397
+ if (data.configured === false) {
398
+ console.log(` ${data.message || 'GitHub not configured.'}`);
399
+ return;
400
+ }
401
+ if (data.ok === false) {
402
+ console.log(` ${data.message || 'GitHub connection rejected.'}`);
403
+ return;
404
+ }
405
+ if (data.alreadyFiled) {
406
+ console.log(` Already filed: ${data.url}`);
407
+ return;
408
+ }
409
+ printSuccess(`Filed ${data.url} (repo: ${data.repo}).`);
410
+ }
411
+
412
+ engagement
413
+ .command('issue <findingId>')
414
+ .description('Raise a finding as a GitHub issue. Honest no-op if GitHub is not configured or the stored token is rejected, never a fake success. Skips re-filing if already raised.')
415
+ .action(raiseIssue);
416
+
417
+ // Deprecated alias, kept working (not advertised) so nothing that
418
+ // already scripted `quox engagement gripe` breaks.
419
+ engagement
420
+ .command('gripe <findingId>', { hidden: true })
421
+ .description('Deprecated alias for "quox engagement issue"')
422
+ .action(raiseIssue);
423
+
424
+ engagement
425
+ .command('propose <findingId>')
426
+ .description('Propose a finding as a QuoxPlan note (default stream: QUOX-SECURITY, #438)')
427
+ .option('--stream-id <id>', 'QuoxPlan stream id to append the note to (default QUOX-SECURITY)')
428
+ .action(async (findingId, options) => {
429
+ auth.requireAuth();
430
+ const ep = resolveEndpoint('engagement.findings.propose', { findingId });
431
+ const body = options.streamId ? { stream_id: options.streamId } : {};
432
+ const data = await client.post(ep.path, { service: ep.service, body });
433
+ printSuccess(`Proposed on ${data.streamId} (note ${data.note?.id ?? '—'}).`);
434
+ });
435
+
436
+ // QUOX-SECURITY-SCHED Phase 4: recurring scan schedules — a standing
437
+ // instruction to re-run a tool against a target on a fixed cadence,
438
+ // owned by an engagement. Backend: lib/securityEngagementSchedules.js
439
+ // (storage) + lib/securityScanScheduler.js (the server-side loop that
440
+ // actually fires due schedules). MIN_INTERVAL_SECONDS=300 is enforced
441
+ // server-side; a too-short --interval fails at create, not silently.
442
+ const schedule = engagement
443
+ .command('schedule')
444
+ .description('Recurring scan schedules (re-run a tool on a fixed cadence)');
445
+
446
+ schedule
447
+ .command('create <engagementId> <target> <tool>')
448
+ .description('Create a recurring schedule (tool: bare capability name, e.g. vuln_scan)')
449
+ .option('--interval <seconds>', 'interval cadence in seconds (minimum 300)')
450
+ .option('--cron <expr>', 'cron cadence, a 5-field UTC expression e.g. "0 3 * * 3" for weekly Wed 03:00')
451
+ .action(async (engagementId, target, tool, options) => {
452
+ auth.requireAuth();
453
+ if (options.interval && options.cron) {
454
+ throw new Error('--interval and --cron are mutually exclusive; pass exactly one cadence');
455
+ }
456
+ if (!options.interval && !options.cron) {
457
+ throw new Error('a cadence is required: pass --interval <seconds> or --cron "<expr>"');
458
+ }
459
+ const body = { target_value: target, tool };
460
+ if (options.cron) {
461
+ body.schedule_kind = 'cron';
462
+ body.cron_expression = options.cron;
463
+ } else {
464
+ const intervalSeconds = parseInt(options.interval, 10);
465
+ if (!Number.isInteger(intervalSeconds) || String(intervalSeconds) !== String(options.interval).trim()) {
466
+ throw new Error(`--interval must be an integer number of seconds, got "${options.interval}"`);
467
+ }
468
+ body.interval_seconds = intervalSeconds;
469
+ }
470
+ const ep = resolveEndpoint('engagement.schedule.create', { id: engagementId });
471
+ const data = await client.post(ep.path, {
472
+ service: ep.service,
473
+ body,
474
+ });
475
+ printSuccess(`Schedule "${data.schedule.id}" created (next run: ${data.schedule.next_run_at}).`);
476
+ formatDetail(data.schedule, { format: program.opts().output, title: 'Schedule' });
477
+ });
478
+
479
+ schedule
480
+ .command('list <engagementId>')
481
+ .description('List schedules for an engagement')
482
+ .action(async (engagementId) => {
483
+ auth.requireAuth();
484
+ const ep = resolveEndpoint('engagement.schedule.list', { id: engagementId });
485
+ const data = await client.get(ep.path, { service: ep.service });
486
+ formatOutput({
487
+ columns: ['id', 'target_value', 'tool', 'interval_seconds', 'enabled', 'next_run_at', 'last_status'],
488
+ headers: ['ID', 'Target', 'Tool', 'Interval (s)', 'Enabled', 'Next Run', 'Last Status'],
489
+ rows: data.schedules || [],
490
+ }, { format: program.opts().output, emptyMessage: 'No schedules yet.' });
491
+ });
492
+
493
+ schedule
494
+ .command('show <scheduleId>')
495
+ .description('Show a schedule, including its last diff summary')
496
+ .action(async (scheduleId) => {
497
+ auth.requireAuth();
498
+ const ep = resolveEndpoint('engagement.schedule.get', { scheduleId });
499
+ const data = await client.get(ep.path, { service: ep.service });
500
+ formatDetail(data.schedule, { format: program.opts().output, title: 'Schedule' });
501
+ if (data.schedule?.last_diff) {
502
+ console.log(` Last diff: +${data.schedule.last_diff.addedCount ?? 0} -${data.schedule.last_diff.removedCount ?? 0} (${data.schedule.last_diff.newHighSeverity?.length ?? 0} new high-severity)`);
503
+ }
504
+ });
505
+
506
+ schedule
507
+ .command('update <scheduleId>')
508
+ .description('Edit a schedule: change its cadence (--interval or --cron), target, tool, or enabled state')
509
+ .option('--interval <seconds>', 'switch to interval cadence in seconds (minimum 300)')
510
+ .option('--cron <expr>', 'switch to cron cadence, a 5-field UTC expression e.g. "0 3 * * 3"')
511
+ .option('--target <value>', 'change the target value (must be a verified, in-scope target)')
512
+ .option('--tool <name>', 'change the tool (bare capability name, e.g. vuln_scan)')
513
+ .option('--enable', 'enable the schedule')
514
+ .option('--disable', 'disable the schedule')
515
+ .action(async (scheduleId, options) => {
516
+ auth.requireAuth();
517
+ if (options.interval && options.cron) {
518
+ throw new Error('--interval and --cron are mutually exclusive; pass at most one cadence');
519
+ }
520
+ if (options.enable && options.disable) {
521
+ throw new Error('--enable and --disable are mutually exclusive');
522
+ }
523
+ const body = {};
524
+ if (options.cron) {
525
+ body.schedule_kind = 'cron';
526
+ body.cron_expression = options.cron;
527
+ } else if (options.interval) {
528
+ const intervalSeconds = parseInt(options.interval, 10);
529
+ if (!Number.isInteger(intervalSeconds) || String(intervalSeconds) !== String(options.interval).trim()) {
530
+ throw new Error(`--interval must be an integer number of seconds, got "${options.interval}"`);
531
+ }
532
+ body.schedule_kind = 'interval';
533
+ body.interval_seconds = intervalSeconds;
534
+ }
535
+ if (options.target) body.target_value = options.target;
536
+ if (options.tool) body.tool = options.tool;
537
+ if (options.enable) body.enabled = true;
538
+ if (options.disable) body.enabled = false;
539
+ if (Object.keys(body).length === 0) {
540
+ throw new Error('nothing to update: pass at least one of --interval, --cron, --target, --tool, --enable, --disable');
541
+ }
542
+ const ep = resolveEndpoint('engagement.schedule.update', { scheduleId });
543
+ const data = await client.patch(ep.path, { service: ep.service, body });
544
+ printSuccess(`Schedule "${data.schedule.id}" updated (next run: ${data.schedule.next_run_at}).`);
545
+ formatDetail(data.schedule, { format: program.opts().output, title: 'Schedule' });
546
+ });
547
+
548
+ schedule
549
+ .command('enable <scheduleId>')
550
+ .description('Re-enable a schedule')
551
+ .action(async (scheduleId) => {
552
+ auth.requireAuth();
553
+ const ep = resolveEndpoint('engagement.schedule.enable', { scheduleId });
554
+ const data = await client.post(ep.path, { service: ep.service, body: {} });
555
+ printSuccess(`Schedule "${data.schedule.id}" enabled (next run: ${data.schedule.next_run_at}).`);
556
+ });
557
+
558
+ schedule
559
+ .command('disable <scheduleId>')
560
+ .description('Disable a schedule (stops firing; existing history kept)')
561
+ .action(async (scheduleId) => {
562
+ auth.requireAuth();
563
+ const ep = resolveEndpoint('engagement.schedule.disable', { scheduleId });
564
+ const data = await client.post(ep.path, { service: ep.service, body: {} });
565
+ printSuccess(`Schedule "${data.schedule.id}" disabled.`);
566
+ });
567
+
568
+ schedule
569
+ .command('delete <scheduleId>')
570
+ .description('Delete a schedule')
571
+ .action(async (scheduleId) => {
572
+ auth.requireAuth();
573
+ const ep = resolveEndpoint('engagement.schedule.delete', { scheduleId });
574
+ await client.delete(ep.path, { service: ep.service });
575
+ printSuccess(`Schedule "${scheduleId}" deleted.`);
576
+ });
577
+
578
+ engagement
579
+ .command('sync-issues <id>')
580
+ .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')
581
+ .action(async (id) => {
582
+ auth.requireAuth();
583
+ const ep = resolveEndpoint('engagement.sync_issue_states', { id });
584
+ const data = await client.post(ep.path, { service: ep.service, body: {} });
585
+ if (data.configured === false) {
586
+ console.log(` ${data.message || 'GitHub not configured.'}`);
587
+ return;
588
+ }
589
+ printSuccess(`Checked ${data.checked ?? 0}, resolved ${data.resolved?.length ?? 0}.`);
590
+ if (data.resolved?.length) {
591
+ console.log(` Resolved: ${data.resolved.join(', ')}`);
592
+ }
593
+ if (data.errors?.length) {
594
+ console.log(` Errors: ${data.errors.map((e) => `${e.findingId} (${e.reason})`).join(', ')}`);
595
+ }
596
+ });
597
+ }