quoxcli 0.0.0-stage → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (516) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +144 -2
  3. package/bin/quox.js +265 -0
  4. package/bin/quoxon.js +8 -0
  5. package/lib/api/endpoints/admin.js +151 -0
  6. package/lib/api/endpoints/aee.js +38 -0
  7. package/lib/api/endpoints/agent-attach.js +126 -0
  8. package/lib/api/endpoints/agent-schedules.js +26 -0
  9. package/lib/api/endpoints/agents.js +63 -0
  10. package/lib/api/endpoints/assistants.js +62 -0
  11. package/lib/api/endpoints/audit.js +14 -0
  12. package/lib/api/endpoints/auth.js +50 -0
  13. package/lib/api/endpoints/awareness.js +34 -0
  14. package/lib/api/endpoints/away.js +15 -0
  15. package/lib/api/endpoints/backup-schedules.js +20 -0
  16. package/lib/api/endpoints/backups.js +86 -0
  17. package/lib/api/endpoints/brain.js +140 -0
  18. package/lib/api/endpoints/budgets.js +62 -0
  19. package/lib/api/endpoints/calendarq.js +71 -0
  20. package/lib/api/endpoints/catalog.js +14 -0
  21. package/lib/api/endpoints/chat.js +61 -0
  22. package/lib/api/endpoints/consent.js +26 -0
  23. package/lib/api/endpoints/contactsq.js +68 -0
  24. package/lib/api/endpoints/conversations.js +46 -0
  25. package/lib/api/endpoints/custom-roles.js +92 -0
  26. package/lib/api/endpoints/divisions.js +56 -0
  27. package/lib/api/endpoints/domains.js +38 -0
  28. package/lib/api/endpoints/engagement.js +231 -0
  29. package/lib/api/endpoints/engine.js +11 -0
  30. package/lib/api/endpoints/entity.js +50 -0
  31. package/lib/api/endpoints/executions.js +45 -0
  32. package/lib/api/endpoints/fetchAllowlist.js +27 -0
  33. package/lib/api/endpoints/file.js +20 -0
  34. package/lib/api/endpoints/files.js +40 -0
  35. package/lib/api/endpoints/github.js +21 -0
  36. package/lib/api/endpoints/governance.js +43 -0
  37. package/lib/api/endpoints/hat.js +66 -0
  38. package/lib/api/endpoints/inbox.js +52 -0
  39. package/lib/api/endpoints/inboxq.js +120 -0
  40. package/lib/api/endpoints/index.js +242 -0
  41. package/lib/api/endpoints/invites.js +19 -0
  42. package/lib/api/endpoints/llm-policy.js +43 -0
  43. package/lib/api/endpoints/lucid.js +89 -0
  44. package/lib/api/endpoints/members.js +32 -0
  45. package/lib/api/endpoints/memory.js +94 -0
  46. package/lib/api/endpoints/mfa.js +32 -0
  47. package/lib/api/endpoints/mind.js +13 -0
  48. package/lib/api/endpoints/mon.js +184 -0
  49. package/lib/api/endpoints/notesq.js +43 -0
  50. package/lib/api/endpoints/objectives.js +68 -0
  51. package/lib/api/endpoints/orgs.js +38 -0
  52. package/lib/api/endpoints/plugins.js +152 -0
  53. package/lib/api/endpoints/powers.js +75 -0
  54. package/lib/api/endpoints/presence.js +33 -0
  55. package/lib/api/endpoints/prompts.js +69 -0
  56. package/lib/api/endpoints/qlarity.js +122 -0
  57. package/lib/api/endpoints/quoxbox.js +55 -0
  58. package/lib/api/endpoints/quoxchat.js +54 -0
  59. package/lib/api/endpoints/quoxflow.js +32 -0
  60. package/lib/api/endpoints/quoxplan.js +121 -0
  61. package/lib/api/endpoints/quoxseo.js +114 -0
  62. package/lib/api/endpoints/reach.js +47 -0
  63. package/lib/api/endpoints/router.js +17 -0
  64. package/lib/api/endpoints/runs.js +38 -0
  65. package/lib/api/endpoints/safety.js +53 -0
  66. package/lib/api/endpoints/scim.js +46 -0
  67. package/lib/api/endpoints/scopes.js +14 -0
  68. package/lib/api/endpoints/security.js +69 -0
  69. package/lib/api/endpoints/setup.js +90 -0
  70. package/lib/api/endpoints/skills.js +200 -0
  71. package/lib/api/endpoints/sso.js +67 -0
  72. package/lib/api/endpoints/stepup.js +20 -0
  73. package/lib/api/endpoints/tags.js +32 -0
  74. package/lib/api/endpoints/teams.js +56 -0
  75. package/lib/api/endpoints/telephony.js +46 -0
  76. package/lib/api/endpoints/tg.js +56 -0
  77. package/lib/api/endpoints/tools.js +50 -0
  78. package/lib/api/endpoints/traces.js +157 -0
  79. package/lib/api/endpoints/triggers.js +96 -0
  80. package/lib/api/endpoints/user-library.js +41 -0
  81. package/lib/api/endpoints/users.js +20 -0
  82. package/lib/api/endpoints/v1.js +585 -0
  83. package/lib/api/endpoints/vault.js +192 -0
  84. package/lib/api/endpoints/voice.js +22 -0
  85. package/lib/api/endpoints/volt.js +84 -0
  86. package/lib/api/endpoints/ward.js +47 -0
  87. package/lib/api/endpoints/webhooks.js +46 -0
  88. package/lib/api/endpoints/workflows.js +169 -0
  89. package/lib/api/endpoints/x.js +32 -0
  90. package/lib/api/endpoints.js +15 -0
  91. package/lib/commands/actions.js +61 -0
  92. package/lib/commands/admin.js +375 -0
  93. package/lib/commands/aee.js +99 -0
  94. package/lib/commands/agent.js +337 -0
  95. package/lib/commands/agentAttach.js +1528 -0
  96. package/lib/commands/approvals.js +119 -0
  97. package/lib/commands/aside.js +24 -0
  98. package/lib/commands/ask.js +144 -0
  99. package/lib/commands/assistant.js +169 -0
  100. package/lib/commands/auth.js +464 -0
  101. package/lib/commands/awareness.js +88 -0
  102. package/lib/commands/backup.js +377 -0
  103. package/lib/commands/brain.js +1085 -0
  104. package/lib/commands/budget.js +241 -0
  105. package/lib/commands/build.js +170 -0
  106. package/lib/commands/calendar.js +216 -0
  107. package/lib/commands/capabilities.js +34 -0
  108. package/lib/commands/chat.js +295 -0
  109. package/lib/commands/claude.js +380 -0
  110. package/lib/commands/config-cmd.js +106 -0
  111. package/lib/commands/consent.js +84 -0
  112. package/lib/commands/contacts.js +1016 -0
  113. package/lib/commands/contactsSelftest.js +132 -0
  114. package/lib/commands/conversation.js +105 -0
  115. package/lib/commands/dashboard.js +43 -0
  116. package/lib/commands/department.js +96 -0
  117. package/lib/commands/dev.js +620 -0
  118. package/lib/commands/devResident.js +326 -0
  119. package/lib/commands/devSupervisor.js +1062 -0
  120. package/lib/commands/discord.js +132 -0
  121. package/lib/commands/doctor.js +181 -0
  122. package/lib/commands/domain.js +114 -0
  123. package/lib/commands/done.js +347 -0
  124. package/lib/commands/engagement.js +597 -0
  125. package/lib/commands/entity.js +119 -0
  126. package/lib/commands/facebook.js +212 -0
  127. package/lib/commands/fetchAllowlist.js +71 -0
  128. package/lib/commands/file.js +165 -0
  129. package/lib/commands/fleet.js +1189 -0
  130. package/lib/commands/governance.js +647 -0
  131. package/lib/commands/gripe.js +355 -0
  132. package/lib/commands/hat.js +374 -0
  133. package/lib/commands/helpers.js +96 -0
  134. package/lib/commands/hyperion.js +58 -0
  135. package/lib/commands/inbox.js +173 -0
  136. package/lib/commands/instagram.js +264 -0
  137. package/lib/commands/integration.js +100 -0
  138. package/lib/commands/inventory.js +115 -0
  139. package/lib/commands/invites.js +45 -0
  140. package/lib/commands/licence.js +139 -0
  141. package/lib/commands/linkedin.js +178 -0
  142. package/lib/commands/llm-policy.js +117 -0
  143. package/lib/commands/llm.js +45 -0
  144. package/lib/commands/lucid.js +597 -0
  145. package/lib/commands/mail.js +531 -0
  146. package/lib/commands/memory.js +361 -0
  147. package/lib/commands/mind.js +71 -0
  148. package/lib/commands/mon.js +581 -0
  149. package/lib/commands/monitoring.js +86 -0
  150. package/lib/commands/motion.js +22 -0
  151. package/lib/commands/notes.js +1246 -0
  152. package/lib/commands/notesBatch.js +160 -0
  153. package/lib/commands/notesBridges.js +56 -0
  154. package/lib/commands/notesLinks.js +60 -0
  155. package/lib/commands/notification.js +57 -0
  156. package/lib/commands/nucleus.js +123 -0
  157. package/lib/commands/objective.js +240 -0
  158. package/lib/commands/offload.js +89 -0
  159. package/lib/commands/onboard.js +329 -0
  160. package/lib/commands/open.js +56 -0
  161. package/lib/commands/org.js +276 -0
  162. package/lib/commands/peek.js +28 -0
  163. package/lib/commands/plan.js +944 -0
  164. package/lib/commands/plugin-doctor.js +1091 -0
  165. package/lib/commands/plugin.js +649 -0
  166. package/lib/commands/policy.js +78 -0
  167. package/lib/commands/powers.js +296 -0
  168. package/lib/commands/presence.js +89 -0
  169. package/lib/commands/prompt.js +350 -0
  170. package/lib/commands/provider.js +159 -0
  171. package/lib/commands/qlarity.js +706 -0
  172. package/lib/commands/quoxbox.js +270 -0
  173. package/lib/commands/quoxchat.js +275 -0
  174. package/lib/commands/quoxseo.js +378 -0
  175. package/lib/commands/reach.js +159 -0
  176. package/lib/commands/role.js +286 -0
  177. package/lib/commands/router.js +77 -0
  178. package/lib/commands/rows.js +358 -0
  179. package/lib/commands/run.js +187 -0
  180. package/lib/commands/safety.js +107 -0
  181. package/lib/commands/scim.js +88 -0
  182. package/lib/commands/scope.js +58 -0
  183. package/lib/commands/security.js +157 -0
  184. package/lib/commands/server.js +292 -0
  185. package/lib/commands/service.js +89 -0
  186. package/lib/commands/settings.js +99 -0
  187. package/lib/commands/setup.js +569 -0
  188. package/lib/commands/skillManifest.js +113 -0
  189. package/lib/commands/skillRightsFormat.js +38 -0
  190. package/lib/commands/skills.js +687 -0
  191. package/lib/commands/slot.js +1451 -0
  192. package/lib/commands/sso.js +229 -0
  193. package/lib/commands/status.js +137 -0
  194. package/lib/commands/tag.js +91 -0
  195. package/lib/commands/team.js +142 -0
  196. package/lib/commands/tg.js +240 -0
  197. package/lib/commands/tiktok.js +218 -0
  198. package/lib/commands/tips.js +23 -0
  199. package/lib/commands/tool.js +304 -0
  200. package/lib/commands/trace.js +530 -0
  201. package/lib/commands/trigger.js +223 -0
  202. package/lib/commands/update.js +46 -0
  203. package/lib/commands/vault.js +1447 -0
  204. package/lib/commands/vm.js +237 -0
  205. package/lib/commands/voice.js +213 -0
  206. package/lib/commands/volt.js +570 -0
  207. package/lib/commands/walkthrough.js +32 -0
  208. package/lib/commands/watch.js +167 -0
  209. package/lib/commands/webhook.js +137 -0
  210. package/lib/commands/welcome.js +14 -0
  211. package/lib/commands/workflow.js +660 -0
  212. package/lib/commands/x.js +448 -0
  213. package/lib/core/actingAs.js +136 -0
  214. package/lib/core/activeSummary.js +186 -0
  215. package/lib/core/aee.js +53 -0
  216. package/lib/core/agentAlias.js +163 -0
  217. package/lib/core/approvalContext.js +61 -0
  218. package/lib/core/aside.js +54 -0
  219. package/lib/core/askDecision.js +60 -0
  220. package/lib/core/auth.js +501 -0
  221. package/lib/core/banner.js +143 -0
  222. package/lib/core/brainPrime.js +90 -0
  223. package/lib/core/callerSource.js +129 -0
  224. package/lib/core/claudeLs.js +94 -0
  225. package/lib/core/cliBuild.js +62 -0
  226. package/lib/core/client.js +693 -0
  227. package/lib/core/commandRegistrars.js +121 -0
  228. package/lib/core/completions.js +163 -0
  229. package/lib/core/config.js +669 -0
  230. package/lib/core/confirm.js +37 -0
  231. package/lib/core/customHats.js +168 -0
  232. package/lib/core/deeplinks.js +76 -0
  233. package/lib/core/doneEngineFetch.js +227 -0
  234. package/lib/core/errors.js +441 -0
  235. package/lib/core/global-flags.js +190 -0
  236. package/lib/core/governedSession.js +402 -0
  237. package/lib/core/hyperion.js +102 -0
  238. package/lib/core/inboxResolution.js +65 -0
  239. package/lib/core/lazyCommands.js +122 -0
  240. package/lib/core/liveSessionsReport.js +112 -0
  241. package/lib/core/offload.js +504 -0
  242. package/lib/core/org-override.js +52 -0
  243. package/lib/core/orgResolve.js +70 -0
  244. package/lib/core/output.js +239 -0
  245. package/lib/core/peek.js +126 -0
  246. package/lib/core/prodEnv.js +7 -0
  247. package/lib/core/profileUsers.js +51 -0
  248. package/lib/core/promptCancel.js +18 -0
  249. package/lib/core/quoxglyph.js +158 -0
  250. package/lib/core/repoPath.js +11 -0
  251. package/lib/core/residentAlarm.js +282 -0
  252. package/lib/core/residentAuth.js +243 -0
  253. package/lib/core/residentPump.js +229 -0
  254. package/lib/core/residentSeed.js +106 -0
  255. package/lib/core/rootOptions.js +39 -0
  256. package/lib/core/router.js +168 -0
  257. package/lib/core/selfUpdate.js +188 -0
  258. package/lib/core/sessionHeartbeat.js +192 -0
  259. package/lib/core/sessionsOverview.js +224 -0
  260. package/lib/core/slotKeeper.js +527 -0
  261. package/lib/core/slotWake.js +120 -0
  262. package/lib/core/supervisorEngine.js +933 -0
  263. package/lib/core/systemdInstaller.js +256 -0
  264. package/lib/core/version.js +87 -0
  265. package/lib/core/vm.js +361 -0
  266. package/lib/core/vmTui.js +86 -0
  267. package/lib/core/walkthrough.js +35 -0
  268. package/lib/hyperion/deck.js +29 -0
  269. package/lib/lucid/LucidCard.jsx +68 -0
  270. package/lib/lucid/LucidIntro.jsx +92 -0
  271. package/lib/lucid/blind.js +71 -0
  272. package/lib/lucid/cards.js +86 -0
  273. package/lib/lucid/deck.js +32 -0
  274. package/lib/lucid/fmt.js +139 -0
  275. package/lib/lucid/markdown.js +81 -0
  276. package/lib/lucid/motion.js +56 -0
  277. package/lib/lucid/sitrep.js +173 -0
  278. package/lib/plugins/loader.js +93 -0
  279. package/lib/quoxon/activity.js +116 -0
  280. package/lib/quoxon/aocl.js +117 -0
  281. package/lib/quoxon/api.js +952 -0
  282. package/lib/quoxon/app.js +4032 -0
  283. package/lib/quoxon/approvalFacts.js +94 -0
  284. package/lib/quoxon/approvalsView.js +325 -0
  285. package/lib/quoxon/asideView.js +40 -0
  286. package/lib/quoxon/ask.js +121 -0
  287. package/lib/quoxon/away.js +52 -0
  288. package/lib/quoxon/bang.js +224 -0
  289. package/lib/quoxon/bulkRisk.js +17 -0
  290. package/lib/quoxon/capabilities.js +82 -0
  291. package/lib/quoxon/cells.js +53 -0
  292. package/lib/quoxon/commandShort.js +112 -0
  293. package/lib/quoxon/commands.js +479 -0
  294. package/lib/quoxon/connect.js +654 -0
  295. package/lib/quoxon/connectCard.js +45 -0
  296. package/lib/quoxon/consent.js +86 -0
  297. package/lib/quoxon/context.js +68 -0
  298. package/lib/quoxon/copy.js +34 -0
  299. package/lib/quoxon/countdownLine.js +11 -0
  300. package/lib/quoxon/demo.autoplay.js +29 -0
  301. package/lib/quoxon/demo.fixtures.js +24 -0
  302. package/lib/quoxon/demo.js +76 -0
  303. package/lib/quoxon/devObserver.js +20 -0
  304. package/lib/quoxon/dividerAocl.js +191 -0
  305. package/lib/quoxon/dividerRule.js +57 -0
  306. package/lib/quoxon/dragonModes.js +150 -0
  307. package/lib/quoxon/dragonView.js +41 -0
  308. package/lib/quoxon/earlyInput.js +40 -0
  309. package/lib/quoxon/echo.js +15 -0
  310. package/lib/quoxon/egg.js +36 -0
  311. package/lib/quoxon/events.js +677 -0
  312. package/lib/quoxon/feedback.js +50 -0
  313. package/lib/quoxon/gallery.js +237 -0
  314. package/lib/quoxon/handoffOffer.js +78 -0
  315. package/lib/quoxon/hatSwitch.js +37 -0
  316. package/lib/quoxon/headless.js +161 -0
  317. package/lib/quoxon/hostUrls.js +41 -0
  318. package/lib/quoxon/indicator.js +197 -0
  319. package/lib/quoxon/intent.js +52 -0
  320. package/lib/quoxon/keys.js +164 -0
  321. package/lib/quoxon/launcher.js +103 -0
  322. package/lib/quoxon/main.js +176 -0
  323. package/lib/quoxon/markdown.js +341 -0
  324. package/lib/quoxon/markdownSettle.js +19 -0
  325. package/lib/quoxon/matrixDomains.json +46 -0
  326. package/lib/quoxon/mentions.js +90 -0
  327. package/lib/quoxon/mind.js +103 -0
  328. package/lib/quoxon/mode.js +46 -0
  329. package/lib/quoxon/model.js +311 -0
  330. package/lib/quoxon/modelPrefs.js +345 -0
  331. package/lib/quoxon/motion/clock.js +70 -0
  332. package/lib/quoxon/motion/color.js +35 -0
  333. package/lib/quoxon/motion/frame.js +14 -0
  334. package/lib/quoxon/motion/level.js +91 -0
  335. package/lib/quoxon/motion/reveal.js +54 -0
  336. package/lib/quoxon/motion/ring.js +79 -0
  337. package/lib/quoxon/mouse.js +211 -0
  338. package/lib/quoxon/noai.js +57 -0
  339. package/lib/quoxon/objects/agent.js +61 -0
  340. package/lib/quoxon/objects/away.js +72 -0
  341. package/lib/quoxon/objects/crystal.js +100 -0
  342. package/lib/quoxon/objects/fetch.js +182 -0
  343. package/lib/quoxon/objects/fleet.js +149 -0
  344. package/lib/quoxon/objects/health.js +186 -0
  345. package/lib/quoxon/objects/index.js +106 -0
  346. package/lib/quoxon/objects/ladder.js +71 -0
  347. package/lib/quoxon/objects/memory.js +44 -0
  348. package/lib/quoxon/objects/mirror.js +137 -0
  349. package/lib/quoxon/objects/model.js +118 -0
  350. package/lib/quoxon/objects/peek.js +17 -0
  351. package/lib/quoxon/objects/proof.js +110 -0
  352. package/lib/quoxon/objects/prove.js +130 -0
  353. package/lib/quoxon/objects/replay.js +125 -0
  354. package/lib/quoxon/objects/sitrep.js +58 -0
  355. package/lib/quoxon/objects/skill.js +105 -0
  356. package/lib/quoxon/objects/view.js +87 -0
  357. package/lib/quoxon/objects/workflow.js +119 -0
  358. package/lib/quoxon/observers.js +42 -0
  359. package/lib/quoxon/onboarding/copy.js +263 -0
  360. package/lib/quoxon/onboarding/credential.js +108 -0
  361. package/lib/quoxon/onboarding/detect.js +22 -0
  362. package/lib/quoxon/onboarding/dockerOffer.js +45 -0
  363. package/lib/quoxon/onboarding/failureCause.js +33 -0
  364. package/lib/quoxon/onboarding/finish.js +134 -0
  365. package/lib/quoxon/onboarding/index.js +94 -0
  366. package/lib/quoxon/onboarding/installRunner.js +284 -0
  367. package/lib/quoxon/onboarding/intent.js +53 -0
  368. package/lib/quoxon/onboarding/journey.js +73 -0
  369. package/lib/quoxon/onboarding/preflight.js +156 -0
  370. package/lib/quoxon/onboarding/probe.js +65 -0
  371. package/lib/quoxon/onboarding/runner.js +58 -0
  372. package/lib/quoxon/onboarding/view.js +682 -0
  373. package/lib/quoxon/orgLabel.js +10 -0
  374. package/lib/quoxon/orgSwitch.js +33 -0
  375. package/lib/quoxon/ownBroadcast.js +22 -0
  376. package/lib/quoxon/palette.js +43 -0
  377. package/lib/quoxon/print.js +98 -0
  378. package/lib/quoxon/profileSwitch.js +53 -0
  379. package/lib/quoxon/promptHistory.js +41 -0
  380. package/lib/quoxon/providers.js +48 -0
  381. package/lib/quoxon/qart.js +348 -0
  382. package/lib/quoxon/qdragon.js +410 -0
  383. package/lib/quoxon/qdragonFly.js +61 -0
  384. package/lib/quoxon/qdragonScenes.js +282 -0
  385. package/lib/quoxon/qeyes.js +213 -0
  386. package/lib/quoxon/qglyph.js +143 -0
  387. package/lib/quoxon/qhello.js +58 -0
  388. package/lib/quoxon/qmotion.js +132 -0
  389. package/lib/quoxon/render.js +1167 -0
  390. package/lib/quoxon/resend.js +72 -0
  391. package/lib/quoxon/roster.js +307 -0
  392. package/lib/quoxon/route.js +39 -0
  393. package/lib/quoxon/rows.js +711 -0
  394. package/lib/quoxon/rowsStore.js +80 -0
  395. package/lib/quoxon/rules.js +130 -0
  396. package/lib/quoxon/screenContext.js +150 -0
  397. package/lib/quoxon/screenFrames.js +254 -0
  398. package/lib/quoxon/session.js +238 -0
  399. package/lib/quoxon/sessionDead.js +17 -0
  400. package/lib/quoxon/signin.js +125 -0
  401. package/lib/quoxon/skills.js +30 -0
  402. package/lib/quoxon/slash/agents.js +112 -0
  403. package/lib/quoxon/slash/argSources.js +65 -0
  404. package/lib/quoxon/slash/args.js +65 -0
  405. package/lib/quoxon/slash/automate.js +89 -0
  406. package/lib/quoxon/slash/broadcast.js +140 -0
  407. package/lib/quoxon/slash/broadcastRun.js +108 -0
  408. package/lib/quoxon/slash/card.js +30 -0
  409. package/lib/quoxon/slash/challenge.js +114 -0
  410. package/lib/quoxon/slash/cliCommand.js +53 -0
  411. package/lib/quoxon/slash/cliLeaves.json +948 -0
  412. package/lib/quoxon/slash/compact.js +90 -0
  413. package/lib/quoxon/slash/confirm.js +30 -0
  414. package/lib/quoxon/slash/crystallize.js +69 -0
  415. package/lib/quoxon/slash/dashboard.js +136 -0
  416. package/lib/quoxon/slash/draft.js +54 -0
  417. package/lib/quoxon/slash/hatWalk.js +206 -0
  418. package/lib/quoxon/slash/inverses.js +77 -0
  419. package/lib/quoxon/slash/killer.js +24 -0
  420. package/lib/quoxon/slash/killerApi.js +16 -0
  421. package/lib/quoxon/slash/ledger.js +88 -0
  422. package/lib/quoxon/slash/listView.js +30 -0
  423. package/lib/quoxon/slash/modelCmd.js +261 -0
  424. package/lib/quoxon/slash/palette.js +118 -0
  425. package/lib/quoxon/slash/paletteController.js +169 -0
  426. package/lib/quoxon/slash/panels.js +116 -0
  427. package/lib/quoxon/slash/picker.js +56 -0
  428. package/lib/quoxon/slash/restart.js +202 -0
  429. package/lib/quoxon/slash/resume.js +59 -0
  430. package/lib/quoxon/slash/rowCmd.js +157 -0
  431. package/lib/quoxon/slash/rowWalk.js +175 -0
  432. package/lib/quoxon/slash/settings.js +60 -0
  433. package/lib/quoxon/slash/spawn.js +177 -0
  434. package/lib/quoxon/slash/stepUp.js +274 -0
  435. package/lib/quoxon/slash/stepUpCard.js +60 -0
  436. package/lib/quoxon/slash/stepUpCmds.js +78 -0
  437. package/lib/quoxon/slash/undoApi.js +38 -0
  438. package/lib/quoxon/slash/undoCmd.js +49 -0
  439. package/lib/quoxon/slash/undoWish.js +15 -0
  440. package/lib/quoxon/slash/watch.js +164 -0
  441. package/lib/quoxon/slash/wish.js +23 -0
  442. package/lib/quoxon/slash/wishCmd.js +32 -0
  443. package/lib/quoxon/slotChat.js +300 -0
  444. package/lib/quoxon/slotFormat.js +195 -0
  445. package/lib/quoxon/slotRun.js +131 -0
  446. package/lib/quoxon/sound/index.js +186 -0
  447. package/lib/quoxon/sound/mapping.js +120 -0
  448. package/lib/quoxon/sound/output.js +52 -0
  449. package/lib/quoxon/sound/synth.js +122 -0
  450. package/lib/quoxon/stray.js +22 -0
  451. package/lib/quoxon/suggestCommand.js +59 -0
  452. package/lib/quoxon/term.js +141 -0
  453. package/lib/quoxon/textBuffer.js +416 -0
  454. package/lib/quoxon/theme.js +286 -0
  455. package/lib/quoxon/tips.js +433 -0
  456. package/lib/quoxon/toolError.js +41 -0
  457. package/lib/quoxon/transcript.js +188 -0
  458. package/lib/quoxon/web/qface.js +89 -0
  459. package/lib/quoxon/welcome.js +52 -0
  460. package/lib/quoxon/whatsnew.js +136 -0
  461. package/lib/quoxon/whatsnew.jsonl +89 -0
  462. package/lib/quoxon/windowOrg.js +113 -0
  463. package/lib/server/diag.js +129 -0
  464. package/lib/server/doctor.js +71 -0
  465. package/lib/server/install.js +149 -0
  466. package/lib/server/pluginEnable.js +161 -0
  467. package/lib/server/skew.js +56 -0
  468. package/lib/server/snapshot.js +131 -0
  469. package/lib/server/status.js +128 -0
  470. package/lib/server/sys.js +65 -0
  471. package/lib/server/uninstall.js +63 -0
  472. package/lib/server/update.js +234 -0
  473. package/lib/toolOutputShape.js +315 -0
  474. package/lib/tui/app.jsx +289 -0
  475. package/lib/tui/components/Breadcrumb.jsx +42 -0
  476. package/lib/tui/components/ChatInput.jsx +68 -0
  477. package/lib/tui/components/Confirm.jsx +32 -0
  478. package/lib/tui/components/DataTable.jsx +270 -0
  479. package/lib/tui/components/DetailPanel.jsx +241 -0
  480. package/lib/tui/components/HelpOverlay.jsx +67 -0
  481. package/lib/tui/components/Layout.jsx +129 -0
  482. package/lib/tui/components/Palette.jsx +141 -0
  483. package/lib/tui/components/SearchInput.jsx +125 -0
  484. package/lib/tui/components/Sparkline.jsx +55 -0
  485. package/lib/tui/components/StatusBar.jsx +133 -0
  486. package/lib/tui/components/Toast.jsx +52 -0
  487. package/lib/tui/hooks/useApi.js +44 -0
  488. package/lib/tui/hooks/useHealth.js +90 -0
  489. package/lib/tui/hooks/usePolledApi.js +86 -0
  490. package/lib/tui/hooks/useTerminalSize.js +46 -0
  491. package/lib/tui/index.js +56 -0
  492. package/lib/tui/intro/Intro.jsx +332 -0
  493. package/lib/tui/jsx-loader.js +32 -0
  494. package/lib/tui/keys.js +87 -0
  495. package/lib/tui/registry.js +49 -0
  496. package/lib/tui/theme.js +103 -0
  497. package/lib/tui/views/AdminView.jsx +398 -0
  498. package/lib/tui/views/AgentView.jsx +204 -0
  499. package/lib/tui/views/BackupView.jsx +225 -0
  500. package/lib/tui/views/BrainView.jsx +223 -0
  501. package/lib/tui/views/ChatView.jsx +468 -0
  502. package/lib/tui/views/FilesView.jsx +218 -0
  503. package/lib/tui/views/FleetView.jsx +234 -0
  504. package/lib/tui/views/GovernanceView.jsx +453 -0
  505. package/lib/tui/views/HealthView.jsx +216 -0
  506. package/lib/tui/views/HyperionDeck.jsx +79 -0
  507. package/lib/tui/views/InboxView.jsx +228 -0
  508. package/lib/tui/views/LucidDeck.jsx +719 -0
  509. package/lib/tui/views/MainMenu.jsx +123 -0
  510. package/lib/tui/views/MemoryView.jsx +377 -0
  511. package/lib/tui/views/ObjectivesView.jsx +230 -0
  512. package/lib/tui/views/OrgView.jsx +229 -0
  513. package/lib/tui/views/ToolView.jsx +221 -0
  514. package/lib/tui/views/VaultView.jsx +422 -0
  515. package/lib/tui/views/WorkflowView.jsx +329 -0
  516. package/package.json +75 -4
