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,161 @@
1
+ /**
2
+ * QUOXON-LATENCY: the headless half of the conversation client (`quox --print`), with no
3
+ * React / Ink / App import. bin/quox.js routes --print here directly, so a headless turn
4
+ * no longer pays ~2 s of renderer load before its request is sent. main.js re-exports
5
+ * every name below, so the interactive client and its importers are unchanged.
6
+ */
7
+ import { parseModelArg } from './model.js';
8
+ import { loadRules, rulesContext } from './rules.js';
9
+
10
+ const FLAGS = new Set(['-p', '--profile', '--verbose', '--demo', '--print', '--output', '--conversation', '--turn-id', '--timeout', '--color', '--no-clear', '-n', '--name', '--service-key', '--demo-onboarding', '--autoplay', '--autoplay-exit', '--provider', '--model', '--agent', '--prompt-profile', '--resume', '-h', '--help', '--']);
11
+ const isFlag = (a) => FLAGS.has(a) || /^--(profile|verbose|print|output|conversation|turn-id|timeout|provider|model|agent|prompt-profile|resume)=/.test(a);
12
+
13
+ export function parseArgs(argv) {
14
+ const o = { profile: null, verbose: 'on', demo: false, help: false, print: false, prompt: null, output: 'text', conversation: null, turnId: null, timeout: 0, color: false, noClear: false, name: null, serviceKey: false, demoOnboarding: false, autoplay: false, autoplayExit: null, provider: null, agent: null, resume: null, verboseSet: false, errors: [] };
15
+ let rawProvider = null, rawModel = null;
16
+ const value = (flag, i) => { const v = argv[i + 1]; if (v === undefined || (v.startsWith('-') && isFlag(v))) { o.errors.push(`${flag} needs a value`); return undefined; } return v; };
17
+ for (let i = 0; i < argv.length; i++) {
18
+ const a = argv[i];
19
+ if (a === '--') { const rest = argv.slice(i + 1).join(' '); if (rest) o.prompt = rest; break; }
20
+ if (a === '-p' || a === '--profile') { const v = value(a, i); if (v !== undefined) { o.profile = v; i++; } }
21
+ else if (a.startsWith('--profile=')) o.profile = a.slice(10);
22
+ else if (a === '--verbose') { o.verboseSet = true; const v = argv[i + 1]; if (['off', 'on', 'full'].includes(v)) { o.verbose = v; i++; } else o.verbose = 'on'; } // no value means on
23
+ else if (a.startsWith('--verbose=')) { o.verboseSet = true; o.verbose = a.slice(10); }
24
+ else if (a === '--resume') { const v = argv[i + 1]; if (v !== undefined && !v.startsWith('-')) { o.resume = v; i++; } else o.resume = true; } // alone: the picker
25
+ else if (a.startsWith('--resume=')) o.resume = a.slice(9) || true;
26
+ else if (a === '-n' || a === '--name') { const v = value(a, i); if (v !== undefined) { o.name = v; i++; } }
27
+ else if (a.startsWith('--name=')) o.name = a.slice(7);
28
+ else if (a === '--demo') o.demo = true;
29
+ else if (a === '--autoplay') o.autoplay = true;
30
+ else if (a === '--autoplay-exit') { o.autoplay = true; const v = argv[i + 1]; if (v !== undefined && /^\d+(\.\d+)?$/.test(v)) { o.autoplayExit = Number(v); i++; } else o.autoplayExit = 5; } // seconds to hold the final frame
31
+ else if (a === '--demo-onboarding') o.demoOnboarding = true;
32
+ else if (a === '--print') { o.print = true; const n = argv[i + 1]; if (n !== undefined && !isFlag(n)) { o.prompt = n; i++; } }
33
+ else if (a.startsWith('--print=')) { o.print = true; o.prompt = a.slice(8); }
34
+ else if (a === '--output') { const v = value(a, i); if (v !== undefined) { o.output = v; i++; } }
35
+ else if (a.startsWith('--output=')) o.output = a.slice(9);
36
+ else if (a === '--conversation') { const v = value(a, i); if (v !== undefined) { o.conversation = v; i++; } }
37
+ else if (a === '--agent') { const v = value(a, i); if (v !== undefined) { o.agent = v; i++; } }
38
+ else if (a.startsWith('--agent=')) o.agent = a.slice(8) || null;
39
+ else if (a === '--turn-id') { const v = value(a, i); if (v !== undefined) { o.turnId = v; i++; } }
40
+ else if (a === '--timeout') { const v = value(a, i); if (v !== undefined) { o.timeout = Number(v) || 0; i++; } }
41
+ else if (a === '--color') o.color = true;
42
+ else if (a === '--no-clear') o.noClear = true;
43
+ else if (a === '--service-key') o.serviceKey = true;
44
+ else if (a === '--provider' || a === '--model') { const v = value(a, i); if (v !== undefined) { if (a === '--provider') rawProvider = v; else rawModel = v; i++; } }
45
+ else if (a.startsWith('--provider=')) rawProvider = a.slice(11);
46
+ else if (a.startsWith('--model=')) rawModel = a.slice(8);
47
+ else if (a === '--prompt-profile') { const v = value(a, i); if (v !== undefined) { o.promptProfile = v; i++; } }
48
+ else if (a.startsWith('--prompt-profile=')) o.promptProfile = a.slice(17);
49
+ else if (a === '-h' || a === '--help') o.help = true;
50
+ else if (!a.startsWith('-') && o.prompt === null && argv.includes('--print')) o.prompt = a; // `--print` given earlier or later; this is its prompt
51
+ else o.errors.push(a.startsWith('-') ? `unknown option ${a}` : `unexpected argument ${a}`);
52
+ }
53
+ if (rawModel && !rawProvider) o.errors.push('--model needs --provider (e.g. --provider openai --model gpt-5.4)');
54
+ else if (rawProvider) {
55
+ const m = parseModelArg(rawModel ? `${rawProvider} ${rawModel}` : rawProvider);
56
+ if (!m.ok) o.errors.push(m.error); else o.provider = m.choice || 'auto'; // 'auto' = this run ignores the saved /model choice
57
+ }
58
+ if (o.promptProfile !== undefined && !['compact', 'full'].includes(o.promptProfile)) { o.errors.push(`invalid --prompt-profile "${o.promptProfile}" (use compact or full)`); delete o.promptProfile; }
59
+ if (!['off', 'on', 'full'].includes(o.verbose)) o.verbose = 'on';
60
+ if (!['text', 'json'].includes(o.output)) { o.errors.push(`invalid --output "${o.output}" (use text or json)`); o.output = 'text'; }
61
+ return o;
62
+ }
63
+
64
+ export const USAGE = 'quox [-p|--profile <name>] [-n|--name <session name>] [--conversation <id>] [--resume [id|name]] [--verbose off|on|full] [--demo [--autoplay [--autoplay-exit [s]]]] [--no-clear] [--service-key] [--provider openai|anthropic|ollama|openrouter|huggingface|groq|together|lmstudio|vllm|custom|auto [--model <id>]] (quoxon is an alias; the old menu is `quox tui`)\n quox --conversation <id> resume that conversation interactively (its saved turns load into the window)\n quox --resume [id|name] pick up a recent conversation; alone it opens a picker\n quox --print "<prompt>" [--output text|json] [--conversation <id>] [--turn-id <id>] [--timeout <s>] [--agent <id>] [--provider <p> [--model <id>]] [--prompt-profile compact|full] (prompt from stdin if absent or -; use -- before a prompt that starts with -)\n exit: 0 completed, 1 other, 2 held for approval, 3 interrupted or abandoned, 4 not signed in, 5 ended without server confirmation\n --provider asks the server for that provider this run (default: your /model choice for this profile, else auto); turn.started from the server says what ran\n --service-key lets chat use an ambient QUOX_SERVICE_KEY when the profile has no login (never used silently)\n --demo plays a simulated journey on its own (no server, nothing real runs)\n --autoplay (with --demo, or QUOX_DEMO_AUTOPLAY=1) plays the whole journey with no input on a fixed script; --autoplay-exit [s] quits s seconds (default 5) after the last frame\n --demo-onboarding walks Getting started with a dry-run install (nothing is downloaded, built or started)';
65
+
66
+ /** Default profile: a resident (agent) login cannot chat, so use the single logged-in human profile. */
67
+ export function pickProfile(explicit, cfgMod, env = process.env) {
68
+ if (explicit) return { name: explicit, note: null };
69
+ if (env.QUOX_PROFILE) return { name: env.QUOX_PROFILE, note: null }; // an explicit one-shot choice (same as -p), never "the default profile"
70
+
71
+ const probe = cfgMod.loadConfig();
72
+ const active = probe.getActiveProfileName?.();
73
+ const residents = new Set((probe.listResidentProfiles?.() || []).map(r => r.name));
74
+ return choosePick(active, residents, probe.listProfiles?.() || []);
75
+ }
76
+ export function choosePick(active, residents, profiles) {
77
+ if (!residents.has(active)) return { name: undefined, note: null };
78
+ const humans = profiles.filter(p => !residents.has(p.name) && p.loggedIn);
79
+ if (humans.length === 1) return { name: humans[0].name, note: null }; // the status line shows the profile; no transcript noise
80
+ const names = profiles.filter(p => !residents.has(p.name) && p.loggedIn).map(p => p.name);
81
+ const hint = names.length ? ` Pass -p <your profile>. Human profiles found: ${names.join(', ')} (start with: quox -p ${names[0]}).` : ' Pass -p <your profile>.';
82
+ return { name: undefined, note: `The default profile "${active}" is an agent login, so this chat runs as the agent, not as a person.${hint}` };
83
+ }
84
+
85
+ /**
86
+ * Chat identity gate. A named profile that does not exist is an error (never created on the fly), and an
87
+ * ambient QUOX_SERVICE_KEY is only a credential when the user asked for it (--service-key or FORCE).
88
+ * -> null when chat may proceed, else { message }.
89
+ */
90
+ export function chatCredentialProblem({ profileArg, profiles, hasStoredLogin, profileName, env = process.env, serviceKeyFlag = false }) {
91
+ if (profileArg && !profiles.some(p => p.name === profileArg)) return { message: `profile ${profileArg} does not exist; create it with: quox login -p ${profileArg}` };
92
+ if (hasStoredLogin) return null;
93
+ if ((serviceKeyFlag || env.QUOX_FORCE_SERVICE_KEY) && env.QUOX_SERVICE_KEY) return null;
94
+ return { message: `Not signed in for chat on profile ${profileName}. Run: quox login -p ${profileName}${env.QUOX_SERVICE_KEY ? ' (an ambient QUOX_SERVICE_KEY is ignored unless you pass --service-key)' : ''}` };
95
+ }
96
+
97
+ /**
98
+ * Is this a machine with no Quox server configured at all? Then bare `quox` is Getting started, and --print
99
+ * says how to get there. Reads profiles only (no network).
100
+ */
101
+ export async function noServerConfigured(opts, cfgMod, env = process.env) {
102
+ const { needsGettingStarted } = await import('./onboarding/detect.js');
103
+ const { loadJourney } = await import('./onboarding/journey.js');
104
+ const probe = cfgMod.loadConfig();
105
+ return needsGettingStarted({ profiles: probe.listProfiles?.() || [], residents: probe.listResidentProfiles?.() || [], profileArg: opts.profile, env, serviceKeyFlag: opts.serviceKey, journey: loadJourney() });
106
+ }
107
+
108
+ /** --print without a usable login: today's message, plus the way in when no server exists yet. */
109
+ export function printAuthMessage(problem, gettingStarted, hint) {
110
+ return `quox: ${problem.message}\n${gettingStarted ? `quox: ${hint}\n` : ''}`;
111
+ }
112
+
113
+ export async function mainPrint(opts) {
114
+ const { runPrint, resolvePrompt, EXIT } = await import('./print.js');
115
+ const out = (s) => process.stdout.write(s);
116
+ const err = (s) => process.stderr.write(s);
117
+ const prompt = await resolvePrompt(opts.prompt, process.stdin);
118
+ let api;
119
+ if (opts.demo) api = (await import('./demo.js')).createDemoApi({ speed: 20 });
120
+ else {
121
+ const cfgMod = await import('../core/config.js');
122
+ const { createAuth } = await import('../core/auth.js');
123
+ const { createClient } = await import('../core/client.js');
124
+ const { createApi } = await import('./api.js');
125
+ const probe = cfgMod.loadConfig();
126
+ const profiles = probe.listProfiles?.() || [];
127
+ const missing = opts.profile && !profiles.some(p => p.name === opts.profile);
128
+ const pick = pickProfile(opts.profile, cfgMod);
129
+ if (pick.note) err(pick.note + '\n');
130
+ const config = missing ? probe : cfgMod.loadConfig(pick.name);
131
+ const auth = createAuth(config);
132
+ const pname = missing ? opts.profile : (config.getActiveProfileName?.() || 'default');
133
+ const problem = chatCredentialProblem({ profileArg: opts.profile, profiles, hasStoredLogin: missing ? false : Boolean(config.getAuth?.()?.access_token), profileName: pname, serviceKeyFlag: opts.serviceKey });
134
+ if (problem) {
135
+ const { NO_SERVER_PRINT } = await import('./onboarding/copy.js');
136
+ err(printAuthMessage(problem, await noServerConfigured(opts, cfgMod), NO_SERVER_PRINT));
137
+ process.exitCode = EXIT.auth; return;
138
+ }
139
+ const { createWindowClient } = await import('./windowOrg.js');
140
+ api = createApi({ ...createWindowClient({ config, auth, createClient }), config }); // #1034: /org stays in this process
141
+ if (opts.conversation && api.conversationExists) {
142
+ const c = await api.conversationExists(opts.conversation);
143
+ if (c.exists === false) { err(`quox: conversation not found: ${opts.conversation}\n`); process.exitCode = EXIT.error; return; }
144
+ if (c.exists === null) err(`quox: could not verify conversation ${opts.conversation} (${c.reason}); continuing\n`);
145
+ }
146
+ }
147
+ const rules = opts.demo ? null : loadRules();
148
+ for (const s of rules?.skipped || []) err(`quox: ignoring ${s.path}: ${s.reason}\n`);
149
+ const choice = opts.provider === 'auto' ? null : opts.provider || api.modelChoice?.() || null;
150
+ process.exitCode = await runPrint({ api, choice, agent: opts.agent || undefined, promptProfile: opts.promptProfile, context: rulesContext(rules), prompt, output: opts.output, conversation: opts.conversation, turnId: opts.turnId || undefined, timeoutS: opts.timeout, color: opts.color, profile: opts.profile || null, out, err });
151
+ }
152
+
153
+ /** bin/quox.js entry for --print: the same arg checks main() makes, then the headless turn. */
154
+ export async function runHeadless(argv) {
155
+ const opts = parseArgs(argv);
156
+ if (!opts.print) return false; // not a headless run after all (e.g. `-- --print` is a prompt): main() owns it
157
+ if (opts.help) { console.log(USAGE); return true; }
158
+ if (opts.errors.length) { process.stderr.write(`quox: ${opts.errors.join('; ')}\n${USAGE}\n`); process.exitCode = 1; return true; }
159
+ await mainPrint(opts);
160
+ return true;
161
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Z2H-R3: which address to PRINT for the dashboard and for the host installer. Over SSH to a fresh
3
+ * install the profile is https://localhost:3000, which only works on the Quox box, so a printed
4
+ * localhost is wrong for anyone reading it elsewhere. Order: the server's app URL (app_base_url),
5
+ * else the address this CLI reached the server on, else the address the install finale showed,
6
+ * else this machine's detected LAN address. Loopback is the last resort, never the first answer.
7
+ */
8
+ import { networkInterfaces } from 'node:os';
9
+
10
+ export const isLoopbackUrl = (u) => {
11
+ try { return /^(localhost|127\.\d+\.\d+\.\d+|\[::1\]|::1|0\.0\.0\.0)$/i.test(new URL(u).hostname); } catch { return true; }
12
+ };
13
+ const ok = (u) => typeof u === 'string' && /^https?:\/\/[^\s/]+/.test(u) && !isLoopbackUrl(u);
14
+ const origin = (u) => new URL(u).origin;
15
+
16
+ /** First non-internal IPv4 address (private ranges preferred), or null. */
17
+ export function detectLanIp(ifaces = networkInterfaces()) {
18
+ const all = Object.entries(ifaces || {}).filter(([n]) => !/^(docker|br-|veth|virbr|cni|flannel|cali|tun|tap)/i.test(n)).flatMap(([, v]) => v || [])
19
+ .filter(a => a && a.family === 'IPv4' && !a.internal && a.address);
20
+ const priv = all.find(a => /^(10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/.test(a.address));
21
+ return (priv || all[0])?.address || null;
22
+ }
23
+
24
+ /** Dashboard base: { url, source } . source: server | profile | install | lan | local. */
25
+ export function resolveDashboardBase({ serverApp, profileUrl, journeyUrl, lanIp } = {}) {
26
+ if (ok(serverApp)) return { url: origin(serverApp), source: 'server' };
27
+ if (ok(profileUrl)) return { url: origin(profileUrl), source: 'profile' };
28
+ if (ok(journeyUrl)) return { url: origin(journeyUrl), source: 'install' };
29
+ if (lanIp) return { url: `https://${lanIp}:3000`, source: 'lan' };
30
+ return { url: profileUrl || 'https://localhost:3000', source: 'local' };
31
+ }
32
+
33
+ /** Base that serves GET /install (the collector, :9848): { url, source }. */
34
+ export function resolveCollectorBase({ collectorUrl, serverApp, journeyUrl, lanIp } = {}) {
35
+ if (ok(collectorUrl)) return { url: origin(collectorUrl), source: 'profile' };
36
+ for (const [u, source] of [[serverApp, 'server'], [journeyUrl, 'install']]) {
37
+ if (ok(u)) return { url: `http://${new URL(u).hostname}:9848`, source };
38
+ }
39
+ if (lanIp) return { url: `http://${lanIp}:9848`, source: 'lan' };
40
+ return { url: collectorUrl || null, source: 'local' };
41
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * HITL-INBOX-BAR (owner 2026-10-26): the owner's Quox inbox in the chat. Pure helpers: the status-area bar text,
3
+ * the arrival line, the /inbox table and the one-item view. No I/O here; app.js owns the calls.
4
+ */
5
+ const clip = (s, n) => { const t = String(s ?? '').replace(/\s+/g, ' ').trim(); return t.length > n ? `${t.slice(0, Math.max(0, n - 1))}…` : t; };
6
+ const pad = (s, n) => { const t = clip(s, n); return t + ' '.repeat(Math.max(0, n - [...t].length)); };
7
+ const ms = (v) => { const t = Date.parse(String(v ?? '').includes('T') || !v ? v : `${String(v).replace(' ', 'T')}Z`); return Number.isFinite(t) ? t : 0; };
8
+ const CLOSED = new Set(['resolved', 'cancelled', 'expired', 'deleted', 'archived']);
9
+
10
+ /** Newest first. */
11
+ export const newestFirst = (items) => [...(Array.isArray(items) ? items : [])].filter((i) => i && i.id).sort((a, b) => ms(b.created_at ?? b.createdAt) - ms(a.created_at ?? a.createdAt));
12
+ /** Unread = the server's per-user is_read flag is not set, and the item is not closed or archived. */
13
+ export const isUnread = (it) => !it.is_read && !it.archived_at && !CLOSED.has(String(it.status || '').toLowerCase());
14
+ export const unreadItems = (items) => newestFirst(items).filter(isUnread);
15
+
16
+ /** The status-area line, or '' when nothing is unread: `📨 2 new · <newest title>`. */
17
+ export function inboxBarText(items, cols = 80) {
18
+ const un = unreadItems(items);
19
+ if (!un.length) return '';
20
+ const head = `📨 ${un.length} new · `;
21
+ return head + clip(un[0].title || un[0].kind || 'untitled', Math.max(8, cols - 4 - [...head].length));
22
+ }
23
+
24
+ /** Unread items not seen on an earlier poll. `seen` is a Set of ids; the first poll seeds it and announces nothing. */
25
+ export function arrivals(items, seen) {
26
+ const un = unreadItems(items);
27
+ const fresh = seen ? un.filter((i) => !seen.has(i.id)) : [];
28
+ return { fresh, ids: new Set((Array.isArray(items) ? items : []).map((i) => i?.id).filter(Boolean)) };
29
+ }
30
+ export const arrivalLine = (it) => `📨 ${clip(it.title || it.kind || 'untitled', 70)} · /inbox 1`;
31
+
32
+ export const fromOf = (it) => String(it.created_by_name || it.payload?.source || it.payload?.approval_context?.source_label || it.source || it.inbox_type || it.kind || 'system');
33
+ export function ageOf(v, now = Date.now()) {
34
+ const t = ms(v); if (!t) return '?';
35
+ const s = Math.max(0, Math.round((now - t) / 1000));
36
+ if (s < 90) return 'now';
37
+ if (s < 5400) return `${Math.round(s / 60)}m`;
38
+ if (s < 129600) return `${Math.round(s / 3600)}h`;
39
+ return `${Math.round(s / 86400)}d`;
40
+ }
41
+
42
+ /** The latest 10 as a compact table; unread rows carry a dot. -> [{ text, tone }] */
43
+ export function inboxTable(items, now = Date.now(), cols = 100, max = 10) {
44
+ const rows = newestFirst(items).slice(0, max);
45
+ if (!rows.length) return [{ text: 'Your inbox is empty.', tone: 'dim' }];
46
+ const tw = Math.max(16, Math.min(60, cols - 40));
47
+ const out = [{ text: ` ${pad('#', 3)}${pad('when', 6)}${pad('from', 14)}${pad('title', tw)} state`, tone: 'dim' }];
48
+ rows.forEach((it, i) => {
49
+ const un = isUnread(it);
50
+ out.push({ text: `${un ? '●' : ' '} ${pad(i + 1, 3)}${pad(ageOf(it.created_at ?? it.createdAt, now), 6)}${pad(fromOf(it), 14)}${pad(it.title || it.kind || 'untitled', tw)} ${un ? 'unread' : (it.status || 'read')}`, tone: un ? 'accent' : 'dim' });
51
+ });
52
+ const un = unreadItems(items).length;
53
+ out.push({ text: `${un} unread · /inbox N open one · /inbox read N|all · /inbox open N (approvals)`, tone: 'dim' });
54
+ return out;
55
+ }
56
+
57
+ /** Arg grammar: '' | N | read N|all | open N. -> { kind, n?|all? } | { kind:'usage' } */
58
+ export function parseInbox(arg) {
59
+ const a = String(arg || '').trim().toLowerCase();
60
+ if (!a || a === 'list' || a === 'latest') return { kind: 'list' };
61
+ let m = /^(?:show\s+)?#?(\d+)$/.exec(a); if (m) return { kind: 'show', n: Number(m[1]) };
62
+ m = /^open\s+#?(\d+)$/.exec(a); if (m) return { kind: 'open', n: Number(m[1]) };
63
+ m = /^read\s+(all|#?\d+)$/.exec(a); if (m) return m[1] === 'all' ? { kind: 'read', all: true } : { kind: 'read', n: Number(m[1].replace('#', '')) };
64
+ return { kind: 'usage' };
65
+ }
66
+ export const INBOX_USAGE = 'Usage: /inbox | /inbox N | /inbox read N|all | /inbox open N';
67
+
68
+ /** One item in full. -> [{ text, tone }] */
69
+ export function inboxDetail(it, now = Date.now()) {
70
+ const p = it.payload || {};
71
+ const body = it.body || it.message || p.message || p.body || p.summary || p.text || '';
72
+ const out = [{ text: `${it.title || it.kind || 'untitled'}`, tone: 'accent' },
73
+ { text: `from ${fromOf(it)} · ${ageOf(it.created_at ?? it.createdAt, now)} ago · ${it.kind || 'item'} · ${it.priority || 'normal'} · ${it.status || '?'}`, tone: 'dim' }];
74
+ if (body) for (const l of String(body).split('\n')) out.push({ text: l, tone: 'text' });
75
+ if (it.dashboard_url) out.push({ text: it.dashboard_url, tone: 'dim' });
76
+ return out;
77
+ }
78
+ /** Approval-type items route to /approvals (the existing view). */
79
+ export const isApprovalItem = (it) => String(it.kind || '').toLowerCase() === 'approval' || Boolean(it.payload?.approval_context) || it.payload?.type === 'decision';
@@ -64,8 +64,10 @@ export function normUsage(u, doneCost) {
64
64
  const tokens = Number.isFinite(Number(out)) && out != null ? Number(out)
65
65
  : Number.isFinite(Number(total)) && total != null ? Number(total)
66
66
  : Number.isFinite(Number(inp)) && inp != null ? Number(inp) : null;
67
+ // cost_source 'claude-cli': the claude CLI's own API-equivalent estimate on the subscription path, not a price.
68
+ const costSource = typeof o.cost_source === 'string' ? o.cost_source : null;
67
69
  const cost = Number(o.cost_usd ?? doneCost);
68
- return { tokens, cost: Number.isFinite(cost) && cost > 0 ? cost : null, real: tokens != null };
70
+ return { tokens, cost: !costSource && Number.isFinite(cost) && cost > 0 ? cost : null, real: tokens != null, costSource };
69
71
  }
70
72
 
71
73
  /** Live token figure: the real number once the server reported it, else an estimate from received text (~chars/4). */
@@ -82,7 +84,9 @@ export function turnTokens(turn) {
82
84
  * Sans Mono fallback (ェエ like I/E, ュョユヨ boxes, ニ "=", ノ "/", フ "7", ヘ "^", リ "U", レ "L", ロ a box, ー a dash)
83
85
  * and the voiced marks ゙ ゚.
84
86
  */
85
- export const SCRAMBLE_GLYPHS = 'ヲァィゥォャッアイウオカキクケコサシスセソタチツテトナヌネハヒホマミムメモヤラルワン';
87
+ /** Decode glyphs: every half-width katakana (U+FF66-FF9D, one cell each, minus the long-vowel bar ー) plus the
88
+ * code-rain digits. Owner 2026-10-15: the reveal felt like "just a few characters" with 44 look-alike kana. */
89
+ export const SCRAMBLE_GLYPHS = 'ヲァィゥェォャュョッアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン0123456789';
86
90
 
87
91
  /** Deterministic hash in [0,1). */
88
92
  const hsh = (a, b, c = 0) => { let x = (a * 374761393 + b * 668265263 + c * 2147483647) | 0; x = Math.imul(x ^ (x >>> 13), 1274126177); return ((x ^ (x >>> 16)) >>> 0) / 4294967296; };
@@ -0,0 +1,79 @@
1
+ /**
2
+ * LAZY-AI-CLI (owner 2026-10-19): "Quox can depend heavily on your choice or blend of Claude and OpenAI models.
3
+ * We advise installing both Claude Code and Codex." Draw people in step by step: when something needs a vendor CLI
4
+ * that is missing, offer to install it THEN, as a y/N.
5
+ *
6
+ * ONE flow behind `quox provider install` and the TUI offer cards (first-run suggestion + just-in-time), so they
7
+ * cannot drift. The server downloads the vendor's own npm package (never a binary Quox ships), as the Quox service
8
+ * user, into a volume that survives updates, audited start and finish (services/collector/routes/providerCli.js).
9
+ */
10
+ import { detectCallerSource } from '../core/callerSource.js';
11
+
12
+ export const INSTALL_TOOLS = {
13
+ 'claude-code': { id: 'claude-code', name: 'Claude Code', vendor: 'Anthropic', bin: 'claude', after: 'Claude sign-in is available now: sign in from the dashboard Terminal, then pick Claude in /model.' },
14
+ codex: { id: 'codex', name: 'Codex', vendor: 'OpenAI', bin: 'codex', after: 'Codex is ready: sign in to ChatGPT from the dashboard Terminal (Codex), then pick it in /model.' },
15
+ };
16
+
17
+ export const SUGGEST_LINE = 'Quox works best with both Claude and OpenAI models. We suggest installing Claude Code and Codex.';
18
+
19
+ const ALIASES = { claude: 'claude-code', 'claude-code': 'claude-code', claudecode: 'claude-code', codex: 'codex', 'openai-codex': 'codex' };
20
+ export const toolFor = (s) => ALIASES[String(s || '').trim().toLowerCase()] || null;
21
+
22
+ const sizeText = (p) => (p?.downloadMb ? `about ${p.downloadMb} MB` : null);
23
+
24
+ export function planLine(p) {
25
+ const t = INSTALL_TOOLS[p?.id] || { name: p?.name || p?.id };
26
+ const size = sizeText(p);
27
+ return `${t.name} ${p?.version || ''} (${t.vendor ? `${t.vendor}'s ` : ''}${p?.package || ''}) from ${p?.source || 'registry.npmjs.org'}${size ? `, ${size} download` : ''}${p?.installedMb ? `, ${p.installedMb} MB on disk` : ''}. It installs on this server as the Quox service user and stays across updates.`.replace(/\s+/g, ' ');
28
+ }
29
+
30
+ export function offerQuestion(tool, plan) {
31
+ const t = INSTALL_TOOLS[tool];
32
+ const size = sizeText(plan);
33
+ return `${t.name} isn't installed on this server. Install it now? (${size ? `${size} from npm` : 'from npm'})`;
34
+ }
35
+
36
+ /** The CLIs the server says are missing, in a stable order. */
37
+ export function missingTools(status) {
38
+ if (!status?.ok || !Array.isArray(status.tools)) return [];
39
+ return Object.keys(INSTALL_TOOLS).filter((id) => status.tools.some((t) => t.id === id && t.installed === false));
40
+ }
41
+
42
+ /** An agent (a session, an attached agent, a Claude process) never self-approves an install: it files a card. */
43
+ export function isAgentCaller(env = process.env, detect = detectCallerSource) {
44
+ if (env.QUOX_AGENT_ID || env.QUOX_SESSION_ID || env.QUOX_SESSION) return true;
45
+ let src = null;
46
+ try { src = detect(env); } catch { src = null; }
47
+ if (!src) return false;
48
+ if (['attached_agent', 'held_slot', 'QUOX_STREAM'].includes(src.via)) return true;
49
+ return src.via === 'process' && /^claude\b/i.test(String(src.label || ''));
50
+ }
51
+
52
+ const POLL_MS = 3000;
53
+
54
+ /**
55
+ * Start the server install and wait for it. Never throws.
56
+ * @returns {Promise<{ok:boolean, version?:string|null, lines:string[]}>}
57
+ */
58
+ export async function runInstall(api, tool, { approvalId = null, onProgress = () => {}, sleep = (ms) => new Promise((r) => setTimeout(r, ms)), now = Date.now, timeoutMs = 15 * 60 * 1000 } = {}) {
59
+ const t = INSTALL_TOOLS[tool];
60
+ if (!t) return { ok: false, lines: [`No installable CLI called "${tool}". Use one of: ${Object.keys(INSTALL_TOOLS).join(', ')}`] };
61
+ const start = await api.providerCliInstall(tool, { approvalId });
62
+ if (!start?.ok || !start.job_id) {
63
+ if (start?.status === 404 || start?.status === 405) return { ok: false, lines: [`This Quox server is too old to install ${t.name} for you. Update it first: quox server update`] };
64
+ if (start?.status === 403) return { ok: false, lines: [`✖ ${start.error || 'Only an instance admin can install software on this server.'} Nothing was installed.`] };
65
+ return { ok: false, lines: [`✖ ${t.name} was not installed: ${start?.error || 'the server did not start the install'}`] };
66
+ }
67
+ const began = now();
68
+ let last = began;
69
+ for (;;) {
70
+ await sleep(POLL_MS);
71
+ const j = await api.providerCliJob(start.job_id);
72
+ if (j?.state === 'done' && j.ok) {
73
+ return { ok: true, version: j.version || null, lines: [`✔ ${t.name} ${j.version || ''} is installed on this server.`.replace(/\s+/g, ' ').replace(' .', '.'), t.after] };
74
+ }
75
+ if (j?.state === 'failed') return { ok: false, lines: [`✖ ${t.name} was not installed: ${j.error || 'the install failed'}`] };
76
+ if (now() - began >= timeoutMs) return { ok: false, lines: [`◐ ${t.name} is still running its install on the server. Check again shortly: quox provider list`] };
77
+ if (now() - last >= 15000) { last = now(); onProgress(`◐ installing ${t.name} (${Math.round((now() - began) / 1000)}s)`); }
78
+ }
79
+ }
@@ -13,14 +13,25 @@ const PANELS = [
13
13
  function PANEL_RULES() { return PANELS.map(([re, cmd]) => [re, () => cmd]); }
14
14
 
15
15
  const RULES = [
16
- [new RegExp(`^attach(?: me)?(?: to)? ${WORD}$`, 'i'), (m) => `/attach ${m[1]}`],
16
+ // owner 2026-10-15: "attach to TRIAGE-Q agent" / "reattach to ..." took four turns; the tip must catch them
17
+ [new RegExp(`^(?:please )?(?:re)?attach(?: me)?(?: to)?(?: the)? ${WORD}(?: agent)?(?: please)?$`, 'i'), (m) => `/attach ${m[1].toLowerCase()}`],
17
18
  [/^rename (?:this|the|my) (?:session|conversation|chat) (?:to|as) ([\w.-]+(?: [\w.-]+){0,2})$/i, (m) => m[1].split(' ').some(w => /^(and|then|but|so|please)$/i.test(w)) ? null : `/rename ${m[1]}`],
18
19
  [/^(?:what(?:'s| is) running|what is running now)$/i, () => '/roster'],
19
20
  [/^(?:show|list|open) (?:my |the )?(?:pending )?approvals$/i, () => '/approvals'],
20
21
  [/^(?:what(?:'s| is)|show me what(?:'s| is)) (?:waiting|pending) (?:for )?(?:my )?approval$/i, () => '/approvals'],
22
+ [/^(?:so )?(?:what (?:did i miss|have i missed)(?: (?:while|when) i was (?:away|out|gone|offline))?|what (?:happened|changed) (?:while|when) i was (?:away|out|gone|offline)|(?:did )?anything happen(?:ed)? while i was (?:away|out|gone|offline)|catch me up)$/i, () => '/away'],
23
+ [/^(?:(?:give me |show me |can i get )?(?:a |the )?sit ?rep(?: please)?|what(?:'s| is) the situation(?: now)?|situation report(?: please)?)$/i, () => '/sitrep'],
24
+ [/^(?:how(?:'s| is) (?:the |my |our )?fleet(?: doing)?|(?:show|list) (?:me )?(?:the |my |our )?fleet|fleet (?:status|health)|what(?:'s| is) (?:the )?(?:state|health) of (?:the |my |our )?(?:fleet|computers|servers))$/i, () => '/fleet'],
25
+ [/^(?:open|show|take me to|go to) (?:me )?(?:to )?(?:the |my )?(?:quox )?(?:web )?dashboard$/i, () => '/dashboard'],
21
26
  [/^detach(?: from (?:the )?agent)?$/i, () => '/detach'],
27
+ [/^(?:clear|cls|clear (?:the )?screen)$/i, () => '/clear'], // owner typed bare "clear" (2026-10-15)
22
28
  [/^(?:start )?(?:a )?new conversation$/i, () => '/new'],
23
29
  [/^(?:please )?(?:compact|summari[sz]e and compact|compress) (?:this|the|our|my) (?:conversation|chat|session|context)(?: please)?$/i, () => '/compact'],
30
+ [/^(?:please )?(?:update|upgrade) (?:the |my |this )?(?:quox )?(?:server|install|instance|quox)(?: (?:to (?:the )?latest|please))?$/i, () => 'quox server update --dry-run (in a terminal; shows the plan first)'],
31
+ [/^(?:please )?roll ?back (?:the |my |this )?(?:quox )?(?:server|install|instance|update)$/i, () => 'quox server rollback --dry-run (in a terminal)'],
32
+ [/^(?:please )?uninstall (?:the |my |this )?(?:quox )?(?:server|install|instance|quox)$/i, () => 'quox server uninstall --dry-run (in a terminal; lists what goes first)'],
33
+ [/^(?:what version is (?:the |my )?(?:quox )?server(?: on| running)?|is (?:the |my )?(?:quox )?server (?:up to date|healthy)|server status)$/i, () => '/server'],
34
+ [/^(?:make|create|write) (?:a )?(?:support|diag(?:nostic)?s?) bundle$/i, () => 'quox server diag (in a terminal; written locally, never uploaded)'],
24
35
  ...PANEL_RULES()
25
36
  ];
26
37
 
@@ -5,8 +5,21 @@
5
5
  *
6
6
  * parseChunk(chunk, state) -> { events, state }
7
7
  * Events: { type: 'text', text } | { type: 'paste', text } | { type: 'key', name, ctrl?, alt? } | { type: 'focus', focused }
8
+ * | { type: 'mouse', action: 'press'|'release'|'drag'|'move'|'wheel', button, x, y, shift, alt, ctrl } (SGR 1006, QUOXON-MOUSE)
9
+ * | { type: 'cursor', row, col } (a cursor position report, the answer to DSR ESC[6n)
8
10
  */
9
11
 
12
+ const BUTTONS = ['left', 'middle', 'right', 'none'];
13
+ const WHEEL = ['up', 'down', 'left', 'right'];
14
+ /** One SGR mouse report (ESC [ < b ; x ; y M|m) as an event. */
15
+ function mouseEvent(b, x, y, final) {
16
+ const low = b & 3;
17
+ const mods = { shift: Boolean(b & 4), alt: Boolean(b & 8), ctrl: Boolean(b & 16) };
18
+ if (b & 64) return { type: 'mouse', action: 'wheel', button: WHEEL[low], x, y, ...mods };
19
+ if (b & 32) return { type: 'mouse', action: low === 3 ? 'move' : 'drag', button: BUTTONS[low], x, y, ...mods };
20
+ return { type: 'mouse', action: final === 'm' ? 'release' : 'press', button: BUTTONS[low], x, y, ...mods };
21
+ }
22
+
10
23
  export const PASTE_ON = '\x1b[?2004h';
11
24
  export const PASTE_OFF = '\x1b[?2004l';
12
25
  const PASTE_START = '\x1b[200~';
@@ -42,18 +55,27 @@ const CSI_KEYS = {
42
55
  '\x1b[1~': 'home', '\x1b[4~': 'end', '\x1b[7~': 'home', '\x1b[8~': 'end',
43
56
  '\x1b[3~': 'delete', '\x1b[Z': 'shift-tab',
44
57
  '\x1b[13;2u': 'shift-enter', '\x1b[27;2;13~': 'shift-enter', '\x1b\r': 'shift-enter', '\x1b\n': 'shift-enter',
45
- '\x1b[1;5D': 'word-left', '\x1b[1;5C': 'word-right', '\x1b[1;3D': 'word-left', '\x1b[1;3C': 'word-right'
58
+ '\x1b[1;5D': 'word-left', '\x1b[1;5C': 'word-right', '\x1b[1;3D': 'word-left', '\x1b[1;3C': 'word-right',
59
+ // COMPOSER-EDIT (owner 2026-10-17: edit like Claude Code's prompt box). Alt+B/F (macOS Option+Left/Right), iTerm2 Alt+arrows,
60
+ // Alt+Backspace, Alt+D, Ctrl/Alt+Delete, and Shift (+Ctrl/Alt) arrows, Home and End for selection.
61
+ '\x1bb': 'word-left', '\x1bf': 'word-right', '\x1b\x1b[D': 'word-left', '\x1b\x1b[C': 'word-right',
62
+ '\x1b\x7f': 'delete-word', '\x1b\x08': 'delete-word', '\x1bd': 'delete-word-forward',
63
+ '\x1b[3;5~': 'delete-word-forward', '\x1b[3;3~': 'delete-word-forward',
64
+ '\x1b[1;2D': 'select-left', '\x1b[1;2C': 'select-right', '\x1b[1;2A': 'select-up', '\x1b[1;2B': 'select-down',
65
+ '\x1b[1;2H': 'select-home', '\x1b[1;2F': 'select-end',
66
+ '\x1b[1;6D': 'select-word-left', '\x1b[1;6C': 'select-word-right', '\x1b[1;4D': 'select-word-left', '\x1b[1;4C': 'select-word-right'
46
67
  };
47
68
  const CSI_LIST = Object.keys(CSI_KEYS).sort((a, b) => b.length - a.length);
48
69
 
49
70
  const CTRL = {
50
- '\x01': 'home', '\x05': 'end', '\x03': 'ctrl-c', '\x04': 'ctrl-d', '\x07': 'ctrl-g',
51
- '\x0f': 'ctrl-o', '\x13': 'ctrl-s', '\x15': 'kill-line', '\x17': 'delete-word',
71
+ '\x01': 'home', '\x05': 'end', '\x03': 'ctrl-c', '\x04': 'ctrl-d', '\x07': 'ctrl-g', '\x12': 'ctrl-r',
72
+ '\x0b': 'kill-line-forward', '\x0f': 'ctrl-o', '\x13': 'ctrl-s', '\x15': 'kill-line', '\x17': 'delete-word',
52
73
  '\x1f': 'undo', '\x19': 'redo', '\x0a': 'newline', '\x7f': 'backspace', '\x08': 'backspace',
53
74
  '\r': 'enter', '\t': 'tab', '\x02': 'left', '\x06': 'right'
54
75
  };
55
76
 
56
- export function parseChunk(chunk, state = initialKeyState()) {
77
+ /** `draft` = the composer text before this chunk (a burst split mid-line continues the line already typed). */
78
+ export function parseChunk(chunk, state = initialKeyState(), draft = '') {
57
79
  const events = [];
58
80
  let s = String(chunk);
59
81
  let st = { partial: '', ...state };
@@ -77,6 +99,12 @@ export function parseChunk(chunk, state = initialKeyState()) {
77
99
  if (s[0] === '\x1b') {
78
100
  // Focus reporting (CSI ?1004h, enabled by sound/index.js): CSI I = focus in, CSI O = focus out.
79
101
  if (s.startsWith('\x1b[I') || s.startsWith('\x1b[O')) { events.push({ type: 'focus', focused: s[2] === 'I' }); s = s.slice(3); continue; }
102
+ // QUOXON-MOUSE: SGR mouse reports; a report split across chunks is held, never typed into the composer.
103
+ const mm = /^\x1b\[<(\d+);(\d+);(\d+)([Mm])/.exec(s);
104
+ if (mm) { events.push(mouseEvent(Number(mm[1]), Number(mm[2]), Number(mm[3]), mm[4])); s = s.slice(mm[0].length); continue; }
105
+ if (/^\x1b\[<[\d;]*$/.test(s)) { st.partial = s; s = ''; break; }
106
+ const cr = /^\x1b\[(\d+);(\d+)R/.exec(s);
107
+ if (cr) { events.push({ type: 'cursor', row: Number(cr[1]), col: Number(cr[2]) }); s = s.slice(cr[0].length); continue; }
80
108
  const hit = CSI_LIST.find(k => s.startsWith(k));
81
109
  if (hit) { events.push({ type: 'key', name: CSI_KEYS[hit] }); s = s.slice(hit.length); continue; }
82
110
  // Unknown escape sequence: swallow ESC [ ... final byte, or a bare ESC+char (alt-x).
@@ -90,22 +118,37 @@ export function parseChunk(chunk, state = initialKeyState()) {
90
118
  const run = /^[^\x00-\x1f\x7f]+/.exec(s);
91
119
  if (run) { events.push({ type: 'text', text: run[0] }); s = s.slice(run[0].length); continue; }
92
120
  const c = s[0];
93
- if (CTRL[c]) { events.push({ type: 'key', name: CTRL[c] }); }
121
+ if (CTRL[c]) { events.push(c === '\x01' ? { type: 'key', name: CTRL[c], via: 'ctrl-a' } : { type: 'key', name: CTRL[c] }); } // via: a double Ctrl+A selects all (app.js)
94
122
  s = s.slice(1);
95
123
  }
96
- return { events: coalesceBurst(events), state: st };
124
+ return { events: coalesceBurst(events, draft), state: st };
97
125
  }
98
126
 
99
127
  /**
100
128
  * If a chunk produced text and then an Enter/newline FOLLOWED by more text, it is
101
129
  * a non-bracketed paste: fold it into one paste event. A trailing single Enter
102
130
  * (text then Enter at the end) stays as text + enter so fast typing still submits.
131
+ *
132
+ * Except a burst whose every line is a slash command (R3 D3): that is typed input batched by tmux,
133
+ * SSH/mosh or a script ("/help" Enter "/model x" Enter), never a paste, so each Enter runs its line.
134
+ * Folding it made a multi-line "/" draft that trapped every later Enter.
103
135
  */
104
- function coalesceBurst(events) {
136
+ const isEnter = (e) => e.type === 'key' && (e.name === 'enter' || e.name === 'newline');
137
+ function commandLines(events, draft = '') {
138
+ let line = draft.includes('\n') ? '\n' : draft; // a multi-line draft is never a command line
139
+ for (const e of events) {
140
+ if (e.type === 'text') line += e.text;
141
+ else if (e.name === 'enter') { if (!line.startsWith('/')) return false; line = ''; }
142
+ else return false; // newline (Ctrl+J), tab or any other key: not plain typed commands
143
+ }
144
+ return line === '' || line.startsWith('/');
145
+ }
146
+ function coalesceBurst(events, draft = '') {
105
147
  const hasInner = events.some((e, i) =>
106
- e.type === 'key' && (e.name === 'enter' || e.name === 'newline') && i > 0 && i < events.length - 1 &&
148
+ isEnter(e) && i > 0 && i < events.length - 1 &&
107
149
  events.slice(i + 1).some(x => x.type === 'text'));
108
150
  if (!hasInner) return events;
151
+ if (commandLines(events, draft)) return events;
109
152
  let text = '';
110
153
  const rest = [];
111
154
  for (let i = 0; i < events.length; i++) {