quoxcli 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -0
  3. package/bin/quox.js +99 -126
  4. package/lib/api/endpoints/actions.js +15 -0
  5. package/lib/api/endpoints/admin.js +46 -0
  6. package/lib/api/endpoints/agent-schedules.js +26 -0
  7. package/lib/api/endpoints/agents.js +7 -0
  8. package/lib/api/endpoints/assistants.js +12 -0
  9. package/lib/api/endpoints/away.js +15 -0
  10. package/lib/api/endpoints/blog.js +16 -0
  11. package/lib/api/endpoints/chat.js +14 -0
  12. package/lib/api/endpoints/contactsq.js +6 -0
  13. package/lib/api/endpoints/conversations.js +14 -0
  14. package/lib/api/endpoints/devServerRegistry.js +25 -0
  15. package/lib/api/endpoints/engagement.js +26 -0
  16. package/lib/api/endpoints/evidence.js +20 -0
  17. package/lib/api/endpoints/executions.js +13 -0
  18. package/lib/api/endpoints/fetchAllowlist.js +27 -0
  19. package/lib/api/endpoints/files.js +14 -0
  20. package/lib/api/endpoints/hat.js +44 -0
  21. package/lib/api/endpoints/inbox.js +20 -0
  22. package/lib/api/endpoints/index.js +26 -0
  23. package/lib/api/endpoints/loops.js +26 -0
  24. package/lib/api/endpoints/memory.js +19 -0
  25. package/lib/api/endpoints/mind.js +13 -0
  26. package/lib/api/endpoints/mon.js +179 -0
  27. package/lib/api/endpoints/objectives.js +12 -0
  28. package/lib/api/endpoints/plugins.js +18 -0
  29. package/lib/api/endpoints/presence.js +33 -0
  30. package/lib/api/endpoints/quoxplan.js +7 -0
  31. package/lib/api/endpoints/quoxseo.js +12 -0
  32. package/lib/api/endpoints/router.js +17 -0
  33. package/lib/api/endpoints/safety.js +7 -0
  34. package/lib/api/endpoints/server.js +21 -0
  35. package/lib/api/endpoints/setup.js +6 -0
  36. package/lib/api/endpoints/skills.js +20 -0
  37. package/lib/api/endpoints/stepup.js +20 -0
  38. package/lib/api/endpoints/teams.js +2 -2
  39. package/lib/api/endpoints/triggers.js +26 -0
  40. package/lib/api/endpoints/v1.js +120 -0
  41. package/lib/api/endpoints/ward.js +7 -0
  42. package/lib/api/endpoints/workflows.js +7 -0
  43. package/lib/api/endpoints/x.js +8 -0
  44. package/lib/commands/actions.js +131 -0
  45. package/lib/commands/admin.js +16 -0
  46. package/lib/commands/agent.js +170 -27
  47. package/lib/commands/agentAttach.js +140 -13
  48. package/lib/commands/approvals.js +78 -1
  49. package/lib/commands/aside.js +24 -0
  50. package/lib/commands/ask.js +146 -0
  51. package/lib/commands/assistant.js +59 -4
  52. package/lib/commands/auth.js +8 -2
  53. package/lib/commands/backup.js +8 -4
  54. package/lib/commands/blog.js +110 -0
  55. package/lib/commands/brain.js +105 -14
  56. package/lib/commands/build.js +19 -5
  57. package/lib/commands/chat.js +180 -26
  58. package/lib/commands/chatCompanion.js +27 -0
  59. package/lib/commands/chatState.js +45 -0
  60. package/lib/commands/claude.js +35 -38
  61. package/lib/commands/contacts.js +134 -4
  62. package/lib/commands/contactsSendAt.js +21 -0
  63. package/lib/commands/conversation.js +35 -4
  64. package/lib/commands/dashboard.js +43 -0
  65. package/lib/commands/department.js +28 -7
  66. package/lib/commands/devResident.js +15 -3
  67. package/lib/commands/devSupervisor.js +13 -1
  68. package/lib/commands/discord.js +75 -39
  69. package/lib/commands/done.js +98 -28
  70. package/lib/commands/engagement.js +107 -6
  71. package/lib/commands/evidence.js +154 -0
  72. package/lib/commands/ext.js +23 -0
  73. package/lib/commands/fetchAllowlist.js +71 -0
  74. package/lib/commands/file.js +38 -1
  75. package/lib/commands/fleet.js +336 -4
  76. package/lib/commands/governance.js +12 -3
  77. package/lib/commands/gripe.js +44 -13
  78. package/lib/commands/hat.js +254 -9
  79. package/lib/commands/helpers.js +18 -1
  80. package/lib/commands/hyperion.js +58 -0
  81. package/lib/commands/image.js +68 -0
  82. package/lib/commands/inbox.js +74 -23
  83. package/lib/commands/kuma.js +1050 -0
  84. package/lib/commands/kumaSla.js +174 -0
  85. package/lib/commands/licence.js +52 -155
  86. package/lib/commands/loop.js +291 -0
  87. package/lib/commands/lucid.js +10 -4
  88. package/lib/commands/memory.js +85 -8
  89. package/lib/commands/mind.js +71 -0
  90. package/lib/commands/mon.js +69 -2
  91. package/lib/commands/monitoring.js +1 -1
  92. package/lib/commands/motion.js +2 -2
  93. package/lib/commands/nucleus.js +48 -17
  94. package/lib/commands/objective.js +59 -1
  95. package/lib/commands/offload.js +89 -0
  96. package/lib/commands/onboard.js +2 -1
  97. package/lib/commands/org.js +30 -3
  98. package/lib/commands/peek.js +28 -0
  99. package/lib/commands/plan.js +38 -0
  100. package/lib/commands/plugin.js +110 -0
  101. package/lib/commands/presence.js +89 -0
  102. package/lib/commands/provider.js +217 -0
  103. package/lib/commands/qlarity.js +19 -3
  104. package/lib/commands/quoxchat.js +13 -4
  105. package/lib/commands/quoxseo.js +69 -0
  106. package/lib/commands/reach.js +2 -1
  107. package/lib/commands/research.js +79 -0
  108. package/lib/commands/router.js +77 -0
  109. package/lib/commands/rows.js +367 -0
  110. package/lib/commands/run.js +82 -10
  111. package/lib/commands/scope.js +2 -2
  112. package/lib/commands/server.js +405 -0
  113. package/lib/commands/setup.js +33 -7
  114. package/lib/commands/shortcut.js +41 -0
  115. package/lib/commands/skills.js +68 -9
  116. package/lib/commands/slot.js +260 -7
  117. package/lib/commands/status.js +1 -1
  118. package/lib/commands/team.js +3 -1
  119. package/lib/commands/tips.js +23 -0
  120. package/lib/commands/tool.js +133 -45
  121. package/lib/commands/trace.js +12 -7
  122. package/lib/commands/trigger.js +95 -0
  123. package/lib/commands/update.js +46 -0
  124. package/lib/commands/vault.js +31 -8
  125. package/lib/commands/vm.js +237 -0
  126. package/lib/commands/voice.js +34 -3
  127. package/lib/commands/volt.js +45 -3
  128. package/lib/commands/walkthrough.js +32 -0
  129. package/lib/commands/watch.js +73 -21
  130. package/lib/commands/welcome.js +14 -0
  131. package/lib/commands/workflow.js +189 -32
  132. package/lib/commands/x.js +13 -4
  133. package/lib/commands/youtube.js +172 -0
  134. package/lib/core/actingAs.js +2 -2
  135. package/lib/core/actionItems.js +115 -0
  136. package/lib/core/activeSummary.js +186 -0
  137. package/lib/core/agentAlias.js +2 -0
  138. package/lib/core/agentFamilies.js +7 -0
  139. package/lib/core/aside.js +54 -0
  140. package/lib/core/askDecision.js +60 -0
  141. package/lib/core/auth.js +164 -13
  142. package/lib/core/callerSource.js +1 -1
  143. package/lib/core/cliBuild.js +62 -0
  144. package/lib/core/client.js +152 -35
  145. package/lib/core/commandRegistrars.js +130 -0
  146. package/lib/core/config.js +143 -25
  147. package/lib/core/customHats.js +168 -0
  148. package/lib/core/doneEngineFetch.js +8 -0
  149. package/lib/core/errors.js +329 -20
  150. package/lib/core/findingReconcile.js +90 -0
  151. package/lib/core/global-flags.js +105 -5
  152. package/lib/core/governedSession.js +1 -3
  153. package/lib/core/hyperion.js +102 -0
  154. package/lib/core/inboxResolution.js +65 -0
  155. package/lib/core/lazyCommands.js +4 -1
  156. package/lib/core/liveSessionsReport.js +1 -1
  157. package/lib/core/loopGlyph.js +97 -0
  158. package/lib/core/offload.js +533 -0
  159. package/lib/core/org-override.js +1 -1
  160. package/lib/core/orgResolve.js +70 -0
  161. package/lib/core/output.js +11 -11
  162. package/lib/core/peek.js +126 -0
  163. package/lib/core/profileUsers.js +51 -0
  164. package/lib/core/promptCancel.js +18 -0
  165. package/lib/core/repoPath.js +11 -0
  166. package/lib/core/residentSeed.js +3 -1
  167. package/lib/core/rootOptions.js +39 -0
  168. package/lib/core/router.js +168 -0
  169. package/lib/core/selfUpdate.js +188 -0
  170. package/lib/core/sessionHeartbeat.js +12 -8
  171. package/lib/core/sessionsOverview.js +64 -0
  172. package/lib/core/shortcut.js +83 -0
  173. package/lib/core/slotKeeper.js +248 -3
  174. package/lib/core/slotWake.js +120 -0
  175. package/lib/core/supervisorEngine.js +71 -2
  176. package/lib/core/version.js +1 -1
  177. package/lib/core/vm.js +366 -0
  178. package/lib/core/vmTui.js +86 -0
  179. package/lib/core/walkthrough.js +35 -0
  180. package/lib/dragon/companionView.js +34 -0
  181. package/lib/dragon/frames.js +118 -0
  182. package/lib/dragon/jobs.js +123 -0
  183. package/lib/dragon/renderWorker.js +15 -0
  184. package/lib/dragon/select.js +72 -0
  185. package/lib/evidence/bundleExport.js +47 -0
  186. package/lib/evidence/bundleVerify.js +77 -0
  187. package/lib/hyperion/deck.js +29 -0
  188. package/lib/lucid/LucidCard.jsx +68 -0
  189. package/lib/lucid/LucidIntro.jsx +45 -58
  190. package/lib/lucid/cards.js +86 -0
  191. package/lib/lucid/markdown.js +81 -0
  192. package/lib/lucid/motion.js +56 -0
  193. package/lib/quoxon/EXTENSIONS.md +23 -0
  194. package/lib/quoxon/activity.js +14 -2
  195. package/lib/quoxon/aocl.js +117 -0
  196. package/lib/quoxon/api.js +690 -25
  197. package/lib/quoxon/app.js +3586 -250
  198. package/lib/quoxon/approvalFacts.js +96 -0
  199. package/lib/quoxon/approvalSentence.js +44 -0
  200. package/lib/quoxon/approvalsView.js +384 -0
  201. package/lib/quoxon/asideView.js +40 -0
  202. package/lib/quoxon/ask.js +121 -0
  203. package/lib/quoxon/away.js +52 -0
  204. package/lib/quoxon/bang.js +224 -0
  205. package/lib/quoxon/bulkRisk.js +17 -0
  206. package/lib/quoxon/capabilities.js +13 -6
  207. package/lib/quoxon/cells.js +53 -0
  208. package/lib/quoxon/commandShort.js +124 -0
  209. package/lib/quoxon/commands.js +429 -39
  210. package/lib/quoxon/connect.js +210 -23
  211. package/lib/quoxon/connectCard.js +19 -2
  212. package/lib/quoxon/consent.js +86 -0
  213. package/lib/quoxon/copy.js +4 -3
  214. package/lib/quoxon/countdownLine.js +20 -0
  215. package/lib/quoxon/deck.js +30 -0
  216. package/lib/quoxon/demo.autoplay.js +29 -0
  217. package/lib/quoxon/demo.fixtures.js +1 -1
  218. package/lib/quoxon/demo.js +46 -3
  219. package/lib/quoxon/devObserver.js +20 -0
  220. package/lib/quoxon/dividerAocl.js +192 -0
  221. package/lib/quoxon/dividerRule.js +57 -0
  222. package/lib/quoxon/dragonCompanion.js +255 -0
  223. package/lib/quoxon/dragonModes.js +151 -0
  224. package/lib/quoxon/dragonMoments.js +82 -0
  225. package/lib/quoxon/dragonReads.js +68 -0
  226. package/lib/quoxon/dragonView.js +41 -0
  227. package/lib/quoxon/earlyInput.js +40 -0
  228. package/lib/quoxon/echo.js +15 -0
  229. package/lib/quoxon/egg.js +2 -2
  230. package/lib/quoxon/events.js +266 -26
  231. package/lib/quoxon/extensions.js +276 -0
  232. package/lib/quoxon/feedback.js +50 -0
  233. package/lib/quoxon/gallery.js +21 -7
  234. package/lib/quoxon/handoffOffer.js +78 -0
  235. package/lib/quoxon/hatSwitch.js +37 -0
  236. package/lib/quoxon/headless.js +161 -0
  237. package/lib/quoxon/hostUrls.js +41 -0
  238. package/lib/quoxon/inboxView.js +79 -0
  239. package/lib/quoxon/indicator.js +6 -2
  240. package/lib/quoxon/installCli.js +79 -0
  241. package/lib/quoxon/intent.js +12 -1
  242. package/lib/quoxon/keys.js +51 -8
  243. package/lib/quoxon/launcher.js +103 -0
  244. package/lib/quoxon/loopCard.js +456 -0
  245. package/lib/quoxon/loopCards.js +180 -0
  246. package/lib/quoxon/loopStream.js +87 -0
  247. package/lib/quoxon/main.js +101 -160
  248. package/lib/quoxon/markdown.js +45 -21
  249. package/lib/quoxon/markdownSettle.js +19 -0
  250. package/lib/quoxon/matrixDomains.json +46 -0
  251. package/lib/quoxon/mentions.js +90 -0
  252. package/lib/quoxon/mind.js +103 -0
  253. package/lib/quoxon/mode.js +46 -0
  254. package/lib/quoxon/model.js +132 -26
  255. package/lib/quoxon/modelPrefs.js +345 -0
  256. package/lib/quoxon/motion/clock.js +3 -2
  257. package/lib/quoxon/motion/level.js +91 -0
  258. package/lib/quoxon/mouse.js +222 -0
  259. package/lib/quoxon/noai.js +57 -0
  260. package/lib/quoxon/objectiveOffer.js +82 -0
  261. package/lib/quoxon/objects/away.js +72 -0
  262. package/lib/quoxon/objects/fetch.js +17 -5
  263. package/lib/quoxon/objects/fleet.js +149 -0
  264. package/lib/quoxon/objects/health.js +186 -0
  265. package/lib/quoxon/objects/index.js +46 -7
  266. package/lib/quoxon/objects/mirror.js +137 -0
  267. package/lib/quoxon/objects/model.js +33 -0
  268. package/lib/quoxon/objects/peek.js +17 -0
  269. package/lib/quoxon/objects/proof.js +12 -4
  270. package/lib/quoxon/objects/prove.js +130 -0
  271. package/lib/quoxon/objects/replay.js +125 -0
  272. package/lib/quoxon/objects/sitrep.js +58 -0
  273. package/lib/quoxon/objects/view.js +11 -0
  274. package/lib/quoxon/onboarding/copy.js +32 -4
  275. package/lib/quoxon/onboarding/credential.js +17 -4
  276. package/lib/quoxon/onboarding/dockerOffer.js +45 -0
  277. package/lib/quoxon/onboarding/failureCause.js +33 -0
  278. package/lib/quoxon/onboarding/finish.js +15 -0
  279. package/lib/quoxon/onboarding/index.js +34 -7
  280. package/lib/quoxon/onboarding/installRunner.js +64 -5
  281. package/lib/quoxon/onboarding/preflight.js +17 -3
  282. package/lib/quoxon/onboarding/probe.js +65 -0
  283. package/lib/quoxon/onboarding/shortcutOffer.js +28 -0
  284. package/lib/quoxon/onboarding/view.js +76 -19
  285. package/lib/quoxon/orgLabel.js +10 -0
  286. package/lib/quoxon/orgSwitch.js +59 -0
  287. package/lib/quoxon/ownBroadcast.js +22 -0
  288. package/lib/quoxon/palette.js +43 -0
  289. package/lib/quoxon/planSteps.js +51 -0
  290. package/lib/quoxon/print.js +12 -5
  291. package/lib/quoxon/profileSwitch.js +53 -0
  292. package/lib/quoxon/promptHistory.js +41 -0
  293. package/lib/quoxon/providers.js +48 -0
  294. package/lib/quoxon/qart.js +20 -51
  295. package/lib/quoxon/qdragon.js +410 -0
  296. package/lib/quoxon/qdragonFly.js +62 -0
  297. package/lib/quoxon/qdragonScenes.js +282 -0
  298. package/lib/quoxon/qeyes.js +213 -0
  299. package/lib/quoxon/qmotion.js +75 -37
  300. package/lib/quoxon/render.js +740 -124
  301. package/lib/quoxon/resend.js +72 -0
  302. package/lib/quoxon/roster.js +117 -17
  303. package/lib/quoxon/route.js +6 -5
  304. package/lib/quoxon/rows.js +954 -0
  305. package/lib/quoxon/rowsStore.js +80 -0
  306. package/lib/quoxon/screenContext.js +150 -0
  307. package/lib/quoxon/screenFrames.js +254 -0
  308. package/lib/quoxon/serverChip.js +42 -0
  309. package/lib/quoxon/session.js +182 -2
  310. package/lib/quoxon/sessionDead.js +17 -0
  311. package/lib/quoxon/signin.js +125 -0
  312. package/lib/quoxon/slash/agents.js +112 -0
  313. package/lib/quoxon/slash/argSources.js +65 -0
  314. package/lib/quoxon/slash/args.js +65 -0
  315. package/lib/quoxon/slash/automate.js +89 -0
  316. package/lib/quoxon/slash/broadcast.js +140 -0
  317. package/lib/quoxon/slash/broadcastRun.js +108 -0
  318. package/lib/quoxon/slash/card.js +30 -0
  319. package/lib/quoxon/slash/challenge.js +114 -0
  320. package/lib/quoxon/slash/cliCommand.js +94 -0
  321. package/lib/quoxon/slash/cliLeaves.json +1023 -0
  322. package/lib/quoxon/slash/compact.js +1 -1
  323. package/lib/quoxon/slash/confirm.js +30 -0
  324. package/lib/quoxon/slash/crystallize.js +69 -0
  325. package/lib/quoxon/slash/dashboard.js +136 -0
  326. package/lib/quoxon/slash/draft.js +54 -0
  327. package/lib/quoxon/slash/evidenceCmd.js +34 -0
  328. package/lib/quoxon/slash/hatWalk.js +206 -0
  329. package/lib/quoxon/slash/inverses.js +77 -0
  330. package/lib/quoxon/slash/killer.js +24 -0
  331. package/lib/quoxon/slash/killerApi.js +16 -0
  332. package/lib/quoxon/slash/ledger.js +13 -8
  333. package/lib/quoxon/slash/listView.js +3 -1
  334. package/lib/quoxon/slash/modelCmd.js +261 -0
  335. package/lib/quoxon/slash/palette.js +22 -8
  336. package/lib/quoxon/slash/paletteController.js +117 -7
  337. package/lib/quoxon/slash/panels.js +21 -6
  338. package/lib/quoxon/slash/picker.js +20 -2
  339. package/lib/quoxon/slash/qpeCmd.js +35 -0
  340. package/lib/quoxon/slash/restart.js +95 -13
  341. package/lib/quoxon/slash/resume.js +18 -1
  342. package/lib/quoxon/slash/rowCmd.js +157 -0
  343. package/lib/quoxon/slash/rowWalk.js +175 -0
  344. package/lib/quoxon/slash/settings.js +16 -6
  345. package/lib/quoxon/slash/spawn.js +177 -0
  346. package/lib/quoxon/slash/stepUp.js +274 -0
  347. package/lib/quoxon/slash/stepUpCard.js +60 -0
  348. package/lib/quoxon/slash/stepUpCmds.js +78 -0
  349. package/lib/quoxon/slash/todoCmd.js +122 -0
  350. package/lib/quoxon/slash/undoApi.js +38 -0
  351. package/lib/quoxon/slash/undoCmd.js +49 -0
  352. package/lib/quoxon/slash/undoWish.js +15 -0
  353. package/lib/quoxon/slash/watch.js +164 -0
  354. package/lib/quoxon/slash/wish.js +23 -0
  355. package/lib/quoxon/slash/wishCmd.js +32 -0
  356. package/lib/quoxon/slotChat.js +300 -0
  357. package/lib/quoxon/slotFormat.js +195 -0
  358. package/lib/quoxon/slotRun.js +131 -0
  359. package/lib/quoxon/sound/mapping.js +2 -1
  360. package/lib/quoxon/stray.js +22 -0
  361. package/lib/quoxon/suggestCommand.js +90 -0
  362. package/lib/quoxon/term.js +46 -11
  363. package/lib/quoxon/textBuffer.js +176 -2
  364. package/lib/quoxon/theme.js +58 -25
  365. package/lib/quoxon/tips.js +433 -0
  366. package/lib/quoxon/toolCards.js +30 -0
  367. package/lib/quoxon/toolError.js +41 -0
  368. package/lib/quoxon/transcript.js +42 -4
  369. package/lib/quoxon/web/qface.js +18 -10
  370. package/lib/quoxon/welcome.js +1 -1
  371. package/lib/quoxon/whatsnew.js +136 -0
  372. package/lib/quoxon/whatsnew.jsonl +134 -0
  373. package/lib/quoxon/windowOrg.js +123 -0
  374. package/lib/server/devServers.js +137 -0
  375. package/lib/server/diag.js +129 -0
  376. package/lib/server/doctor.js +71 -0
  377. package/lib/server/install.js +149 -0
  378. package/lib/server/pluginEnable.js +161 -0
  379. package/lib/server/skew.js +56 -0
  380. package/lib/server/snapshot.js +131 -0
  381. package/lib/server/status.js +128 -0
  382. package/lib/server/sys.js +65 -0
  383. package/lib/server/uninstall.js +63 -0
  384. package/lib/server/update.js +234 -0
  385. package/lib/toolOutputShape.js +315 -0
  386. package/lib/tui/views/HyperionDeck.jsx +79 -0
  387. package/lib/tui/views/LucidDeck.jsx +209 -217
  388. package/package.json +5 -1
  389. package/vendor/dragonlab/SOURCE +4 -0
  390. package/vendor/dragonlab/design/cipher/v1/cipher-master.svg +46 -0
  391. package/vendor/dragonlab/design/cipher/v2/character.mjs +49 -0
  392. package/vendor/dragonlab/design/cipher/v2/review.mjs +33 -0
  393. package/vendor/dragonlab/design/cipher/v2/watch.mjs +56 -0
  394. package/vendor/dragonlab/design/drago/v1/character.mjs +19 -0
  395. package/vendor/dragonlab/design/drago/v1/drago-master.svg +56 -0
  396. package/vendor/dragonlab/design/drago/v2/character.mjs +87 -0
  397. package/vendor/dragonlab/design/drago/v4/character.mjs +67 -0
  398. package/vendor/dragonlab/design/drago/v4/profile.mjs +35 -0
  399. package/vendor/dragonlab/design/drago/v4/review.mjs +33 -0
  400. package/vendor/dragonlab/design/drago/v4/watch.mjs +56 -0
  401. package/vendor/dragonlab/design/hyperion/v1/hyperion-master.svg +56 -0
  402. package/vendor/dragonlab/design/hyperion/v2/character.mjs +79 -0
  403. package/vendor/dragonlab/design/hyperion/v2/review.mjs +33 -0
  404. package/vendor/dragonlab/design/hyperion/v2/watch.mjs +56 -0
  405. package/vendor/dragonlab/design/lucid/recovery/arrival.mjs +22 -0
  406. package/vendor/dragonlab/design/lucid/recovery/character.mjs +106 -0
  407. package/vendor/dragonlab/design/lucid/recovery/layout.mjs +50 -0
  408. package/vendor/dragonlab/design/lucid/recovery/lucid-master.svg +78 -0
  409. package/vendor/dragonlab/design/lucid/recovery/motion.mjs +42 -0
  410. package/vendor/dragonlab/design/lucid/recovery/render.mjs +121 -0
  411. package/vendor/dragonlab/design/lucid/recovery/review.mjs +68 -0
  412. package/vendor/dragonlab/design/lucid/recovery/verify.mjs +84 -0
  413. package/vendor/dragonlab/design/lucid/recovery/watch.mjs +56 -0
  414. package/vendor/dragonlab/design/seer/v1/seer-master.svg +53 -0
  415. package/vendor/dragonlab/design/seer/v2/character.mjs +88 -0
  416. package/vendor/dragonlab/design/seer/v3/character.mjs +33 -0
  417. package/vendor/dragonlab/design/seer/v4/character.mjs +66 -0
  418. package/vendor/dragonlab/design/seer/v4/review.mjs +33 -0
  419. package/vendor/dragonlab/design/seer/v4/watch.mjs +56 -0
  420. package/vendor/dragonlab/design/stage3/companion-review.mjs +28 -0
  421. package/vendor/dragonlab/design/stage3/sentinel-art.js +39 -0
  422. package/vendor/dragonlab/design/stage3/sentinel-braille.js +216 -0
  423. package/vendor/dragonlab/design/stage3/sentinel-character.js +257 -0
  424. package/vendor/dragonlab/design/stage3/sentinel-icon.js +103 -0
  425. package/vendor/dragonlab/design/stage3/sentinel-warden-eyes.js +95 -0
  426. package/vendor/dragonlab/design/stage3/svg-coverage.js +148 -0
  427. package/vendor/dragonlab/factory/svg-rig.mjs +63 -0
  428. package/vendor/dragonlab/integration/companions-v1/characters.mjs +88 -0
  429. package/vendor/dragonlab/integration/companions-v1/cipher.mjs +2 -0
  430. package/vendor/dragonlab/integration/companions-v1/companion.mjs +63 -0
  431. package/vendor/dragonlab/integration/companions-v1/drago.mjs +2 -0
  432. package/vendor/dragonlab/integration/companions-v1/hyperion.mjs +2 -0
  433. package/vendor/dragonlab/integration/companions-v1/lucid.mjs +2 -0
  434. package/vendor/dragonlab/integration/companions-v1/seer.mjs +2 -0
  435. package/vendor/dragonlab/integration/companions-v1/sentinel.mjs +2 -0
  436. package/vendor/dragonlab/integration/companions-v1/view.mjs +28 -0
  437. package/vendor/dragonlab/integration/hyperion-v3/activity.mjs +76 -0
  438. package/vendor/dragonlab/lib/power-effects.js +294 -0
  439. package/vendor/dragonlab/lib/power-registry.js +74 -0
  440. package/vendor/dragonlab/vendor/quox-dragon/qart.js +348 -0
  441. package/vendor/dragonlab/vendor/quox-dragon/qeyes.js +213 -0
  442. package/lib/core/websiteSession.js +0 -148
