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,501 @@
1
+ /**
2
+ * Auth module for QuoxCLI
3
+ *
4
+ * Handles login, logout, token storage, and auto-refresh.
5
+ * Tokens stored in config profile's auth field.
6
+ */
7
+
8
+ import { readFileSync, statSync, openSync, closeSync, unlinkSync } from 'node:fs';
9
+ import { QuoxAuthError, QuoxApiError, QuoxTransientAuthError } from './errors.js';
10
+ import { loadResidentProfile, residentLogin, resolveResidentBaseUrl } from './residentAuth.js';
11
+
12
+ // A resident agent has no refresh token: its expired-access-token seam is
13
+ // re-running the signed challenge exchange, not demanding `quox login`,
14
+ // since there is no human at the terminal to run it. Kept as a standalone helper
15
+ // so createAuth()'s refresh() stays the single seam client.js already calls
16
+ // on a 401, per quox#453 phase 15.
17
+ async function refreshResident(config, baseUrl, storedAuth) {
18
+ let profile;
19
+ try {
20
+ profile = loadResidentProfile(storedAuth.resident_profile_path);
21
+ } catch (err) {
22
+ throw new QuoxAuthError(`Resident re-authentication failed: ${err.message}`);
23
+ }
24
+
25
+ // The caller passes getServiceUrl('auth'), which can port-swap to
26
+ // loopback :3101 and be unreachable from a remote host, so the resident
27
+ // rules (bootstrap instance_url / explicit profile URL) come FIRST. But
28
+ // edge-only was its own outage (2026-08-23, both residents): on the
29
+ // control box the edge is self-signed https, the systemd unit has no CA
30
+ // trust, and the one URL that does work there is exactly the
31
+ // services-aware one the caller passed (plain-http loopback auth). Try
32
+ // the resident-rules URL, then fall back to the caller's, and only die
33
+ // if both refuse. An unattended runner must exhaust its options before
34
+ // giving up: Option A persistence depends on this.
35
+ const candidates = [...new Set([resolveResidentBaseUrl(config, profile), baseUrl].filter(Boolean))];
36
+
37
+ let tokenData;
38
+ const failures = [];
39
+ const errs = [];
40
+ for (const candidate of candidates) {
41
+ try {
42
+ tokenData = await residentLogin({ baseUrl: candidate, profile });
43
+ break;
44
+ } catch (err) {
45
+ failures.push(`${candidate}: ${err.message}`);
46
+ errs.push(err);
47
+ }
48
+ }
49
+ if (!tokenData) {
50
+ // Every candidate failed for a reason that clears by itself (429, 5xx,
51
+ // connection refused/reset/timeout): surface a typed transient error so a
52
+ // long-lived listener can back off instead of dying.
53
+ const isTransient = (e) => e instanceof QuoxTransientAuthError
54
+ || (!(e instanceof QuoxAuthError) && /fetch failed|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENOTFOUND|socket hang up|timed? ?out/i.test(`${e?.message} ${e?.cause?.code || ''} ${e?.cause?.message || ''}`));
55
+ if (errs.length > 0 && errs.every(isTransient)) {
56
+ const waits = errs.map((e) => e.retryAfterMs).filter((n) => Number.isFinite(n));
57
+ throw new QuoxTransientAuthError(`Resident re-authentication failed: ${failures.join('; ')}`, waits.length ? Math.max(...waits) : null);
58
+ }
59
+ throw new QuoxAuthError(`Resident re-authentication failed: ${failures.join('; ')}`);
60
+ }
61
+
62
+ const authData = {
63
+ ...storedAuth,
64
+ access_token: tokenData.access_token,
65
+ user_id: tokenData.user?.id ?? storedAuth.user_id,
66
+ email: tokenData.user?.email ?? storedAuth.email,
67
+ org_role: tokenData.user?.role ?? storedAuth.org_role,
68
+ actor_type: tokenData.user?.actor_type ?? storedAuth.actor_type,
69
+ host_id: tokenData.user?.host_id ?? storedAuth.host_id,
70
+ expires_at: Date.now() + (tokenData.expires_in || 900) * 1000,
71
+ };
72
+ config.setAuth(authData, { refresh: true });
73
+ return tokenData;
74
+ }
75
+
76
+ // When a password-backed session dead-ends (revoked or expired) the only
77
+ // recovery is an interactive `quox login`, which an unattended agent cannot
78
+ // run. A resident profile, if one is configured, IS that recovery: durable,
79
+ // passwordless, self-renewing. Build a one-line pointer to it, preferring a
80
+ // resident on the same org as the profile that just died. Returns '' when no
81
+ // resident profile exists (nothing useful to suggest). Never throws — a
82
+ // hint-builder must not become a new failure seam in the auth path.
83
+ function residentProfileHint(config, deadOrgId = null) {
84
+ try {
85
+ const residents = typeof config.listResidentProfiles === 'function'
86
+ ? config.listResidentProfiles()
87
+ : [];
88
+ if (!residents.length) return '';
89
+ const sameOrg = deadOrgId ? residents.find(r => r.orgId === deadOrgId) : null;
90
+ const pick = sameOrg || residents[0];
91
+ return ` For unattended automation use the durable resident profile instead: `
92
+ + `\`quox -p ${pick.name} <command>\` (Ed25519, no password, self-renews — never needs re-login).`;
93
+ } catch {
94
+ return '';
95
+ }
96
+ }
97
+
98
+ // QUOX_TOKEN_FILE: the W4 builder principal (SDK-TEAM design v2, 2.1.3) runs
99
+ // on ONE agent token that an owner-side helper writes to a file and rewrites
100
+ // near expiry. The builder holds no refresh token, no user session and no
101
+ // service key, so this mode is exclusive and fails CLOSED: the file's token or
102
+ // nothing, never the profile, never an ambient QUOX_SERVICE_KEY. Its "refresh"
103
+ // is re-reading the file; it never calls the auth service and never writes or
104
+ // clears the profile.
105
+ function tokenFilePath() {
106
+ return process.env.QUOX_TOKEN_FILE || null;
107
+ }
108
+
109
+ function readTokenFile(file) {
110
+ let st;
111
+ try {
112
+ st = statSync(file);
113
+ } catch {
114
+ throw new QuoxAuthError(`QUOX_TOKEN_FILE ${file} is not readable. The owner-side builder helper writes it; is the lease live?`);
115
+ }
116
+ if (st.mode & 0o004) {
117
+ throw new QuoxAuthError(`QUOX_TOKEN_FILE ${file} is readable by other users (mode ${(st.mode & 0o777).toString(8)}); refusing to use it. Expected 0640 or tighter.`);
118
+ }
119
+ const token = readFileSync(file, 'utf8').trim();
120
+ if (!token) throw new QuoxAuthError(`QUOX_TOKEN_FILE ${file} is empty.`);
121
+ return { access_token: token, expires_at: jwtExpiryMs(token), token_file: file };
122
+ }
123
+
124
+ // Expiry only, for the client's "looks expired, try the refresh seam" signal.
125
+ // Not verification: the server verifies. Undecodable means no local expiry.
126
+ function jwtExpiryMs(token) {
127
+ try {
128
+ const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString('utf8'));
129
+ return typeof payload.exp === 'number' ? payload.exp * 1000 : null;
130
+ } catch {
131
+ return null;
132
+ }
133
+ }
134
+
135
+ /** Keep the last login email on the profile (not a secret) so a signed-out session can prefill /login. */
136
+ export function rememberEmail(config, email) {
137
+ try {
138
+ const prof = config.getActiveProfile?.();
139
+ if (prof && email && prof.last_email !== email) { prof.last_email = email; config.save?.(); }
140
+ } catch { /* best-effort */ }
141
+ }
142
+
143
+ // Cross-process refresh lock (wx file next to the credentials). Returns a release function. Never throws:
144
+ // after the wait it proceeds unlocked, because a stuck lock must not sign anyone out.
145
+ async function acquireRefreshLock(file, { staleMs = 20000, waitMs = 20000 } = {}) {
146
+ const deadline = Date.now() + waitMs;
147
+ for (;;) {
148
+ try {
149
+ closeSync(openSync(file, 'wx', 0o600));
150
+ return () => { try { unlinkSync(file); } catch { /* already gone */ } };
151
+ } catch (e) {
152
+ if (e.code !== 'EEXIST') return () => {};
153
+ try {
154
+ if (Date.now() - statSync(file).mtimeMs > staleMs) { try { unlinkSync(file); } catch { /* raced */ } continue; }
155
+ } catch { continue; }
156
+ if (Date.now() > deadline) return () => {};
157
+ await new Promise(r => setTimeout(r, 25));
158
+ }
159
+ }
160
+ }
161
+
162
+ export function createAuth(config) {
163
+ return {
164
+ async login(baseUrl, email, password, orgId) {
165
+ if (tokenFilePath()) {
166
+ throw new QuoxAuthError('QUOX_TOKEN_FILE is set: this principal runs on its issued token only and cannot log in as a user.');
167
+ }
168
+ const url = `${baseUrl}/api/auth/login`;
169
+ // client: 'cli' marks this a CLI/TUI sign-in so the server gives it the longer CLI session window.
170
+ const body = { email, password, client: 'cli' };
171
+ if (orgId) body.org_id = orgId;
172
+
173
+ const res = await fetch(url, {
174
+ method: 'POST',
175
+ headers: { 'Content-Type': 'application/json' },
176
+ body: JSON.stringify(body),
177
+ });
178
+
179
+ const data = await res.json();
180
+
181
+ if (!res.ok) {
182
+ throw new QuoxApiError(res.status, data.error, data.message || 'Login failed');
183
+ }
184
+
185
+ // Store auth data
186
+ const authData = {
187
+ access_token: data.access_token,
188
+ refresh_token: data.refresh_token,
189
+ user_id: data.user?.id,
190
+ email: data.user?.email,
191
+ display_name: data.user?.display_name,
192
+ org_id: data.org?.id || null,
193
+ org_name: data.org?.name || null,
194
+ org_slug: data.org?.slug || null,
195
+ org_role: data.org?.role || null,
196
+ mfa_required: data.mfa_required,
197
+ expires_at: Date.now() + (data.expires_in || 900) * 1000,
198
+ };
199
+
200
+ config.setAuth(authData);
201
+ rememberEmail(config, email);
202
+ return { ...data, authData };
203
+ },
204
+
205
+ async verifyMfa(baseUrl, token, code) {
206
+ const url = `${baseUrl}/api/mfa/verify`;
207
+ const res = await fetch(url, {
208
+ method: 'POST',
209
+ headers: {
210
+ 'Content-Type': 'application/json',
211
+ 'Authorization': `Bearer ${token}`,
212
+ },
213
+ body: JSON.stringify({ code }),
214
+ });
215
+
216
+ const data = await res.json();
217
+ if (!res.ok) {
218
+ throw new QuoxApiError(res.status, data.error, data.message || 'MFA verification failed');
219
+ }
220
+
221
+ // Update stored token if new one issued
222
+ if (data.access_token) {
223
+ const auth = config.getAuth();
224
+ auth.access_token = data.access_token;
225
+ auth.mfa_required = false;
226
+ if (data.expires_in) auth.expires_at = Date.now() + data.expires_in * 1000;
227
+ config.setAuth(auth);
228
+ }
229
+
230
+ return data;
231
+ },
232
+
233
+ async logout(baseUrl) {
234
+ const auth = config.getAuth();
235
+ if (auth?.access_token) {
236
+ try {
237
+ await fetch(`${baseUrl}/api/auth/logout`, {
238
+ method: 'POST',
239
+ headers: { 'Authorization': `Bearer ${auth.access_token}` },
240
+ });
241
+ } catch {
242
+ // Best-effort server-side logout
243
+ }
244
+ }
245
+ config.clearAuth();
246
+ },
247
+
248
+ /**
249
+ * Refresh the session. Stale-sign-in safe: serialised across processes by a lock file next to the
250
+ * credentials, and it adopts credentials another window or `quox login` wrote since this process
251
+ * loaded. A fresh token on disk is reused with no server call (so two windows never spend one
252
+ * rotating refresh token twice); a rejected refresh re-reads disk and retries ONCE if it changed.
253
+ */
254
+ async refresh(baseUrl) {
255
+ if (tokenFilePath() || typeof config.reloadAuth !== 'function' || typeof config.refreshLockFile !== 'function') {
256
+ return this._refreshOnce(baseUrl);
257
+ }
258
+ const lock = await acquireRefreshLock(config.refreshLockFile());
259
+ try {
260
+ if (config.reloadAuth() && config.getAuth()?.access_token && !this.isExpired()) {
261
+ return { access_token: config.getAuth().access_token, reused: true };
262
+ }
263
+ const sent = config.getAuth()?.refresh_token;
264
+ try {
265
+ return await this._refreshOnce(baseUrl);
266
+ } catch (e) {
267
+ if (e?.code === 'session_expired') {
268
+ config.reloadAuth();
269
+ const now = config.getAuth();
270
+ if (now?.refresh_token && now.refresh_token !== sent) {
271
+ if (!this.isExpired()) return { access_token: now.access_token, reused: true };
272
+ return await this._refreshOnce(baseUrl);
273
+ }
274
+ }
275
+ throw e;
276
+ }
277
+ } finally {
278
+ lock();
279
+ }
280
+ },
281
+
282
+ async _refreshOnce(baseUrl) {
283
+ const tokenFile = tokenFilePath();
284
+ if (tokenFile) {
285
+ const before = this._tokenFileLast;
286
+ const now = readTokenFile(tokenFile);
287
+ const stillExpired = now.expires_at && Date.now() > now.expires_at;
288
+ if (stillExpired || (before && now.access_token === before)) {
289
+ throw new QuoxAuthError(`QUOX_TOKEN_FILE ${tokenFile} has not been renewed; the owner-side helper re-mints it. Not falling back to any other credential.`);
290
+ }
291
+ this._tokenFileLast = now.access_token;
292
+ return { access_token: now.access_token };
293
+ }
294
+ const auth = config.getAuth();
295
+
296
+ // Resident agents authenticate via a signed challenge, not a refresh
297
+ // token; see refreshResident() above.
298
+ if (auth?.kind === 'resident') {
299
+ return refreshResident(config, baseUrl, auth);
300
+ }
301
+
302
+ if (!auth?.refresh_token) {
303
+ throw new QuoxAuthError('No refresh token. Please login again.');
304
+ }
305
+
306
+ // The token goes in the BODY. POST /api/auth/refresh reads
307
+ // `req.cookies.refresh_token || req.body.refresh_token` and never looks
308
+ // at the Authorization header, so sending it only as a Bearer header
309
+ // meant the server saw no token at all: it answered `no_refresh_token`,
310
+ // /health answered OK, and the clearing branch below destroyed a
311
+ // perfectly good session. That is the "logged in, then Session expired
312
+ // twenty minutes later" outage, logged three separate times before
313
+ // anyone read the transport rather than the error text. Verified live
314
+ // 2026-08-24: header -> no_refresh_token, body -> invalid_refresh_token.
315
+ // The header is kept because it costs nothing and other deployments may
316
+ // read it.
317
+ // QUOXON-FW12: a timeout or network error says nothing about the token.
318
+ // It is TRANSIENT (tokens kept, caller backs off and retries), never
319
+ // "signed out". 2026-10-14 02:50: a slow auth service signed the owner out
320
+ // of a session the server still held active.
321
+ let res;
322
+ try {
323
+ res = await fetch(`${baseUrl}/api/auth/refresh`, {
324
+ method: 'POST',
325
+ headers: {
326
+ 'Content-Type': 'application/json',
327
+ 'Authorization': `Bearer ${auth.refresh_token}`,
328
+ },
329
+ body: JSON.stringify({ refresh_token: auth.refresh_token }),
330
+ signal: AbortSignal.timeout(15000),
331
+ });
332
+ } catch (e) {
333
+ throw new QuoxTransientAuthError(`Quox server is slow or unreachable (${e?.name === 'TimeoutError' ? 'refresh timed out' : (e?.cause?.code || e?.message || 'network error')}). Stored session KEPT; retrying.`);
334
+ }
335
+
336
+ let data = null;
337
+ try { data = await res.json(); } catch { /* non-JSON error body */ }
338
+ if (!res.ok) {
339
+ // Only a DEFINITIVE rejection (401/403) means the refresh token is
340
+ // dead. A 5xx or gateway hiccup must NOT destroy the stored session:
341
+ // on 2026-08-15 a wiped profile here erased the only copy of a
342
+ // rejected refresh token before anyone could diagnose WHY the auth
343
+ // service rejected it.
344
+ // `no_refresh_token` means the REQUEST carried nothing, not that the
345
+ // stored token is dead. Clearing on it turns a client-side transport
346
+ // fault into a lost session, which is exactly how the header/body bug
347
+ // above cost three re-logins in one night before it was found.
348
+ if ((data && data.error) === 'no_refresh_token') {
349
+ throw new QuoxTransientAuthError(
350
+ 'The auth service received no refresh token, so it could not tell whether yours is valid. '
351
+ + 'Stored session KEPT. This is a client/transport fault, not an expiry — retry, and report it if it persists.'
352
+ );
353
+ }
354
+
355
+ if (res.status === 401 || res.status === 403) {
356
+ // A 401 from refresh means one of TWO very different things, and
357
+ // destroying the session on the wrong one costs the user their login.
358
+ //
359
+ // (a) the refresh token really is dead -> clearing is correct
360
+ // (b) the auth service is not READY yet -> clearing is data loss
361
+ //
362
+ // 2026-08-19: rebuilding the auth container logged out every CLI
363
+ // session, twice in one evening, because a starting service answers
364
+ // 401 before it can answer properly. For an autonomous agent that is
365
+ // a hard stop mid-task; for a customer it is a surprise re-login
366
+ // after any routine restart. The old comment here already refused to
367
+ // clear on a 5xx for the same reason — this is the same hazard one
368
+ // status code over.
369
+ //
370
+ // So ask the service whether it is healthy before believing it about
371
+ // the token. Unreachable or unhealthy means we learned nothing about
372
+ // the token, and a session is far more expensive to lose than a
373
+ // command is to retry.
374
+ let healthy = false;
375
+ try {
376
+ const h = await fetch(`${baseUrl}/health`, { signal: AbortSignal.timeout(3000) });
377
+ healthy = h.ok;
378
+ } catch {
379
+ healthy = false;
380
+ }
381
+ if (!healthy) {
382
+ throw new QuoxTransientAuthError(
383
+ `Auth service is not healthy right now (refresh returned ${res.status} but /health did not answer OK). ` +
384
+ `Stored session KEPT — this is very likely a restart or deploy in progress. Retry in a moment.`
385
+ );
386
+ }
387
+ config.clearAuth({ refresh: true });
388
+ const dead = new QuoxAuthError(
389
+ `Session expired (refresh rejected: ${(data && data.error) || res.status}). Please login again.`
390
+ + residentProfileHint(config, auth?.org_id),
391
+ );
392
+ // Machine-readable, so the TUI can say WHY (idle_timeout | max_session, from the server's session policy).
393
+ dead.code = 'session_expired';
394
+ // A revoke carries no `reason`; keep the error so the card can still say why (SIGNOUT-WIPE).
395
+ dead.reason = (data && data.reason) || ((data && data.error) === 'session_revoked' ? 'session_revoked' : null);
396
+ throw dead;
397
+ }
398
+ // 5xx / 429 / anything not a definitive 401/403: the token was never judged.
399
+ const ra = Number(res.headers?.get?.('retry-after'));
400
+ throw new QuoxTransientAuthError(`Token refresh failed (${res.status}); stored session kept. Retry, or login again if this persists.`, Number.isFinite(ra) && ra > 0 ? ra * 1000 : null);
401
+ }
402
+
403
+ auth.access_token = data.access_token;
404
+ // The route does not rotate the refresh token today. If it ever starts,
405
+ // silently dropping the new one would make the NEXT refresh fail and
406
+ // clear the session: the same outage with a different first cause.
407
+ if (data.refresh_token) auth.refresh_token = data.refresh_token;
408
+ if (data.expires_in) auth.expires_at = Date.now() + data.expires_in * 1000;
409
+ config.setAuth(auth, { refresh: true });
410
+ return data;
411
+ },
412
+
413
+ getTokens() {
414
+ const tokenFile = tokenFilePath();
415
+ if (tokenFile) {
416
+ const t = readTokenFile(tokenFile);
417
+ this._tokenFileLast = t.access_token;
418
+ return t;
419
+ }
420
+ return config.getAuth();
421
+ },
422
+
423
+ isLoggedIn() {
424
+ const tokenFile = tokenFilePath();
425
+ if (tokenFile) {
426
+ try { readTokenFile(tokenFile); return true; } catch { return false; }
427
+ }
428
+ // Precedence, matching client.js: a stored USER token is what we
429
+ // actually authenticate with, so it decides this answer. A configured
430
+ // QUOX_SERVICE_KEY still counts as authenticated on its own, but only
431
+ // as the automation/daemon fallback when nobody has logged in.
432
+ //
433
+ // Checking the env var first was a live bug (quoxai/quox#404): dev
434
+ // boxes export QUOX_SERVICE_KEY globally, so the CLI reported
435
+ // "logged in" off the ambient key while sending the wrong credential.
436
+ const auth = config.getAuth();
437
+ // QUOX_FORCE_SERVICE_KEY: a daemon that owns its key deliberately bypasses
438
+ // any stored session (see client.js), so the key alone decides here.
439
+ if (process.env.QUOX_FORCE_SERVICE_KEY && process.env.QUOX_SERVICE_KEY) return true;
440
+ if (auth?.access_token) return true;
441
+ return !!process.env.QUOX_SERVICE_KEY;
442
+ },
443
+
444
+ isExpired() {
445
+ const tokenFile = tokenFilePath();
446
+ if (tokenFile) {
447
+ try {
448
+ const { expires_at } = readTokenFile(tokenFile);
449
+ return expires_at ? Date.now() > expires_at : false;
450
+ } catch {
451
+ return true;
452
+ }
453
+ }
454
+ // A forced service key is not a locally-tracked JWT: never "expired".
455
+ if (process.env.QUOX_FORCE_SERVICE_KEY && process.env.QUOX_SERVICE_KEY) return false;
456
+ // A stored user token is the credential in play (see isLoggedIn), so
457
+ // its expiry is the one that matters — an ambient service key must not
458
+ // suppress the expiry signal, or client.js's 401 auto-refresh path
459
+ // (which only fires when isExpired() is true) would never refresh a
460
+ // genuinely expired session.
461
+ const auth = config.getAuth();
462
+ if (auth?.access_token) {
463
+ if (!auth.expires_at) return true;
464
+ return Date.now() > auth.expires_at;
465
+ }
466
+ // Service-key mode: not a JWT with a locally-tracked expiry, so treat
467
+ // it as never expired and never attempt a refresh for it.
468
+ if (process.env.QUOX_SERVICE_KEY) return false;
469
+ if (!auth?.expires_at) return true;
470
+ return Date.now() > auth.expires_at;
471
+ },
472
+
473
+ requireAuth() {
474
+ if (!this.isLoggedIn()) {
475
+ // Name the PROFILE. "Run `quox login`" alone sent the owner in a
476
+ // circle on 2026-08-23: he ran `quox login`, saw it succeed, then
477
+ // `quox -p moo dev enable` still refused, because the login had
478
+ // stored its session in whichever profile was active at the time
479
+ // while -p pointed at a different one. The advice was literally
480
+ // true and still useless, because it omitted the only detail that
481
+ // mattered.
482
+ const name = typeof config.getActiveProfileName === 'function' ? config.getActiveProfileName() : null;
483
+ const activeOrgId = config.getAuth()?.org_id || null;
484
+ if (name) {
485
+ throw new QuoxAuthError(
486
+ `Not authenticated on profile "${name}". Run \`quox -p ${name} login\`. `
487
+ + `Note that \`quox login\` without -p logs into whichever profile is active, which may not be this one.`
488
+ + residentProfileHint(config, activeOrgId)
489
+ );
490
+ }
491
+ throw new QuoxAuthError(`Not authenticated. Run \`quox login\` first.${residentProfileHint(config, activeOrgId)}`);
492
+ }
493
+ },
494
+
495
+ getOrgId(overrideOrg) {
496
+ if (overrideOrg) return overrideOrg;
497
+ const auth = config.getAuth();
498
+ return auth?.org_id || null;
499
+ },
500
+ };
501
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * The bare-`quox` splash (DEVSUITE-GLYPH follow-on): the horned Q in
3
+ * truecolor half-blocks with the brand gradient, plus a one-glance verb map.
4
+ * Bitmap generated from QuoxSymbols quoxQ (fontforge export at 26px); the
5
+ * gradient stops mirror @quox/glyph. Colour gating matches the renderer:
6
+ * QUOX_COLOR=0 off / 1|2 force on, else tty && !NO_COLOR.
7
+ */
8
+ import { GRAD } from './quoxglyph.js';
9
+
10
+ const Q_BITMAP = [
11
+ ' # ',
12
+ ' # # ',
13
+ ' ## # ',
14
+ ' ## ## ',
15
+ ' ######### ',
16
+ ' ########### ',
17
+ ' ### ### ',
18
+ '### ### ',
19
+ '### ### ',
20
+ '### ### ',
21
+ '### ### ',
22
+ '### ### ',
23
+ '### ### ### ',
24
+ '### ###### ',
25
+ ' ### ##### ',
26
+ ' ############ ',
27
+ ' ###########',
28
+ ' ### ',
29
+ ];
30
+
31
+ export function colorOn() {
32
+ const env = process.env.QUOX_COLOR;
33
+ if (env === '0') return false;
34
+ if (env === '1' || env === '2') return true;
35
+ return Boolean(process.stdout && process.stdout.isTTY) && !process.env.NO_COLOR;
36
+ }
37
+
38
+ const R = '\x1b[0m';
39
+ const DIM = '\x1b[2m';
40
+ const BOLD = '\x1b[1m';
41
+
42
+ function gradFg(t) {
43
+ const [a, b] = GRAD;
44
+ const c = a.map((v, k) => Math.round(v + (b[k] - v) * t));
45
+ return `\x1b[38;2;${c[0]};${c[1]};${c[2]}m`;
46
+ }
47
+ function gradBg(t) {
48
+ const [a, b] = GRAD;
49
+ const c = a.map((v, k) => Math.round(v + (b[k] - v) * t));
50
+ return `\x1b[48;2;${c[0]};${c[1]};${c[2]}m`;
51
+ }
52
+
53
+ /** Render the Q as half-block rows (2 bitmap rows per terminal row). */
54
+ export function qArt(col) {
55
+ const cols = Q_BITMAP[0].length;
56
+ const out = [];
57
+ for (let y = 0; y < Q_BITMAP.length; y += 2) {
58
+ const top = Q_BITMAP[y];
59
+ const bot = Q_BITMAP[y + 1] || ' '.repeat(cols);
60
+ let line = '';
61
+ for (let x = 0; x < cols; x++) {
62
+ const t = x / (cols - 1);
63
+ const hasTop = top[x] === '#';
64
+ const hasBot = bot[x] === '#';
65
+ if (!col) {
66
+ line += hasTop && hasBot ? '█' : hasTop ? '▀' : hasBot ? '▄' : ' ';
67
+ } else if (hasTop && hasBot) {
68
+ line += `${gradFg(t)}${gradBg(t)}▀${R}`;
69
+ } else if (hasTop) {
70
+ line += `${gradFg(t)}▀${R}`;
71
+ } else if (hasBot) {
72
+ line += `${gradFg(t)}▄${R}`;
73
+ } else {
74
+ line += ' ';
75
+ }
76
+ }
77
+ out.push(line);
78
+ }
79
+ return out;
80
+ }
81
+
82
+ /**
83
+ * One frame of the intro sweep: the Q is revealed left to right with a bright
84
+ * leading edge; progress >= 1 returns exactly qArt(col). Always the same
85
+ * number of rows and columns, so the live region never changes height.
86
+ */
87
+ export function qArtFrame(progress, col) {
88
+ if (progress >= 1 || !col) return qArt(col);
89
+ const cols = Q_BITMAP[0].length;
90
+ const head = progress * (cols + 3) - 1.5;
91
+ const px = (x, on) => (on && x <= head ? 1 : 0);
92
+ const glow = (x, t) => {
93
+ const d = head - x; // 0 at the leading edge, grows behind it
94
+ const k = d < 3 ? 1 - d / 3 : 0;
95
+ const [a, b] = GRAD;
96
+ const base = a.map((v, j) => v + (b[j] - v) * t);
97
+ const c = base.map(v => Math.round(v + (255 - v) * 0.7 * k));
98
+ return `\x1b[38;2;${c[0]};${c[1]};${c[2]}m`;
99
+ };
100
+ const out = [];
101
+ for (let y = 0; y < Q_BITMAP.length; y += 2) {
102
+ const top = Q_BITMAP[y];
103
+ const bot = Q_BITMAP[y + 1] || ' '.repeat(cols);
104
+ let line = '';
105
+ for (let x = 0; x < cols; x++) {
106
+ const t = x / (cols - 1);
107
+ const hasTop = px(x, top[x] === '#');
108
+ const hasBot = px(x, bot[x] === '#');
109
+ if (!hasTop && !hasBot) line += ' ';
110
+ else if (hasTop && hasBot) line += `${glow(x, t)}${gradBg(t)}▀${R}`;
111
+ else line += `${glow(x, t)}${hasTop ? '▀' : '▄'}${R}`;
112
+ }
113
+ out.push(line);
114
+ }
115
+ return out;
116
+ }
117
+
118
+ export function printBanner(version, { loggedIn = true } = {}) {
119
+ const col = colorOn();
120
+ const art = qArt(col);
121
+ const b = (s) => (col ? `${BOLD}${s}${R}` : s);
122
+ const d = (s) => (col ? `${DIM}${s}${R}` : s);
123
+ const text = [
124
+ '',
125
+ `${b('Quox')} ${d('— the governed agent platform')}${version ? d(` v${version}`) : ''}`,
126
+ d('every action witnessed · every agent governed'),
127
+ '',
128
+ // quox#624: a logged-out first run starts at login, not at the auth wall.
129
+ ...(loggedIn ? [] : [`🔑 ${b('quox login')} ${d('start here: sign in to your instance')}`]),
130
+ `🧭 ${b('quox plan focus')} ${d('what matters now')}`,
131
+ `🛰️ ${b('quox fleet status')} ${d('the estate at a glance')}`,
132
+ `📡 ${b('quox workflow list')} ${d('governed automations')}`,
133
+ `⛓ ${b('quox volt verify')} ${d('the evidence chain')}`,
134
+ '',
135
+ d('quox --help for everything'),
136
+ ];
137
+ const lines = [];
138
+ const rows = Math.max(art.length, text.length);
139
+ for (let i = 0; i < rows; i++) {
140
+ lines.push(` ${art[i] || ' '.repeat(Q_BITMAP[0].length)} ${text[i] || ''}`);
141
+ }
142
+ process.stdout.write('\n' + lines.join('\n') + '\n\n');
143
+ }