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,157 @@
1
+ /**
2
+ * QUOXON-FW17: /row, custom rows above the status line. Kept out of app.js.
3
+ *
4
+ * url rows save to the user's server-side preferences (api.setRowPrefs, namespace quoxonRows); cmd rows save to a
5
+ * local file only (api.setLocalCmdRows) and are never sent anywhere. The engine (rows.js) does the polling.
6
+ */
7
+ import { checkRow, parseRowCmd, splitRows, rowLabel, normRows, MAX_ROWS, rowDisplay, ROLES, secretInRow, syncable } from '../rows.js';
8
+ import { clip } from '../theme.js';
9
+ import { createRowWalk } from './rowWalk.js';
10
+
11
+ /** How a row reads its data, in words (for /row list). */
12
+ export const whereOf = (r) => {
13
+ const s = r.source;
14
+ if (s.type === 'url') return `url ${s.url}`;
15
+ if (s.type === 'quox') return `quox ${s.path}${s.service === 'collector' ? ' (collector)' : ''}`;
16
+ if (s.type === 'plugin') return `plugin ${s.plugin} ${s.path}`;
17
+ return `cmd ${s.cmd} (this machine only)`;
18
+ };
19
+
20
+ /** ROWS: rows changed elsewhere (`quox rows`, another window) reach an open window within this long. */
21
+ export const RELOAD_MS = 60 * 1000;
22
+
23
+ const cleanLine = (l) => String(l).replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, '');
24
+
25
+ export function createRowCmd({ api, say, engine, bump, cols, setT = setTimeout, clearT = clearTimeout }) {
26
+ let url = []; // synced (url, quox and plugin rows)
27
+ let cmd = []; // this machine only
28
+ let reloadTimer = null;
29
+ const all = () => [...url, ...cmd];
30
+ const apply = () => { engine.setRows(all()); bump(); };
31
+
32
+ /** Start up: the cached/local rows at once, then the server copy. Never throws. */
33
+ async function load() {
34
+ try { url = api.localRowPrefs?.() || []; cmd = api.localCmdRows?.() || []; } catch { url = []; cmd = []; }
35
+ engine.setEnabled(api.settings?.().rows !== 'off');
36
+ apply();
37
+ await refreshFromStores();
38
+ schedule();
39
+ }
40
+
41
+ /** Re-read both stores; the engine keeps the state of rows that did not change. -> true when anything changed. */
42
+ async function refreshFromStores() {
43
+ const before = JSON.stringify(all());
44
+ try { cmd = api.localCmdRows?.() || cmd; } catch { /* keep */ }
45
+ try {
46
+ const r = await api.rowPrefs?.();
47
+ if (r?.source === 'server') url = normRows(r.rows);
48
+ } catch { /* the local copy stands */ }
49
+ const changed = JSON.stringify(all()) !== before;
50
+ if (changed) apply();
51
+ return changed;
52
+ }
53
+
54
+ function schedule() {
55
+ clearT(reloadTimer);
56
+ reloadTimer = setT(async () => { await refreshFromStores(); schedule(); }, RELOAD_MS);
57
+ reloadTimer?.unref?.();
58
+ }
59
+
60
+ /** Save one row (new, or replacing `replace`). Shared by /row add and the walk. -> { ok, text }. */
61
+ async function put(row, { replace = null } = {}) {
62
+ const exists = replace || find(row.id);
63
+ if (!exists && all().length >= MAX_ROWS) return { ok: false, text: `You have ${MAX_ROWS} rows already (the most there is room for). /row rm <id> makes space.` };
64
+ if (exists) { url = url.filter((x) => x !== exists); cmd = cmd.filter((x) => x !== exists); }
65
+ if (exists && !syncable(exists) && syncable(row)) api.setLocalCmdRows?.(cmd);
66
+ if (syncable(row)) {
67
+ url = [...url, row];
68
+ apply();
69
+ const r = api.setRowPrefs ? await api.setRowPrefs(url) : { synced: true };
70
+ if (!r.synced) return { ok: false, text: `Saved on this machine only (${r.error || 'the server did not take it'}). Making the change again retries.` };
71
+ return { ok: true, text: `Row ${row.id} ${exists ? 'saved' : 'added'}. It follows your account to other machines. /row test ${row.id} shows the raw value.` };
72
+ }
73
+ cmd = [...cmd, row];
74
+ apply();
75
+ if (exists && syncable(exists) && api.setRowPrefs) await api.setRowPrefs(url);
76
+ const ok = api.setLocalCmdRows ? api.setLocalCmdRows(cmd) : true;
77
+ return ok ? { ok: true, text: `Row ${row.id} ${exists ? 'saved' : 'added'}. It runs your command on THIS machine only and is never synced.` } : { ok: false, text: 'Could not save the row to the local file; it lasts until you quit.' };
78
+ }
79
+
80
+ const walk = createRowWalk({ say, bump, probe: (row) => engine.probe(row), save: put, existing: () => all() });
81
+
82
+ async function saveUrl(tail = '') {
83
+ if (!api.setRowPrefs) return;
84
+ const r = await api.setRowPrefs(url);
85
+ if (!r.synced) say(`Saved on this machine only (${r.error || 'the server did not take it'}). Making the change again retries.`, 'warn');
86
+ else if (tail) say(tail);
87
+ }
88
+
89
+ const find = (id) => all().find((r) => r.id.toLowerCase() === String(id).toLowerCase());
90
+
91
+ async function handle(arg) {
92
+ const sub = parseRowCmd(arg);
93
+ const w = Math.max(20, cols() - 4);
94
+ switch (sub.kind) {
95
+ case 'error': return say(sub.text, 'warn');
96
+ case 'list': {
97
+ const rows = all();
98
+ if (!rows.length) return say('No custom rows yet. /row new walks you through one (or quox rows add from a shell).');
99
+ say(`Custom rows (${rows.length} of ${MAX_ROWS})${engine.isEnabled() ? '' : ', hidden: /row on shows them'}:`);
100
+ for (const r of rows) {
101
+ const s = engine.state(r.id);
102
+ say(clip(` ${r.id}${r.label && r.label !== r.id ? ` (${r.label})` : ''} every ${r.source.every} ${whereOf(r)}${r.template ? '' : ' [status-row contract]'}`, w));
103
+ if (s && engine.isEnabled()) say(clip(` now: ${rowDisplay(s).text}`, w), s.status === 'error' ? 'warn' : undefined);
104
+ }
105
+ return undefined;
106
+ }
107
+ case 'on': case 'off': {
108
+ engine.setEnabled(sub.kind === 'on');
109
+ api.saveSetting?.('rows', sub.kind);
110
+ bump();
111
+ return say(sub.kind === 'on' ? 'Custom rows are on.' : 'Custom rows hidden and paused. /row on brings them back.');
112
+ }
113
+ case 'rm': {
114
+ const r = find(sub.id);
115
+ if (!r) return say(`No row "${sub.id}". /row list shows them.`, 'warn');
116
+ if (syncable(r)) { url = url.filter((x) => x !== r); apply(); await saveUrl(); } else { cmd = cmd.filter((x) => x !== r); apply(); api.setLocalCmdRows?.(cmd); }
117
+ return say(`Removed row ${r.id}.`, 'ok');
118
+ }
119
+ case 'test': {
120
+ const r = find(sub.id);
121
+ if (!r) return say(`No row "${sub.id}". /row list shows them.`, 'warn');
122
+ say(`Testing ${r.id}…`);
123
+ const p = await engine.probe(r);
124
+ if (!p.ok && !p.raw) return say(clip(`⚠ ${rowLabel(r)}: ${p.error}`, w), 'warn');
125
+ const lines = p.raw.split('\n').filter((l) => l.trim()).slice(0, 4);
126
+ say('Raw value:');
127
+ for (const l of lines) say(clip(` ${cleanLine(l)}`, w));
128
+ if (p.raw.length > 400 || lines.length >= 4) say(' …');
129
+ if (!p.ok) return say(clip(`⚠ ${rowLabel(r)}: ${p.error}`, w), 'warn');
130
+ for (const wn of p.warnings || []) say(clip(`Note: ${wn}`, w), 'warn');
131
+ if (p.seg?.link) say(clip(`Link: ${p.seg.link}`, w));
132
+ return say(clip(`Row shows: ${p.text}`, w), 'ok');
133
+ }
134
+ case 'add': {
135
+ const v = checkRow(sub.row);
136
+ if (v.error) return say(v.error, 'warn');
137
+ const secret = secretInRow(v.row);
138
+ if (secret) return say(secret, 'warn');
139
+ const r = await put(v.row);
140
+ return say(r.text, r.ok ? 'ok' : 'warn');
141
+ }
142
+ case 'new': return walk.start('new');
143
+ case 'edit': return walk.start('edit', sub.id);
144
+ case 'reload': {
145
+ const changed = await refreshFromStores();
146
+ return say(changed ? `Rows reloaded: ${all().map((r) => r.id).join(', ') || 'none'}.` : 'Rows reloaded; nothing changed.');
147
+ }
148
+ default: return undefined;
149
+ }
150
+ }
151
+
152
+ return {
153
+ handle, load, stopEngine: () => { clearT(reloadTimer); engine.stop(); }, view: () => engine.view(), all, colours: ROLES, counts: () => splitRows(all()),
154
+ /** the /row new|edit walk: app.js routes typed lines, its y/N/c card and Esc here */
155
+ walk
156
+ };
157
+ }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * ROWS (owner 2026-10-17): /row new and /row edit <id> in the quox window. The same shape as the hat walk
3
+ * (hatWalk.js): every question is a y/N/c card (askText) or a one-line text capture. Esc, or any slash command,
4
+ * ends the walk and nothing is saved. Before the last card the row is fetched ONCE and the card shows what it
5
+ * will say, so a row is never saved blind.
6
+ *
7
+ * Command rows get their own card naming the exact command, because a command runs a shell on this machine every
8
+ * refresh. This file has no I/O beyond the functions it is given (probe, save, existing).
9
+ */
10
+ import { askText } from '../ask.js';
11
+ import { checkRow, secretInRow, validId, parseEvery, rowLabel } from '../rows.js';
12
+
13
+ export const KINDS = [
14
+ { key: 'url', name: 'a web address (URL) that returns JSON or text', ask: 'Which URL? It is fetched with no credentials.' },
15
+ { key: 'quox', name: 'a path on your own Quox, read with your session', ask: 'Which path? It starts with /api/; {org} is filled in for you. Example: /api/orgs/{org}/inbox/stats' },
16
+ { key: 'plugin', name: 'a status row a plugin ships', ask: 'Which plugin and row path? Example: qpe row' },
17
+ { key: 'cmd', name: 'a command on THIS machine (never synced or shared)', ask: 'Which command? It runs through your shell, 5 s at most.' }
18
+ ];
19
+
20
+ const slug = (s) => String(s ?? '').toLowerCase().replace(/[^a-z0-9_-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 24);
21
+
22
+ /** A kind and the typed target -> a source, or { error }. */
23
+ export function sourceFrom(kind, line, every = '60s') {
24
+ const t = String(line ?? '').trim();
25
+ if (!t) return { error: 'That is empty.' };
26
+ if (kind === 'url') return { source: { type: 'url', url: t, every } };
27
+ if (kind === 'cmd') return { source: { type: 'cmd', cmd: t, every } };
28
+ if (kind === 'quox') return { source: { type: 'quox', service: t.startsWith('/api/v1/') ? 'collector' : 'auth', path: t, every } };
29
+ const [plugin, ...rest] = t.split(/\s+/);
30
+ if (!rest.length) return { error: 'Give the plugin id and the row path, e.g. qpe row.' };
31
+ return { source: { type: 'plugin', plugin, path: rest.join(''), every } };
32
+ }
33
+
34
+ /** "1", "url", "a URL" -> a KINDS key, or null. */
35
+ export function pickKind(line) {
36
+ const t = String(line ?? '').trim().toLowerCase();
37
+ if (/^[1-4]$/.test(t)) return KINDS[Number(t) - 1].key;
38
+ const hit = KINDS.find((k) => k.key === t || (t.length >= 3 && k.name.toLowerCase().includes(t)));
39
+ return hit ? hit.key : (t === 'command' || t === 'shell' ? 'cmd' : null);
40
+ }
41
+
42
+ export function createRowWalk({ say, bump = () => {}, probe, save, existing }) {
43
+ let W = null;
44
+
45
+ const ask = (question, reason = '') => { const st = { at: Date.now() }; W.card = st; say(askText(question, { reason }), 'ask'); return st; };
46
+ const yn = (question, on, reason) => { W.wait = { kind: 'yn', on }; ask(question, reason); bump(); };
47
+ // path: the answer may itself start with / (a quox path like /api/...), so only a known slash command ends the walk
48
+ const text = (prompt, on, { empty = false, path = false } = {}) => { W.wait = { kind: 'text', on, empty, path }; W.card = null; say(prompt); bump(); };
49
+ const end = () => { W = null; bump(); };
50
+
51
+ /** Validate, preview once, then the final card. */
52
+ async function preview(draft, verb) {
53
+ const c = checkRow(draft);
54
+ if (c.error) { say(c.error, 'warn'); return W?.retry?.(); }
55
+ const secret = secretInRow(c.row);
56
+ if (secret) { say(secret, 'warn'); return W?.retry?.(); }
57
+ const row = c.row;
58
+ say(`Fetching ${rowLabel(row)} once to show you what it will say…`);
59
+ const p = await probe(row);
60
+ if (!W) return undefined; // cancelled while fetching
61
+ if (p.ok) {
62
+ say(`It will show: ${p.text}`, 'ok');
63
+ for (const w of p.warnings || []) say(`Note: ${w}`, 'warn');
64
+ } else say(`It could not be read just now (${p.error}). You can still save it; the row shows the problem until it works.`, 'warn');
65
+ yn(`${verb} the ${rowLabel(row)} row?`, async (yes) => {
66
+ if (!yes) { say('Nothing saved.'); return end(); }
67
+ const r = await save(row, { replace: W.replace || null });
68
+ say(r.text, r.ok ? 'ok' : 'warn');
69
+ end();
70
+ }, row.source.type === 'cmd' ? 'Runs on this machine only, never synced' : 'Follows your account to your other machines');
71
+ return undefined;
72
+ }
73
+
74
+ // Shared tail: template, interval, then preview.
75
+ function tail(draft, verb) {
76
+ const askTemplate = () => text(draft.template
77
+ ? `Template? Enter keeps ${draft.template}; "-" uses the status-row contract ({"text": ...}).`
78
+ : 'Template? Enter reads the status-row contract ({"text": ..., "value": ..., "tone": ...}) or plain text. Or type one, e.g. {{pending_approvals}} waiting', (line) => {
79
+ const t = line.trim();
80
+ if (t === '-') delete draft.template; else if (t) draft.template = t;
81
+ askEvery();
82
+ }, { empty: true });
83
+ const askEvery = () => yn(`Refresh every ${draft.source.every}?`, (yes) => {
84
+ if (yes) return preview(draft, verb);
85
+ const again = () => text('How often? 30s, 5m or 1h (5 s to 1 day).', (line) => {
86
+ if (parseEvery(line) === null) { say(`"${line.trim()}" is not an interval.`, 'warn'); return again(); }
87
+ draft.source.every = line.trim(); preview(draft, verb);
88
+ });
89
+ again();
90
+ });
91
+ W.retry = askTemplate;
92
+ askTemplate();
93
+ }
94
+
95
+ function askSource(draft, verb) {
96
+ KINDS.forEach((k, i) => say(` ${i + 1}. ${k.name}`));
97
+ const pick = () => text('Where does it read from? A number.', (line) => {
98
+ const kind = pickKind(line);
99
+ if (!kind) { say('Pick 1, 2, 3 or 4.', 'warn'); return pick(); }
100
+ const target = () => text(KINDS.find((k) => k.key === kind).ask, (l2) => {
101
+ const s = sourceFrom(kind, l2, draft.source?.every || '60s');
102
+ if (s.error) { say(s.error, 'warn'); return target(); }
103
+ draft.source = s.source;
104
+ const c = checkRow({ ...draft, template: draft.template });
105
+ if (c.error && !/template|Template/.test(c.error)) { say(c.error, 'warn'); return target(); }
106
+ const secret = secretInRow(draft);
107
+ if (secret) { say(secret, 'warn'); return target(); }
108
+ if (kind === 'cmd') return yn(`Run \`${s.source.cmd}\` on this machine every ${s.source.every}?`, (yes) => {
109
+ if (!yes) { say('Nothing saved.'); return end(); }
110
+ tail(draft, verb);
111
+ }, 'A command row runs a shell here and is never synced or shared');
112
+ tail(draft, verb);
113
+ }, { path: kind === 'quox' });
114
+ target();
115
+ });
116
+ pick();
117
+ }
118
+
119
+ function startNew() {
120
+ W = { mode: 'new' };
121
+ const draft = { source: { every: '60s' } };
122
+ const askName = () => text('What should the row be called? A short label, e.g. Approvals.', (line) => {
123
+ const label = line.trim().slice(0, 24);
124
+ const id = slug(label);
125
+ if (!validId(id)) { say('Use letters or digits in the name.', 'warn'); return askName(); }
126
+ if (existing().some((r) => r.id.toLowerCase() === id)) { say(`A row called ${id} exists. /row edit ${id} changes it, or pick another name.`, 'warn'); return askName(); }
127
+ draft.id = id; draft.label = label;
128
+ askSource(draft, 'Add');
129
+ });
130
+ askName();
131
+ }
132
+
133
+ function startEdit(id) {
134
+ const cur = existing().find((r) => r.id.toLowerCase() === String(id).toLowerCase());
135
+ if (!cur) { say(`No row "${id}". /row list shows them.`, 'warn'); return; }
136
+ W = { mode: 'edit', replace: cur };
137
+ const draft = JSON.parse(JSON.stringify(cur));
138
+ yn(`Change where ${rowLabel(cur)} reads from?`, (yes) => {
139
+ if (yes) return askSource(draft, 'Save');
140
+ yn('Rename it?', (y2) => {
141
+ if (!y2) return tail(draft, 'Save');
142
+ const again = () => text('What is the new label?', (line) => { const l = line.trim().slice(0, 24); if (!l) { say('A label cannot be empty.', 'warn'); return again(); } draft.label = l; tail(draft, 'Save'); });
143
+ again();
144
+ });
145
+ });
146
+ }
147
+
148
+ return {
149
+ active: () => Boolean(W),
150
+ card: () => (W?.wait?.kind === 'yn' ? W.card : null),
151
+ start(kind, arg = '') {
152
+ if (W) { say('A row walk is already open. Esc cancels it.', 'warn'); return; }
153
+ if (kind === 'new') return startNew();
154
+ return startEdit(arg);
155
+ },
156
+ /** One typed line. true = consumed. A slash command ends the walk and is left to run. */
157
+ answer(line) {
158
+ if (!W?.wait) return false;
159
+ const t = String(line ?? '').trim();
160
+ const isPath = W.wait.kind === 'text' && W.wait.path && t.startsWith('/api/');
161
+ if (t.startsWith('/') && !isPath) { W = null; say('Row walk ended. Nothing was saved.'); bump(); return false; }
162
+ const w = W.wait;
163
+ if (w.kind === 'yn') {
164
+ const yes = /^y(es)?$/i.test(t);
165
+ if (!yes && t && !/^no?$/i.test(t)) return false; // c or free text: the card's normal chat behaviour
166
+ W.wait = null; Promise.resolve(w.on(yes)).catch((e) => { say(`Row walk failed: ${e?.message || 'error'}.`, 'warn'); end(); });
167
+ return true;
168
+ }
169
+ if (!t && !w.empty) return true;
170
+ W.wait = null; Promise.resolve(w.on(t)).catch((e) => { say(`Row walk failed: ${e?.message || 'error'}.`, 'warn'); end(); });
171
+ return true;
172
+ },
173
+ cancel() { if (!W) return false; W = null; say('Row walk cancelled. Nothing was saved.'); bump(); return true; }
174
+ };
175
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * /config settings, saved on the CLI config profile (profile.quoxon = { theme, motion, verbose }), so a
3
+ * choice survives restarts and follows `-p`. Theme replaces the env-only QUOX_THEME: the saved value is
4
+ * applied by setting the variable the theme module already reads, unless the user's shell set it, in
5
+ * which case the shell wins and the card says so.
6
+ */
7
+ import { PROFILE_IDS } from '../sound/mapping.js';
8
+ import { LEVELS, DEFAULT_LEVEL, normaliseLevel } from '../motion/level.js';
9
+
10
+ // Captured at import, before anything here writes it: what the user's shell set.
11
+ const SHELL = {
12
+ theme: process.env.QUOX_THEME || null,
13
+ motion: process.env.QUOX_REDUCED_MOTION === '1' ? 'quiet' : (normaliseLevel(process.env.QUOX_MOTION) || null),
14
+ motionVar: process.env.QUOX_REDUCED_MOTION === '1' ? 'QUOX_REDUCED_MOTION' : 'QUOX_MOTION',
15
+ };
16
+
17
+
18
+ /** The settings this build can honour. Sound is the /sound controller's own profile (server-side soundProfile). */
19
+ export function settingsList({ sound = true } = {}) {
20
+ return [
21
+ { id: 'theme', label: 'Theme', values: ['auto', 'dark', 'light'], def: 'auto', help: 'auto follows your terminal background' },
22
+ { id: 'motion', label: 'Motion', values: LEVELS, def: DEFAULT_LEVEL, help: 'quiet stills it all; expressive adds glows; cinematic adds the dragon' },
23
+ ...(sound ? [{ id: 'sound', label: 'Sound', values: PROFILE_IDS, def: 'minimal', help: 'same as /sound; saved to your account' }] : []),
24
+ { id: 'verbose', label: 'Verbose', values: ['off', 'on', 'full'], def: 'on', help: 'protocol lines in turns' },
25
+ { id: 'slotBell', label: 'Slot bell', values: ['off', 'on'], def: 'on', help: 'a terminal bell when a slot message arrives' },
26
+ { id: 'model', label: 'Model', action: 'model', help: 'Enter opens /model' }
27
+ ];
28
+ }
29
+
30
+ /** Apply saved look settings to this process. -> { themeFromShell, motionFromShell }. */
31
+ export function applySettings(s = {}, env = process.env, shell = SHELL) {
32
+ if (!shell.theme) {
33
+ if (s.theme === 'dark' || s.theme === 'light') env.QUOX_THEME = s.theme; else delete env.QUOX_THEME;
34
+ }
35
+ if (!shell.motion) {
36
+ const level = normaliseLevel(s.motion) || DEFAULT_LEVEL; // pre-levels profiles saved full / reduced
37
+ if (level === DEFAULT_LEVEL) delete env.QUOX_MOTION; else env.QUOX_MOTION = level;
38
+ if (level === 'quiet') env.QUOX_REDUCED_MOTION = '1'; else delete env.QUOX_REDUCED_MOTION; // every older reader agrees
39
+ }
40
+ return { themeFromShell: Boolean(shell.theme), motionFromShell: Boolean(shell.motion) };
41
+ }
42
+
43
+ export const shellOverrides = () => ({ ...SHELL });
44
+
45
+ /** The card rows. `values` = current settings, `extra` = { model: label, verbose: current }. */
46
+ export function configRows(list, values, { shell = SHELL, model = 'auto' } = {}) {
47
+ return list.map(it => {
48
+ if (it.action === 'model') return { key: it.label, text: model, note: it.help };
49
+ const v = it.id === 'motion' ? (normaliseLevel(values.motion) || it.def) : (values[it.id] ?? it.def);
50
+ const motionVar = shell.motionVar || 'QUOX_MOTION';
51
+ const forced = (it.id === 'theme' && shell.theme) ? `QUOX_THEME=${shell.theme} in your shell wins`
52
+ : (it.id === 'motion' && shell.motion) ? (motionVar === 'QUOX_MOTION' ? `QUOX_MOTION=${shell.motion} in your shell wins` : `${motionVar} in your shell wins`) : null;
53
+ return { key: it.label, text: it.values.map(x => (x === v ? `[${x}]` : x)).join(' '), note: forced || it.help };
54
+ });
55
+ }
56
+
57
+ export const cycle = (it, cur, dir = 1) => {
58
+ const i = it.values.indexOf((it.id === 'motion' ? normaliseLevel(cur) : cur) ?? it.def);
59
+ return it.values[(i + dir + it.values.length) % it.values.length];
60
+ };
@@ -0,0 +1,177 @@
1
+ /**
2
+ * /spawn: ask HYPERION (agent atlas) for a disposable VM. VMs are requested ONLY by posting to slot `provisioner`
3
+ * ("VM please <c>c <r>G <d>G for <purpose>") and released with "done <vmid>"; nothing here talks to the pool tool.
4
+ *
5
+ * Lifespan is required (max 24h). At most 3 live VMs per session, counted from local state. Leases are stored
6
+ * locally with their expiry. No server-side slot reminder exists, so expiry is client side: a timer while the TUI
7
+ * runs, `sweep()` on the next start, and /spawn list shows overdue leases in red.
8
+ */
9
+ import { askText } from '../ask.js';
10
+ import { mkdirSync, readFileSync, writeFileSync, renameSync } from 'node:fs';
11
+ import { homedir } from 'node:os';
12
+ import { dirname, join } from 'node:path';
13
+
14
+ export const PROVISIONER = 'provisioner';
15
+ export const MAX_LIVE = 3;
16
+ export const MAX_LIFESPAN_MS = 24 * 3600000;
17
+ const MAX_TIMER = 2147483647;
18
+ export const spawnStatePath = () => join(homedir(), '.config', 'quox', 'quoxon-spawns.json');
19
+
20
+ const UNIT = '(?:d|days?|h|hrs?|hours?|m|mins?|minutes?)';
21
+ const unitMs = (u) => (/^d/i.test(u) ? 86400000 : /^h/i.test(u) ? 3600000 : 60000);
22
+
23
+ /** Loose text -> { cores, ramG, diskG, lifespanMs, objective } plus `missing` (names) and `error` when it cannot proceed. */
24
+ export function parseSpawn(text) {
25
+ let rest = String(text || '').trim();
26
+ const out = { cores: null, ramG: null, diskG: null, lifespanMs: null, objective: null };
27
+ const om = new RegExp(`\\b(?:for|to)\\s+(?!\\d+(?:\\.\\d+)?\\s*${UNIT}\\b)(.+)$`, 'i').exec(rest);
28
+ if (om) { out.objective = om[1].trim(); rest = rest.slice(0, om.index); }
29
+ const take = (re, fn) => { const m = re.exec(rest); if (m) { fn(m); rest = rest.replace(m[0], ' '); } };
30
+ take(/(\d+)\s*(?:c|cores?|cpus?|vcpus?)\b/i, (m) => { out.cores = Number(m[1]); });
31
+ take(/(\d+)\s*g(?:i?b)?\s*(?:of\s+)?(?:ram|mem(?:ory)?)\b/i, (m) => { out.ramG = Number(m[1]); });
32
+ take(/(\d+)\s*g(?:i?b)?\s*(?:of\s+)?(?:disk|storage)\b/i, (m) => { out.diskG = Number(m[1]); });
33
+ take(new RegExp(`(?:\\bfor\\s+)?\\b(\\d+(?:\\.\\d+)?)\\s*(${UNIT})\\b`, 'i'), (m) => { out.lifespanMs = Math.round(Number(m[1]) * unitMs(m[2])); });
34
+ for (let m = /(\d+)\s*g(?:i?b)?\b/i.exec(rest); m && (out.ramG == null || out.diskG == null); m = /(\d+)\s*g(?:i?b)?\b/i.exec(rest)) {
35
+ if (out.ramG == null) out.ramG = Number(m[1]); else out.diskG = Number(m[1]);
36
+ rest = rest.replace(m[0], ' ');
37
+ }
38
+ if (out.diskG == null) out.diskG = 20;
39
+ const missing = [];
40
+ if (!out.cores) missing.push('cores');
41
+ if (!out.ramG) missing.push('ram');
42
+ if (!out.lifespanMs) missing.push('lifespan');
43
+ if (!out.objective) missing.push('objective');
44
+ if (missing.length) out.missing = missing;
45
+ if (out.lifespanMs && out.lifespanMs > MAX_LIFESPAN_MS) out.error = 'Lifespan is capped at 24h.';
46
+ if (out.lifespanMs != null && out.lifespanMs <= 0) out.error = 'Lifespan must be more than zero.';
47
+ return out;
48
+ }
49
+
50
+ /** HYPERION's free-text reply -> { vmid, ip, user } | null (no vmid = not a lease). */
51
+ export function parseLease(text) {
52
+ const t = String(text || '');
53
+ const v = /vm\s*id\D{0,3}(\d{3,6})/i.exec(t) || /\b(9\d{3})\b/.exec(t);
54
+ if (!v) return null;
55
+ const ip = /\b(\d{1,3}(?:\.\d{1,3}){3})\b/.exec(t);
56
+ const u = /\buser\W{0,3}([a-z_][\w-]*)/i.exec(t);
57
+ return { vmid: Number(v[1]), ip: ip ? ip[1] : null, user: u ? u[1] : 'hyp' };
58
+ }
59
+
60
+ export const fmtSpan = (ms) => { const m = Math.round(ms / 60000); return m >= 60 && m % 60 === 0 ? `${m / 60}h` : m >= 60 ? `${Math.floor(m / 60)}h${m % 60}m` : `${Math.max(m, 0)}m`; };
61
+
62
+ const readAll = (p) => { try { const d = JSON.parse(readFileSync(p, 'utf8')); return Array.isArray(d?.leases) ? d : { leases: [] }; } catch { return { leases: [] }; } };
63
+ const writeAll = (d, p) => { try { mkdirSync(dirname(p), { recursive: true }); const tmp = `${p}.${process.pid}.tmp`; writeFileSync(tmp, JSON.stringify(d, null, 2), { mode: 0o600 }); renameSync(tmp, p); } catch { /* a read-only home never breaks the TUI */ } };
64
+
65
+ /**
66
+ * deps: api (slotSay, slotStream), say, session() -> this window's session name, from() -> slot signature,
67
+ * statePath, now, setTimer/clearTimer (tests), replyTimeoutMs.
68
+ */
69
+ export function createSpawnCmd({ api, say, session, from, statePath = spawnStatePath(), now = Date.now, setTimer = (fn, ms) => { const t = setTimeout(fn, ms); t.unref?.(); return t; }, clearTimer = clearTimeout, replyTimeoutMs = 180000 }) {
70
+ const st = { pending: null, waiting: null, timers: new Map() };
71
+
72
+ const leases = ({ live = false } = {}) => readAll(statePath).leases.filter((l) => l.session === session() && (!live || l.state === 'live'));
73
+ const save = (fn) => { const d = readAll(statePath); d.leases = fn(d.leases); writeAll(d, statePath); };
74
+
75
+ function arm(l) {
76
+ clearTimer(st.timers.get(l.vmid));
77
+ const ms = Math.min(Math.max(l.expiresAt - now(), 0), MAX_TIMER);
78
+ st.timers.set(l.vmid, setTimer(() => { st.timers.delete(l.vmid); return release(l.vmid, 'lifespan reached'); }, ms));
79
+ }
80
+
81
+ async function release(vmid, why) {
82
+ const l = leases({ live: true }).find((x) => x.vmid === vmid);
83
+ if (!l) return say(`No live VM ${vmid} in this session. /spawn list shows yours.`, 'warn');
84
+ const r = await api.slotSay(PROVISIONER, `done ${vmid}`, { from: from() });
85
+ if (!r?.ok) return say(`Could not post "done ${vmid}" to ${PROVISIONER}: ${r?.message || 'failed'}. It is still listed; retry /spawn done ${vmid}.`, 'error');
86
+ clearTimer(st.timers.get(vmid)); st.timers.delete(vmid);
87
+ save((all) => all.map((x) => (x.vmid === vmid && x.session === l.session ? { ...x, state: 'released', releasedAt: now() } : x)));
88
+ return say(`VM ${vmid} released to HYPERION (${why}).`, 'ok');
89
+ }
90
+
91
+ function usage() {
92
+ return say('Usage: /spawn <cores>c <ram>G <disk>G <lifespan> for <objective> e.g. /spawn 4 cores 8G 20G for 2h to test the installer. Also: /spawn list, /spawn done <vmid>.', 'warn');
93
+ }
94
+
95
+ async function run(arg = '') {
96
+ const t = String(arg || '').trim();
97
+ if (!t) return usage();
98
+ if (/^list$/i.test(t)) return list();
99
+ const d = /^done\s+(\d+)$/i.exec(t);
100
+ if (d) return release(Number(d[1]), 'released by you');
101
+ st.pending = null;
102
+ const p = parseSpawn(t);
103
+ if (p.error) return say(p.error, 'warn');
104
+ if (p.missing?.includes('lifespan')) return say('How long should it live? Lifespan is required (max 24h): add e.g. 2h or 90m, then run /spawn again.', 'warn');
105
+ if (p.missing) return say(`Missing: ${p.missing.join(', ')}. ${p.missing.includes('objective') ? 'Say what it is for: "for <objective>". ' : ''}Example: /spawn 4c 8G 20G 2h for test the installer`, 'warn');
106
+ const live = leases({ live: true });
107
+ if (live.length >= MAX_LIVE) return say(`This session already has ${live.length} live VMs (cap ${MAX_LIVE}). /spawn done <vmid> frees one: ${live.map((l) => l.vmid).join(', ')}.`, 'warn');
108
+ if (!api.slotSay || !api.slotStream) return say('/spawn needs slots, which this server or mode does not have.', 'warn');
109
+ st.pending = { p, at: now() };
110
+ say(`Request a VM from HYPERION: ${p.cores}c ${p.ramG}G ${p.diskG}G, lifespan ${fmtSpan(p.lifespanMs)}, for: ${p.objective}.`, 'accent');
111
+ say(askText('Go?', { action: `post "VM please ${p.cores}c ${p.ramG}G ${p.diskG}G" to slot ${PROVISIONER}`, reason: `This posts to slot ${PROVISIONER} (HYPERION, agent atlas), signed ${from()}; it releases the VM at expiry.` }), 'ask');
112
+ return undefined;
113
+ }
114
+
115
+ function answer(text) {
116
+ const pe = st.pending;
117
+ if (!pe) return false;
118
+ const t = String(text || '').trim();
119
+ if (t.startsWith('/')) { st.pending = null; return false; }
120
+ st.pending = null;
121
+ if (/^y(es)?$/i.test(t) && now() - pe.at < 600000) { st.waiting = request(pe.p); return true; }
122
+ say('Cancelled; nothing posted.', 'dim');
123
+ return /^no?$/i.test(t);
124
+ }
125
+
126
+ async function request(p) {
127
+ const me = from();
128
+ const since = new Date(now() - 1000).toISOString();
129
+ const ac = new AbortController();
130
+ const timer = setTimeout(() => ac.abort(), replyTimeoutMs);
131
+ try {
132
+ const stream = api.slotStream(PROVISIONER, { since, excludeFrom: me }, ac.signal);
133
+ const reader = (async () => {
134
+ for await (const ev of stream) {
135
+ if (ev?.type !== 'slot.message' || !ev.message || ev.message.from === me) continue;
136
+ const lease = parseLease(ev.message.text);
137
+ if (lease) return { lease, msg: ev.message };
138
+ if (/refus|cannot|can't|no capacity|denied|unable|error|not allowed/i.test(ev.message.text || '')) return { refusal: ev.message.text };
139
+ }
140
+ return null;
141
+ })();
142
+ const purpose = `${p.objective} (lifespan ${fmtSpan(p.lifespanMs)}, released by the requester at expiry)`;
143
+ const r = await api.slotSay(PROVISIONER, `VM please ${p.cores}c ${p.ramG}G ${p.diskG}G for ${purpose}`, { from: me });
144
+ if (!r?.ok) { ac.abort(); await reader.catch(() => {}); return say(`Could not post to ${PROVISIONER}: ${r?.message || 'failed'}. Nothing was requested.`, 'error'); }
145
+ say(`Posted to ${PROVISIONER}; waiting for HYPERION's lease reply.`, 'dim');
146
+ const got = await reader.catch(() => null);
147
+ if (got?.refusal) return say(`HYPERION: ${got.refusal}`, 'warn');
148
+ if (!got?.lease) return say(`No lease reply from HYPERION within ${Math.round(replyTimeoutMs / 1000)}s. The request is posted; check slot ${PROVISIONER} (/slot log) and, if a VM arrives, /spawn done <vmid> when finished.`, 'warn');
149
+ const { lease } = got;
150
+ const rec = { vmid: lease.vmid, ip: lease.ip, user: lease.user, cores: p.cores, ramG: p.ramG, diskG: p.diskG, objective: p.objective, session: session(), leasedAt: now(), expiresAt: now() + p.lifespanMs, state: 'live' };
151
+ save((all) => [...all, rec]);
152
+ arm(rec);
153
+ say(`VM ${lease.vmid} leased${lease.ip ? ` at ${lease.ip}` : ''}, user ${lease.user}, expires in ${fmtSpan(p.lifespanMs)}.`, 'ok');
154
+ if (lease.ip) say(`Connect: ssh ${lease.user}@${lease.ip} (use the key HYPERION's reply names)`, 'ok');
155
+ say(`Release early with /spawn done ${lease.vmid}.`, 'dim');
156
+ return undefined;
157
+ } finally { clearTimeout(timer); }
158
+ }
159
+
160
+ function list() {
161
+ const all = leases();
162
+ if (!all.length) return say('No VMs spawned by this session.', 'dim');
163
+ for (const l of all) {
164
+ const overdue = l.state === 'live' && l.expiresAt < now();
165
+ const when = l.state === 'released' ? 'released' : overdue ? `OVERDUE by ${fmtSpan(now() - l.expiresAt)}, releases on /spawn done ${l.vmid} or next start` : `expires in ${fmtSpan(l.expiresAt - now())}`;
166
+ say(`${l.vmid} ${l.ip || '?'} ${l.user}@ ${l.cores}c ${l.ramG}G ${l.diskG}G ${when} ${l.objective}`, overdue ? 'error' : l.state === 'released' ? 'dim' : undefined);
167
+ }
168
+ return undefined;
169
+ }
170
+
171
+ /** On start: release this session's overdue leases, and re-arm timers for the rest. */
172
+ async function sweep() {
173
+ for (const l of leases({ live: true })) { if (l.expiresAt <= now()) await release(l.vmid, 'overdue at start'); else arm(l); }
174
+ }
175
+
176
+ return { run, answer, sweep, leases, get pending() { return st.pending; }, get waiting() { return st.waiting; } };
177
+ }