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,282 @@
1
+ /**
2
+ * Resident alarm — QUOXPERTICUS-REVISIT P5 "warden watchdog + loud alarm".
3
+ *
4
+ * While the warden pumps (residentPump.js) instead of answering, it also
5
+ * watches the resident it wards: is the tmux pane still there, has a
6
+ * permission dialog been stuck too long to ever clear itself, has the
7
+ * provider started refusing turns. Three independent conditions, each with
8
+ * its own escalating alert schedule (10m, 30m, then hourly) so a wedged
9
+ * resident is loud rather than silently absent, and each condition clears
10
+ * itself the moment its detector stops firing (no human-ack mechanism yet:
11
+ * clearing = the underlying thing is gone).
12
+ *
13
+ * Every I/O dependency (tmux session list, pane read, release/redeploy,
14
+ * the alert transport, the turn-receipt store, the clock) is injected, same
15
+ * contract as supervisorEngine.js and residentPump.js, so this runs under
16
+ * vitest with no real tmux host and no real Telegram bridge.
17
+ */
18
+
19
+ import { ERROR_NOTICE_WINDOW_MS } from './supervisorEngine.js';
20
+
21
+ // Gaps BETWEEN successive alerts for the SAME still-active condition:
22
+ // immediate on first detection (see shouldSendAlarm), then +10m, then +30m,
23
+ // then hourly forever until the condition clears.
24
+ export const ALARM_SCHEDULE_MS = [10 * 60 * 1000, 30 * 60 * 1000, ERROR_NOTICE_WINDOW_MS];
25
+
26
+ // A stuck permission dialog only becomes an alarm condition once it has sat
27
+ // unresolved this long. Below this age it is exactly what residentPump.js
28
+ // already logs: held input, not yet an incident.
29
+ export const STUCK_DIALOG_ALARM_THRESHOLD_MS = 10 * 60 * 1000;
30
+
31
+ // Pane-text scan patterns for a provider/credits failure. Deliberately
32
+ // simple substring/regex matches (this is pane TEXT, not a structured
33
+ // provider error object the way detectTurnError in supervisorEngine.js
34
+ // parses a headless turn's JSON output) — same spirit, different signal.
35
+ export const PROVIDER_ISSUE_PATTERNS = [
36
+ { pattern: 'out of usage credits', regex: /out of usage credits/i },
37
+ { pattern: 'usage limit', regex: /usage limit/i },
38
+ { pattern: 'provider error', regex: /provider error/i },
39
+ ];
40
+
41
+ export function scanPaneForProviderIssue(paneText) {
42
+ const text = String(paneText || '');
43
+ for (const { pattern, regex } of PROVIDER_ISSUE_PATTERNS) {
44
+ if (regex.test(text)) return { matched: true, pattern };
45
+ }
46
+ return { matched: false, pattern: null };
47
+ }
48
+
49
+ export function createInitialAlarmCondition() {
50
+ return { active: false, first_detected_at: null, last_alerted_at: null, alert_count: 0 };
51
+ }
52
+
53
+ export function createInitialAlarmState() {
54
+ return {
55
+ pane_dead_attempted: false,
56
+ pane_dead_redeploy_failed: false,
57
+ conditions: {
58
+ pane_dead: createInitialAlarmCondition(),
59
+ stuck_dialog: createInitialAlarmCondition(),
60
+ provider_error: createInitialAlarmCondition(),
61
+ },
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Advance one condition's state from a single boolean detection this pass.
67
+ * A detection while already active is a no-op (first_detected_at sticks to
68
+ * the original onset). No detection while active is a full reset: clearing
69
+ * a condition means the next detection is treated as a fresh onset, with
70
+ * its own fresh alert schedule.
71
+ */
72
+ export function updateAlarmCondition(conditionState, detected, now) {
73
+ const current = conditionState || createInitialAlarmCondition();
74
+ if (detected) {
75
+ if (current.active) return current;
76
+ return { ...current, active: true, first_detected_at: now };
77
+ }
78
+ if (current.active) return createInitialAlarmCondition();
79
+ return current;
80
+ }
81
+
82
+ /**
83
+ * Whether condition `conditionState` should alert right now. The first
84
+ * alert for a freshly-active condition fires immediately (no delay: a dead
85
+ * pane or a provider outage is news the moment it is confirmed). Every
86
+ * alert after that waits at least the schedule's gap for however many
87
+ * alerts have already fired, capped at the schedule's last (hourly) entry.
88
+ */
89
+ export function shouldSendAlarm(conditionState, now, schedule = ALARM_SCHEDULE_MS) {
90
+ if (!conditionState || !conditionState.active) return false;
91
+ if (conditionState.last_alerted_at == null) return true;
92
+ const idx = Math.min(Math.max((conditionState.alert_count || 0) - 1, 0), schedule.length - 1);
93
+ const gap = schedule[idx];
94
+ return (now - conditionState.last_alerted_at) >= gap;
95
+ }
96
+
97
+ export function recordAlarmSent(conditionState, now) {
98
+ return { ...conditionState, last_alerted_at: now, alert_count: (conditionState.alert_count || 0) + 1 };
99
+ }
100
+
101
+ /**
102
+ * Send one alarm through both transports in order, per spec: tg-send.sh
103
+ * first (the owner's phone), `quox slot say` second as the fallback when
104
+ * tg-send.sh is unavailable or fails — never silent, and the absence of the
105
+ * script is itself treated as "alarm-transport-unavailable", not a reason
106
+ * to skip the slot notice.
107
+ */
108
+ export async function sendAlarm({ tgSend, postSlotNotice, text, sessionName = 'WARDEN', log = () => {} }) {
109
+ let tgSent = false;
110
+ if (tgSend) {
111
+ try {
112
+ await tgSend(sessionName, text);
113
+ tgSent = true;
114
+ } catch (err) {
115
+ log('warn', `alarm: tg-send.sh failed, falling back to slot say: ${err.message}`);
116
+ }
117
+ } else {
118
+ log('warn', 'alarm: alarm-transport-unavailable (tg-send.sh not found), falling back to slot say');
119
+ }
120
+ if (!tgSent) {
121
+ try {
122
+ await postSlotNotice(text);
123
+ } catch (err) {
124
+ log('error', `alarm: slot say fallback also failed: ${err.message}`);
125
+ }
126
+ }
127
+ return { tg_sent: tgSent };
128
+ }
129
+
130
+ /**
131
+ * Run one warden watchdog pass.
132
+ *
133
+ * Injected dependencies:
134
+ * listSessions() -> array of the host's tmux sessions ({name, ...})
135
+ * readPane() -> current pane text (only called while the pane
136
+ * is confirmed alive)
137
+ * releaseSlot() -> release the resident's slot (best-effort)
138
+ * redeploy() -> run the redeploy path (reuses `quox dev
139
+ * resident deploy`; throws on failure)
140
+ * claimFallback() -> attempt to claim the slot as this warden's
141
+ * own fallback voice (only called when redeploy
142
+ * fails); returns {claimed: bool}
143
+ * postSlotNotice(text) -> slot `say`
144
+ * recordTurn(fields) -> turn receipt (outcome/error_note only;
145
+ * NEVER a new outcome value — see
146
+ * devSupervisorTurnsStore.js OUTCOMES)
147
+ * tgSend(session, text) -> shells out to tg-send.sh; absent/failing is
148
+ * handled by sendAlarm above, never thrown here
149
+ * stuckDialogSinceMs -> residentPump.js's state.stuck_dialog_since
150
+ * for this slot (single source of truth for
151
+ * dialog detection; this module only ages it)
152
+ */
153
+ export async function runWardenPass({
154
+ hostId,
155
+ slot,
156
+ sessionName,
157
+ listSessions,
158
+ readPane,
159
+ releaseSlot,
160
+ redeploy,
161
+ claimFallback,
162
+ postSlotNotice,
163
+ recordTurn,
164
+ tgSend,
165
+ stuckDialogSinceMs = null,
166
+ state,
167
+ log = () => {},
168
+ now = () => Date.now(),
169
+ schedule = ALARM_SCHEDULE_MS,
170
+ stuckDialogThresholdMs = STUCK_DIALOG_ALARM_THRESHOLD_MS,
171
+ }) {
172
+ const base = state || createInitialAlarmState();
173
+ const nextState = {
174
+ pane_dead_attempted: base.pane_dead_attempted ?? false,
175
+ pane_dead_redeploy_failed: base.pane_dead_redeploy_failed ?? false,
176
+ conditions: {
177
+ pane_dead: base.conditions?.pane_dead || createInitialAlarmCondition(),
178
+ stuck_dialog: base.conditions?.stuck_dialog || createInitialAlarmCondition(),
179
+ provider_error: base.conditions?.provider_error || createInitialAlarmCondition(),
180
+ },
181
+ };
182
+
183
+ const nowMs = now();
184
+ const report = { pane_alive: null, redeployed: false, redeploy_error: null, fallback_claimed: false, alerts_sent: [] };
185
+
186
+ const sessions = await listSessions();
187
+ const paneAlive = !!(sessions || []).find((s) => s.name === sessionName);
188
+ report.pane_alive = paneAlive;
189
+
190
+ let paneText = null;
191
+
192
+ if (!paneAlive) {
193
+ if (!nextState.pane_dead_attempted) {
194
+ nextState.pane_dead_attempted = true;
195
+ log('warn', `warden: resident session ${sessionName} on ${hostId} is not present (pane dead); releasing the slot and redeploying.`);
196
+ try {
197
+ await releaseSlot();
198
+ } catch (err) {
199
+ log('warn', `warden: releasing ${slot} failed (continuing): ${err.message}`);
200
+ }
201
+ try {
202
+ await redeploy();
203
+ report.redeployed = true;
204
+ nextState.pane_dead_redeploy_failed = false;
205
+ nextState.conditions.pane_dead = createInitialAlarmCondition();
206
+ try {
207
+ await postSlotNotice(`Resident on ${hostId} lost its pane; redeployed automatically.`);
208
+ } catch (err) {
209
+ log('warn', `warden: redeploy notice failed to post: ${err.message}`);
210
+ }
211
+ try {
212
+ await recordTurn({ outcome: 'error', error_note: 'pane_dead_auto_redeployed' });
213
+ } catch (err) {
214
+ log('warn', `warden: redeploy receipt failed to post: ${err.message}`);
215
+ }
216
+ } catch (err) {
217
+ report.redeploy_error = err.message;
218
+ nextState.pane_dead_redeploy_failed = true;
219
+ log('error', `warden: redeploy failed for ${hostId}: ${err.message}`);
220
+ try {
221
+ await postSlotNotice(`Resident on ${hostId} lost its pane and automatic redeploy FAILED (${err.message}). A human should check this host.`);
222
+ } catch (err2) {
223
+ log('warn', `warden: failure notice failed to post: ${err2.message}`);
224
+ }
225
+ try {
226
+ const fb = await claimFallback();
227
+ report.fallback_claimed = !!(fb && fb.claimed);
228
+ } catch (err2) {
229
+ log('warn', `warden: fallback claim attempt failed: ${err2.message}`);
230
+ }
231
+ }
232
+ }
233
+ if (nextState.pane_dead_redeploy_failed) {
234
+ nextState.conditions.pane_dead = updateAlarmCondition(nextState.conditions.pane_dead, true, nowMs);
235
+ }
236
+ } else {
237
+ if (nextState.pane_dead_attempted || nextState.conditions.pane_dead.active) {
238
+ log('info', `warden: resident session ${sessionName} on ${hostId} is present again; pane-dead condition cleared.`);
239
+ }
240
+ nextState.pane_dead_attempted = false;
241
+ nextState.pane_dead_redeploy_failed = false;
242
+ nextState.conditions.pane_dead = updateAlarmCondition(nextState.conditions.pane_dead, false, nowMs);
243
+
244
+ try {
245
+ paneText = await readPane();
246
+ } catch (err) {
247
+ log('warn', `warden: pane read failed on ${hostId} (skipping provider-error scan this pass): ${err.message}`);
248
+ }
249
+ }
250
+
251
+ // Stuck dialog: this module never detects the dialog itself, it only ages
252
+ // what residentPump.js already found (single source of truth).
253
+ const dialogStuck = stuckDialogSinceMs != null && (nowMs - stuckDialogSinceMs) >= stuckDialogThresholdMs;
254
+ nextState.conditions.stuck_dialog = updateAlarmCondition(nextState.conditions.stuck_dialog, dialogStuck, nowMs);
255
+
256
+ if (paneText != null) {
257
+ const scan = scanPaneForProviderIssue(paneText);
258
+ nextState.conditions.provider_error = updateAlarmCondition(nextState.conditions.provider_error, scan.matched, nowMs);
259
+ }
260
+
261
+ const conditionAlerts = [
262
+ ['pane_dead', `Resident on ${hostId}: pane is dead and automatic redeploy failed. A human should check this host.`],
263
+ ['stuck_dialog', `Resident on ${hostId}: a permission dialog has been stuck for over ${Math.round(stuckDialogThresholdMs / 60000)} minutes and will never resolve itself; a human must approve or cancel it.`],
264
+ ['provider_error', `Resident on ${hostId}: a provider/credits error was detected in its pane. A human should check its account/billing status.`],
265
+ ];
266
+
267
+ for (const [key, message] of conditionAlerts) {
268
+ const condition = nextState.conditions[key];
269
+ if (shouldSendAlarm(condition, nowMs, schedule)) {
270
+ const sendResult = await sendAlarm({ tgSend, postSlotNotice, text: message, sessionName: `WARDEN-${hostId}`, log });
271
+ nextState.conditions[key] = recordAlarmSent(condition, nowMs);
272
+ try {
273
+ await recordTurn({ outcome: 'error', error_note: `alarm_${key}` });
274
+ } catch (err) {
275
+ log('warn', `warden: alarm receipt failed for ${key}: ${err.message}`);
276
+ }
277
+ report.alerts_sent.push({ condition: key, tg_sent: sendResult.tg_sent });
278
+ }
279
+ }
280
+
281
+ return { state: nextState, ...report };
282
+ }
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Resident agent authentication for QuoxCLI (quox#453 phase 15)
3
+ *
4
+ * A dev server's resident agent authenticates with an Ed25519 key on disk
5
+ * instead of a password, zero human minting. The exchange is a signed
6
+ * challenge:
7
+ *
8
+ * 1. POST {baseUrl}/api/auth/resident/challenge {org_id, host_id}
9
+ * -> {challenge_id, challenge_b64, expires_in}
10
+ * 2. sign the raw challenge bytes with the Ed25519 private key at key_path
11
+ * 3. POST {baseUrl}/api/auth/resident/token {challenge_id, org_id, host_id, signature_b64}
12
+ * -> {access_token, token_type, expires_in, user}
13
+ *
14
+ * Bootstrap file on a dev host (not secret itself, points at the real key):
15
+ * ~/.config/quox/resident-profile.json
16
+ * {kind: 'resident', org_id, host_id, key_id, key_path, instance_url}
17
+ *
18
+ * Neither the private key nor any resulting token is ever printed or logged
19
+ * by this module.
20
+ */
21
+
22
+ import { existsSync, readFileSync } from 'node:fs';
23
+ import { homedir } from 'node:os';
24
+ import { join } from 'node:path';
25
+ import { sign, createPrivateKey } from 'node:crypto';
26
+ import { QuoxError, QuoxAuthError, QuoxTransientAuthError } from './errors.js';
27
+
28
+ export function defaultResidentProfilePath() {
29
+ return join(homedir(), '.config', 'quox', 'resident-profile.json');
30
+ }
31
+
32
+ export function expandHome(p) {
33
+ if (!p) return p;
34
+ if (p === '~') return homedir();
35
+ if (p.startsWith('~/')) return join(homedir(), p.slice(2));
36
+ return p;
37
+ }
38
+
39
+ /**
40
+ * Load and validate the resident bootstrap profile.
41
+ * @param {string} [explicitPath] - overrides the default ~/.config/quox/resident-profile.json
42
+ */
43
+ export function loadResidentProfile(explicitPath) {
44
+ const path = expandHome(explicitPath) || defaultResidentProfilePath();
45
+
46
+ if (!existsSync(path)) {
47
+ throw new QuoxError(
48
+ `No resident profile found at ${path}. A resident agent needs this bootstrap file `
49
+ + `({kind, org_id, host_id, key_id, key_path, instance_url}) before it can authenticate. `
50
+ + `Run --resident-profile <path> to point at a different one.`
51
+ );
52
+ }
53
+
54
+ let raw;
55
+ try {
56
+ raw = readFileSync(path, 'utf-8');
57
+ } catch (err) {
58
+ throw new QuoxError(`Could not read resident profile at ${path}: ${err.message}`);
59
+ }
60
+
61
+ let data;
62
+ try {
63
+ data = JSON.parse(raw);
64
+ } catch (err) {
65
+ throw new QuoxError(`Resident profile at ${path} is not valid JSON: ${err.message}`);
66
+ }
67
+
68
+ const required = ['org_id', 'host_id', 'key_path'];
69
+ const missing = required.filter((key) => !data[key]);
70
+ if (missing.length > 0) {
71
+ throw new QuoxError(
72
+ `Resident profile at ${path} is missing required field(s): ${missing.join(', ')}.`
73
+ );
74
+ }
75
+
76
+ return { ...data, kind: 'resident', _path: path };
77
+ }
78
+
79
+ /**
80
+ * Prefer the bootstrap file's instance_url when the active CLI profile has
81
+ * no explicit URL of its own: a resident agent bootstraps its own instance
82
+ * pointer, it shouldn't need a human to also run `quox config`.
83
+ */
84
+ export function resolveResidentBaseUrl(config, profile) {
85
+ // quox#455 gap 1: the resident endpoints live on the instance EDGE
86
+ // (https://<host>:3000/api/auth/resident/*, proxied to auth). NEVER
87
+ // derive them by port-swapping to :3101: auth binds loopback on the
88
+ // control box, so :3101 is connection-refused from every remote host.
89
+ if (process.env.QUOX_AUTH_URL) return process.env.QUOX_AUTH_URL;
90
+ if (process.env.QUOX_URL) return process.env.QUOX_URL;
91
+ const activeProfile = typeof config.getActiveProfile === 'function' ? config.getActiveProfile() : null;
92
+ const hasExplicitUrl = !!(activeProfile && activeProfile.url && activeProfile.url !== 'http://localhost:3000');
93
+ if (hasExplicitUrl) return activeProfile.url;
94
+ if (profile?.instance_url) return profile.instance_url;
95
+ // Local default profile on the control box itself: auth is directly
96
+ // reachable on loopback :3101, keep the historical local-dev behaviour.
97
+ return config.getServiceUrl('auth');
98
+ }
99
+
100
+ /**
101
+ * True only when `urlStr` is https AND its host is a bare IP or a
102
+ * .local/LAN-style single-label name: the shapes that can never carry a
103
+ * real, publicly-issued certificate, so a self-signed edge is the expected
104
+ * (not the exceptional) case. A public FQDN with a real cert must never get
105
+ * this baked in, even if it happens to be reachable over the LAN.
106
+ */
107
+ function isSelfSignedLikelyHost(urlStr) {
108
+ let u;
109
+ try {
110
+ u = new URL(urlStr);
111
+ } catch {
112
+ return false;
113
+ }
114
+ if (u.protocol !== 'https:') return false;
115
+ const host = u.hostname;
116
+ if (!host) return false;
117
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) return true; // bare IPv4
118
+ if (host.includes(':')) return true; // bare IPv6 (URL strips the brackets)
119
+ if (/\.local$/i.test(host)) return true; // mDNS / LAN-style
120
+ if (!host.includes('.')) return true; // single-label hostname, never a public FQDN
121
+ return false;
122
+ }
123
+
124
+ /**
125
+ * Derive the systemd unit environment for a resident's headless supervisor
126
+ * from its bootstrap profile (item 1, live outage 2026-08-23: a unit run
127
+ * directly by systemd inherits none of the caller's shell environment, so
128
+ * every re-auth against a self-signed LAN edge died with a bare "fetch
129
+ * failed"). Baked in once, at install time, from the same profile the
130
+ * resident already trusts for its own identity: QUOX_URL and QUOX_AUTH_URL
131
+ * from instance_url, QUOX_COLLECTOR_URL from collector_url, and
132
+ * QUOX_INSECURE only when isSelfSignedLikelyHost(instance_url) is true.
133
+ * Returns {} for a missing profile or a profile with neither URL: the
134
+ * caller is responsible for warning in that case, this function only ever
135
+ * derives, never decides whether the result is enough.
136
+ */
137
+ export function deriveSupervisorEnv(profile) {
138
+ const env = {};
139
+ if (!profile) return env;
140
+ if (profile.instance_url) {
141
+ env.QUOX_URL = profile.instance_url;
142
+ env.QUOX_AUTH_URL = profile.instance_url;
143
+ if (isSelfSignedLikelyHost(profile.instance_url)) env.QUOX_INSECURE = '1';
144
+ }
145
+ if (profile.collector_url) env.QUOX_COLLECTOR_URL = profile.collector_url;
146
+ return env;
147
+ }
148
+
149
+ const TRANSIENT_STATUSES = new Set([429, 502, 503, 504]);
150
+
151
+ // Retry-After is delta-seconds or an HTTP date; returns ms or null.
152
+ function parseRetryAfterMs(res) {
153
+ const raw = res?.headers && typeof res.headers.get === 'function' ? res.headers.get('retry-after') : null;
154
+ if (!raw) return null;
155
+ const secs = Number(raw);
156
+ if (Number.isFinite(secs) && secs >= 0) return Math.round(secs * 1000);
157
+ const at = Date.parse(raw);
158
+ return Number.isFinite(at) ? Math.max(0, at - Date.now()) : null;
159
+ }
160
+
161
+ function exchangeFailure(what, res, data) {
162
+ const msg = `${what} failed (${res.status}): ${data?.error || res.statusText || 'unknown error'}`;
163
+ if (TRANSIENT_STATUSES.has(res.status)) return new QuoxTransientAuthError(msg, parseRetryAfterMs(res));
164
+ return new QuoxAuthError(msg);
165
+ }
166
+
167
+ async function safeJson(res) {
168
+ try {
169
+ return await res.json();
170
+ } catch {
171
+ return null;
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Run the challenge/sign/token exchange and return the token response.
177
+ * Never logs the private key or the returned token.
178
+ *
179
+ * @param {object} opts
180
+ * @param {string} opts.baseUrl
181
+ * @param {object} opts.profile - result of loadResidentProfile()
182
+ * @param {typeof fetch} [opts.fetchImpl]
183
+ */
184
+ export async function residentLogin({ baseUrl, profile, fetchImpl = fetch }) {
185
+ const { org_id, host_id, key_path } = profile;
186
+ const keyPath = expandHome(key_path);
187
+
188
+ if (!existsSync(keyPath)) {
189
+ throw new QuoxError(
190
+ `Resident identity key not found at ${keyPath}. Provision the Ed25519 key before logging in.`
191
+ );
192
+ }
193
+
194
+ let keyPem;
195
+ try {
196
+ keyPem = readFileSync(keyPath, 'utf-8');
197
+ } catch (err) {
198
+ throw new QuoxError(`Could not read resident identity key at ${keyPath}: ${err.message}`);
199
+ }
200
+
201
+ let privateKey;
202
+ try {
203
+ privateKey = createPrivateKey(keyPem);
204
+ } catch (err) {
205
+ throw new QuoxError(`Resident identity key at ${keyPath} is not a valid private key: ${err.message}`);
206
+ }
207
+
208
+ // Step 1: request a fresh challenge.
209
+ const challengeRes = await fetchImpl(`${baseUrl}/api/auth/resident/challenge`, {
210
+ method: 'POST',
211
+ headers: { 'Content-Type': 'application/json' },
212
+ body: JSON.stringify({ org_id, host_id }),
213
+ });
214
+ const challengeData = await safeJson(challengeRes);
215
+ if (!challengeRes.ok) {
216
+ throw exchangeFailure('Resident challenge request', challengeRes, challengeData);
217
+ }
218
+ const { challenge_id, challenge_b64 } = challengeData || {};
219
+ if (!challenge_id || !challenge_b64) {
220
+ throw new QuoxError('Resident challenge response is missing challenge_id or challenge_b64.');
221
+ }
222
+
223
+ // Step 2: sign the raw challenge bytes with the Ed25519 key.
224
+ const challengeBytes = Buffer.from(challenge_b64, 'base64');
225
+ const signatureBytes = sign(null, challengeBytes, privateKey);
226
+ const signature_b64 = signatureBytes.toString('base64');
227
+
228
+ // Step 3: exchange the signature for an access token.
229
+ const tokenRes = await fetchImpl(`${baseUrl}/api/auth/resident/token`, {
230
+ method: 'POST',
231
+ headers: { 'Content-Type': 'application/json' },
232
+ body: JSON.stringify({ challenge_id, org_id, host_id, signature_b64 }),
233
+ });
234
+ const tokenData = await safeJson(tokenRes);
235
+ if (!tokenRes.ok) {
236
+ throw exchangeFailure('Resident token exchange', tokenRes, tokenData);
237
+ }
238
+ if (!tokenData?.access_token) {
239
+ throw new QuoxError('Resident token response is missing access_token.');
240
+ }
241
+
242
+ return tokenData;
243
+ }