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,89 @@
1
+ /**
2
+ * `quox presence [who]` / `quox presence set present|away|sleeping`: PRESENCE (owner 2026-10-16).
3
+ * A human says they are not at their terminal (/afk, /sleep in quox do the same). Sessions and agents
4
+ * read it so they stop waiting on approvals, which still queue normally: nothing is ever auto-approved.
5
+ * Collector routes GET/PUT /api/v1/presence; the org is the caller's, only a human login sets its own.
6
+ */
7
+ import { resolveEndpoint } from '../api/endpoints.js';
8
+ import { resolveOrgId } from './helpers.js';
9
+
10
+ const STATES = ['present', 'away', 'sleeping'];
11
+
12
+ function stamp(iso) {
13
+ const d = new Date(iso);
14
+ return Number.isNaN(d.getTime()) ? String(iso || '') : `${d.toISOString().slice(0, 10)} ${d.toISOString().slice(11, 16)} UTC`;
15
+ }
16
+
17
+ /** One plain line for a presence record. */
18
+ export function presenceText(p) {
19
+ if (!p || !p.away) return `${p?.who || 'they'}: present`;
20
+ const note = p.note ? `, "${p.note}"` : '';
21
+ return `${p.who}: ${p.auto ? 'away (idle)' : p.state} since ${stamp(p.since)}${note} (approvals will queue)`;
22
+ }
23
+
24
+ /** The fields an approval request reports, from GET /api/v1/presence. */
25
+ export function approvalPresenceFields(list) {
26
+ const away = (list && Array.isArray(list.presence) ? list.presence : []).filter((p) => p && p.away);
27
+ if (!away.length) return { owner_away: false };
28
+ const p = away.find((x) => x.who === 'owner') || away[0];
29
+ return { owner_away: true, owner_away_since: p.since, owner_presence: p.state, owner_away_notice: list.notice || presenceText(p) };
30
+ }
31
+
32
+ /** Read the org's presence; null on any failure (a presence read never fails the caller). */
33
+ export async function readPresence(client, orgId) {
34
+ try {
35
+ const ep = resolveEndpoint('presence.list');
36
+ return await client.get(ep.path, { service: ep.service, query: orgId ? { org_id: orgId } : undefined, retries: 0, timeout: 5000 });
37
+ } catch { return null; }
38
+ }
39
+
40
+ export function registerPresenceCommands(program, { client, auth }) {
41
+ const pr = program
42
+ .command('presence')
43
+ .description('Is a person at their terminal? With no name: everyone with presence on record. When someone is away, approvals queue; work on something else')
44
+ .argument('[who]', 'user id, email, email name, "owner" or "me"')
45
+ .action(async (who) => {
46
+ auth.requireAuth();
47
+ const orgId = resolveOrgId(auth, program);
48
+ const json = program.opts().output === 'json';
49
+ if (who) {
50
+ const ep = resolveEndpoint('presence.get', { who });
51
+ const d = await client.get(ep.path, { service: ep.service, query: { org_id: orgId } });
52
+ if (json) { console.log(JSON.stringify(d, null, 2)); return; }
53
+ console.log(presenceText(d.presence));
54
+ return;
55
+ }
56
+ const ep = resolveEndpoint('presence.list');
57
+ const d = await client.get(ep.path, { service: ep.service, query: { org_id: orgId } });
58
+ if (json) { console.log(JSON.stringify(d, null, 2)); return; }
59
+ const list = Array.isArray(d.presence) ? d.presence : [];
60
+ if (!list.some((p) => p.away)) console.log('Everyone is present: nobody has marked themselves away.');
61
+ for (const p of list) console.log(presenceText(p));
62
+ });
63
+
64
+ pr
65
+ .command('set')
66
+ .description('Say where you are: present, away or sleeping (a human login only). Same as /afk, /sleep, /back in quox')
67
+ .argument('<state>', 'present | away | sleeping')
68
+ .option('--note <text>', 'a short note sessions will see ("school run")')
69
+ .option('--slots <list>', 'your own window slots, comma separated (hitl and triage-q always count)')
70
+ .option('--auto-away <minutes>', 'read as away after this many idle minutes; "off" to disable (default off)')
71
+ .action(async (state, options) => {
72
+ if (!STATES.includes(state)) throw new Error('state must be present, away or sleeping');
73
+ auth.requireAuth();
74
+ const orgId = resolveOrgId(auth, program);
75
+ const body = { state };
76
+ if (options.note !== undefined) body.note = options.note;
77
+ if (options.slots) body.slots = String(options.slots).split(',').map((s) => s.trim()).filter(Boolean);
78
+ if (options.autoAway !== undefined) {
79
+ const off = /^(off|0|no)$/i.test(String(options.autoAway));
80
+ const n = Number(options.autoAway);
81
+ if (!off && (!Number.isInteger(n) || n < 1 || n > 1440)) throw new Error('--auto-away is minutes (1 to 1440) or off');
82
+ body.auto_away_minutes = off ? 0 : n;
83
+ }
84
+ const ep = resolveEndpoint('presence.set');
85
+ const d = await client.put(ep.path, { service: ep.service, body, query: { org_id: orgId } });
86
+ if (program.opts().output === 'json') { console.log(JSON.stringify(d, null, 2)); return; }
87
+ console.log(presenceText(d.presence));
88
+ });
89
+ }
@@ -0,0 +1,217 @@
1
+ /**
2
+ * quox provider list | connect <kind> | test [<kind>] (quox#1281, CLI verb first)
3
+ *
4
+ * The SAME backend calls as the TUI's /connect: cli/lib/quoxon/connect.js flows (connectFlow, connectOllamaFlow,
5
+ * connectCompatFlow, connectionLines) over cli/lib/quoxon/api.js createApi (the auth vault create/update with
6
+ * confirm_private_origin, the server-side key test and Ollama / OpenAI-compatible probes, capabilities, consent).
7
+ * Nothing here talks to the backend on its own, so the TUI and the CLI cannot drift. The vault routes audit every
8
+ * write server side (integration.private_origin for a private address), org-scoped by the caller's session.
9
+ *
10
+ * Secrets: a key is never an argv flag or argument. It arrives on stdin (--key-from-stdin) or from a hidden prompt,
11
+ * is sent once to the server test and the vault, and is never printed (connectFlow scrubs it from any message).
12
+ */
13
+ import { formatDetail, printInfo } from '../core/output.js';
14
+ import { resolveOrgId } from './helpers.js';
15
+ import { resolveEndpoint } from '../api/endpoints.js';
16
+ import { createApi, isInteractiveHuman } from '../quoxon/api.js';
17
+ import { PROVIDERS, providerFor, looksLikeKey, connectionLines, connectFlow, connectOllamaFlow, connectCompatFlow } from '../quoxon/connect.js';
18
+ import { needsConsent, consentCovers, connectConsentNote, providerLabel } from '../quoxon/consent.js';
19
+ import { readStdin } from '../quoxon/print.js';
20
+ import { INSTALL_TOOLS, toolFor, planLine, offerQuestion, runInstall, isAgentCaller } from '../quoxon/installCli.js';
21
+ import { YN_OPTIONS, waitForDecision } from '../core/askDecision.js';
22
+ import { detectCallerSource } from '../core/callerSource.js';
23
+
24
+ const KINDS = Object.keys(PROVIDERS);
25
+ const SAFE_ROW = ['id', 'integration_type', 'label', 'is_default', 'test_status', 'last_tested_at', 'created_at'];
26
+ const CONTAINER_NOTE = 'Quox runs in containers: 127.0.0.1 or localhost means the Quox container, not your machine. Use the host\'s LAN IP, e.g. http://192.0.2.10:11434';
27
+
28
+ function defaultIO() {
29
+ return {
30
+ isTTY: () => isInteractiveHuman(),
31
+ isAgent: () => isAgentCaller(process.env),
32
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
33
+ readStdin: () => readStdin(),
34
+ promptSecret: async (message) => {
35
+ const { default: inquirer } = await import('inquirer');
36
+ const a = await inquirer.prompt([{ type: 'password', name: 'v', message, mask: '*' }]);
37
+ return a.v;
38
+ },
39
+ confirm: async (message) => {
40
+ const { default: inquirer } = await import('inquirer');
41
+ const a = await inquirer.prompt([{ type: 'confirm', name: 'v', message, default: false }]);
42
+ return Boolean(a.v);
43
+ },
44
+ };
45
+ }
46
+
47
+ const say = (lines) => { for (const [t] of lines || []) printInfo(t); };
48
+
49
+ export function registerProviderCommands(program, ctx) {
50
+ const { client, auth, config } = ctx;
51
+ const io = ctx.providerIO || defaultIO();
52
+ const api = () => (ctx.makeApi ? ctx.makeApi() : createApi({ client, config, auth }));
53
+
54
+ const provider = program
55
+ .command('provider')
56
+ .description('Connect the AI providers your org uses (the same as /connect in the Quox terminal)');
57
+
58
+ // ── provider list ──────────────────────────────────────────────────────────
59
+ provider
60
+ .command('list')
61
+ .description('Show which AI providers this org can use, and how each is connected')
62
+ .action(async () => {
63
+ auth.requireAuth();
64
+ const a = api();
65
+ const [vault, caps] = await Promise.all([a.connections(), a.capabilities ? a.capabilities() : null]);
66
+ if (program.opts().output === 'json') {
67
+ const rows = vault?.ok ? vault.rows.map((r) => Object.fromEntries(SAFE_ROW.filter((k) => r[k] !== undefined).map((k) => [k, r[k]]))) : null;
68
+ formatDetail({ connections: rows, ...(vault?.ok ? {} : { error: vault?.error || 'vault not readable' }), capabilities: caps?.ok ? caps.capabilities : null }, { format: 'json' });
69
+ return;
70
+ }
71
+ say(connectionLines(vault, caps));
72
+ });
73
+
74
+ // ── provider connect ───────────────────────────────────────────────────────
75
+ const readKey = async (p, opts) => {
76
+ if (opts.keyFromStdin) return String(await io.readStdin() || '').replace(/\r?\n$/, '').trim();
77
+ if (io.isTTY()) return String(await io.promptSecret(`${p.name} key (hidden, never shown or stored on this machine):`) || '').trim();
78
+ throw new Error(`No key given. Pipe it in with --key-from-stdin (for example: quox provider connect ${p.id} --key-from-stdin < keyfile), or run this in a terminal to be asked. A key is never taken as an argument.`);
79
+ };
80
+ const consentAfter = async (type) => {
81
+ const a = api();
82
+ if (!needsConsent(type) || !a.consentStatus) return;
83
+ let rec = null; try { rec = await a.consentStatus(); } catch { rec = null; }
84
+ if (consentCovers(rec, type)) return;
85
+ // --yes never grants consent: it is this person's own OK to send their conversations to a third party
86
+ if (!io.isTTY() || !a.grantConsent) { printInfo(connectConsentNote(type)); return; }
87
+ const yes = await io.confirm(`Allow Quox to send your conversations to ${providerLabel(type)}? Your messages and tool results go to ${providerLabel(type)}, a third party, when it answers. Nothing is sent until you say yes.`);
88
+ if (!yes) { printInfo(`Left off: nothing goes to ${providerLabel(type)} until you say so. ${connectConsentNote(type)}`); return; }
89
+ try { await a.grantConsent(type); printInfo(`✔ Your OK is recorded: ${providerLabel(type)} can now answer. Withdraw it any time: quox consent list.`); }
90
+ catch (e) { printInfo(`Quox could not record your OK (${e?.message || e}). Try: quox consent grant ${type}`); }
91
+ };
92
+
93
+ provider
94
+ .command('connect')
95
+ .description('Connect an AI provider for this org: a keyed provider (openai, anthropic, groq...) takes its key on stdin or a hidden prompt; a self-hosted one (ollama, lmstudio, vllm, custom) takes a URL')
96
+ .argument('<kind>', `provider: ${KINDS.join(', ')}`)
97
+ .argument('[url]', 'server URL for ollama / lmstudio / vllm / custom (same as --url)')
98
+ .option('--url <url>', `server URL for a self-hosted provider. ${CONTAINER_NOTE}`)
99
+ .option('--model <model>', 'default model to use on that server')
100
+ .option('--key-from-stdin', 'read the API key from stdin (never pass a key as an argument)')
101
+ .option('--yes', 'confirm connecting to an address on your private network (the server still checks it and audits the decision)')
102
+ .addHelpText('after', `\n${CONTAINER_NOTE}\nKeys: --key-from-stdin, or run in a terminal to be asked with a hidden prompt.`)
103
+ .action(async (kind, urlArg, opts) => {
104
+ auth.requireAuth();
105
+ const p = providerFor(kind);
106
+ if (!p) throw new Error(`No provider called "${kind}". Use one of: ${KINDS.join(', ')}`);
107
+ if (urlArg && (looksLikeKey(urlArg) || (p.card && !p.url))) throw new Error('never pass a key as an argument (it would sit in your shell history and process list). Use --key-from-stdin, or run in a terminal to be asked.');
108
+ const a = api();
109
+ const url = opts.url || urlArg || '';
110
+ let r;
111
+ if (p.id === 'ollama') {
112
+ r = await connectOllamaFlow(a, [url, opts.model].filter(Boolean).join(' '));
113
+ say(r.lines);
114
+ if (r.privateAsk) {
115
+ const q = `Connect to ${r.privateAsk.host}? It's on your private network. Quox saves only ${r.privateAsk.origin}, after the server checks it answers as Ollama.`;
116
+ let yes = Boolean(opts.yes);
117
+ if (!yes && io.isTTY()) yes = await io.confirm(q);
118
+ if (!yes && !io.isTTY()) throw new Error(`${r.privateAsk.host} is on your private network. Run again with --yes to confirm this one address. Nothing was saved.`);
119
+ if (!yes) throw new Error(`Left blocked: ${r.privateAsk.host} was not connected. Nothing was saved.`);
120
+ r = await r.privateAsk.retry();
121
+ say(r.lines);
122
+ }
123
+ } else if (p.url) {
124
+ r = await connectCompatFlow(a, p.id, [url, opts.model].filter(Boolean).join(' '));
125
+ say(r.lines);
126
+ if (r.needsKey) { r = await connectFlow(a, 'custom', await readKey(PROVIDERS.custom, opts), r.extra); printInfo(r.text); if (r.disclosure) printInfo(r.disclosure); }
127
+ } else {
128
+ r = await connectFlow(a, p.id, await readKey(p, opts));
129
+ printInfo(r.text);
130
+ if (r.ok && r.disclosure) printInfo(r.disclosure);
131
+ }
132
+ if (r?.lanOffer) printInfo(' From the CLI an instance admin allows private targets with: quox server config lan-targets on then run this again.');
133
+ if (!r?.ok) throw new Error(r?.text || `${p.name} was not connected. Nothing was saved.`);
134
+ await consentAfter(p.type);
135
+ });
136
+
137
+ // ── provider install (LAZY-AI-CLI) ────────────────────────────────────────
138
+ // The user's own server downloads the vendor's npm package (never a binary Quox ships). A person decides: y/N on
139
+ // a terminal, a high-risk approval card when an agent runs it (an agent's --yes is not a person's yes).
140
+ provider
141
+ .command('install')
142
+ .description('Install Claude Code or Codex on this Quox server from npm (the vendor\'s own package), so Claude sign-in and Codex work. Instance admin only')
143
+ .argument('<tool>', `what to install: ${Object.keys(INSTALL_TOOLS).join(', ')}`)
144
+ .option('--yes', 'install without the y/N question (a script; an agent still gets an approval card)')
145
+ .action(async (toolArg, opts) => {
146
+ auth.requireAuth();
147
+ const tool = toolFor(toolArg);
148
+ if (!tool) throw new Error(`No installable CLI called "${toolArg}". Use one of: ${Object.keys(INSTALL_TOOLS).join(', ')}`);
149
+ const t = INSTALL_TOOLS[tool];
150
+ const a = api();
151
+ const st = await a.providerCliStatus();
152
+ const cur = st?.ok ? st.tools.find((x) => x.id === tool) : null;
153
+ if (cur?.installed) { printInfo(`✔ ${t.name} is already installed on this server (${cur.version || 'version unknown'}${cur.location ? `, ${cur.location}` : ''}). Nothing to do.`); return; }
154
+ const plan = await a.providerCliPlan(tool);
155
+ if (!plan?.ok) {
156
+ if (plan?.status === 404 || plan?.status === 405) throw new Error(`This Quox server is too old to install ${t.name} for you. Update it first: quox server update`);
157
+ throw new Error(plan?.status === 403 ? (plan.error || 'Only an instance admin can install software on this server.') : `Could not plan the install: ${plan?.error || 'no answer'}`);
158
+ }
159
+ printInfo(planLine(plan));
160
+ let approvalId = null;
161
+ if (io.isAgent()) {
162
+ const orgId = resolveOrgId(auth, program);
163
+ const src = (() => { try { return detectCallerSource(process.env); } catch { return {}; } })();
164
+ const session = process.env.QUOX_SESSION || src?.label || 'agent';
165
+ const ep = resolveEndpoint('inbox.decision.create', { orgId });
166
+ const filed = await client.post(ep.path, { service: ep.service, body: {
167
+ session, title: `Install ${t.name} on this server?`, why: planLine(plan), options: YN_OPTIONS, recommend: 'y', high_risk: true,
168
+ justification: `${session} asked to install ${t.vendor}'s ${plan.package} ${plan.version} from npm on this Quox server, so ${t.name} works. It runs as the Quox service user, never root, and every install is audited.`,
169
+ agent: process.env.QUOX_AGENT_ID || undefined,
170
+ } });
171
+ const id = filed?.decision?.id;
172
+ if (!id) throw new Error('Could not file the approval card. Nothing was installed.');
173
+ printInfo(`An agent is running this, so a person decides: approval card ${id} is in /approvals (needs the step-up PIN).`);
174
+ const r = await waitForDecision({
175
+ poll: (w) => { const g = resolveEndpoint('inbox.decision.get', { orgId, id }); return client.get(g.path, { service: g.service, query: { wait: w }, timeout: (w + 15) * 1000, retries: 0 }); },
176
+ log: (m) => process.stderr.write(`${m}\n`),
177
+ });
178
+ if (r.decision?.state !== 'answered' || r.decision?.answer?.option !== 'y') { printInfo(`Not installed: the card was not approved. Nothing was downloaded.`); process.exitCode = 2; return; }
179
+ approvalId = id;
180
+ } else if (!opts.yes) {
181
+ if (!io.isTTY()) throw new Error(`Run again with --yes to install ${t.name}, or run it in a terminal to be asked. Nothing was installed.`);
182
+ if (!(await io.confirm(offerQuestion(tool, plan)))) { printInfo('Not installed. Nothing was downloaded.'); return; }
183
+ }
184
+ printInfo(`◐ installing ${t.name} ${plan.version} on the server (this takes a minute or two)`);
185
+ const r = await runInstall(a, tool, { approvalId, onProgress: printInfo, sleep: io.sleep || undefined });
186
+ if (!r.ok) throw new Error(r.lines.join(' '));
187
+ for (const l of r.lines) printInfo(l);
188
+ });
189
+
190
+ // ── provider test ──────────────────────────────────────────────────────────
191
+ provider
192
+ .command('test')
193
+ .description('Test the stored connection for a provider (or every default connection) through the vault test')
194
+ .argument('[kind]', 'provider to test (default: every connected provider)')
195
+ .action(async (kind) => {
196
+ auth.requireAuth();
197
+ const p = kind ? providerFor(kind) : null;
198
+ if (kind && !p) throw new Error(`No provider called "${kind}". Use one of: ${KINDS.join(', ')}`);
199
+ const vault = await api().connections();
200
+ if (!vault?.ok) throw new Error(`Could not read the vault: ${vault?.error || 'no answer'}`);
201
+ const types = new Set(p ? [p.type] : Object.values(PROVIDERS).map((x) => x.type));
202
+ const rows = vault.rows.filter((r) => types.has(r.integration_type) && (r.is_default === 1 || r.is_default === true));
203
+ if (!rows.length) throw new Error(p ? `No ${p.id} connection is stored for this org. Connect one: quox provider connect ${p.id}` : 'No provider connection is stored for this org. Connect one: quox provider connect <kind>');
204
+ const orgId = resolveOrgId(auth, program);
205
+ let failed = 0;
206
+ for (const row of rows) {
207
+ const name = (Object.values(PROVIDERS).find((x) => x.type === row.integration_type) || {}).name || row.integration_type;
208
+ const ep = resolveEndpoint('vault.test', { orgId, id: row.id });
209
+ let res;
210
+ try { res = await client.post(ep.path, { service: ep.service, retries: 0 }); }
211
+ catch (e) { res = { success: false, message: e?.message || 'failed' }; }
212
+ if (res?.success === false) { failed++; printInfo(`✖ ${name} ${row.label || row.id} failed: ${res?.message || 'no reason given'}`); }
213
+ else printInfo(`✔ ${name} ${row.label || row.id} ok${res?.message ? `: ${res.message}` : ''}`);
214
+ }
215
+ if (failed) process.exitCode = 1;
216
+ });
217
+ }
@@ -49,9 +49,12 @@ function annotate(err) {
49
49
  throw err;
50
50
  }
