quoxcli 0.2.0 → 0.4.0

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 (442) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -0
  3. package/bin/quox.js +99 -126
  4. package/lib/api/endpoints/actions.js +15 -0
  5. package/lib/api/endpoints/admin.js +46 -0
  6. package/lib/api/endpoints/agent-schedules.js +26 -0
  7. package/lib/api/endpoints/agents.js +7 -0
  8. package/lib/api/endpoints/assistants.js +12 -0
  9. package/lib/api/endpoints/away.js +15 -0
  10. package/lib/api/endpoints/blog.js +16 -0
  11. package/lib/api/endpoints/chat.js +14 -0
  12. package/lib/api/endpoints/contactsq.js +6 -0
  13. package/lib/api/endpoints/conversations.js +14 -0
  14. package/lib/api/endpoints/devServerRegistry.js +25 -0
  15. package/lib/api/endpoints/engagement.js +26 -0
  16. package/lib/api/endpoints/evidence.js +20 -0
  17. package/lib/api/endpoints/executions.js +13 -0
  18. package/lib/api/endpoints/fetchAllowlist.js +27 -0
  19. package/lib/api/endpoints/files.js +14 -0
  20. package/lib/api/endpoints/hat.js +44 -0
  21. package/lib/api/endpoints/inbox.js +20 -0
  22. package/lib/api/endpoints/index.js +26 -0
  23. package/lib/api/endpoints/loops.js +26 -0
  24. package/lib/api/endpoints/memory.js +19 -0
  25. package/lib/api/endpoints/mind.js +13 -0
  26. package/lib/api/endpoints/mon.js +179 -0
  27. package/lib/api/endpoints/objectives.js +12 -0
  28. package/lib/api/endpoints/plugins.js +18 -0
  29. package/lib/api/endpoints/presence.js +33 -0
  30. package/lib/api/endpoints/quoxplan.js +7 -0
  31. package/lib/api/endpoints/quoxseo.js +12 -0
  32. package/lib/api/endpoints/router.js +17 -0
  33. package/lib/api/endpoints/safety.js +7 -0
  34. package/lib/api/endpoints/server.js +21 -0
  35. package/lib/api/endpoints/setup.js +6 -0
  36. package/lib/api/endpoints/skills.js +20 -0
  37. package/lib/api/endpoints/stepup.js +20 -0
  38. package/lib/api/endpoints/teams.js +2 -2
  39. package/lib/api/endpoints/triggers.js +26 -0
  40. package/lib/api/endpoints/v1.js +120 -0
  41. package/lib/api/endpoints/ward.js +7 -0
  42. package/lib/api/endpoints/workflows.js +7 -0
  43. package/lib/api/endpoints/x.js +8 -0
  44. package/lib/commands/actions.js +131 -0
  45. package/lib/commands/admin.js +16 -0
  46. package/lib/commands/agent.js +170 -27
  47. package/lib/commands/agentAttach.js +140 -13
  48. package/lib/commands/approvals.js +78 -1
  49. package/lib/commands/aside.js +24 -0
  50. package/lib/commands/ask.js +146 -0
  51. package/lib/commands/assistant.js +59 -4
  52. package/lib/commands/auth.js +8 -2
  53. package/lib/commands/backup.js +8 -4
  54. package/lib/commands/blog.js +110 -0
  55. package/lib/commands/brain.js +105 -14
  56. package/lib/commands/build.js +19 -5
  57. package/lib/commands/chat.js +180 -26
  58. package/lib/commands/chatCompanion.js +27 -0
  59. package/lib/commands/chatState.js +45 -0
  60. package/lib/commands/claude.js +35 -38
  61. package/lib/commands/contacts.js +134 -4
  62. package/lib/commands/contactsSendAt.js +21 -0
  63. package/lib/commands/conversation.js +35 -4
  64. package/lib/commands/dashboard.js +43 -0
  65. package/lib/commands/department.js +28 -7
  66. package/lib/commands/devResident.js +15 -3
  67. package/lib/commands/devSupervisor.js +13 -1
  68. package/lib/commands/discord.js +75 -39
  69. package/lib/commands/done.js +98 -28
  70. package/lib/commands/engagement.js +107 -6
  71. package/lib/commands/evidence.js +154 -0
  72. package/lib/commands/ext.js +23 -0
  73. package/lib/commands/fetchAllowlist.js +71 -0
  74. package/lib/commands/file.js +38 -1
  75. package/lib/commands/fleet.js +336 -4
  76. package/lib/commands/governance.js +12 -3
  77. package/lib/commands/gripe.js +44 -13
  78. package/lib/commands/hat.js +254 -9
  79. package/lib/commands/helpers.js +18 -1
  80. package/lib/commands/hyperion.js +58 -0
  81. package/lib/commands/image.js +68 -0
  82. package/lib/commands/inbox.js +74 -23
  83. package/lib/commands/kuma.js +1050 -0
  84. package/lib/commands/kumaSla.js +174 -0
  85. package/lib/commands/licence.js +52 -155
  86. package/lib/commands/loop.js +291 -0
  87. package/lib/commands/lucid.js +10 -4
  88. package/lib/commands/memory.js +85 -8
  89. package/lib/commands/mind.js +71 -0
  90. package/lib/commands/mon.js +69 -2
  91. package/lib/commands/monitoring.js +1 -1
  92. package/lib/commands/motion.js +2 -2
  93. package/lib/commands/nucleus.js +48 -17
  94. package/lib/commands/objective.js +59 -1
  95. package/lib/commands/offload.js +89 -0
  96. package/lib/commands/onboard.js +2 -1
  97. package/lib/commands/org.js +30 -3
  98. package/lib/commands/peek.js +28 -0
  99. package/lib/commands/plan.js +38 -0
  100. package/lib/commands/plugin.js +110 -0
  101. package/lib/commands/presence.js +89 -0
  102. package/lib/commands/provider.js +217 -0
  103. package/lib/commands/qlarity.js +19 -3
  104. package/lib/commands/quoxchat.js +13 -4
  105. package/lib/commands/quoxseo.js +69 -0
  106. package/lib/commands/reach.js +2 -1
  107. package/lib/commands/research.js +79 -0
  108. package/lib/commands/router.js +77 -0
  109. package/lib/commands/rows.js +367 -0
  110. package/lib/commands/run.js +82 -10
  111. package/lib/commands/scope.js +2 -2
  112. package/lib/commands/server.js +405 -0
  113. package/lib/commands/setup.js +33 -7
  114. package/lib/commands/shortcut.js +41 -0
  115. package/lib/commands/skills.js +68 -9
  116. package/lib/commands/slot.js +260 -7
  117. package/lib/commands/status.js +1 -1
  118. package/lib/commands/team.js +3 -1
  119. package/lib/commands/tips.js +23 -0
  120. package/lib/commands/tool.js +133 -45
  121. package/lib/commands/trace.js +12 -7
  122. package/lib/commands/trigger.js +95 -0
  123. package/lib/commands/update.js +46 -0
  124. package/lib/commands/vault.js +31 -8
  125. package/lib/commands/vm.js +237 -0
  126. package/lib/commands/voice.js +34 -3
  127. package/lib/commands/volt.js +45 -3
  128. package/lib/commands/walkthrough.js +32 -0
  129. package/lib/commands/watch.js +73 -21
  130. package/lib/commands/welcome.js +14 -0
  131. package/lib/commands/workflow.js +189 -32
  132. package/lib/commands/x.js +13 -4
  133. package/lib/commands/youtube.js +172 -0
  134. package/lib/core/actingAs.js +2 -2
  135. package/lib/core/actionItems.js +115 -0
  136. package/lib/core/activeSummary.js +186 -0
  137. package/lib/core/agentAlias.js +2 -0
  138. package/lib/core/agentFamilies.js +7 -0
  139. package/lib/core/aside.js +54 -0
  140. package/lib/core/askDecision.js +60 -0
  141. package/lib/core/auth.js +164 -13
  142. package/lib/core/callerSource.js +1 -1
  143. package/lib/core/cliBuild.js +62 -0
  144. package/lib/core/client.js +152 -35
  145. package/lib/core/commandRegistrars.js +130 -0
  146. package/lib/core/config.js +143 -25
  147. package/lib/core/customHats.js +168 -0
  148. package/lib/core/doneEngineFetch.js +8 -0
  149. package/lib/core/errors.js +329 -20
  150. package/lib/core/findingReconcile.js +90 -0
  151. package/lib/core/global-flags.js +105 -5
  152. package/lib/core/governedSession.js +1 -3
  153. package/lib/core/hyperion.js +102 -0
  154. package/lib/core/inboxResolution.js +65 -0
  155. package/lib/core/lazyCommands.js +4 -1
  156. package/lib/core/liveSessionsReport.js +1 -1
  157. package/lib/core/loopGlyph.js +97 -0
  158. package/lib/core/offload.js +533 -0
  159. package/lib/core/org-override.js +1 -1
  160. package/lib/core/orgResolve.js +70 -0
  161. package/lib/core/output.js +11 -11
  162. package/lib/core/peek.js +126 -0
  163. package/lib/core/profileUsers.js +51 -0
  164. package/lib/core/promptCancel.js +18 -0
  165. package/lib/core/repoPath.js +11 -0
  166. package/lib/core/residentSeed.js +3 -1
  167. package/lib/core/rootOptions.js +39 -0
  168. package/lib/core/router.js +168 -0
  169. package/lib/core/selfUpdate.js +188 -0
  170. package/lib/core/sessionHeartbeat.js +12 -8
  171. package/lib/core/sessionsOverview.js +64 -0
  172. package/lib/core/shortcut.js +83 -0
  173. package/lib/core/slotKeeper.js +248 -3
  174. package/lib/core/slotWake.js +120 -0
  175. package/lib/core/supervisorEngine.js +71 -2
  176. package/lib/core/version.js +1 -1
  177. package/lib/core/vm.js +366 -0
  178. package/lib/core/vmTui.js +86 -0
  179. package/lib/core/walkthrough.js +35 -0
  180. package/lib/dragon/companionView.js +34 -0
  181. package/lib/dragon/frames.js +118 -0
  182. package/lib/dragon/jobs.js +123 -0
  183. package/lib/dragon/renderWorker.js +15 -0
  184. package/lib/dragon/select.js +72 -0
  185. package/lib/evidence/bundleExport.js +47 -0
  186. package/lib/evidence/bundleVerify.js +77 -0
  187. package/lib/hyperion/deck.js +29 -0
  188. package/lib/lucid/LucidCard.jsx +68 -0
  189. package/lib/lucid/LucidIntro.jsx +45 -58
  190. package/lib/lucid/cards.js +86 -0
  191. package/lib/lucid/markdown.js +81 -0
  192. package/lib/lucid/motion.js +56 -0
  193. package/lib/quoxon/EXTENSIONS.md +23 -0
  194. package/lib/quoxon/activity.js +14 -2
  195. package/lib/quoxon/aocl.js +117 -0
  196. package/lib/quoxon/api.js +690 -25
  197. package/lib/quoxon/app.js +3586 -250
  198. package/lib/quoxon/approvalFacts.js +96 -0
  199. package/lib/quoxon/approvalSentence.js +44 -0
  200. package/lib/quoxon/approvalsView.js +384 -0
  201. package/lib/quoxon/asideView.js +40 -0
  202. package/lib/quoxon/ask.js +121 -0
  203. package/lib/quoxon/away.js +52 -0
  204. package/lib/quoxon/bang.js +224 -0
  205. package/lib/quoxon/bulkRisk.js +17 -0
  206. package/lib/quoxon/capabilities.js +13 -6
  207. package/lib/quoxon/cells.js +53 -0
  208. package/lib/quoxon/commandShort.js +124 -0
  209. package/lib/quoxon/commands.js +429 -39
  210. package/lib/quoxon/connect.js +210 -23
  211. package/lib/quoxon/connectCard.js +19 -2
  212. package/lib/quoxon/consent.js +86 -0
  213. package/lib/quoxon/copy.js +4 -3
  214. package/lib/quoxon/countdownLine.js +20 -0
  215. package/lib/quoxon/deck.js +30 -0
  216. package/lib/quoxon/demo.autoplay.js +29 -0
  217. package/lib/quoxon/demo.fixtures.js +1 -1
  218. package/lib/quoxon/demo.js +46 -3
  219. package/lib/quoxon/devObserver.js +20 -0
  220. package/lib/quoxon/dividerAocl.js +192 -0
  221. package/lib/quoxon/dividerRule.js +57 -0
  222. package/lib/quoxon/dragonCompanion.js +255 -0
  223. package/lib/quoxon/dragonModes.js +151 -0
  224. package/lib/quoxon/dragonMoments.js +82 -0
  225. package/lib/quoxon/dragonReads.js +68 -0
  226. package/lib/quoxon/dragonView.js +41 -0
  227. package/lib/quoxon/earlyInput.js +40 -0
  228. package/lib/quoxon/echo.js +15 -0
  229. package/lib/quoxon/egg.js +2 -2
  230. package/lib/quoxon/events.js +266 -26
  231. package/lib/quoxon/extensions.js +276 -0
  232. package/lib/quoxon/feedback.js +50 -0
  233. package/lib/quoxon/gallery.js +21 -7
  234. package/lib/quoxon/handoffOffer.js +78 -0
  235. package/lib/quoxon/hatSwitch.js +37 -0
  236. package/lib/quoxon/headless.js +161 -0
  237. package/lib/quoxon/hostUrls.js +41 -0
  238. package/lib/quoxon/inboxView.js +79 -0
  239. package/lib/quoxon/indicator.js +6 -2
  240. package/lib/quoxon/installCli.js +79 -0
  241. package/lib/quoxon/intent.js +12 -1
  242. package/lib/quoxon/keys.js +51 -8
  243. package/lib/quoxon/launcher.js +103 -0
  244. package/lib/quoxon/loopCard.js +456 -0
  245. package/lib/quoxon/loopCards.js +180 -0
  246. package/lib/quoxon/loopStream.js +87 -0
  247. package/lib/quoxon/main.js +101 -160
  248. package/lib/quoxon/markdown.js +45 -21
  249. package/lib/quoxon/markdownSettle.js +19 -0
  250. package/lib/quoxon/matrixDomains.json +46 -0
  251. package/lib/quoxon/mentions.js +90 -0
  252. package/lib/quoxon/mind.js +103 -0
  253. package/lib/quoxon/mode.js +46 -0
  254. package/lib/quoxon/model.js +132 -26
  255. package/lib/quoxon/modelPrefs.js +345 -0
  256. package/lib/quoxon/motion/clock.js +3 -2
  257. package/lib/quoxon/motion/level.js +91 -0
  258. package/lib/quoxon/mouse.js +222 -0
  259. package/lib/quoxon/noai.js +57 -0
  260. package/lib/quoxon/objectiveOffer.js +82 -0
  261. package/lib/quoxon/objects/away.js +72 -0
  262. package/lib/quoxon/objects/fetch.js +17 -5
  263. package/lib/quoxon/objects/fleet.js +149 -0
  264. package/lib/quoxon/objects/health.js +186 -0
  265. package/lib/quoxon/objects/index.js +46 -7
  266. package/lib/quoxon/objects/mirror.js +137 -0
  267. package/lib/quoxon/objects/model.js +33 -0
  268. package/lib/quoxon/objects/peek.js +17 -0
  269. package/lib/quoxon/objects/proof.js +12 -4
  270. package/lib/quoxon/objects/prove.js +130 -0
  271. package/lib/quoxon/objects/replay.js +125 -0
  272. package/lib/quoxon/objects/sitrep.js +58 -0
  273. package/lib/quoxon/objects/view.js +11 -0
  274. package/lib/quoxon/onboarding/copy.js +32 -4
  275. package/lib/quoxon/onboarding/credential.js +17 -4
  276. package/lib/quoxon/onboarding/dockerOffer.js +45 -0
  277. package/lib/quoxon/onboarding/failureCause.js +33 -0
  278. package/lib/quoxon/onboarding/finish.js +15 -0
  279. package/lib/quoxon/onboarding/index.js +34 -7
  280. package/lib/quoxon/onboarding/installRunner.js +64 -5
  281. package/lib/quoxon/onboarding/preflight.js +17 -3
  282. package/lib/quoxon/onboarding/probe.js +65 -0
  283. package/lib/quoxon/onboarding/shortcutOffer.js +28 -0
  284. package/lib/quoxon/onboarding/view.js +76 -19
  285. package/lib/quoxon/orgLabel.js +10 -0
  286. package/lib/quoxon/orgSwitch.js +59 -0
  287. package/lib/quoxon/ownBroadcast.js +22 -0
  288. package/lib/quoxon/palette.js +43 -0
  289. package/lib/quoxon/planSteps.js +51 -0
  290. package/lib/quoxon/print.js +12 -5
  291. package/lib/quoxon/profileSwitch.js +53 -0
  292. package/lib/quoxon/promptHistory.js +41 -0
  293. package/lib/quoxon/providers.js +48 -0
  294. package/lib/quoxon/qart.js +20 -51
  295. package/lib/quoxon/qdragon.js +410 -0
  296. package/lib/quoxon/qdragonFly.js +62 -0
  297. package/lib/quoxon/qdragonScenes.js +282 -0
  298. package/lib/quoxon/qeyes.js +213 -0
  299. package/lib/quoxon/qmotion.js +75 -37
  300. package/lib/quoxon/render.js +740 -124
  301. package/lib/quoxon/resend.js +72 -0
  302. package/lib/quoxon/roster.js +117 -17
  303. package/lib/quoxon/route.js +6 -5
  304. package/lib/quoxon/rows.js +954 -0
  305. package/lib/quoxon/rowsStore.js +80 -0
  306. package/lib/quoxon/screenContext.js +150 -0
  307. package/lib/quoxon/screenFrames.js +254 -0
  308. package/lib/quoxon/serverChip.js +42 -0
  309. package/lib/quoxon/session.js +182 -2
  310. package/lib/quoxon/sessionDead.js +17 -0
  311. package/lib/quoxon/signin.js +125 -0
  312. package/lib/quoxon/slash/agents.js +112 -0
  313. package/lib/quoxon/slash/argSources.js +65 -0
  314. package/lib/quoxon/slash/args.js +65 -0
  315. package/lib/quoxon/slash/automate.js +89 -0
  316. package/lib/quoxon/slash/broadcast.js +140 -0
  317. package/lib/quoxon/slash/broadcastRun.js +108 -0
  318. package/lib/quoxon/slash/card.js +30 -0
  319. package/lib/quoxon/slash/challenge.js +114 -0
  320. package/lib/quoxon/slash/cliCommand.js +94 -0
  321. package/lib/quoxon/slash/cliLeaves.json +1023 -0
  322. package/lib/quoxon/slash/compact.js +1 -1
  323. package/lib/quoxon/slash/confirm.js +30 -0
  324. package/lib/quoxon/slash/crystallize.js +69 -0
  325. package/lib/quoxon/slash/dashboard.js +136 -0
  326. package/lib/quoxon/slash/draft.js +54 -0
  327. package/lib/quoxon/slash/evidenceCmd.js +34 -0
  328. package/lib/quoxon/slash/hatWalk.js +206 -0
  329. package/lib/quoxon/slash/inverses.js +77 -0
  330. package/lib/quoxon/slash/killer.js +24 -0
  331. package/lib/quoxon/slash/killerApi.js +16 -0
  332. package/lib/quoxon/slash/ledger.js +13 -8
  333. package/lib/quoxon/slash/listView.js +3 -1
  334. package/lib/quoxon/slash/modelCmd.js +261 -0
  335. package/lib/quoxon/slash/palette.js +22 -8
  336. package/lib/quoxon/slash/paletteController.js +117 -7
  337. package/lib/quoxon/slash/panels.js +21 -6
  338. package/lib/quoxon/slash/picker.js +20 -2
  339. package/lib/quoxon/slash/qpeCmd.js +35 -0
  340. package/lib/quoxon/slash/restart.js +95 -13
  341. package/lib/quoxon/slash/resume.js +18 -1
  342. package/lib/quoxon/slash/rowCmd.js +157 -0
  343. package/lib/quoxon/slash/rowWalk.js +175 -0
  344. package/lib/quoxon/slash/settings.js +16 -6
  345. package/lib/quoxon/slash/spawn.js +177 -0
  346. package/lib/quoxon/slash/stepUp.js +274 -0
  347. package/lib/quoxon/slash/stepUpCard.js +60 -0
  348. package/lib/quoxon/slash/stepUpCmds.js +78 -0
  349. package/lib/quoxon/slash/todoCmd.js +122 -0
  350. package/lib/quoxon/slash/undoApi.js +38 -0
  351. package/lib/quoxon/slash/undoCmd.js +49 -0
  352. package/lib/quoxon/slash/undoWish.js +15 -0
  353. package/lib/quoxon/slash/watch.js +164 -0
  354. package/lib/quoxon/slash/wish.js +23 -0
  355. package/lib/quoxon/slash/wishCmd.js +32 -0
  356. package/lib/quoxon/slotChat.js +300 -0
  357. package/lib/quoxon/slotFormat.js +195 -0
  358. package/lib/quoxon/slotRun.js +131 -0
  359. package/lib/quoxon/sound/mapping.js +2 -1
  360. package/lib/quoxon/stray.js +22 -0
  361. package/lib/quoxon/suggestCommand.js +90 -0
  362. package/lib/quoxon/term.js +46 -11
  363. package/lib/quoxon/textBuffer.js +176 -2
  364. package/lib/quoxon/theme.js +58 -25
  365. package/lib/quoxon/tips.js +433 -0
  366. package/lib/quoxon/toolCards.js +30 -0
  367. package/lib/quoxon/toolError.js +41 -0
  368. package/lib/quoxon/transcript.js +42 -4
  369. package/lib/quoxon/web/qface.js +18 -10
  370. package/lib/quoxon/welcome.js +1 -1
  371. package/lib/quoxon/whatsnew.js +136 -0
  372. package/lib/quoxon/whatsnew.jsonl +134 -0
  373. package/lib/quoxon/windowOrg.js +123 -0
  374. package/lib/server/devServers.js +137 -0
  375. package/lib/server/diag.js +129 -0
  376. package/lib/server/doctor.js +71 -0
  377. package/lib/server/install.js +149 -0
  378. package/lib/server/pluginEnable.js +161 -0
  379. package/lib/server/skew.js +56 -0
  380. package/lib/server/snapshot.js +131 -0
  381. package/lib/server/status.js +128 -0
  382. package/lib/server/sys.js +65 -0
  383. package/lib/server/uninstall.js +63 -0
  384. package/lib/server/update.js +234 -0
  385. package/lib/toolOutputShape.js +315 -0
  386. package/lib/tui/views/HyperionDeck.jsx +79 -0
  387. package/lib/tui/views/LucidDeck.jsx +209 -217
  388. package/package.json +5 -1
  389. package/vendor/dragonlab/SOURCE +4 -0
  390. package/vendor/dragonlab/design/cipher/v1/cipher-master.svg +46 -0
  391. package/vendor/dragonlab/design/cipher/v2/character.mjs +49 -0
  392. package/vendor/dragonlab/design/cipher/v2/review.mjs +33 -0
  393. package/vendor/dragonlab/design/cipher/v2/watch.mjs +56 -0
  394. package/vendor/dragonlab/design/drago/v1/character.mjs +19 -0
  395. package/vendor/dragonlab/design/drago/v1/drago-master.svg +56 -0
  396. package/vendor/dragonlab/design/drago/v2/character.mjs +87 -0
  397. package/vendor/dragonlab/design/drago/v4/character.mjs +67 -0
  398. package/vendor/dragonlab/design/drago/v4/profile.mjs +35 -0
  399. package/vendor/dragonlab/design/drago/v4/review.mjs +33 -0
  400. package/vendor/dragonlab/design/drago/v4/watch.mjs +56 -0
  401. package/vendor/dragonlab/design/hyperion/v1/hyperion-master.svg +56 -0
  402. package/vendor/dragonlab/design/hyperion/v2/character.mjs +79 -0
  403. package/vendor/dragonlab/design/hyperion/v2/review.mjs +33 -0
  404. package/vendor/dragonlab/design/hyperion/v2/watch.mjs +56 -0
  405. package/vendor/dragonlab/design/lucid/recovery/arrival.mjs +22 -0
  406. package/vendor/dragonlab/design/lucid/recovery/character.mjs +106 -0
  407. package/vendor/dragonlab/design/lucid/recovery/layout.mjs +50 -0
  408. package/vendor/dragonlab/design/lucid/recovery/lucid-master.svg +78 -0
  409. package/vendor/dragonlab/design/lucid/recovery/motion.mjs +42 -0
  410. package/vendor/dragonlab/design/lucid/recovery/render.mjs +121 -0
  411. package/vendor/dragonlab/design/lucid/recovery/review.mjs +68 -0
  412. package/vendor/dragonlab/design/lucid/recovery/verify.mjs +84 -0
  413. package/vendor/dragonlab/design/lucid/recovery/watch.mjs +56 -0
  414. package/vendor/dragonlab/design/seer/v1/seer-master.svg +53 -0
  415. package/vendor/dragonlab/design/seer/v2/character.mjs +88 -0
  416. package/vendor/dragonlab/design/seer/v3/character.mjs +33 -0
  417. package/vendor/dragonlab/design/seer/v4/character.mjs +66 -0
  418. package/vendor/dragonlab/design/seer/v4/review.mjs +33 -0
  419. package/vendor/dragonlab/design/seer/v4/watch.mjs +56 -0
  420. package/vendor/dragonlab/design/stage3/companion-review.mjs +28 -0
  421. package/vendor/dragonlab/design/stage3/sentinel-art.js +39 -0
  422. package/vendor/dragonlab/design/stage3/sentinel-braille.js +216 -0
  423. package/vendor/dragonlab/design/stage3/sentinel-character.js +257 -0
  424. package/vendor/dragonlab/design/stage3/sentinel-icon.js +103 -0
  425. package/vendor/dragonlab/design/stage3/sentinel-warden-eyes.js +95 -0
  426. package/vendor/dragonlab/design/stage3/svg-coverage.js +148 -0
  427. package/vendor/dragonlab/factory/svg-rig.mjs +63 -0
  428. package/vendor/dragonlab/integration/companions-v1/characters.mjs +88 -0
  429. package/vendor/dragonlab/integration/companions-v1/cipher.mjs +2 -0
  430. package/vendor/dragonlab/integration/companions-v1/companion.mjs +63 -0
  431. package/vendor/dragonlab/integration/companions-v1/drago.mjs +2 -0
  432. package/vendor/dragonlab/integration/companions-v1/hyperion.mjs +2 -0
  433. package/vendor/dragonlab/integration/companions-v1/lucid.mjs +2 -0
  434. package/vendor/dragonlab/integration/companions-v1/seer.mjs +2 -0
  435. package/vendor/dragonlab/integration/companions-v1/sentinel.mjs +2 -0
  436. package/vendor/dragonlab/integration/companions-v1/view.mjs +28 -0
  437. package/vendor/dragonlab/integration/hyperion-v3/activity.mjs +76 -0
  438. package/vendor/dragonlab/lib/power-effects.js +294 -0
  439. package/vendor/dragonlab/lib/power-registry.js +74 -0
  440. package/vendor/dragonlab/vendor/quox-dragon/qart.js +348 -0
  441. package/vendor/dragonlab/vendor/quox-dragon/qeyes.js +213 -0
  442. package/lib/core/websiteSession.js +0 -148