@@ -0,0 +1,174 @@
1
+ /**
2
+ * quox kuma sla ... : signed, verifiable uptime reports (NQ-KUMA-7).
3
+ *
4
+ * quox kuma sla [--month YYYY-MM | --from <iso> --to <iso>] [--out <file>] [--json] generate + sign + store
5
+ * quox kuma sla list this org's stored reports
6
+ * quox kuma sla verify <id|file> [--public-key <base64>] recompute the hash, check the signature
7
+ *
8
+ * `verify <id>` asks the collector, which checks against its pinned WARD key (trust anchored).
9
+ * `verify <file>` is fully OFFLINE: it recomputes sha256 over the canonical report and checks the Ed25519
10
+ * signature with node:crypto. Against the key embedded in the file this proves integrity only (anyone can
11
+ * mint a keypair); pass --public-key <raw 32 byte base64 WARD key> to pin the signer yourself.
12
+ *
13
+ * Exit codes: 0 ok / verified, 1 error, 2 verification FAILED.
14
+ */
15
+
16
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
17
+ import { createHash, createPublicKey, verify as cryptoVerify } from 'node:crypto';
18
+ import { resolveEndpoint } from '../api/endpoints.js';
19
+ import { line as glyphLine } from '../core/quoxglyph.js';
20
+
21
+ // Same canonical form as services/collector/lib/bundleSigner.js canonicalize (sorted keys, no whitespace).
22
+ export function canonicalize(val) {
23
+ if (val === null || val === undefined) return JSON.stringify(val);
24
+ if (typeof val !== 'object') return JSON.stringify(val);
25
+ if (Array.isArray(val)) return `[${val.map((v) => canonicalize(v)).join(',')}]`;
26
+ const keys = Object.keys(val).filter((k) => val[k] !== undefined).sort();
27
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalize(val[k])}`).join(',')}}`;
28
+ }
29
+
30
+ const SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
31
+ const keyFromRaw = (b64) => {
32
+ try {
33
+ const raw = Buffer.from(String(b64 || ''), 'base64');
34
+ if (raw.length !== 32) return null;
35
+ return createPublicKey({ key: Buffer.concat([SPKI_PREFIX, raw]), format: 'der', type: 'spki' });
36
+ } catch { return null; }
37
+ };
38
+
39
+ /** Offline verification of an export bundle. Mirrors kumaSlaReports.verifyBundle. */
40
+ export function verifyBundleOffline(bundle, { publicKey } = {}) {
41
+ const out = { valid: false, hash_ok: false, binding_ok: false, signature_ok: false, key_source: 'none', trust_anchored: false };
42
+ if (!bundle || typeof bundle !== 'object' || !bundle.report || typeof bundle.report !== 'object') return { ...out, reason: 'malformed_bundle' };
43
+ const sig = bundle.signature;
44
+ if (!sig || !sig.signature || !sig.message) return { ...out, reason: 'missing_signature' };
45
+ const recomputed = createHash('sha256').update(canonicalize(bundle.report), 'utf-8').digest('hex');
46
+ out.recomputed_sha256 = recomputed;
47
+ out.hash_ok = recomputed === bundle.report_sha256;
48
+ if (!out.hash_ok) return { ...out, reason: 'report_hash_mismatch' };
49
+ const r = bundle.report;
50
+ const expected = r.period ? {
51
+ kind: 'quox.kuma.sla.report', report_id: bundle.report_id, org_id: r.org_id,
52
+ period_from: r.period.from, period_to: r.period.to, generated_at: r.generated_at, report_sha256: recomputed,
53
+ } : null;
54
+ out.binding_ok = !!expected && canonicalize(expected) === canonicalize(sig.message);
55
+ if (!out.binding_ok) return { ...out, reason: 'signature_not_bound_to_report' };
56
+ if (sig.sig_type !== 'ed25519') return { ...out, reason: `unverifiable_sig_type:${sig.sig_type}` };
57
+ let key = null; let source = 'none';
58
+ if (publicKey) { key = keyFromRaw(publicKey); source = 'pinned'; if (!key) return { ...out, reason: 'bad_public_key' }; }
59
+ else { key = keyFromRaw(sig.public_key); source = 'embedded'; }
60
+ if (!key) return { ...out, reason: 'no_verification_key' };
61
+ out.key_source = source;
62
+ try {
63
+ out.signature_ok = cryptoVerify(null, Buffer.from(canonicalize(sig.message), 'utf-8'), key, Buffer.from(String(sig.signature), 'base64'));
64
+ } catch (err) { return { ...out, reason: `verify_error:${err.message}` }; }
65
+ if (!out.signature_ok) return { ...out, reason: 'signature_mismatch' };
66
+ out.valid = true;
67
+ out.trust_anchored = source === 'pinned' && !sig.warning;
68
+ if (sig.warning) out.warning = sig.warning;
69
+ return out;
70
+ }
71
+
72
+ const dur = (s) => {
73
+ if (s === null || s === undefined) return '-';
74
+ if (s < 60) return `${s}s`;
75
+ if (s < 3600) return `${Math.floor(s / 60)}m ${s % 60}s`;
76
+ return `${Math.floor(s / 3600)}h ${Math.floor((s % 3600) / 60)}m`;
77
+ };
78
+
79
+ export function registerKumaSla(kuma, { client, auth, jsonOut, guard, KumaFail, pad }) {
80
+ const slaFail = (verb, ep, err) => {
81
+ const status = err && (err.status ?? err.statusCode);
82
+ const msg = (err && err.message) || String(err);
83
+ const head = `quox kuma ${verb}: ${ep.method} ${ep.path} failed${status ? ` with HTTP ${status}` : ''}`;
84
+ if (status === 422) return new KumaFail(`${head}: Uptime Kuma is not configured for this org (add the uptime_kuma credential in the vault).`);
85
+ if (status === 404) return new KumaFail(`${head}: not found in this org.`);
86
+ return new KumaFail(`${head}: ${msg}`);
87
+ };
88
+
89
+ const sla = kuma
90
+ .command('sla')
91
+ .description('Signed, verifiable uptime (SLA) report from Kuma heartbeats; `sla verify <id|file>` checks one')
92
+ .option('--month <YYYY-MM>', 'calendar month (UTC), e.g. 2026-09')
93
+ .option('--from <iso>', 'range start (with --to)')
94
+ .option('--to <iso>', 'range end, exclusive')
95
+ .option('--out <file>', 'also write the signed bundle here for offline verification')
96
+ .addHelpText('after', '\nExit codes: 0 ok, 1 error, 2 `sla verify` found the report does NOT verify.')
97
+ .action(guard('sla', async (options) => {
98
+ auth.requireAuth();
99
+ if (!options.month && !(options.from && options.to)) throw new KumaFail('quox kuma sla: give --month YYYY-MM, or both --from and --to.');
100
+ const ep = resolveEndpoint('mon.kuma.sla.create');
101
+ const body = options.month ? { month: options.month } : { from: options.from, to: options.to };
102
+ let data;
103
+ try { data = await client.post(ep.path, { service: ep.service, body }); } catch (err) { throw slaFail('sla', ep, err); }
104
+ if (!data || data.success === false || data.error || !data.report || !data.signature) {
105
+ throw new KumaFail(`quox kuma sla: ${ep.method} ${ep.path} returned an error body: ${(data && data.error) || 'response had no report'}`);
106
+ }
107
+ if (options.out) {
108
+ writeFileSync(options.out, `${JSON.stringify({ report_id: data.report_id, report: data.report, report_sha256: data.report_sha256, signature: data.signature }, null, 2)}\n`);
109
+ }
110
+ if (jsonOut()) { console.log(JSON.stringify(data, null, 2)); return; }
111
+ const r = data.report; const ro = r.rollup;
112
+ console.log(glyphLine('kuma', 'sla', { result: `${r.period.label} · ${ro.uptime_pct === null ? 'no data' : `${ro.uptime_pct}%`}`, status: ro.uptime_pct === null ? 'warn' : 'ok' }));
113
+ console.log(`Report ${data.report_id} sha256 ${data.report_sha256}`);
114
+ console.log(`Period ${r.period.from} to ${r.period.effective_to}${r.period.partial ? ' (partial: the period has not ended)' : ''}`);
115
+ const rows = [['MONITOR', 'UPTIME', 'OUTAGES', 'DOWNTIME', 'LONGEST', 'MTTR', 'MAINT', 'GAPS']];
116
+ for (const m of r.monitors) {
117
+ rows.push([m.name || m.monitor_id, m.status === 'no_data' ? 'no data' : `${m.uptime_pct}%`, m.outages.count, dur(m.outages.total_downtime_seconds), dur(m.outages.longest_seconds), dur(m.outages.mttr_seconds), m.beats.maintenance, m.coverage.gaps.length]);
118
+ }
119
+ console.log(`\n${pad(rows).join('\n')}`);
120
+ console.log(`\nOrg: ${ro.uptime_pct === null ? 'no data' : `${ro.uptime_pct}%`} over ${ro.monitors_with_data}/${ro.monitors_total} monitors with data; ${ro.outages.count} ${ro.outages.count === 1 ? 'outage' : 'outages'}, ${dur(ro.outages.total_downtime_seconds)} down, MTTR ${dur(ro.outages.mttr_seconds)}.`);
121
+ if (ro.monitors_no_data) console.log(`${ro.monitors_no_data} ${ro.monitors_no_data === 1 ? 'monitor has' : 'monitors have'} no heartbeats in the period: reported as no data, not as 100%.`);
122
+ console.log(data.trust_anchored ? 'Signed with the WARD key and witnessed.' : 'WARNING: signed with an ephemeral key (no persistent WARD key on this collector): integrity only, not trust anchored.');
123
+ console.log(`Verify: quox kuma sla verify ${options.out || data.report_id}`);
124
+ }));
125
+
126
+ sla
127
+ .command('list')
128
+ .description('This org\'s stored SLA reports')
129
+ .action(guard('sla list', async () => {
130
+ auth.requireAuth();
131
+ const ep = resolveEndpoint('mon.kuma.sla.list');
132
+ let data;
133
+ try { data = await client.get(ep.path, { service: ep.service }); } catch (err) { throw slaFail('sla list', ep, err); }
134
+ if (!data || data.success === false || data.error || !Array.isArray(data.reports)) {
135
+ throw new KumaFail(`quox kuma sla list: ${ep.method} ${ep.path} returned an error body: ${(data && data.error) || 'response had no reports array'}`);
136
+ }
137
+ if (jsonOut()) { console.log(JSON.stringify(data, null, 2)); return; }
138
+ console.log(glyphLine('kuma', 'sla list', { result: `${data.reports.length} ${data.reports.length === 1 ? 'report' : 'reports'}`, status: 'ok' }));
139
+ if (!data.reports.length) { console.log('No reports yet. Generate one: quox kuma sla --month YYYY-MM'); return; }
140
+ const rows = [['REPORT', 'PERIOD', 'CREATED', 'SHA256']];
141
+ for (const r of data.reports) rows.push([r.report_id, r.period, r.created_at, String(r.report_sha256).slice(0, 16)]);
142
+ console.log(pad(rows).join('\n'));
143
+ }));
144
+
145
+ sla
146
+ .command('verify')
147
+ .description('Recompute the report hash and check the signature (id: via the collector, pinned WARD key; file: fully offline)')
148
+ .argument('<id|file>', 'a report id from `quox kuma sla list`, or a bundle file written by `sla --out`')
149
+ .option('--public-key <base64>', 'offline: pin this raw 32 byte Ed25519 WARD public key instead of trusting the key in the file')
150
+ .action(guard('sla verify', async (target, options) => {
151
+ let result;
152
+ if (existsSync(target)) {
153
+ let bundle;
154
+ try { bundle = JSON.parse(readFileSync(target, 'utf-8')); } catch (err) { throw new KumaFail(`quox kuma sla verify: ${target} is not valid JSON: ${err.message}`); }
155
+ result = { report_id: bundle && bundle.report_id, mode: 'offline', ...verifyBundleOffline(bundle, { publicKey: options.publicKey }) };
156
+ } else {
157
+ auth.requireAuth();
158
+ const ep = resolveEndpoint('mon.kuma.sla.verify', { id: target });
159
+ let data;
160
+ try { data = await client.get(ep.path, { service: ep.service }); } catch (err) { throw slaFail('sla verify', ep, err); }
161
+ if (!data || data.success === false || data.error || typeof data.valid !== 'boolean') {
162
+ throw new KumaFail(`quox kuma sla verify: ${ep.method} ${ep.path} returned an error body: ${(data && data.error) || 'response had no verdict'}`);
163
+ }
164
+ result = { mode: 'collector', ...data };
165
+ }
166
+ if (jsonOut()) { console.log(JSON.stringify(result, null, 2)); } else {
167
+ console.log(glyphLine('kuma', 'sla verify', { result: result.valid ? `verified (${result.mode})` : `NOT verified: ${result.reason}`, status: result.valid ? 'ok' : 'error' }));
168
+ console.log(`hash ${result.hash_ok ? 'matches' : 'DOES NOT match'} binding ${result.binding_ok ? 'ok' : 'FAILED'} signature ${result.signature_ok ? 'valid' : 'INVALID'} key ${result.key_source}${result.trust_anchored ? ' (trust anchored)' : ' (not trust anchored)'}`);
169
+ if (result.valid && !result.trust_anchored) console.log('Integrity holds, but the signer was not checked against the WARD key. Pin it with --public-key, or verify by id through the collector.');
170
+ if (result.warning) console.log(`Warning: ${result.warning}`);
171
+ }
172
+ if (!result.valid) process.exitCode = 2;
173
+ }));
174
+ }
@@ -5,53 +5,26 @@
5
5
  * Phase 1 (instance-local): status reads the lease (never the key), refresh