51
51
 
52
- async function call(client, id, params = {}, body) {
52
+ // quox#907: a browser login is slower than a read; well above the client's 30s default.
53
+ const LOGIN_TIMEOUT_MS = 120000;
54
+
55
+ async function call(client, id, params = {}, body, extra = {}) {
53
56
  const ep = resolveEndpoint(id, params);
54
- const opts = { service: ep.service };
57
+ const opts = { service: ep.service, ...extra };
55
58
  if (body) opts.body = body;
56
59
  switch (ep.method) {
57
60
  case 'POST': return client.post(ep.path, opts);
@@ -634,15 +637,28 @@ export function registerQlarityCommands(program, ctx) {
634
637
  (await call(client, 'qlarity.sessions.list')).sessions || [],
635
638
  normalizedOrigin,
636
639
  );
640
+ let fresh = false;
637
641
  if (!sessionId) {
638
642
  const s = await call(client, 'qlarity.session.create', {}, {});
639
643
  sessionId = s.id;
644
+ fresh = true;
640
645
  }
641
646
 
642
647
  const body = { origin: normalizedOrigin, credential_id: credentialId };
643
648
  if (options.loginPath) body.login_path = options.loginPath;
644
649
 
645
- const r = await call(client, 'qlarity.session.login', { sessionId }, body);
650
+ // quox#907: a real login (page load, form fill, cookie wait) can outlast the CLI's 30s default on a loaded
651
+ // box, and the server kept finishing it, so every timed-out retry left a headless browser behind. Give login
652
+ // its own budget, and close a session THIS call created when the login call fails (as openSession does, #680).
653
+ let r;
654
+ try {
655
+ r = await call(client, 'qlarity.session.login', { sessionId }, body, { timeout: LOGIN_TIMEOUT_MS });
656
+ } catch (loginErr) {
657
+ if (fresh) {
658
+ try { await call(client, 'qlarity.session.close', { sessionId }); } catch { /* the server reaper will get it */ }
659
+ }
660
+ throw loginErr;
661
+ }
646
662
 
647
663
  if (r.logged_in) {
648
664
  loggedIn[normalizedOrigin] = { sessionId, logged_in_at: new Date().toISOString() };
@@ -131,11 +131,19 @@ export function registerQuoxchatCommands(program, ctx) {
131
131
  .action(async () => {
132
132
  auth.requireAuth();
133
133
  const orgId = resolveOrgId(auth, program);
134
- const data = await client.get(resolveEndpoint('quoxchat.widgets.list', { orgId }).path, { service: resolveEndpoint('quoxchat.widgets.list', { orgId }).service });
134
+ // QC-BRUTALOOP-W15: the route pages at 20; listing only page 1 silently
135
+ // under-reported the org's widgets (20 of 34). Walk every page.
136
+ const ep = resolveEndpoint('quoxchat.widgets.list', { orgId });
137
+ const rows = [];
138
+ for (let page = 1; page <= 50; page++) {
139
+ const data = await client.get(page === 1 ? ep.path : `${ep.path}?page=${page}`, { service: ep.service });
140
+ rows.push(...(Array.isArray(data) ? data : (data.items || [])));
141
+ if (Array.isArray(data) || !data.pagination?.has_more) break;
142
+ }
135
143
  formatOutput({
136
144
  columns: ['id', 'name', 'assistant_name', 'status', 'operator_online', 'created_at'],
137
145
  headers: ['ID', 'Name', 'Assistant', 'Status', 'Operator online', 'Created'],
138
- rows: data.items || data,
146
+ rows,
139
147
  }, { format: program.opts().output, emptyMessage: 'No widgets found.' });
140
148
  });
141
149
 
@@ -209,7 +217,8 @@ export function registerQuoxchatCommands(program, ctx) {
209
217
  .description('Print a conversation transcript in chronological order')
210
218
  .argument('<widgetId>', 'widget ID')
211
219
  .argument('<conversationId>', 'conversation ID')
212
- .action(async (widgetId, conversationId) => {
220
+ .option('--full', 'do not truncate long messages in table output (JSON output is never truncated)')
221
+ .action(async (widgetId, conversationId, cmdOpts = {}) => {
213
222
  auth.requireAuth();
214
223
  const orgId = resolveOrgId(auth, program);
215
224
 
@@ -236,7 +245,7 @@ export function registerQuoxchatCommands(program, ctx) {
236
245
  from: m.role === 'user' ? visitorName : m.role === 'operator' ? (m.sender_name || 'Operator') : 'AI assistant',
237
246
  content: m.content || '',
238
247
  })),
239
- }, { format: program.opts().output, emptyMessage: 'No messages in this conversation yet.' });
248
+ }, { format: program.opts().output, full: cmdOpts.full === true, emptyMessage: 'No messages in this conversation yet.' });
240
249
  });
241
250
 
242
251
  quoxchat
@@ -100,6 +100,44 @@ export function registerQuoxseoCommands(program, ctx) {
100
100
  } catch (err) { annotate(err); }
101
101
  });
