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,50 @@
1
+ /**
2
+ * /feedback: tell the Quox team something, through the public quox.ai feedback API
3
+ * (POST https://api.quox.ai/api/feedback, anonymous, 5 per hour per IP; lands in the quox.ai admin feedback view).
4
+ * /gripe stays the GitHub path. What is sent is type + message (the CLI version is appended to the message)
5
+ * and nothing else: no name, email, org, profile or token. The user sees the exact payload and says y first.
6
+ */
7
+ export const FEEDBACK_URL = 'https://api.quox.ai/api/feedback';
8
+ const MIN = 5, MAX = 5000; // the endpoint refuses outside these (api/routes/feedback.js in quox-website)
9
+ const TIMEOUT_MS = 15000;
10
+
11
+ /** -> { type:'feedback', message } | null when the endpoint would refuse it. */
12
+ export function feedbackPayload(text, version) {
13
+ const body = String(text || '').trim();
14
+ if (body.length < MIN) return null;
15
+ const message = `${body}\n\n(sent with /feedback from quox CLI ${version || 'unknown'})`;
16
+ if (message.length > MAX) return null;
17
+ return { type: 'feedback', message };
18
+ }
19
+
20
+ /** The lines shown before asking: where it goes and exactly what is in the body. */
21
+ export function feedbackPreview(payload, url = FEEDBACK_URL) {
22
+ return [
23
+ `POST ${url} (the Quox team at quox.ai, not your Quox server)`,
24
+ 'type: feedback',
25
+ 'message:',
26
+ ...String(payload.message).split('\n').map(l => ` ${l}`),
27
+ 'Nothing else: no name, email, org or token.',
28
+ ];
29
+ }
30
+
31
+ /** -> { ok:true, id, url } | { ok:false, message }. Status is checked before the body is trusted. */
32
+ export async function sendFeedback(payload, { fetchImpl = globalThis.fetch, url = FEEDBACK_URL, version = null, timeoutMs = TIMEOUT_MS } = {}) {
33
+ const ctl = new AbortController();
34
+ const t = setTimeout(() => ctl.abort(), timeoutMs);
35
+ try {
36
+ const res = await fetchImpl(url, {
37
+ method: 'POST',
38
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'User-Agent': `quox-cli/${version || 'unknown'}` },
39
+ body: JSON.stringify({ type: payload.type, message: payload.message }),
40
+ signal: ctl.signal,
41
+ });
42
+ let data = null;
43
+ try { data = await res.json(); } catch { data = null; }
44
+ if (!res.ok) return { ok: false, message: `HTTP ${res.status}: ${(data && data.error) || 'the feedback API refused it'}` };
45
+ if (!data || data.success !== true) return { ok: false, message: `HTTP ${res.status} but the reply did not confirm it was stored` };
46
+ return { ok: true, id: data.id ?? null, url };
47
+ } catch (e) {
48
+ return { ok: false, message: e?.name === 'AbortError' ? `no answer from ${url} within ${Math.round(timeoutMs / 1000)} s` : (e?.message || 'failed') };
49
+ } finally { clearTimeout(t); }
50
+ }
@@ -0,0 +1,237 @@
1
+ /**
2
+ * `quox motion`: the product version of the animation lab (~/quoxon-work/intro/lab). Plays every Q motion
3
+ * with a caption: Orbit & Lock, Portal, Particle collapse, the perpetual status Q in its
4
+ * four states, the hello, the composer ring and the reveal effects. No server, no account, nothing sent.
5
+ *
6
+ * Scenes are pure: galleryScenes() returns { id, title, caption, ms, frame(t) -> ANSI lines }. runGallery()
7
+ * is the only part that touches a terminal (cursor, timers, signals).
8
+ */
9
+ import {
10
+ labSize, orbitCellsAt, portalCellsAt, particleCellsAt, cellsToAnsi, qCells,
11
+ INTRO_MS, PORTAL_MS, SKY, VIOLET, AMBER,
12
+ } from './qart.js';
13
+ import { qGlyph, glyphAnsi, GLYPH_STATES } from './qglyph.js';
14
+ import { helloCells, helloAnsi, helloGradient, helloMs } from './qhello.js';
15
+ import { CLIPS, MOOD_CLIPS, clipCells, clipCaption } from './qdragonScenes.js';
16
+ import { isLight } from './theme.js';
17
+
18
+ const RESET = '\x1b[0m';
19
+ const fg = (rgb) => `\x1b[38;2;${rgb.map((v) => Math.max(0, Math.min(255, Math.round(v)))).join(';')}m`;
20
+ const mix = (a, b, t) => a.map((v, i) => v + (b[i] - v) * t);
21
+ const clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
22
+ const DIM = [58, 66, 82], MUTED = [120, 132, 150], TXT = [226, 232, 240], TEAL = [6, 182, 212];
23
+ const GREEN = [34, 197, 94], WHITE = [255, 255, 255];
24
+ const paint = (s, rgb, mono) => (mono || !rgb ? s : fg(rgb) + s + RESET);
25
+
26
+ // ---- composer ring (lab rules.mjs: the website's conic border as one light ring over the two rules) ------
27
+ const PAL = { work: [SKY, VIOLET, TEAL], wait: [AMBER, [0xea, 0x58, 0x0c], [0xb4, 0x53, 0x09]] };
28
+ const STOPS = [[0, 0, 0.85], [0.35, 1, 0.8], [0.6, 0, 0], [0.85, 2, 0.55], [1, 0, 0.85]];
29
+ function ringAt(u, w) {
30
+ let i = 0; while (i < STOPS.length - 2 && u > STOPS[i + 1][0]) i++;
31
+ const [p0, c0, a0] = STOPS[i], [p1, c1, a1] = STOPS[i + 1], t = (u - p0) / (p1 - p0 || 1);
32
+ const col = (c) => mix(PAL.work[c], PAL.wait[c], w);
33
+ const A = a0 + (a1 - a0) * t;
34
+ const pm = mix(col(c0).map((v) => v * a0), col(c1).map((v) => v * a1), t);
35
+ return [A ? pm.map((v) => v / A) : col(c0), A];
36
+ }
37
+ const RING_SEG = [['idle', 0.8], ['work', 3.2], ['settle', 0.4], ['idle', 0.6], ['wait', 2.2], ['settle', 0.4]];
38
+ export const RING_MS = RING_SEG.reduce((a, s) => a + s[1], 0) * 1000;
39
+ /** Ring state at t ms, integrated at 60 Hz from 0 (deterministic). { angle, k, w, mode } */
40
+ export function ringState(t) {
41
+ let angle = 0, k = 0, w = 0, mode = 'idle', settleFrom = { om: 144 }, om = 0, prev = 'idle';
42
+ const T = t / 1000, dt = 1 / 60;
43
+ for (let s = 0; s < T; s += dt) {
44
+ let acc = 0, si = 0;
45
+ while (si < RING_SEG.length - 1 && acc + RING_SEG[si][1] <= s) acc += RING_SEG[si++][1];
46
+ const [m, dur] = RING_SEG[si], p = clamp((s - acc) / dur);
47
+ if (m === 'settle' && prev !== 'settle') settleFrom = { om };
48
+ mode = m; prev = m;
49
+ let wt = 0;
50
+ if (m === 'work') { om = 144; k = Math.min(1, k + dt / 0.15); wt = 0; }
51
+ else if (m === 'wait') { om = 144 * 0.3; k = Math.min(1, k + dt / 0.15); wt = 1; }
52
+ else if (m === 'settle') { om = settleFrom.om * (1 - p) ** 2; k = 1 - p * p * (3 - 2 * p); wt = w; }
53
+ else { om = 0; k = 0; w = 0; }
54
+ if (m !== 'idle') w += (wt - w) * Math.min(1, dt / 0.2);
55
+ angle = (angle + om * dt) % 360;
56
+ }
57
+ return { angle, k, w, mode };
58
+ }
59
+ /** The two composer rules at ring state st, W columns wide. */
60
+ export function ringLines(W, st, mono = false) {
61
+ const line = (top) => {
62
+ let s = '', last = '';
63
+ const base = top ? mix(DIM, SKY, 0.75) : DIM;
64
+ for (let x = 0; x < W; x++) {
65
+ let rgb = base;
66
+ if (st.k > 0) {
67
+ const pos = top ? (x + 0.5) / (2 * W) : (W + (W - 1 - x) + 0.5) / (2 * W);
68
+ const u = (((pos - st.angle / 360) % 1) + 1) % 1;
69
+ const [c, a] = ringAt(u, st.w);
70
+ rgb = mix(base, mix(DIM, c, a), st.k);
71
+ }
72
+ if (!mono) { const e = fg(rgb); if (e !== last) { s += e; last = e; } }
73
+ s += '─';
74
+ }
75
+ return s + (mono ? '' : RESET);
76
+ };
77
+ const label = { idle: 'idle', work: 'working', wait: 'waiting for approval', settle: 'done' }[st.mode];
78
+ return [line(true), paint('› ', SKY, mono) + paint('Ask Quox anything…', MUTED, mono), line(false), paint(`ring: ${label}`, MUTED, mono)];
79
+ }
80
+
81
+ // ---- reveal effects (lab fx.mjs, the reveals that ship in the client) ------------------------------------
82
+ const decodeLine = (text, t, f, mono, perChar = 14) => helloAnsi(helloCells(text, t, { fg: f, mono, perChar }));
83
+ /** fx at t ms: a tool row settles, an answer drops in, an approval card arms, a receipt seals, ProofFlow lights. */
84
+ export function fxLines(t, W, mono = false) {
85
+ const out = [];
86
+ const at = (s) => t - s;
87
+ // tool row (0 ms): spinner while running, then a flash to ✔ and the result decodes in
88
+ const L = at(0), SETTLE = 900;
89
+ const head = L < SETTLE ? paint('◐◓◑◒'[Math.floor(t / 125) % 4], SKY, mono) : paint('✔', L < SETTLE + 120 ? WHITE : GREEN, mono);
90
+ out.push(head + decodeLine(' SEO │ summary │ ', L, (i, ch) => (ch === '│' ? DIM : i < 5 ? TEAL : MUTED), mono) + (L < SETTLE ? paint('…', MUTED, mono) : decodeLine('30 crawls · 2.4s', L - SETTLE, TXT, mono)));
91
+ out.push('');
92
+ // answer (1400 ms): lines drop in one by one, first chars decode, afterglow behind
93
+ const ans = ['Crawls finished with no blocking errors this week.', 'Impressions are up 8 percent; 3 pages moved into the top ten.'];
94
+ ans.forEach((ln, k) => { const s = at(1400 + k * 110); out.push(s < -140 ? '' : ' ' + decodeLine(ln.slice(0, W - 4), s, TXT, mono, 6)); });
95
+ out.push('');
96
+ // approval card (2600 ms): amber rule draws in, title decodes, 300 ms arming, then ready
97
+ const A = at(2600), iw = Math.min(W - 4, 50), draw = clamp(A / 250), n = Math.round(draw * (iw + 2));
98
+ const rule = (a, b) => (A <= 0 ? '' : paint([...(a + '─'.repeat(iw) + b)].slice(0, n).join(''), AMBER, mono));
99
+ out.push(rule('╭', '╮'));
100
+ const armed = clamp((A - 400) / 300), ready = A >= 700;
101
+ out.push(A > 100 ? paint('│ ', AMBER, mono) + decodeLine('Send the weekly SEO report?', A - 100, TXT, mono) : '');
102
+ out.push(A > 100 ? paint('│ ', AMBER, mono) + paint('[ Approve ]', ready ? AMBER : DIM, mono) + ' ' + paint('[ Deny ]', ready ? MUTED : DIM, mono) + ' ' + paint(ready ? 'ready ' : 'arming', ready ? GREEN : AMBER, mono) + ' ' + paint([...'▰▰▰▰▰▰'].map((c, i) => (ready || i < Math.floor(armed * 6) ? '▰' : '▱')).join(''), ready ? GREEN : AMBER, mono) : '');
103
+ out.push(rule('╰', '╯'));
104
+ out.push('');
105
+ // receipt seal (3800 ms): text resolves, the seal settles with a one-frame glint
106
+ const R = at(3800), seq = ['·', '○', '◌', '◎'];
107
+ const seal = R < 0 ? ' ' : R < 400 ? paint(seq[Math.floor(R / 70) % 4], mix(DIM, TEAL, clamp(R / 400)), mono) : paint('◉', R < 460 ? WHITE : TEAL, mono);
108
+ out.push(R < -140 ? '' : seal + decodeLine(' WARD │ witnessed · seq 393', R, (i, ch) => (ch === '│' ? DIM : i < 5 ? TEAL : TXT), mono));
109
+ // ProofFlow (4600 ms): AEE > AOCL > TOOL > WARD light up in sequence; completed ones stay still
110
+ const P = at(4600);
111
+ out.push(P < 0 ? '' : paint('proof ', MUTED, mono) + ['AEE', 'AOCL', 'TOOL', 'WARD'].map((nm, i) => { const d = P - i * 120; return paint(nm, d < 0 ? DIM : d < 100 ? WHITE : SKY, mono); }).join(paint(' ─▸ ', mix(DIM, SKY, 0.8), mono)));
112
+ out.push('');
113
+ // closing line (5400 ms)
114
+ const C = at(5400);
115
+ out.push(C < -140 ? '' : decodeLine('Thought for 12s · 1.4k tokens · $0.02', C, (i, ch) => (ch === '·' ? DIM : MUTED), mono));
116
+ return out;
117
+ }
118
+ export const FX_MS = 6600;
119
+
120
+ // ---- scenes ------------------------------------------------------------------------------------------------
121
+ const STATE_WORD = { resting: 'Ready', working: 'Thinking…', waiting: 'Waiting for approval', failing: 'Failed' };
122
+ const STATE_TAG = { resting: 'resting: slow breathing glow', working: 'working: the gradient flows through it', waiting: 'waiting: amber pulse', failing: 'failing: a red flash, then steady red' };
123
+ const STATE_RGB = { resting: SKY, working: SKY, waiting: AMBER, failing: [239, 68, 68] };
124
+ export const PERP_MS = 2600;
125
+
126
+ /**
127
+ * The gallery's scenes for a terminal. narrow forces the 40-column layout (the compact particle Q).
128
+ * Each scene: { id, title, caption, ms, frame(t, { mono }) -> string[] }.
129
+ */
130
+ export function galleryScenes({ cols = 100, rows = 30, narrow = false, theme = isLight() ? 'light' : 'dark' } = {}) {
131
+ const W = narrow ? 40 : cols;
132
+ // the dragon (qdragonScenes.js): eyes as quadrant blocks with a background colour, the lab's light palette on light terminals
133
+ const fit = (str) => (str.length > W ? str.slice(0, W - 1) + '…' : str);
134
+ const dragon = (name, t, o) => [...art(clipCells(name, t, { cols: W, theme, mono: o.mono })), '', paint(fit(clipCaption(name, t)), MUTED, o.mono)];
135
+ const MOOD_SPAN = MOOD_CLIPS.map((m) => CLIPS[m].dur + 400);
136
+ const size = narrow ? 'small' : labSize(W, rows);
137
+ const big = size === 'small' ? 'small' : size;
138
+ const art = (cells) => cellsToAnsi(cells);
139
+ const hello = "Hi, I'm Quox.";
140
+ return [
141
+ { id: 'orbit', title: 'Orbit & Lock', caption: size === 'small' ? 'the compact Q: particles circle, then collapse into the mark (the intro under 60 columns)' : 'particles swirl on a ring with trailing drag, then lock into the Q in an angular wave; horns grow out, the tail sweeps in last (the intro)', ms: INTRO_MS + 300,
142
+ frame: (t, o) => art(orbitCellsAt(big, t, { color: !o.mono })) },
143
+ { id: 'portal', title: 'Portal', caption: 'a ring spins edge-on and opens; the Q fills in from the opening, the tail sweeps in last', ms: PORTAL_MS + 600,
144
+ frame: (t, o) => art(portalCellsAt(big, t, { color: !o.mono })) },
145
+ { id: 'particle', title: 'Particle collapse', caption: 'density rises on a circle, then collapses into the compact Q', ms: 1100 + 500,
146
+ frame: (t, o) => art(particleCellsAt('small', t, { color: !o.mono })) },
147
+ { id: 'hello', title: 'Hello', caption: 'the Q locks, then says hello: the line decodes in from katakana with a short afterglow', ms: INTRO_MS + helloMs(hello) + 600,
148
+ frame: (t, o) => {
149
+ const q = art(t < INTRO_MS ? orbitCellsAt(big, t, { color: !o.mono }) : qCells(big, { color: !o.mono }));
150
+ const line = helloAnsi(helloCells(hello, t - INTRO_MS, { fg: helloGradient(hello), mono: o.mono }));
151
+ if (W < 60) return [...q, '', line];
152
+ const mid = Math.floor(q.length / 2);
153
+ return q.map((l, i) => (i === mid ? l + ' ' + line : l));
154
+ } },
155
+ { id: 'perpetual', title: 'The perpetual Q', caption: 'the status glyph carries state in colour alone, one fixed shape: resting breathes, working flows sky to violet to teal, waiting pulses amber, failing flashes red', ms: PERP_MS * 4,
156
+ frame: (t, o) => {
157
+ const i = Math.min(3, Math.floor(t / PERP_MS)), st = GLYPH_STATES[i], tt = t - i * PERP_MS;
158
+ return GLYPH_STATES.map((s, k) => {
159
+ const g = qGlyph(s, k === i ? tt : 0, { mono: o.mono, quiet: k !== i, since: 0 });
160
+ const word = paint(STATE_WORD[s], k === i ? STATE_RGB[s] : DIM, o.mono);
161
+ const tag = W >= 70 ? ' ' + paint(STATE_TAG[s], k === i ? MUTED : DIM, o.mono) : '';
162
+ return (k === i ? '› ' : ' ') + glyphAnsi(g) + ' ' + word + tag;
163
+ }).concat(['', ' ' + paint(`now: ${st}`, MUTED, o.mono)]);
164
+ } },
165
+ { id: 'ring', title: 'The composer ring', caption: 'the two composer rules become one light ring while Quox works; amber while it waits for you', ms: RING_MS,
166
+ frame: (t, o) => ringLines(Math.min(W - 2, 72), ringState(t), o.mono) },
167
+ { id: 'fx', title: 'Reveal effects', caption: 'tool row, answer drop-in, approval arming, receipt seal, ProofFlow, closing line', ms: FX_MS,
168
+ frame: (t, o) => fxLines(t, Math.min(W - 2, 78), o.mono) },
169
+ { id: 'dragon', title: CLIPS.pick.title, caption: CLIPS.pick.caption, ms: CLIPS.pick.dur, frame: (t, o) => dragon('pick', t, o) },
170
+ { id: 'dragon-fire', title: CLIPS.fire.title, caption: CLIPS.fire.caption, ms: CLIPS.fire.dur, frame: (t, o) => dragon('fire', t, o) },
171
+ { id: 'dragon-moods', title: 'Dragon moods', caption: 'at the cinematic motion level the dragon reacts: wide eyes and an amber glint while it waits for you, a wink when done, a puff of smoke on failure, and a greeting on launch',
172
+ ms: MOOD_SPAN.reduce((a, b) => a + b, 0),
173
+ frame: (t, o) => {
174
+ let i = 0, s0 = 0;
175
+ while (i < MOOD_CLIPS.length - 1 && t >= s0 + MOOD_SPAN[i]) s0 += MOOD_SPAN[i++];
176
+ const m = MOOD_CLIPS[i];
177
+ return [...art(clipCells(m, t - s0, { cols: Math.min(W, 64), theme, mono: o.mono })), '', paint(fit(`${m}: ${CLIPS[m].caption.split(':')[1].replace(/\s*\(.*\)$/, '').trim()}`), MUTED, o.mono)];
178
+ } },
179
+ ];
180
+ }
181
+ export const SCENE_IDS = ['orbit', 'portal', 'particle', 'hello', 'perpetual', 'ring', 'fx', 'dragon', 'dragon-fire', 'dragon-moods'];
182
+
183
+ // ---- runner (the only impure part) ---------------------------------------------------------------------------
184
+ const visible = (s) => s.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, ''); // eslint-disable-line no-control-regex
185
+ const width = (s) => [...visible(s)].length;
186
+
187
+ /** One full screen for a scene frame: title + caption on top, the frame centred under it. */
188
+ export function screenFor(scene, t, { cols, mono, index, total }) {
189
+ const lines = scene.frame(t, { mono });
190
+ const head = paint(`[${index + 1}/${total}] ${scene.title}`, SKY, mono);
191
+ const cap = wrap(scene.caption, Math.max(20, cols - 2)).map((l) => paint(l, MUTED, mono));
192
+ const w = Math.max(0, ...lines.map(width));
193
+ const left = ' '.repeat(Math.max(0, Math.floor((cols - w) / 2)));
194
+ return [head, ...cap, '', ...lines.map((l) => left + l)];
195
+ }
196
+ function wrap(s, n) { const o = []; let l = ''; for (const w of s.split(' ')) { if ((l + ' ' + w).trim().length > n) { o.push(l); l = w; } else l = (l + ' ' + w).trim(); } if (l) o.push(l); return o; }
197
+
198
+ export async function runGallery({ out = process.stdout, env = process.env, only = [], narrow = false, slow = false, mono: monoFlag = false } = {}) {
199
+ const cols = narrow ? 40 : Math.max(40, out.columns || 100), rows = out.rows || 30;
200
+ const mono = monoFlag || Boolean(env.NO_COLOR) || env.TERM === 'dumb';
201
+ const still = env.QUOX_REDUCED_MOTION === '1' || !out.isTTY;
202
+ const all = galleryScenes({ cols, rows, narrow });
203
+ const bad = only.filter((n) => !SCENE_IDS.includes(n));
204
+ if (bad.length) throw new Error(`unknown motion: ${bad.join(', ')} (one of ${SCENE_IDS.join(', ')})`);
205
+ const scenes = only.length ? all.filter((s) => only.includes(s.id)) : all;
206
+ if (still) {
207
+ // Reduced motion or piped: every scene's settled frame, printed once.
208
+ scenes.forEach((s, i) => out.write(screenFor(s, s.ms, { cols, mono, index: i, total: scenes.length }).join('\n') + '\n\n'));
209
+ return;
210
+ }
211
+ const SLOW = slow ? 3 : 1;
212
+ let restored = false;
213
+ const restore = () => { if (!restored) { restored = true; out.write(RESET + '\x1b[?25h\n'); } };
214
+ const onSig = () => { restore(); process.exit(130); };
215
+ process.once('SIGINT', onSig); process.once('SIGTERM', onSig);
216
+ out.write('\x1b[?25l');
217
+ try {
218
+ for (let i = 0; i < scenes.length; i++) {
219
+ const s = scenes[i];
220
+ out.write('\x1b[2J\x1b[H');
221
+ const t0 = performance.now();
222
+ for (let n = 0; ; n++) {
223
+ const t = (performance.now() - t0) / SLOW;
224
+ const done = t >= s.ms;
225
+ const lines = screenFor(s, done ? s.ms : t, { cols, mono, index: i, total: scenes.length });
226
+ out.write('\x1b[?2026h\x1b[H' + lines.map((l) => l + '\x1b[K').join('\n') + '\x1b[J\x1b[?2026l');
227
+ if (done) break;
228
+ const wait = t0 + (n + 1) * (1000 / 30) - performance.now();
229
+ if (wait > 0) await new Promise((r) => setTimeout(r, wait));
230
+ }
231
+ await new Promise((r) => setTimeout(r, 700 * SLOW));
232
+ }
233
+ } finally {
234
+ process.off('SIGINT', onSig); process.off('SIGTERM', onSig);
235
+ restore();
236
+ }
237
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * QUOXON handoff offer (owner, live 2026-10-17 19:51). Attached to TRIAGE-Q, the owner asked which VMs use the most CPU
3
+ * and memory; TRIAGE-Q had no fleet tools and told him to detach and ask CommanderQ. Offer, don't instruct: the server
4
+ * sends turn.handoff_suggested (from the turn's tool selection, never the answer text) and the TUI asks once:
5
+ *
6
+ * ⚠ TRIAGE-Q asks: Hand this question to CommanderQ?
7
+ * do ask CommanderQ "which VMs use the most CPU and memory" once; you stay attached to TRIAGE-Q
8
+ * why TRIAGE-Q can't read VM stats.
9
+ * ▶ Hand this question to CommanderQ? y yes · n no
10
+ *
11
+ * y sends the SAME question to CommanderQ for that one turn; the window stays attached. n does nothing.
12
+ */
13
+ import { askText } from './ask.js';
14
+
15
+ const clipQ = (s, n) => { const t = String(s ?? '').replace(/\s+/g, ' ').trim(); return t.length > n ? `${t.slice(0, n - 1)}…` : t; };
16
+
17
+ /** The suggestion from a turn event, when it applies to THIS window (attached to the agent it names). Else null. */
18
+ export function handoffFromEvent(ev, attachedAgent) {
19
+ if (!ev || ev.type !== 'turn.handoff_suggested') return null;
20
+ const h = ev.handoff_suggested;
21
+ const at = String(attachedAgent || '').toLowerCase();
22
+ if (!h || typeof h !== 'object' || !h.agent || !at) return null;
23
+ if (String(h.agent).toLowerCase() === at) return null; // already talking to the target
24
+ if (h.from && String(h.from).toLowerCase() !== at) return null; // a suggestion about some other agent
25
+ return { agent: String(h.agent), name: String(h.name || h.agent), from: at, fromName: String(h.from_name || at.toUpperCase()), reason: String(h.reason || '').trim() };
26
+ }
27
+
28
+ /** The ask line (drawn as the approval-style card). question: the user's own words being handed over. */
29
+ export function handoffAskText(h, question, cols = 100) {
30
+ const action = `ask ${h.name} "${clipQ(question, Math.max(20, cols - 60))}" once; you stay attached to ${h.fromName}`;
31
+ return askText(`Hand this question to ${h.name}?`, { keys: 'y/N', who: h.fromName, reason: h.reason, action });
32
+ }
33
+
34
+ /** The one line after a yes-path answer: who answered, and that nothing about the attach changed. */
35
+ export const handoffAnsweredNote = (h) => `↪ ${h.name} answered that one. Still attached to ${h.fromName}.`;
36
+
37
+ /** The agent's own suggested command, dropped because the handoff answers the question (never silently). */
38
+ export const handoffSupersededNote = (h, line) => `↷ ${h.fromName} also suggested ${line || 'a command'}; not offered, because ${h.name} can answer the question itself.`;
39
+
40
+ /** A handoff that could not be offered because a plan card is open (never silently). */
41
+ export const handoffSkippedNote = (h) => `↷ ${h.name} could answer this, but the plan card is open, so the handoff is not offered.`;
42
+
43
+ /**
44
+ * QUOXON handoff streak (owner, live 05:10-05:17): attached to TRIAGE-Q he asked three infra questions and every one ended
45
+ * in "Hand this question to CommanderQ?". When the SAME attached agent has suggested a handoff on 2 turns in a row, the
46
+ * card offers the bigger move (back to CommanderQ) instead of the same small one. c ("keep asking") silences the bigger
47
+ * offer until a new streak starts; a turn with no suggestion, or a different agent, breaks the streak.
48
+ */
49
+ export function createHandoffStreak() {
50
+ let agent = null, count = 0, muted = false;
51
+ return {
52
+ /** A turn ended with a handoff suggestion from `from`. -> { count, escalate } */
53
+ offered(from) {
54
+ const f = String(from || '').toLowerCase();
55
+ if (f === agent) count += 1; else { agent = f; count = 1; muted = false; }
56
+ return { count, escalate: count >= 2 && !muted };
57
+ },
58
+ /** A turn ended without a suggestion (or the window moved agents): the streak is over. */
59
+ clear() { agent = null; count = 0; muted = false; },
60
+ /** n (just this one): the bigger offer needs a fresh run of two before it returns. */
61
+ restart() { count = 0; },
62
+ /** c (keep asking): no bigger offer until a new streak starts. */
63
+ mute() { muted = true; },
64
+ peek: () => ({ agent, count, muted }),
65
+ };
66
+ }
67
+
68
+ export const HANDOFF_BIG_KEYS = (h) => `y switch · n just this one · c keep asking ${h.fromName}`;
69
+
70
+ /** The bigger card: offer to move the whole window back instead of handing over one more question. */
71
+ export function handoffBiggerAskText(h, count) {
72
+ return askText(`Switch this window back to ${h.name}?`, {
73
+ keys: HANDOFF_BIG_KEYS(h),
74
+ who: h.fromName,
75
+ reason: `${h.fromName} has handed off your last ${count} questions`,
76
+ action: `/detach: this window stops being attached to ${h.fromName} and talks to ${h.name}`,
77
+ });
78
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * /hat helpers (pure), quox#1183. Match a hat the way a person names it (key, display name, "the DevOps hat", a unique
3
+ * prefix) and the lines /hat prints. Never guesses between ties; an unknown name is { none: true }.
4
+ */
5
+ const norm = (s) => String(s ?? '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim().replace(/^the /, '').replace(/ hat$/, '').trim();
6
+
7
+ export const isPrivileged = (hat) => hat?.definition?.transitions?.privileged === true || hat?.privileged === true;
8
+
9
+ /** -> { hat } | { ambiguous: [hats] } | { none: true } */
10
+ export function matchHat(query, hats) {
11
+ const rows = Array.isArray(hats) ? hats.filter(Boolean) : [];
12
+ const raw = String(query ?? '').trim();
13
+ const q = norm(raw);
14
+ if (!q) return { none: true };
15
+ const byId = rows.filter((h) => h.id && h.id === raw);
16
+ if (byId.length === 1) return { hat: byId[0] };
17
+ const exact = rows.filter((h) => norm(h.key) === q || norm(h.name) === q);
18
+ if (exact.length === 1) return { hat: exact[0] };
19
+ if (exact.length > 1) return { ambiguous: exact };
20
+ const pre = rows.filter((h) => norm(h.key).startsWith(q) || norm(h.name).startsWith(q));
21
+ if (pre.length === 1) return { hat: pre[0] };
22
+ if (pre.length > 1) return { ambiguous: pre };
23
+ return { none: true };
24
+ }
25
+
26
+ /** "key (Name)" for each hat, for an error that tells you what to type. */
27
+ export const hatChoices = (hats) => (Array.isArray(hats) ? hats : []).map((h) => (h.name && h.name !== h.key ? `${h.key} (${h.name})` : h.key)).join(', ');
28
+
29
+ /** One line per hat; the active one is marked with *. */
30
+ export function hatLines(hats, activeKey) {
31
+ const rows = Array.isArray(hats) ? hats : [];
32
+ const w = Math.min(28, Math.max(4, ...rows.map((h) => [...String(h.name || h.key)].length)));
33
+ return rows.map((h) => `${h.key === activeKey ? '* ' : ' '}${String(h.name || h.key).padEnd(w)} ${h.key}${isPrivileged(h) ? ' (privileged: asks you first)' : ''}`);
34
+ }
35
+
36
+ /** A conversation id in the collector's own shape (turnStream ensureConversationId), for a window with none yet. */
37
+ export const mintConversationId = (uuid) => `QXC_${uuid}`;
@@ -0,0 +1,161 @@
1
+ /**
2
+ * QUOXON-LATENCY: the headless half of the conversation client (`quox --print`), with no
3
+ * React / Ink / App import. bin/quox.js routes --print here directly, so a headless turn
4
+ * no longer pays ~2 s of renderer load before its request is sent. main.js re-exports
5
+ * every name below, so the interactive client and its importers are unchanged.
6
+ */
7
+ import { parseModelArg } from './model.js';
8
+ import { loadRules, rulesContext } from './rules.js';
9
+
10
+ const FLAGS = new Set(['-p', '--profile', '--verbose', '--demo', '--print', '--output', '--conversation', '--turn-id', '--timeout', '--color', '--no-clear', '-n', '--name', '--service-key', '--demo-onboarding', '--autoplay', '--autoplay-exit', '--provider', '--model', '--agent', '--prompt-profile', '--resume', '-h', '--help', '--']);
11
+ const isFlag = (a) => FLAGS.has(a) || /^--(profile|verbose|print|output|conversation|turn-id|timeout|provider|model|agent|prompt-profile|resume)=/.test(a);
12
+
13
+ export function parseArgs(argv) {
14
+ const o = { profile: null, verbose: 'on', demo: false, help: false, print: false, prompt: null, output: 'text', conversation: null, turnId: null, timeout: 0, color: false, noClear: false, name: null, serviceKey: false, demoOnboarding: false, autoplay: false, autoplayExit: null, provider: null, agent: null, resume: null, verboseSet: false, errors: [] };
15
+ let rawProvider = null, rawModel = null;
16
+ const value = (flag, i) => { const v = argv[i + 1]; if (v === undefined || (v.startsWith('-') && isFlag(v))) { o.errors.push(`${flag} needs a value`); return undefined; } return v; };
17
+ for (let i = 0; i < argv.length; i++) {
18
+ const a = argv[i];
19
+ if (a === '--') { const rest = argv.slice(i + 1).join(' '); if (rest) o.prompt = rest; break; }
20
+ if (a === '-p' || a === '--profile') { const v = value(a, i); if (v !== undefined) { o.profile = v; i++; } }
21
+ else if (a.startsWith('--profile=')) o.profile = a.slice(10);
22
+ else if (a === '--verbose') { o.verboseSet = true; const v = argv[i + 1]; if (['off', 'on', 'full'].includes(v)) { o.verbose = v; i++; } else o.verbose = 'on'; } // no value means on
23
+ else if (a.startsWith('--verbose=')) { o.verboseSet = true; o.verbose = a.slice(10); }
24
+ else if (a === '--resume') { const v = argv[i + 1]; if (v !== undefined && !v.startsWith('-')) { o.resume = v; i++; } else o.resume = true; } // alone: the picker
25
+ else if (a.startsWith('--resume=')) o.resume = a.slice(9) || true;
26
+ else if (a === '-n' || a === '--name') { const v = value(a, i); if (v !== undefined) { o.name = v; i++; } }
27
+ else if (a.startsWith('--name=')) o.name = a.slice(7);
28
+ else if (a === '--demo') o.demo = true;
29
+ else if (a === '--autoplay') o.autoplay = true;
30
+ else if (a === '--autoplay-exit') { o.autoplay = true; const v = argv[i + 1]; if (v !== undefined && /^\d+(\.\d+)?$/.test(v)) { o.autoplayExit = Number(v); i++; } else o.autoplayExit = 5; } // seconds to hold the final frame
31
+ else if (a === '--demo-onboarding') o.demoOnboarding = true;
32
+ else if (a === '--print') { o.print = true; const n = argv[i + 1]; if (n !== undefined && !isFlag(n)) { o.prompt = n; i++; } }
33
+ else if (a.startsWith('--print=')) { o.print = true; o.prompt = a.slice(8); }
34
+ else if (a === '--output') { const v = value(a, i); if (v !== undefined) { o.output = v; i++; } }
35
+ else if (a.startsWith('--output=')) o.output = a.slice(9);
36
+ else if (a === '--conversation') { const v = value(a, i); if (v !== undefined) { o.conversation = v; i++; } }
37
+ else if (a === '--agent') { const v = value(a, i); if (v !== undefined) { o.agent = v; i++; } }
38
+ else if (a.startsWith('--agent=')) o.agent = a.slice(8) || null;
39
+ else if (a === '--turn-id') { const v = value(a, i); if (v !== undefined) { o.turnId = v; i++; } }
40
+ else if (a === '--timeout') { const v = value(a, i); if (v !== undefined) { o.timeout = Number(v) || 0; i++; } }
41
+ else if (a === '--color') o.color = true;
42
+ else if (a === '--no-clear') o.noClear = true;
43
+ else if (a === '--service-key') o.serviceKey = true;
44
+ else if (a === '--provider' || a === '--model') { const v = value(a, i); if (v !== undefined) { if (a === '--provider') rawProvider = v; else rawModel = v; i++; } }
45
+ else if (a.startsWith('--provider=')) rawProvider = a.slice(11);
46
+ else if (a.startsWith('--model=')) rawModel = a.slice(8);
47
+ else if (a === '--prompt-profile') { const v = value(a, i); if (v !== undefined) { o.promptProfile = v; i++; } }
48
+ else if (a.startsWith('--prompt-profile=')) o.promptProfile = a.slice(17);
49
+ else if (a === '-h' || a === '--help') o.help = true;
50
+ else if (!a.startsWith('-') && o.prompt === null && argv.includes('--print')) o.prompt = a; // `--print` given earlier or later; this is its prompt
51
+ else o.errors.push(a.startsWith('-') ? `unknown option ${a}` : `unexpected argument ${a}`);
52
+ }
53
+ if (rawModel && !rawProvider) o.errors.push('--model needs --provider (e.g. --provider openai --model gpt-5.4)');
54
+ else if (rawProvider) {
55
+ const m = parseModelArg(rawModel ? `${rawProvider} ${rawModel}` : rawProvider);
56
+ if (!m.ok) o.errors.push(m.error); else o.provider = m.choice || 'auto'; // 'auto' = this run ignores the saved /model choice
57
+ }
58
+ if (o.promptProfile !== undefined && !['compact', 'full'].includes(o.promptProfile)) { o.errors.push(`invalid --prompt-profile "${o.promptProfile}" (use compact or full)`); delete o.promptProfile; }
59
+ if (!['off', 'on', 'full'].includes(o.verbose)) o.verbose = 'on';
60
+ if (!['text', 'json'].includes(o.output)) { o.errors.push(`invalid --output "${o.output}" (use text or json)`); o.output = 'text'; }
61
+ return o;
62
+ }
63
+
64
+ export const USAGE = 'quox [-p|--profile <name>] [-n|--name <session name>] [--conversation <id>] [--resume [id|name]] [--verbose off|on|full] [--demo [--autoplay [--autoplay-exit [s]]]] [--no-clear] [--service-key] [--provider openai|anthropic|ollama|openrouter|huggingface|groq|together|lmstudio|vllm|custom|auto [--model <id>]] (quoxon is an alias; the old menu is `quox tui`)\n quox --conversation <id> resume that conversation interactively (its saved turns load into the window)\n quox --resume [id|name] pick up a recent conversation; alone it opens a picker\n quox --print "<prompt>" [--output text|json] [--conversation <id>] [--turn-id <id>] [--timeout <s>] [--agent <id>] [--provider <p> [--model <id>]] [--prompt-profile compact|full] (prompt from stdin if absent or -; use -- before a prompt that starts with -)\n exit: 0 completed, 1 other, 2 held for approval, 3 interrupted or abandoned, 4 not signed in, 5 ended without server confirmation\n --provider asks the server for that provider this run (default: your /model choice for this profile, else auto); turn.started from the server says what ran\n --service-key lets chat use an ambient QUOX_SERVICE_KEY when the profile has no login (never used silently)\n --demo plays a simulated journey on its own (no server, nothing real runs)\n --autoplay (with --demo, or QUOX_DEMO_AUTOPLAY=1) plays the whole journey with no input on a fixed script; --autoplay-exit [s] quits s seconds (default 5) after the last frame\n --demo-onboarding walks Getting started with a dry-run install (nothing is downloaded, built or started)';
65
+
66
+ /** Default profile: a resident (agent) login cannot chat, so use the single logged-in human profile. */
67
+ export function pickProfile(explicit, cfgMod, env = process.env) {
68
+ if (explicit) return { name: explicit, note: null };
69
+ if (env.QUOX_PROFILE) return { name: env.QUOX_PROFILE, note: null }; // an explicit one-shot choice (same as -p), never "the default profile"
70
+
71
+ const probe = cfgMod.loadConfig();
72
+ const active = probe.getActiveProfileName?.();
73
+ const residents = new Set((probe.listResidentProfiles?.() || []).map(r => r.name));
74
+ return choosePick(active, residents, probe.listProfiles?.() || []);
75
+ }
76
+ export function choosePick(active, residents, profiles) {
77
+ if (!residents.has(active)) return { name: undefined, note: null };
78
+ const humans = profiles.filter(p => !residents.has(p.name) && p.loggedIn);
79
+ if (humans.length === 1) return { name: humans[0].name, note: null }; // the status line shows the profile; no transcript noise
80
+ const names = profiles.filter(p => !residents.has(p.name) && p.loggedIn).map(p => p.name);
81
+ const hint = names.length ? ` Pass -p <your profile>. Human profiles found: ${names.join(', ')} (start with: quox -p ${names[0]}).` : ' Pass -p <your profile>.';
82
+ return { name: undefined, note: `The default profile "${active}" is an agent login, so this chat runs as the agent, not as a person.${hint}` };
83
+ }
84
+
85
+ /**
86
+ * Chat identity gate. A named profile that does not exist is an error (never created on the fly), and an
87
+ * ambient QUOX_SERVICE_KEY is only a credential when the user asked for it (--service-key or FORCE).
88
+ * -> null when chat may proceed, else { message }.
89
+ */
90
+ export function chatCredentialProblem({ profileArg, profiles, hasStoredLogin, profileName, env = process.env, serviceKeyFlag = false }) {
91
+ if (profileArg && !profiles.some(p => p.name === profileArg)) return { message: `profile ${profileArg} does not exist; create it with: quox login -p ${profileArg}` };
92
+ if (hasStoredLogin) return null;
93
+ if ((serviceKeyFlag || env.QUOX_FORCE_SERVICE_KEY) && env.QUOX_SERVICE_KEY) return null;
94
+ return { message: `Not signed in for chat on profile ${profileName}. Run: quox login -p ${profileName}${env.QUOX_SERVICE_KEY ? ' (an ambient QUOX_SERVICE_KEY is ignored unless you pass --service-key)' : ''}` };
95
+ }
96
+
97
+ /**
98
+ * Is this a machine with no Quox server configured at all? Then bare `quox` is Getting started, and --print
99
+ * says how to get there. Reads profiles only (no network).
100
+ */
101
+ export async function noServerConfigured(opts, cfgMod, env = process.env) {
102
+ const { needsGettingStarted } = await import('./onboarding/detect.js');
103
+ const { loadJourney } = await import('./onboarding/journey.js');
104
+ const probe = cfgMod.loadConfig();
105
+ return needsGettingStarted({ profiles: probe.listProfiles?.() || [], residents: probe.listResidentProfiles?.() || [], profileArg: opts.profile, env, serviceKeyFlag: opts.serviceKey, journey: loadJourney() });
106
+ }
107
+
108
+ /** --print without a usable login: today's message, plus the way in when no server exists yet. */
109
+ export function printAuthMessage(problem, gettingStarted, hint) {
110
+ return `quox: ${problem.message}\n${gettingStarted ? `quox: ${hint}\n` : ''}`;
111
+ }
112
+
113
+ export async function mainPrint(opts) {
114
+ const { runPrint, resolvePrompt, EXIT } = await import('./print.js');
115
+ const out = (s) => process.stdout.write(s);
116
+ const err = (s) => process.stderr.write(s);
117
+ const prompt = await resolvePrompt(opts.prompt, process.stdin);
118
+ let api;
119
+ if (opts.demo) api = (await import('./demo.js')).createDemoApi({ speed: 20 });
120
+ else {
121
+ const cfgMod = await import('../core/config.js');
122
+ const { createAuth } = await import('../core/auth.js');
123
+ const { createClient } = await import('../core/client.js');
124
+ const { createApi } = await import('./api.js');
125
+ const probe = cfgMod.loadConfig();
126
+ const profiles = probe.listProfiles?.() || [];
127
+ const missing = opts.profile && !profiles.some(p => p.name === opts.profile);
128
+ const pick = pickProfile(opts.profile, cfgMod);
129
+ if (pick.note) err(pick.note + '\n');
130
+ const config = missing ? probe : cfgMod.loadConfig(pick.name);
131
+ const auth = createAuth(config);
132
+ const pname = missing ? opts.profile : (config.getActiveProfileName?.() || 'default');
133
+ const problem = chatCredentialProblem({ profileArg: opts.profile, profiles, hasStoredLogin: missing ? false : Boolean(config.getAuth?.()?.access_token), profileName: pname, serviceKeyFlag: opts.serviceKey });
134
+ if (problem) {
135
+ const { NO_SERVER_PRINT } = await import('./onboarding/copy.js');
136
+ err(printAuthMessage(problem, await noServerConfigured(opts, cfgMod), NO_SERVER_PRINT));
137
+ process.exitCode = EXIT.auth; return;
138
+ }
139
+ const { createWindowClient } = await import('./windowOrg.js');
140
+ api = createApi({ ...createWindowClient({ config, auth, createClient }), config }); // #1034: /org stays in this process
141
+ if (opts.conversation && api.conversationExists) {
142
+ const c = await api.conversationExists(opts.conversation);
143
+ if (c.exists === false) { err(`quox: conversation not found: ${opts.conversation}\n`); process.exitCode = EXIT.error; return; }
144
+ if (c.exists === null) err(`quox: could not verify conversation ${opts.conversation} (${c.reason}); continuing\n`);
145
+ }
146
+ }
147
+ const rules = opts.demo ? null : loadRules();
148
+ for (const s of rules?.skipped || []) err(`quox: ignoring ${s.path}: ${s.reason}\n`);
149
+ const choice = opts.provider === 'auto' ? null : opts.provider || api.modelChoice?.() || null;
150
+ process.exitCode = await runPrint({ api, choice, agent: opts.agent || undefined, promptProfile: opts.promptProfile, context: rulesContext(rules), prompt, output: opts.output, conversation: opts.conversation, turnId: opts.turnId || undefined, timeoutS: opts.timeout, color: opts.color, profile: opts.profile || null, out, err });
151
+ }
152
+
153
+ /** bin/quox.js entry for --print: the same arg checks main() makes, then the headless turn. */
154
+ export async function runHeadless(argv) {
155
+ const opts = parseArgs(argv);
156
+ if (!opts.print) return false; // not a headless run after all (e.g. `-- --print` is a prompt): main() owns it
157
+ if (opts.help) { console.log(USAGE); return true; }
158
+ if (opts.errors.length) { process.stderr.write(`quox: ${opts.errors.join('; ')}\n${USAGE}\n`); process.exitCode = 1; return true; }
159
+ await mainPrint(opts);
160
+ return true;
161
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Z2H-R3: which address to PRINT for the dashboard and for the host installer. Over SSH to a fresh
3
+ * install the profile is https://localhost:3000, which only works on the Quox box, so a printed
4
+ * localhost is wrong for anyone reading it elsewhere. Order: the server's app URL (app_base_url),
5
+ * else the address this CLI reached the server on, else the address the install finale showed,
6
+ * else this machine's detected LAN address. Loopback is the last resort, never the first answer.
7
+ */
8
+ import { networkInterfaces } from 'node:os';
9
+
10
+ export const isLoopbackUrl = (u) => {
11
+ try { return /^(localhost|127\.\d+\.\d+\.\d+|\[::1\]|::1|0\.0\.0\.0)$/i.test(new URL(u).hostname); } catch { return true; }
12
+ };
13
+ const ok = (u) => typeof u === 'string' && /^https?:\/\/[^\s/]+/.test(u) && !isLoopbackUrl(u);
14
+ const origin = (u) => new URL(u).origin;
15
+
16
+ /** First non-internal IPv4 address (private ranges preferred), or null. */
17
+ export function detectLanIp(ifaces = networkInterfaces()) {
18
+ const all = Object.entries(ifaces || {}).filter(([n]) => !/^(docker|br-|veth|virbr|cni|flannel|cali|tun|tap)/i.test(n)).flatMap(([, v]) => v || [])
19
+ .filter(a => a && a.family === 'IPv4' && !a.internal && a.address);
20
+ const priv = all.find(a => /^(10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/.test(a.address));
21
+ return (priv || all[0])?.address || null;
22
+ }
23
+
24
+ /** Dashboard base: { url, source } . source: server | profile | install | lan | local. */
25
+ export function resolveDashboardBase({ serverApp, profileUrl, journeyUrl, lanIp } = {}) {
26
+ if (ok(serverApp)) return { url: origin(serverApp), source: 'server' };
27
+ if (ok(profileUrl)) return { url: origin(profileUrl), source: 'profile' };
28
+ if (ok(journeyUrl)) return { url: origin(journeyUrl), source: 'install' };
29
+ if (lanIp) return { url: `https://${lanIp}:3000`, source: 'lan' };
30
+ return { url: profileUrl || 'https://localhost:3000', source: 'local' };
31
+ }
32
+
33
+ /** Base that serves GET /install (the collector, :9848): { url, source }. */
34
+ export function resolveCollectorBase({ collectorUrl, serverApp, journeyUrl, lanIp } = {}) {
35
+ if (ok(collectorUrl)) return { url: origin(collectorUrl), source: 'profile' };
36
+ for (const [u, source] of [[serverApp, 'server'], [journeyUrl, 'install']]) {
37
+ if (ok(u)) return { url: `http://${new URL(u).hostname}:9848`, source };
38
+ }
39
+ if (lanIp) return { url: `http://${lanIp}:9848`, source: 'lan' };
40
+ return { url: collectorUrl || null, source: 'local' };
41
+ }