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,405 @@
1
+ /**
2
+ * quox server: lifecycle of a local self-hosted install (plan C4).
3
+ *
4
+ * status version, health per service, disk, ports, last update (read-only)
5
+ * update current -> latest public quoxcore tag; backup first; fetch, build, migrate, health;
6
+ * auto-rollback on failure; resumable; --dry-run prints the plan and runs nothing
7
+ * rollback previous snapshot + previous version; --dry-run
8
+ * uninstall lists exactly what goes, two-step confirmation, keeps backups unless --purge; --dry-run
9
+ * doctor the quox doctor checks + server-side checks (read-only)
10
+ * diag a redacted support bundle written locally (allowlist redaction), never uploaded
11
+ *
12
+ * Every mutating host command goes through lib/server/sys.js gate(): in a dry run it is printed, never run.
13
+ * The install is the one Getting started recorded (or --dir / QUOX_INSTALL_DIR / the running containers).
14
+ */
15
+ import chalk from 'chalk';
16
+ import { homedir } from 'node:os';
17
+ import { resolve } from 'node:path';
18
+ import { resolveEndpoint } from '../api/endpoints.js';
19
+ import { createSys } from '../server/sys.js';
20
+ import { resolveInstall, backupsRoot, readInstalled, fetchAvailable, cmpSemver, vtag, compareUrl, releaseNotesUrl } from '../server/install.js';
21
+ import { gatherStatus, statusRows, defaultUrls } from '../server/status.js';
22
+ import { runUpdate, runRollback, UPDATE_STEPS } from '../server/update.js';
23
+ import { planUninstall, runUninstall } from '../server/uninstall.js';
24
+ import { serverChecks } from '../server/doctor.js';
25
+ import { writeDiagBundle } from '../server/diag.js';
26
+ import { latestSnapshot, listSnapshots } from '../server/snapshot.js';
27
+ import { loadRegistry, saveServer, buildRows, planGo, hereIdentity, registryPath } from '../server/devServers.js';
28
+
29
+ const icon = (s) => (s === 'pass' ? chalk.green('✔') : s === 'warn' ? chalk.yellow('⚠') : s === 'skip' ? chalk.dim('·') : chalk.red('✖'));
30
+
31
+ /** Pure: is the two-step uninstall confirmation satisfied? */
32
+ export function uninstallConfirmed({ word, path, root }) {
33
+ return String(word || '').trim() === 'uninstall' && resolve(String(path || '').trim() || '/nonexistent') === resolve(root);
34
+ }
35
+
36
+ function printRows(rows, out = console.log) {
37
+ for (const r of rows) {
38
+ if (r.kind === 'head') out(chalk.bold(`\n ${r.text}`));
39
+ else if (r.kind === 'gap') out('');
40
+ else if (r.kind === 'dim') out(chalk.dim(` ${r.key ? `${String(r.key).padEnd(16)} ` : ''}${r.text}`));
41
+ else out(` ${String(r.key).padEnd(16)} ${r.text}`);
42
+ }
43
+ out('');
44
+ }
45
+
46
+ export function registerServerCommands(program, ctx = {}) {
47
+ const deps = ctx.serverDeps || {};
48
+ const env = deps.env || process.env;
49
+ const sys = deps.sys || createSys();
50
+ const fetchImpl = deps.fetchImpl || ((...a) => globalThis.fetch(...a));
51
+ const backups = deps.backups || backupsRoot(env);
52
+ const log = deps.log || ((l) => console.log(chalk.dim(` ${l}`)));
53
+ const ask = deps.ask || (async (message) => {
54
+ const { default: inquirer } = await import('inquirer');
55
+ const a = await inquirer.prompt([{ type: 'input', name: 'v', message }]);
56
+ return a.v;
57
+ });
58
+ const isTTY = deps.isTTY ?? (() => Boolean(process.stdin.isTTY));
59
+ const urls = () => deps.urls || defaultUrls(ctx.config);
60
+ const json = () => program.opts().output === 'json' || program.opts().json;
61
+ const dry = (o) => Boolean(o.dryRun || program.opts().dryRun);
62
+ const yes = (o) => Boolean(o.yes || program.opts().yes);
63
+
64
+ async function install(o) {
65
+ const i = await resolveInstall({ dir: o.dir, env, sys, journey: deps.journey });
66
+ if (i.error) { const e = new Error(i.error); e.exitCode = 2; throw e; }
67
+ return i;
68
+ }
69
+
70
+ const server = program.command('server').description('Your self-hosted Quox install: status, update, rollback, uninstall, doctor, diag');
71
+ const withDir = (c) => c.option('--dir <path>', 'the install directory (default: the one Getting started recorded)');
72
+
73
+ withDir(server.command('status').description('Version, health per service, disk, ports and the last update (read-only)'))
74
+ .action(async (o) => {
75
+ const i = await install(o);
76
+ const st = await gatherStatus({ install: i, sys, fetchImpl, urls: urls(), backups });
77
+ if (json()) { console.log(JSON.stringify(st, null, 2)); return; }
78
+ printRows(statusRows(st));
79
+ if (!st.http.every((h) => h.ok) || (st.services.ok && st.services.services.some((s) => s.state !== 'running'))) process.exitCode = 1;
80
+ });
81
+
82
+ withDir(server.command('update').description('Update to the latest release: backup first, then fetch, build, migrate, health; rolls back on failure'))
83
+ .option('--dry-run', 'print the plan and run nothing')
84
+ .option('--to <version>', 'a specific release (default: the latest quoxcore tag)')
85
+ .option('--force', 'proceed despite local changes or low disk, or re-apply the same version')
86
+ .option('--restart', 'abandon a stopped update to a different version and start this one')
87
+ .option('--no-rollback', 'on failure, stop where it failed instead of rolling back')
88
+ .option('-y, --yes', 'skip the confirmation')
89
+ .action(async (o) => {
90
+ const i = await install(o);
91
+ const dryRun = dry(o);
92
+ if (!dryRun && !yes(o)) {
93
+ const installed = await readInstalled(i, sys);
94
+ let target = o.to ? vtag(o.to) : null;
95
+ if (!target) { const av = await fetchAvailable({ fetchImpl }); if (!av.ok) throw new Error(`could not read the available versions: ${av.error}`); target = av.latest; }
96
+ const cur = installed.release ? vtag(installed.release) : '?';
97
+ if (cmpSemver(target, cur) !== null && cmpSemver(target, cur) <= 0 && !o.force) { console.log(`Already on ${cur} (latest is ${target}). Nothing to do.`); return; }
98
+ console.log(`\n Update ${cur} -> ${chalk.bold(target)} at ${i.repoDir}`);
99
+ console.log(` Changes: ${installed.release ? compareUrl(cur, target) : releaseNotesUrl(target)}`);
100
+ console.log(` A backup (database + config) is taken first, into ${backups}. The stack stops briefly while volumes are archived.`);
101
+ console.log(' Preview every command first with: quox server update --dry-run\n');
102
+ if (!isTTY()) throw new Error('quox server update needs confirmation; re-run with --yes in a non-interactive shell');
103
+ const a = await ask(`Type "update" to update to ${target}`);
104
+ if (String(a).trim() !== 'update') { console.log('Not updated. Nothing was changed.'); return; }
105
+ }
106
+ const r = await runUpdate({ install: i, sys, backups, urls: urls(), fetchImpl, dryRun, to: o.to, force: o.force, restart: o.restart, noRollback: o.rollback === false, log });
107
+ if (json()) { console.log(JSON.stringify(r, null, 2)); if (!r.ok) process.exitCode = 2; return; }
108
+ const p = r.plan || {};
109
+ if (r.upToDate) { console.log(`Already on ${p.current} (latest is ${p.target}). Nothing to do.`); return; }
110
+ if (dryRun && r.ok) {
111
+ console.log(chalk.bold(`\n Plan: ${p.current || '?'} (${p.currentCommit || '?'}) -> ${p.target}${p.targetCommit ? ` (${p.targetCommit})` : ''}`));
112
+ if (p.changes) console.log(` What changes: ${p.changes}`);
113
+ console.log(` Release notes: ${p.releaseNotes} (if published for this tag)`);
114
+ if (p.resumedAfter) console.log(` Resumes after: ${p.resumedAfter}`);
115
+ console.log(` Steps: ${UPDATE_STEPS.join(' -> ')}; on failure it rolls back automatically.`);
116
+ if (p.problems?.length) for (const x of p.problems) console.log(chalk.yellow(` ⚠ ${x}`));
117
+ console.log(chalk.dim(`\n Dry run: ${r.planned.length} changes listed above, none made. Re-run without --dry-run to update.\n`));
118
+ return;
119
+ }
120
+ if (r.ok) { console.log(chalk.green(`\n ✔ Updated to ${p.target}. Backup: ${p.snapshot}\n`)); return; }
121
+ process.exitCode = 2;
122
+ console.log(chalk.red(`\n ✖ Update failed at ${r.failedStep}: ${r.error}`));
123
+ if (r.rollback) console.log(r.rolledBack ? chalk.yellow(` Rolled back (${r.rollback.scope}); you are on ${p.current} again.`) : chalk.red(` Rollback did not complete: ${r.rollback.error}. Backup: ${p.snapshot}. Run quox server rollback.`));
124
+ else if (r.failedStep !== 'preflight' && r.failedStep !== 'backup') console.log(` Not rolled back. Re-run quox server update to resume, or quox server rollback.`);
125
+ console.log('');
126
+ });
127
+
128
+ withDir(server.command('rollback').description('Restore the previous snapshot and version (database, config, code)'))
129
+ .option('--dry-run', 'print the plan and run nothing')
130
+ .option('--snapshot <dir>', 'a specific snapshot (default: the latest)')
131
+ .option('--list', 'list the snapshots and exit')
132
+ .option('-y, --yes', 'skip the confirmation')
133
+ .action(async (o) => {
134
+ if (o.list) {
135
+ const s = listSnapshots(backups);
136
+ if (json()) { console.log(JSON.stringify(s, null, 2)); return; }
137
+ if (!s.length) { console.log(`No snapshots in ${backups}.`); return; }
138
+ for (const x of s) console.log(` ${x.createdAt} ${String(x.release || '?').padEnd(8)} ${String(x.commit || '?').padEnd(10)} ${x.reason} ${x.dir}`);
139
+ return;
140
+ }
141
+ const i = await install(o);
142
+ const dryRun = dry(o);
143
+ const snap = o.snapshot ? listSnapshots(backups).find((s) => s.dir === o.snapshot || s.dir.endsWith(`/${o.snapshot}`)) : latestSnapshot(backups);
144
+ if (!snap) throw new Error(o.snapshot ? `no complete snapshot at ${o.snapshot}` : `no snapshot in ${backups}; nothing to roll back to`);
145
+ if (!dryRun && !yes(o)) {
146
+ console.log(`\n Roll back to ${snap.release || '?'} (${snap.commit}) from ${snap.createdAt}.`);
147
+ console.log(chalk.yellow(' This REPLACES the current database and config with the snapshot; anything since then is lost.'));
148
+ console.log(' Preview first with: quox server rollback --dry-run\n');
149
+ if (!isTTY()) throw new Error('quox server rollback needs confirmation; re-run with --yes in a non-interactive shell');
150
+ const a = await ask('Type "rollback" to continue');
151
+ if (String(a).trim() !== 'rollback') { console.log('Not rolled back. Nothing was changed.'); return; }
152
+ }
153
+ const r = await runRollback({ install: i, sys, backups, urls: urls(), fetchImpl, dryRun, snapshotDir: snap.dir, log });
154
+ if (json()) { console.log(JSON.stringify(r, null, 2)); if (!r.ok) process.exitCode = 2; return; }
155
+ if (r.ok && dryRun) { console.log(chalk.dim(`\n Dry run: rollback to ${snap.release || snap.commit} from ${snap.dir}; ${r.planned.length} changes listed above, none made.\n`)); return; }
156
+ if (r.ok) { console.log(chalk.green(`\n ✔ Rolled back to ${snap.release || snap.commit}.\n`)); return; }
157
+ process.exitCode = 2;
158
+ console.log(chalk.red(`\n ✖ Rollback failed at ${r.step}: ${r.error}\n`));
159
+ });
160
+
161
+ withDir(server.command('uninstall').description('Remove this install (lists everything first; keeps backups unless --purge)'))
162
+ .option('--dry-run', 'list what would be removed and run nothing')
163
+ .option('--purge', 'also delete every backup')
164
+ .option('--no-backup', 'skip the final snapshot')
165
+ .option('-y, --yes', 'non-interactive: confirm step one (needs --confirm too)')
166
+ .option('--confirm <path>', 'non-interactive: confirm step two by naming the install directory')
167
+ .action(async (o) => {
168
+ const i = await install(o);
169
+ const dryRun = dry(o);
170
+ const plan = await planUninstall({ install: i, sys, backups, purge: Boolean(o.purge), home: deps.home || homedir() });
171
+ if (json() && dryRun) { console.log(JSON.stringify({ plan }, null, 2)); return; }
172
+ console.log(chalk.bold('\n This removes:'));
173
+ console.log(` Containers (${plan.containers.length}): ${plan.containers.join(', ') || plan.containersError || 'none'}`);
174
+ console.log(` Volumes, with ALL their data (${plan.volumes.length}): ${plan.volumes.join(', ') || 'none'}`);
175
+ console.log(` Images built for this install (${plan.images.length}): ${plan.images.join(', ') || 'none'}`);
176
+ console.log(` Networks: ${plan.networks}`);
177
+ console.log(` Directories: ${[...plan.dirs, ...(plan.root ? [plan.root] : [])].join(', ')}`);
178
+ if (plan.journeyFile) console.log(` Getting started state: ${plan.journeyFile}`);
179
+ if (plan.refused.length) console.log(chalk.yellow(` Refusing to remove (too broad): ${plan.refused.join(', ')}`));
180
+ console.log(chalk.bold(' It keeps:'));
181
+ console.log(plan.backups.removed ? chalk.red(` Nothing: --purge also deletes ${plan.backups.count} backup(s) in ${plan.backups.dir}`)
182
+ : ` Backups in ${plan.backups.dir} (${plan.backups.count} now${o.backup === false ? '' : ', plus a final snapshot taken first'})`);
183
+ console.log('');
184
+ if (!dryRun) {
185
+ let ok;
186
+ if (isTTY() && !(yes(o) && o.confirm)) {
187
+ const w = await ask('Step 1 of 2: type "uninstall" to continue');
188
+ if (String(w).trim() !== 'uninstall') { console.log('Not uninstalled. Nothing was changed.'); return; }
189
+ const p = await ask(`Step 2 of 2: type the install directory (${i.root}) to confirm`);
190
+ ok = uninstallConfirmed({ word: 'uninstall', path: p, root: i.root });
191
+ } else ok = yes(o) && uninstallConfirmed({ word: 'uninstall', path: o.confirm, root: i.root });
192
+ if (!ok) {
193
+ if (!isTTY()) throw new Error(`quox server uninstall needs two confirmations; in a non-interactive shell pass --yes and --confirm ${i.root}`);
194
+ console.log('The directory did not match. Not uninstalled. Nothing was changed.');
195
+ return;
196
+ }
197
+ }
198
+ const r = await runUninstall({ install: i, sys, backups, purge: Boolean(o.purge), noBackup: o.backup === false, dryRun, log, plan });
199
+ if (json()) { console.log(JSON.stringify(r, null, 2)); if (!r.ok) process.exitCode = 2; return; }
200
+ if (!r.ok) { process.exitCode = 2; console.log(chalk.red(` ✖ Uninstall stopped at ${r.step}: ${r.error}\n`)); return; }
201
+ if (dryRun) { console.log(chalk.dim(` Dry run: ${r.planned.length} changes listed above, none made.\n`)); return; }
202
+ console.log(chalk.green(` ✔ Uninstalled.${r.finalSnapshot ? ` Final snapshot: ${r.finalSnapshot}` : ''}\n`));
203
+ });
204
+
205
+ withDir(server.command('doctor').description('The quox doctor checks plus server-side checks (read-only)'))
206
+ .action(async (o) => {
207
+ const i = await install(o);
208
+ const { runDoctorChecks } = await import('./doctor.js');
209
+ const platform = deps.platformChecks ? await deps.platformChecks() : await runDoctorChecks({ config: ctx.config });
210
+ const srv = await serverChecks({ install: i, sys, fetchImpl, urls: urls(), backups });
211
+ const all = [...platform, ...srv];
212
+ const failed = all.filter((c) => c.status === 'fail' || c.status === 'error').length;
213
+ const warned = all.filter((c) => c.status === 'warn').length;
214
+ if (json()) { console.log(JSON.stringify({ platform, server: srv, summary: { failed, warnings: warned } }, null, 2)); }
215
+ else {
216
+ console.log(chalk.bold('\n Platform'));
217
+ for (const c of platform) console.log(` ${icon(c.status)} ${String(c.name).padEnd(20)} ${chalk.dim(c.detail || '')}`);
218
+ console.log(chalk.bold('\n Server'));
219
+ for (const c of srv) console.log(` ${icon(c.status)} ${String(c.name).padEnd(20)} ${chalk.dim(c.detail || '')}`);
220
+ console.log(`\n ${all.length - failed - warned} passed · ${warned} warnings · ${failed} failed\n`);
221
+ }
222
+ process.exitCode = failed ? 2 : warned ? 1 : 0;
223
+ });
224
+
225
+ withDir(server.command('diag').description('Write a redacted support bundle locally (never uploaded)'))
226
+ .option('--out <dir>', 'where to write it (default: the current directory)')
227
+ .action(async (o) => {
228
+ const i = await install(o);
229
+ const { runDoctorChecks } = await import('./doctor.js');
230
+ const { resolveCliVersion } = await import('../core/version.js');
231
+ const st = await gatherStatus({ install: i, sys, fetchImpl, urls: urls(), backups });
232
+ const platform = deps.platformChecks ? await deps.platformChecks() : await runDoctorChecks({ config: ctx.config }).catch(() => []);
233
+ const srv = await serverChecks({ install: i, sys, fetchImpl, urls: urls(), backups });
234
+ const r = await writeDiagBundle({
235
+ install: i, sys, out: resolve(o.out || process.cwd()),
236
+ data: { versions: { cli: resolveCliVersion(), installed: st.installed, running: st.running }, status: st, doctor: [...platform, ...srv] }
237
+ });
238
+ if (json()) { console.log(JSON.stringify(r, null, 2)); return; }
239
+ console.log(chalk.green(`\n ✔ Support bundle written: ${r.file}`));
240
+ console.log(chalk.dim(' Nothing was uploaded. Secrets are kept out by an allowlist; look through it before you share it.\n'));
241
+ });
242
+
243
+ // `quox server config app-url [url]`: read or set the public app URL (instance_config app_base_url).
244
+ // Server-side source of truth, the same value Settings > General > App URL edits.
245
+ const cfg = server.command('config').description('Instance settings stored on the server');
246
+ cfg.command('app-url')
247
+ .description('Show or set the public app URL (used in sign-in links and emails). Pass a URL to set it, or --clear to fall back to the environment value')
248
+ .argument('[url]', 'the new public app URL, e.g. https://quox.example.com')
249
+ .option('--clear', 'remove the stored value')
250
+ .action(async (url, o) => {
251
+ const { client, auth } = ctx;
252
+ auth?.requireAuth?.();
253
+ const get = resolveEndpoint('admin.instance.basics.get');
254
+ const set = resolveEndpoint('admin.instance.basics.set');
255
+ if (url && o.clear) { const e = new Error('give a URL or --clear, not both'); e.exitCode = 2; throw e; }
256
+ if (url || o.clear) {
257
+ const r = await client.put(set.path, { service: set.service, body: { appBaseUrl: o.clear ? null : url } });
258
+ if (json()) { console.log(JSON.stringify(r, null, 2)); return; }
259
+ console.log(chalk.green(o.clear ? ' ✔ App URL cleared; using the environment value.' : ` ✔ App URL set to ${url}`));
260
+ console.log(chalk.dim(` Effective now: ${r.effective?.appBaseUrl}\n`));
261
+ return;
262
+ }
263
+ const r = await client.get(get.path, { service: get.service });
264
+ if (json()) { console.log(JSON.stringify(r, null, 2)); return; }
265
+ const src = r.effective?.appBaseUrlSource === 'instance_config' ? 'set on this server' : 'from the environment (not set in Settings)';
266
+ console.log(`${r.effective?.appBaseUrl} ${chalk.dim(`(${src})`)}`);
267
+ });
268
+
269
+ // `quox server config lan-targets [on|off]`: let integrations (Ollama, LM Studio, vLLM, Grafana ...)
270
+ // point at private-network addresses. Default off. Loopback and link-local stay blocked either way.
271
+ cfg.command('lan-targets')
272
+ .description('Show or set whether AI servers and integrations on your own private network are allowed (default off)')
273
+ .argument('[state]', 'on or off')
274
+ .action(async (state) => {
275
+ const { client, auth } = ctx;
276
+ auth?.requireAuth?.();
277
+ const get = resolveEndpoint('admin.instance.lan_targets.get');
278
+ const set = resolveEndpoint('admin.instance.lan_targets.set');
279
+ if (state !== undefined && !['on', 'off'].includes(String(state).toLowerCase())) { const e = new Error('give on or off'); e.exitCode = 2; throw e; }
280
+ if (state !== undefined) {
281
+ const r = await client.put(set.path, { service: set.service, body: { enabled: String(state).toLowerCase() === 'on' } });
282
+ if (json()) { console.log(JSON.stringify(r, null, 2)); return; }
283
+ console.log(chalk.green(r.enabled ? ' ✔ Private-network targets are ON: integrations may use addresses on your own network.' : ' ✔ Private-network targets are OFF.'));
284
+ console.log(chalk.dim(' Loopback (localhost) and link-local addresses stay blocked either way.\n'));
285
+ return;
286
+ }
287
+ const r = await client.get(get.path, { service: get.service });
288
+ if (json()) { console.log(JSON.stringify(r, null, 2)); return; }
289
+ console.log(`Private-network targets: ${r.enabled ? 'on' : 'off'} ${chalk.dim('(quox server config lan-targets on|off)')}`);
290
+ });
291
+
292
+ // DEVFLEET CX-2: `quox server roles` — the server roles registry (role -> url). A role (brain today) can be pointed
293
+ // off-box so the service runs on another host without an env edit; resolveServiceUrl reads the registry before env.
294
+ // list is readable by any instance admin; set/unset mutate instance-wide state so are instance-OWNER (superadmin) only.
295
+ const roles = server.command('roles').description('Point a server role (e.g. brain) at a URL, so a service can run off-box without an env change');
296
+ roles.command('list')
297
+ .description('Show each role, its URL, and whether it comes from the registry or the environment (read-only)')
298
+ .action(async () => {
299
+ const { client, auth } = ctx;
300
+ auth?.requireAuth?.();
301
+ const ep = resolveEndpoint('server.roles.list');
302
+ const r = await client.get(ep.path, { service: ep.service });
303
+ if (json()) { console.log(JSON.stringify(r, null, 2)); return; }
304
+ if (r.corrupt) console.log(chalk.yellow(` ⚠ the roles store is unreadable (${r.error}); showing environment values only
305
+ `));
306
+ for (const row of (r.roles || [])) {
307
+ const where = row.source === 'registry' ? chalk.green('registry') : (row.source === 'env' ? chalk.dim('environment') : chalk.dim('unset'));
308
+ console.log(` ${row.role.padEnd(10)} ${row.url || chalk.dim('(none)')} ${chalk.dim(`[${where}]`)}`);
309
+ }
310
+ if (!(r.roles || []).length) console.log(chalk.dim(' no roles'));
311
+ });
312
+ roles.command('set')
313
+ .description('Point a role at a URL (instance owner only). Use --test to probe it for health and version first')
314
+ .argument('<role>', 'the role, e.g. brain')
315
+ .argument('<url>', 'the URL the role should resolve to, e.g. http://10.1.2.3:3106')
316
+ .option('--test', 'probe <url> for health and version before storing it; abort if it is unreachable')
317
+ .action(async (role, url, o) => {
318
+ const { client, auth } = ctx;
319
+ auth?.requireAuth?.();
320
+ if (o.test) {
321
+ const tp = resolveEndpoint('server.roles.test', { role });
322
+ const t = await client.post(tp.path, { service: tp.service, body: { url } });
323
+ if (!t.success) { const e = new Error(`--test failed: ${t.error || `status ${t.status}`}; not storing`); e.exitCode = 1; throw e; }
324
+ if (!json()) console.log(chalk.dim(` probe ok: status ${t.status}${t.version ? `, version ${t.version}` : ''}`));
325
+ }
326
+ const ep = resolveEndpoint('server.roles.set', { role });
327
+ const r = await client.put(ep.path, { service: ep.service, body: { url } });
328
+ if (json()) { console.log(JSON.stringify(r, null, 2)); return; }
329
+ console.log(chalk.green(` ✔ role ${r.role} -> ${r.url}`));
330
+ });
331
+ roles.command('unset')
332
+ .description('Remove a role from the registry so it falls back to the environment value (instance owner only)')
333
+ .argument('<role>', 'the role to remove')
334
+ .action(async (role) => {
335
+ const { client, auth } = ctx;
336
+ auth?.requireAuth?.();
337
+ const ep = resolveEndpoint('server.roles.unset', { role });
338
+ const r = await client.delete(ep.path, { service: ep.service });
339
+ if (json()) { console.log(JSON.stringify(r, null, 2)); return; }
340
+ console.log(chalk.green(r.removed ? ` ✔ role ${role} removed; falling back to the environment value` : ` role ${role} was not set`));
341
+ });
342
+
343
+ // DEVFLEET: the registered dev servers. Bastion supplies address + state, the org's dev-server registry supplies purpose/project/workdir.
344
+ const regPath = () => deps.registryPath || registryPath(env);
345
+ async function devRows() {
346
+ const { client, auth } = ctx;
347
+ auth?.requireAuth?.();
348
+ const ep = resolveEndpoint('v1.fleet.hosts.list');
349
+ const d = await client.get(ep.path, { service: ep.service, retries: 0, timeout: 5000 });
350
+ const hosts = d?.hosts || d;
351
+ if (!Array.isArray(hosts)) throw new Error('could not read the Bastion host registry (quox fleet hosts)');
352
+ { const reg = await loadRegistry({ client, path: regPath() }); return buildRows(hosts, { servers: reg.servers, defaults: reg.defaults }, deps.here || hereIdentity()); }
353
+ }
354
+ server.command('list')
355
+ .description('The registered dev servers: purpose, project, reachable, and which one this is (read-only)')
356
+ .action(async () => {
357
+ const rows = await devRows();
358
+ if (json()) { console.log(JSON.stringify(rows.map(({ ip, user, jump, ...r }) => r), null, 2)); return; }
359
+ if (!rows.length) { console.log(chalk.dim(` no dev servers registered. Add one: quox server mark <host> --purpose "main dev"`)); return; }
360
+ const showDeploy = rows.some(r => r.lastDeploy);
361
+ const cell = (v) => (v == null || v === '' ? '-' : String(v));
362
+ const w = (k) => Math.max(k.length, ...rows.map(r => cell(r[k]).length));
363
+ const cols = ['name', 'purpose', 'project', 'repo'];
364
+ const widths = Object.fromEntries(cols.map(k => [k, w(k)]));
365
+ console.log(chalk.bold(` ${cols.map(k => k.toUpperCase().padEnd(widths[k])).join(' ')} REACHABLE${showDeploy ? ' LAST DEPLOY' : ''}`));
366
+ for (const r of rows) {
367
+ const line = `${cols.map(k => cell(r[k]).padEnd(widths[k])).join(' ')} ${(r.reachable ? 'yes' : 'no').padEnd(9)}${showDeploy ? ` ${cell(r.lastDeploy)}` : ''}`;
368
+ console.log(` ${line}${r.current ? chalk.cyan(' <- this server') : ''}`);
369
+ }
370
+ });
371
+ server.command('mark')
372
+ .description('Record what a dev server is for (org registry on the Quox server, additive): purpose, project, repo, workdir, ci')
373
+ .argument('<name>', 'the Bastion host id, e.g. web01')
374
+ .option('--purpose <text>', 'what this server is for, e.g. "main dev"')
375
+ .option('--project <text>').option('--repo <text>').option('--workdir <path>', 'where go starts quox on that server')
376
+ .option('--ci <text>', 'CI or auto-deploy note or URL').option('--user <user>', 'ssh user (default control)').option('--jump <host>', 'ssh ProxyJump alias (default: the registry defaults.jump, else a direct ssh)')
377
+ .option('--last-deploy <text>', 'last deploy note (shown only if any server has one)')
378
+ .action(async (name, o) => {
379
+ ctx.auth?.requireAuth?.();
380
+ const e = await saveServer({ client: ctx.client, name, fields: { ...o, lastDeploy: o.lastDeploy }, path: regPath() });
381
+ if (json()) { console.log(JSON.stringify({ name, ...e }, null, 2)); return; }
382
+ console.log(chalk.green(` ✔ ${name} recorded${e.purpose ? ` as ${e.purpose}` : ''}`));
383
+ });
384
+ server.command('go')
385
+ .description('Open a Quox window on a registered dev server (ssh via your own config and the Bastion jump host, then quox in its workdir)')
386
+ .argument('<name>', 'a server from `quox server list`')
387
+ .option('--dry-run', 'print what would run and run nothing')
388
+ .action(async (name, o) => {
389
+ const rows = await devRows();
390
+ const plan = planGo(rows.find(r => r.name === name));
391
+ if (plan.error) {
392
+ const known = rows.map(r => r.name).join(', ') || 'none registered';
393
+ const e = new Error(plan.error === 'unknown server' ? `unknown server "${name}". Registered: ${known}` : plan.error);
394
+ e.exitCode = 1; throw e;
395
+ }
396
+ console.log(chalk.dim(` going to ${name}: ${plan.text}`));
397
+ if (dry(o)) return;
398
+ const spawnImpl = deps.spawnSync || (await import('node:child_process')).spawnSync;
399
+ const r = spawnImpl(plan.cmd, plan.args, { stdio: 'inherit', cwd: plan.cwd });
400
+ if (r?.error) { const e = new Error(`could not start ${plan.cmd}: ${r.error.message}`); e.exitCode = 1; throw e; }
401
+ if (r?.status) process.exitCode = r.status;
402
+ });
403
+
404
+ return server;
405
+ }
@@ -7,6 +7,16 @@ import { formatDetail, printSuccess, printInfo } from '../core/output.js';
7
7
  import { resolveEndpoint } from '../api/endpoints.js';