102
102
 
103
+ s.command('remove-site <site>')
104
+ .description('Remove a registered site and its warehouse data for this org (governed, witnessed). <site> is an origin or property id')
105
+ .option('-y, --yes', 'skip the confirmation prompt')
106
+ .action(async (site, opts) => {
107
+ try {
108
+ const list = await get(client, 'quoxseo.sites');
109
+ const props = list.properties || [];
110
+ const norm = (v) => String(v).trim().replace(/\/+$/, '').toLowerCase();
111
+ const target = props.find((p) => String(p.id) === String(site).trim() || norm(p.origin) === norm(site));
112
+ if (!target) {
113
+ throw new Error(`Site not found: ${site} is not registered for this org. List them with: quox seo sites`);
114
+ }
115
+ const warn = `This permanently removes ${target.origin} (#${target.id}) from this org and deletes its crawl, GSC and GA4 warehouse data. This cannot be undone.`;
116
+ if (!opts.yes && program.opts().yes !== true) {
117
+ if (!process.stdin.isTTY) {
118
+ throw new Error(`${warn} Confirmation needed; re-run with --yes in a non-interactive shell: quox seo remove-site ${target.origin} --yes`);
119
+ }
120
+ console.log(warn);
121
+ const { confirmDestructive } = await import('../core/confirm.js');
122
+ const ok = await confirmDestructive(program, {}, `Remove ${target.origin}?`);
123
+ if (!ok) { console.log('Aborted. Nothing was removed.'); return; }
124
+ }
125
+ const ep = resolveEndpoint('quoxseo.sites.remove', { id: target.id });
126
+ let data;
127
+ try {
128
+ data = await client.delete(ep.path, { service: ep.service });
129
+ } catch (err) {
130
+ const code = err?.status ?? err?.statusCode ?? err?.response?.status;
131
+ if (code === 404 || code === 405 || /\b(404|405)\b|not found|read.only/i.test(err?.message || '')) {
132
+ err.message = `${err.message}\nThis install's QuoxSEO service does not support site removal (no DELETE route), or the site no longer exists. Nothing was removed.`;
133
+ }
134
+ throw err;
135
+ }
136
+ if (jsonOut(data)) return;
137
+ console.log(`removed #${target.id} ${target.origin}`);
138
+ } catch (err) { annotate(err); }
139
+ });
140
+
103
141
  s.command('urls')
