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,121 @@
1
+ /**
2
+ * QUOXON-ASK (owner 2026-10-15): one shape for everything that needs the human's answer, so it is learnt once.
3
+ *
4
+ * ▶ <reason> <question> <keys>
5
+ *
6
+ * A fixed glyph first, the reason (why it is asking) next, the question, and the answer keys LAST. Rendered in the
7
+ * 'ask' tone (bold, the ask colour on the glyph and keys; never dim). Approval cards end in the same line.
8
+ * Every y/N the TUI says goes through askText; a test fails on a raw "y/N" anywhere else.
9
+ */
10
+ export const ASK_GLYPH = '▶';
11
+ export const ASK_KEYS = 'y/N/c'; // y: do it · n or Enter: don't · c or any other text: chat about it first (owner 2026-10-15)
12
+
13
+ // The structured ask behind each line askText made, so the renderer can draw the card (who, action, why, keys)
14
+ // from the text a module said. Bounded; an unknown line is parsed back instead.
15
+ const MADE = new Map();
16
+ const MADE_MAX = 200;
17
+
18
+ /**
19
+ * The ask line's text (the transcript form; the TUI draws it as a card). reason is said first (a trailing "." is
20
+ * added when it has no end stop). who: who is asking (QUOX, an agent, a slot sender; default this terminal).
21
+ * action: exactly what a yes does, shown in full on the card.
22
+ */
23
+ export function askText(question, { keys = ASK_KEYS, reason = '', who = '', action = '', title = '', plain = false } = {}) {
24
+ const r = String(reason || '').trim();
25
+ const q = String(question || '').trim();
26
+ const lead = r ? `${/[.!?:)]$/.test(r) ? r : `${r}.`} ` : '';
27
+ const text = `${ASK_GLYPH} ${lead}${q}${keys ? ` ${keys}` : ''}`;
28
+ MADE.set(text, { question: q, reason: r, keys: keys || '', who: String(who || '').trim(), action: String(action || '').trim(), title: String(title || '').trim(), plain: Boolean(plain) });
29
+ if (MADE.size > MADE_MAX) MADE.delete(MADE.keys().next().value);
30
+ return text;
31
+ }
32
+
33
+ /** An ask line -> { question, reason, keys, who, action } (what askText was given; parsed back when unknown). */
34
+ export function parseAsk(text) {
35
+ const t = String(text ?? '');
36
+ if (MADE.has(t)) return { ...MADE.get(t) };
37
+ const question = askQuestion(t);
38
+ const body = t.replace(new RegExp(`^\\s*${ASK_GLYPH}\\s*`), '');
39
+ const qi = body.lastIndexOf(question);
40
+ const reason = qi > 0 ? body.slice(0, qi).trim() : '';
41
+ const keys = qi >= 0 ? body.slice(qi + question.length).trim() : '';
42
+ return { question, reason, keys, who: '', action: '' };
43
+ }
44
+
45
+ /** The card footer's keys, spelled out the way approvals show theirs. */
46
+ export function keysLabel(keys) {
47
+ const k = String(keys || '').trim();
48
+ if (k === 'y/N/c') return 'y yes · n no · c chat';
49
+ if (k === 'y/N' || k === 'y/n') return 'y yes · n no';
50
+ if (/^\(?Y\/n\)?$/.test(k)) return 'y yes · n no (Enter is yes)';
51
+ return k;
52
+ }
53
+
54
+ /** The question part of an ask line ("▶ reason. Question? keys" -> "Question?"), for "Tell me more about: ...". */
55
+ export function askQuestion(line) {
56
+ const s = String(line || '').replace(new RegExp(`^\\s*${ASK_GLYPH}\\s*`), '');
57
+ const end = s.lastIndexOf('?');
58
+ if (end < 0) return s.trim();
59
+ const head = s.slice(0, end + 1);
60
+ let start = 0;
61
+ for (const m of head.matchAll(/[.;!)] (?=[A-Z])/g)) start = m.index + 2; // the last sentence before the "?"
62
+ return head.slice(start).trim();
63
+ }
64
+
65
+ /** y/N/c: what an answer line means. 'yes' | 'no' (n, no, empty) | 'command' (a slash line) | 'chat' (c, or any other text). */
66
+ export function askAnswer(text) {
67
+ const t = String(text ?? '').trim();
68
+ if (/^y(es)?$/i.test(t)) return 'yes';
69
+ if (!t || /^no?$/i.test(t)) return 'no';
70
+ if (t.startsWith('/')) return 'command';
71
+ return 'chat';
72
+ }
73
+
74
+ /** The chat turn an ask's c/free text sends. Bare c (or "chat"): "Tell me more about: <question>, before I decide". */
75
+ export function askChatMessage(text, question) {
76
+ const t = String(text ?? '').trim();
77
+ if (/^c(hat)?$/i.test(t)) return `Tell me more about: ${question.replace(/\?$/, '')}, before I decide`;
78
+ return `${t}\n\n(The terminal is waiting for my y/N answer to: "${question}". I have not answered it yet.)`;
79
+ }
80
+
81
+ // ASK-SETTLE (owner 2026-10-17: "questions should go once answered, or change to answer"): an open ask is a live
82
+ // card; once answered, expired or no longer open it leaves the screen and ONE line stays in the scrollback.
83
+ /** HH:MM of a timestamp, in this terminal's local time. */
84
+ export function askClock(at = Date.now()) {
85
+ const d = new Date(at);
86
+ return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
87
+ }
88
+
89
+ /** The one line an ask leaves behind. outcome.kind: yes | no | run | skipped (outcome.cmd) | expired | closed. */
90
+ export function settledAskLine(question, outcome, at = Date.now()) {
91
+ const q = String(question || '').trim();
92
+ const t = askClock(at);
93
+ switch (outcome?.kind) {
94
+ case 'yes': return `✔ ${q} yes (${t})`;
95
+ case 'run': return `✔ ${q} run (${t})`;
96
+ case 'no': return `✔ ${q} no (${t})`;
97
+ case 'skipped': return `✔ ${q} skipped, you ran ${String(outcome.cmd || 'a command').split(/\s/)[0]} (${t})`;
98
+ case 'dismissed': return `↷ ${q} offer dismissed (${t})`;
99
+ case 'expired': return `⌛ ${q} expired, not answered (${t})`;
100
+ default: return `↷ ${q} no longer open (${t})`;
101
+ }
102
+ }
103
+ /** The note tone a settled line is shown in (never dim: no grey text). */
104
+ export const settledAskTone = (kind) => (kind === 'yes' || kind === 'run' ? 'ok' : 'plain');
105
+
106
+ /** What a submitted line does to an open ask: { kind } for an answer, or null (c / free text keeps it open). */
107
+ export function askOutcomeOf(text, { runWord = false } = {}) {
108
+ const t = String(text ?? '').trim();
109
+ if (runWord && /^run$/i.test(t)) return { kind: 'run' };
110
+ const a = askAnswer(t);
111
+ if (a === 'yes') return { kind: 'yes' };
112
+ if (a === 'no') return { kind: 'no' };
113
+ if (a === 'command') return { kind: 'skipped', cmd: t };
114
+ return null;
115
+ }
116
+
117
+ /** A chat line that asks about the open offer (so the card stays): a question, "c", or a why/what/how/explain opener. Anything else is the user moving on, which dismisses the offer. */
118
+ export function isAskClarification(text) {
119
+ const t = String(text ?? '').trim();
120
+ return /^c(hat)?$/i.test(t) || /\?\s*$/.test(t) || /^(why|what|how|which|who|when|where|explain|tell me|can you|could you|does|is it|is this)\b/i.test(t);
121
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * E5 local state: when this profile was last seen in the client (~/.config/quox/away.json, keyed by
3
+ * profile). Only a timestamp is kept; the digest itself always comes from the server. Writes are
4
+ * best-effort: a read-only home must never break the client.
5
+ */
6
+ import { readFileSync, writeFileSync, mkdirSync, renameSync } from 'node:fs';
7
+ import { homedir } from 'node:os';
8
+ import { join, dirname } from 'node:path';
9
+
10
+ export const AWAY_AFTER_MS = 2 * 3600000;
11
+ export const awayStatePath = () => join(homedir(), '.config', 'quox', 'away.json');
12
+
13
+ function readAll(path) {
14
+ try { const j = JSON.parse(readFileSync(path, 'utf8')); return j && typeof j === 'object' && !Array.isArray(j) ? j : {}; } catch { return {}; }
15
+ }
16
+
17
+ /** -> epoch ms or null (never seen, or unreadable). */
18
+ export function readLastSeen(profile, path = awayStatePath()) {
19
+ const v = readAll(path)[profile || 'default'];
20
+ const t = typeof v === 'number' ? v : Date.parse(v);
21
+ return Number.isFinite(t) ? t : null;
22
+ }
23
+
24
+ export function writeLastSeen(profile, ts = Date.now(), path = awayStatePath()) {
25
+ try {
26
+ const all = readAll(path);
27
+ all[profile || 'default'] = new Date(ts).toISOString();
28
+ mkdirSync(dirname(path), { recursive: true });
29
+ const tmp = `${path}.${process.pid}.tmp`;
30
+ writeFileSync(tmp, JSON.stringify(all) + '\n');
31
+ renameSync(tmp, path);
32
+ return true;
33
+ } catch { return false; }
34
+ }
35
+
36
+ /** Show the launch card only when the last session ended more than 2 h ago (never on a first launch). */
37
+ export const awayDue = (lastSeen, now = Date.now()) => lastSeen != null && now - lastSeen > AWAY_AFTER_MS;
38
+
39
+ /**
40
+ * /away [since]: a window (90m, 3h, 2d), an ISO time, or nothing (last seen, else 24 h).
41
+ * -> { ok: true, since: iso } | { ok: false, error }
42
+ */
43
+ export function parseSince(arg, { now = Date.now(), lastSeen = null } = {}) {
44
+ const a = String(arg || '').trim();
45
+ if (!a) return { ok: true, since: new Date(lastSeen ?? now - 24 * 3600000).toISOString() };
46
+ const m = a.match(/^(\d+)\s*([mhd])$/i);
47
+ if (m) return { ok: true, since: new Date(now - Number(m[1]) * { m: 60000, h: 3600000, d: 86400000 }[m[2].toLowerCase()]).toISOString() };
48
+ const t = /^\d{4}-\d\d-\d\d/.test(a) ? Date.parse(a) : NaN;
49
+ if (!Number.isFinite(t)) return { ok: false, error: `Usage: /away [since] (e.g. /away 3h, /away 2d, /away 2026-10-13T08:00:00Z)` };
50
+ if (t > now) return { ok: false, error: 'That time is in the future.' };
51
+ return { ok: true, since: new Date(t).toISOString() };
52
+ }
@@ -0,0 +1,224 @@
1
+ /**
2
+ * QUOXON-FW8: "!" commands. A line that starts with `!` runs on THIS machine, in the session cwd, and its output
3
+ * is shown inline. It never reaches the model (no turn, no server call) and needs no approval card: it is the
4
+ * user's own action on their own machine. Pure parsing plus one process runner; app.js owns the display.
5
+ *
6
+ * !<shell command> run via $SHELL -lc
7
+ * !quox <args> / !q <args> run THIS quox binary (not whatever is on PATH) with the current profile injected
8
+ * !! reserved (refused with a note)
9
+ *
10
+ * BANG-TTY (owner 2026-10-16): capture stays the default (so /share works), but a known interactive command
11
+ * (needsTerminal, e.g. quox lucid deck, vim, top) or a captured run that failed for want of a terminal (ttyFailure)
12
+ * is handed the terminal, the same way a slot run request is.
13
+ */
14
+ import { spawn } from 'node:child_process';
15
+ import { fileURLToPath } from 'node:url';
16
+ import { dirname, join } from 'node:path';
17
+
18
+ export const MAX_LINES = 2000;
19
+ export const MAX_BYTES = 256 * 1024;
20
+ export const SHARE_MAX_BYTES = 16 * 1024;
21
+ export const DEFAULT_TIMEOUT_MS = 120000;
22
+ export const KILL_GRACE_MS = 2000;
23
+ export const QUOX_ENTRY = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'bin', 'quox.js');
24
+
25
+ /** -> null (not a bang line) | { kind: 'reserved' | 'empty' | 'shell' | 'quox', cmd?, args? } */
26
+ export function parseBang(line) {
27
+ const t = String(line ?? '').replace(/^\s+/, '');
28
+ if (!t.startsWith('!')) return null;
29
+ if (t.startsWith('!!')) return { kind: 'reserved' };
30
+ const rest = t.slice(1).trim();
31
+ if (!rest) return { kind: 'empty' };
32
+ const m = /^(quox|q)(?:\s+([\s\S]*))?$/.exec(rest);
33
+ if (m) return { kind: 'quox', args: tokenize(m[2] || ''), cmd: rest };
34
+ return { kind: 'shell', cmd: rest };
35
+ }
36
+
37
+ /** Minimal shell-ish word split: whitespace, 'single' and "double" quotes, backslash escapes outside single quotes. */
38
+ export function tokenize(s) {
39
+ const out = []; let cur = ''; let has = false; let q = null;
40
+ for (let i = 0; i < s.length; i++) {
41
+ const ch = s[i];
42
+ if (q === "'") { if (ch === "'") q = null; else cur += ch; continue; }
43
+ if (q === '"') {
44
+ if (ch === '"') q = null;
45
+ else if (ch === '\\' && i + 1 < s.length && /["\\$`]/.test(s[i + 1])) cur += s[++i];
46
+ else cur += ch;
47
+ continue;
48
+ }
49
+ if (ch === "'" || ch === '"') { q = ch; has = true; continue; }
50
+ if (ch === '\\' && i + 1 < s.length) { cur += s[++i]; has = true; continue; }
51
+ if (/\s/.test(ch)) { if (cur || has) out.push(cur); cur = ''; has = false; continue; }
52
+ cur += ch; has = true;
53
+ }
54
+ if (cur || has) out.push(cur);
55
+ return out;
56
+ }
57
+
58
+ const hasProfile = (args) => args.some(a => a === '-p' || a === '--profile' || a.startsWith('--profile=') || /^-p.+/.test(a));
59
+ const INTERACTIVE = new Set(['tui', 'quoxon']);
60
+ // quox leaves that draw a full-screen view (first two positional words); `--interactive` anywhere also counts
61
+ const INTERACTIVE_QUOX = new Set(['lucid deck']);
62
+ const INTERACTIVE_FLAG = (a) => a === '--interactive'; // not -i: some leaves use it for other things
63
+ // shell programs that need a terminal whatever their arguments; ssh only when it has no remote command
64
+ const INTERACTIVE_SHELL = new Set(['vim', 'vi', 'nvim', 'nano', 'emacs', 'less', 'more', 'most', 'man', 'top', 'htop', 'btop', 'atop', 'watch', 'tmux', 'screen', 'mc', 'ranger']);
65
+ const SSH_VALUE_FLAGS = new Set(['-b', '-c', '-D', '-E', '-e', '-F', '-I', '-i', '-J', '-L', '-l', '-m', '-O', '-o', '-p', '-Q', '-R', '-S', '-W', '-w', '-B']);
66
+
67
+ const quoxPositional = (args) => {
68
+ const pos = [];
69
+ for (let i = 0; i < args.length; i++) {
70
+ const a = args[i];
71
+ if (a === '-p' || a === '--profile') { i++; continue; }
72
+ if (a.startsWith('-')) continue;
73
+ pos.push(a);
74
+ }
75
+ return pos;
76
+ };
77
+
78
+ /** -> a short reason when this ! command needs a real terminal (hand it over), else null. */
79
+ export function needsTerminal(bang) {
80
+ if (!bang || (bang.kind !== 'shell' && bang.kind !== 'quox')) return null;
81
+ if (bang.kind === 'quox') {
82
+ const args = bang.args || [];
83
+ const pos = quoxPositional(args);
84
+ const info = args.some(a => a === '--help' || a === '-h' || a === '--version' || a === '-V');
85
+ if (!pos.length || info || INTERACTIVE.has(pos[0])) return null; // bare quox and tui stay refused (quoxArgv): this window IS that app
86
+ if (INTERACTIVE_QUOX.has(pos.slice(0, 2).join(' '))) return `quox ${pos.slice(0, 2).join(' ')} is interactive`;
87
+ if (args.some(INTERACTIVE_FLAG)) return 'it asked for --interactive';
88
+ return null;
89
+ }
90
+ const words = tokenize(bang.cmd);
91
+ let i = 0;
92
+ while (i < words.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(words[i])) i++; // FOO=1 less x
93
+ if (/[|;&<>]/.test(bang.cmd)) return null; // a pipeline or redirect is the user's own plumbing: leave it captured
94
+ const prog = (words[i] || '').split('/').pop();
95
+ if (INTERACTIVE_SHELL.has(prog)) return `${prog} is interactive`;
96
+ if (prog === 'ssh') {
97
+ const rest = words.slice(i + 1); let host = -1;
98
+ for (let k = 0; k < rest.length; k++) {
99
+ if (SSH_VALUE_FLAGS.has(rest[k])) { k++; continue; }
100
+ if (rest[k].startsWith('-')) continue;
101
+ host = k; break;
102
+ }
103
+ if (host >= 0 && host === rest.length - 1) return 'ssh with no command opens a shell';
104
+ }
105
+ return null;
106
+ }
107
+
108
+ const TTY_FAIL = /needs an interactive terminal|not a tty|inappropriate ioctl|must be run (from|in) a terminal|(input|stdin|stdout) is not a terminal|requires a tty|no tty present/i;
109
+ /** -> true when a captured run failed because it had no terminal (re-run it handed over). */
110
+ export function ttyFailure(r) {
111
+ if (!r || r.code === 0 || r.code == null || r.timedOut || r.killed) return false;
112
+ return TTY_FAIL.test(String(r.text || '').slice(-2000));
113
+ }
114
+
115
+ /** -> { refuse: 'one-line hint' } | { argv: [...] } for process.execPath. */
116
+ export function quoxArgv(args, { profile, entry = QUOX_ENTRY } = {}) {
117
+ // positional words: skip flags and the value that follows -p/--profile
118
+ const pos = quoxPositional(args);
119
+ const info = args.some(a => a === '--help' || a === '-h' || a === '--version' || a === '-V');
120
+ const inject = profile && !hasProfile(args) ? ['-p', profile] : [];
121
+ const argv = [entry, ...inject, ...args];
122
+ // refuse = it cannot run captured inline; tty = the argv to hand the terminal to instead (BANG-TTY)
123
+ if (!pos.length && !info) return { refuse: 'bare quox opens the full-screen app, which needs the terminal. Give it a subcommand, e.g. !quox inbox list (or !quox --help).' };
124
+ if (INTERACTIVE.has(pos[0])) return { refuse: `quox ${pos[0]} is interactive and needs the terminal, so it cannot run inline. Open it in another terminal.` };
125
+ if (needsTerminal({ kind: 'quox', args })) return { refuse: `quox ${pos.slice(0, 2).join(' ')} is interactive and needs the terminal, so it cannot run inline.`, tty: { argv } };
126
+ return { argv };
127
+ }
128
+
129
+ const STDIN_HINT = /stdin|end of file|\bEOF\b|password|passphrase|\[y\/n\]|\(y\/n\)|continue\?|press (any )?key|enter .*:\s*$/i;
130
+
131
+ /**
132
+ * Run one command. -> { kill(), done: Promise<result> }.
133
+ * result: { code, signal, ms, timedOut, killed, lines, truncated, text, stdinNote }
134
+ * onData(text) is called with accepted output only (capped at MAX_LINES / MAX_BYTES); `text` is the same capped capture.
135
+ */
136
+ export function runBang(plan, { cwd = process.cwd(), env = process.env, timeoutMs, onData = () => {}, graceMs = KILL_GRACE_MS, now = Date.now, shell } = {}) {
137
+ const tmo = Number(timeoutMs ?? env.QUOX_BANG_TIMEOUT_MS ?? DEFAULT_TIMEOUT_MS) || DEFAULT_TIMEOUT_MS;
138
+ const t0 = now();
139
+ let file, args;
140
+ if (plan.argv) { file = process.execPath; args = plan.argv; }
141
+ else { file = shell || env.SHELL || '/bin/sh'; args = ['-lc', plan.cmd]; }
142
+ const st = { timedOut: false, killed: false, lines: 0, acc: 0, bytes: 0, truncated: false, text: '', partial: false };
143
+ let child, timer, killTimer;
144
+ const done = new Promise((resolve) => {
145
+ const finish = (code, signal) => {
146
+ clearTimeout(timer); clearTimeout(killTimer);
147
+ const res = { code, signal, ms: now() - t0, timedOut: st.timedOut, killed: st.killed && !st.timedOut, lines: st.lines, truncated: st.truncated, text: st.text };
148
+ if (code !== 0 && !st.timedOut && !st.killed && (!st.text.trim() || STDIN_HINT.test(st.text.slice(-400)))) res.stdinNote = 'This command got end-of-file on stdin (input is not available here), so it may have been waiting for input.';
149
+ resolve(res);
150
+ };
151
+ try {
152
+ child = spawn(file, args, { cwd, env, detached: true, stdio: ['pipe', 'pipe', 'pipe'] });
153
+ } catch (e) { st.text = String(e.message); return finish(127, null); }
154
+ child.on('error', (e) => { st.text += `${e.message}\n`; onData(`${e.message}\n`); finish(127, null); });
155
+ child.stdin.on('error', () => {});
156
+ child.stdin.end(); // EOF immediately: nothing here can answer a prompt
157
+ const take = (buf) => {
158
+ let s = buf.toString('utf8');
159
+ if (!s) return;
160
+ for (const ch of s) if (ch === '\n') st.lines++;
161
+ if (st.truncated) return;
162
+ if (st.acc >= MAX_LINES) { st.truncated = true; return; }
163
+ const room = MAX_BYTES - st.bytes;
164
+ if (Buffer.byteLength(s) > room) { s = Buffer.from(s).subarray(0, Math.max(0, room)).toString('utf8'); st.truncated = true; }
165
+ let nl = 0;
166
+ for (let i = 0; i < s.length; i++) {
167
+ if (s[i] === '\n' && st.acc + (++nl) >= MAX_LINES) { if (i + 1 < s.length) st.truncated = true; s = s.slice(0, i + 1); break; }
168
+ }
169
+ st.acc += (s.match(/\n/g) || []).length; st.bytes += Buffer.byteLength(s); st.text += s;
170
+ if (s) onData(s);
171
+ };
172
+ const onOut = (b) => take(b);
173
+ child.stdout.on('data', onOut); child.stderr.on('data', onOut);
174
+ child.on('close', (code, signal) => finish(code, signal));
175
+ timer = setTimeout(() => { st.timedOut = true; kill(); }, tmo);
176
+ });
177
+ function kill() {
178
+ if (!child || child.exitCode !== null || child.signalCode) return;
179
+ st.killed = true;
180
+ const sig = (s) => { try { process.kill(-child.pid, s); } catch { try { child.kill(s); } catch { /* gone */ } } };
181
+ sig('SIGTERM');
182
+ killTimer = setTimeout(() => sig('SIGKILL'), graceMs);
183
+ }
184
+ return { kill, done };
185
+ }
186
+
187
+ /** The "exit N · 1.2s" footer, plus the plain-words reasons. */
188
+ export function footer(r, { timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
189
+ const secs = `${(r.ms / 1000).toFixed(1)}s`;
190
+ if (r.timedOut) return `timed out after ${Math.round(timeoutMs / 1000)}s and was stopped · ${secs} (QUOX_BANG_TIMEOUT_MS changes the limit)`;
191
+ if (r.killed) return `stopped (Ctrl+C) · ${secs}`;
192
+ if (r.signal) return `killed by ${r.signal} · ${secs}`;
193
+ return `exit ${r.code} · ${secs}`;
194
+ }
195
+
196
+ export const truncNote = (r) => `(output truncated, ${r.lines} lines)`;
197
+
198
+ /** Text to attach for /share: capped at SHARE_MAX_BYTES. -> { text, truncated } */
199
+ export function shareText(text, max = SHARE_MAX_BYTES) {
200
+ const b = Buffer.from(String(text ?? ''), 'utf8');
201
+ if (b.length <= max) return { text: String(text ?? ''), truncated: false };
202
+ return { text: b.subarray(0, max).toString('utf8'), truncated: true };
203
+ }
204
+
205
+ /** The message sent to the model: the attached output, clearly framed, then the user's words. */
206
+ export function withShared(message, shared) {
207
+ if (!shared?.length) return message;
208
+ const blocks = shared.map(s => `[output of \`${s.cmd}\`${s.truncated ? ' (truncated)' : ''}]\n${s.text}`).join('\n\n');
209
+ return `${message}\n\n${blocks}`;
210
+ }
211
+
212
+ /**
213
+ * Make command output safe for Ink: keep SGR colour codes, drop every other escape sequence (cursor moves, OSC titles,
214
+ * clears), resolve carriage-return rewrites (progress bars) to their last state, expand tabs, drop other control bytes.
215
+ */
216
+ export function cleanOutput(s) {
217
+ let t = String(s ?? '');
218
+ t = t.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?/g, ''); // OSC
219
+ t = t.replace(/\x1b\[([0-9;:]*)([\x20-\x2f]*)([\x40-\x7e])/g, (m, p, i, f) => (f === 'm' ? m : '')); // CSI: keep only colour
220
+ t = t.replace(/\x1b[PX^_][^\x1b]*(?:\x1b\\)?/g, '').replace(/\x1b[@-Z\\-_]/g, '');
221
+ t = t.replace(/\r\n/g, '\n').split('\n').map(l => { const i = l.lastIndexOf('\r'); return i >= 0 && i < l.length - 1 ? l.slice(i + 1) : l.replace(/\r/g, ''); }).join('\n');
222
+ t = t.replace(/\t/g, ' ');
223
+ return t.replace(/[\x00-\x08\x0b-\x1a\x1c-\x1f\x7f]/g, '');
224
+ }
@@ -0,0 +1,17 @@
1
+ // APPROVALS-AUDIT finding 1: a bulk yes (/yestoall, /approve all, /answer 1-3 y) never covers a risky ask.
2
+ // A held decision is walked one card at a time, with its full text and its own explicit yes.
3
+ // Mirror of services/auth/lib/bulkGuard.js (the server refuses the same set); a test pins the two together.
4
+ const LOOSEN = /loosen|\.test\.[a-z]+|\.spec\.[a-z]+|\/tests?\/|__tests__|test[- ]guard|\bguard\b|skip(ping)? (a |the )?test|re-?baseline/i;
5
+ const PRIV = /restart|daemon|privileged|elevated|sudo|\bgrant\b.*\btools?\b/i;
6
+
7
+ export function heldReason(it) {
8
+ const p = it?.payload || {};
9
+ if (p.high_risk === true) return 'high risk';
10
+ if (p.privileged === true) return 'privileged';
11
+ if (p.category === 'loosening' || p.category === 'test_change') return p.category;
12
+ const text = [it?.title, p.why, p.category, ...(Array.isArray(p.paths) ? p.paths : []), p.approval_context?.reason].filter(Boolean).join(' ');
13
+ if (LOOSEN.test(text)) return 'changes a test or guard';
14
+ if (PRIV.test(String(it?.title || ''))) return 'privileged or restart';
15
+ return null;
16
+ }
17
+ export const isHeldFromBulk = (it) => heldReason(it) !== null;
@@ -7,6 +7,8 @@
7
7
  * `res` is { ok:true, capabilities } or { ok:false, status?, missing?, error? }.
8
8
  */
9
9
 
10
+ import { capabilityName, statusUnreadable } from './providers.js';
11
+
10
12
  const norm = (s) => String(s || '').trim().toLowerCase();
11
13
 
12
14
  /** -> the capability matching an id, a provider short name, or a display name; else null. */
@@ -27,7 +29,7 @@ export function findCapability(query, res) {
27
29
  export function offerFor(capabilityId, res) {
28
30
  const c = findCapability(capabilityId, res);
29
31
  if (!c) return null;
30
- const name = c.name || c.id;
32
+ const name = capabilityName(c);
31
33
  switch (c.availability) {
32
34
  case 'installed': return null;
33
35
  case 'planned': return `Ⓠ ${name} is planned, not built yet.`;
@@ -53,22 +55,27 @@ export function capabilityLines(res) {
53
55
  const of = (a) => caps.filter(c => c.availability === a);
54
56
  const out = [['What this Quox can do here:']];
55
57
  const installed = of('installed');
56
- for (const c of installed) out.push([`● ${c.name} ${c.intent}`, 'ok']);
58
+ for (const c of installed) out.push([`● ${capabilityName(c)} ${c.intent}`, 'ok']);
57
59
  if (!installed.length) out.push([' nothing is ready yet']);
58
60
  const setup = of('requires_setup');
59
61
  if (setup.length) {
60
62
  out.push(['Needs setup:']);
61
- for (const c of setup) out.push([`◐ ${c.name} ${c.reason || 'needs setup'}${c.setup ? ` · ${c.setup}` : ''}`, 'warn']);
63
+ for (const c of setup) out.push([`◐ ${capabilityName(c)} ${c.reason || 'needs setup'}${c.setup ? ` · ${c.setup}` : ''}`, 'warn']);
64
+ }
65
+ const unk = caps.filter(statusUnreadable);
66
+ if (unk.length) {
67
+ out.push(['Unknown (status could not be read):']);
68
+ for (const c of unk) out.push([`? ${capabilityName(c)} ${c.reason}`, 'warn']);
62
69
  }
63
- const off = of('unavailable');
70
+ const off = of('unavailable').filter(c => !statusUnreadable(c));
64
71
  if (off.length) {
65
72
  out.push(['Not available here:']);
66
- for (const c of off) out.push([`○ ${c.name} ${c.reason || 'unavailable'}${c.setup ? ` · ${c.setup}` : ''}`]);
73
+ for (const c of off) out.push([`○ ${capabilityName(c)} ${c.reason || 'unavailable'}${c.setup ? ` · ${c.setup}` : ''}`]);
67
74
  }
68
75
  const planned = of('planned');
69
76
  if (planned.length) {
70
77
  out.push(['Planned (not built yet):']);
71
- for (const c of planned) out.push([`· ${c.name} ${c.intent}`]);
78
+ for (const c of planned) out.push([`· ${capabilityName(c)} ${c.intent}`]);
72
79
  }
73
80
  out.push([' /capabilities <name> for one, e.g. /capabilities workflows']);
74
81
  return out;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * TUI-FOOTER: terminal cell widths for the bottom chrome (divider, custom rows, status rows).
3
+ *
4
+ * string-width counts East Asian Ambiguous characters as one cell. Several of ours are Ambiguous: the ' · '
5
+ * separator, ● Ⓠ │ ─ ▁ █ ▲ ▼ ▶. A terminal set to draw them wide (a CJK locale, or the "ambiguous characters
6
+ * are wide" option in iTerm2, VTE or Windows Terminal) gives each two cells, so a row measured to fit overflows
7
+ * and the terminal wraps it mid-label. Policy, in order: QUOX_AMBIGUOUS=wide|narrow; else a ja/zh/ko locale
8
+ * (LC_ALL, LC_CTYPE, LANG) means wide; else narrow (what nearly every Western terminal does).
9
+ * Pure apart from the env default; no extra dependency (the ranges below are Unicode EastAsianWidth=A, U+00A0..U+2FFF,
10
+ * combining marks excluded since they take no cell).
11
+ */
12
+ import stringWidth from 'string-width';
13
+
14
+ const RANGES = ('a1 a4 a7-a8 aa ad-ae b0-b4 b6-ba bc-bf c6 d0 d7-d8 de-e1 e6 e8-ea ec-ed f0 f2-f3 f7-fa fc fe 101 111 113 11b ' +
15
+ '126-127 12b 131-133 138 13f-142 144 148-14b 14d 152-153 166-167 16b 1ce 1d0 1d2 1d4 1d6 1d8 1da 1dc 251 261 2c4 2c7 2c9-2cb ' +
16
+ '2cd 2d0 2d8-2db 2dd 2df 391-3a1 3a3-3a9 3b1-3c1 3c3-3c9 401 410-44f 451 2010 2013-2016 2018-2019 201c-201d 2020-2022 ' +
17
+ '2024-2027 2030 2032-2033 2035 203b 203e 2074 207f 2081-2084 20ac 2103 2105 2109 2113 2116 2121-2122 2126 212b 2153-2154 ' +
18
+ '215b-215e 2160-216b 2170-2179 2189 2190-2199 21b8-21b9 21d2 21d4 21e7 2200 2202-2203 2207-2208 220b 220f 2211 2215 221a ' +
19
+ '221d-2220 2223 2225 2227-222c 222e 2234-2237 223c-223d 2248 224c 2252 2260-2261 2264-2267 226a-226b 226e-226f 2282-2283 ' +
20
+ '2286-2287 2295 2299 22a5 22bf 2312 2460-24e9 24eb-254b 2550-2573 2580-258f 2592-2595 25a0-25a1 25a3-25a9 25b2-25b3 ' +
21
+ '25b6-25b7 25bc-25bd 25c0-25c1 25c6-25c8 25cb 25ce-25d1 25e2-25e5 25ef 2605-2606 2609 260e-260f 261c 261e 2640 2642 ' +
22
+ '2660-2661 2663-2665 2667-266a 266c-266d 266f 269e-269f 26bf 26c6-26cd 26cf-26d3 26d5-26e1 26e3 26e8-26e9 26eb-26f1 26f4 ' +
23
+ '26f6-26f9 26fb-26fc 26fe-26ff 273d 2776-277f 2b56-2b59')
24
+ .split(' ').map((r) => { const [a, b = a] = r.split('-'); return [parseInt(a, 16), parseInt(b, 16)]; });
25
+
26
+ /** Is this code point East Asian Ambiguous (drawn one or two cells depending on the terminal)? */
27
+ export function isAmbiguous(cp) {
28
+ let lo = 0, hi = RANGES.length - 1;
29
+ while (lo <= hi) {
30
+ const m = (lo + hi) >> 1;
31
+ if (cp < RANGES[m][0]) hi = m - 1; else if (cp > RANGES[m][1]) lo = m + 1; else return true;
32
+ }
33
+ return false;
34
+ }
35
+
36
+ /** The ambiguous-width policy for this terminal: true when Ambiguous characters take two cells. */
37
+ export function ambiguousWide(env = process.env) {
38
+ const o = String(env.QUOX_AMBIGUOUS || '').toLowerCase();
39
+ if (o === 'wide') return true;
40
+ if (o === 'narrow') return false;
41
+ return /^(ja|zh|ko)([_.@-]|$)/i.test(env.LC_ALL || env.LC_CTYPE || env.LANG || '');
42
+ }
43
+
44
+ const ANSI = /\x1b\[[0-9;]*m/g;
45
+ /** Cells `text` takes in a terminal with the given ambiguous-width policy (escapes take none). */
46
+ export function cellWidth(text, { wide = ambiguousWide() } = {}) {
47
+ const t = String(text ?? '');
48
+ let w = stringWidth(t);
49
+ if (!wide) return w;
50
+ for (const ch of t.replace(ANSI, '')) if (isAmbiguous(ch.codePointAt(0))) w += 1;
51
+ return w;
52
+ }
53
+
@@ -0,0 +1,124 @@
1
+ /**
2
+ * The palette's one-line description of each slash command: plain words, the job not the mechanism, at most 54
3
+ * characters so a row never clips at 79 columns. The long wording stays in the registry for /help.
4
+ * Rule: lowercase first word unless it is an acronym or a command name. A command with no entry here uses its
5
+ * registry desc, which a test requires to be short already.
6
+ */
7
+ export const SHORT_MAX = 54;
8
+ export const SHORT = {
9
+ walkthrough: 'a guided pass: what it does, why, my advice',
10
+ inbox: 'your Quox inbox: latest items, read, open',
11
+ approvals: 'the questions waiting for you, one at a time',
12
+ peek: 'look up an envelope, item, run or receipt by id',
13
+ aside: 'a read-only side question, kept out of the chat',
14
+ yestoall: 'approve everything waiting (same as /approve all)',
15
+ approve: 'approve item N, or everything one agent asked',
16
+ deny: 'deny item N (or all of one agent), with a reason',
17
+ answer: 'answer a question a session asked you',
18
+ authenticate: 'enter your PIN once, good for 10 minutes',
19
+ override: 'override one blocked action (PIN, witnessed)',
20
+ skills: 'list your org\'s governed skills, or show one',
21
+ skill: 'ask Quox to run a skill (approvals still apply)',
22
+ rules: 'which rules win: QUOX.md, dev pack, server policy',
23
+ clear: 'fresh conversation and a clear screen',
24
+ stop: 'stop the current turn, same as Esc',
25
+ devservers: 'your registered dev servers, and how to open one',
26
+ resume: 'pick a recent conversation and carry on',
27
+ compact: 'summarise the conversation, keeping decisions',
28
+ challenge: 'a second opinion on the last answer (one extra turn)',
29
+ spawn: 'ask HYPERION for a disposable VM (max 24h)',
30
+ consent: 'AI data sharing: see where you stand, or agree',
31
+ contact: 'add a contact, or list them (as quox contacts)',
32
+ help: 'this reference, or detail for one command',
33
+ note: 'save a note to NotesQ (/notes lists them)',
34
+ devnote: 'jot a note for the dev observer (also // text)',
35
+ share: 'attach your last ! command\'s output to your message',
36
+ copy: 'copy the last answer to the clipboard',
37
+ dragon: 'the Q comes alive; explain, ask, sleep',
38
+ 'dragon-fly': 'the dragon flies across the screen',
39
+ sound: 'earcons: Silent, Minimal, Operational or Full',
40
+ mouse: 'click to reply and answer cards (off by default)',
41
+ tip: 'a "did you know" tip about something Quox can do',
42
+ tips: 'search the tips, or list the topics',
43
+ 'tip-settings': 'tips: off, core (free features) or all',
44
+ whatsnew: 'new commands and release notes since you last looked',
45
+ welcome: 'show the welcome screen again',
46
+ about: 'who we are, terms, privacy, licence and docs',
47
+ quit: 'leave; finished work is kept, /resume picks it up',
48
+ restart: 'restart into the latest quox and resume',
49
+ update: 'update quox to the latest version, then restart',
50
+ demo: 'play the simulated demo; nothing real runs',
51
+ feedback: 'tell the Quox team something (you see it first)',
52
+ gripe: 'file a GitHub issue; /gripe! files at once',
53
+ sessions: 'everything live in your org, and where it runs',
54
+ active: 'who is doing what, one line per worker',
55
+ dashboard: 'open the web dashboard already signed in',
56
+ rename: 'name this session (alone shows the current name)',
57
+ slot: 'give this session a slot address',
58
+ say: 'message one or more slots directly',
59
+ join: 'join a slot as a group room',
60
+ leave: 'leave a slot room you joined',
61
+ rooms: 'rooms this window has joined, with unread counts',
62
+ broadcast: 'tell every held slot in this org at once',
63
+ page: 'urgent message to one slot, with acknowledgement',
64
+ ack: 'acknowledge a broadcast',
65
+ send: 'send a local text file into a slot (64 KB cap)',
66
+ attach: 'send your turns to a specific agent',
67
+ login: 'sign in again without leaving quox',
68
+ profile: 'switch this window to another signed-in profile',
69
+ connect: 'see what is connected; /connect openai adds a key',
70
+ hosts: 'add a host to your fleet; /hosts lists them',
71
+ capabilities: 'what this Quox can do here, and what needs setup',
72
+ verbose: 'protocol lines: off, on (compact) or full',
73
+ proof: 'the evidence chain for the last tool call',
74
+ prove: 'check the last turn\'s tool calls did what they said',
75
+ replay: 'rebuild an earlier run from the audit trail',
76
+ aocl: 'every AOCL layer of the last turn',
77
+ reconcile: 'check tool calls that ended with effect unknown',
78
+ roster: 'show or hide the live roster under the status line',
79
+ workflow: 'the step flow of a workflow\'s latest run',
80
+ crystal: 'could this workflow become a Crystal?',
81
+ undo: 'reverse the last thing Quox did, if it knows how',
82
+ wish: 'describe something Quox cannot do yet',
83
+ agents: 'pick an agent, or show one\'s card',
84
+ which: 'what this window is using: login, org, agent, model',
85
+ crystallize: 'turn what was just done into a reusable skill',
86
+ automate: 'turn this conversation or a task into a workflow',
87
+ watch: 'turn a site, repo, server or metric into an observer',
88
+ fleet: 'computers, agents, missions and SubFleets, with health',
89
+ memory: 'facts Quox remembers: source, age, trust, conflicts',
90
+ sitrep: 'the situation now: waiting, running, unseen',
91
+ away: 'what happened while you were away',
92
+ vm: 'VMs on Proxmox via HYPERION: list, start, stop',
93
+ hyperion: 'which VMs HYPERION has out, and who holds each',
94
+ observers: 'LUCID: the situation and what changed since',
95
+ mind: 'QuoxMind implants: status and last activity',
96
+ hat: 'list hats, switch hat, or make your own',
97
+ org: 'list your orgs, switch to one, or go back',
98
+ r: 'reply to whoever last messaged you (also /reply)',
99
+ poke: 'wake a session with an urgent page (also /wake)',
100
+ afk: 'tell sessions you are away; approvals still queue',
101
+ back: 'mark yourself present and show what you missed',
102
+ observe: 'post a note to the session watching your window',
103
+ huh: 'explain the last output in plain words',
104
+ sleep: 'say you are asleep; sessions stop waiting on you',
105
+ mode: 'ask first (default) or plan only',
106
+ slots: 'held slots: who holds them and where',
107
+ todo: 'add an Action Tracker item; /todo alone lists',
108
+ done: 'mark Action Tracker items done: /done td1,td2',
109
+ todos: 'your Action Tracker by column: td1 do1 dn1',
110
+ plugins: 'installed plugins (extensions) and their state',
111
+ mirror: 'what CommanderQ sees about this org right now',
112
+ health: 'dashboard summary: services, agents, approvals',
113
+ status: 'profile, org, server health, model, agent, slot',
114
+ cost: 'tokens and cost per model for this session',
115
+ context: 'model, tokens, rules, skills, memory, agent',
116
+ doctor: 'the quox doctor checks plus chat checks',
117
+ server: 'your self-hosted install: version, health, disk',
118
+ config: 'settings: theme, motion, verbose, model',
119
+ model: 'choose the provider and model for your next turns',
120
+ qpe: 'how many proof engine rows show',
121
+ row: 'your own live lines above the status bar',
122
+ deck: 'QuoxDeck MIDI pads (the quoxdeck extension)',
123
+ ext: 'installed extensions: list, turn on or off',
124
+ };