8
8
  import { QuoxApiError, QuoxValidationError } from '../core/errors.js';
9
9
  import { confirmDestructive } from '../core/confirm.js';
10
+ import { passwordProblem } from '../quoxon/onboarding/finish.js';
11
+
12
+ // The auth service's rule (services/auth/utils/passwordValidation.js), said up front.
13
+ export const PASSWORD_POLICY = 'Passwords need at least 12 characters, with an uppercase letter, a lowercase letter, a number and a special character (for example - or !).';
14
+
15
+ async function readStdinText() {
16
+ const chunks = [];
17
+ for await (const chunk of process.stdin) chunks.push(chunk);
18
+ return Buffer.concat(chunks).toString('utf8');
19
+ }
10
20
 
11
21
  /**
12
22
  * LICENSE-LAUNCH LL-8: read a license-key-v2 JSON object from a file or
@@ -183,11 +193,23 @@ export function registerSetupCommands(program, ctx) {
183
193
  .command('admin')
184
194
  .description('Create the initial admin account')
185
195
  .option('--email <email>', 'admin email')
186
- .option('--password <password>', 'admin password')
196
+ .option('--password <password>', 'admin password (visible in the process list; prefer --password-stdin)')
197
+ .option('--password-stdin', 'read the admin password from stdin (first line), never argv')
187
198
  .option('--name <name>', 'admin display name')
188
199
  .action(async (options) => {
189
200
  let { email, password, name } = options;
190
201
 
202
+ // QUOXON-Z2H lane R: argv is visible in ps and shell history, so a
203
+ // script pipes the password in, same as `quox login --password-stdin`.
204
+ if (options.password && options.passwordStdin) {
205
+ throw new QuoxValidationError('Pass the password with --password or --password-stdin, not both.');
206
+ }
207
+ if (options.passwordStdin) {
208
+ const text = await (ctx.readStdin || readStdinText)();
209
+ password = String(text || '').split('\n')[0].replace(/\r$/, '');
210
+ if (!password) throw new QuoxValidationError('--password-stdin given but stdin was empty.');
211
+ }
212
+
191
213
  if (!email || !password || !name) {
192
214
  const { default: inquirer } = await import('inquirer');
193
215
 
@@ -206,18 +228,22 @@ export function registerSetupCommands(program, ctx) {
206
228
  }
207
229
  if (!password) {
208
230
  const ans = await inquirer.prompt([{
209
- type: 'password', name: 'password', message: 'Password:', mask: '*',
231
+ type: 'password', name: 'password', message: `Password (${PASSWORD_POLICY}):`, mask: '*',
232
+ validate: (v) => { const bad = passwordProblem(v); return bad ? `${bad} ${PASSWORD_POLICY}` : true; },
210
233
  }]);
211
234
  password = ans.password;
212
235
  }
213
236
  }
214
237
 
238
+ // The policy is the FIRST thing a weak password meets, before any
239
+ // server call (the server enforces the same rule; this only says it sooner).
240
+ const problem = passwordProblem(password);
241
+ if (problem) throw new QuoxValidationError(`${PASSWORD_POLICY} ${problem} Nothing was sent to the server.`);
242
+
215
243
  // services/auth/routes/setup.js POST /admin destructures `name` (not
216
- // `display_name`) from req.body — sending only display_name silently
217
- // dropped the admin's name (fell back to 'Admin'). Send both: `name`
218
- // is what the endpoint actually reads; `display_name` is kept for
219
- // forward-compat / any other consumer that expects it.
220
- const data = await client.post(resolveEndpoint('setup.admin').path, {
244
+ // `display_name`) from req.body. Send both: `name` is what the endpoint
245
+ // reads; `display_name` is kept for forward-compat.
246
+ await client.post(resolveEndpoint('setup.admin').path, {
221
247
  service: resolveEndpoint('setup.admin').service,
222
248
  body: { email, password, name, display_name: name },
223
249
  noAuth: true,
@@ -0,0 +1,41 @@
1
+ // `quox shortcut add|remove`: a short command (default `q`) for quox. Local only; never shadows an existing command.
2
+ import chalk from 'chalk';
3
+ import { createInterface } from 'node:readline/promises';
4
+ import { addShortcut, removeShortcut, DEFAULT_NAME } from '../core/shortcut.js';
5
+ import { askText } from '../quoxon/ask.js';
6
+
7
+ async function askYes(line) {
8
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
9
+ try { return /^y(es)?$/i.test((await rl.question(`${line} `)).trim()); } finally { rl.close(); }
10
+ }
11
+
12
+ export function registerShortcutCommand(program, ctx = {}) {
13
+ const d = ctx.shortcutDeps || {};
14
+ const out = d.log || ((l) => console.log(l));
15
+ const sc = program.command('shortcut')
16
+ .description('A short command for quox, like q (add / remove); refuses if the name already runs something else, never uses sudo');
17
+ sc.command('add')
18
+ .description('Make a "q" command that runs quox: a symlink in a directory you own that is on PATH. Refuses (and says what it found) if q already resolves to something else on PATH; never shadows an existing command, no sudo, no shell rc edits. Safe to repeat.')
19
+ .option('--name <name>', 'the short command name', DEFAULT_NAME)
20
+ .option('-y, --yes', 'create without asking (needed in a non-interactive shell)')
21
+ .action(async (o) => {
22
+ const name = o.name || DEFAULT_NAME;
23
+ const yes = o.yes || program.opts().yes;
24
+ if (!yes) {
25
+ const tty = d.isTTY ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
26
+ if (!tty) { out('Not created: this needs a yes. Re-run with quox shortcut add --yes in a non-interactive shell.'); process.exitCode = 1; return; }
27
+ if (!(await (d.ask || askYes)(askText(`Make \`${name}\` run quox?`, { keys: 'y/N' })))) { out('Not created. Nothing was changed.'); return; }
28
+ }
29
+ const r = (d.add || addShortcut)({ name, ...(d.opts || {}) });
30
+ out(r.ok ? chalk.green(`✔ ${r.message}`) : chalk.red(`✖ ${r.message}`));
31
+ if (!r.ok) process.exitCode = 1;
32
+ });
33
+ sc.command('remove')
34
+ .description('Remove the short command, only if it is a link to this quox (anything else named q is left alone)')
35
+ .option('--name <name>', 'the short command name', DEFAULT_NAME)
36
+ .action(async (o) => {
37
+ const r = (d.remove || removeShortcut)({ name: o.name || DEFAULT_NAME, ...(d.opts || {}) });
38
+ out(r.ok ? r.message : chalk.red(`✖ ${r.message}`));
39
+ if (!r.ok) process.exitCode = 1;
40
+ });
41
+ }
@@ -71,6 +71,38 @@ export function registerSkillsCommands(program, ctx) {
71
71
  const data = await client.get(resolveEndpoint('quoxflow.skills.list').path, { service: resolveEndpoint('quoxflow.skills.list').service, query });
72
72
  let rows = data.data || data.records || data;
73
73
  if (!Array.isArray(rows)) rows = [];
74
+ rows = rows.map((r) => ({ ...r, store: 'library' }));
75
+
76
+ // 4.2.4 skills/t3-skill-view: a library skill with an active quox#748 review row (pending or rejected) cannot
77
+ // run, and skill_view refuses it. Say so per skill. A queue read failure leaves runnable unknown, never true.
78
+ try {
79
+ const rq = resolveEndpoint('skills.reviewQueue.list');
80
+ const active = new Map();
81
+ for (const status of ['pending', 'rejected']) {
82
+ const r = await client.get(rq.path, { service: rq.service, query: { status, orgId: query.orgId } });
83
+ const items = r && (r.data || r.records);
84
+ for (const it of Array.isArray(items) ? items : []) if (it && it.skill_id) active.set(it.skill_id, it.status || status);
85
+ }
86
+ rows = rows.map((r) => ({ ...r, review_status: active.get(r.id) || null, runnable: !active.has(r.id) }));
87
+ } catch (err) {
88
+ printWarning(`Skill review queue could not be read, so which skills can run is unknown: ${err.message}`);
89
+ }
90
+
91
+ // #1006: crystals (`skills from-execution`) live in quoxflow's crystal store, not this library. Merge them
92
+ // unless a library-only filter is set (crystals have no edition/category/agent fit). A failure there warns,
93
+ // it never sinks the library listing.
94
+ if (!options.edition && !options.category && !options.agent) {
95
+ try {
96
+ const ep = resolveEndpoint('skills.crystals.list');
97
+ const c = await client.get(ep.path, { service: ep.service, query: { orgId: query.orgId } });
98
+ let crystals = c.data || c.skills || c;
99
+ if (!Array.isArray(crystals)) crystals = [];
100
+ if (options.tag) crystals = crystals.filter((r) => Array.isArray(r.tags) && r.tags.includes(options.tag));
101
+ rows = rows.concat(crystals.map((r) => ({ ...r, store: 'crystal' })));
102
+ } catch (err) {
103
+ printWarning(`Crystal store (skills from-execution) could not be read, showing the library only: ${err.message}`);
104
+ }
105
+ }
74
106
 
75
107
  if (options.search) {
76
108
  const needle = options.search.toLowerCase();
@@ -84,8 +116,8 @@ export function registerSkillsCommands(program, ctx) {
84
116
  // shown as undeclared, never defaulted to a licence.
85
117
  rows = rows.map((r) => ({ ...r, licence: shortLicenceLabel(r.license) }));
86
118
  formatOutput({
87
- columns: ['id', 'name', 'category', 'edition', 'version', 'licence', 'decayState', 'createdAt'],
88
- headers: ['ID', 'Name', 'Category', 'Edition', 'Version', 'Licence', 'Decay', 'Created'],
119
+ columns: ['id', 'store', 'name', 'category', 'edition', 'version', 'licence', 'decayState', 'createdAt'],
120
+ headers: ['ID', 'Store', 'Name', 'Category', 'Edition', 'Version', 'Licence', 'Decay', 'Created'],
89
121
  rows,
90
122
  }, { format: program.opts().output, emptyMessage: 'No skills found.' });
91
123
  });
@@ -101,8 +133,17 @@ export function registerSkillsCommands(program, ctx) {
101
133
  .action(async (id, options) => {
102
134
  auth.requireAuth();
103
135
  const query = { orgId: options.org || resolveOrgId(auth, program) };
104
- const data = await client.get(resolveEndpoint('quoxflow.skills.get', { id }).path, { service: resolveEndpoint('quoxflow.skills.get', { id }).service, query });
105
- formatDetail(data.data || data, { format: program.opts().output, title: `Skill: ${id}` });
136
+ let data, store = 'library';
137
+ try {
138
+ data = await client.get(resolveEndpoint('quoxflow.skills.get', { id }).path, { service: resolveEndpoint('quoxflow.skills.get', { id }).service, query });
139
+ } catch (err) {
140
+ if (err.status !== 404) throw err;
141
+ // #1006: not in the library; a crystal from `skills from-execution` lives in the crystal store
142
+ const ep = resolveEndpoint('skills.crystals.get', { id });
143
+ try { data = await client.get(ep.path, { service: ep.service, query }); } catch (err2) { if (err2.status === 404) throw err; throw err2; }
144
+ store = 'crystal';
145
+ }
146
+ formatDetail({ ...(data.data || data), store }, { format: program.opts().output, title: `Skill: ${id}` });
106
147
  });
107
148
 
108
149
  // ── skills run ───────────────────────────────────────────────────────────
@@ -354,7 +395,9 @@ export function registerSkillsCommands(program, ctx) {
354
395
  const ep = resolveEndpoint('skills.rollbackToVersion');
355
396
  const data = await client.post(ep.path, { service: ep.service, body, query: { orgId } });
356
397
  const record = data.data || data;
357
- printSuccess(`Skill rolled back to version ${targetVersion}.`);
398
+ // #1006: rolling back to the version that is already current is a server-side no-op (it returns that row)
399
+ if (record && Number(record.version) === targetVersion) printInfo(`Already at version ${targetVersion}: nothing was written.`);
400
+ else printSuccess(`Skill rolled back to version ${targetVersion} (written as version ${record?.version ?? '?'}).`);
358
401
  formatDetail(record, { format: program.opts().output });
359
402
  });
360
403
 
@@ -542,11 +585,27 @@ export function registerSkillsCommands(program, ctx) {
542
585
  sealed
543
586
  .command('show')
544
587
  .description('Show a sealed skill\'s manifest (name, description, input contract, content hash). Manifest only.')
545
- .argument('<sealedId>', 'sealed skill ID')
546
- .action(async (sealedId) => {
588
+ .argument('<sealedId>', 'sealed skill ID or name')
589
+ .action(async (idOrName) => {
547
590
  auth.requireAuth();
548
- const ep = resolveEndpoint('skills.sealed.get', { sealedId });
549
- const data = await client.get(ep.path, { service: ep.service });
591
+ let sealedId = idOrName;
592
+ let data;
593
+ try {
594
+ const ep = resolveEndpoint('skills.sealed.get', { sealedId });
595
+ data = await client.get(ep.path, { service: ep.service });
596
+ } catch (err) {
597
+ // Coverage wave (ev_mvg4iwfp_d0b2e3): chat passed the skill's name. A 404 that matches exactly one sealed
598
+ // skill's name resolves to its id; anything else keeps the original error.
599
+ if (err.status !== 404) throw err;
600
+ const lep = resolveEndpoint('skills.sealed.list');
601
+ const list = await client.get(lep.path, { service: lep.service });
602
+ const rows = (list && (list.sealed ?? list)) || [];
603
+ const hits = (Array.isArray(rows) ? rows : []).filter((r) => r && r.name === idOrName);
604
+ if (hits.length !== 1) throw err;
605
+ sealedId = hits[0].sealedId;
606
+ const ep = resolveEndpoint('skills.sealed.get', { sealedId });
607
+ data = await client.get(ep.path, { service: ep.service });
608
+ }
550
609
  printInfo('Manifest only. The sealed content is never returned by the API.');
551
610
  formatDetail(data, { format: program.opts().output, title: `Sealed skill: ${sealedId}` });
552
611
  });