104
142
  .description('Warehouse rows for the latest crawl')
105
143
  .option('--filter <f>', 'orphans | noindex | errors | no-gsc | discovered')
@@ -193,6 +231,37 @@ export function registerQuoxseoCommands(program, ctx) {
193
231
  } catch (err) { annotate(err); }
194
232
  });
195
233
 
234
+ s.command('queries')
235
+ .description('Ranked Search Console query rows: query, page, impressions, clicks, CTR, position')
236
+ .option('--sort <s>', 'impressions | clicks | position-opportunity (high impressions at position 5-20)', 'impressions')
237
+ .option('--limit <n>', 'max rows (default 25, max 100)', '25')
238
+ .option('--filter <text>', 'only queries containing this text, e.g. "conversation"')
239
+ .option('--gaps', 'topic-gap view: queries with impressions that no crawled page targets')
240
+ .action(async (opts) => {
241
+ try {
242
+ const qs = new URLSearchParams();
243
+ qs.set('sort', opts.sort);
244
+ qs.set('limit', opts.limit);
245
+ if (opts.filter) qs.set('filter', opts.filter);
246
+ if (opts.gaps) qs.set('gaps', '1');
247
+ const data = await get(client, 'quoxseo.queries.rows', { query: `?${qs}` });
248
+ if (jsonOut(data)) return;
249
+ if (!data.crawl_id) { console.log('No Search Console query data yet; the ingest with Search Console access has not run.'); return; }
250
+ const p = data.period;
251
+ const when = p && p.start ? `${p.start}..${p.end}` : 'period unknown';
252
+ const rows = data.rows || [];
253
+ if (data.view === 'topic-gaps') {
254
+ console.log(`topic gaps (impressions, no page targets the query) · ${when} · ${rows.length} of ${data.total_gaps}`);
255
+ } else {
256
+ console.log(`queries by ${data.sort}${opts.filter ? ` matching "${opts.filter}"` : ''} · ${when} · ${rows.length} of ${data.matched}`);
257
+ }
258
+ if (!rows.length) { console.log('No rows.'); return; }
259
+ for (const r of rows) {
260
+ console.log(`${r.query} | ${r.page} impr ${r.impressions} clk ${r.clicks} ctr ${(r.ctr * 100).toFixed(1)}% pos ${r.position ?? '-'}`);
261
+ }
262
+ } catch (err) { annotate(err); }
263
+ });
264
+
196
265
  s.command('coverage')
