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,77 @@
1
+ /**
2
+ * /undo: which tool calls count as state changes, and the small registry of KNOWN inverses.
3
+ *
4
+ * Receipts are content-free on purpose (the AEE envelope commits to the action by digest, never plaintext
5
+ * args) and the stream summary only echoes a few safe keys. So an inverse is never built from "what the
6
+ * receipt said the args were". Each entry binds its target from something that is really there: a safe key
7
+ * in the call's own summary (slot name), or the server's own record of what the call produced, matched by
8
+ * time window and accepted only when EXACTLY ONE record fits. Zero or several = not bound = nothing runs,
9
+ * and the user gets the by-hand route. Nothing here guesses an inverse.
10
+ */
11
+
12
+ const READ_ONLY = /(^|_)(list|get|read|search|status|view|history|health|query|check|show|stats|find|lookup|related|suggest|research|describe|info|accounts|folders|pending|proposals|blocked|report|transcript|doctor|ping|whoami|verify)(_|$)/;
13
+
14
+ /** A settled, successful, receipt-backed call that is not a read. */
15
+ export function isStateChanging(b) {
16
+ return Boolean(b) && b.kind === 'tool' && b.status === 'ok' && Boolean(b.envelopeId) && Boolean(b.tool) && !READ_ONLY.test(String(b.tool));
17
+ }
18
+
19
+ export function lastStateChange(items) {
20
+ const all = (items || []).filter(isStateChanging);
21
+ return all.length ? all[all.length - 1] : null;
22
+ }
23
+
24
+ const WIN_BEFORE = 10_000;
25
+ const WIN_AFTER = 20_000;
26
+ const toMs = (v) => (typeof v === 'number' ? v : Number.isFinite(Date.parse(v)) ? Date.parse(v) : null);
27
+ function inWindow(call, ts) {
28
+ const t = toMs(ts);
29
+ if (t == null || !Number.isFinite(call.startedAt)) return false;
30
+ return t >= call.startedAt - WIN_BEFORE && t <= call.startedAt + (call.durationMs || 0) + WIN_AFTER;
31
+ }
32
+ const summaryKey = (call, key) => new RegExp(`(?:^|\\s)${key}=(\\S+)`).exec(String(call.summary || ''))?.[1] || null;
33
+
34
+ /** Each bind -> { ok: true, label, run(ops) -> { receipt } } | { ok: false, why, byHand } */
35
+ export const INVERSES = {
36
+ slot_checkout: {
37
+ did: 'checked a slot out for this conversation',
38
+ async bind(call) {
39
+ const name = summaryKey(call, 'name');
40
+ if (!name) return { ok: false, why: 'the call summary does not name the slot', byHand: 'quox slot list, then quox slot release <name>' };
41
+ return { ok: true, reverse: `release slot ${name} (the hold ends; the slot itself stays)`, run: (ops) => ops.releaseSlot(name) };
42
+ }
43
+ },
44
+ inbox_move: {
45
+ did: 'moved mail between folders',
46
+ async bind(call, ops) {
47
+ const moves = (await ops.recentMoves()).filter(m => inWindow(call, m.movedAt ?? m.moved_at));
48
+ if (moves.length === 1) {
49
+ const m = moves[0];
50
+ return { ok: true, reverse: `move the message back from ${m.toFolder || m.to_folder || 'its new folder'} to ${m.fromFolder || m.from_folder || 'its source folder'} (move ${m.id})`, run: (o) => o.undoMove(m.id) };
51
+ }
52
+ return { ok: false, why: moves.length ? `more than one move fits that call (${moves.map(m => m.id).join(', ')}), so Quox can't tell which one it made` : 'no recorded move fits that call', byHand: 'quox mail moves, then quox mail undo <moveId>' };
53
+ }
54
+ },
55
+ notesq_create: {
56
+ did: 'created a note',
57
+ async bind(call, ops) {
58
+ const notes = (await ops.listNotes()).filter(n => inWindow(call, n.createdAt ?? n.created_at));
59
+ if (notes.length === 1) {
60
+ const n = notes[0];
61
+ return { ok: true, reverse: `delete the note "${n.title || n.id}" (${n.id})`, run: (o) => o.deleteNote(n.id) };
62
+ }
63
+ return { ok: false, why: notes.length ? `more than one note fits that call (${notes.map(n => n.id).join(', ')})` : 'no recorded note fits that call', byHand: 'quox notes list, then delete the one you mean from NotesQ' };
64
+ }
65
+ }
66
+ };
67
+
68
+ /** Tools that change state but have no automatic inverse here: the by-hand route, stated plainly. */
69
+ export const BY_HAND = {
70
+ activate_workflow: 'quox workflow list, then deactivate it from the dashboard or ask Quox to deactivate_workflow with the id',
71
+ deactivate_workflow: 'reactivate it with activate_workflow, or from the dashboard',
72
+ skill_manage: 'a created skill sits in the review queue: reject it there, or leave it unapproved',
73
+ notesq_delete: 'ask Quox to notesq_restore the note, or use quox notes restore <id>',
74
+ email_send: 'a sent email cannot be recalled',
75
+ email: 'a sent email cannot be recalled',
76
+ slot_say: 'a message already posted in a slot room stays there'
77
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * /watch, /automate, /crystallize: the shared factory app.js wires (switch cases + one answer hook).
3
+ * Each command makes ONE preview card; the next line y/n creates through an existing backend path.
4
+ */
5
+ import { createConfirm } from './confirm.js';
6
+ import { createWatchCmd } from './watch.js';
7
+ import { createAutomateCmd } from './automate.js';
8
+ import { createCrystallizeCmd } from './crystallize.js';
9
+
10
+ export function createKillerCmds({ api, say, getState, getFields }) {
11
+ const confirm = createConfirm({ say });
12
+ const watch = createWatchCmd({ api, say, confirm });
13
+ const automate = createAutomateCmd({ api, say, confirm, getState, getFields });
14
+ const crystallize = createCrystallizeCmd({ api, say, confirm, getState, getFields });
15
+ return {
16
+ crystallize: (arg) => crystallize.handle(arg),
17
+ automate: (arg) => automate.handle(arg),
18
+ watch: (arg) => watch.handle(arg),
19
+ /** The composer line after a preview card: true when consumed as the y/n answer. */
20
+ answer: (text) => confirm.answer(text),
21
+ pending: () => confirm.pending(),
22
+ touch: () => confirm.touch()
23
+ };
24
+ }
@@ -0,0 +1,16 @@
1
+ /** Backend calls behind /watch, /automate and /crystallize, kept out of api.js (one spread there). */
2
+ import { resolveEndpoint } from '../../api/endpoints.js';
3
+
4
+ export function createKillerApi({ client }) {
5
+ return {
6
+ async watchCreate(body) { const ep = resolveEndpoint('agent-schedules.create'); return client.post(ep.path, { service: ep.service, body }); },
7
+ async watchList() { const ep = resolveEndpoint('agent-schedules.list'); return client.get(ep.path, { service: ep.service, retries: 0 }); },
8
+ async watchStop(id) { const ep = resolveEndpoint('agent-schedules.delete', { id }); return client.delete(ep.path, { service: ep.service }); },
9
+ /** quoxflow-direct, as `quox workflow apply`: -> the stored workflow (has .id). */
10
+ async workflowCreate(def) { const ep = resolveEndpoint('workflows.applyCreate'); const d = await client.post(ep.path, { service: ep.service, body: def }); return d?.data || d; },
11
+ /** As `quox trigger workflow-activate`: registers the workflow's triggers. */
12
+ async workflowActivate(workflowId) { const ep = resolveEndpoint('workflows.activate', { workflowId }); return client.post(ep.path, { service: ep.service, body: {} }); },
13
+ /** QSKL store create. { dryRun: true } asks only for the skill firewall verdict (throws 422 when blocked). */
14
+ async skillCreate(body, { dryRun = false } = {}) { const ep = resolveEndpoint('skills.create'); const d = await client.post(ep.path, { service: ep.service, body, query: dryRun ? { dry_run: 'true' } : undefined }); return d?.data || d; }
15
+ };
16
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Session usage ledger for /cost, /usage and /context. Fed the raw stream events of each turn; counts only
3
+ * what the server reported (turn.finished.usage, or done.usage on a server that never sends turn.finished).
4
+ * Nothing is estimated: a turn without usage is counted as "not reported", and cost stays blank unless the
5
+ * server sent cost_usd.
6
+ */
7
+ const num = (v) => (v == null || v === '' || !Number.isFinite(Number(v)) ? null : Number(v));
8
+
9
+ export function readUsage(u) {
10
+ const o = u && typeof u === 'object' ? u : {};
11
+ const input = num(o.input_tokens ?? o.prompt_tokens);
12
+ const output = num(o.output_tokens ?? o.completion_tokens);
13
+ const cacheRead = num(o.cache_read_input_tokens);
14
+ const cacheWrite = num(o.cache_creation_input_tokens);
15
+ const cost = num(o.cost_usd);
16
+ const costSource = typeof o.cost_source === 'string' ? o.cost_source : null; // 'claude-cli' = that CLI's own estimate
17
+ return { input, output, cacheRead, cacheWrite, cost, costSource, reported: input != null || output != null };
18
+ }
19
+
20
+ export function createLedger() {
21
+ const models = new Map(); // key -> row
22
+ const convs = new Map(); // conversation id -> tokens
23
+ let turn = null;
24
+ const finalize = (usage, convId) => {
25
+ if (!turn) return;
26
+ const t = turn; turn = null;
27
+ const raw = usage ?? t.doneUsage;
28
+ const u = readUsage(t.viaCli && raw && !raw.cost_source ? { ...raw, cost_source: 'claude-cli' } : raw); // the claude CLI path, from a server that does not label it yet
29
+ const key = `${t.provider || '?'}|${t.model || '?'}`;
30
+ const r = models.get(key) || { provider: t.provider, model: t.model, turns: 0, reported: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, costTurns: 0, estimate: 0, estimateTurns: 0 };
31
+ r.turns++;
32
+ if (u.reported) {
33
+ r.reported++;
34
+ r.input += u.input || 0; r.output += u.output || 0; r.cacheRead += u.cacheRead || 0; r.cacheWrite += u.cacheWrite || 0;
35
+ const id = convId || t.conversationId;
36
+ if (id) convs.set(id, (convs.get(id) || 0) + (u.input || 0) + (u.output || 0));
37
+ }
38
+ if (u.cost != null && u.costSource) { r.estimate += u.cost; r.estimateTurns++; } // reported by a tool, not a price
39
+ else if (u.cost != null) { r.cost += u.cost; r.costTurns++; }
40
+ models.set(key, r);
41
+ };
42
+ return {
43
+ observe(ev) {
44
+ if (!ev || typeof ev !== 'object') return;
45
+ if (ev.type === 'turn.started') turn = { provider: ev.provider || null, model: ev.model || null, conversationId: ev.conversation_id || null, doneUsage: null };
46
+ else if (ev.type === 'done' && turn) { turn.doneUsage = ev.usage || (ev.cost_usd != null ? { cost_usd: ev.cost_usd } : null); turn.viaCli = Boolean(ev.cli_session_id); }
47
+ else if (ev.type === 'turn.finished') finalize(ev.usage, ev.conversation_id);
48
+ },
49
+ /** The stream closed: settle a turn whose turn.finished never came (legacy servers send only done). */
50
+ end() { if (turn) finalize(null, null); },
51
+ rows: () => [...models.values()],
52
+ conversationTokens: (id) => (id ? convs.get(id) ?? 0 : 0),
53
+ totals() {
54
+ const t = { turns: 0, reported: 0, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, costTurns: 0, estimate: 0, estimateTurns: 0 };
55
+ for (const r of models.values()) for (const k of Object.keys(t)) t[k] += r[k];
56
+ return t;
57
+ }
58
+ };
59
+ }
60
+
61
+ const n = (v) => Number(v || 0).toLocaleString('en-US');
62
+ const usd = (v) => `$${v < 0.01 ? v.toFixed(4) : v.toFixed(2)}`;
63
+
64
+ function line(r) {
65
+ const parts = [`${r.turns} turn${r.turns === 1 ? '' : 's'}`];
66
+ if (r.reported) {
67
+ parts.push(`in ${n(r.input)}`, `out ${n(r.output)}`);
68
+ if (r.cacheRead || r.cacheWrite) parts.push(`cache ${n(r.cacheRead)} read / ${n(r.cacheWrite)} written`);
69
+ }
70
+ if (r.reported < r.turns) parts.push(`${r.turns - r.reported} without usage from the server`);
71
+ if (r.costTurns || !r.estimateTurns) parts.push(r.costTurns === r.turns && r.turns ? usd(r.cost) : r.costTurns ? `${usd(r.cost)} for ${r.costTurns} of ${r.turns} turns` : 'cost not reported');
72
+ if (r.estimateTurns) parts.push(`${usd(r.estimate)} claude CLI's own estimate${r.estimateTurns < r.turns ? ` for ${r.estimateTurns} of ${r.turns} turns` : ''}`);
73
+ return parts.join(' · ');
74
+ }
75
+
76
+ /** -> help model rows for /cost. */
77
+ export function costModel(ledger) {
78
+ const rows = ledger.rows();
79
+ const out = [{ kind: 'head', text: 'Cost and usage, this session' }];
80
+ if (!rows.length) return [...out, { kind: 'plain', text: 'No turns yet in this session.' }];
81
+ for (const r of rows) out.push({ kind: 'row', key: `${r.provider || '?'} ${r.model || ''}`.trim(), text: line(r) });
82
+ const t = ledger.totals();
83
+ if (rows.length > 1) out.push({ kind: 'row', key: 'Total', text: line(t) });
84
+ if (t.costTurns + t.estimateTurns < t.turns) out.push({ kind: 'dim', text: 'Cost is blank where the server sent tokens but no cost_usd for that provider; Quox does not guess prices.' });
85
+ if (t.estimateTurns) out.push({ kind: 'dim', text: "The Claude subscription path shows what the claude CLI reports: its own API-equivalent estimate, passed through as is. It is not a bill." });
86
+ if (t.reported < t.turns) out.push({ kind: 'dim', text: 'Some turns ended without usage (an older server, or a turn that failed or was interrupted).' });
87
+ return out;
88
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * One small list component for everything slash commands pop up under the composer: the command
3
+ * palette, the /resume picker and the /config card. Rows: { key, text, note?, dim? }. The selected
4
+ * row carries a marker; dim rows show their note (the reason) in muted text.
5
+ */
6
+ import React from 'react';
7
+ import { Box, Text } from 'ink';
8
+ import { c, clip } from '../theme.js';
9
+
10
+ const h = React.createElement;
11
+
12
+ export function ListView({ title = null, rows = [], sel = 0, cols = 80, hint = null, empty = null, more = 0 }) {
13
+ // The key column grows with the window (22 cells minimum, 32 at most) and always leaves a one-cell gap, so a name that fits is shown whole;
14
+ // one that does not gets the ellipsis from truncate-end, never a silent cut.
15
+ const keyW = Math.min(Math.max(22, Math.min(32, Math.floor(cols / 3))), Math.max(8, ...rows.map(r => [...String(r.key || '')].length)) + 1);
16
+ const textW = Math.max(10, cols - keyW - 6);
17
+ return h(Box, { flexDirection: 'column', paddingLeft: 1 },
18
+ title ? h(Text, { color: c('muted'), bold: true }, clip(title, cols - 2)) : null,
19
+ !rows.length && empty ? h(Text, { color: c('muted') }, ` ${clip(empty, cols - 4)}`) : null,
20
+ ...rows.map((r, i) => {
21
+ const on = i === sel;
22
+ const body = r.dim && r.note ? `${r.note} · ${r.text}` : r.note ? `${r.text} ${r.note}` : r.text;
23
+ return h(Box, { key: `${i}:${r.key}` },
24
+ h(Text, { color: on ? c('key') : c('muted') }, on ? '▸ ' : ' '),
25
+ h(Box, { width: keyW, flexShrink: 0 }, h(Text, { color: r.dim ? c('muted') : on ? c('key') : c('label'), bold: on && !r.dim, wrap: 'truncate-end' }, String(r.key || ''))),
26
+ h(Text, { color: c(r.dim ? 'muted' : 'text'), wrap: 'truncate-end' }, clip(String(body || ''), textW)));
27
+ }),
28
+ more > 0 ? h(Text, { color: c('muted') }, ` … ${more} more, keep typing to narrow`) : null,
29
+ hint ? h(Text, { color: c('dim') }, ` ${clip(hint, cols - 4)}`) : null);
30
+ }
@@ -0,0 +1,261 @@
1
+ /**
2
+ * QUOXON-FW6: the /model favourites, default and chooser behaviour, kept out of app.js.
3
+ *
4
+ * Favourites and the default live in the user's server-side preferences (api.modelPrefs and
5
+ * api.setModelPrefs); this module only holds the in-memory copy (`mp.prefs`) and calls the api to
6
+ * save. Provider parity: nothing here knows one provider from another; the connected list and the
7
+ * live model lists decide what is shown.
8
+ */
9
+ import {
10
+ CHOICES,
11
+ connectedChoices,
12
+ providerId,
13
+ choiceId,
14
+ modelLabel,
15
+ normChoice,
16
+ } from '../model.js';
17
+ import {
18
+ normPrefs,
19
+ addFav,
20
+ rmFav,
21
+ setDefault,
22
+ clearDefault,
23
+ toggleFav,
24
+ favLines,
25
+ liveModels,
26
+ checkModelId,
27
+ chooserItems,
28
+ chooserRow,
29
+ currentKey,
30
+ label,
31
+ } from '../modelPrefs.js';
32
+ import { clip } from '../theme.js';
33
+
34
+ /**
35
+ * deps: api, say, command, mc (ref to the stored choice), mp (ref { prefs, touched, warned }),
36
+ * picker, state (ref), bump, cols.
37
+ */
38
+ export function createModelCmd({ api, say, command, mc, mp, picker, state, bump, cols }) {
39
+ const byId = (id) => CHOICES.find((x) => x.id === id) || null;
40
+ const current = () => currentKey(mc.current, choiceId);
41
+
42
+ /** Optimistic: the in-memory copy changes at once; the server (user preferences) is the truth. */
43
+ async function save(next) {
44
+ mp.current.prefs = next;
45
+ bump();
46
+ if (!api.setModelPrefs) return true;
47
+ const r = await api.setModelPrefs(next);
48
+ if (!r.synced) {
49
+ say(
50
+ `Saved on this machine only (${r.error || 'the server did not take it'}). It is not on ` +
51
+ 'the server yet, so it will not follow you to other machines. ' +
52
+ 'Making the change again retries.',
53
+ 'warn',
54
+ );
55
+ }
56
+ return Boolean(r.synced);
57
+ }
58
+
59
+ async function addOrDefault(sub) {
60
+ const pid = providerId(sub.provider);
61
+ if (!pid) {
62
+ const ids = CHOICES.map((x) => x.id).join(', ');
63
+ return say(`No provider called "${sub.provider}". Use ${ids}.`, 'warn');
64
+ }
65
+ // New models appear before lists update: an id missing from the live list is kept, with a
66
+ // warning.
67
+ const live = await liveModels(api, pid);
68
+ const chk = checkModelId(live.ids, sub.model, byId(pid).name);
69
+ const prefs = mp.current.prefs;
70
+ const r =
71
+ sub.kind === 'fav-add' ? addFav(prefs, pid, chk.model) : setDefault(prefs, pid, chk.model);
72
+ if (r.added === false || r.ok === false) return say(r.text, r.tone);
73
+ await save(r.prefs);
74
+ say(clip(r.text, cols() - 4), r.tone);
75
+ if (chk.note) say(clip(chk.note, cols() - 4), 'warn');
76
+ return undefined;
77
+ }
78
+
79
+ /**
80
+ * Handles the favourites / default / chooser forms of /model.
81
+ * -> undefined when `sub` (parseModelSub) is not one of them (the caller carries on), else a
82
+ * promise.
83
+ */
84
+ function handle(sub) {
85
+ const prefs = mp.current.prefs;
86
+ switch (sub.kind) {
87
+ case 'error':
88
+ return Promise.resolve(say(sub.text, 'warn'));
89
+ case 'fav-list':
90
+ for (const [t, tone] of favLines(prefs, current())) say(clip(t, cols() - 4), tone);
91
+ return Promise.resolve();
92
+ case 'use': {
93
+ const f = prefs.favourites[sub.n - 1];
94
+ if (f) return command(`/model ${f.provider} ${f.model}`);
95
+ return Promise.resolve(say(`No favourite ${sub.n}. /model fav lists them.`, 'warn'));
96
+ }
97
+ case 'default-show':
98
+ return Promise.resolve(
99
+ say(
100
+ prefs.default
101
+ ? `Default model: ${label(prefs.default)}. New sessions start on it when its ` +
102
+ 'provider is connected. /model default clear removes it.'
103
+ : 'No default model. /model default <provider> <model> sets one ' +
104
+ '(or press d in the /model chooser).',
105
+ ),
106
+ );
107
+ case 'default-clear': {
108
+ const c = clearDefault(prefs);
109
+ return save(c.prefs).then(() => say(c.text, c.tone));
110
+ }
111
+ case 'fav-rm': {
112
+ const r = rmFav(prefs, sub.ref);
113
+ return (r.removed ? save(r.prefs) : Promise.resolve()).then(() => say(r.text, r.tone));
114
+ }
115
+ case 'fav-add':
116
+ case 'default-set':
117
+ return addOrDefault(sub);
118
+ case 'chooser':
119
+ return openChooser();
120
+ default:
121
+ return undefined;
122
+ }
123
+ }
124
+
125
+ /** The /model chooser: favourites first, then each CONNECTED provider's live models. */
126
+ async function openChooser() {
127
+ if (!api.connections) return say('Model choice is not available in this mode.', 'warn');
128
+ const [res, caps] = await Promise.all([
129
+ api.connections(),
130
+ api.capabilities ? api.capabilities() : null,
131
+ ]);
132
+ const conn = connectedChoices(res, caps);
133
+ if (!conn.length) {
134
+ return say(
135
+ res?.ok === false
136
+ ? `Could not read what is connected: ${res.error || 'no answer'}`
137
+ : 'No AI provider is connected yet. /connect openai, /connect anthropic or ' +
138
+ '/connect ollama <url>.',
139
+ 'warn',
140
+ );
141
+ }
142
+ const groups = await Promise.all(
143
+ conn.map(async (c) => {
144
+ const l = await liveModels(api, c.id);
145
+ const note = l.ok ? null : 'could not list its models; Enter uses the server default';
146
+ return { id: c.id, name: c.name, ids: l.ids.slice(0, 200), note };
147
+ }),
148
+ );
149
+ const items = () => chooserItems(mp.current.prefs, groups);
150
+ const hidden = () =>
151
+ mp.current.prefs.favourites.filter((f) => !groups.some((g) => g.id === f.provider)).length;
152
+ const title = () => {
153
+ const now = modelLabel(state.current.ctx, mc.current) || 'auto';
154
+ const def = mp.current.prefs.default ? label(mp.current.prefs.default) : 'none';
155
+ const h = hidden();
156
+ const note = h ? ` · ${h} favourite${h === 1 ? '' : 's'} hidden, provider not connected` : '';
157
+ return `Model · current ${now} · default ${def}${note}`;
158
+ };
159
+ picker.open({
160
+ titleFn: title,
161
+ rowsFn: () => items().map((it) => chooserRow(it, mp.current.prefs, current())),
162
+ hint: '↑↓ select · Enter use · f favourite · d default · Esc close',
163
+ onChar: (ch, sel) => {
164
+ if (ch !== 'f' && ch !== 'd') return undefined;
165
+ const it = items()[sel];
166
+ if (!it || !it.model) {
167
+ say('That row has no model to save. Pick a model row.', 'warn');
168
+ return true;
169
+ }
170
+ const m = { provider: it.provider, model: it.model };
171
+ if (ch === 'f') {
172
+ const t = toggleFav(mp.current.prefs, m);
173
+ save(t.prefs);
174
+ say(`${t.added ? 'Added to' : 'Removed from'} favourites: ${label(m)}.`, 'ok');
175
+ } else {
176
+ save(setDefault(mp.current.prefs, m.provider, m.model).prefs);
177
+ say(`Default model: ${label(m)}. New sessions start on it.`, 'ok');
178
+ }
179
+ const next = items();
180
+ const same = (x) =>
181
+ x.kind === it.kind && x.provider === it.provider && x.model === it.model;
182
+ const j = next.findIndex(same);
183
+ return j >= 0 ? j : Math.min(sel, Math.max(0, next.length - 1));
184
+ },
185
+ onPick: (_, i) => {
186
+ const it = items()[i];
187
+ if (it) command(it.model ? `/model ${it.provider} ${it.model}` : `/model ${it.provider}`);
188
+ },
189
+ });
190
+ return undefined;
191
+ }
192
+
193
+ /**
194
+ * New sessions start on the saved default model when its provider is connected. A --provider
195
+ * flag, or a /model typed first, wins. A default on a provider that is not connected is said
196
+ * once and nothing changes. -> a cancel function.
197
+ */
198
+ function startOnDefault({ flagged, off, dispatchCtx }) {
199
+ if (!api.modelPrefs || off) return () => {};
200
+ let dead = false;
201
+ (async () => {
202
+ let got = null;
203
+ try {
204
+ got = await api.modelPrefs();
205
+ } catch {
206
+ got = null;
207
+ }
208
+ if (dead || !got) return;
209
+ mp.current.prefs = normPrefs(got.prefs);
210
+ bump();
211
+ const def = mp.current.prefs.default;
212
+ if (!def || flagged || mp.current.touched) return;
213
+ let res = null;
214
+ let caps = null;
215
+ try {
216
+ [res, caps] = await Promise.all([
217
+ api.connections?.(),
218
+ api.capabilities ? api.capabilities() : null,
219
+ ]);
220
+ } catch {
221
+ return; // cannot tell what is connected: stay quiet, the model is unchanged
222
+ }
223
+ if (dead || mp.current.touched || !res || res.ok === false) return;
224
+ const row = byId(def.provider);
225
+ if (!row) return;
226
+ if (!connectedChoices(res, caps).some((c) => c.id === row.id)) {
227
+ if (!mp.current.warned) {
228
+ mp.current.warned = true;
229
+ say(
230
+ `Your default model ${label(def)} is on ${row.name}, which is not connected, so ` +
231
+ `the model is unchanged. /connect ${row.connect} to use it, or ` +
232
+ '/model default clear.',
233
+ 'warn',
234
+ );
235
+ }
236
+ return;
237
+ }
238
+ let host = null;
239
+ if (row.dynamic) {
240
+ try {
241
+ const lst =
242
+ row.id === 'ollama' ? await api.ollamaModels?.() : await api.compatModels?.(row.id);
243
+ host = lst?.host || null;
244
+ } catch {
245
+ host = null;
246
+ }
247
+ }
248
+ if (dead || mp.current.touched) return;
249
+ const via = row.via ? { via: row.via } : {};
250
+ const at = host && !row.hosted ? { host } : {};
251
+ mc.current = normChoice({ provider: row.provider, model: def.model, ...via, ...at });
252
+ dispatchCtx();
253
+ bump();
254
+ })();
255
+ return () => {
256
+ dead = true;
257
+ };
258
+ }
259
+
260
+ return { handle, openChooser, startOnDefault, save };
261
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * L0 command palette: the pure model behind the list that opens under the composer when a draft
3
+ * starts with `/`. Built from the one REGISTRY (commands.js), so the palette, /help and dispatch can
4
+ * never disagree. Only commands that can work here are bright; anything this session cannot run is
5
+ * dim with the reason. Planned commands stay hidden unless typed exactly.
6
+ */
7
+ import { REGISTRY, entryVisible } from '../commands.js';
8
+
9
+ export const PALETTE_MAX = 8;
10
+
11
+ /** The draft as a palette query: '/co' -> 'co'; null when the draft is not a bare command word. */
12
+ export function paletteQuery(draft) {
13
+ const t = String(draft ?? '');
14
+ if (!t.startsWith('/') || t.startsWith('//')) return null;
15
+ if (/[\s]/.test(t)) return null; // args typed (or several lines): the palette steps aside
16
+ return t.slice(1).toLowerCase();
17
+ }
18
+
19
+ /**
20
+ * Why a command cannot run in this session, or null. `api` is the session's server wrapper (a method
21
+ * missing means this mode has no route for it); `caps` is GET /api/v1/capabilities when the server has
22
+ * it ({ id: { available, reason } }), else null; `blocked` is the not-signed-in message.
23
+ */
24
+ export function unavailableReason(entry, { api = {}, caps = null, blocked = null } = {}) {
25
+ const cap = caps ? caps[entry.cap || entry.id] : null;
26
+ if (cap && cap.available === false) return cap.reason || 'not available on this server';
27
+ for (const m of entry.needs || []) if (typeof api[m] !== 'function') return 'not available in this mode';
28
+ if (blocked && entry.server) return '(sign in)';
29
+ return null;
30
+ }
31
+
32
+ /**
33
+ * GET /api/v1/capabilities as api.capabilities() returns it ({ ok, capabilities:[{ id, availability, reason, setup }] })
34
+ * -> { id: { available, reason } }, or null when the server has no contract. Only `installed` runs.
35
+ */
36
+ export function capabilityMap(res) {
37
+ if (!res || res.ok !== true || !Array.isArray(res.capabilities)) return null;
38
+ const label = { requires_setup: 'needs setup', unavailable: 'unavailable on this server', planned: 'planned, not built yet' };
39
+ return Object.fromEntries(res.capabilities.filter(c => c && c.id).map(c => {
40
+ const available = c.availability === 'installed';
41
+ return [c.id, { available, reason: available ? null : (c.reason || label[c.availability] || 'not available on this server'), setup: c.setup || null }];
42
+ }));
43
+ }
44
+
45
+ /** All palette rows for this session, registry order. */
46
+ export function paletteItems(ctx = {}, registry = REGISTRY) {
47
+ return registry.filter(e => e.kind === 'command' && entryVisible(e)).map(e => ({
48
+ id: e.id,
49
+ name: '/' + e.id,
50
+ usage: e.usage,
51
+ desc: e.short || e.desc, // the palette row is short (commandShort.js); the long wording is searched and shown by /help
52
+ longDesc: e.desc,
53
+ aliases: (e.aliases || []).map(a => '/' + a),
54
+ planned: e.status === 'planned',
55
+ disabled: e.status === 'planned' ? 'planned, not built yet' : unavailableReason(e, ctx)
56
+ }));
57
+ }
58
+
59
+ /** Subsequence score of q in s (lower is better), or null when q is not a subsequence. */
60
+ export function fuzzy(q, s) {
61
+ q = String(q).toLowerCase(); s = String(s).toLowerCase();
62
+ if (!q) return 0;
63
+ if (s.startsWith(q)) return 0;
64
+ const at = s.indexOf(q);
65
+ if (at >= 0) return 10 + at;
66
+ let i = 0, gaps = 0, last = -1;
67
+ for (let j = 0; j < s.length && i < q.length; j++) {
68
+ if (s[j] === q[i]) { if (last >= 0) gaps += j - last - 1; last = j; i++; }
69
+ }
70
+ return i === q.length ? 100 + gaps : null;
71
+ }
72
+
73
+ /** Filter + rank. Planned rows appear only on an exact name or alias match. */
74
+ export function filterPalette(items, query) {
75
+ const q = String(query ?? '').toLowerCase();
76
+ const exact = (it) => it.id === q || it.aliases.includes('/' + q);
77
+ const wordStart = (text) => q.length >= 2 && new RegExp('(^|[^a-z0-9])' + q.replace(/[^a-z0-9]/g, '\\$&')).test(String(text).toLowerCase());
78
+ const scored = [];
79
+ items.forEach((it, idx) => {
80
+ if (it.planned && !exact(it)) return;
81
+ // Rank: exact, prefix of the name, prefix of an alias, inside a name, a word in the description at a word start,
82
+ // then fuzzy subsequence, then any other mention in the description.
83
+ let best = null;
84
+ const long = it.longDesc || it.desc;
85
+ const nameSc = fuzzy(q, it.id);
86
+ if (nameSc === 0) best = 0;
87
+ else if (it.aliases.some(a => a.slice(1).toLowerCase().startsWith(q))) best = 1;
88
+ else if (nameSc != null && nameSc < 100) best = nameSc; // a substring of the name (10 + position)
89
+ else {
90
+ for (const a of it.aliases) { const sc = fuzzy(q, a.slice(1)); if (sc != null && sc < 100 && (best == null || sc < best)) best = sc; }
91
+ if (best == null && wordStart(long)) best = 60;
92
+ if (best == null) { // fuzzy subsequence of the name or an alias
93
+ for (const n of [it.id, ...it.aliases.map(a => a.slice(1))]) { const sc = fuzzy(q, n); if (sc != null && (best == null || sc < best)) best = sc; }
94
+ }
95
+ if (best == null && q.length >= 3 && long.toLowerCase().includes(q)) best = 500;
96
+ }
97
+ if (best == null) return;
98
+ if (exact(it)) best = -1;
99
+ if (it.disabled) best += 0.5; // a working command outranks an unavailable one on a tie
100
+ scored.push({ it, best, idx });
101
+ });
102
+ return scored.sort((a, b) => a.best - b.best || a.idx - b.idx).map(x => x.it);
103
+ }
104
+
105
+ /** What Tab puts in the draft: the name, plus a space when the command takes arguments. */
106
+ export function completion(item) {
107
+ const takesArgs = /^\S+\s+\S/.test(String(item.usage).trim());
108
+ return item.name + (takesArgs ? ' ' : '');
109
+ }
110
+
111
+ /** View rows for the palette: a window of PALETTE_MAX around the selection. */
112
+ export function paletteView(rows, sel, max = PALETTE_MAX) {
113
+ if (!rows.length) return { rows: [], sel: 0, more: 0, empty: true };
114
+ const s = Math.max(0, Math.min(sel, rows.length - 1));
115
+ const start = Math.max(0, Math.min(s - max + 1, rows.length - max));
116
+ const win = rows.slice(Math.max(0, start), Math.max(0, start) + max);
117
+ return { rows: win, sel: s - Math.max(0, start), more: rows.length - win.length, empty: false };
118
+ }