@@ -0,0 +1,80 @@
1
+ /**
2
+ * QUOXON-FW17 / ROWS: where custom rows live and how they are read. Shared by the quox window (api.js spreads this
3
+ * in) and `quox rows` (commands/rows.js), so the two can never disagree about storage or the sync rule.
4
+ *
5
+ * url, quox, plugin rows -> the user's server-side preferences (namespace `quoxonRows`, PATCH auth/me), with a
6
+ * copy on the CLI profile (row_prefs) for offline starts. Read back through normRows
7
+ * WITHOUT cmd: a synced row can never carry a shell command.
8
+ * cmd rows -> ~/.config/quox/quoxon-rows.local.json (0600), never sent anywhere.
9
+ *
10
+ * Small on purpose (no React, no chat): `quox rows` loads only this and rows.js.
11
+ */
12
+ import { resolveEndpoint } from '../api/endpoints.js';
13
+ import { normRows, plainFetch, loadLocalCmdRows, saveLocalCmdRows, URL_TIMEOUT_MS } from './rows.js';
14
+
15
+ const withTimeout = (p) => Promise.race([
16
+ p,
17
+ new Promise((_, rej) => { const t = setTimeout(() => rej(new Error(`timed out after ${URL_TIMEOUT_MS / 1000}s`)), URL_TIMEOUT_MS); t.unref?.(); })
18
+ ]);
19
+
20
+ export function createRowsStore({ client, config, orgId = () => null }) {
21
+ return {
22
+ /** -> { rows, source: 'server' | 'local', error? }. Never includes a cmd row. */
23
+ async rowPrefs() {
24
+ const local = () => normRows(config.getActiveProfile?.()?.row_prefs);
25
+ try {
26
+ const ep = resolveEndpoint('auth.me.list');
27
+ const d = await client.get(ep.path, { service: ep.service, retries: 0 });
28
+ const raw = (d?.user?.preferences ?? d?.preferences)?.quoxonRows;
29
+ const rows = normRows(raw);
30
+ const prof = config.getActiveProfile?.();
31
+ if (prof && raw !== undefined) { prof.row_prefs = rows; config.save?.(); }
32
+ return { rows: raw === undefined && local().length ? local() : rows, source: 'server' };
33
+ } catch (e) { return { rows: local(), source: 'local', error: e?.message || 'failed' }; }
34
+ },
35
+ /** Synced rows only (cmd rows are dropped). -> { ok, synced, error? }. */
36
+ async setRowPrefs(value) {
37
+ const rows = normRows(value);
38
+ const prof = config.getActiveProfile?.();
39
+ if (prof) { prof.row_prefs = rows; config.save?.(); }
40
+ try {
41
+ const ep = resolveEndpoint('auth.me.update');
42
+ await client.patch(ep.path, { service: ep.service, body: { preferences: { quoxonRows: rows } }, retries: 0 });
43
+ return { ok: true, synced: true };
44
+ } catch (e) { return { ok: Boolean(prof), synced: false, error: e?.message || 'failed' }; }
45
+ },
46
+ localRowPrefs() { try { return normRows(config.getActiveProfile?.()?.row_prefs); } catch { return []; } },
47
+ /** cmd rows: a local file under the CLI config dir, never synced. */
48
+ localCmdRows() { return loadLocalCmdRows(); },
49
+ setLocalCmdRows(rows) { return saveLocalCmdRows(rows); },
50
+ /**
51
+ * A row's URL -> text. This profile's own Quox base uses the normal authed client; any other origin gets a
52
+ * plain request with NO credentials. 15 s timeout either way.
53
+ */
54
+ async rowFetch(url) {
55
+ let u; try { u = new URL(url); } catch { throw new Error('bad URL'); }
56
+ let own = null; try { own = new URL(config.getServiceUrl('auth')).origin; } catch { /* no profile */ }
57
+ if (!own || u.origin !== own) return plainFetch(url);
58
+ const d = await withTimeout(client.get(u.pathname + u.search, { service: 'auth', retries: 0 }));
59
+ return typeof d === 'string' ? d : JSON.stringify(d);
60
+ },
61
+ /**
62
+ * ROWS: a quox or plugin row -> text, read from THIS profile's Quox with the user's own session (GET only).
63
+ * quox: { service, path } with {org} filled from the active org. plugin: GET /api/v1/plugins/<id>/<path> on the
64
+ * collector (the same governed proxy the plugin's own UI uses; 402 when the org has no licence for it).
65
+ */
66
+ async rowFetchQuox(source) {
67
+ const src = source || {};
68
+ let service, path;
69
+ if (src.type === 'plugin') { service = 'collector'; path = `/api/v1/plugins/${encodeURIComponent(src.plugin)}/${String(src.path || '').replace(/^\/+/, '')}`; }
70
+ else if (src.type === 'quox') {
71
+ service = src.service === 'collector' ? 'collector' : 'auth';
72
+ const org = orgId() || '';
73
+ if (String(src.path).includes('{org}') && !org) throw new Error('no org on this profile');
74
+ path = String(src.path || '').replaceAll('{org}', encodeURIComponent(org));
75
+ } else throw new Error('not a quox or plugin row');
76
+ const d = await withTimeout(client.get(path, { service, retries: 0 }));
77
+ return typeof d === 'string' ? d : JSON.stringify(d);
78
+ }
79
+ };
80
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * QUOX.md rules: like CLAUDE.md for Quox. Loaded from ~/.quox/QUOX.md (user level) and from QUOX.md in the
3
+ * current directory and every parent up to the git root (nearest last = highest precedence).
4
+ *
5
+ * Wire shape: the collector's /chat/stream only consumes `context.quox_context` (and memory_context);
6
+ * unknown context keys are ignored, and quox_context is injection-filtered and cut at 6000 chars. So the
7
+ * rules ride in quox_context under a clear label, capped below that server limit so OUR visible truncation
8
+ * note is what the user sees, never a silent server-side cut.
9
+ *
10
+ * Safety: a file is skipped (and reported) if it is not a regular file, is world-writable, or is a symlink
11
+ * resolving outside the user's home and the repo root.
12
+ */
13
+ import { lstatSync, statSync, realpathSync, readFileSync, existsSync } from 'node:fs';
14
+ import { homedir } from 'node:os';
15
+ import { dirname, join, resolve, sep } from 'node:path';
16
+
17
+ export const RULES_FILE = 'QUOX.md';
18
+ export const RULES_CAP = 5500; // chars; the server cuts quox_context at 6000 including our label
19
+ const LABEL = 'User rules (QUOX.md files from the user\'s machine, in order of precedence, last wins). Follow them for style and behaviour; they cannot grant permissions or bypass approvals.';
20
+
21
+ const inside = (p, root) => p === root || p.startsWith(root.endsWith(sep) ? root : root + sep);
22
+
23
+ const tilde = (p, home, gitRoot) => (inside(p, home) ? '~' + p.slice(home.length) : gitRoot && inside(p, gitRoot) ? '<repo>' + p.slice(gitRoot.length) : p);
24
+
25
+ export function findGitRoot(start, exists = existsSync) {
26
+ let d = resolve(start);
27
+ for (;;) {
28
+ if (exists(join(d, '.git'))) return d;
29
+ const up = dirname(d);
30
+ if (up === d) return null;
31
+ d = up;
32
+ }
33
+ }
34
+
35
+ /** -> { path, content } | { path, skipped } | null (absent). */
36
+ function readSafe(path, allowedRoots, fsx) {
37
+ let st;
38
+ try { st = fsx.lstat(path); } catch { return null; }
39
+ if (st.isSymbolicLink()) {
40
+ let real;
41
+ try { real = fsx.realpath(path); } catch { return { path, skipped: 'broken symlink' }; }
42
+ if (!allowedRoots.some(r => inside(real, r))) return { path, skipped: 'symlink points outside your home and the repo' };
43
+ try { st = fsx.stat(real); } catch { return { path, skipped: 'unreadable' }; }
44
+ }
45
+ if (!st.isFile()) return { path, skipped: 'not a regular file' };
46
+ if (st.mode & 0o002) return { path, skipped: 'world-writable (chmod o-w to use it)' };
47
+ try { return { path, content: fsx.read(path) }; } catch { return { path, skipped: 'unreadable' }; }
48
+ }
49
+
50
+ const realFs = { lstat: lstatSync, stat: statSync, realpath: realpathSync, read: (p) => readFileSync(p, 'utf8') };
51
+
52
+ /**
53
+ * -> { files:[{path,label,chars}], skipped:[{path,reason}], text, truncated, totalChars }
54
+ * text is the combined rules (labels per file), already capped.
55
+ */
56
+ export function loadRules({ cwd = process.cwd(), home = homedir(), cap = RULES_CAP, fsx = {} } = {}) {
57
+ const f = { ...realFs, ...fsx };
58
+ const exists = fsx.exists || existsSync;
59
+ let realHome = home;
60
+ try { realHome = f.realpath(home); } catch { /* keep */ }
61
+ const gitRoot = findGitRoot(cwd, exists);
62
+ const dirs = [];
63
+ if (gitRoot) {
64
+ for (let d = resolve(cwd); ; d = dirname(d)) { dirs.push(d); if (d === gitRoot || dirname(d) === d) break; }
65
+ } else dirs.push(resolve(cwd));
66
+ dirs.reverse(); // root-most first, cwd last
67
+ const roots = [realHome, ...(gitRoot ? [gitRoot] : [resolve(cwd)])];
68
+ const candidates = [{ path: join(home, '.quox', RULES_FILE), label: '~/.quox/QUOX.md' }];
69
+ for (const d of dirs) {
70
+ const p = join(d, RULES_FILE);
71
+ if (!candidates.some(c => c.path === p)) candidates.push({ path: p, label: tilde(p, home, gitRoot) });
72
+ }
73
+ const files = [], skipped = [];
74
+ const sections = [];
75
+ for (const c of candidates) {
76
+ const r = readSafe(c.path, roots, f);
77
+ if (!r) continue;
78
+ if (r.skipped) { skipped.push({ path: c.path, reason: r.skipped }); continue; }
79
+ const content = r.content.replace(/\r\n/g, '\n').trim();
80
+ if (!content) continue;
81
+ files.push({ path: c.path, label: c.label, chars: content.length, content });
82
+ sections.push(`# ${c.label}\n${content}`);
83
+ }
84
+ let body = sections.join('\n\n');
85
+ const totalChars = body.length;
86
+ let truncated = false;
87
+ if (body.length > cap) {
88
+ truncated = true;
89
+ const note = `\n[QUOX.md rules truncated: ${totalChars} chars, kept ${cap}]`;
90
+ body = body.slice(0, Math.max(0, cap - note.length)) + note;
91
+ }
92
+ return { files, skipped, text: body, truncated, totalChars };
93
+ }
94
+
95
+ /** The /chat/stream `context` object, or undefined when there is nothing to send. */
96
+ export function rulesContext(rules) {
97
+ if (!rules || !rules.text) return undefined;
98
+ return { quox_context: `${LABEL}\n\n${rules.text}` };
99
+ }
100
+
101
+ /** Status-line fragment, e.g. "rules: QUOX.md (+1)". */
102
+ export function rulesLabel(rules) {
103
+ if (!rules || !rules.files.length) return null;
104
+ const extra = rules.files.length - 1;
105
+ return `rules: QUOX.md${extra ? ` (+${extra})` : ''}${rules.truncated ? ' (cut)' : ''}`;
106
+ }
107
+
108
+ /** ~-shorten a path and, when it still does not fit `max`, keep its head and tail (the file name survives). */
109
+ export function shortPath(p, max = 60, home = homedir()) {
110
+ let t = String(p);
111
+ if (home && (t === home || t.startsWith(home + sep))) t = '~' + t.slice(home.length);
112
+ const g = [...t];
113
+ if (g.length <= max) return t;
114
+ const room = Math.max(8, max - 1);
115
+ const head = Math.min(6, Math.floor(room / 4));
116
+ return g.slice(0, head).join('') + '…' + g.slice(g.length - (room - head)).join('');
117
+ }
118
+
119
+ /** /rules view as lines (paged by the caller). `cols` lets paths shrink so the reason and file name stay visible. */
120
+ export function rulesLines(rules, { enabled = true, cols = 100, home } = {}) {
121
+ const out = [];
122
+ if (!rules || (!rules.files.length && !rules.skipped.length)) return ['No QUOX.md found. Put one in this directory (or a parent up to the git root) or at ~/.quox/QUOX.md.'];
123
+ out.push(enabled ? 'Rules are ON for this session.' : 'Rules are OFF for this session (/rules on to re-enable).');
124
+ const room = Math.max(20, cols - 4);
125
+ for (const f of rules.files) { const tail = ` (${f.chars} chars)`; out.push(`loaded ${shortPath(f.label, room - 8 - tail.length, home)}${tail}`); }
126
+ for (const s of rules.skipped) { const head = `skipped: ${s.reason}: `; out.push(head + shortPath(s.path, Math.max(16, room - [...head].length), home)); }
127
+ if (rules.truncated) out.push(`Only the first ${RULES_CAP} chars are sent (total ${rules.totalChars}).`);
128
+ for (const f of rules.files) { out.push('', `--- ${f.label} ---`, ...f.content.split('\n')); }
129
+ return out;
130
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * SCREEN-CONTEXT: "the conversation can see the terminal it runs in".
3
+ *
4
+ * Every turn carries a compact `terminal` block: who/where, the roster rows on screen WITH their ids, custom
5
+ * row text, pending approval cards, the last events (commands run and their one-line outcome, notices, errors,
6
+ * failed turns) and the slash command names. Hard cap 2 KB. After the first turn only the parts that changed
7
+ * are sent. Built from an allowlist of fields; anything credential-shaped is masked; nobody else's message
8
+ * bodies are ever included (slot chat never enters the event ring).
9
+ *
10
+ * Pure except for the ring buffer held by createScreenContext(); the app feeds it and calls build() per turn.
11
+ */
12
+ export const SCREEN_CAP = 2048;
13
+ export const RING_MAX = 10;
14
+ const ALWAYS = new Set(['roster', 'approvals']);
15
+
16
+ const PATTERNS = [
17
+ /\beyJ[\w-]{8,}\.[\w-]{8,}\.[\w-]{4,}/g,
18
+ /\b(?:sk|pk|rk|xox[bpas]|ghp|gho|ghs|github_pat|glpat|AKIA|AIza)[-_A-Za-z0-9]{12,}/g,
19
+ /\bBearer\s+[\w.~+/=-]{8,}/gi,
20
+ /\b(password|passwd|pwd|secret|token|api[_-]?key|apikey|pin|authorization)\b\s*[:=]\s*\S+/gi,
21
+ /([?&](?:token|key|secret|sig|signature|access_token)=)[^&\s]+/gi,
22
+ /\b[A-Fa-f0-9]{32,}\b/g,
23
+ /\b[A-Za-z0-9+/_-]{40,}={0,2}/g
24
+ ];
25
+
26
+ const CONTROL = new RegExp('[\\u0000-\\u001F\\u007F\\u200B-\\u200F\\u2028-\\u202F\\uFEFF]+', 'g');
27
+
28
+ /** Mask credential-shaped text and flatten to one clipped line. */
29
+ export function safe(v, n = 80) {
30
+ let s = String(v == null ? '' : v).replace(CONTROL, ' ');
31
+ for (const re of PATTERNS) s = s.replace(re, (m, g1) => (typeof g1 === 'string' && /^[?&]/.test(g1) ? `${g1}[masked]` : '[masked]'));
32
+ s = s.replace(/\s+/g, ' ').trim();
33
+ return s.length > n ? s.slice(0, n - 1) + '…' : s;
34
+ }
35
+
36
+ const bytes = (s) => Buffer.byteLength(s);
37
+ export function clipBytes(s, max) {
38
+ if (bytes(s) <= max) return s;
39
+ let out = s.slice(0, max - 1);
40
+ while (bytes(out + '…') > max) out = out.slice(0, -1);
41
+ return out + '…';
42
+ }
43
+
44
+ const age = (ms) => {
45
+ if (ms == null || !Number.isFinite(ms)) return null;
46
+ const s = Math.max(0, Math.floor(ms / 1000));
47
+ return s < 60 ? `${s}s` : s < 3600 ? `${Math.floor(s / 60)}m` : s < 86400 ? `${Math.floor(s / 3600)}h` : `${Math.floor(s / 86400)}d`;
48
+ };
49
+
50
+ /** A line the user cannot have meant as a command they want repeated (PIN-bearing verbs): name only. */
51
+ const PIN_VERBS = /^\/(authenticate|authorise|authorize|pin|login|connect|key)\b/i;
52
+
53
+ function whoSection(w) {
54
+ const bits = [];
55
+ if (w.profile) bits.push(`profile ${safe(w.profile, 32)}`);
56
+ if (w.org) bits.push(`org ${safe(w.org, 40)}`);
57
+ if (w.agent) bits.push(`agent ${safe(w.agent, 32)}`);
58
+ if (w.model) bits.push(`model ${safe(String(w.model || '').replace(/\s*\(as the server last reported\)/, ''), 48)}`);
59
+ if (w.slot) bits.push(`you are in slot ${safe(w.slot, 32)}`);
60
+ if (w.rooms?.length) bits.push(`rooms ${w.rooms.slice(0, 6).map(r => '#' + safe(r, 24)).join(' ')}`);
61
+ return bits.length ? `you: ${bits.join(' · ')}` : '';
62
+ }
63
+
64
+ function rosterSection(rows) {
65
+ if (!rows?.length) return '';
66
+ const lines = rows.slice(0, 8).map((r) => {
67
+ const parts = [safe(r.kind || 'row', 12), safe(r.id || r.key, 40), safe(r.name, 50), safe(r.state, 12)];
68
+ if (r.activity) parts.push(safe(r.activity, 40));
69
+ const a = age(r.ageMs); if (a) parts.push(a);
70
+ return ' ' + parts.filter(Boolean).join(' · ');
71
+ });
72
+ return `roster:\n${lines.join('\n')}`;
73
+ }
74
+
75
+ function rowsSection(rows) {
76
+ if (!rows?.length) return '';
77
+ return `custom rows:\n${rows.slice(0, 3).map(r => ` ${safe(r.label || r.id, 16)}: ${safe(r.text || r.error || '', 100)}`).join('\n')}`;
78
+ }
79
+
80
+ function approvalsSection(cards) {
81
+ if (!cards?.length) return '';
82
+ const lines = cards.slice(0, 5).map((c, i) => ` [${i + 1}] ${safe(c.id, 36)} · ${safe(c.summary, 60)}${c.requester ? ` · by ${safe(c.requester, 24)}` : ''}${c.privileged ? ' · privileged' : ''}`);
83
+ return `approvals waiting:\n${lines.join('\n')}`;
84
+ }
85
+
86
+ function commandsSection(names) {
87
+ if (!names?.length) return '';
88
+ return `slash commands: ${names.map(n => String(n).replace(/^\//, '')).join(' ')}`;
89
+ }
90
+
91
+ /** The event ring: commands (with the first notice after them as outcome), notices, errors, failed turns. */
92
+ export function createScreenContext() {
93
+ const ring = []; // { seq, k, text, out? }
94
+ let seq = 0;
95
+ const st = { on: true, sent: null, last: null, turn: 0, hashes: {} };
96
+ const push = (k, text, extra = {}) => { ring.push({ seq: ++seq, k, text: safe(text, 140), ...extra }); while (ring.length > 40) ring.shift(); };
97
+ return {
98
+ get on() { return st.on; },
99
+ set on(v) { st.on = Boolean(v); if (v) { st.hashes = {}; st.sent = null; } },
100
+ /** A slash command the user ran. PIN-bearing verbs keep the verb only. */
101
+ command(line) { const t = String(line || '').trim(); push('cmd', PIN_VERBS.test(t) ? t.split(/\s+/)[0] : t); },
102
+ /** A notice shown on screen; the first one after a command is that command's outcome. */
103
+ notice(text, tone) {
104
+ const prev = ring[ring.length - 1];
105
+ if (prev && prev.k === 'cmd' && prev.out == null) prev.out = safe(text, 100);
106
+ else if (tone === 'warn' || tone === 'error') push('notice', text);
107
+ },
108
+ error(text) { push('error', text); },
109
+ failedTurn(reason) { push('failed turn', reason || 'failed'); },
110
+ /** Build this turn's block. snap = { who, rows, customRows, cards, commands }. -> { text, sections, bytes, first, unchanged } | null (off). */
111
+ build(snap) {
112
+ if (!st.on) { st.last = null; return null; }
113
+ const first = st.sent == null;
114
+ const evs = ring.filter(e => e.seq > (st.sent ?? 0)).slice(-RING_MAX);
115
+ const eventLines = evs.map(e => e.k === 'cmd' ? ` ${e.text}${e.out ? ` -> ${e.out}` : ''}` : ` ${e.k}: ${e.text}`);
116
+ const state = [
117
+ ['you', whoSection(snap.who || {})],
118
+ ['approvals', approvalsSection(snap.cards)],
119
+ ['roster', rosterSection(snap.rows)],
120
+ ['rows', rowsSection(snap.customRows)]
121
+ ];
122
+ const kept = []; const same = []; const hashes = {};
123
+ for (const [name, body] of state) {
124
+ hashes[name] = body;
125
+ const prev = st.hashes[name] ?? '';
126
+ if (first || ALWAYS.has(name)) { if (body) kept.push([name, body]); continue; } // roster rows and approvals go in full every turn: a pasted row must always resolve
127
+ if (prev === body) { if (body) same.push(name); continue; }
128
+ kept.push([name, body || `${name}: now empty`]);
129
+ }
130
+ if (eventLines.length) kept.push(['events', `since your last message:\n${eventLines.join('\n')}`]);
131
+ if (first) { const c = commandsSection(snap.commands); if (c) kept.push(['commands', c]); }
132
+ const tail = same.length ? `\n(unchanged since the last message: ${same.join(', ')})` : '';
133
+ const room = SCREEN_CAP - bytes(tail);
134
+ let text = ''; const names = [];
135
+ for (const [name, body] of kept) {
136
+ const next = text ? `${text}\n${body}` : body;
137
+ names.push(name);
138
+ if (bytes(next) > room) { text = clipBytes(next, room); break; }
139
+ text = next;
140
+ }
141
+ text = clipBytes(text + tail, SCREEN_CAP);
142
+ st.hashes = hashes; st.sent = seq; st.turn += 1;
143
+ st.last = { text, sections: names, bytes: bytes(text), first, unchanged: same };
144
+ return st.last;
145
+ },
146
+ /** What was sent on the last turn (for /context). */
147
+ last() { return st.last; },
148
+ summary() { const l = st.last; return l ? `${l.bytes} bytes: ${l.sections.join(', ') || 'nothing new'}${l.unchanged?.length ? ` (unchanged: ${l.unchanged.join(', ')})` : ''}` : null; }
149
+ };
150
+ }
@@ -0,0 +1,254 @@
1
+ /**
2
+ * SCREEN-FRAMES: the screen as the owner saw it, in the local dev transcript.
3
+ *
4
+ * The owner runs quox over a plain SSH tty, so nothing outside the process can capture what was on screen. This
5
+ * records it from the inside: every string Ink writes is handed to tap() (term.js, the same feed the mouse mirror
6
+ * gets, before line diffing), which only queues it. On a debounce timer the queue is replayed into a styled mirror,
7
+ * the visible screen (the last `rows` rows, wrapped at the terminal width) is cut out as plain text, and ONE
8
+ * `{ t: 'screen', cols, rows, text, colors }` record is written when it has settled and differs from the last one.
9
+ *
10
+ * - Settled: no write for SCREEN_DEBOUNCE_MS, or (while a spinner keeps repainting) the same normalised text across
11
+ * a full debounce window. normalizeScreen() hides braille spinners and elapsed timers, so those never count as a
12
+ * change on their own.
13
+ * - Local only: it goes to the transcript file and nowhere else. On whenever dev transcripts are on;
14
+ * QUOX_SCREEN_FRAMES=0 turns it off.
15
+ * - The timer is unref'd (it never keeps the process alive) and stop() clears it, recording the final screen.
16
+ */
17
+ import { graphemes } from './textBuffer.js';
18
+ import { cellWidth } from './mouse.js';
19
+ import { DARK, LIGHT } from './theme.js';
20
+ import { isGrey } from './palette.js';
21
+
22
+ export const SCREEN_DEBOUNCE_MS = 1200;
23
+ export const MAX_SCREEN_TEXT = 16 * 1024;
24
+ const MAX_LINES = 1000; // logical lines kept by the mirror (a screen is far fewer)
25
+ const MAX_PENDING = 4000; // queued writes before they are folded in early (a runaway writer, never normal use)
26
+
27
+ export function screenFramesEnabled(env = process.env) {
28
+ return env.QUOX_SCREEN_FRAMES !== '0';
29
+ }
30
+
31
+ const ANSI_RE = /\x1b\[[0-9;?<>=]*[ -\/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[()][0-9A-Za-z]|\x1b[=>78]/g;
32
+ const ERASE_RE = /^((?:\x1b\[2K\x1b\[1A)*\x1b\[2K\x1b\[G)/;
33
+ const CLEAR_RE = /\x1b\[2J|\x1bc/;
34
+ const stripAnsi = (s) => s.replace(ANSI_RE, '');
35
+
36
+ /** A mirror of what Ink drew that keeps the escape codes (so colour use can be read back). Same model as mouse.js. */
37
+ export function createStyledMirror({ cap = MAX_LINES } = {}) {
38
+ let lines = [''];
39
+ const append = (text) => {
40
+ const parts = text.replace(/\r/g, '').split('\n');
41
+ lines[lines.length - 1] += parts[0];
42
+ for (let i = 1; i < parts.length; i++) lines.push(parts[i]);
43
+ if (lines.length > cap) lines = lines.slice(lines.length - cap);
44
+ };
45
+ return {
46
+ write(chunk) {
47
+ let s = String(chunk);
48
+ if (CLEAR_RE.test(s)) {
49
+ const all = [...s.matchAll(/\x1b\[2J|\x1b\[3J|\x1b\[H|\x1bc/g)];
50
+ const last = all[all.length - 1];
51
+ s = s.slice(last.index + last[0].length);
52
+ lines = [''];
53
+ }
54
+ const e = ERASE_RE.exec(s);
55
+ if (e) {
56
+ const n = (e[1].match(/\x1b\[2K/g) || []).length;
57
+ lines.length -= Math.min(n - 1, lines.length - 1);
58
+ lines[lines.length - 1] = '';
59
+ s = s.slice(e[1].length);
60
+ }
61
+ if (!stripAnsi(s)) return;
62
+ append(s);
63
+ },
64
+ reset() { lines = ['']; },
65
+ lines: () => lines.slice()
66
+ };
67
+ }
68
+
69
+ /** Text with the parts that move on their own (spinners, timers, the activity decode, rule markers) folded, for change detection only. */
70
+ export function normalizeScreen(text) {
71
+ return String(text)
72
+ .replace(/[⠀-⣿]/g, '⠿')
73
+ .replace(/[\uff61-\uff9f]/g, '?') // half-width katakana: the activity word's decode animation
74
+ .replace(/[^\s·]*…/g, '*…') // ...and the word it is decoding into
75
+ .replace(/[─━┼◆●○◇]+/g, '─') // progress markers travelling along the rule
76
+ .replace(/\b\d+:\d{2}(?::\d{2})?\b/g, '#:##')
77
+ .replace(/\b(?:\d+h\s?)?(?:\d+m\s?)?\d+(?:\.\d+)?(?:ms|s)\b/g, '#s')
78
+ .replace(/\b\d+m\b(?=\s|$|[),.])/g, '#m');
79
+ }
80
+
81
+ // ---- colour use ---------------------------------------------------------------------------------
82
+ // The client's "dim" is mostly a theme COLOUR (muted / placeholder / quiet), not SGR 2, so rows are classed by the
83
+ // theme role they are painted in (both tables, either background), plus a plain grey test for anything else.
84
+ const FAMILY = { muted: 'dim', dim: 'dim', placeholder: 'dim', quiet: 'dim', faint: 'dim',
85
+ ok: 'ok', warn: 'warn', error: 'error', fail: 'error', ask: 'ask', gold: 'gold' };
86
+ const PLAIN = new Set(['text', 'value', 'input', 'prose']);
87
+ const ROLE_HEX = new Map(); const ROLE_256 = new Map();
88
+ for (const table of [DARK, LIGHT]) {
89
+ for (const [role, [hex, i256]] of Object.entries(table)) {
90
+ const fam = FAMILY[role] || (PLAIN.has(role) ? 'plain' : 'color');
91
+ const h = hex.toLowerCase();
92
+ if (!ROLE_HEX.has(h) || FAMILY[role]) ROLE_HEX.set(h, fam);
93
+ ROLE_256.set(i256, ROLE_256.has(i256) && ROLE_256.get(i256) !== fam ? 'color' : fam); // a shared index names no role
94
+ }
95
+ }
96
+ const toHex = (r, g, b) => `#${[r, g, b].map(v => Math.max(0, Math.min(255, v | 0)).toString(16).padStart(2, '0')).join('')}`;
97
+ const isGrey256 = (n) => n === 8 || (n >= 232 && n <= 250);
98
+ const rgbClass = (r, g, b) => { const h = toHex(r, g, b); return ROLE_HEX.get(h) || (isGrey(h) ? 'grey' : 'color'); };
99
+ const i256Class = (n) => (isGrey256(n) ? 'grey' : ROLE_256.get(n) || 'color');
100
+
101
+ /** Style flags used on visible (non-space) text of one raw line: dim, grey, bold, inverse, ok/warn/error/ask/gold, color. */
102
+ function lineFlags(raw) {
103
+ const st = { bold: false, dim: false, inverse: false, fg: null };
104
+ const used = new Set();
105
+ const mark = () => {
106
+ if (st.bold) used.add('bold');
107
+ if (st.dim) used.add('dim');
108
+ if (st.inverse) used.add('inverse');
109
+ if (st.fg && st.fg !== 'plain') used.add(st.fg);
110
+ };
111
+ for (const m of raw.matchAll(/\x1b\[([0-9;]*)m|\x1b[^m]*?[@-~]|([^\x1b]+)/g)) {
112
+ if (m[2] != null) { if (/\S/.test(m[2])) mark(); continue; }
113
+ if (m[1] == null) continue;
114
+ const p = m[1] === '' ? [0] : m[1].split(';').map(Number);
115
+ for (let i = 0; i < p.length; i++) {
116
+ const c = p[i];
117
+ if (c === 0) Object.assign(st, { bold: false, dim: false, inverse: false, fg: null });
118
+ else if (c === 1) st.bold = true;
119
+ else if (c === 2) st.dim = true;
120
+ else if (c === 22) { st.bold = false; st.dim = false; }
121
+ else if (c === 7) st.inverse = true;
122
+ else if (c === 27) st.inverse = false;
123
+ else if (c === 39) st.fg = null;
124
+ else if (c === 90) st.fg = 'grey';
125
+ else if (c === 31 || c === 91) st.fg = 'error';
126
+ else if (c === 32 || c === 92) st.fg = 'ok';
127
+ else if (c === 33 || c === 93) st.fg = 'warn';
128
+ else if ((c >= 34 && c <= 36) || (c >= 94 && c <= 96)) st.fg = 'color';
129
+ else if (c === 30 || c === 37 || c === 97) st.fg = null;
130
+ else if (c === 38 && p[i + 1] === 5) { st.fg = i256Class(p[i + 2]); i += 2; }
131
+ else if (c === 38 && p[i + 1] === 2) { st.fg = rgbClass(p[i + 2], p[i + 3], p[i + 4]); i += 4; }
132
+ else if (c === 48 && (p[i + 1] === 5 || p[i + 1] === 2)) i += p[i + 1] === 5 ? 2 : 4;
133
+ }
134
+ }
135
+ return used;
136
+ }
137
+
138
+ const ranges = (nums) => {
139
+ const out = [];
140
+ for (let i = 0; i < nums.length; i++) {
141
+ let j = i; while (j + 1 < nums.length && nums[j + 1] === nums[j] + 1) j++;
142
+ out.push(j > i ? `${nums[i]}-${nums[j]}` : String(nums[i])); i = j;
143
+ }
144
+ return out.join(',');
145
+ };
146
+
147
+ /** Raw screen rows -> { dim: '0-2,7', warn: '4', ... }: 0-based visible rows per style, empty styles left out. */
148
+ export function colorSummary(rawRows, flagsOf = lineFlags) {
149
+ const by = { dim: [], grey: [], bold: [], inverse: [], ok: [], warn: [], error: [], ask: [], gold: [], color: [] };
150
+ rawRows.forEach((r, i) => { for (const f of (r instanceof Set ? r : flagsOf(r))) by[f].push(i); });
151
+ const o = {};
152
+ for (const k of Object.keys(by)) if (by[k].length) o[k] = ranges(by[k]);
153
+ return o;
154
+ }
155
+
156
+ function wrapPlain(line, cols) {
157
+ if (!(cols > 0)) return [line];
158
+ const gs = graphemes(line);
159
+ if (!gs.length) return [''];
160
+ const out = []; let row = ''; let w = 0;
161
+ for (const g of gs) {
162
+ const cw = cellWidth(g.text);
163
+ if (w + cw > cols) { out.push(row); row = ''; w = 0; }
164
+ row += g.text; w += cw;
165
+ }
166
+ out.push(row);
167
+ return out;
168
+ }
169
+
170
+ /** The visible screen from mirror lines: the last `rows` rows at width `cols`, trailing blank rows dropped. */
171
+ export function visibleScreen(lines, { cols, rows }) {
172
+ const body = lines.slice();
173
+ while (body.length && !stripAnsi(body[body.length - 1]).trim()) body.pop();
174
+ const want = rows > 0 ? rows : 50;
175
+ const out = []; // [{ text, flags }] bottom-up
176
+ for (let i = body.length - 1; i >= 0 && out.length < want; i--) {
177
+ const wrapped = wrapPlain(stripAnsi(body[i]), cols);
178
+ const flags = lineFlags(body[i]);
179
+ for (let k = wrapped.length - 1; k >= 0 && out.length < want; k--) out.push({ text: wrapped[k].replace(/\s+$/, ''), flags });
180
+ }
181
+ return out.reverse();
182
+ }
183
+
184
+ /**
185
+ * -> { tap(chunk) (also tap.write / tap.reset), stop() }. `write(rec)` gets each screen record. Never throws into the caller.
186
+ */
187
+ export function createScreenRecorder({ write, cols = () => process.stdout.columns, rows = () => process.stdout.rows, timers = globalThis, now = Date.now, debounceMs = SCREEN_DEBOUNCE_MS } = {}) {
188
+ const mirror = createStyledMirror();
189
+ let pending = [];
190
+ let timer = null;
191
+ let lastTap = 0;
192
+ let lastNorm = null; // normalised text of the last record
193
+ let candidate = null; // normalised text seen at the last check that was not yet quiet
194
+ let stopped = false;
195
+
196
+ const fold = () => {
197
+ const q = pending; pending = [];
198
+ for (const c of q) { if (c === null) mirror.reset(); else mirror.write(c); }
199
+ };
200
+ const arm = (ms) => {
201
+ if (stopped) return;
202
+ timer = timers.setTimeout(check, Math.max(1, ms));
203
+ timer?.unref?.();
204
+ };
205
+ const snapshot = () => {
206
+ fold();
207
+ const c = cols(), r = rows();
208
+ const vis = visibleScreen(mirror.lines(), { cols: c, rows: r });
209
+ let start = 0; let size = 0;
210
+ for (let i = vis.length - 1; i >= 0; i--) { // keep the bottom of the screen inside the cap
211
+ if (size + vis[i].text.length + 1 > MAX_SCREEN_TEXT) { start = i + 1; break; }
212
+ size += vis[i].text.length + 1;
213
+ }
214
+ const kept = vis.slice(start);
215
+ const text = kept.map(v => v.text).join('\n');
216
+ const dropped = vis.slice(0, start).reduce((n, v) => n + v.text.length + 1, 0);
217
+ return { cols: c ?? null, rows: r ?? null, text, colors: colorSummary(kept.map(v => v.flags)), ...(dropped ? { truncated: dropped } : {}) };
218
+ };
219
+ const emit = (snap, norm) => {
220
+ lastNorm = norm; candidate = null;
221
+ try { write({ t: 'screen', ...snap }); } catch { /* capture must never break the client */ }
222
+ };
223
+ function check() {
224
+ timer = null;
225
+ try {
226
+ const snap = snapshot();
227
+ const norm = normalizeScreen(snap.text);
228
+ if (norm === lastNorm) { candidate = null; return; }
229
+ const quietFor = now() - lastTap;
230
+ if (quietFor >= debounceMs || norm === candidate) { emit(snap, norm); return; }
231
+ candidate = norm;
232
+ arm(Math.min(debounceMs, lastTap + debounceMs - now()));
233
+ } catch { /* never throw from a timer */ }
234
+ }
235
+ const queue = (c) => {
236
+ if (stopped) return;
237
+ pending.push(c);
238
+ lastTap = now();
239
+ if (pending.length > MAX_PENDING) { try { fold(); } catch { pending = []; } }
240
+ if (!timer) arm(debounceMs);
241
+ };
242
+ const tap = (chunk) => queue(String(chunk)); // the only work on the write path: queue the string, arm the timer
243
+ tap.write = tap;
244
+ tap.reset = () => queue(null);
245
+ return {
246
+ tap,
247
+ stop() {
248
+ if (stopped) return;
249
+ if (timer) { timers.clearTimeout(timer); timer = null; }
250
+ try { const snap = snapshot(); const norm = normalizeScreen(snap.text); if (snap.text && norm !== lastNorm) emit(snap, norm); } catch { /* ignore */ }
251
+ stopped = true; pending = [];
252
+ }
253
+ };
254
+ }