197
266
  .description("Google index coverage: what's indexed, and where Google disagrees with the page")
198
267
  .option('--filter <f>', 'indexed | not-indexed')
@@ -103,7 +103,8 @@ export function registerReachCommands(program, ctx) {
103
103
  const orgId = resolveOrgId(auth, program);
104
104
  const days = parseInt(options.days, 10) || 30;
105
105
  const ep = resolveEndpoint('reach.agent.leastPrivilege', { orgId, agentId, days });
106
- const result = await client.get(ep.path, { service: ep.service });
106
+ // #923: the report scans the usage window and outlived the 30 s default under load
107
+ const result = await client.get(ep.path, { service: ep.service, timeout: 120000 });
107
108
 
108
109
  if (isJsonOutput(program)) {
109
110
  process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
@@ -0,0 +1,79 @@
1
+ /**
2
+ * quox research domains list|add <domain>|remove <domain>
3
+ *
4
+ * The curated trusted research list: hosts the research agent may web_read after a web_search has run.
5
+ * Stored as the org setting RESEARCH_TRUSTED_DOMAINS through the SAME governed settings path as
6
+ * `quox settings set` (PATCH /api/v1/platform/settings/flags/:key, human org admin, witnessed there).
7
+ * Unset or empty means the built-in default list, which add/remove seed from so a first edit never drops it.
8
+ */
9
+
10
+ import { resolveEndpoint } from '../api/endpoints.js';
11
+
12
+ const KEY = 'RESEARCH_TRUSTED_DOMAINS';
13
+
14
+ // Mirror of services/collector/lib/researchTrustedDefaults.js DEFAULT_TRUSTED_SETTING (a test keeps them identical).
15
+ export const DEFAULT_DOMAINS = (`nist.gov,cisa.gov,cve.org,=cve.mitre.org,=cveawg.mitre.org,=www.kernel.org,=developers.cloudflare.com,=attack.mitre.org,=www.python.org,=docs.python.org,=peps.python.org,=go.dev,=tip.golang.org,` +
16
+ `=nginx.org/en/,=nginx.org/license,=www.postgresql.org/docs/,=www.postgresql.org/support/security/,=www.postgresql.org/about/news/,` +
17
+ `openssl-library.org,openssl.org,openssh.com,=kubernetes.io,msrc.microsoft.com,=www.rfc-editor.org,=datatracker.ietf.org/doc/,` +
18
+ `security-tracker.debian.org,ubuntu.com/security,github.com/advisories/ghsa-`).split(',');
19
+
20
+ const tokens = (v) => String(v || '').split(/[\s,]+/).map((t) => t.trim().toLowerCase()).filter(Boolean);
21
+
22
+ function cleanDomain(raw) {
23
+ const eq = String(raw || '').trim().startsWith('=') ? '=' : '';
24
+ const t = String(raw || '').trim().replace(/^=/, '').toLowerCase().replace(/^https?:\/\//, '').replace(/\/+$/, '');
25
+ const host = t.split('/')[0].replace(/\.+$/, '');
26
+ if (!/^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/.test(host)) return null; // IP literals and junk refused
27
+ return eq + (t.includes('/') ? host + t.slice(t.indexOf('/')) : host);
28
+ }
29
+
30
+ async function effective(client) {
31
+ const ep = resolveEndpoint('v1.settings.list');
32
+ const data = await client.get(ep.path, { service: ep.service });
33
+ const f = (data.flags || []).find((x) => x.key === KEY);
34
+ const list = tokens(f && f.value);
35
+ const isOrg = !!(f && f.source === 'setting' && list.length);
36
+ return { list: isOrg ? list : DEFAULT_DOMAINS.slice(), source: isOrg ? 'org setting' : 'built-in default' };
37
+ }
38
+
39
+ async function write(client, list) {
40
+ const ep = resolveEndpoint('v1.settings.set', { key: KEY });
41
+ return client.patch(ep.path, { service: ep.service, body: { value: list.join(',') } });
42
+ }
43
+
44
+ export function registerResearchCommands(program, ctx) {
45
+ const { client } = ctx;
46
+ const r = program.command('research').description('Governed web research settings');
47
+ const d = r.command('domains').description('Trusted research domains: hosts agents may read after a web search (org setting)');
48
+
49
+ d.command('list', { isDefault: true })
50
+ .description('Show the effective trusted research list and where it comes from')
51
+ .action(async () => {
52
+ const { list, source } = await effective(client);
53
+ process.stdout.write(`source: ${source}\n`);
54
+ for (const e of list) process.stdout.write(` ${e}\n`);
55
+ });
56
+
57
+ d.command('add <domain>')
58
+ .description('Add a domain (or host/path-prefix; a leading = means exact host only) to the org list (human org admin)')
59
+ .action(async (domain) => {
60
+ const dom = cleanDomain(domain);
61
+ if (!dom) { process.stderr.write(`Not added: "${domain}" is not a domain name (IP literals are never trusted)\n`); process.exitCode = 1; return; }
62
+ const { list } = await effective(client);
63
+ if (list.includes(dom)) { process.stdout.write(`${dom} is already on the list\n`); return; }
64
+ await write(client, [...list, dom]);
65
+ process.stdout.write(`added ${dom} (${list.length + 1} entries, org setting)\n`);
66
+ });
67
+
68
+ d.command('remove <domain>')
69
+ .description('Remove a domain from the org list (human org admin)')
70
+ .action(async (domain) => {
71
+ const dom = cleanDomain(domain);
72
+ const { list } = await effective(client);
73
+ if (!dom || !list.includes(dom)) { process.stderr.write(`Not removed: ${domain} is not on the list\n`); process.exitCode = 1; return; }
74
+ const next = list.filter((e) => e !== dom);
75
+ if (!next.length) { process.stderr.write('Not removed: that would empty the list, which means the built-in default. Add another entry first.\n'); process.exitCode = 1; return; }
76
+ await write(client, next);
77
+ process.stdout.write(`removed ${dom} (${next.length} entries, org setting)\n`);
78
+ });
79
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * `quox router` : read-only MikroTik RouterOS views via the NETWATCH tools (ROUTEROS-WIRE).
3
+ *
4
+ * quox router health CPU, memory, temperature, uptime
5
+ * quox router leases DHCP leases
6
+ * quox router pools DHCP pool usage
7
+ * quox router conflicts IP address conflicts (ARP vs DHCP leases)
8
+ * quox router free-ip [--count n] [--pool name] spare addresses for static use (reserves nothing)
9
+ * quox router vpn VPN tunnel health (WireGuard + IPsec)
10
+ * quox router wireguard WireGuard peers and whether they are connected
11
+ * quox router inventory routers and the devices connected to them
12
+ * quox router trust --show the router's current TLS fingerprint (sends NO credentials; pins nothing)
13
+ *
14
+ * Every verb is one tool call through POST /api/v1/tools/execute. No verb writes to the router.
15
+ * Engine: lib/core/router.js.
16
+ */
17
+ import { resolveOrgId } from './helpers.js';
18
+ import { ROUTER_TOOLS, ROUTER_FORMATTERS, makeRouterExec, formatTrust } from '../core/router.js';
19
+ import { resolveEndpoint } from '../api/endpoints.js';
20
+
21
+ const DESCRIPTIONS = {
22
+ health: 'Router CPU, memory, temperature and uptime (read only)',
23
+ leases: 'DHCP leases: which devices hold an address (read only)',
24
+ pools: 'DHCP pool usage: size, used, free (read only)',
25
+ conflicts: 'IP address conflicts seen by the router, ARP vs DHCP leases (read only)',
26
+ 'free-ip': 'Spare pool addresses for static assignment; reserves nothing (read only)',
27
+ vpn: 'VPN tunnel health, WireGuard and IPsec (read only)',
28
+ wireguard: 'WireGuard peers and whether each is connected (read only)',
29
+ inventory: 'MikroTik routers and the devices connected to them (read only)',
30
+ };
31
+
32
+ export function registerRouterCommands(program, ctx) {
33
+ const { client, auth } = ctx;
34
+ const json = () => program.opts().output === 'json';
35
+
36
+ const router = program
37
+ .command('router')
38
+ .description('MikroTik RouterOS read-only views via NETWATCH (health, DHCP, VPN, inventory)');
39
+
40
+ for (const verb of Object.keys(ROUTER_TOOLS)) {
41
+ const cmd = router.command(verb).description(DESCRIPTIONS[verb]);
42
+ if (verb === 'free-ip') {
43
+ cmd.option('--count <n>', 'how many addresses (1-20)', '3').option('--pool <name>', 'only this DHCP pool');
44
+ }
45
+ cmd.action(async (opts = {}) => {
46
+ auth.requireAuth();
47
+ const exec = makeRouterExec({ client, orgId: resolveOrgId(auth, program) });
48
+ const input = {};
49
+ if (verb === 'free-ip') {
50
+ const n = Number.parseInt(opts.count, 10);
51
+ if (!Number.isInteger(n) || n < 1 || n > 20) throw new Error('--count must be a whole number from 1 to 20');
52
+ input.count = n;
53
+ if (opts.pool) input.pool = String(opts.pool);
54
+ }
55
+ const p = await exec(ROUTER_TOOLS[verb], input);
56
+ if (json()) console.log(JSON.stringify(p, null, 2));
57
+ else console.log(ROUTER_FORMATTERS[verb](p).join('\n'));
58
+ });
59
+ }
60
+
61
+ // quoxai/quox#1236: the operator reads the endpoint's fingerprint here (no credentials sent),
62
+ // compares it out of band, then sets MIKROTIK_TLS_PIN themselves. This verb never pins.
63
+ router
64
+ .command('trust')
65
+ .description("Show the router's current TLS fingerprint, sending no credentials (never pins; you set MIKROTIK_TLS_PIN yourself)")
66
+ .option('--show', "print the fingerprint the router's HTTPS endpoint presents now")
67
+ .action(async (opts = {}) => {
68
+ auth.requireAuth();
69
+ if (!opts.show) {
70
+ throw new Error('quox router trust only has --show. It never pins anything: compare the fingerprint out of band, then set MIKROTIK_TLS_PIN yourself.');
71
+ }
72
+ const ep = resolveEndpoint('router.tlsFingerprint');
73
+ const data = await client.get(ep.path, { service: ep.service, query: { org_id: resolveOrgId(auth, program) } });
74
+ if (json()) { console.log(JSON.stringify(data, null, 2)); return; }
75
+ console.log(formatTrust(data).join('\n'));
76
+ });
77
+ }