6
6
  * runs the renewal, seats lists/cleans band holders (dry run default).
7
7
  *
8
- * Phase 2 (owner-only, talks to the quox.ai WEBSITE admin API with a separate
9
- * website session, see core/websiteSession.js): reissue re-bands/extends a
10
- * licence record, catalogue-sign re-signs the product catalogue. Both are DRY
11
- * RUN unless --apply. The signing key never leaves the website VM; the CLI
12
- * never sees, prints or sends key material or licence keys.
8
+ * Phase 2 (owner-only vendor tooling for the quox.ai website) lives in
9
+ * lib/vendor/licenceOwner.js and is registered only when that file is present.
10
+ * lib/vendor/ is excluded from the npm package (quox#1134): the public build
11
+ * never carries it.
13
12
  */
14
13
 
15
- import { writeFileSync } from 'node:fs';
16
14
  import { formatOutput, formatDetail, printSuccess, printInfo, printWarning } from '../core/output.js';
17
15
  import { resolveEndpoint } from '../api/endpoints.js';
18
16
  import { QuoxValidationError } from '../core/errors.js';
19
- import {
20
- DEFAULT_WEBSITE_URL, DEFAULT_WEBSITE_PROFILE, normaliseWebsiteUrl, websiteLogin, websitePost,
21
- saveWebsiteSession, clearWebsiteSession, getWebsiteSession,
22
- } from '../core/websiteSession.js';
23
17
 
24
- const OWNER_HELP = `
25
- Owner-only (website admin session, dry run unless --apply):
26
- website-login log in to the quox.ai admin API once (password is not stored)
27
- reissue <licenseId> band bare paid ids / add ids, re-signed on the website VM
28
- catalogue-sign re-sign the product catalogue with the live key
29
- See the quox-licensing skill (~/.claude/skills/quox-licensing/SKILL.md).
30
- `;
31
-
32
- const ID_LIST_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/;
33
-
34
- export function parseIdList(value, flag) {
35
- if (value === undefined) return undefined;
36
- const ids = String(value).split(',').map((s) => s.trim()).filter(Boolean);
37
- for (const id of ids) {
38
- if (!ID_LIST_RE.test(id)) throw new QuoxValidationError(`${flag}: invalid plugin id "${id}"`);
39
- }
40
- return ids;
18
+ // quox#1134: the owner-only website verbs are vendor tooling, kept out of the published package (package.json
19
+ // "files" excludes lib/vendor/). Present in the dev tree, absent for every npm user.
20
+ let VENDOR_OWNER = null;
21
+ try {
22
+ VENDOR_OWNER = await import('../vendor/licenceOwner.js');
23
+ } catch (e) {
24
+ if (!(e?.code === 'ERR_MODULE_NOT_FOUND' && String(e?.message || '').includes('licenceOwner'))) throw e;
41
25
  }
42
26
 
43
- export function parseOrgs(value) {
44
- if (value === undefined) return undefined;
45
- const n = Number(value);
46
- if (!Number.isInteger(n) || n < 1 || n > 1000) throw new QuoxValidationError('--orgs must be an integer between 1 and 1000');
47
- return n;
48
- }
49
-
50
- function list(arr) {
51
- return arr && arr.length ? arr.join(', ') : '(none)';
52
- }
53
-
54
- export function registerLicenceCommands(program, ctx) {
27
+ export function registerLicenceCommands(program, ctx, { owner = VENDOR_OWNER } = {}) {
55
28
  const { client, auth } = ctx;
56
29
  const fetchImpl = ctx.websiteFetch || globalThis.fetch;
57
30
  const json = () => program.opts().output === 'json';
@@ -60,8 +33,8 @@ export function registerLicenceCommands(program, ctx) {
60
33
  .command('licence')
61
34
  .alias('license')
62
35
  .description('Instance licence status, lease refresh and seat bands')
63
- .addHelpText('after', OWNER_HELP)
64
36
  .action(() => { licence.help(); });
37
+ if (owner) licence.addHelpText('after', owner.OWNER_HELP);
65
38
 
66
39
  licence
67
40
  .command('status')
@@ -136,9 +109,21 @@ export function registerLicenceCommands(program, ctx) {
136
109
  .description('List a plugin band holders; --release-dead frees non-live holders (dry run unless --yes)')
137
110
  .argument('<pluginId>', 'plugin id, e.g. notesq')
138
111
  .option('--release-dead', 'release holders that are not live orgs (deleted orgs, user: ids)')
112
+ .option('--release <orgId>', 'release a LIVE org seat (its install of the plugin is disabled); dry run unless --yes')
113
+ .option('--to <orgId>', 'with --release: hand the freed seat to this org in the same call')
139
114
  .option('--yes', 'actually release (otherwise dry run)')
140
115
  .action(async (pluginId, opts) => {
141
116
  auth.requireAuth();
117
+ if (opts.to && !opts.release) {
118
+ const e = new Error('--to only works together with --release <orgId>');
119
+ e.exitCode = 2;
120
+ throw e;
121
+ }
122
+ if (opts.release && opts.releaseDead) {
123
+ const e = new Error('use either --release or --release-dead, not both');
124
+ e.exitCode = 2;
125
+ throw e;
126
+ }
142
127
  const lep = resolveEndpoint('setup.licensing.seats.list', { pluginId });
143
128
  const list = await client.get(lep.path, { service: lep.service });
144
129
  let rel = null;
@@ -146,7 +131,15 @@ export function registerLicenceCommands(program, ctx) {
146
131
  const rep = resolveEndpoint('setup.licensing.seats.releaseDead', { pluginId });
147
132
  rel = await client.post(rep.path, { service: rep.service, body: { dry_run: !opts.yes } });
148
133
  }
149
- if (json()) { console.log(JSON.stringify({ ...list, release: rel }, null, 2)); return; }
134
+ let live = null;
135
+ if (opts.release) {
136
+ const rp = resolveEndpoint('setup.licensing.seats.release', { pluginId });
137
+ live = await client.post(rp.path, {
138
+ service: rp.service,
139
+ body: { org_id: opts.release, ...(opts.to ? { to_org_id: opts.to } : {}), dry_run: !opts.yes },
140
+ });
141
+ }
142
+ if (json()) { console.log(JSON.stringify({ ...list, release: rel, live_release: live }, null, 2)); return; }
150
143
  printInfo(`${pluginId}: ${list.holders.length} holder(s), cap ${list.allowance ?? '(no band)'}`);
151
144
  formatOutput({
152
145
  columns: ['id', 'name', 'status'],
@@ -160,121 +153,25 @@ export function registerLicenceCommands(program, ctx) {
160
153
  printSuccess(`Released ${rel.released.length} non-live holder(s): ${rel.released.join(', ') || '(none)'}`);
161
154
  }
162
155
  }
163
- });
164
-
165
- // ---------------- Phase 2: owner-only, website admin API ----------------
166
-
167
- licence
168
- .command('website-login')
169
- .description('Log in to the quox.ai website admin API for reissue/catalogue-sign (stores the session cookie only, 0600)')
170
- .option('--website-profile <name>', 'website session profile', DEFAULT_WEBSITE_PROFILE)
171
- .option('--url <url>', 'website base URL', DEFAULT_WEBSITE_URL)
172
- .option('--username <name>', 'website admin username')
173
- .option('--password-stdin', 'read the password from stdin (one line) instead of prompting')
174
- .action(async (opts) => {
175
- normaliseWebsiteUrl(opts.url); // refuse a bad or plain-http URL before asking for a password
176
- let { username } = opts;
177
- let password;
178
- if (opts.passwordStdin) {
179
- const chunks = [];
180
- for await (const c of process.stdin) chunks.push(Buffer.from(c));
181
- password = Buffer.concat(chunks).toString('utf8').split('\n')[0].trim();
182
- }
183
- if (!username || !password) {
184
- const { default: inquirer } = await import('inquirer');
185
- if (!username) username = (await inquirer.prompt([{ type: 'input', name: 'u', message: 'Website admin username:' }])).u;
186
- if (!password) password = (await inquirer.prompt([{ type: 'password', name: 'p', message: 'Password:', mask: '*' }])).p;
187
- }
188
- if (!username || !password) throw new QuoxValidationError('username and password are required');
189
- const session = await websiteLogin({ url: opts.url, username, password, fetchImpl });
190
- password = null;
191
- saveWebsiteSession(opts.websiteProfile, session);
192
- if (json()) { console.log(JSON.stringify({ profile: opts.websiteProfile, url: session.url, username: session.username, must_change_password: session.must_change_password }, null, 2)); return; }
193
- printSuccess(`Website session saved for ${session.username} at ${session.url} (profile ${opts.websiteProfile}, valid ~24h). Password not stored.`);
194
- if (session.must_change_password) printWarning('This admin must change their password on the website before licence operations will be allowed (HTTP 403 until then).');
195
- });
196
-
197
- licence
198
- .command('website-logout')
199
- .description('End the website admin session and delete the stored cookie')
200
- .option('--website-profile <name>', 'website session profile', DEFAULT_WEBSITE_PROFILE)
201
- .action(async (opts) => {
202
- if (getWebsiteSession(opts.websiteProfile)) {
203
- try { await websitePost(opts.websiteProfile, '/api/admin/logout', {}, { fetchImpl }); } catch { /* session may already be dead */ }
156
+ if (live) {
157
+ const who = `${live.org_name || live.org_id} (${live.org_id})`;
158
+ const loses = live.will_disable_install
159
+ ? `its install of ${pluginId} goes disabled and the plugin stops working there`
160
+ : `it loses access to ${pluginId} (no enabled install to disable)`;
161
+ if (live.dry_run) {
162
+ printInfo(`DRY RUN: would release the ${pluginId} seat held by ${who}: ${loses}.`);
163
+ if (live.would_assign_to) printInfo(`DRY RUN: the freed seat would go to ${live.would_assign_to}.`);
164
+ else printInfo(`Seats are first-come: add --to <orgId> to hand the freed seat to a specific org, otherwise the first org to use ${pluginId} takes it.`);
165
+ printInfo(`Re-run with --yes to release.`);
166
+ } else if (live.released) {
167
+ printSuccess(`Released the ${pluginId} seat held by ${who}: ${loses}.`);
168
+ if (live.assigned_to) printSuccess(`Seat now held by ${live.assigned_to}.`);
169
+ else if (opts.to) printWarning(`The seat was freed but ${opts.to} could not take it (see the band with: quox licence seats ${pluginId}).`);
170
+ } else {
171
+ printWarning(`Nothing released: ${who} no longer holds a ${pluginId} seat.`);
172
+ }
204
173
  }
205
- const had = clearWebsiteSession(opts.websiteProfile);
206
- printSuccess(had ? `Website session ${opts.websiteProfile} removed.` : `No website session ${opts.websiteProfile} was stored.`);
207
174
  });
208
175
 
209
- licence
210
- .command('reissue')
211
- .description('Owner-only: band bare paid ids and add ids on a licence record, re-signed on the website (dry run unless --apply)')
212
- .argument('<licenseId>', 'licence id, e.g. lic_abc123')
213
- .option('--orgs <n>', 'org band for every banded/added id (default 10)')
214
- .option('--add <ids>', 'comma-separated ids to ADD as <id>@orgs=N')
215
- .option('--band <ids>', 'comma-separated ids already present to FORCE-band')
216
- .option('--apply', 'actually reissue (otherwise dry run, nothing changes)')
217
- .option('--website-profile <name>', 'website session profile', DEFAULT_WEBSITE_PROFILE)
218
- .action(async (licenseId, opts) => {
219
- const body = { dryRun: !opts.apply };
220
- const orgs = parseOrgs(opts.orgs);
221
- if (orgs !== undefined) body.orgs = orgs;
222
- const add = parseIdList(opts.add, '--add');
223
- if (add) body.add = add;
224
- const band = parseIdList(opts.band, '--band');
225
- if (band) body.band = band;
226
-
227
- const d = await websitePost(opts.websiteProfile, `/api/admin/licenses/${encodeURIComponent(licenseId)}/reissue`, body, { fetchImpl });
228
- if (json()) { console.log(JSON.stringify(d, null, 2)); return; }
229
-
230
- printInfo(`Licence ${d.license_id}${d.customer_email ? ` (${d.customer_email})` : ''}: max_servers ${d.max_servers ?? '?'}, expires ${d.expires_at || 'never'}`);
231
- const removed = new Set(d.diff?.removed || []);
232
- const added = new Set(d.diff?.added || []);
233
- for (const e of d.current || []) console.log(` ${removed.has(e) ? '-' : ' '} ${e}`);
234
- for (const e of d.next || []) if (added.has(e)) console.log(` + ${e}`);
235
- if (d.unknown_ids?.length) printWarning(`Not issuable (unknown to the signed catalogue), apply will be refused: ${d.unknown_ids.join(', ')}`);
236
-
237
- if (!d.changed) { printSuccess('No change needed: the licence already has these entitlements.'); return; }
238
- if (d.dry_run) { printInfo('DRY RUN: nothing changed. Re-run with --apply to reissue.'); return; }
239
- printSuccess(`Reissued ${d.license_id}. Audit/backup id: ${d.backup_id}`);
240
- printInfo('Instances pick this up on their next lease renewal (~6h). To pull it now, on the instance: quox licence refresh');
241
- });
242
-
243
- licence
244
- .command('catalogue-sign')
245
- .alias('catalog-sign')
246
- .description('Owner-only: re-sign the product catalogue with the live key on the website (dry run unless --apply)')
247
- .option('--apply', 'actually sign and publish to the website API (otherwise dry run)')
248
- .option('--out <path>', 'after --apply, save the signed catalogue (public artifact) here for committing')
249
- .option('--website-profile <name>', 'website session profile', DEFAULT_WEBSITE_PROFILE)
250
- .action(async (opts) => {
251
- if (opts.out && !opts.apply) throw new QuoxValidationError('--out is only meaningful with --apply');
252
- const d = await websitePost(opts.websiteProfile, '/api/admin/catalog/sign', { dryRun: !opts.apply }, { fetchImpl });
253
- if (opts.out && d.artifact) writeFileSync(opts.out, JSON.stringify(d.artifact, null, 2) + '\n', 'utf8');
254
- if (json()) {
255
- const { artifact, ...rest } = d;
256
- console.log(JSON.stringify({ ...rest, artifact_saved_to: opts.out && artifact ? opts.out : null }, null, 2));
257
- return;
258
- }
259
- const p = d.published || {};
260
- formatDetail({
261
- published: p.exists ? `${p.verified ? 'verified' : 'NOT VERIFIED'}, sha256 ${p.sha256 ? p.sha256.slice(0, 16) : '?'}..., ${p.generated_at || '?'}` : '(none)',
262
- products: `${p.product_count ?? 0} -> ${d.candidate?.product_count ?? '?'}`,
263
- governed: `${p.governed_count ?? 0} -> ${d.candidate?.governed_count ?? '?'}`,
264
- products_added: list(d.diff?.products_added),
265
- products_removed: list(d.diff?.products_removed),
266
- products_changed: list(d.diff?.products_changed),
267
- governed_added: list(d.diff?.governed_added),
268
- governed_removed: list(d.diff?.governed_removed),
269
- signing_key: d.signing ? (d.signing.trusted ? `trusted (${d.signing.trusted_key_prefix}...)` : (d.signing.configured ? 'NOT the trusted key: apply will be refused' : 'not configured: apply will be refused')) : '?',
270
- }, { title: d.dry_run ? 'Catalogue sign (DRY RUN)' : 'Catalogue sign' });
271
- if (d.diff?.governed_added?.length) {
272
- printWarning(`Newly governed: ${d.diff.governed_added.join(', ')}. Reissue the owner licence with these ids (quox licence reissue <id> --add ...) and refresh its lease BEFORE instances install this catalogue, or they lock out.`);
273
- }
274
- if (!d.changed) { printSuccess('No change: the published catalogue already matches.'); return; }
275
- if (d.dry_run) { printInfo('DRY RUN: nothing signed. Re-run with --apply to sign.'); return; }
276
- printSuccess(`Catalogue signed. sha256 ${d.sha256}, governed ${d.governed_count}, audit id ${d.audit_id}`);
277
- if (opts.out) printInfo(`Signed catalogue saved to ${opts.out}.`);
278
- printInfo('The website container copy does not survive a rebuild: commit the artifact to quox-website api/data/signed-catalog.json and quox-dashboard services/auth/data/signed-catalog.json (use --out to save it).');
279
- });
176
+ if (owner) owner.registerLicenceOwnerCommands(licence, { json, fetchImpl });
280
177
  }