@@ -0,0 +1,90 @@
1
+ /**
2
+ * The assistant can hand the TUI a slash command for things only the terminal can do (attach a slot, join a room,
3
+ * open a view). The TUI is the authority: only an active registry command, never a `!` shell, never the commands the
4
+ * human must type (approvals, grants, session end, sign-in). The offer is "Run /slot hitl? y/N"; y runs it through the
5
+ * normal command path so every existing guard applies. A read-only view may run without asking when the user's
6
+ * ask was explicit.
7
+ */
8
+ import { findCommandEntry } from './commands.js';
9
+
10
+ // Commands that need the human's own typing: never offered, never run from a model suggestion.
11
+ export const HUMAN_TYPED = new Set(['approve', 'yestoall', 'deny', 'authorise', 'authorize', 'authenticate', 'override', 'restart', 'quit', 'exit', 'login']);
12
+ // Pure views, no effect: may run without asking when explicit, and only with no arguments.
13
+ export const READ_ONLY_VIEWS = new Set(['approvals', 'mind', 'which', 'context', 'rooms', 'active', 'slots']);
14
+
15
+ /** -> { ok: true, line, id, readOnly } | { ok: false, reason } */
16
+ export function vetSuggestion(raw) {
17
+ const line = typeof raw === 'string' ? raw.trim() : '';
18
+ if (!line) return { ok: false, reason: 'empty' };
19
+ if (/[\r\n]/.test(line) || line.length > 120) return { ok: false, reason: 'not a single short line' };
20
+ if (!line.startsWith('/') || line.startsWith('//')) return { ok: false, reason: 'only slash commands can be suggested' };
21
+ const word = line.slice(1).split(/\s+/)[0];
22
+ const entry = findCommandEntry(word);
23
+ if (!entry || entry.kind !== 'command' || entry.status !== 'active') return { ok: false, reason: `/${word} is not a command here` };
24
+ if (HUMAN_TYPED.has(entry.id) || HUMAN_TYPED.has(word.toLowerCase())) return { ok: false, reason: `/${word} must be typed by you` };
25
+ const bare = line.slice(1 + word.length).trim() === '';
26
+ return { ok: true, line, id: entry.id, readOnly: READ_ONLY_VIEWS.has(entry.id) && bare };
27
+ }
28
+
29
+ /**
30
+ * Safety net (owner 2026-10-15 "STOP TELLING ME TO RUN COMMANDS"): the answer text named a command instead of the
31
+ * model calling terminal_suggest_command. Exactly ONE distinct slash command in backticks that passes vetSuggestion
32
+ * -> that vetted suggestion; none, two or more distinct, or a human-typed one (/approve, /restart...) -> null.
33
+ */
34
+ export function namedCommand(text) {
35
+ const src = String(text || '');
36
+ const closing = closingProposal(src);
37
+ if (closing !== undefined) return closing; // the answer's own closing question names THE command: that one or nothing
38
+ const seen = new Set();
39
+ for (const m of src.matchAll(/`(\/[a-z][^`\n]{0,118})`/gi)) {
40
+ if (mentionsAPath(src, m.index, m[0].length, m[1])) continue; // `/health` the endpoint is not /health the command
41
+ seen.add(m[1].trim().replace(/\s+/g, ' '));
42
+ }
43
+ if (seen.size !== 1) return null;
44
+ return offerable([...seen][0]);
45
+ }
46
+
47
+ // Commands that SEND or WRITE something: a mention is never an offer (the model must call terminal_suggest_command).
48
+ export const WRITE_COMMANDS = new Set(['broadcast', 'roar', 'say', 'page', 'ack', 'reply', 'r', 'ask', 'aside', 'afk', 'back', 'observe', 'awaken', 'poke', 'wake', 'nudge', 'ping', 'slap', 'feedback', 'gripe', 'gripe!', 'note', 'dragon', 'wish', 'challenge', 'huh']);
49
+
50
+ // vetted AND safe to offer from a mention: not a write/send, and no required <arg> left unfilled.
51
+ function offerable(line) {
52
+ const v = vetSuggestion(line);
53
+ if (!v.ok) return null;
54
+ if (WRITE_COMMANDS.has(v.id) || WRITE_COMMANDS.has(line.slice(1).split(/\s+/)[0].toLowerCase())) return null;
55
+ const entry = findCommandEntry(line.slice(1).split(/\s+/)[0]);
56
+ const bare = line.trim().split(/\s+/).length === 1;
57
+ if (bare && /</.test(String(entry?.usage || ''))) return null; // "/ack <broadcast id>" needs its argument
58
+ return v;
59
+ }
60
+
61
+ // "Do you want me to run X?" / "Shall I run X?" -> undefined (no such question) | the offer for X | null.
62
+ // X as a slash command is offered when safe; X as a `quox <verb>` CLI line is offered only as the matching read-only slash view.
63
+ function closingProposal(src) {
64
+ const m = /(?:do you want me to|want me to|would you like me to|shall i|should i|can i)\s+(?:run|try|use|show you)\s+`?([^?`\n]{1,120}?)`?\s*(?:and\b[^?\n]*)?\?\s*$/i.exec(src.trim());
65
+ if (!m) return undefined;
66
+ const x = m[1].trim();
67
+ if (x.startsWith('/')) return offerable(x.replace(/\s+/g, ' '));
68
+ const cli = /^!?quox\s+([a-z][\w-]*)\s*$/i.exec(x);
69
+ if (cli) {
70
+ const slash = '/' + cli[1].toLowerCase();
71
+ const v = offerable(slash);
72
+ return v && v.readOnly ? v : null;
73
+ }
74
+ return null;
75
+ }
76
+
77
+ // A URL path, not a command (owner live 05:26: "the `/health` endpoint is erroring" offered "Run /health?").
78
+ const PATHY = /\b(endpoints?|paths?|routes?|urls?|uris?|ports?|curl|wget|https?|localhost)\b|:\d{2,5}\b/i;
79
+ const VERBS = /\b(GET|POST|PUT|PATCH|DELETE|HEAD)\b/; // upper-case only: "to get a view" is not an HTTP verb
80
+ function mentionsAPath(src, at, len, inner) {
81
+ const first = String(inner).trim().split(/\s+/)[0];
82
+ if (/^\/[^/]*\//.test(first)) return true; // /api/x or /health/ready: a longer path
83
+ const sentence = (from, dir) => { // the same sentence, up to 40 characters away
84
+ const t = dir < 0 ? src.slice(Math.max(0, from - 40), from) : src.slice(from, from + 40);
85
+ const parts = t.split(/[.!?\n]/);
86
+ return dir < 0 ? parts[parts.length - 1] : parts[0];
87
+ };
88
+ const near = sentence(at, -1) + ' ' + sentence(at + len, 1);
89
+ return PATHY.test(near) || VERBS.test(near);
90
+ }
@@ -1,3 +1,5 @@
1
+ import { graphemes } from './textBuffer.js';
2
+ import { createScreenMirror, MOUSE_ON, MOUSE_OFF, DSR } from './mouse.js';
1
3
  /**
2
4
  * The terminal as Ink sees it.
3
5
  *
@@ -12,9 +14,16 @@
12
14
  * repaint checks read stdout.rows). The app reads the real height from termRows() and keeps its live
13
15
  * region shorter than the screen itself (app.js), so log-update can always erase it.
14
16
  */
15
- export function inkStdout(out) {
17
+ export function inkStdout(out, { tap = null } = {}) {
16
18
  if (!out || out.__quoxInk) return out;
17
- const diff = out.isTTY ? createLineDiff() : null;
19
+ // QUOXON-MOUSE: what Ink has drawn, as plain lines, for click hit-testing (mouse.js). Fed the same writes.
20
+ const mirror = out.isTTY ? createScreenMirror({ columns: () => out.columns }) : null;
21
+ // SCREEN-FRAMES: the same feed goes to the screen recorder's tap (screenFrames.js), which only queues it.
22
+ const feed = mirror && tap ? {
23
+ write: (c) => { mirror.write(c); try { tap.write(c); } catch { /* never break a frame */ } },
24
+ reset: () => { mirror.reset(); try { tap.reset(); } catch { /* ignore */ } }
25
+ } : mirror;
26
+ const diff = out.isTTY ? createLineDiff({ columns: () => out.columns, mirror: feed }) : null;
18
27
  // One frame = one write: Ink brackets each frame in separate synchronized-update writes (?2026h ... ?2026l);
19
28
  // they are held and sent together with the (diffed) frame, and a frame that changed nothing sends nothing.
20
29
  // A write that sends nothing (or is held for the frame) must still call its callback: Ink's unmount waits
@@ -44,7 +53,9 @@ export function inkStdout(out) {
44
53
  if (prop === 'write' && write) return write;
45
54
  if (prop === 'rows') return Infinity;
46
55
  if (prop === 'termRows') return target.rows;
56
+ if (prop === 'frameRows') { const n = diff?.rows(); return n == null ? null : Math.min(n, Math.max(0, (target.rows || n + 1) - 1)); }
47
57
  if (prop === '__quoxInk') return true;
58
+ if (prop === 'screen') return mirror;
48
59
  const v = Reflect.get(target, prop, target);
49
60
  return typeof v === 'function' ? v.bind(target) : v;
50
61
  },
@@ -69,16 +80,34 @@ export function termRows(stdout) {
69
80
  * through untouched and resets the diff, so the next frame is drawn in full. Cursor moves never scroll.
70
81
  * (Ink's own incrementalRendering was tried first and duplicates lines on the dropped-text replay.)
71
82
  */
83
+ const ANSI_RE = /\x1b\[[0-9;?]*[ -\/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g;
84
+ const visibleWidth = (line) => graphemes(String(line).replace(ANSI_RE, '')).length;
72
85
  const FRAME_RE = /^((?:\x1b\[2K\x1b\[1A)*\x1b\[2K\x1b\[G)([\s\S]*)$/;
73
86
  const SYNC_ON = '\x1b[?2026h', SYNC_OFF = '\x1b[?2026l';
74
- const TRANSPARENT = new Set(['\x1b[?2026h', '\x1b[?2026l', '\x1b[?25l', '\x1b[?25h', '']);
75
- export function createLineDiff() {
87
+ const TRANSPARENT = new Set(['\x1b[?2026h', '\x1b[?2026l', '\x1b[?25l', '\x1b[?25h', '', MOUSE_ON, MOUSE_OFF, DSR]); // + QUOXON-MOUSE mode switches and the click's cursor query
88
+ export function createLineDiff({ columns = () => null, mirror = null } = {}) {
76
89
  let prev = null; // lines of the last frame, trailing '' included
90
+ const bodyLines = (body) => (body && body.endsWith('\n') && !body.includes('\x1b[2J') ? body.split('\n') : null);
77
91
  const diff = (chunk) => {
78
- if (TRANSPARENT.has(chunk)) return chunk; // synchronized-update brackets and cursor hide/show around a frame
92
+ if (TRANSPARENT.has(chunk)) return chunk;
93
+ mirror?.write(chunk); // synchronized-update brackets and cursor hide/show around a frame
79
94
  const m = FRAME_RE.exec(chunk);
80
- if (!m || !m[2].endsWith('\n') || m[2].includes('\x1b[2J')) { prev = null; return chunk; }
95
+ // Not an erase+frame: a frame Ink drew after clearing (no erase prefix), static history, or something else.
96
+ // The last such write before the next erase is the frame on screen (Ink writes static output, then the frame).
97
+ if (!m) { prev = bodyLines(chunk); return chunk; }
81
98
  const erased = (m[1].match(/\x1b\[2K/g) || []).length;
99
+ const cols = columns();
100
+ if (prev && cols > 0) {
101
+ // R3 D2: after a resize the terminal has reflowed the old frame (a narrower screen wraps each line onto more
102
+ // rows), and Ink's erase counts lines, not rows: ghost composers, or history reprinted to cover them. Count
103
+ // the rows the frame really occupies at this width; when that differs, go to its top and clear down.
104
+ const rows = prev.slice(0, -1).reduce((n, l) => n + Math.max(1, Math.ceil(visibleWidth(l) / cols)), 0);
105
+ if (rows !== erased - 1) {
106
+ prev = bodyLines(m[2]);
107
+ return `\r${rows ? `\x1b[${rows}A` : ''}\x1b[J${m[2]}`;
108
+ }
109
+ }
110
+ if (!m[2].endsWith('\n') || m[2].includes('\x1b[2J')) { prev = null; return chunk; }
82
111
  const next = m[2].split('\n');
83
112
  if (!prev || erased !== prev.length || next.length !== prev.length || next.length < 2) { prev = next; return chunk; }
84
113
  const body = next.length - 1;
@@ -92,15 +121,21 @@ export function createLineDiff() {
92
121
  prev = next;
93
122
  return changed ? out : '';
94
123
  };
95
- diff.reset = () => { prev = null; };
124
+ diff.reset = () => { prev = null; mirror?.reset(); };
125
+ /** Rows the live frame occupies on screen (wrapped at the current width), or null when unknown. SLOT-RUN hand-over. */
126
+ diff.rows = () => {
127
+ if (!prev) return null;
128
+ const cols = columns();
129
+ return prev.slice(0, -1).reduce((n, l) => n + (cols > 0 ? Math.max(1, Math.ceil(visibleWidth(l) / cols)) : 1), 0);
130
+ };
96
131
  return diff;
97
132
  }
98
133
 
99
134
  /**
100
135
  * Ink render options for the interactive client. Standard rendering (Ink's incrementalRendering duplicated lines on
101
- * the dropped-text replay); the stdout proxy line-diffs same-height frames instead. Never mouse tracking, never the
102
- * alternate screen: native selection keeps working.
136
+ * the dropped-text replay); the stdout proxy line-diffs same-height frames instead. Never the alternate screen, and
137
+ * no mouse tracking unless the user turns it on (/mouse on, QUOXON-MOUSE): native selection keeps working by default.
103
138
  */
104
- export function inkRenderOptions(stdout) {
105
- return { exitOnCtrlC: false, stdout: inkStdout(stdout) };
139
+ export function inkRenderOptions(stdout, { tap = null } = {}) {
140
+ return { exitOnCtrlC: false, stdout: inkStdout(stdout, { tap }) };
106
141
  }
@@ -18,7 +18,135 @@ const UNDO_LIMIT = 100;
18
18
  const PASTE_RE = /\[Pasted text #(\d+) \+\d+ lines\]/g;
19
19
 
20
20
  export function initialBuffer(text = '') {
21
- return { text, cursor: text.length, undo: [], redo: [], pastes: {}, pasteSeq: 0 };
21
+ return { text, cursor: text.length, anchor: null, undo: [], redo: [], pastes: {}, pasteSeq: 0 };
22
+ }
23
+
24
+ /**
25
+ * QUOXON-MOUSE: the composer selection, { from, to } (from < to), or null. A selection is the range between
26
+ * `anchor` (where a drag started) and the cursor; only the mouse sets an anchor.
27
+ */
28
+ export function selection(s) {
29
+ if (s?.anchor == null || s.anchor === s.cursor) return null;
30
+ return { from: Math.min(s.anchor, s.cursor), to: Math.max(s.anchor, s.cursor) };
31
+ }
32
+
33
+ /** The word under `idx` for a double-click: a run of non-space characters, or the one space character itself. */
34
+ export function wordAt(text, idx) {
35
+ const t = String(text);
36
+ const i = Math.max(0, Math.min(idx, t.length - 1));
37
+ if (!t.length) return { from: 0, to: 0 };
38
+ if (/\s/.test(t[i])) return { from: i, to: nextBoundary(t, i) };
39
+ let a = i; let b = i;
40
+ while (a > 0 && !/\s/.test(t[a - 1])) a--;
41
+ while (b < t.length && !/\s/.test(t[b])) b++;
42
+ return { from: a, to: b };
43
+ }
44
+
45
+ /** Snap an index onto a grapheme boundary (at or before it). */
46
+ function snap(text, idx) {
47
+ const i = Math.max(0, Math.min(Number(idx) || 0, text.length));
48
+ return i === text.length ? i : prevBoundary(text, i + 1);
49
+ }
50
+
51
+ /** The selected range widened so it never leaves half a paste placeholder behind. */
52
+ function selRange(s, sel) {
53
+ let { from, to } = sel;
54
+ PASTE_RE.lastIndex = 0;
55
+ let m;
56
+ while ((m = PASTE_RE.exec(s.text))) {
57
+ const a = m.index, b = a + m[0].length;
58
+ if (to > a && from < b) { from = Math.min(from, a); to = Math.max(to, b); }
59
+ }
60
+ return { from, to };
61
+ }
62
+
63
+ const EDITS = new Set(['insert', 'newline', 'paste', 'backspace', 'delete', 'deleteWordBack', 'killLineBack', 'killLineForward', 'deleteWordForward']);
64
+
65
+ /** COMPOSER-EDIT: the selected text, paste placeholders expanded (what Shift selection copies). '' when none. */
66
+ export function selectedText(s) {
67
+ const sel = selection(s);
68
+ if (!sel) return '';
69
+ const { from, to } = selRange(s, sel);
70
+ return expandPastes({ text: s.text.slice(from, to), pastes: s.pastes || {} });
71
+ }
72
+
73
+ // ---- COMPOSER-EDIT: words and soft-wrapped rows ------------------------------------------------
74
+ const WORD = /[\p{L}\p{N}_]/u;
75
+ /** Readline backward-word: skip non-word characters, then the word. */
76
+ function wordLeftIndex(t, i) {
77
+ while (i > 0 && !WORD.test(t[i - 1])) i--;
78
+ while (i > 0 && WORD.test(t[i - 1])) i--;
79
+ return i;
80
+ }
81
+ /** Readline forward-word: skip non-word characters, then the word (lands at its end). */
82
+ function wordRightIndex(t, i) {
83
+ while (i < t.length && !WORD.test(t[i])) i++;
84
+ while (i < t.length && WORD.test(t[i])) i++;
85
+ return i;
86
+ }
87
+ /** Remove [from, to), widened so no paste placeholder is left half cut. */
88
+ function cutRange(s, from, to) {
89
+ if (from >= to) return s;
90
+ const r = selRange(s, { from, to });
91
+ return commit(s, { text: s.text.slice(0, r.from) + s.text.slice(r.to), cursor: r.from });
92
+ }
93
+
94
+ const cellW = (g) => (/[ᄀ-ᅟ⺀-〾ぁ-㏿㐀-䶿一-鿿ꀀ-꓏가-힣豈-﫿︰-﹏＀-⦆¢-₩]/u.test(g) || (/\p{Extended_Pictographic}/u.test(g) && (g.codePointAt(0) >= 0x1f000 || g.includes('\uFE0F'))) ? 2 : 1);
95
+ /**
96
+ * The draft as the rows the terminal shows at `width` cells: [{ start, end }] (UTF-16 indices, end exclusive,
97
+ * newline not included). Word wrap like Ink's: a word that does not fit starts a new row, spaces hang at the row
98
+ * end, and a word longer than a row is split hard.
99
+ */
100
+ export function visualRows(text, width) {
101
+ const out = [];
102
+ let o = 0;
103
+ for (const line of String(text).split('\n')) {
104
+ if (!(width > 0) || !line.length) { out.push({ start: o, end: o + line.length }); o += line.length + 1; continue; }
105
+ const tokens = line.match(/\s+|\S+/g);
106
+ let rowStart = o; let w = 0; let p = o;
107
+ for (const tok of tokens) {
108
+ const gs = graphemes(tok);
109
+ const tw = gs.reduce((n, g) => n + cellW(g.text), 0);
110
+ if (/^\s/.test(tok) || w + tw <= width) { w += tw; p += tok.length; continue; }
111
+ if (tw <= width) { out.push({ start: rowStart, end: p }); rowStart = p; w = tw; p += tok.length; continue; }
112
+ for (const g of gs) { // longer than a row: split it hard
113
+ const cw = cellW(g.text);
114
+ if (w + cw > width && w > 0) { out.push({ start: rowStart, end: p }); rowStart = p; w = 0; }
115
+ w += cw; p += g.text.length;
116
+ }
117
+ }
118
+ out.push({ start: rowStart, end: o + line.length });
119
+ o += line.length + 1;
120
+ }
121
+ return out;
122
+ }
123
+ /** Index of the visual row holding `cursor`. A cursor at a soft break sits at the start of the next row. */
124
+ function rowOf(rows, cursor) {
125
+ for (let i = rows.length - 1; i >= 0; i--) if (cursor >= rows[i].start) return i;
126
+ return 0;
127
+ }
128
+ /** COMPOSER-EDIT: is the cursor on the first (`up`) or last (`down`) row the terminal shows? */
129
+ export function atVisualEdge(s, dir, width) {
130
+ if (!(width > 0)) {
131
+ return dir === 'up' ? !s.text.slice(0, s.cursor).includes('\n') : !s.text.slice(s.cursor).includes('\n');
132
+ }
133
+ const rows = visualRows(s.text, width);
134
+ const r = rowOf(rows, s.cursor);
135
+ return dir === 'up' ? r === 0 : r === rows.length - 1;
136
+ }
137
+ function moveVisual(s, dir, width) {
138
+ const rows = visualRows(s.text, width);
139
+ const r = rowOf(rows, s.cursor);
140
+ const t = dir === 'up' ? r - 1 : r + 1;
141
+ if (t < 0 || t >= rows.length) return s;
142
+ const col = graphemes(s.text.slice(rows[r].start, s.cursor)).reduce((n, g) => n + cellW(g.text), 0);
143
+ const row = rows[t];
144
+ const soft = t + 1 < rows.length && rows[t + 1].start === row.end; // the row breaks softly: its end is the next row's start
145
+ const gs = graphemes(s.text.slice(row.start, row.end));
146
+ let at = 0; let idx = row.end;
147
+ for (const g of gs) { const w = cellW(g.text); if (col < at + w) { idx = row.start + g.index; break; } at += w; }
148
+ if (soft && idx >= row.end) idx = prevBoundary(s.text, row.end);
149
+ return { ...s, cursor: idx };
22
150
  }
23
151
 
24
152
  export function graphemes(text) {
@@ -86,6 +214,14 @@ function indexAtLineCol(text, line, col) {
86
214
  return start + gs[col].index;
87
215
  }
88
216
 
217
+ /** First logical line the composer shows (it shows at most `maxLines`, keeping the cursor line in view). */
218
+ export function composerFrom(buf, maxLines = 8) {
219
+ const n = buf.text.split('\n').length;
220
+ if (n <= maxLines) return 0;
221
+ const { line } = lineCol(buf.text, buf.cursor);
222
+ return Math.min(Math.max(0, line - Math.floor(maxLines / 2)), n - maxLines);
223
+ }
224
+
89
225
  export function normalizeNewlines(t) { return String(t).replace(/\r\n?/g, '\n'); }
90
226
 
91
227
  /** Build the collapsed form of a paste, or null when it is small enough to inline. */
@@ -94,7 +230,36 @@ export function shouldCollapse(text) {
94
230
  return text.length > PASTE_CHAR_LIMIT || lines > PASTE_LINE_LIMIT;
95
231
  }
96
232
 
97
- export function bufferReducer(s, a) {
233
+ export function bufferReducer(s0, a) {
234
+ const sel = selection(s0);
235
+ const s = s0.anchor == null ? s0 : { ...s0, anchor: null };
236
+ if (sel && EDITS.has(a.type)) {
237
+ // QUOXON-MOUSE: Backspace/Delete remove the selection; typing or pasting replaces it. One undo step.
238
+ const { from, to } = selRange(s, sel);
239
+ const cut = { ...s, text: s.text.slice(0, from) + s.text.slice(to), cursor: from };
240
+ if (a.type !== 'insert' && a.type !== 'newline' && a.type !== 'paste') return commit(s, { text: cut.text, cursor: from });
241
+ const next = baseReducer({ ...cut, undo: [] }, a);
242
+ return commit(s, { text: next.text, cursor: next.cursor, pastes: next.pastes, pasteSeq: next.pasteSeq });
243
+ }
244
+ if (sel && a.type === 'left') return { ...s, cursor: sel.from };
245
+ if (sel && a.type === 'right') return { ...s, cursor: sel.to };
246
+ switch (a.type) {
247
+ case 'extend': { // COMPOSER-EDIT: Shift+movement grows the selection from where it started
248
+ const anchor = s0.anchor ?? s0.cursor;
249
+ const moved = baseReducer({ ...s, anchor: null }, { type: a.move, width: a.width });
250
+ return { ...s, cursor: moved.cursor, anchor: moved.cursor === anchor ? null : anchor };
251
+ }
252
+ case 'selectAll': return s.text ? { ...s, anchor: 0, cursor: s.text.length } : s;
253
+ case 'place': return { ...s, cursor: snap(s.text, a.index) };
254
+ case 'select': {
255
+ const anchor = snap(s.text, a.anchor); const cursor = snap(s.text, a.cursor);
256
+ return { ...s, anchor: anchor === cursor ? null : anchor, cursor };
257
+ }
258
+ default: return baseReducer(s, a);
259
+ }
260
+ }
261
+
262
+ function baseReducer(s, a) {
98
263
  switch (a.type) {
99
264
  case 'insert': {
100
265
  const t = normalizeNewlines(a.text);
@@ -144,6 +309,14 @@ export function bufferReducer(s, a) {
144
309
  if (from === s.cursor) return s;
145
310
  return commit(s, { text: s.text.slice(0, from) + s.text.slice(s.cursor), cursor: from });
146
311
  }
312
+ case 'wordLeft': return { ...s, cursor: wordLeftIndex(s.text, s.cursor) };
313
+ case 'wordRight': return { ...s, cursor: wordRightIndex(s.text, s.cursor) };
314
+ case 'killLineForward': { // Ctrl+K: to the end of the line; at the end, join the next line
315
+ const nl = s.text.indexOf('\n', s.cursor);
316
+ const end = nl === -1 ? s.text.length : (nl === s.cursor ? nl + 1 : nl);
317
+ return cutRange(s, s.cursor, end);
318
+ }
319
+ case 'deleteWordForward': return cutRange(s, s.cursor, wordRightIndex(s.text, s.cursor)); // Alt+D
147
320
  case 'left': return { ...s, cursor: prevBoundary(s.text, s.cursor) };
148
321
  case 'right': return { ...s, cursor: nextBoundary(s.text, s.cursor) };
149
322
  case 'home': return { ...s, cursor: lineCol(s.text, s.cursor).start };
@@ -152,6 +325,7 @@ export function bufferReducer(s, a) {
152
325
  return { ...s, cursor: nl === -1 ? s.text.length : nl };
153
326
  }
154
327
  case 'up': case 'down': {
328
+ if (a.width > 0) return moveVisual(s, a.type, a.width); // COMPOSER-EDIT: through soft-wrapped rows
155
329
  const { line, col } = lineCol(s.text, s.cursor);
156
330
  const target = a.type === 'up' ? line - 1 : line + 1;
157
331
  const count = s.text.split('\n').length;
@@ -8,6 +8,7 @@
8
8
  * Purple is for keys and accents only, never whole sentences.
9
9
  */
10
10
  import { graphemes } from "./textBuffer.js";
11
+ import { SEMANTIC_DARK, SEMANTIC_LIGHT, stripMarks, MARK } from './palette.js';
11
12
 
12
13
  export const MIN_COLS = 40;
13
14
 
@@ -20,14 +21,18 @@ export const DARK = {
20
21
  value: ['#f1f5f9', 255],
21
22
  number: ['#38bdf8', 75],
22
23
  link: ['#38bdf8', 75],
23
- code: ['#06b6d4', 38], codeBg: ['#1f2937', 235], codeRule: ['#155e75', 24],
24
- key: ['#a855f7', 135],
25
- muted: ['#7d8594', 245],
24
+ code: ['#2dd4bf', 80], codeBg: ['#1f2937', 235], codeRule: ['#155e75', 24],
25
+ key: ['#2dd4bf', 80], // a thing you type: the command colour (purple is brand chrome only)
26
+ muted: ['#bcc8e8', 189],
26
27
  ok: ['#4ade80', 78], warn: ['#f59e0b', 214], error: ['#f87171', 210],
28
+ ask: ['#f472b6', 211], // QUOXON-ASK: "needs you" (every y/N and approval): glyph and keys, bold line
29
+ gold: ['#d4af37', 178], // the authorisation moment: step-up PIN card frame, filled dots, human-present countdown
27
30
  // legacy names, kept so existing call sites read the same
28
31
  sky: ['#38bdf8', 75], violet: ['#a855f7', 135], indigo: ['#818cf8', 105], teal: ['#06b6d4', 38],
29
- fail: ['#f87171', 210], dim: ['#7d8594', 245], faint: ['#4b5563', 240],
30
- text: ['#cbd5e1', 252], input: ['#ffffff', 231], placeholder: ['#9ca3af', 248]
32
+ fail: ['#f87171', 210], dim: ['#bcc8e8', 189], faint: ['#7c8fe0', 105],
33
+ text: ['#f1f5f9', 255], input: ['#ffffff', 231], placeholder: ['#bcc8e8', 189],
34
+ // semantic roles (palette.js)
35
+ prose: SEMANTIC_DARK.prose, command: SEMANTIC_DARK.command, id: SEMANTIC_DARK.value, brand: SEMANTIC_DARK.brand, quiet: SEMANTIC_DARK.quiet, rule: SEMANTIC_DARK.rule
31
36
  };
32
37
  export const LIGHT = {
33
38
  heading: ['#0369a1', 25],
@@ -37,16 +42,19 @@ export const LIGHT = {
37
42
  value: ['#0f172a', 234],
38
43
  number: ['#0369a1', 25],
39
44
  link: ['#0369a1', 25],
40
- code: ['#0e7490', 30], codeBg: ['#ecfeff', 195], codeRule: ['#67e8f9', 117],
41
- key: ['#7e22ce', 91],
42
- muted: ['#4b5563', 240],
45
+ code: ['#0f766e', 30], codeBg: ['#ecfeff', 195], codeRule: ['#67e8f9', 117],
46
+ key: ['#0f766e', 30],
47
+ muted: ['#3b4f87', 60],
43
48
  ok: ['#15803d', 28], warn: ['#b45309', 130], error: ['#b91c1c', 124],
49
+ ask: ['#be185d', 161],
50
+ gold: ['#8a6500', 94],
44
51
  sky: ['#0369a1', 25], violet: ['#7e22ce', 91], indigo: ['#4338ca', 62], teal: ['#0e7490', 30],
45
- fail: ['#b91c1c', 124], dim: ['#4b5563', 240], faint: ['#9ca3af', 248],
46
- text: ['#334155', 238], input: ['#0f172a', 234], placeholder: ['#6b7280', 243]
52
+ fail: ['#b91c1c', 124], dim: ['#3b4f87', 60], faint: ['#7280c8', 104],
53
+ text: ['#0f172a', 234], input: ['#0f172a', 234], placeholder: ['#3b4f87', 60],
54
+ prose: SEMANTIC_LIGHT.prose, command: SEMANTIC_LIGHT.command, id: SEMANTIC_LIGHT.value, brand: SEMANTIC_LIGHT.brand, quiet: SEMANTIC_LIGHT.quiet, rule: SEMANTIC_LIGHT.rule
47
55
  };
48
56
  /** Roles a reader must be able to read: each is held to >= 4.5:1 on its background (tests). */
49
- export const ROLES = ['heading', 'icon', 'bullet1', 'bullet2', 'bullet3', 'label', 'value', 'number', 'link', 'code', 'key', 'muted', 'ok', 'warn', 'error', 'text', 'placeholder'];
57
+ export const ROLES = ['heading', 'icon', 'bullet1', 'bullet2', 'bullet3', 'label', 'value', 'number', 'link', 'code', 'key', 'muted', 'ok', 'warn', 'error', 'ask', 'gold', 'text', 'placeholder', 'prose', 'command', 'id', 'brand', 'quiet'];
50
58
 
51
59
  /** Heading gradient stops across the word: sky -> indigo -> violet (light: the -700 shades). */
52
60
  export const HEADING_STOPS = { dark: ['#38bdf8', '#6366f1', '#a855f7'], light: ['#0369a1', '#4338ca', '#7e22ce'] };
@@ -59,12 +67,12 @@ export const AGENT_SHADES = {
59
67
  /** Model providers in their brand colours (dark, light). Unknown = muted. */
60
68
  export const PROVIDER_COLORS = {
61
69
  openai: ['#f5f5f5', '#111111'], anthropic: ['#D97757', '#b4532e'], google: ['#4796E3', '#1a62c4'],
62
- ollama: ['#e5e7eb', '#4b5563'], mistral: ['#FA520F', '#c2410c'], deepseek: ['#4D6BFE', '#2f4fe0'],
70
+ ollama: ['#e5e7eb', '#3b4f87'], mistral: ['#FA520F', '#c2410c'], deepseek: ['#4D6BFE', '#2f4fe0'],
63
71
  perplexity: ['#20808D', '#1a6b76'], xai: ['#f5f5f5', '#111111'],
64
72
  // B5: OpenAI-compatible presets (dark bg shade, light bg shade; all >= 4.5:1 on their background)
65
73
  openrouter: ['#6566F1', '#4f46e5'], huggingface: ['#FFD21E', '#8a6500'], groq: ['#F55036', '#c2290f'],
66
74
  together: ['#0F6FFF', '#0b57d0'], lmstudio: ['#a78bfa', '#6d28d9'], vllm: ['#30A2FF', '#0b6bb8'],
67
- openai_compatible: ['#9ca3af', '#4b5563']
75
+ openai_compatible: ['#c7d2fe', '#3b4f87']
68
76
  };
69
77
  /** Protocol trace marks (AEE, AOCL, VOLT, WARD), each its own Q shade (dark, light). */
70
78
  export const PROTO_COLORS = {
@@ -187,21 +195,21 @@ export const bulletRole = (depth) => ['bullet1', 'bullet2', 'bullet3'][((depth %
187
195
 
188
196
  // ---- paint: split a plain line into coloured runs; the characters never change ---------------------
189
197
  const GLYPH_ROLE = {
190
- '●': 'ok', '✔': 'ok', '◐': 'warn', '○': 'muted', '?': 'muted', '✖': 'error', '⚠': 'warn', '⊘': 'warn',
191
- '◉': 'icon', 'Ⓠ': 'icon', '▸': 'key', '⛔': 'warn', '↳': 'icon', '»': 'muted', '·': 'muted', '⬢': 'icon', '⬡': 'muted'
198
+ '●': 'ok', '✔': 'ok', '◐': 'warn', '○': 'quiet', '?': 'quiet', '✖': 'error', '⚠': 'warn', '⊘': 'warn',
199
+ '◉': 'icon', 'Ⓠ': 'icon', '▸': 'brand', '⛔': 'warn', '↳': 'icon', '»': 'quiet', '·': 'quiet', '⬢': 'icon', '⬡': 'quiet', '▶': 'ask'
192
200
  };
193
201
  const KEYS = '(?:(?:Ctrl|Shift|Alt|Cmd|Option)\\+\\S+|Enter|Tab|Esc|Up|Down|Left|Right|Space|Backspace|PgUp|PgDn)';
194
202
  const TOKEN = new RegExp([
195
203
  '(https?:\\/\\/[^\\s)]+)', // 1 link
196
204
  '(\\bquox(?: (?![·])[^\\s·,;)]+)+)', // 2 a quox command
197
- '((?<![\\w/])\\/[a-z][\\w-]*)', // 3 slash command
205
+ '((?<![\\w/])\\/[a-z][\\w-]*(?: (?:--?[a-z][\\w-]*|<[^>\\s]+>|\\[[^\\]\\s]+\\]|\\d+|[\\w-]+(?:\\|[\\w-]+)+))*)', // 3 slash command + its flags/args
198
206
  `((?<![\\w+])${KEYS}(?![\\w+]))`, // 4 keyboard key
199
207
  '((?<![\\w<])<[^>\\s]+>|\\[[^\\]\\s]+\\])', // 5 placeholder <name> or [args]
200
208
  '( · )', // 6 separator
201
209
  '((?<![\\w/.-])(?:(?:gpt|claude|gemini|o[134]|mistral|codestral|deepseek|sonar|grok)-[\\w.:-]*\\w|ollama:[\\w.:-]*\\w|OpenAI|openai|Anthropic|anthropic|Claude(?: API)?|Gemini|Ollama|ollama|Mistral|DeepSeek|Perplexity|Grok|xAI)(?![\\w-]))', // 7 provider/model
202
210
  '((?<![\\w.#-])\\d+(?:[.,:-]\\d+)*(?:%|ms|s|k|m|h|d|x|KB|MB|GB)?(?![\\w]))' // 8 number
203
211
  ].join('|'), 'g');
204
- const ROLE_OF = [null, 'link', 'code', 'key', 'key', 'muted', 'muted', 'provider', 'number'];
212
+ const ROLE_OF = [null, 'id', 'command', 'command', 'command', 'command', 'quiet', 'provider', 'id'];
205
213
  const LETTER_KEY = /^([a-z])(?= (?:approve|deny|open|stop|peek|confirm|edit|retry|cancel|copy|quit)\b)/;
206
214
 
207
215
  /** Runs of `text` with roles; `base` colours the prose between tokens. Pure. */
@@ -211,6 +219,16 @@ export function paintRuns(text, base) {
211
219
  const s = String(text ?? '');
212
220
  let i = 0;
213
221
  const startsSeg = (at) => at === 0 || s.slice(0, at).endsWith(' · ') || /^\s*$/.test(s.slice(0, at));
222
+ if (/[⟦⟪]/.test(s)) { // explicit markup: `command` and «value» are atomic spans, the rest is auto-tokenised
223
+ let j = 0;
224
+ for (const m of s.matchAll(MARK)) {
225
+ for (const r of paintRuns(s.slice(j, m.index), base)) push(r.text, r.role);
226
+ push(m[1] ?? m[2], m[1] !== undefined ? 'command' : 'id');
227
+ j = m.index + m[0].length;
228
+ }
229
+ for (const r of paintRuns(s.slice(j), base)) push(r.text, r.role);
230
+ return out;
231
+ }
214
232
  for (const m of s.matchAll(TOKEN)) {
215
233
  proseRuns(s.slice(i, m.index), base, push, startsSeg(i));
216
234
  const g = m.findIndex((x, k) => k > 0 && x !== undefined);
@@ -222,32 +240,47 @@ export function paintRuns(text, base) {
222
240
  }
223
241
  function proseRuns(seg, base, push, segStart) {
224
242
  const k = segStart && seg.match(LETTER_KEY);
225
- if (k) { push(k[1], 'key'); seg = seg.slice(1); }
243
+ if (k) { push(k[1], 'command'); seg = seg.slice(1); }
226
244
  push(seg, base);
227
245
  }
228
246
 
229
247
  /**
230
248
  * A note/list line as runs: a leading state glyph, then `Label value` (two spaces) or `label: value`,
231
- * then tokens. tone: undefined/'dim' (neutral), 'ok', 'warn', 'accent', 'error'.
249
+ * then tokens. tone: undefined/'dim' (neutral), 'ok', 'warn', 'accent', 'error', 'ask' (a question for the human).
232
250
  */
233
251
  export function paintLine(text, tone) {
234
252
  const s = String(text ?? '');
235
253
  const out = [];
236
- const neutral = !tone || tone === 'dim';
237
- const prose = tone === 'warn' ? 'warn' : tone === 'error' || tone === 'fail' ? 'error' : tone === 'accent' ? 'sky' : null;
238
- const m = s.match(/^(\s*)(?:([●✔◐○?✖⚠⊘◉Ⓠ▸⛔↳»·⬢⬡])( ))?(.*)$/u);
254
+ // The tone colours the GLYPH only. The sentence stays prose, so commands and values inside keep their roles.
255
+ const TONE_GLYPH = { warn: 'warn', error: 'error', fail: 'error', ok: 'ok', accent: 'brand', ask: 'ask' };
256
+ // [\s\S]* not .*: a notice can carry a newline (a server error body), and a failed match crashed the TUI
257
+ // ("m is not iterable", 2026-10-15 03:44 while the collector was down).
258
+ const m = s.match(/^(\s*)(?:([●✔◐○?✖⚠⊘◉Ⓠ▸⛔↳»·⬢⬡▶])( ))?([\s\S]*)$/u) || [s, '', undefined, undefined, s];
239
259
  const [, lead, glyph, gsp, rest0] = m;
240
260
  if (lead) out.push({ text: lead, role: undefined });
241
- if (glyph) out.push({ text: glyph, role: tone === 'warn' && glyph !== '○' ? 'warn' : GLYPH_ROLE[glyph] }, { text: gsp, role: undefined });
261
+ if (glyph) out.push({ text: glyph, role: ['ok', 'warn', 'error'].includes(GLYPH_ROLE[glyph]) ? GLYPH_ROLE[glyph] : (TONE_GLYPH[tone] || GLYPH_ROLE[glyph]) }, { text: gsp, role: undefined });
242
262
  let rest = rest0;
263
+ if (tone === 'ask') { // QUOXON-ASK: "▶ reason. question? keys": the keys after the last "? " are the ask colour
264
+ const q = rest.lastIndexOf('? ');
265
+ if (q >= 0) { out.push(...paintRuns(rest.slice(0, q + 2), 'prose'), ...paintRuns(rest.slice(q + 2), 'ask')); return out; }
266
+ out.push(...paintRuns(rest, 'prose')); return out;
267
+ }
243
268
  const lv = rest.match(/^(\S[^·]{0,30}?\S|\S)( {2,})(\S.*)$/) || rest.match(/^([A-Za-z][\w .-]{0,22}:)( )(\S.*)$/);
244
- let valueBase = prose || (tone === 'ok' && !glyph ? 'ok' : neutral && !lv && !glyph ? 'muted' : 'text');
269
+ const valueBase = 'prose';
245
270
  if (lv && !/^https?:/.test(lv[1])) {
246
271
  const pv = providerOf(lv[1].replace(/\s*\(.*\)$/, ''), null);
247
272
  out.push({ text: lv[1], role: pv ? `provider:${pv}` : 'label' }, { text: lv[2], role: undefined });
248
273
  rest = lv[3];
249
- if (!prose) valueBase = 'value';
250
274
  }
251
275
  out.push(...paintRuns(rest, valueBase));
252
276
  return out;
253
277
  }
278
+
279
+ /** The glyph a bare notice line gets so its tone shows without tinting the sentence. '' for neutral tones. */
280
+ export const TONE_PREFIX = { warn: '⚠ ', error: '✖ ', fail: '✖ ', ok: '✔ ' };
281
+ export const toneText = (text, tone) => {
282
+ const p = TONE_PREFIX[tone];
283
+ const t = String(text ?? '');
284
+ return p && !/^\s*[●✔◐○?✖⚠⊘◉Ⓠ▸⛔↳»·⬢⬡▶] /u.test(t) ? p + t : t;
285
+ };
286
+ export { stripMarks };