quoxcli 0.0.0-stage → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (516) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +144 -2
  3. package/bin/quox.js +265 -0
  4. package/bin/quoxon.js +8 -0
  5. package/lib/api/endpoints/admin.js +151 -0
  6. package/lib/api/endpoints/aee.js +38 -0
  7. package/lib/api/endpoints/agent-attach.js +126 -0
  8. package/lib/api/endpoints/agent-schedules.js +26 -0
  9. package/lib/api/endpoints/agents.js +63 -0
  10. package/lib/api/endpoints/assistants.js +62 -0
  11. package/lib/api/endpoints/audit.js +14 -0
  12. package/lib/api/endpoints/auth.js +50 -0
  13. package/lib/api/endpoints/awareness.js +34 -0
  14. package/lib/api/endpoints/away.js +15 -0
  15. package/lib/api/endpoints/backup-schedules.js +20 -0
  16. package/lib/api/endpoints/backups.js +86 -0
  17. package/lib/api/endpoints/brain.js +140 -0
  18. package/lib/api/endpoints/budgets.js +62 -0
  19. package/lib/api/endpoints/calendarq.js +71 -0
  20. package/lib/api/endpoints/catalog.js +14 -0
  21. package/lib/api/endpoints/chat.js +61 -0
  22. package/lib/api/endpoints/consent.js +26 -0
  23. package/lib/api/endpoints/contactsq.js +68 -0
  24. package/lib/api/endpoints/conversations.js +46 -0
  25. package/lib/api/endpoints/custom-roles.js +92 -0
  26. package/lib/api/endpoints/divisions.js +56 -0
  27. package/lib/api/endpoints/domains.js +38 -0
  28. package/lib/api/endpoints/engagement.js +231 -0
  29. package/lib/api/endpoints/engine.js +11 -0
  30. package/lib/api/endpoints/entity.js +50 -0
  31. package/lib/api/endpoints/executions.js +45 -0
  32. package/lib/api/endpoints/fetchAllowlist.js +27 -0
  33. package/lib/api/endpoints/file.js +20 -0
  34. package/lib/api/endpoints/files.js +40 -0
  35. package/lib/api/endpoints/github.js +21 -0
  36. package/lib/api/endpoints/governance.js +43 -0
  37. package/lib/api/endpoints/hat.js +66 -0
  38. package/lib/api/endpoints/inbox.js +52 -0
  39. package/lib/api/endpoints/inboxq.js +120 -0
  40. package/lib/api/endpoints/index.js +242 -0
  41. package/lib/api/endpoints/invites.js +19 -0
  42. package/lib/api/endpoints/llm-policy.js +43 -0
  43. package/lib/api/endpoints/lucid.js +89 -0
  44. package/lib/api/endpoints/members.js +32 -0
  45. package/lib/api/endpoints/memory.js +94 -0
  46. package/lib/api/endpoints/mfa.js +32 -0
  47. package/lib/api/endpoints/mind.js +13 -0
  48. package/lib/api/endpoints/mon.js +184 -0
  49. package/lib/api/endpoints/notesq.js +43 -0
  50. package/lib/api/endpoints/objectives.js +68 -0
  51. package/lib/api/endpoints/orgs.js +38 -0
  52. package/lib/api/endpoints/plugins.js +152 -0
  53. package/lib/api/endpoints/powers.js +75 -0
  54. package/lib/api/endpoints/presence.js +33 -0
  55. package/lib/api/endpoints/prompts.js +69 -0
  56. package/lib/api/endpoints/qlarity.js +122 -0
  57. package/lib/api/endpoints/quoxbox.js +55 -0
  58. package/lib/api/endpoints/quoxchat.js +54 -0
  59. package/lib/api/endpoints/quoxflow.js +32 -0
  60. package/lib/api/endpoints/quoxplan.js +121 -0
  61. package/lib/api/endpoints/quoxseo.js +114 -0
  62. package/lib/api/endpoints/reach.js +47 -0
  63. package/lib/api/endpoints/router.js +17 -0
  64. package/lib/api/endpoints/runs.js +38 -0
  65. package/lib/api/endpoints/safety.js +53 -0
  66. package/lib/api/endpoints/scim.js +46 -0
  67. package/lib/api/endpoints/scopes.js +14 -0
  68. package/lib/api/endpoints/security.js +69 -0
  69. package/lib/api/endpoints/setup.js +90 -0
  70. package/lib/api/endpoints/skills.js +200 -0
  71. package/lib/api/endpoints/sso.js +67 -0
  72. package/lib/api/endpoints/stepup.js +20 -0
  73. package/lib/api/endpoints/tags.js +32 -0
  74. package/lib/api/endpoints/teams.js +56 -0
  75. package/lib/api/endpoints/telephony.js +46 -0
  76. package/lib/api/endpoints/tg.js +56 -0
  77. package/lib/api/endpoints/tools.js +50 -0
  78. package/lib/api/endpoints/traces.js +157 -0
  79. package/lib/api/endpoints/triggers.js +96 -0
  80. package/lib/api/endpoints/user-library.js +41 -0
  81. package/lib/api/endpoints/users.js +20 -0
  82. package/lib/api/endpoints/v1.js +585 -0
  83. package/lib/api/endpoints/vault.js +192 -0
  84. package/lib/api/endpoints/voice.js +22 -0
  85. package/lib/api/endpoints/volt.js +84 -0
  86. package/lib/api/endpoints/ward.js +47 -0
  87. package/lib/api/endpoints/webhooks.js +46 -0
  88. package/lib/api/endpoints/workflows.js +169 -0
  89. package/lib/api/endpoints/x.js +32 -0
  90. package/lib/api/endpoints.js +15 -0
  91. package/lib/commands/actions.js +61 -0
  92. package/lib/commands/admin.js +375 -0
  93. package/lib/commands/aee.js +99 -0
  94. package/lib/commands/agent.js +337 -0
  95. package/lib/commands/agentAttach.js +1528 -0
  96. package/lib/commands/approvals.js +119 -0
  97. package/lib/commands/aside.js +24 -0
  98. package/lib/commands/ask.js +144 -0
  99. package/lib/commands/assistant.js +169 -0
  100. package/lib/commands/auth.js +464 -0
  101. package/lib/commands/awareness.js +88 -0
  102. package/lib/commands/backup.js +377 -0
  103. package/lib/commands/brain.js +1085 -0
  104. package/lib/commands/budget.js +241 -0
  105. package/lib/commands/build.js +170 -0
  106. package/lib/commands/calendar.js +216 -0
  107. package/lib/commands/capabilities.js +34 -0
  108. package/lib/commands/chat.js +295 -0
  109. package/lib/commands/claude.js +380 -0
  110. package/lib/commands/config-cmd.js +106 -0
  111. package/lib/commands/consent.js +84 -0
  112. package/lib/commands/contacts.js +1016 -0
  113. package/lib/commands/contactsSelftest.js +132 -0
  114. package/lib/commands/conversation.js +105 -0
  115. package/lib/commands/dashboard.js +43 -0
  116. package/lib/commands/department.js +96 -0
  117. package/lib/commands/dev.js +620 -0
  118. package/lib/commands/devResident.js +326 -0
  119. package/lib/commands/devSupervisor.js +1062 -0
  120. package/lib/commands/discord.js +132 -0
  121. package/lib/commands/doctor.js +181 -0
  122. package/lib/commands/domain.js +114 -0
  123. package/lib/commands/done.js +347 -0
  124. package/lib/commands/engagement.js +597 -0
  125. package/lib/commands/entity.js +119 -0
  126. package/lib/commands/facebook.js +212 -0
  127. package/lib/commands/fetchAllowlist.js +71 -0
  128. package/lib/commands/file.js +165 -0
  129. package/lib/commands/fleet.js +1189 -0
  130. package/lib/commands/governance.js +647 -0
  131. package/lib/commands/gripe.js +355 -0
  132. package/lib/commands/hat.js +374 -0
  133. package/lib/commands/helpers.js +96 -0
  134. package/lib/commands/hyperion.js +58 -0
  135. package/lib/commands/inbox.js +173 -0
  136. package/lib/commands/instagram.js +264 -0
  137. package/lib/commands/integration.js +100 -0
  138. package/lib/commands/inventory.js +115 -0
  139. package/lib/commands/invites.js +45 -0
  140. package/lib/commands/licence.js +139 -0
  141. package/lib/commands/linkedin.js +178 -0
  142. package/lib/commands/llm-policy.js +117 -0
  143. package/lib/commands/llm.js +45 -0
  144. package/lib/commands/lucid.js +597 -0
  145. package/lib/commands/mail.js +531 -0
  146. package/lib/commands/memory.js +361 -0
  147. package/lib/commands/mind.js +71 -0
  148. package/lib/commands/mon.js +581 -0
  149. package/lib/commands/monitoring.js +86 -0
  150. package/lib/commands/motion.js +22 -0
  151. package/lib/commands/notes.js +1246 -0
  152. package/lib/commands/notesBatch.js +160 -0
  153. package/lib/commands/notesBridges.js +56 -0
  154. package/lib/commands/notesLinks.js +60 -0
  155. package/lib/commands/notification.js +57 -0
  156. package/lib/commands/nucleus.js +123 -0
  157. package/lib/commands/objective.js +240 -0
  158. package/lib/commands/offload.js +89 -0
  159. package/lib/commands/onboard.js +329 -0
  160. package/lib/commands/open.js +56 -0
  161. package/lib/commands/org.js +276 -0
  162. package/lib/commands/peek.js +28 -0
  163. package/lib/commands/plan.js +944 -0
  164. package/lib/commands/plugin-doctor.js +1091 -0
  165. package/lib/commands/plugin.js +649 -0
  166. package/lib/commands/policy.js +78 -0
  167. package/lib/commands/powers.js +296 -0
  168. package/lib/commands/presence.js +89 -0
  169. package/lib/commands/prompt.js +350 -0
  170. package/lib/commands/provider.js +159 -0
  171. package/lib/commands/qlarity.js +706 -0
  172. package/lib/commands/quoxbox.js +270 -0
  173. package/lib/commands/quoxchat.js +275 -0
  174. package/lib/commands/quoxseo.js +378 -0
  175. package/lib/commands/reach.js +159 -0
  176. package/lib/commands/role.js +286 -0
  177. package/lib/commands/router.js +77 -0
  178. package/lib/commands/rows.js +358 -0
  179. package/lib/commands/run.js +187 -0
  180. package/lib/commands/safety.js +107 -0
  181. package/lib/commands/scim.js +88 -0
  182. package/lib/commands/scope.js +58 -0
  183. package/lib/commands/security.js +157 -0
  184. package/lib/commands/server.js +292 -0
  185. package/lib/commands/service.js +89 -0
  186. package/lib/commands/settings.js +99 -0
  187. package/lib/commands/setup.js +569 -0
  188. package/lib/commands/skillManifest.js +113 -0
  189. package/lib/commands/skillRightsFormat.js +38 -0
  190. package/lib/commands/skills.js +687 -0
  191. package/lib/commands/slot.js +1451 -0
  192. package/lib/commands/sso.js +229 -0
  193. package/lib/commands/status.js +137 -0
  194. package/lib/commands/tag.js +91 -0
  195. package/lib/commands/team.js +142 -0
  196. package/lib/commands/tg.js +240 -0
  197. package/lib/commands/tiktok.js +218 -0
  198. package/lib/commands/tips.js +23 -0
  199. package/lib/commands/tool.js +304 -0
  200. package/lib/commands/trace.js +530 -0
  201. package/lib/commands/trigger.js +223 -0
  202. package/lib/commands/update.js +46 -0
  203. package/lib/commands/vault.js +1447 -0
  204. package/lib/commands/vm.js +237 -0
  205. package/lib/commands/voice.js +213 -0
  206. package/lib/commands/volt.js +570 -0
  207. package/lib/commands/walkthrough.js +32 -0
  208. package/lib/commands/watch.js +167 -0
  209. package/lib/commands/webhook.js +137 -0
  210. package/lib/commands/welcome.js +14 -0
  211. package/lib/commands/workflow.js +660 -0
  212. package/lib/commands/x.js +448 -0
  213. package/lib/core/actingAs.js +136 -0
  214. package/lib/core/activeSummary.js +186 -0
  215. package/lib/core/aee.js +53 -0
  216. package/lib/core/agentAlias.js +163 -0
  217. package/lib/core/approvalContext.js +61 -0
  218. package/lib/core/aside.js +54 -0
  219. package/lib/core/askDecision.js +60 -0
  220. package/lib/core/auth.js +501 -0
  221. package/lib/core/banner.js +143 -0
  222. package/lib/core/brainPrime.js +90 -0
  223. package/lib/core/callerSource.js +129 -0
  224. package/lib/core/claudeLs.js +94 -0
  225. package/lib/core/cliBuild.js +62 -0
  226. package/lib/core/client.js +693 -0
  227. package/lib/core/commandRegistrars.js +121 -0
  228. package/lib/core/completions.js +163 -0
  229. package/lib/core/config.js +669 -0
  230. package/lib/core/confirm.js +37 -0
  231. package/lib/core/customHats.js +168 -0
  232. package/lib/core/deeplinks.js +76 -0
  233. package/lib/core/doneEngineFetch.js +227 -0
  234. package/lib/core/errors.js +441 -0
  235. package/lib/core/global-flags.js +190 -0
  236. package/lib/core/governedSession.js +402 -0
  237. package/lib/core/hyperion.js +102 -0
  238. package/lib/core/inboxResolution.js +65 -0
  239. package/lib/core/lazyCommands.js +122 -0
  240. package/lib/core/liveSessionsReport.js +112 -0
  241. package/lib/core/offload.js +504 -0
  242. package/lib/core/org-override.js +52 -0
  243. package/lib/core/orgResolve.js +70 -0
  244. package/lib/core/output.js +239 -0
  245. package/lib/core/peek.js +126 -0
  246. package/lib/core/prodEnv.js +7 -0
  247. package/lib/core/profileUsers.js +51 -0
  248. package/lib/core/promptCancel.js +18 -0
  249. package/lib/core/quoxglyph.js +158 -0
  250. package/lib/core/repoPath.js +11 -0
  251. package/lib/core/residentAlarm.js +282 -0
  252. package/lib/core/residentAuth.js +243 -0
  253. package/lib/core/residentPump.js +229 -0
  254. package/lib/core/residentSeed.js +106 -0
  255. package/lib/core/rootOptions.js +39 -0
  256. package/lib/core/router.js +168 -0
  257. package/lib/core/selfUpdate.js +188 -0
  258. package/lib/core/sessionHeartbeat.js +192 -0
  259. package/lib/core/sessionsOverview.js +224 -0
  260. package/lib/core/slotKeeper.js +527 -0
  261. package/lib/core/slotWake.js +120 -0
  262. package/lib/core/supervisorEngine.js +933 -0
  263. package/lib/core/systemdInstaller.js +256 -0
  264. package/lib/core/version.js +87 -0
  265. package/lib/core/vm.js +361 -0
  266. package/lib/core/vmTui.js +86 -0
  267. package/lib/core/walkthrough.js +35 -0
  268. package/lib/hyperion/deck.js +29 -0
  269. package/lib/lucid/LucidCard.jsx +68 -0
  270. package/lib/lucid/LucidIntro.jsx +92 -0
  271. package/lib/lucid/blind.js +71 -0
  272. package/lib/lucid/cards.js +86 -0
  273. package/lib/lucid/deck.js +32 -0
  274. package/lib/lucid/fmt.js +139 -0
  275. package/lib/lucid/markdown.js +81 -0
  276. package/lib/lucid/motion.js +56 -0
  277. package/lib/lucid/sitrep.js +173 -0
  278. package/lib/plugins/loader.js +93 -0
  279. package/lib/quoxon/activity.js +116 -0
  280. package/lib/quoxon/aocl.js +117 -0
  281. package/lib/quoxon/api.js +952 -0
  282. package/lib/quoxon/app.js +4032 -0
  283. package/lib/quoxon/approvalFacts.js +94 -0
  284. package/lib/quoxon/approvalsView.js +325 -0
  285. package/lib/quoxon/asideView.js +40 -0
  286. package/lib/quoxon/ask.js +121 -0
  287. package/lib/quoxon/away.js +52 -0
  288. package/lib/quoxon/bang.js +224 -0
  289. package/lib/quoxon/bulkRisk.js +17 -0
  290. package/lib/quoxon/capabilities.js +82 -0
  291. package/lib/quoxon/cells.js +53 -0
  292. package/lib/quoxon/commandShort.js +112 -0
  293. package/lib/quoxon/commands.js +479 -0
  294. package/lib/quoxon/connect.js +654 -0
  295. package/lib/quoxon/connectCard.js +45 -0
  296. package/lib/quoxon/consent.js +86 -0
  297. package/lib/quoxon/context.js +68 -0
  298. package/lib/quoxon/copy.js +34 -0
  299. package/lib/quoxon/countdownLine.js +11 -0
  300. package/lib/quoxon/demo.autoplay.js +29 -0
  301. package/lib/quoxon/demo.fixtures.js +24 -0
  302. package/lib/quoxon/demo.js +76 -0
  303. package/lib/quoxon/devObserver.js +20 -0
  304. package/lib/quoxon/dividerAocl.js +191 -0
  305. package/lib/quoxon/dividerRule.js +57 -0
  306. package/lib/quoxon/dragonModes.js +150 -0
  307. package/lib/quoxon/dragonView.js +41 -0
  308. package/lib/quoxon/earlyInput.js +40 -0
  309. package/lib/quoxon/echo.js +15 -0
  310. package/lib/quoxon/egg.js +36 -0
  311. package/lib/quoxon/events.js +677 -0
  312. package/lib/quoxon/feedback.js +50 -0
  313. package/lib/quoxon/gallery.js +237 -0
  314. package/lib/quoxon/handoffOffer.js +78 -0
  315. package/lib/quoxon/hatSwitch.js +37 -0
  316. package/lib/quoxon/headless.js +161 -0
  317. package/lib/quoxon/hostUrls.js +41 -0
  318. package/lib/quoxon/indicator.js +197 -0
  319. package/lib/quoxon/intent.js +52 -0
  320. package/lib/quoxon/keys.js +164 -0
  321. package/lib/quoxon/launcher.js +103 -0
  322. package/lib/quoxon/main.js +176 -0
  323. package/lib/quoxon/markdown.js +341 -0
  324. package/lib/quoxon/markdownSettle.js +19 -0
  325. package/lib/quoxon/matrixDomains.json +46 -0
  326. package/lib/quoxon/mentions.js +90 -0
  327. package/lib/quoxon/mind.js +103 -0
  328. package/lib/quoxon/mode.js +46 -0
  329. package/lib/quoxon/model.js +311 -0
  330. package/lib/quoxon/modelPrefs.js +345 -0
  331. package/lib/quoxon/motion/clock.js +70 -0
  332. package/lib/quoxon/motion/color.js +35 -0
  333. package/lib/quoxon/motion/frame.js +14 -0
  334. package/lib/quoxon/motion/level.js +91 -0
  335. package/lib/quoxon/motion/reveal.js +54 -0
  336. package/lib/quoxon/motion/ring.js +79 -0
  337. package/lib/quoxon/mouse.js +211 -0
  338. package/lib/quoxon/noai.js +57 -0
  339. package/lib/quoxon/objects/agent.js +61 -0
  340. package/lib/quoxon/objects/away.js +72 -0
  341. package/lib/quoxon/objects/crystal.js +100 -0
  342. package/lib/quoxon/objects/fetch.js +182 -0
  343. package/lib/quoxon/objects/fleet.js +149 -0
  344. package/lib/quoxon/objects/health.js +186 -0
  345. package/lib/quoxon/objects/index.js +106 -0
  346. package/lib/quoxon/objects/ladder.js +71 -0
  347. package/lib/quoxon/objects/memory.js +44 -0
  348. package/lib/quoxon/objects/mirror.js +137 -0
  349. package/lib/quoxon/objects/model.js +118 -0
  350. package/lib/quoxon/objects/peek.js +17 -0
  351. package/lib/quoxon/objects/proof.js +110 -0
  352. package/lib/quoxon/objects/prove.js +130 -0
  353. package/lib/quoxon/objects/replay.js +125 -0
  354. package/lib/quoxon/objects/sitrep.js +58 -0
  355. package/lib/quoxon/objects/skill.js +105 -0
  356. package/lib/quoxon/objects/view.js +87 -0
  357. package/lib/quoxon/objects/workflow.js +119 -0
  358. package/lib/quoxon/observers.js +42 -0
  359. package/lib/quoxon/onboarding/copy.js +263 -0
  360. package/lib/quoxon/onboarding/credential.js +108 -0
  361. package/lib/quoxon/onboarding/detect.js +22 -0
  362. package/lib/quoxon/onboarding/dockerOffer.js +45 -0
  363. package/lib/quoxon/onboarding/failureCause.js +33 -0
  364. package/lib/quoxon/onboarding/finish.js +134 -0
  365. package/lib/quoxon/onboarding/index.js +94 -0
  366. package/lib/quoxon/onboarding/installRunner.js +284 -0
  367. package/lib/quoxon/onboarding/intent.js +53 -0
  368. package/lib/quoxon/onboarding/journey.js +73 -0
  369. package/lib/quoxon/onboarding/preflight.js +156 -0
  370. package/lib/quoxon/onboarding/probe.js +65 -0
  371. package/lib/quoxon/onboarding/runner.js +58 -0
  372. package/lib/quoxon/onboarding/view.js +682 -0
  373. package/lib/quoxon/orgLabel.js +10 -0
  374. package/lib/quoxon/orgSwitch.js +33 -0
  375. package/lib/quoxon/ownBroadcast.js +22 -0
  376. package/lib/quoxon/palette.js +43 -0
  377. package/lib/quoxon/print.js +98 -0
  378. package/lib/quoxon/profileSwitch.js +53 -0
  379. package/lib/quoxon/promptHistory.js +41 -0
  380. package/lib/quoxon/providers.js +48 -0
  381. package/lib/quoxon/qart.js +348 -0
  382. package/lib/quoxon/qdragon.js +410 -0
  383. package/lib/quoxon/qdragonFly.js +61 -0
  384. package/lib/quoxon/qdragonScenes.js +282 -0
  385. package/lib/quoxon/qeyes.js +213 -0
  386. package/lib/quoxon/qglyph.js +143 -0
  387. package/lib/quoxon/qhello.js +58 -0
  388. package/lib/quoxon/qmotion.js +132 -0
  389. package/lib/quoxon/render.js +1167 -0
  390. package/lib/quoxon/resend.js +72 -0
  391. package/lib/quoxon/roster.js +307 -0
  392. package/lib/quoxon/route.js +39 -0
  393. package/lib/quoxon/rows.js +711 -0
  394. package/lib/quoxon/rowsStore.js +80 -0
  395. package/lib/quoxon/rules.js +130 -0
  396. package/lib/quoxon/screenContext.js +150 -0
  397. package/lib/quoxon/screenFrames.js +254 -0
  398. package/lib/quoxon/session.js +238 -0
  399. package/lib/quoxon/sessionDead.js +17 -0
  400. package/lib/quoxon/signin.js +125 -0
  401. package/lib/quoxon/skills.js +30 -0
  402. package/lib/quoxon/slash/agents.js +112 -0
  403. package/lib/quoxon/slash/argSources.js +65 -0
  404. package/lib/quoxon/slash/args.js +65 -0
  405. package/lib/quoxon/slash/automate.js +89 -0
  406. package/lib/quoxon/slash/broadcast.js +140 -0
  407. package/lib/quoxon/slash/broadcastRun.js +108 -0
  408. package/lib/quoxon/slash/card.js +30 -0
  409. package/lib/quoxon/slash/challenge.js +114 -0
  410. package/lib/quoxon/slash/cliCommand.js +53 -0
  411. package/lib/quoxon/slash/cliLeaves.json +948 -0
  412. package/lib/quoxon/slash/compact.js +90 -0
  413. package/lib/quoxon/slash/confirm.js +30 -0
  414. package/lib/quoxon/slash/crystallize.js +69 -0
  415. package/lib/quoxon/slash/dashboard.js +136 -0
  416. package/lib/quoxon/slash/draft.js +54 -0
  417. package/lib/quoxon/slash/hatWalk.js +206 -0
  418. package/lib/quoxon/slash/inverses.js +77 -0
  419. package/lib/quoxon/slash/killer.js +24 -0
  420. package/lib/quoxon/slash/killerApi.js +16 -0
  421. package/lib/quoxon/slash/ledger.js +88 -0
  422. package/lib/quoxon/slash/listView.js +30 -0
  423. package/lib/quoxon/slash/modelCmd.js +261 -0
  424. package/lib/quoxon/slash/palette.js +118 -0
  425. package/lib/quoxon/slash/paletteController.js +169 -0
  426. package/lib/quoxon/slash/panels.js +116 -0
  427. package/lib/quoxon/slash/picker.js +56 -0
  428. package/lib/quoxon/slash/restart.js +202 -0
  429. package/lib/quoxon/slash/resume.js +59 -0
  430. package/lib/quoxon/slash/rowCmd.js +157 -0
  431. package/lib/quoxon/slash/rowWalk.js +175 -0
  432. package/lib/quoxon/slash/settings.js +60 -0
  433. package/lib/quoxon/slash/spawn.js +177 -0
  434. package/lib/quoxon/slash/stepUp.js +274 -0
  435. package/lib/quoxon/slash/stepUpCard.js +60 -0
  436. package/lib/quoxon/slash/stepUpCmds.js +78 -0
  437. package/lib/quoxon/slash/undoApi.js +38 -0
  438. package/lib/quoxon/slash/undoCmd.js +49 -0
  439. package/lib/quoxon/slash/undoWish.js +15 -0
  440. package/lib/quoxon/slash/watch.js +164 -0
  441. package/lib/quoxon/slash/wish.js +23 -0
  442. package/lib/quoxon/slash/wishCmd.js +32 -0
  443. package/lib/quoxon/slotChat.js +300 -0
  444. package/lib/quoxon/slotFormat.js +195 -0
  445. package/lib/quoxon/slotRun.js +131 -0
  446. package/lib/quoxon/sound/index.js +186 -0
  447. package/lib/quoxon/sound/mapping.js +120 -0
  448. package/lib/quoxon/sound/output.js +52 -0
  449. package/lib/quoxon/sound/synth.js +122 -0
  450. package/lib/quoxon/stray.js +22 -0
  451. package/lib/quoxon/suggestCommand.js +59 -0
  452. package/lib/quoxon/term.js +141 -0
  453. package/lib/quoxon/textBuffer.js +416 -0
  454. package/lib/quoxon/theme.js +286 -0
  455. package/lib/quoxon/tips.js +433 -0
  456. package/lib/quoxon/toolError.js +41 -0
  457. package/lib/quoxon/transcript.js +188 -0
  458. package/lib/quoxon/web/qface.js +89 -0
  459. package/lib/quoxon/welcome.js +52 -0
  460. package/lib/quoxon/whatsnew.js +136 -0
  461. package/lib/quoxon/whatsnew.jsonl +89 -0
  462. package/lib/quoxon/windowOrg.js +113 -0
  463. package/lib/server/diag.js +129 -0
  464. package/lib/server/doctor.js +71 -0
  465. package/lib/server/install.js +149 -0
  466. package/lib/server/pluginEnable.js +161 -0
  467. package/lib/server/skew.js +56 -0
  468. package/lib/server/snapshot.js +131 -0
  469. package/lib/server/status.js +128 -0
  470. package/lib/server/sys.js +65 -0
  471. package/lib/server/uninstall.js +63 -0
  472. package/lib/server/update.js +234 -0
  473. package/lib/toolOutputShape.js +315 -0
  474. package/lib/tui/app.jsx +289 -0
  475. package/lib/tui/components/Breadcrumb.jsx +42 -0
  476. package/lib/tui/components/ChatInput.jsx +68 -0
  477. package/lib/tui/components/Confirm.jsx +32 -0
  478. package/lib/tui/components/DataTable.jsx +270 -0
  479. package/lib/tui/components/DetailPanel.jsx +241 -0
  480. package/lib/tui/components/HelpOverlay.jsx +67 -0
  481. package/lib/tui/components/Layout.jsx +129 -0
  482. package/lib/tui/components/Palette.jsx +141 -0
  483. package/lib/tui/components/SearchInput.jsx +125 -0
  484. package/lib/tui/components/Sparkline.jsx +55 -0
  485. package/lib/tui/components/StatusBar.jsx +133 -0
  486. package/lib/tui/components/Toast.jsx +52 -0
  487. package/lib/tui/hooks/useApi.js +44 -0
  488. package/lib/tui/hooks/useHealth.js +90 -0
  489. package/lib/tui/hooks/usePolledApi.js +86 -0
  490. package/lib/tui/hooks/useTerminalSize.js +46 -0
  491. package/lib/tui/index.js +56 -0
  492. package/lib/tui/intro/Intro.jsx +332 -0
  493. package/lib/tui/jsx-loader.js +32 -0
  494. package/lib/tui/keys.js +87 -0
  495. package/lib/tui/registry.js +49 -0
  496. package/lib/tui/theme.js +103 -0
  497. package/lib/tui/views/AdminView.jsx +398 -0
  498. package/lib/tui/views/AgentView.jsx +204 -0
  499. package/lib/tui/views/BackupView.jsx +225 -0
  500. package/lib/tui/views/BrainView.jsx +223 -0
  501. package/lib/tui/views/ChatView.jsx +468 -0
  502. package/lib/tui/views/FilesView.jsx +218 -0
  503. package/lib/tui/views/FleetView.jsx +234 -0
  504. package/lib/tui/views/GovernanceView.jsx +453 -0
  505. package/lib/tui/views/HealthView.jsx +216 -0
  506. package/lib/tui/views/HyperionDeck.jsx +79 -0
  507. package/lib/tui/views/InboxView.jsx +228 -0
  508. package/lib/tui/views/LucidDeck.jsx +719 -0
  509. package/lib/tui/views/MainMenu.jsx +123 -0
  510. package/lib/tui/views/MemoryView.jsx +377 -0
  511. package/lib/tui/views/ObjectivesView.jsx +230 -0
  512. package/lib/tui/views/OrgView.jsx +229 -0
  513. package/lib/tui/views/ToolView.jsx +221 -0
  514. package/lib/tui/views/VaultView.jsx +422 -0
  515. package/lib/tui/views/WorkflowView.jsx +329 -0
  516. package/package.json +75 -4
