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,90 @@
1
+ /**
2
+ * @mention completion in the composer (MENTIONS). Typing `@` at the start of a word suggests agents, fleet hosts,
3
+ * tasks (objectives) and receipts (this session's own tool-call envelopes). The remote lists are fetched ONCE per
4
+ * session on the first `@`, then every keystroke filters the cache: no request per key. A failed fetch is quiet
5
+ * (that kind just has no rows) and is retried at most every RETRY_MS. Tab or Enter accepts, Esc closes.
6
+ */
7
+ export const RETRY_MS = 30000;
8
+ export const KIND_ORDER = ['agent', 'host', 'task', 'receipt'];
9
+
10
+ /** The @word being typed at the end of the draft -> { start (index after '@'), partial } or null. */
11
+ export function mentionContext(draft) {
12
+ const t = String(draft ?? '');
13
+ const m = t.match(/(^|\s)@([^\s@]*)$/);
14
+ if (!m) return null;
15
+ return { start: t.length - m[2].length, partial: m[2] };
16
+ }
17
+
18
+ const clean = (s) => String(s ?? '').replace(/\s+/g, ' ').trim();
19
+
20
+ /** api -> [{ value, kind, label }] from the session's remote sources, never throwing; failures named per kind. */
21
+ export async function loadMentions(api) {
22
+ const kinds = {
23
+ agent: async () => (await api.agents()).map(a => ({ value: String(a.agent_id), kind: 'agent', label: a.name && a.name !== a.agent_id ? clean(a.name) : '' })),
24
+ host: async () => {
25
+ const none = { status: 'absent', value: [] };
26
+ const [inv, beat] = await Promise.all([api.mentionHosts, api.objects?.fleetNames].map((fn, i) => (fn ? Promise.allSettled([i ? api.objects.fleetNames() : api.mentionHosts()]).then(r => r[0]) : none)));
27
+ const tried = [inv, beat].filter(r => r.status !== 'absent');
28
+ if (tried.length && tried.every(r => r.status === 'rejected')) throw tried[0].reason;
29
+ const out = (inv.status === 'fulfilled' ? inv.value : []).map(h => ({ value: String(h.id), kind: 'host', label: clean([h.group, h.health].filter(Boolean).join(' · ')) }));
30
+ for (const h of beat.status === 'fulfilled' ? beat.value || [] : []) if (!out.some(o => o.value === String(h.value))) out.push({ value: String(h.value), kind: 'host', label: clean(h.desc) });
31
+ return out;
32
+ },
33
+ task: async () => ((await api.mentionTasks?.()) || []).map(o => ({ value: String(o.id), kind: 'task', label: clean(`${o.title || ''}${o.status ? ` (${o.status})` : ''}`) })),
34
+ };
35
+ const out = []; const failed = [];
36
+ await Promise.all(Object.entries(kinds).map(async ([k, fn]) => {
37
+ try { out.push(...(await fn()).filter(x => x.value && /^[\w.:-]+$/.test(x.value))); } catch { failed.push(k); }
38
+ }));
39
+ return { items: out, failed };
40
+ }
41
+
42
+ /** This session's tool calls that carry an envelope id: their receipts, newest first. Local, no request. */
43
+ export function receiptMentions(items) {
44
+ const seen = new Set(); const out = [];
45
+ for (const b of [...(items || [])].reverse()) {
46
+ if (!b || b.kind !== 'tool' || !b.envelopeId || seen.has(b.envelopeId)) continue;
47
+ seen.add(b.envelopeId);
48
+ out.push({ value: String(b.envelopeId), kind: 'receipt', label: clean(`${b.tool || 'tool call'}${b.status ? ` ${b.status}` : ''}`) });
49
+ }
50
+ return out;
51
+ }
52
+
53
+ /** Prefix on the value first, then a substring of the value or its label; agents, hosts, tasks, receipts in that order. */
54
+ export function rankMentions(items, partial, max = 50) {
55
+ const q = String(partial || '').toLowerCase();
56
+ const seen = new Set();
57
+ const uniq = (items || []).filter(i => i?.value && !seen.has(`${i.kind}:${i.value}`) && seen.add(`${i.kind}:${i.value}`));
58
+ const order = (a, b) => KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b.kind);
59
+ if (!q) return uniq.sort(order).slice(0, max);
60
+ const pre = uniq.filter(i => i.value.toLowerCase().startsWith(q)).sort(order);
61
+ const sub = uniq.filter(i => !i.value.toLowerCase().startsWith(q) && (i.value.toLowerCase().includes(q) || i.label.toLowerCase().includes(q))).sort(order);
62
+ const out = [...pre, ...sub];
63
+ return out.length === 1 && out[0].value.toLowerCase() === q ? [] : out.slice(0, max);
64
+ }
65
+
66
+ /** Accepting a mention: replace the partial word, add one space. */
67
+ export const applyMention = (draft, cx, value) => String(draft).slice(0, cx.start) + value + ' ';
68
+
69
+ /**
70
+ * One cache for the session. items() returns what is loaded (never blocks) and starts the single load the first
71
+ * time it is asked; a failed kind is retried no sooner than RETRY_MS. loads() counts fetches (tests, /status).
72
+ */
73
+ export function createMentionCache({ load, refresh = () => {}, now = Date.now, retryMs = RETRY_MS }) {
74
+ let items = []; let state = 'idle'; let failedAt = 0; let loads = 0;
75
+ const start = () => {
76
+ state = 'loading'; loads++;
77
+ Promise.resolve().then(load).then(
78
+ (r) => { items = r?.items || []; state = r?.failed?.length ? 'partial' : 'ready'; if (state === 'partial') failedAt = now(); },
79
+ () => { state = 'failed'; failedAt = now(); }
80
+ ).then(() => refresh());
81
+ };
82
+ return {
83
+ items() {
84
+ if (state === 'idle' || ((state === 'failed' || state === 'partial') && now() - failedAt >= retryMs)) start();
85
+ return items;
86
+ },
87
+ state: () => state,
88
+ loads: () => loads,
89
+ };
90
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * /mind: QuoxMind cognitive implants, read from the collector (same data as `quox mind`).
3
+ * Pure: takes api.mind() output, returns [[text, tone]]. Implants with nothing recorded
4
+ * say "no activity recorded"; nothing is invented. Lines are not clipped: notes wrap to the
5
+ * terminal width, so a long description reads in full (owner drive 2026-10-15).
6
+ */
7
+
8
+ const MARK = { healthy: '✔', degraded: '⚠', weak: '⚠', collecting: '◐', 'not-reported': '·', unreachable: '✖', 'license-required': '⛔' };
9
+
10
+ /** The 14 implant short ids (the collector's registry, in order). Used for help and for "did you mean". */
11
+ export const MIND_IMPLANTS = ['sense', 'repobrain', 'memory', 'temporal', 'focus', 'value', 'guard', 'qortex', 'insight', 'executive', 'predict', 'reflect', 'dream', 'learn'];
12
+ const SHORT_OF = { 'quox-nucleus': 'repobrain', 'quox-brain2': 'qortex' };
13
+ const shortOf = (id) => SHORT_OF[id] || String(id).replace(/^quox-/, '');
14
+
15
+ export const MIND_HELP_ARGS = new Set(['help', '-h', '--help', '?']);
16
+
17
+ /** /mind help: the forms and the implant ids. No server call. */
18
+ export function mindHelpLines() {
19
+ return [
20
+ ['/mind lists every QuoxMind implant with its status and last activity. Read-only, no model call.'],
21
+ ['/mind <implant> shows one implant in full: what it does, how it grows, its metrics.'],
22
+ [`Implants: ${MIND_IMPLANTS.join(', ')}.`],
23
+ ];
24
+ }
25
+
26
+ /**
27
+ * A failed /mind call as plain lines. A 404 "unknown implant" names the implant that does not exist,
28
+ * lists the real ones, and points at a TUI command of the same name when there is one (/mind doctor -> /doctor).
29
+ * Any other failure reports the server's own reason, not just "Request failed with status N".
30
+ */
31
+ export function mindErrorLines(e, arg, { isCommand = () => false } = {}) {
32
+ const body = e?.body && typeof e.body === 'object' ? e.body : {};
33
+ const reason = body.error || body.message || (typeof e?.code === 'string' && !/^E[A-Z]/.test(e.code) ? e.code : '') || e?.message || 'failed';
34
+ if (arg && (e?.status === 404 || /unknown implant/i.test(reason))) {
35
+ const ids = Array.isArray(body.valid) && body.valid.length ? body.valid.map(shortOf) : MIND_IMPLANTS;
36
+ const out = [[`No implant called "${arg}". /mind <implant> shows one implant's status (read-only).`, 'warn'],
37
+ [`Implants: ${ids.join(', ')}.`]];
38
+ const cmd = String(arg).split(/\s+/)[0].toLowerCase();
39
+ if (isCommand(cmd)) out.push([`For the ${cmd} command, use /${cmd}.`]);
40
+ else out.push(['/mind on its own lists them all.']);
41
+ return out;
42
+ }
43
+ return [[`mind: ${e?.status ? `HTTP ${e.status}: ` : ''}${reason}`, 'error']];
44
+ }
45
+
46
+ /** Relative "fired" time for an ISO timestamp; 'never' when null. `now` is injectable for tests. */
47
+ export function fmtFired(iso, now = Date.now()) {
48
+ if (!iso) return 'never';
49
+ const t = Date.parse(iso);
50
+ if (Number.isNaN(t)) return String(iso);
51
+ const s = Math.max(0, Math.round((now - t) / 1000));
52
+ if (s < 60) return 'just now';
53
+ if (s < 3600) return `${Math.floor(s / 60)}m ago`;
54
+ if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
55
+ return `${Math.floor(s / 86400)}d ago`;
56
+ }
57
+
58
+ /**
59
+ * The firing evidence for one implant (QUOXMIND-PROOF): last fired + 7-day count from the server.
60
+ * Returns null when the server predates the evidence fields, so the old line is kept.
61
+ */
62
+ function firingText(i, now) {
63
+ if (!('fired_7d' in i)) return null;
64
+ if (i.activity_error) return `firing unknown: ${i.activity_error}`;
65
+ if (typeof i.fired_7d !== 'number') return 'firing not tracked';
66
+ const idle = typeof i.idle_runs_7d === 'number' && i.idle_runs_7d > 0 ? ` (${i.idle_runs_7d} idle runs)` : '';
67
+ return `${i.last_fired ? `fired ${fmtFired(i.last_fired, now)}` : 'never fired'} · 7d ${i.fired_7d}${idle}`;
68
+ }
69
+
70
+ export function mindLines(d, { now = Date.now() } = {}) {
71
+ const out = [];
72
+ const add = (t, tone) => out.push([t, tone]);
73
+ if (d.implant) {
74
+ const i = d.implant;
75
+ add(`${MARK[i.status] || '·'} ${i.name} (${i.short}) · ${i.status}${i.tier ? ` · ${i.tier}` : ''}`);
76
+ add(` ${i.description || i.purpose}`);
77
+ if (i.inspired_by) add(` inspired by: ${i.inspired_by}`);
78
+ if (i.summary) add(` now: ${i.summary}`);
79
+ add(` ${i.activity}`);
80
+ const fired = firingText(i, now);
81
+ if (fired && typeof i.fired_7d === 'number') add(` fired: ${fmtFired(i.last_fired, now)}${i.last_fired ? ` (${i.last_fired})` : ''} · 7d ${i.fired_7d}${i.activity_source ? ` · from ${i.activity_source}` : ''}`);
82
+ else if (fired) add(` ${fired}`);
83
+ if (i.how_to_grow) add(` how it grows: ${i.how_to_grow}`);
84
+ if (Array.isArray(i.connected_to) && i.connected_to.length) add(` connected to: ${i.connected_to.map(shortOf).join(', ')}`);
85
+ for (const [k, v] of Object.entries(i.metrics || {})) if (v !== null && typeof v !== 'object') add(` ${k}: ${typeof v === 'number' && !Number.isInteger(v) ? Math.round(v * 100) / 100 : v}`);
86
+ const others = MIND_IMPLANTS.filter(s => s !== i.short);
87
+ add(`This is ${i.name}'s status, read-only: nothing runs. /mind for all; others: ${others.map(s => `/mind ${s}`).join(', ')}.`, 'dim');
88
+ return out;
89
+ }
90
+ const list = d.implants || [];
91
+ add(`QuoxMind: ${list.length} cognitive implants`);
92
+ for (const i of list) {
93
+ const fired = firingText(i, now);
94
+ const tail = fired !== null
95
+ ? `${fired}${typeof i.fired_7d === 'number' && !i.activity_error && i.summary && i.activity !== 'no activity recorded' ? ` · ${i.summary}` : ''}`
96
+ : (i.activity === 'no activity recorded' ? 'no activity recorded' : i.summary || i.activity);
97
+ add(`${MARK[i.status] || '·'} ${i.short.padEnd(10)} ${i.status.padEnd(13)} ${tail}`, i.status === 'unreachable' ? 'warn' : undefined);
98
+ }
99
+ const h = d.health;
100
+ if (h && typeof h.fired_7d_total === 'number') add(`${h.fired_7d_implants} of ${list.length} fired in the last 7 days (${h.fired_7d_total} firings), ${h.silent_7d} silent, ${h.activity_unknown} not tracked. /mind <id> for one.`);
101
+ else if (h) add(`${h.with_activity} with activity, ${h.no_activity} with none recorded${h.average_health !== null ? `, average health ${h.average_health}` : ''}. /mind <id> for one.`);
102
+ return out;
103
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * /mode (owner 2026-10-16 "/say quoxon a but log b"): this window's mode.
3
+ * ask (default) Quox asks you before it acts; what needs approval is your org's policy.
4
+ * plan Quox investigates (read tools) and proposes the steps and exact commands; it runs nothing that
5
+ * changes state. The SERVER enforces it: the turn carries mode 'plan' (lib/planMode.js in the collector).
6
+ * auto not built. Logged as a plan: it needs standing grants first.
7
+ * Window-scoped: carried across /restart (QUOX_RESTART_MODE), never written to the shared profile.
8
+ */
9
+
10
+ export const MODES = ['ask', 'plan'];
11
+ export const AUTO_NOT_YET = 'not available yet: auto mode needs standing grants (planned).';
12
+ export const PLAN_GO = 'go ahead with the plan';
13
+
14
+ /** -> 'plan' | 'ask' (anything else, including 'auto', is 'ask'). */
15
+ export function normMode(v) {
16
+ return String(v || '').trim().toLowerCase() === 'plan' ? 'plan' : 'ask';
17
+ }
18
+
19
+ const DESC = {
20
+ ask: 'ask first: Quox asks you before it acts. What needs your approval is your org\'s policy on the server (/approvals lists what is waiting).',
21
+ plan: 'plan only: Quox investigates with read tools and proposes the steps and the exact commands. It runs nothing that changes state; the server enforces it.',
22
+ };
23
+
24
+ /** One line per mode for the arrow-key chooser (the same one /model uses). Auto is not offered: it is not built. */
25
+ export const MODE_ROWS = [
26
+ { id: 'ask', text: 'ask first: Quox asks before it acts' },
27
+ { id: 'plan', text: 'plan only: Quox proposes the steps and runs nothing that changes state' },
28
+ ];
29
+
30
+ /**
31
+ * Pure: the reply to `/mode [arg]` given the current mode.
32
+ * -> { text, tone, mode? } where mode is set only when it changes.
33
+ */
34
+ export function modeReply(arg, current) {
35
+ const cur = normMode(current);
36
+ const a = String(arg || '').trim().toLowerCase();
37
+ if (!a || /^(-h|--help|help|\?)$/.test(a)) {
38
+ return { tone: null, text: `mode ${DESC[cur]}\nOptions: /mode ask (the default), /mode plan (plan only). Auto mode is ${AUTO_NOT_YET}` };
39
+ }
40
+ if (a === 'auto') return { tone: 'warn', text: `/mode auto is ${AUTO_NOT_YET} Options: /mode ask, /mode plan.` };
41
+ if (!MODES.includes(a)) return { tone: 'warn', text: `Unknown mode '${clipWord(a)}'. Options: /mode ask, /mode plan.` };
42
+ if (a === cur) return { tone: 'dim', text: `Already in mode ${DESC[cur]}` };
43
+ return { mode: a, tone: 'ok', text: a === 'plan' ? `mode ${DESC.plan} /mode ask to go back.` : `mode ${DESC.ask}` };
44
+ }
45
+
46
+ function clipWord(w) { return w.length > 20 ? `${w.slice(0, 19)}…` : w; }
@@ -0,0 +1,311 @@
1
+ /**
2
+ * /model: which provider (and optionally which model) the next turns ask the server for.
3
+ * Pure, apart from the vault list result passed in.
4
+ *
5
+ * The choice is a REQUEST: the stream body carries `provider` (and `model`), and the collector's
6
+ * policy plan (resolveCollectorPolicyPlan) decides. turn.started.provider/model is the truth; when it
7
+ * differs from what was asked, the client says so once (never a silent fallback).
8
+ * null choice = auto (no provider in the body; the server's policy picks).
9
+ *
10
+ * Availability is the same source as /connect, /doctor and /capabilities (providers.js): the server's
11
+ * capabilities, with the org vault for detail.
12
+ * Model ids mirror src/config/modelCatalog.js (openai + anthropic rows); an id not listed is passed
13
+ * through with a note, the server decides whether it exists.
14
+ */
15
+ import { PROVIDERS as CONNECT, offerText } from './connect.js';
16
+ import { providerState, SUBSCRIPTION_NAME } from './providers.js';
17
+
18
+ /**
19
+ * No hardcoded model lists for the hosted providers: OpenAI and the Claude API are listed live by the server
20
+ * (GET /api/v1/providers/<p>/models, 1 h server cache, refreshed by /model). The Claude SUBSCRIPTION has no
21
+ * key to list with, so it takes the Claude CLI's family aliases, which the CLI resolves to the newest model.
22
+ */
23
+ export const CLAUDE_FAMILIES = ['fable', 'opus', 'sonnet', 'haiku'];
24
+
25
+ export const CHOICES = [
26
+ { id: 'openai', provider: 'openai', name: 'OpenAI', vault: 'openai', connect: 'openai', discover: true, models: [] },
27
+ { id: 'anthropic', provider: 'anthropic', name: 'Claude API', vault: 'anthropic', connect: 'anthropic', discover: true, models: [] },
28
+ // R3: the server's signed-in Claude subscription (dev only). The stream asks for anthropic; with no Claude API
29
+ // key the server runs it through the claude CLI on that sign-in. `via` only tells the client which row it is.
30
+ { id: 'claude-cli', provider: 'anthropic', via: 'subscription', cap: 'claude_subscription', name: SUBSCRIPTION_NAME, aliases: CLAUDE_FAMILIES, models: [] },
31
+ // B3: served by the chat stream; models are whatever that Ollama has installed (listed by the server).
32
+ { id: 'ollama', provider: 'ollama', name: 'Ollama', vault: 'ollama', connect: 'ollama', dynamic: true, hint: 'http://<host>:11434', models: [] },
33
+ // B5: OpenAI-compatible presets. Models come from the provider's own /models (api.compatModels).
34
+ // hosted: a model is required (listed, never defaulted); selfHosted: the connection default or the first served.
35
+ ...[
36
+ ['openrouter', 'OpenRouter', 'openrouter', true, ''], ['huggingface', 'Hugging Face', 'huggingface', true, ''],
37
+ ['groq', 'Groq', 'groq', true, ''], ['together', 'Together', 'together', true, ''],
38
+ ['lmstudio', 'LM Studio', 'lmstudio', false, ' http://<host>:1234'], ['vllm', 'vLLM', 'vllm', false, ' http://<host>:8000'],
39
+ ['custom', 'Custom', 'openai_compatible', false, ' https://<host>/v1']
40
+ ].map(([id, name, vault, hosted, hint]) => ({ id, provider: id, name, vault, connect: id, dynamic: true, compat: true, hosted, selfHosted: !hosted, hint: hint.trim(), models: [] }))
41
+ ];
42
+ const ALIASES = { 'claude-subscription': 'claude-cli', subscription: 'claude-cli', 'claude-code': 'claude-cli', gpt: 'openai', chatgpt: 'openai', claude: 'anthropic', 'claude-api': 'anthropic', 'anthropic-api': 'anthropic', local: 'ollama', hf: 'huggingface', 'lm-studio': 'lmstudio', 'openai-compatible': 'custom', openai_compatible: 'custom', default: 'auto', policy: 'auto' };
43
+ const byProvider = (p) => CHOICES.find(c => c.provider === p) || null;
44
+ /** The CHOICES row a stored choice is (the subscription row shares provider anthropic). */
45
+ const rowOf = (ch) => (ch?.via === 'subscription' ? CHOICES.find(c => c.via === 'subscription') : byProvider(ch?.provider));
46
+
47
+ /** Stored or flag value -> { provider, model? } | null. Only the two known fields survive. */
48
+ export function normChoice(v) {
49
+ if (!v || typeof v !== 'object' || typeof v.provider !== 'string' || !byProvider(v.provider)) return null;
50
+ const out = typeof v.model === 'string' && v.model.trim() ? { provider: v.provider, model: v.model.trim() } : { provider: v.provider };
51
+ if (v.provider === 'anthropic' && v.via === 'subscription') out.via = 'subscription';
52
+ // B3/B5: the Ollama / self-hosted host, for the status line before the first turn (turn.started.endpoint wins after).
53
+ if ((v.provider === 'ollama' || byProvider(v.provider).selfHosted) && typeof v.host === 'string' && v.host.trim()) out.host = v.host.trim().slice(0, 120);
54
+ // D3: prompt profile (compact | full); absent = the server default (compact on ollama / local presets, full elsewhere).
55
+ if (PROFILES.includes(v.profile)) out.profile = v.profile;
56
+ return out;
57
+ }
58
+
59
+ export const PROFILES = ['compact', 'full'];
60
+
61
+ /** "/model <provider> [model] [compact|full]" text -> { ok:true, choice } (choice null = auto) | { ok:false, error }. */
62
+ export function parseModelArg(arg) {
63
+ const words = String(arg || '').trim().split(/\s+/).filter(Boolean);
64
+ const profile = words.length > 1 && PROFILES.includes(String(words[words.length - 1]).toLowerCase()) ? words.pop().toLowerCase() : null;
65
+ const [rawName, model] = words;
66
+ const n = String(rawName || '').toLowerCase();
67
+ const id = ALIASES[n] || n;
68
+ if (id === 'auto') return { ok: true, choice: null };
69
+ const c = CHOICES.find(x => x.id === id);
70
+ if (!c) return { ok: false, error: `No provider called "${rawName}". Use ${CHOICES.map(x => x.id).join(', ')} or auto (/model lists them).` };
71
+ const resolved = model;
72
+ const choice = resolved ? { provider: c.provider, model: resolved } : { provider: c.provider };
73
+ if (c.via) choice.via = c.via;
74
+ if (profile) choice.profile = profile;
75
+ return { ok: true, choice };
76
+ }
77
+
78
+ /** -> { state, row?, reason? } for one choice, from the shared provider status (providers.js). */
79
+ const availability = (res, c, caps) => providerState({ cap: c.cap || c.id, vault: c.vault }, caps, res);
80
+
81
+ /** QUOXON-FW6: a typed provider name or alias -> the CHOICES id, or null. */
82
+ export const providerId = (name) => {
83
+ const n = String(name || '').trim().toLowerCase();
84
+ const id = ALIASES[n] || n;
85
+ return CHOICES.some(c => c.id === id) ? id : null;
86
+ };
87
+ /** The CHOICES id a stored choice is (the subscription row shares provider anthropic). */
88
+ export const choiceId = (ch) => (ch ? rowOf(ch)?.id || null : null);
89
+ /** QUOXON-FW6: the CHOICES rows usable on this server now. Never lists a provider not connected. */
90
+ export const connectedChoices = (res, caps = null) =>
91
+ CHOICES.filter(c => c.served !== false && availability(res, c, caps).state === 'connected');
92
+
93
+ const same = (a, b) => (a?.provider || null) === (b?.provider || null) && (a?.via || null) === (b?.via || null);
94
+
95
+ /** -> [[text, tone?]] lines for /model. `caps` = api.capabilities() (the server's word on each provider). */
96
+ export function modelLines(res, current, caps = null) {
97
+ const out = [['Model for your next turns (the server decides; turn.started shows what ran):']];
98
+ const mark = (on) => (on ? '▸' : ' ');
99
+ out.push([`${mark(!current)} auto server policy picks the provider and model · /model auto`]);
100
+ for (const c of CHOICES) {
101
+ const a = availability(res, c, caps);
102
+ const picked = same(current, c);
103
+ const sel = picked && current.model ? ` · using ${current.model}` : '';
104
+ if (c.via === 'subscription') {
105
+ const api = availability(res, byProvider('anthropic'), caps).state === 'connected';
106
+ if (a.state === 'connected') out.push([`${mark(picked)} ● ${c.name} ready on this server${sel} · /model claude-cli${api ? ' (the Claude API key is used first while it is connected)' : ''}`, 'ok']);
107
+ else if (a.state === 'unknown') out.push([`${mark(picked)} ? ${c.name} unknown (the server did not say)`, 'warn']);
108
+ else out.push([`${mark(picked)} ○ ${c.name} ${a.reason || 'not signed in on this server'} · sign in from the dashboard Terminal`]);
109
+ continue;
110
+ }
111
+ if (c.served === false) {
112
+ out.push([`${mark(picked)} ${c.name} ${a.state === 'connected' ? 'connected, but ' : ''}not served by the chat stream yet`]);
113
+ continue;
114
+ }
115
+ if (c.dynamic) {
116
+ if (a.state === 'connected') out.push([`${mark(picked)} ● ${c.name} connected · ${a.row.label || a.row.id}${sel} · /model ${c.id} lists its ${c.compat ? '' : 'installed '}models`, 'ok']);
117
+ else if (a.state === 'stored') out.push([`${mark(picked)} ◐ ${c.name} stored but not the default · quox vault set-default ${a.row.id}`, 'warn']);
118
+ else if (a.state === 'missing' || a.state === 'unavailable') out.push([`${mark(picked)} ○ ${c.name} not connected · /connect ${c.connect}${c.hint ? ` ${c.hint}` : ''}`]);
119
+ else out.push([`${mark(picked)} ? ${c.name} unknown (vault not readable)${sel}`, 'warn']);
120
+ continue;
121
+ }
122
+ const models = c.models.join(', ');
123
+ if (a.state === 'connected') out.push([`${mark(picked)} ● ${c.name} connected${sel} · /model ${c.id} [${models || 'live list'}]`, 'ok']);
124
+ else if (a.state === 'unavailable') out.push([`${mark(picked)} ○ ${c.name} ${a.reason || 'not available on this server'}`]);
125
+ else if (a.state === 'stored') out.push([`${mark(picked)} ◐ ${c.name} stored but not the default · quox vault set-default ${a.row.id}`, 'warn']);
126
+ else if (a.state === 'missing') out.push([`${mark(picked)} ○ ${c.name} not connected · /connect ${c.connect}`]);
127
+ else out.push([`${mark(picked)} ? ${c.name} unknown (vault not readable)${sel}`, 'warn']);
128
+ }
129
+ if (!res?.ok) out.push([` could not read the vault: ${res?.error || 'no answer'}`, 'warn']);
130
+ return out;
131
+ }
132
+
133
+ /**
134
+ * The /model <arg> decision. -> { ok, choice?, text, tone? }. A provider the vault says is not usable is
135
+ * refused (with the /connect offer), never quietly swapped for another.
136
+ */
137
+ export function selectModel(res, arg, caps = null) {
138
+ const p = parseModelArg(arg);
139
+ if (!p.ok) return { ok: false, text: p.error, tone: 'warn' };
140
+ if (!p.choice) return { ok: true, choice: null, text: 'Model: auto. The server policy picks the provider and model.', tone: 'ok' };
141
+ const c = rowOf(p.choice);
142
+ if (c.served === false) return { ok: false, text: `${c.name} is not served by the chat stream yet, so I can't use it here. /model lists what can.`, tone: 'warn' };
143
+ const a = availability(res, c, caps);
144
+ const label0 = p.choice.model ? `${c.id} ${p.choice.model}` : c.id;
145
+ if (c.via === 'subscription') {
146
+ if (a.state === 'unknown') return { ok: true, choice: p.choice, text: `Model: ${label0}. Could not confirm the ${c.name} is signed in; the server will say if it is not.`, tone: 'warn' };
147
+ if (a.state !== 'connected') return { ok: false, text: `${c.name} is not ready on this server (${a.reason || 'not signed in'}). Sign in from the dashboard Terminal.`, tone: 'warn' };
148
+ const api = availability(res, byProvider('anthropic'), caps).state === 'connected';
149
+ return { ok: true, choice: p.choice, text: `Model: ${c.name}${p.choice.model ? ` · ${p.choice.model}` : ''}. ${api ? 'The Claude API key is connected, so the server uses it first.' : 'Your next turns ask for it.'}`, tone: api ? 'warn' : 'ok' };
150
+ }
151
+ if (c.id === 'anthropic' && a.state === 'missing' && availability(res, rowOf({ via: 'subscription' }), caps).state === 'connected') {
152
+ // what --provider anthropic does: no API key, so the server runs Claude on its signed-in subscription
153
+ return { ok: true, choice: { ...p.choice, via: 'subscription' }, text: `Model: ${label0}. The Claude API isn't connected, so your next turns run on the ${SUBSCRIPTION_NAME}.`, tone: 'warn' };
154
+ }
155
+ if (c.dynamic && a.state === 'missing') return { ok: false, text: `${c.name} isn't connected, so I can't use it yet. Connect it: /connect ${c.connect}${c.hint ? ` ${c.hint}` : ''}`, tone: 'warn' };
156
+ if (a.state === 'missing') return { ok: false, text: offerText(CONNECT[c.connect]), tone: 'warn' };
157
+ if (a.state === 'unavailable') return { ok: false, text: `${c.name} isn't available on this server${a.reason ? `: ${a.reason}` : ''}.`, tone: 'warn' };
158
+ if (a.state === 'stored') return { ok: false, text: `${c.name} has a key stored, but not as the org default, so the server will not use it. Fix: quox vault set-default ${a.row.id}`, tone: 'warn' };
159
+ const label = p.choice.model ? `${c.id} ${p.choice.model}` : c.id;
160
+ const extra = p.choice.model && !c.dynamic && !c.discover && !c.models.includes(p.choice.model) ? ` ${p.choice.model} is not in this client's list; the server decides whether it exists.` : '';
161
+ if (a.state === 'unknown') return { ok: true, choice: p.choice, text: `Model: ${label}. Could not confirm ${c.name} is connected (the server did not say); the server will say if it is not.${extra}`, tone: 'warn' };
162
+ return { ok: true, choice: p.choice, text: `Model: ${label}. Your next turns ask for it.${extra}`, tone: extra ? 'warn' : 'ok' };
163
+ }
164
+
165
+ /** Newest-first discovered rows -> ids. */
166
+ const ids = (res) => (res?.ok && Array.isArray(res.models) ? res.models.map(modelId).filter(Boolean) : []);
167
+
168
+ /**
169
+ * /model openai|anthropic [model] against the live list (res from api.providerModels(provider)).
170
+ * Match order: exact id, "latest" (newest listed), a family (opus, sonnet, mini, ...) or any prefix/substring ->
171
+ * the NEWEST match. No match -> a clear error with the list. Discovery failed -> any typed id is allowed with a
172
+ * plain note (the provider validates it on first use). -> { ok, choice?, text, tone, models?, current? }
173
+ */
174
+ export function selectDiscoveredModel(res, choice, row) {
175
+ const name = row.name;
176
+ const model = choice.model || null;
177
+ const list = ids(res);
178
+ const rows = res?.ok ? res.models : [];
179
+ if (!res?.ok || !list.length) {
180
+ const why = res?.message || res?.error || 'no answer';
181
+ if (!model) return { ok: true, choice, text: `Model: ${row.id}. Could not list ${name}'s models (${why}); the server uses its default.`, tone: 'warn' };
182
+ return { ok: true, choice, text: `Model: ${row.id} ${model}. Could not list ${name}'s models (${why}); ${model} is sent as typed and the provider will say if it does not exist.`, tone: 'warn' };
183
+ }
184
+ const newest = res.default_model || list[0];
185
+ if (!model) return { ok: true, choice, text: `Model: ${name} · ${newest} (the newest listed). Models below; pick one with /model ${row.id} <model|family|latest>.`, tone: 'ok', models: rows, current: newest };
186
+ const q = model.toLowerCase();
187
+ const hit = q === 'latest' ? list[0]
188
+ : list.find(i => i.toLowerCase() === q)
189
+ || rows.find(r => r.family && r.family === q)?.id
190
+ || list.find(i => i.toLowerCase().startsWith(q))
191
+ || list.find(i => i.toLowerCase().includes(q));
192
+ if (!hit) return { ok: false, text: `${model} is not in ${name}'s live list. Pick an id, a family (${[...new Set(rows.map(r => r.family).filter(Boolean))].slice(0, 6).join(', ') || 'see below'}) or latest.`, tone: 'warn', models: rows };
193
+ return { ok: true, choice: { ...choice, model: hit }, text: `Model: ${name} · ${hit}${hit !== model ? ` (from "${model}")` : ''}. Your next turns ask for it.`, tone: 'ok' };
194
+ }
195
+
196
+ /** Subscription path: a Claude family alias (the CLI resolves the newest) or a full claude-* id; anything else is an error with the aliases. */
197
+ export function selectSubscriptionModel(choice) {
198
+ const m = choice.model;
199
+ if (!m) return { ok: true, choice, text: 'Model: claude-cli. The server default applies (the newest Sonnet unless your org set CLAUDE_CLI_DEFAULT_MODEL).', tone: 'ok' };
200
+ const q = m.toLowerCase();
201
+ if (CLAUDE_FAMILIES.includes(q)) return { ok: true, choice: { ...choice, model: q }, text: `Model: claude-cli ${q} (the Claude CLI resolves the newest ${q}; turn.started shows what ran).`, tone: 'ok' };
202
+ if (q === 'latest') return { ok: true, choice: { provider: choice.provider, via: choice.via }, text: 'Model: claude-cli, the server default.', tone: 'ok' };
203
+ if (/^claude-[a-z0-9.\-]+$/i.test(m)) return { ok: true, choice, text: `Model: claude-cli ${m}. The CLI will say if it does not exist.`, tone: 'ok' };
204
+ return { ok: false, text: `Unknown Claude model "${m}". Use ${CLAUDE_FAMILIES.join(', ')} (the CLI picks the newest) or a full claude-* id.`, tone: 'warn' };
205
+ }
206
+
207
+ /**
208
+ * B3: /model ollama [model] against the server's installed-model list (res from api.ollamaModels()).
209
+ * -> { ok, choice?, text, tone, rows? } ; rows = the installed list to show.
210
+ */
211
+ export function selectOllamaModel(res, model) {
212
+ if (!res?.ok) {
213
+ if (res?.error === 'not_connected') return { ok: false, text: 'Ollama is not connected. /connect ollama http://<host>:11434', tone: 'warn' };
214
+ const why = res?.message || res?.error || 'no answer';
215
+ return model
216
+ ? { ok: true, choice: { provider: 'ollama', model }, text: `Model: ollama ${model}. Could not list Ollama's models (${why}); the server will say if it is missing.`, tone: 'warn' }
217
+ : { ok: false, text: `Could not list Ollama's models: ${why}`, tone: 'warn' };
218
+ }
219
+ const models = Array.isArray(res.models) ? res.models : [];
220
+ const names = models.map(m => m.name);
221
+ const host = res.host || null;
222
+ const withHost = (c) => (host ? { ...c, host } : c);
223
+ if (!model) {
224
+ const def = res.default_model || names[0] || null;
225
+ if (!def) return { ok: false, text: `Ollama at ${host} has no models installed. On that machine run: ollama pull qwen2.5`, tone: 'warn' };
226
+ return { ok: true, choice: withHost({ provider: 'ollama' }), text: `Model: Ollama · ${host} · ${def} (the connection's default). Installed models below; pick one with /model ollama <model>.`, tone: 'ok', models, current: def };
227
+ }
228
+ const hit = names.includes(model) ? model : (names.includes(`${model}:latest`) ? `${model}:latest` : null);
229
+ if (!hit) return { ok: false, text: `${model} is not installed on ${host}. On that machine run: ollama pull ${model}`, tone: 'warn', models };
230
+ return { ok: true, choice: withHost({ provider: 'ollama', model: hit }), text: `Model: Ollama · ${host} · ${hit}. Your next turns ask for it.`, tone: 'ok' };
231
+ }
232
+
233
+ /** One compat row of a /models list: id (strings pass through). */
234
+ const modelId = (m) => (typeof m === 'string' ? m : m?.id);
235
+
236
+ /**
237
+ * B5: /model <preset> [model] against the provider's own /models (res from api.compatModels(preset)).
238
+ * Hosted presets never default a model: with none given the list is shown and no choice is set.
239
+ * -> { ok, choice?, text, tone, models?, current? }
240
+ */
241
+ export function selectCompatModel(res, presetId, model) {
242
+ const c = byProvider(presetId);
243
+ const connectCmd = `/connect ${c.connect}${c.hint ? ` ${c.hint}` : ''}`;
244
+ if (!res?.ok) {
245
+ if (res?.error === 'not_connected') return { ok: false, text: `${c.name} is not connected. ${connectCmd}`, tone: 'warn' };
246
+ const why = res?.message || res?.error || 'no answer';
247
+ return model
248
+ ? { ok: true, choice: { provider: c.provider, model }, text: `Model: ${c.id} ${model}. Could not list ${c.name}'s models (${why}); the server will say if it is missing.`, tone: 'warn' }
249
+ : { ok: false, text: `Could not list ${c.name}'s models: ${why}`, tone: 'warn' };
250
+ }
251
+ const models = Array.isArray(res.models) ? res.models : [];
252
+ const ids = models.map(modelId).filter(Boolean);
253
+ const host = c.selfHosted ? (res.host || null) : null;
254
+ const withHost = (ch) => (host ? { ...ch, host } : ch);
255
+ if (!model) {
256
+ if (c.hosted) return { ok: false, text: `${c.name} needs a model. Pick one with /model ${c.id} <model>${ids.length ? ' (listed below)' : ''}.`, tone: 'warn', models };
257
+ const def = res.default_model || ids[0] || null;
258
+ if (!def) return { ok: false, text: `${c.name} at ${host} serves no models yet.`, tone: 'warn' };
259
+ return { ok: true, choice: withHost({ provider: c.provider }), text: `Model: ${c.name} · ${host} · ${def} (the connection's default). Models below; pick one with /model ${c.id} <model>.`, tone: 'ok', models, current: def };
260
+ }
261
+ if (ids.length && !ids.includes(model)) return { ok: false, text: `${model} is not served by ${host || c.name}. Models below.`, tone: 'warn', models };
262
+ if (!ids.length) return { ok: true, choice: withHost({ provider: c.provider, model }), text: `Model: ${c.id} ${model}. ${c.name} listed no models; the server will say if it is missing.`, tone: 'warn' };
263
+ return { ok: true, choice: withHost({ provider: c.provider, model }), text: `Model: ${c.name}${host ? ` · ${host}` : ''} · ${model}. Your next turns ask for it.`, tone: 'ok' };
264
+ }
265
+
266
+ /**
267
+ * Z2H-3B: after a successful /connect, should the new provider become the active choice? Yes when the current
268
+ * selection is not a working connected provider (nothing connected, or a saved/phantom choice that is not connected),
269
+ * or when auto has no OTHER connected provider to pick. No provider is a silent default: with another working one
270
+ * active, nothing changes and the caller keeps its "Use it: /model <id>" hint.
271
+ * -> { adopt:true, choice, line } | { adopt:false }. `vault`/`caps` are re-read AFTER the connect.
272
+ */
273
+ export function adoptAfterConnect({ vault, caps, current, id, host = null, model = null }) {
274
+ const row = CHOICES.find(c => c.id === id);
275
+ if (!row) return { adopt: false };
276
+ const connected = (c) => availability(vault, c, caps).state === 'connected';
277
+ const cur = current ? rowOf(current) : null;
278
+ const curWorks = cur && cur.id !== row.id && connected(cur);
279
+ const othersConnected = CHOICES.some(c => c.id !== row.id && connected(c));
280
+ if (curWorks) return { adopt: false };
281
+ if (!current && othersConnected) return { adopt: false };
282
+ const choice = normChoice({ provider: row.provider, ...(model ? { model } : {}), ...(host ? { host } : {}) });
283
+ return { adopt: true, choice, line: `Using ${row.name}${model ? ` ${model}` : ''} now. /model to change.` };
284
+ }
285
+
286
+ /** Status-line text: what the server said it ran, else what is selected; null for auto before any turn (keeps the line short). */
287
+ export function modelLabel(ctx, choice) {
288
+ if (ctx?.model && ctx.provider === 'anthropic' && ctx.auth === 'subscription') return `claude-cli ${ctx.model}`;
289
+ if (ctx?.model && ctx.provider === 'ollama') return `Ollama · ${ctx.endpoint || choice?.host || '?'} · ${ctx.model}`;
290
+ const cc = ctx?.provider ? byProvider(ctx.provider) : null;
291
+ if (ctx?.model && cc?.compat) return cc.hosted ? `${cc.name} · ${ctx.model}` : `${cc.name} · ${ctx.endpoint || choice?.host || '?'} · ${ctx.model}`;
292
+ if (ctx?.model) return ctx.provider ? `${ctx.provider} ${ctx.model}` : ctx.model;
293
+ if (!choice) return null;
294
+ if (choice.via === 'subscription') return choice.model ? `claude-cli ${choice.model}` : 'claude-cli';
295
+ if (choice.provider === 'ollama') return `Ollama · ${choice.host || '?'} · ${choice.model || 'default model'}`;
296
+ const ch = byProvider(choice.provider);
297
+ if (ch?.compat) return ch.hosted ? `${ch.name} · ${choice.model || 'pick a model'}` : `${ch.name} · ${choice.host || '?'} · ${choice.model || 'default model'}`;
298
+ return choice.model ? `${choice.provider} ${choice.model}` : choice.provider;
299
+ }
300
+
301
+ /** One line when turn.started shows the server ran something other than what was asked; else null. */
302
+ export function fallbackNote(choice, ev) {
303
+ if (!choice || !ev || ev.type !== 'turn.started' || !ev.provider) return null;
304
+ const ran = `${ev.provider}${ev.model ? ` ${ev.model}` : ''}`;
305
+ if (ev.provider !== choice.provider) return `You asked for ${choice.provider}; the server ran ${ran} (its policy or a fallback). /model shows what is connected.`;
306
+ if (choice.model && ev.model && ev.model !== choice.model) return `You asked for ${choice.model}; the server ran ${ran}.`;
307
+ return null;
308
+ }
309
+
310
+ /** Fields for the stream body. */
311
+ export const streamFields = (choice) => (choice ? { provider: choice.provider, ...(choice.model ? { model: choice.model } : {}), ...(choice.profile ? { promptProfile: choice.profile } : {}) } : {});