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,195 @@
1
+ /**
2
+ * SLOT-RUN A (owner 2026-10-16, about a slot message that arrived as a wrapped wall of text around a long
3
+ * shell command: "messy needs a format, structure, styling"). Incoming slot message bodies render light
4
+ * markdown: paragraphs, - bullets, **bold**, `inline code` and fenced blocks.
5
+ *
6
+ * Code is never wrapped mid-token. A long inline command is lifted into its own block; a block line that does
7
+ * not fit breaks ONLY at unquoted whitespace and ends in " \" (shell line continuation), so a copied block is
8
+ * still the same command. A single token longer than the row hard-breaks with a visible " ↩".
9
+ *
10
+ * Pure layout: rows of segments { t, role, bold? }. render.js paints them; no grey roles (owner rule).
11
+ */
12
+ import { graphemes } from './textBuffer.js';
13
+
14
+ export const CONT = ' \\';
15
+ export const HARD = ' ↩';
16
+ export const GUTTER = '│ ';
17
+ export const LONG_INLINE = 32; // an inline code span longer than this gets its own block
18
+
19
+ const len = (s) => graphemes(String(s)).length;
20
+
21
+ /** Where a line may break: unquoted, unescaped whitespace before any unquoted # comment. */
22
+ function safeBreaks(chars) {
23
+ const safe = new Array(chars.length).fill(false);
24
+ let q = null; let comment = false;
25
+ for (let i = 0; i < chars.length; i++) {
26
+ const ch = chars[i];
27
+ if (comment) continue;
28
+ if (q === "'") { if (ch === "'") q = null; continue; }
29
+ if (q === '"') { if (ch === '\\') i++; else if (ch === '"') q = null; continue; }
30
+ if (ch === '\\') { i++; continue; }
31
+ if (ch === "'" || ch === '"') { q = ch; continue; }
32
+ if (ch === '#' && (i === 0 || /\s/.test(chars[i - 1]))) { comment = true; continue; }
33
+ if (/\s/.test(ch)) safe[i] = true;
34
+ }
35
+ return safe;
36
+ }
37
+
38
+ /** One command line as rows of at most `width` columns. Continuation rows are indented two spaces. */
39
+ export function wrapShell(line, width) {
40
+ const w = Math.max(12, width);
41
+ const chars = [...String(line)];
42
+ if (chars.length <= w) return [chars.join('')];
43
+ const safe = safeBreaks(chars);
44
+ const out = [];
45
+ let start = 0;
46
+ for (;;) {
47
+ const indent = out.length ? ' ' : '';
48
+ const avail = w - indent.length;
49
+ if (chars.length - start <= avail) { out.push(indent + chars.slice(start).join('')); break; }
50
+ const lim = avail - CONT.length;
51
+ let cut = -1;
52
+ for (let i = start + lim; i > start; i--) if (safe[i]) { cut = i; break; }
53
+ if (cut > start) {
54
+ out.push(indent + chars.slice(start, cut).join('').replace(/\s+$/, '') + CONT);
55
+ start = cut;
56
+ while (start < chars.length && safe[start]) start++;
57
+ } else {
58
+ const n = avail - HARD.length;
59
+ out.push(indent + chars.slice(start, start + n).join('') + HARD);
60
+ start += n;
61
+ }
62
+ }
63
+ return out;
64
+ }
65
+
66
+ /** Inline runs of one line: plain text, **bold**, `code`. */
67
+ function inlineRuns(text) {
68
+ const out = [];
69
+ const re = /(`+)([^`]+?)\1|\*\*([^*]+?)\*\*/g;
70
+ let last = 0; let m;
71
+ const s = String(text);
72
+ while ((m = re.exec(s))) {
73
+ if (m.index > last) out.push({ kind: 'text', t: s.slice(last, m.index) });
74
+ if (m[1]) out.push({ kind: 'code', t: m[2].trim() });
75
+ else out.push({ kind: 'bold', t: m[3] });
76
+ last = m.index + m[0].length;
77
+ }
78
+ if (last < s.length) out.push({ kind: 'text', t: s.slice(last) });
79
+ return out;
80
+ }
81
+
82
+ /** Block structure: code (fenced, `$ ` lines, long inline spans), bullets, paragraphs, gaps. */
83
+ function blocks(text) {
84
+ const src = String(text ?? '').replace(/\r\n?/g, '\n').split('\n');
85
+ const out = [];
86
+ const push = (b) => {
87
+ if (b.type === 'gap' && (!out.length || out[out.length - 1].type === 'gap')) return;
88
+ out.push(b);
89
+ };
90
+ for (let i = 0; i < src.length; i++) {
91
+ const line = src[i];
92
+ const fence = /^\s{0,3}(`{3,}|~{3,})/.exec(line);
93
+ if (fence) {
94
+ const lines = [];
95
+ for (i++; i < src.length && !src[i].trim().startsWith(fence[1]); i++) lines.push(src[i]);
96
+ push({ type: 'code', lines });
97
+ continue;
98
+ }
99
+ if (/^\s*\$ \S/.test(line)) {
100
+ const prev = out[out.length - 1];
101
+ if (prev?.type === 'code' && prev.dollar) prev.lines.push(line.trim()); else push({ type: 'code', lines: [line.trim()], dollar: true });
102
+ continue;
103
+ }
104
+ if (!line.trim()) { push({ type: 'gap' }); continue; }
105
+ const bullet = /^\s*(?:[-*•]|(\d+[.)]))\s+(.*)$/.exec(line);
106
+ const heading = /^\s*#{1,6}\s+(.*)$/.exec(line);
107
+ const kind = bullet ? 'bullet' : 'para';
108
+ const body = bullet ? bullet[2] : heading ? `**${heading[1]}**` : line;
109
+ const mark = bullet ? (bullet[1] ? `${bullet[1]} ` : '• ') : '';
110
+ // A long inline command is lifted out into its own block between the prose around it.
111
+ let pending = [];
112
+ let first = true;
113
+ const flush = () => {
114
+ if (!pending.length) return;
115
+ if (pending.some(r => r.kind !== 'text' || r.t.trim())) push({ type: kind, runs: pending, mark: first ? mark : (mark ? ' ' : '') });
116
+ first = false; pending = [];
117
+ };
118
+ for (const r of inlineRuns(body)) {
119
+ if (r.kind === 'code' && len(r.t) > LONG_INLINE) { flush(); push({ type: 'code', lines: [r.t] }); first = false; }
120
+ else pending.push(r);
121
+ }
122
+ flush();
123
+ }
124
+ while (out.length && out[out.length - 1].type === 'gap') out.pop();
125
+ return out;
126
+ }
127
+
128
+ /** Word-wrap inline runs to `width`; code spans are unbreakable atoms (hard-split only if wider than the row). */
129
+ function wrapRuns(runs, width, role, prefix) {
130
+ const atoms = [];
131
+ for (const r of runs) {
132
+ if (r.kind === 'code') { atoms.push({ t: r.t, role: 'code', sp: /^\s/.test(r.t) }); continue; }
133
+ const parts = r.t.split(/(\s+)/);
134
+ for (const p of parts) {
135
+ if (!p) continue;
136
+ if (/^\s+$/.test(p)) { atoms.push({ space: true }); continue; }
137
+ atoms.push({ t: p, role, bold: r.kind === 'bold' || undefined });
138
+ }
139
+ }
140
+ const rows = [];
141
+ let cur = []; let w = 0; let gap = false;
142
+ const ind = len(prefix);
143
+ const room = Math.max(8, width - ind);
144
+ const add = (seg) => {
145
+ const last = cur[cur.length - 1];
146
+ if (last && last.role === seg.role && last.bold === seg.bold) last.t += seg.t; else cur.push({ ...seg });
147
+ };
148
+ const newRow = () => { rows.push(cur); cur = []; w = 0; };
149
+ for (const a of atoms) {
150
+ if (a.space) { gap = true; continue; }
151
+ const sep = cur.length && gap ? 1 : 0; // runs that touch (`a`**b**) stay touching
152
+
153
+ let t = a.t;
154
+ if (w + sep + len(t) > room && cur.length) newRow();
155
+ else if (sep) { add({ t: ' ', role, bold: undefined }); w += 1; }
156
+ while (len(t) > room) { const g = graphemes(t); add({ t: g.slice(0, room - w).join(''), role: a.role, bold: a.bold }); t = g.slice(room - w).join(''); newRow(); }
157
+ add({ t, role: a.role, bold: a.bold }); w += len(t);
158
+ gap = false;
159
+ }
160
+ if (cur.length) rows.push(cur);
161
+ return rows.map((r, i) => (prefix ? [{ t: i === 0 ? prefix : ' '.repeat(ind), role: 'bullet1' }, ...r] : r));
162
+ }
163
+
164
+ /** A code block's rows: gutter, the code, and the continuation marker in the label colour. */
165
+ export function codeRows(lines, width) {
166
+ const rows = [];
167
+ for (const line of lines) {
168
+ for (const piece of wrapShell(line, Math.max(12, width - len(GUTTER)))) {
169
+ const mk = piece.endsWith(CONT) ? CONT : piece.endsWith(HARD) ? HARD : '';
170
+ const body = mk ? piece.slice(0, -mk.length) : piece;
171
+ rows.push([{ t: GUTTER, role: 'label' }, { t: body || ' ', role: 'code' }, ...(mk ? [{ t: mk, role: 'label' }] : [])]);
172
+ }
173
+ }
174
+ return rows;
175
+ }
176
+
177
+ /** The body of a slot message as rows of segments, at most `width` columns each. */
178
+ export function formatSlotBody(text, width, { role = 'text' } = {}) {
179
+ const rows = [];
180
+ for (const b of blocks(text)) {
181
+ if (b.type === 'gap') rows.push([]);
182
+ else if (b.type === 'code') rows.push(...codeRows(b.lines, width));
183
+ else rows.push(...wrapRuns(b.runs, width, role, b.mark));
184
+ }
185
+ return rows;
186
+ }
187
+
188
+ /** Plain text of each row (tests, NO_COLOR). */
189
+ export const rowsText = (rows) => rows.map(r => r.map(s => s.t).join(''));
190
+
191
+ /** A short plain one-liner keeps the chat shape: on the header line after the time. */
192
+ export function inlineable(text, room) {
193
+ const t = String(text ?? '');
194
+ return !/[\n`]|\*\*/.test(t) && !/^\s*(?:[-*•]\s|\d+[.)]\s|\$ |#{1,6}\s)/.test(t) && len(t) <= room;
195
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * SLOT-RUN B (owner 2026-10-16: "if its a request to run the command, let me just say yes"). A slot message may
3
+ * carry ONE command for the human to run here (`quox slot say <slot> --run '<command>' "<why>"`, stored by the
4
+ * collector as the message's `run` field, never executed server-side).
5
+ *
6
+ * The TUI shows it with the shared ask: "▶ SENDER asks you to run this here: why", the FULL command in a code
7
+ * block (wrapped, never truncated: this is a confirm-what-you-see boundary), then "Run it? y/N/c". y hands the
8
+ * terminal over (like /restart: the user's own shell, stdio inherited, so OTP and login prompts work), then
9
+ * the TUI resumes and prints the exit code.
10
+ *
11
+ * Rules (non-negotiable): never auto-run; only from a verified sender (author_identity) in this org, otherwise
12
+ * shown but refused; one command; the ask expires after RUN_TTL_MS; never through the model (no turn, never a
13
+ * terminal_suggest_command offer); secret-shaped or destructive commands need "run" typed instead of y.
14
+ */
15
+ import { spawnSync } from 'node:child_process';
16
+ import { askText, askAnswer } from './ask.js';
17
+ import { looksLikeKey } from './connect.js';
18
+ import { resolveCliSlash } from './slash/cliCommand.js';
19
+ import { tokenize } from './bang.js';
20
+ import { codeRows } from './slotFormat.js';
21
+ import { graphemes } from './textBuffer.js';
22
+ import { TERMINAL_RESTORE } from './slash/restart.js';
23
+ import { PASTE_ON } from './keys.js';
24
+
25
+ export const RUN_TTL_MS = 10 * 60 * 1000;
26
+ export const RUN_MAX_LEN = 2000;
27
+ // Same rule as the collector (devSlotMessagesStore validateRunCommand): one line, nothing that can make the
28
+ // screen differ from what runs (control/ESC/CR, bidi overrides, zero-width, line separators).
29
+ const RUN_BAD_RE = /[\u0000-\u001f\u007f\u0080-\u009f\u200b-\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069\ufeff]/;
30
+
31
+ /** -> null (no run request) | { id, cmd, why, who, runnable, refuse, danger, expiresAt }. */
32
+ export function runRequest(m, { orgId = null, now = Date.now() } = {}) {
33
+ if (!m || m.run === undefined || m.run === null || m.run === '') return null;
34
+ const cmd = String(m.run);
35
+ const who = String(m.from || m.author_identity || 'someone').replace(/\s*\(.*$/, '').trim() || 'someone';
36
+ let refuse = null;
37
+ if (!m.author_identity) refuse = `${who} is not a verified sender, so this window will not run it`;
38
+ else if (orgId && m.org_id && m.org_id !== orgId) refuse = `it came from another org, so this window will not run it`;
39
+ else if (cmd.length > RUN_MAX_LEN || !cmd.trim() || RUN_BAD_RE.test(cmd)) refuse = 'it is not one plain command on one line, so this window will not run it';
40
+ return { id: m.id || null, cmd, why: String(m.text ?? '').trim(), who, runnable: !refuse, refuse, danger: refuse ? null : runDanger(cmd), expiresAt: now + RUN_TTL_MS };
41
+ }
42
+
43
+ // Destructive shell shapes (the bang runner has no shell danger list of its own; these are the ones that cost
44
+ // real work to undo). Matching one means "type run", never a refusal: it is the human's machine.
45
+ const DESTRUCTIVE = [
46
+ /(^|[\s;&|(])rm\s+(-[a-zA-Z]*[rf][a-zA-Z]*\s+)*-[a-zA-Z]*[rRf]/, /(^|[\s;&|(])sudo\s/, /(^|[\s;&|(])doas\s/,
47
+ /(^|[\s;&|(])mkfs(\.\w+)?\s/, /(^|[\s;&|(])dd\s+[^|;&]*\bof=/, />\s*\/dev\/(sd|nvme|hd|vd|disk)/,
48
+ /\|\s*(sudo\s+)?(ba|z|da|k)?sh\b/, /\bgit\s+push\b[^|;&]*\s(--force\b|-f\b|--force-with-lease\b|--delete\b)/,
49
+ /\bgit\s+reset\s+--hard\b/, /\bgit\s+clean\s+-[a-zA-Z]*f/, /\bchmod\s+-R\b/, /\bchown\s+-R\b/,
50
+ /:\(\)\s*\{/, /(^|[\s;&|(])(shutdown|reboot|halt|poweroff)\b/, /\bdrop\s+(table|database)\b/i, /\bdocker\s+(system\s+prune|volume\s+rm)\b/,
51
+ ];
52
+
53
+ /** null when y is enough; otherwise the reason "run" must be typed. Reuses the bang secret check and the quox CLI leaf policy. */
54
+ export function runDanger(cmd) {
55
+ const c = String(cmd || '');
56
+ if (looksLikeKey(c)) return 'It contains something that looks like a key or token';
57
+ const words = tokenize(c);
58
+ if (words[0] === 'quox' || words[0] === 'q') {
59
+ const hit = resolveCliSlash(`/${words[1] || ''}`, words.slice(2).map(w => (/[\s"']/.test(w) ? JSON.stringify(w) : w)).join(' '));
60
+ if (hit?.cls === 'never') return `${hit.display} is a never-from-chat quox command`;
61
+ }
62
+ if (DESTRUCTIVE.some(re => re.test(c))) return /(^|[\s;&|(])(sudo|doas)\s/.test(c) ? 'It runs as root' : 'It looks destructive';
63
+ return null;
64
+ }
65
+
66
+ /** 'run' | 'needs-run' (y on a dangerous one) | 'no' | 'chat' | 'command'. */
67
+ export function runAnswer(text, req) {
68
+ const t = String(text ?? '').trim();
69
+ if (/^run$/i.test(t)) return 'run';
70
+ const a = askAnswer(t);
71
+ if (a === 'yes') return req?.danger ? 'needs-run' : 'run';
72
+ return a;
73
+ }
74
+
75
+ const len = (s) => graphemes(String(s)).length;
76
+ function wrapWords(text, width) {
77
+ const out = []; let line = '';
78
+ for (const w of String(text).split(/\s+/).filter(Boolean)) {
79
+ if (line && len(line) + 1 + len(w) > width) { out.push(line); line = w; } else line = line ? `${line} ${w}` : w;
80
+ }
81
+ if (line) out.push(line);
82
+ return out;
83
+ }
84
+
85
+ /** The ask as rows of segments (render.js paints them; slotFormat rows shape). */
86
+ export function runAskRows(req, width) {
87
+ const w = Math.max(20, width);
88
+ const head = askText(`${req.who} asks you to run this here:`, { keys: '' }) + (req.why ? ` ${req.why}` : '');
89
+ const rows = wrapWords(head, w).map((l, i) => [{ t: i ? ` ${l}` : l, role: 'ask', bold: true }]);
90
+ // wrapWords may push an indented continuation over the width: re-wrap continuations at w - 2
91
+ const fixed = [];
92
+ for (const r of rows) { if (len(r[0].t) <= w) fixed.push(r); else for (const p of wrapWords(r[0].t, w - 2)) fixed.push([{ t: ` ${p}`, role: 'ask', bold: true }]); }
93
+ fixed.push(...codeRows([req.cmd], w));
94
+ let last;
95
+ if (!req.runnable) last = { t: `✖ Not offered: ${req.refuse}. Copy it yourself only if you trust it.`, role: 'warn' };
96
+ else if (req.danger) last = { t: askText('Type run to run it, n to skip?', { keys: 'run/N/c', reason: req.danger }), role: 'ask', bold: true };
97
+ else last = { t: askText('Run it?'), role: 'ask', bold: true };
98
+ for (const l of wrapWords(last.t, w)) fixed.push([{ ...last, t: l }]);
99
+ return fixed;
100
+ }
101
+
102
+ /**
103
+ * Run the command in the user's own shell with the terminal handed over (stdio inherited), then take it back.
104
+ * The live frame is cleared first and padded after, so the TUI redraws below the command's output instead of
105
+ * over it. -> { code, signal, ms } | { error, ms }.
106
+ */
107
+ export function handOverRun({ cmd, file = null, args = null, env = process.env, cwd = process.cwd(), stdout = null, setRawMode = null, spawn = spawnSync, shell, now = Date.now, resume = '' }) {
108
+ const tty = Boolean(stdout?.isTTY);
109
+ const rows = tty ? Math.max(0, Number(stdout.frameRows) || 0) : 0;
110
+ const put = (s) => { try { stdout.write(Buffer.from(s)); } catch { /* closed */ } };
111
+ if (tty) put(`\r${rows ? `\x1b[${rows}A` : ''}\x1b[J${TERMINAL_RESTORE}`);
112
+ try { setRawMode?.(false); } catch { /* not a tty */ }
113
+ const t0 = now();
114
+ let r;
115
+ try { // file + args (BANG-TTY: !quox runs this binary's argv) or a shell command line
116
+ r = file ? spawn(file, args || [], { stdio: 'inherit', cwd, env }) : spawn(shell || env.SHELL || '/bin/sh', ['-lc', cmd], { stdio: 'inherit', cwd, env });
117
+ } catch (e) { r = { error: e }; }
118
+ const ms = now() - t0;
119
+ try { setRawMode?.(true); } catch { /* not a tty */ }
120
+ if (tty) put(`\n${'\n'.repeat(rows)}${PASTE_ON}${resume}`); // resume: mouse tracking back on when /mouse on (QUOXON-MOUSE)
121
+ if (r?.error) return { error: `${r.error.code || 'failed'}: ${r.error.message}`, ms };
122
+ return { code: r?.status ?? null, signal: r?.signal || null, ms };
123
+ }
124
+
125
+ /** The footer after a hand-over run. */
126
+ export function runFooter(r) {
127
+ const secs = `${(r.ms / 1000).toFixed(1)}s`;
128
+ if (r.error) return `Could not start it: ${r.error}`;
129
+ if (r.signal) return `killed by ${r.signal} · ${secs}`;
130
+ return `exit ${r.code} · ${secs}`;
131
+ }
@@ -0,0 +1,186 @@
1
+ /**
2
+ * QuoxSound in the terminal: the controller the app calls from its single
3
+ * dispatch point. Decides (mapping.js), gates (profile, rate limit, mode) and
4
+ * plays (synthesised WAV through the OS player, or the terminal bell).
5
+ *
6
+ * const snd = createSound({ api, stdout });
7
+ * snd.observe(prevState, nextState, action) // after every reduce
8
+ * snd.focus(true|false) // from CSI I / CSI O (keys.js)
9
+ * await snd.command(arg, say) // /sound
10
+ *
11
+ * Never throws, never blocks: players are spawned detached with a kill timer.
12
+ * The setting is the dashboard's server-side `soundProfile` preference
13
+ * (PATCH /api/auth/me), so dashboard and terminal agree; the active CLI
14
+ * profile keeps a local copy for offline starts and as the fallback store.
15
+ */
16
+ import { spawn as nodeSpawn } from 'node:child_process';
17
+ import { existsSync, mkdirSync, mkdtempSync, renameSync, writeFileSync } from 'node:fs';
18
+ import { homedir, tmpdir } from 'node:os';
19
+ import { join } from 'node:path';
20
+ import { soundForEvent, createLimiter, normalizeSettings, allowed, PROFILE_IDS, PROFILE_LABELS } from './mapping.js';
21
+ import { soundMode, findPlayer, BEL, BEL_IDS } from './output.js';
22
+ import { earconWav } from './synth.js';
23
+
24
+ export const FOCUS_ON = '\x1b[?1004h';
25
+ export const FOCUS_OFF = '\x1b[?1004l';
26
+ export const PLAYER_TIMEOUT_MS = 5000;
27
+
28
+ const cap = (s) => s[0].toUpperCase() + s.slice(1);
29
+ const USAGE = 'Usage: /sound [Silent | Minimal | Operational | Full | test]';
30
+
31
+ function offReason({ env, stdout, print }) {
32
+ if (print) return '--print never makes sound';
33
+ if (!stdout?.isTTY) return 'not a terminal';
34
+ if (String(env.QUOX_SOUND ?? '').length && soundMode({ env: { QUOX_SOUND: env.QUOX_SOUND }, stdout: { isTTY: true } }) === 'off') return `QUOX_SOUND=${env.QUOX_SOUND}`;
35
+ return 'CI';
36
+ }
37
+
38
+ function defaultCacheDir(env) {
39
+ const base = env.XDG_CACHE_HOME || join(homedir(), '.cache');
40
+ const dir = join(base, 'quox', 'sound');
41
+ try { mkdirSync(dir, { recursive: true, mode: 0o700 }); return dir; } catch { return mkdtempSync(join(tmpdir(), 'quox-sound-')); }
42
+ }
43
+
44
+ export function createSound({
45
+ api = null, stdout = null, env = process.env, print = false, platform = process.platform,
46
+ has, spawn = nodeSpawn, now = Date.now, cacheDir = null, exitHook = (fn) => process.once('exit', fn),
47
+ timeoutMs = PLAYER_TIMEOUT_MS
48
+ } = {}) {
49
+ const mode = soundMode({ env, stdout, print });
50
+ const limiter = createLimiter();
51
+ const localProfile = safe(() => api?.localSoundProfile?.()) || null;
52
+ let settings = normalizeSettings({ profile: localProfile || undefined });
53
+ let source = localProfile ? 'local' : 'default';
54
+ let userSet = false;
55
+ let focused = null;
56
+ let player; // undefined = not looked up yet
57
+ let dir = cacheDir;
58
+ let focusOn = false;
59
+ let serverError = null;
60
+
61
+ function safe(fn) { try { return fn(); } catch { return null; } }
62
+ const write = (s) => { try { stdout?.write?.(s); } catch { /* never throw */ } };
63
+
64
+ // Server preference (source of truth), loaded in the background; a /sound typed meanwhile wins.
65
+ const ready = (async () => {
66
+ if (mode === 'off' || !api?.soundSettings) return;
67
+ try {
68
+ const v = await api.soundSettings();
69
+ if (v && !userSet) { settings = normalizeSettings(v); source = 'account'; safe(() => api.setLocalSoundProfile?.(settings.profile)); }
70
+ } catch (e) { serverError = e?.message || 'unreachable'; }
71
+ })();
72
+
73
+ if (mode !== 'off') {
74
+ write(FOCUS_ON);
75
+ focusOn = true;
76
+ safe(() => exitHook(dispose));
77
+ }
78
+
79
+ function getPlayer() {
80
+ if (player === undefined) player = safe(() => findPlayer({ platform, ...(has ? { has } : {}) })) || null;
81
+ return player;
82
+ }
83
+
84
+ function wavFile(id) {
85
+ if (!dir) dir = defaultCacheDir(env);
86
+ const vol = Math.round(settings.volume * 100);
87
+ const file = join(dir, `quox-${id}-v${vol}.wav`);
88
+ if (!existsSync(file)) {
89
+ const tmp = `${file}.${process.pid}.tmp`;
90
+ writeFileSync(tmp, earconWav(id, { volume: settings.volume }));
91
+ renameSync(tmp, file);
92
+ }
93
+ return file;
94
+ }
95
+
96
+ /** Emit `id` now (gates already passed). Returns how it went out: 'audio' | 'bel' | null. */
97
+ function emit(id) {
98
+ if (mode === 'off') return null;
99
+ const p = mode === 'audio' ? getPlayer() : null;
100
+ if (!p) {
101
+ if (!BEL_IDS.has(id)) return null;
102
+ write(BEL);
103
+ return 'bel';
104
+ }
105
+ try {
106
+ const file = wavFile(id);
107
+ const child = spawn(p.cmd, p.args(file), { detached: true, stdio: 'ignore', windowsHide: true });
108
+ child.on?.('error', () => {});
109
+ child.unref?.();
110
+ const t = setTimeout(() => { try { child.kill?.(); } catch { /* gone */ } }, timeoutMs);
111
+ t.unref?.();
112
+ return 'audio';
113
+ } catch {
114
+ if (BEL_IDS.has(id)) { write(BEL); return 'bel'; }
115
+ return null;
116
+ }
117
+ }
118
+
119
+ function observe(prev, next, action) {
120
+ if (mode === 'off') return null;
121
+ try {
122
+ const id = soundForEvent(prev, action, { next, now: action?.now ?? now(), focused });
123
+ if (!id || !allowed(id, settings) || !limiter.take(id, now())) return null;
124
+ emit(id);
125
+ return id;
126
+ } catch { return null; }
127
+ }
128
+
129
+ function outputLine() {
130
+ if (mode === 'off') return `Output: off (${offReason({ env, stdout, print })}).`;
131
+ if (mode === 'bel') return 'Output: terminal bell, approvals and critical alerts only (over SSH the speaker is on your side).';
132
+ const p = getPlayer();
133
+ return p ? `Output: speaker via ${p.cmd}.` : 'Output: terminal bell, approvals and critical alerts only (no audio player found).';
134
+ }
135
+
136
+ function whereLine() {
137
+ if (source === 'account') return 'Saved to your account; the dashboard uses the same setting.';
138
+ if (source === 'local') return `Saved on this machine only${serverError ? ` (account setting unreachable: ${serverError})` : ''}.`;
139
+ return `Default${serverError ? ` (account setting unreachable: ${serverError})` : ''}.`;
140
+ }
141
+
142
+ async function command(arg, say) {
143
+ const a = String(arg || '').trim().toLowerCase();
144
+ if (!a) {
145
+ say(`Sound: ${cap(settings.profile)} (${PROFILE_LABELS[settings.profile]}). ${whereLine()}`);
146
+ say(outputLine());
147
+ say('Change with /sound silent|minimal|operational|full · /sound test plays the approval chime · QUOX_SOUND=off mutes this terminal.');
148
+ return;
149
+ }
150
+ if (a === 'test') {
151
+ if (mode === 'off') return say(outputLine(), 'warn');
152
+ const how = emit('hitl');
153
+ return say(how === 'audio' ? `Played the approval chime via ${getPlayer().cmd}.` : how === 'bel' ? 'Rang the terminal bell.' : 'Nothing to play on.', how ? undefined : 'warn');
154
+ }
155
+ if (!PROFILE_IDS.includes(a)) return say(USAGE, 'warn');
156
+ userSet = true;
157
+ settings = normalizeSettings({ ...settings, profile: a });
158
+ safe(() => api?.setLocalSoundProfile?.(a));
159
+ try {
160
+ if (!api?.setSoundSettings) throw new Error('not available in this mode');
161
+ await api.setSoundSettings(settings);
162
+ source = 'account';
163
+ serverError = null;
164
+ } catch (e) {
165
+ serverError = e?.message || 'failed';
166
+ source = api?.setLocalSoundProfile ? 'local' : 'session';
167
+ }
168
+ const where = source === 'account' ? 'Saved to your account (the dashboard uses the same setting).'
169
+ : source === 'local' ? `Saved on this machine only: the account setting could not be written (${serverError}).`
170
+ : `For this session only: ${serverError}.`;
171
+ say(`Sound: ${cap(a)} (${PROFILE_LABELS[a]}). ${where}`, source === 'account' ? 'ok' : 'warn');
172
+ }
173
+
174
+ function dispose() {
175
+ if (!focusOn) return;
176
+ focusOn = false;
177
+ write(FOCUS_OFF);
178
+ }
179
+
180
+ return {
181
+ mode, ready, observe, command, dispose, emit,
182
+ focus(v) { focused = Boolean(v); },
183
+ settings: () => ({ ...settings }),
184
+ get focused() { return focused; }
185
+ };
186
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * QuoxSound for the terminal: pure event -> earcon mapping, profiles and the
3
+ * rate limiter. Mirrors the dashboard's design (src/services/quoxSound.js):
4
+ * sound is STATE, not personality; the more common the event, the quieter;
5
+ * `hitl` is reserved for "a human must act". No sound for typing, text,
6
+ * routine tool rows or anything the human just did themselves.
7
+ *
8
+ * Pure: no I/O, no clocks (time comes in on ctx.now / the action).
9
+ */
10
+
11
+ export const SOUND_IDS = ['hitl', 'notify', 'success', 'denied', 'critical'];
12
+
13
+ /** Higher wins when several sounds land in one burst. */
14
+ export const PRIORITY = { critical: 5, hitl: 4, denied: 3, success: 2, notify: 1 };
15
+
16
+ /** Same layer split as the dashboard, so its per-layer toggles apply here too. */
17
+ export const SOUND_LAYERS = { hitl: 'notifications', notify: 'notifications', success: 'agents', denied: 'security', critical: 'security' };
18
+ export const LAYER_IDS = ['ui', 'agents', 'notifications', 'security'];
19
+
20
+ /**
21
+ * Terminal profiles. Same names and setting as the dashboard. Minimal here
22
+ * also carries failures (owner brief 2026-10-12: "hitl + critical + failure"),
23
+ * because a failed turn in a terminal you are not watching is otherwise silent.
24
+ */
25
+ export const PROFILES = {
26
+ silent: [],
27
+ minimal: ['hitl', 'critical', 'denied'],
28
+ operational: ['hitl', 'critical', 'denied', 'success', 'notify'],
29
+ full: ['hitl', 'critical', 'denied', 'success', 'notify']
30
+ };
31
+ export const PROFILE_IDS = ['silent', 'minimal', 'operational', 'full'];
32
+ export const PROFILE_LABELS = {
33
+ silent: 'nothing',
34
+ minimal: 'approvals, critical alerts, failures',
35
+ operational: 'approvals, critical alerts, failures, long turns finishing',
36
+ full: 'approvals, critical alerts, failures, long turns finishing'
37
+ };
38
+ export const DEFAULT_PROFILE = 'minimal';
39
+
40
+ export const LONG_TURN_MS = 20000;
41
+ export const RATE_LIMIT_MS = 2000;
42
+ /** Sounds this close together are one burst: only a more urgent one may follow. */
43
+ export const BURST_MS = 300;
44
+
45
+ const DEFAULT_LAYERS = Object.freeze({ ui: true, agents: true, notifications: true, security: true });
46
+
47
+ /** Same shape as the dashboard's soundProfile preference: { profile, volume, layers }. */
48
+ export function normalizeSettings(raw) {
49
+ const src = raw && typeof raw === 'object' ? raw : {};
50
+ const profile = PROFILE_IDS.includes(src.profile) ? src.profile : DEFAULT_PROFILE;
51
+ const v = Number(src.volume);
52
+ const volume = Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : 0.6;
53
+ const layers = { ...DEFAULT_LAYERS };
54
+ if (src.layers && typeof src.layers === 'object') {
55
+ for (const id of LAYER_IDS) if (typeof src.layers[id] === 'boolean') layers[id] = src.layers[id];
56
+ }
57
+ return { profile, volume, layers };
58
+ }
59
+
60
+ /** May `id` play under these settings? */
61
+ export function allowed(id, settings) {
62
+ const s = normalizeSettings(settings);
63
+ if (!SOUND_IDS.includes(id) || s.volume <= 0) return false;
64
+ if (!PROFILES[s.profile].includes(id)) return false;
65
+ return s.layers[SOUND_LAYERS[id]] !== false;
66
+ }
67
+
68
+ const FAILED_TURN = new Set(['failed', 'unconfirmed']);
69
+
70
+ /**
71
+ * prevState + the dispatched action (+ ctx.next, the reduced state) -> sound id or null.
72
+ * ctx: { next, now, focused } where focused is true | false | null (unknown = treated as looking).
73
+ * Only state CHANGES the reducer accepted make sound: a duplicate approval or a repeated tool
74
+ * finish leaves the state alone and stays silent.
75
+ */
76
+ export function soundForEvent(prev, action, ctx = {}) {
77
+ const next = ctx.next || prev;
78
+ const now = ctx.now ?? action?.now ?? 0;
79
+ if (!prev || !action) return null;
80
+ // An approval card appeared (stream event or inbox poll).
81
+ if (next.approvals?.length > (prev.approvals?.length || 0) || next.waiting?.length > (prev.waiting?.length || 0)) return 'hitl'; // a card, or another session's notice
82
+ if (action.type === 'approval.result') return action.outcome === 'failed' ? 'denied' : null;
83
+ if (action.type === 'stepup.result') return action.ok ? 'success' : 'denied'; // QUOXON-STEPUP: reuse the existing cues
84
+ if (action.type !== 'event' || !action.ev) return null;
85
+ const ev = action.ev;
86
+ switch (ev.type) {
87
+ case 'alert':
88
+ return ev.severity === 'critical' ? 'critical' : null;
89
+ case 'tool.finished': {
90
+ if (ev.status !== 'failed' && ev.status !== 'denied') return null;
91
+ return prev.doneCalls?.[ev.call_id] ? null : 'denied';
92
+ }
93
+ case 'turn.finished': {
94
+ if (!prev.turn) return null; // dedupe: no running turn
95
+ const status = ev.status || 'completed';
96
+ if (FAILED_TURN.has(status)) return 'denied';
97
+ if (status !== 'completed') return null; // interrupted: the human pressed Esc
98
+ const took = prev.turn.startedAt != null ? now - prev.turn.startedAt : 0;
99
+ return took > LONG_TURN_MS || ctx.focused === false ? 'success' : null;
100
+ }
101
+ default:
102
+ return null;
103
+ }
104
+ }
105
+
106
+ /** At most one sound per type per RATE_LIMIT_MS; inside a burst only a more urgent sound plays. */
107
+ export function createLimiter({ windowMs = RATE_LIMIT_MS, burstMs = BURST_MS } = {}) {
108
+ const last = new Map();
109
+ let lastAny = null;
110
+ return {
111
+ take(id, now) {
112
+ const t = last.get(id);
113
+ if (t != null && now - t < windowMs) return false;
114
+ if (lastAny && now - lastAny.at < burstMs && PRIORITY[id] <= PRIORITY[lastAny.id]) return false;
115
+ last.set(id, now);
116
+ lastAny = { id, at: now };
117
+ return true;
118
+ }
119
+ };
120
+ }