@@ -0,0 +1,706 @@
1
+ /**
2
+ * `quox qlarity` — governed browser inspection from the terminal.
3
+ *
4
+ * House gate: nothing ships without CLI parity, and it matters more here than
5
+ * usual. Qlarity exists so an agent stops guessing at runtime state and stops
6
+ * asking a human to paste the console. An agent reaches for a CLI, not a
7
+ * dashboard panel, so this IS the primary surface and the view mirrors it.
8
+ *
9
+ * Every call goes through the collector proxy, which is the governed boundary:
10
+ * it resolves the org, enforces the licence gate and emits the AEE envelope.
11
+ * There is no direct-to-service flag, deliberately — BLUEPRINT defect 5.1 is
12
+ * exactly a standalone CLI that defaults to the ungoverned path, and this
13
+ * command was written after that finding rather than before it.
14
+ *
15
+ * The one-shot form is the one people will actually use:
16
+ *
17
+ * quox qlarity read http://localhost:5173/ --selector .card --inspector pseudo_style
18
+ *
19
+ * It opens a session, reads, prints, and closes. A leaked session is a browser
20
+ * context sitting on the server until the reaper takes it, so the convenient
21
+ * path is also the tidy one.
22
+ */
23
+
24
+ import fs from 'node:fs';
25
+ import path from 'node:path';
26
+ import os from 'node:os';
27
+ import { resolveEndpoint } from '../api/endpoints.js';
28
+ import { resolveOrgId } from './helpers.js';
29
+
30
+ /**
31
+ * Rethrow so bin/quox.js's handleError sets the house exit code (3 auth,
32
+ * 7 network, statusToExitCode otherwise). An earlier version caught
33
+ * everything and exited 1, which looked fine and quietly broke the contract
34
+ * every other command keeps: `quox plan` exits 3 on an expired session and
35
+ * this exited 1 for the same cause.
36
+ *
37
+ * The added value here is the EXPLANATION, not the exit code, so annotate and
38
+ * rethrow rather than swallow.
39
+ */
40
+ function annotate(err) {
41
+ const msg = err?.message || String(err);
42
+ if (/not licensed/i.test(msg)) {
43
+ err.message = `${msg}\nThe service is running and refusing you: that is the licence gate working, not a fault.`;
44
+ } else if (/egress_denied|not_in_allowlist/i.test(msg)) {
45
+ err.message = `${msg}\nThe origin is not on this deployment's allowlist. Default-deny is the policy; widen it deliberately.`;
46
+ } else if (/org_context_required/i.test(msg)) {
47
+ err.message = `${msg}\nNo organisation context. Run \`quox auth login\` or select an org first.`;
48
+ }
49
+ throw err;
50
+ }
51
+
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 = {}) {
56
+ const ep = resolveEndpoint(id, params);
57
+ const opts = { service: ep.service, ...extra };
58
+ if (body) opts.body = body;
59
+ switch (ep.method) {
60
+ case 'POST': return client.post(ep.path, opts);
61
+ case 'DELETE': return client.delete(ep.path, opts);
62
+ default: return client.get(ep.path, opts);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Local bookkeeping for `quox qlarity login` (issue #15): which server-side
68
+ * session, per origin, THIS CLI created and successfully logged in. This is
69
+ * deliberately client-side rather than a qlarity/collector concept — the
70
+ * server has no notion of "a session the CLI is allowed to reuse", only
71
+ * sessions and their org. A one-shot verb cross-checks an entry here against
72
+ * the server's live session list (see findReusableSessionId) before ever
73
+ * trusting it, so a stale or server-reaped entry is never silently reused.
74
+ */
75
+ function qlaritySessionsFile() {
76
+ return path.join(os.homedir(), '.local', 'share', 'quox', 'qlarity-sessions.json');
77
+ }
78
+
79
+ function readLoggedInSessions() {
80
+ try {
81
+ const parsed = JSON.parse(fs.readFileSync(qlaritySessionsFile(), 'utf8'));
82
+ return (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) ? parsed : {};
83
+ } catch {
84
+ return {};
85
+ }
86
+ }
87
+
88
+ function writeLoggedInSessions(map) {
89
+ const file = qlaritySessionsFile();
90
+ fs.mkdirSync(path.dirname(file), { recursive: true });
91
+ fs.writeFileSync(file, JSON.stringify(map, null, 2));
92
+ }
93
+
94
+ /**
95
+ * Pure matcher, exported for unit tests: given the CLI's own origin ->
96
+ * session bookkeeping and the ORG's live session list (from
97
+ * `qlarity.sessions.list`), decide which session id (if any) is safe to
98
+ * reuse for `targetOrigin`. A recorded session that the server no longer
99
+ * lists (reaped, closed, or belonging to a different org entirely) is never
100
+ * reused — that would either 404 or silently attach to the wrong context.
101
+ * @param {Record<string, {sessionId: string}>} loggedInMap
102
+ * @param {Array<{id: string}>} liveSessions
103
+ * @param {string} targetOrigin
104
+ * @returns {string|null}
105
+ */
106
+ export function findReusableSessionId(loggedInMap, liveSessions, targetOrigin) {
107
+ const entry = loggedInMap?.[targetOrigin];
108
+ if (!entry?.sessionId) return null;
109
+ const alive = Array.isArray(liveSessions) && liveSessions.some((s) => s.id === entry.sessionId);
110
+ return alive ? entry.sessionId : null;
111
+ }
112
+
113
+ /**
114
+ * The one-shot lifecycle every single-page command shares: reuse a session
115
+ * `quox qlarity login` already authenticated for this origin when one is
116
+ * still live, otherwise create a fresh one, then open the url on it.
117
+ *
118
+ * Callers are responsible for closing a FRESH session on every exit path
119
+ * (see `read` below) — that half is NOT folded in here, because a command
120
+ * that fails between open and its own work still needs the session id to
121
+ * close it, AND a reused/explicit session must never be auto-closed by a
122
+ * one-shot verb (that would silently log the caller back out). The
123
+ * `reused` flag on the return value is what a caller uses to decide.
124
+ */
125
+ async function openSession(client, url, { waitUntil, extraWaitMs, sessionId: explicitSessionId } = {}) {
126
+ let sessionId = explicitSessionId || null;
127
+ let reused = Boolean(explicitSessionId);
128
+
129
+ if (!sessionId) {
130
+ try {
131
+ const origin = new URL(url).origin;
132
+ const loggedIn = readLoggedInSessions();
133
+ if (loggedIn[origin]) {
134
+ const live = await call(client, 'qlarity.sessions.list');
135
+ const reusable = findReusableSessionId(loggedIn, live.sessions || [], origin);
136
+ if (reusable) {
137
+ sessionId = reusable;
138
+ reused = true;
139
+ } else {
140
+ // Stale bookkeeping (the session was reaped or closed server-side):
141
+ // drop it so the next one-shot does not re-check a dead session id.
142
+ delete loggedIn[origin];
143
+ writeLoggedInSessions(loggedIn);
144
+ }
145
+ }
146
+ } catch {
147
+ // Reuse is an optimisation, never a hard requirement — fall through to
148
+ // creating a fresh session exactly as before this feature existed.
149
+ }
150
+ }
151
+
152
+ if (!sessionId) {
153
+ const s = await call(client, 'qlarity.session.create', {}, {});
154
+ sessionId = s.id;
155
+ }
156
+
157
+ const openBody = { url };
158
+ // SPA pages finish rendering AFTER the load event, so a plain 'load' capture
159
+ // catches a splash/initialising screen. These pass through to qlarity's
160
+ // navigate (wait_until + extra_wait_ms) so one-shots can wait for the app.
161
+ if (waitUntil) openBody.wait_until = waitUntil;
162
+ if (extraWaitMs) openBody.extra_wait_ms = extraWaitMs;
163
+ let opened;
164
+ try {
165
+ opened = await call(client, 'qlarity.session.open', { sessionId }, openBody);
166
+ } catch (err) {
167
+ // quox#680: this throws BEFORE the caller learns the session id, so the
168
+ // caller's finally cannot close it. A FRESH session is closed right here
169
+ // (three failed opens used to fill the org's 3-session cap -> 429). A
170
+ // reused/logged-in session is left alone, same as the callers' rule.
171
+ if (!reused) {
172
+ try { await call(client, 'qlarity.session.close', { sessionId }); } catch { /* reaper will get it */ }
173
+ }
174
+ throw err;
175
+ }
176
+ return { sessionId, opened, reused };
177
+ }
178
+
179
+ /** Shared parse of the settle options common to the one-shot inspection verbs. */
180
+ function settleOpts(options) {
181
+ const out = {};
182
+ if (options.waitUntil) out.waitUntil = options.waitUntil;
183
+ if (options.settle) {
184
+ const ms = parseInt(options.settle, 10);
185
+ if (Number.isFinite(ms) && ms > 0) out.extraWaitMs = ms;
186
+ }
187
+ return out;
188
+ }
189
+
190
+ /**
191
+ * quox#735: parse `--viewport WxH` and `--device-scale-factor n` for screenshot.
192
+ * Bounds mirror qlarity's server-side check (min 320x320, max 3840x2160, dsf
193
+ * 1-3) so a bad value fails here, before a session is created. Returns the
194
+ * body fragment to merge into the screenshot request ({} when neither given).
195
+ * @throws {Error} with a plain message on invalid input
196
+ */
197
+ export function parseCaptureGeometryOptions({ viewport, deviceScaleFactor } = {}) {
198
+ const out = {};
199
+ if (viewport !== undefined) {
200
+ const m = /^(\d+)x(\d+)$/i.exec(String(viewport).trim());
201
+ if (!m) throw new Error(`Invalid --viewport "${viewport}": expected WxH, e.g. 1440x900`);
202
+ const width = parseInt(m[1], 10);
203
+ const height = parseInt(m[2], 10);
204
+ if (width < 320 || height < 320 || width > 3840 || height > 2160) {
205
+ throw new Error(`Invalid --viewport ${width}x${height}: must be between 320x320 and 3840x2160`);
206
+ }
207
+ out.viewport = { width, height };
208
+ }
209
+ if (deviceScaleFactor !== undefined) {
210
+ const n = Number(deviceScaleFactor);
211
+ if (!Number.isFinite(n) || n < 1 || n > 3) {
212
+ throw new Error(`Invalid --device-scale-factor "${deviceScaleFactor}": must be a number from 1 to 3`);
213
+ }
214
+ out.device_scale_factor = n;
215
+ }
216
+ return out;
217
+ }
218
+
219
+ /** Where one-shot screenshots land. Binary, so stdout is the wrong place. */
220
+ function screenshotDir() {
221
+ return path.join(os.homedir(), '.local', 'share', 'quox', 'qlarity-shots');
222
+ }
223
+
224
+ function screenshotFilename(url) {
225
+ let host = 'page';
226
+ try { host = new URL(url).hostname || host; } catch { /* keep default */ }
227
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
228
+ return `${host}-${ts}.png`;
229
+ }
230
+
231
+ export function registerQlarityCommands(program, ctx) {
232
+ const { client } = ctx;
233
+
234
+ const q = program
235
+ .command('qlarity')
236
+ .description('Qlarity: open a page, ask it a question, keep the receipt');
237
+
238
+ // quox#560: `read`/`console`/`network` declare their own local `--json`
239
+ // flag (for `--json` without a preceding `-o`/`--output`), which
240
+ // `mergeShadowedGlobals` (lib/core/global-flags.js) correctly hands down
241
+ // when the root `--json` shorthand is used. But the root's OTHER, equally
242
+ // canonical way to request JSON — `-o json` / `--output json`, which every
243
+ // other command in the CLI honours via `program.opts().output` — is a
244
+ // DIFFERENT attribute name (`output`, not `json`) that these subcommands
245
+ // never declare locally, so there is nothing for the merge hook to hand
246
+ // down and `-o json qlarity read ...` silently fell back to table-ish
247
+ // text output. Checking the root's resolved output format here alongside
248
+ // the local flag makes both spellings equivalent, matching every other
249
+ // command.
250
+ const wantsJson = (options) => Boolean(options.json) || program.opts().output === 'json';
251
+
252
+ q.command('health')
253
+ .description('Service health through the governed proxy')
254
+ .action(async () => {
255
+ try {
256
+ const d = await call(client, 'qlarity.health');
257
+ process.stdout.write(`\u{1F50D} QLARITY: ${d.status} — ${d.sessions} session(s), egress ${d.egress}\n`);
258
+ } catch (err) {
259
+ annotate(err);
260
+ }
261
+ });
262
+
263
+ q.command('allowlist')
264
+ .description('Which origins this deployment may inspect')
265
+ .action(async () => {
266
+ try {
267
+ const d = await call(client, 'qlarity.allowlist');
268
+ process.stdout.write(`policy: ${d.policy}\n`);
269
+ if (!d.entries?.length) {
270
+ process.stdout.write('no origins declared — nothing can be inspected (safe default)\n');
271
+ } else {
272
+ for (const e of d.entries) process.stdout.write(` ${e}\n`);
273
+ }
274
+ } catch (err) {
275
+ annotate(err);
276
+ }
277
+ });
278
+
279
+ // NO-ENV-ONLY-CONFIG (2026-08-18). `allowlist` above reports the install-wide
280
+ // env list an operator set on the host. THIS group is the one a customer can
281
+ // change themselves, which is what makes QLarity usable by its own buyer:
282
+ // before it, a new org opened QLarity, was told nothing could be inspected,
283
+ // and had no way to declare an origin without shell access.
284
+ const al = q.command('origins')
285
+ .description("The origins THIS org may inspect (add/remove without touching the host)");
286
+
287
+ al.command('list', { isDefault: true })
288
+ .description('Show this org\'s declared origins, and the install-wide ones')
289
+ .action(async () => {
290
+ try {
291
+ const d = await call(client, 'qlarity.orgAllowlist.list');
292
+ process.stdout.write(`policy: ${d.policy}\n`);
293
+ if (d.origins?.length) {
294
+ process.stdout.write('this org:\n');
295
+ for (const e of d.origins) process.stdout.write(` ${e.origin}${e.note ? ` (${e.note})` : ''}\n`);
296
+ } else {
297
+ process.stdout.write("this org: none declared yet — add one with `quox qlarity origins add <origin>`\n");
298
+ }
299
+ if (d.install_wide?.length) {
300
+ process.stdout.write('install-wide (set by the operator, applies to every org):\n');
301
+ for (const e of d.install_wide) process.stdout.write(` ${e}\n`);
302
+ }
303
+ } catch (err) {
304
+ annotate(err);
305
+ }
306
+ });
307
+
308
+ al.command('add <origin>')
309
+ .description('Declare an origin this org may inspect, e.g. https://staging.example.com')
310
+ .option('--note <text>', 'why it is allowed, shown alongside it later')
311
+ .action(async (origin, opts) => {
312
+ try {
313
+ const d = await call(client, 'qlarity.orgAllowlist.add', {}, { origin, note: opts.note });
314
+ process.stdout.write(`\u{1F50D} QLARITY: added ${d.entry.origin}\n`);
315
+ process.stdout.write(` ${d.origins.length} origin(s) now declared for this org\n`);
316
+ } catch (err) {
317
+ // A refusal here is usually a wildcard or a typo, and the server sends
318
+ // the reason. Surface it instead of a bare 400.
319
+ if (err?.status === 400 && err?.message) {
320
+ process.stderr.write(`Not added: ${err.message}\n`);
321
+ process.exitCode = 1;
322
+ return;
323
+ }
324
+ annotate(err);
325
+ }
326
+ });
327
+
328
+ al.command('remove <origin>')
329
+ .description('Stop allowing an origin')
330
+ .action(async (origin) => {
331
+ try {
332
+ const d = await call(client, 'qlarity.orgAllowlist.remove', { origin });
333
+ if (d.removed) process.stdout.write(`\u{1F50D} QLARITY: removed ${origin}\n`);
334
+ else process.stdout.write(`nothing to remove: ${origin} was not declared for this org\n`);
335
+ } catch (err) {
336
+ annotate(err);
337
+ }
338
+ });
339
+
340
+ q.command('inspectors')
341
+ .description('The named reads available (data in, never code)')
342
+ .action(async () => {
343
+ try {
344
+ const d = await call(client, 'qlarity.inspectors.list');
345
+ for (const i of d.inspectors || []) {
346
+ process.stdout.write(` ${i.name.padEnd(16)} ${i.describe}\n`);
347
+ }
348
+ } catch (err) {
349
+ annotate(err);
350
+ }
351
+ });
352
+
353
+ q.command('sessions')
354
+ .description('Open sessions for this org')
355
+ .action(async () => {
356
+ try {
357
+ const d = await call(client, 'qlarity.sessions.list');
358
+ if (!d.sessions?.length) { process.stdout.write('no open sessions\n'); return; }
359
+ for (const s of d.sessions) {
360
+ process.stdout.write(` ${s.id} ${s.url || '(no page)'} last used ${s.last_used_at}\n`);
361
+ }
362
+ } catch (err) {
363
+ annotate(err);
364
+ }
365
+ });
366
+
367
+ q.command('close <sessionId>')
368
+ .description('Close a session and free its browser context')
369
+ .action(async (sessionId) => {
370
+ try {
371
+ await call(client, 'qlarity.session.close', { sessionId });
372
+ process.stdout.write(`closed ${sessionId}\n`);
373
+ } catch (err) {
374
+ annotate(err);
375
+ }
376
+ });
377
+
378
+ /**
379
+ * The workhorse. Opens, reads, prints, closes — including on failure, so a
380
+ * bad selector does not strand a browser context.
381
+ */
382
+ q.command('read <url>')
383
+ .description('One-shot: open a page, run an inspector, print the result, close')
384
+ .option('--inspector <name>', 'named inspector', 'computed_style')
385
+ .option('--selector <css>', 'CSS selector for the inspector')
386
+ .option('--properties <list>', 'comma-separated CSS properties to narrow the result')
387
+ .option('--key <key>', 'storage key (storage inspector)')
388
+ .option('--assert <type>', 'assertion for the `expect` inspector (exists|visible|text_contains|count_equals|attribute_equals|…)')
389
+ .option('--value <value>', 'expected value for the `expect` inspector')
390
+ .option('--name <attr>', 'attribute name (attribute / expect attribute_equals inspectors)')
391
+ .option('--widths <list>', 'comma-separated viewport widths for the `responsive_sweep` inspector')
392
+ .option('--depth <n>', 'tree depth for the `dom_summary` inspector')
393
+ .option('--wait-until <cond>', 'navigation wait: load | domcontentloaded | networkidle')
394
+ .option('--settle <ms>', 'extra settle time after navigation, ms')
395
+ .option('--session <id>', 'reuse an existing session (e.g. one left open by `quox qlarity login`) instead of opening a fresh one')
396
+ .option('--json', 'raw JSON output')
397
+ .action(async (url, options) => {
398
+ let sessionId = null;
399
+ let reused = false;
400
+ try {
401
+ const { sessionId: sid, opened, reused: wasReused } = await openSession(client, url, { ...settleOpts(options), sessionId: options.session });
402
+ sessionId = sid;
403
+ reused = wasReused;
404
+
405
+ const args = {};
406
+ if (options.selector) args.selector = options.selector;
407
+ if (options.key) args.key = options.key;
408
+ if (options.properties) args.properties = options.properties.split(',').map((x) => x.trim());
409
+ // clarify-parity inspectors: expect (assert/value/name), responsive_sweep
410
+ // (widths), dom_summary (depth). Passed as data; the fixed page-side body
411
+ // lives in qlarity/src/inspectors.js.
412
+ if (options.assert) args.assert = options.assert;
413
+ if (options.value !== undefined) args.value = options.value;
414
+ if (options.name) args.name = options.name;
415
+ if (options.widths) args.widths = options.widths.split(',').map((x) => x.trim());
416
+ if (options.depth !== undefined) args.depth = parseInt(options.depth, 10);
417
+
418
+ const r = await call(client, 'qlarity.session.inspect', { sessionId },
419
+ { inspector: options.inspector, ...args });
420
+
421
+ const blocked = await call(client, 'qlarity.session.blocked', { sessionId });
422
+
423
+ if (wantsJson(options)) {
424
+ process.stdout.write(`${JSON.stringify({ page: opened, result: r.result, blocked: blocked.blocked }, null, 2)}\n`);
425
+ } else {
426
+ process.stdout.write(`\u{1F50D} QLARITY: ${opened.url} (${opened.status}) ${opened.title || ''}\n\n`);
427
+ process.stdout.write(`${options.inspector}:\n${JSON.stringify(r.result, null, 2)}\n`);
428
+ if (blocked.blocked?.length) {
429
+ // Surfaced even on success: a page that silently failed to load a
430
+ // subresource looks fine and is not.
431
+ process.stdout.write(`\nblocked by egress policy (${blocked.blocked.length}):\n`);
432
+ for (const b of blocked.blocked) process.stdout.write(` ${b.url} -> ${b.reason}\n`);
433
+ }
434
+ }
435
+ } catch (err) {
436
+ annotate(err);
437
+ } finally {
438
+ // Always, including on failure — UNLESS the session was reused (a
439
+ // `quox qlarity login` session, or an explicit --session): closing
440
+ // one of those would silently log the caller back out. A stranded
441
+ // FRESH context lives until the reaper and counts against this
442
+ // org's concurrency cap meanwhile, so those are always closed.
443
+ if (sessionId && !reused) {
444
+ try { await call(client, 'qlarity.session.close', { sessionId }); } catch { /* reaper will get it */ }
445
+ }
446
+ }
447
+ });
448
+
449
+ /**
450
+ * Console and network are read-only recorders attached at page open (see
451
+ * the server's attachRecorders), so a one-shot here needs no `inspect`
452
+ * step: open, read the ring buffer, print, close.
453
+ */
454
+ q.command('console <url>')
455
+ .description('One-shot: open a page, print its console log, close')
456
+ .option('--wait-until <cond>', 'navigation wait: load | domcontentloaded | networkidle')
457
+ .option('--settle <ms>', 'extra settle time after navigation, ms')
458
+ .option('--session <id>', 'reuse an existing session instead of opening a fresh one')
459
+ .option('--json', 'raw JSON output')
460
+ .action(async (url, options) => {
461
+ let sessionId = null;
462
+ let reused = false;
463
+ try {
464
+ const { sessionId: sid, opened, reused: wasReused } = await openSession(client, url, { ...settleOpts(options), sessionId: options.session });
465
+ sessionId = sid;
466
+ reused = wasReused;
467
+
468
+ const c = await call(client, 'qlarity.session.console', { sessionId });
469
+
470
+ if (wantsJson(options)) {
471
+ process.stdout.write(`${JSON.stringify({ page: opened, messages: c.messages, total: c.total, dropped: c.dropped }, null, 2)}\n`);
472
+ } else {
473
+ process.stdout.write(`\u{1F50D} QLARITY: ${opened.url} (${opened.status}) ${opened.title || ''}\n\n`);
474
+ process.stdout.write(`console (${c.total}):\n${JSON.stringify(c.messages, null, 2)}\n`);
475
+ if (c.dropped) {
476
+ // Reported, never hidden: a caller reading a truncated log would
477
+ // otherwise draw the wrong conclusion about what the page logged.
478
+ process.stdout.write(`\n${c.dropped} message(s) dropped (ring buffer full)\n`);
479
+ }
480
+ }
481
+ } catch (err) {
482
+ annotate(err);
483
+ } finally {
484
+ if (sessionId && !reused) {
485
+ try { await call(client, 'qlarity.session.close', { sessionId }); } catch { /* reaper will get it */ }
486
+ }
487
+ }
488
+ });
489
+
490
+ q.command('network <url>')
491
+ .description('One-shot: open a page, print its network log, close')
492
+ .option('--wait-until <cond>', 'navigation wait: load | domcontentloaded | networkidle')
493
+ .option('--settle <ms>', 'extra settle time after navigation, ms')
494
+ .option('--session <id>', 'reuse an existing session instead of opening a fresh one')
495
+ .option('--json', 'raw JSON output')
496
+ .action(async (url, options) => {
497
+ let sessionId = null;
498
+ let reused = false;
499
+ try {
500
+ const { sessionId: sid, opened, reused: wasReused } = await openSession(client, url, { ...settleOpts(options), sessionId: options.session });
501
+ sessionId = sid;
502
+ reused = wasReused;
503
+
504
+ const n = await call(client, 'qlarity.session.network', { sessionId });
505
+
506
+ if (wantsJson(options)) {
507
+ process.stdout.write(`${JSON.stringify({ page: opened, events: n.events, total: n.total, dropped: n.dropped }, null, 2)}\n`);
508
+ } else {
509
+ process.stdout.write(`\u{1F50D} QLARITY: ${opened.url} (${opened.status}) ${opened.title || ''}\n\n`);
510
+ process.stdout.write(`network (${n.total}):\n${JSON.stringify(n.events, null, 2)}\n`);
511
+ if (n.dropped) {
512
+ process.stdout.write(`\n${n.dropped} event(s) dropped (ring buffer full)\n`);
513
+ }
514
+ }
515
+ } catch (err) {
516
+ annotate(err);
517
+ } finally {
518
+ if (sessionId && !reused) {
519
+ try { await call(client, 'qlarity.session.close', { sessionId }); } catch { /* reaper will get it */ }
520
+ }
521
+ }
522
+ });
523
+
524
+ /**
525
+ * Screenshots come back as base64 JSON (see qlarity's app.js: the digest
526
+ * is what makes an image witnessable, and the governed proxy assumes
527
+ * JSON), so this writes the decoded bytes to a file and prints the path —
528
+ * stdout is the wrong place for binary.
529
+ */
530
+ q.command('screenshot <url>')
531
+ .description('One-shot: open a page, capture a screenshot, save it to a file, close')
532
+ .option('--selector <css>', 'CSS selector to capture a single element instead of the viewport')
533
+ .option('--full-page', 'capture the full scrollable page, not just the viewport')
534
+ .option('--wait-until <cond>', "navigation wait: load | domcontentloaded | networkidle (SPAs want networkidle)")
535
+ .option('--settle <ms>', 'extra settle time after navigation, ms (for SPAs that hydrate after load)')
536
+ .option('--wait-for <css>', 'wait for this element to be visible before the shot (SPAs paint after load; capped 10s)')
537
+ .option('--wait-ms <n>', 'wait this many ms right before the shot, capped 10s (prefer --wait-for when you know the element)', (v) => parseInt(v, 10))
538
+ .option('--no-reduced-motion', 'do NOT emulate prefers-reduced-motion (default emulates it so animated heroes/reveals render for the capture)')
539
+ .option('--viewport <WxH>', 'capture viewport for this call, e.g. 1440x900 or 390x844 (320x320 to 3840x2160; default 1280x720)')
540
+ .option('--device-scale-factor <n>', 'device pixel ratio for this call, 1 to 3 (default 1)')
541
+ .option('--session <id>', 'reuse an existing session instead of opening a fresh one')
542
+ .action(async (url, options) => {
543
+ let sessionId = null;
544
+ let reused = false;
545
+ try {
546
+ // Validate before any session exists: nothing to leak on a typo.
547
+ let geometry;
548
+ try {
549
+ geometry = parseCaptureGeometryOptions(options);
550
+ } catch (e) {
551
+ process.stderr.write(`${e.message}\n`);
552
+ process.exitCode = 2;
553
+ return;
554
+ }
555
+ const { sessionId: sid, opened, reused: wasReused } = await openSession(client, url, { ...settleOpts(options), sessionId: options.session });
556
+ sessionId = sid;
557
+ reused = wasReused;
558
+
559
+ const body = { ...geometry };
560
+ if (options.selector) body.selector = options.selector;
561
+ if (options.fullPage) body.full_page = true;
562
+ if (options.waitFor) body.wait_for = options.waitFor;
563
+ if (Number.isFinite(options.waitMs)) body.wait_ms = options.waitMs;
564
+ // commander sets reducedMotion=false only when --no-reduced-motion is passed.
565
+ if (options.reducedMotion === false) body.reduced_motion = false;
566
+
567
+ const shot = await call(client, 'qlarity.session.screenshot', { sessionId }, body);
568
+
569
+ const dir = screenshotDir();
570
+ fs.mkdirSync(dir, { recursive: true });
571
+ const file = path.join(dir, screenshotFilename(opened.url || url));
572
+ fs.writeFileSync(file, Buffer.from(shot.data, 'base64'));
573
+
574
+ process.stdout.write(`\u{1F50D} QLARITY: ${opened.url} (${opened.status}) ${opened.title || ''}\n`);
575
+ process.stdout.write(`saved ${shot.bytes} bytes to ${file}\n`);
576
+ process.stdout.write(`sha256 ${shot.sha256}\n`);
577
+ if (shot.viewport) {
578
+ process.stdout.write(`viewport ${shot.viewport.width}x${shot.viewport.height} @${shot.device_scale_factor ?? 1}x\n`);
579
+ }
580
+ } catch (err) {
581
+ annotate(err);
582
+ } finally {
583
+ if (sessionId && !reused) {
584
+ try { await call(client, 'qlarity.session.close', { sessionId }); } catch { /* reaper will get it */ }
585
+ }
586
+ }
587
+ });
588
+
589
+ q.command('eval <url> <script>')
590
+ .description('Request approval to run arbitrary JS. Files an inbox item and exits; never runs inline.')
591
+ .action(async (url, script) => {
592
+ let sessionId = null;
593
+ try {
594
+ const { sessionId: sid } = await openSession(client, url);
595
+ sessionId = sid;
596
+ const r = await call(client, 'qlarity.session.eval', { sessionId }, { script });
597
+ process.stdout.write(`\u{1F50D} QLARITY: ${r.status} — inbox item ${r.inbox_item_id}\n`);
598
+ process.stdout.write('Arbitrary evaluation is always a human approval. Nothing has run.\n');
599
+ } catch (err) {
600
+ annotate(err);
601
+ } finally {
602
+ // The session is NOT closed here: the approved script needs it. The
603
+ // reaper reclaims it if nobody approves.
604
+ if (sessionId) process.stdout.write(`session ${sessionId} left open for the approved run\n`);
605
+ }
606
+ });
607
+
608
+ /**
609
+ * Governed authed-session bootstrap (issue #15). Only ever sends a
610
+ * `credential_id` to the collector — never a raw password — and the
611
+ * collector resolves it server-side against a stored `browser_login`
612
+ * vault credential (see server.js's resolveQlarityLoginCredential). The
613
+ * session is deliberately kept open on success and recorded locally so
614
+ * `read`/`console`/`network`/`screenshot` one-shots against the SAME
615
+ * origin pick it up automatically instead of opening a fresh, logged-out
616
+ * one (see openSession's reuse check above).
617
+ */
618
+ q.command('login <origin>')
619
+ .description('Log in to <origin> via a stored browser_login vault credential, leaving the session open for other qlarity verbs to reuse')
620
+ .requiredOption('--credential <idOrName>', 'vault credential id or label (must be a browser_login credential)')
621
+ .option('--login-path <path>', 'login form path, if the credential does not already declare one')
622
+ .action(async (origin, options) => {
623
+ let normalizedOrigin;
624
+ try {
625
+ normalizedOrigin = new URL(origin).origin;
626
+ } catch {
627
+ process.stderr.write(`Not a valid origin: ${origin}\n`);
628
+ process.exitCode = 1;
629
+ return;
630
+ }
631
+ try {
632
+ const credentialId = await resolveBrowserLoginCredentialId(ctx, options.credential);
633
+
634
+ const loggedIn = readLoggedInSessions();
635
+ let sessionId = findReusableSessionId(
636
+ loggedIn,
637
+ (await call(client, 'qlarity.sessions.list')).sessions || [],
638
+ normalizedOrigin,
639
+ );
640
+ let fresh = false;
641
+ if (!sessionId) {
642
+ const s = await call(client, 'qlarity.session.create', {}, {});
643
+ sessionId = s.id;
644
+ fresh = true;
645
+ }
646
+
647
+ const body = { origin: normalizedOrigin, credential_id: credentialId };
648
+ if (options.loginPath) body.login_path = options.loginPath;
649
+
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
+ }
662
+
663
+ if (r.logged_in) {
664
+ loggedIn[normalizedOrigin] = { sessionId, logged_in_at: new Date().toISOString() };
665
+ writeLoggedInSessions(loggedIn);
666
+ process.stdout.write(`\u{1F50D} QLARITY: logged in to ${normalizedOrigin} — session ${sessionId} kept open for reuse\n`);
667
+ process.stdout.write(`evidence: ${r.evidence}\n`);
668
+ } else {
669
+ // Do NOT record this session as reusable: a failed login left
670
+ // open and later "reused" would silently serve a logged-out page.
671
+ process.stdout.write(`\u{1F50D} QLARITY: no new session cookie appeared for ${normalizedOrigin} — login likely failed\n`);
672
+ process.stdout.write(`session ${sessionId} left open for inspection (not marked reusable); close it with \`quox qlarity close ${sessionId}\` when done\n`);
673
+ process.exitCode = 1;
674
+ }
675
+ } catch (err) {
676
+ annotate(err);
677
+ }
678
+ });
679
+
680
+ return q;
681
+ }
682
+
683
+ /**
684
+ * Resolve a `--credential` value (a vault credential id, OR a case-
685
+ * insensitive match on its label) to a credential id, scoped to
686
+ * `browser_login` credentials only — the same integration type
687
+ * server.js's resolveQlarityLoginCredential requires.
688
+ */
689
+ async function resolveBrowserLoginCredentialId({ client, auth, program }, idOrName) {
690
+ const orgId = resolveOrgId(auth, program);
691
+ const ep = resolveEndpoint('vault.list', { orgId });
692
+ const data = await client.get(ep.path, { service: ep.service, query: { type: 'browser_login' } });
693
+ const rows = data.credentials || data || [];
694
+ const match = rows.find((r) => r.id === idOrName)
695
+ || rows.find((r) => (r.label || '').toLowerCase() === String(idOrName).toLowerCase());
696
+ if (!match) {
697
+ const err = new Error(
698
+ `No browser_login credential matched "${idOrName}". Run \`quox vault list --type browser_login\` to see stored credentials.`,
699
+ );
700
+ err.status = 404;
701
+ throw err;
702
+ }
703
+ return match.id;
704
+ }
705
+
706
+ export default registerQlarityCommands;