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,229 @@
1
+ /**
2
+ * Resident pump — QUOXPERTICUS-REVISIT P4 "steer pump".
3
+ *
4
+ * When a resident's slot is held by a FULL INTERACTIVE claude session (the
5
+ * resident itself, deployed via `quox dev resident deploy`), the warden
6
+ * process never answers (see the HOLD-IS-THE-VOICE gate in
7
+ * supervisorEngine.js): the resident already holds the voice. What the
8
+ * warden does instead, while that hold lasts, is PUMP: relay new owner
9
+ * messages from the slot channel into the resident's live tmux pane via the
10
+ * dev-sessions `keys` route (services/collector/lib/devSessionsProxy.js), so
11
+ * the interactive session hears its owner without anyone opening the pane
12
+ * by hand.
13
+ *
14
+ * Live-spike findings this reads (2026-09-04, real injection into a real
15
+ * claude TUI on .131 via tmux send-keys):
16
+ * - Text injected mid-turn queues cleanly in the composer and submits when
17
+ * the turn ends. Injection mid-turn is SAFE.
18
+ * - A permission dialog ("Do you want to proceed? ... Esc to cancel")
19
+ * BLOCKS the pipeline indefinitely. Queued input survives but does not
20
+ * process. This pump NEVER injects bare Enter or any control key to
21
+ * resolve one; a stuck dialog is a residentAlarm.js condition, not
22
+ * something this module resolves. See paneHasDialog below.
23
+ * - `keys` is one line, max 4096 chars (the collector's MAX_KEYS_LENGTH),
24
+ * and the collector appends Enter itself.
25
+ *
26
+ * Every I/O dependency (fetch, pane read, key injection, heartbeat, the
27
+ * clock, sleep) is injected, matching supervisorEngine.js's contract, so
28
+ * this runs under vitest with no real network, no real tmux pane.
29
+ */
30
+
31
+ import { pickNewMessages, boundSeenIds, pruneOldTurns, shouldPostErrorNotice, ERROR_NOTICE_WINDOW_MS, DEFAULT_SETTINGS } from './supervisorEngine.js';
32
+
33
+ // The exact literal markers a Claude Code permission dialog shows
34
+ // ("Do you want to proceed? 1. Yes 2. No ... Esc to cancel"). Either marker
35
+ // alone is enough to hold injection: better to over-detect (delay a message
36
+ // one pass) than under-detect and blind-inject into a live approval prompt.
37
+ export const DIALOG_MARKERS = ['Do you want to proceed?', 'Esc to cancel'];
38
+
39
+ export function paneHasDialog(paneText) {
40
+ const text = String(paneText || '');
41
+ return DIALOG_MARKERS.some((marker) => text.includes(marker));
42
+ }
43
+
44
+ function sanitizeOneLine(text) {
45
+ return String(text || '').replace(/\r?\n+/g, ' ').replace(/[ \t]{2,}/g, ' ').trim();
46
+ }
47
+
48
+ /**
49
+ * Build the `keys` line(s) for one owner message: `[slot:<name> owner]
50
+ * <text>`, newlines collapsed to spaces (a literal newline mid-line would
51
+ * submit a half-typed line early — same reasoning as residentSeed.js's
52
+ * oneLine()). A message that overflows maxLen is CHUNKED, never truncated
53
+ * (silent data loss is worse than several turns): each chunk carries its
54
+ * own prefix and a `(i/n)` suffix so the resident can read each line
55
+ * standalone.
56
+ */
57
+ export function buildPumpLines({ slotName, text, maxLen = 4000 }) {
58
+ const prefix = `[slot:${slotName} owner] `;
59
+ const sanitized = sanitizeOneLine(text);
60
+ if (prefix.length + sanitized.length <= maxLen) {
61
+ return [`${prefix}${sanitized}`];
62
+ }
63
+ // Reserve headroom for the ` (NN/NN)` suffix appended to every chunk.
64
+ const suffixReserve = 10;
65
+ const available = Math.max(1, maxLen - prefix.length - suffixReserve);
66
+ const parts = [];
67
+ for (let i = 0; i < sanitized.length; i += available) {
68
+ parts.push(sanitized.slice(i, i + available));
69
+ }
70
+ return parts.map((part, i) => `${prefix}${part} (${i + 1}/${parts.length})`);
71
+ }
72
+
73
+ export function createInitialPumpState() {
74
+ return {
75
+ bootstrapped: false,
76
+ seen_ids: [],
77
+ // Owner messages picked up from the channel but not yet delivered to
78
+ // the pane: held back by a stuck dialog, or by the per-hour cap. A
79
+ // message only ever leaves `pending` by being injected, never dropped.
80
+ pending: [],
81
+ injection_timestamps: [],
82
+ overflow_notices: [],
83
+ // Set the moment a permission dialog is first observed; cleared the
84
+ // moment it is no longer observed. residentAlarm.js reads the age of
85
+ // this field to decide whether the stuck-dialog condition should alarm.
86
+ stuck_dialog_since: null,
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Heartbeat the resident's slot hold with retry/backoff. The pump must not
92
+ * rely on the wait long-poll alone to keep the hold warm: a failed poll
93
+ * pass must never stop heartbeats while the collector is genuinely
94
+ * reachable, so this is a small standalone retry loop, called every pump
95
+ * pass regardless of what else happens this pass.
96
+ */
97
+ export async function heartbeatWithBackoff({
98
+ heartbeatHold,
99
+ log = () => {},
100
+ sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
101
+ maxAttempts = 3,
102
+ baseDelayMs = 2000,
103
+ }) {
104
+ let lastErr = null;
105
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
106
+ try {
107
+ await heartbeatHold();
108
+ return { ok: true, attempts: attempt };
109
+ } catch (err) {
110
+ lastErr = err;
111
+ log('warn', `pump: heartbeat attempt ${attempt}/${maxAttempts} failed: ${err.message}`);
112
+ if (attempt < maxAttempts) {
113
+ await sleep(baseDelayMs * attempt);
114
+ }
115
+ }
116
+ }
117
+ log('warn', `pump: heartbeat failed after ${maxAttempts} attempt(s), giving up this pass (hold may lapse): ${lastErr ? lastErr.message : 'unknown error'}`);
118
+ return { ok: false, attempts: maxAttempts, error: lastErr };
119
+ }
120
+
121
+ /**
122
+ * Run one pump pass: heartbeat the resident's hold, read the pane for a
123
+ * stuck permission dialog, pick up new owner messages, and inject whatever
124
+ * is safe to inject this pass (never while a dialog is visible, never past
125
+ * the per-hour cap).
126
+ *
127
+ * Injected dependencies:
128
+ * fetchMessages() -> array of slot messages (same shape the engine
129
+ * uses; a `wait`-mode caller can pass the same
130
+ * closure it already built for the headless path)
131
+ * readPane() -> current pane text (string)
132
+ * injectKeys(line) -> POST one `keys` line to the resident's pane
133
+ * heartbeatHold() -> heartbeat the slot AS the resident's own
134
+ * holder_session (not this warden's identity)
135
+ * postReply(text) -> slot `say`, used only for the once-per-hour
136
+ * overflow notice
137
+ */
138
+ export async function runPumpOnce({
139
+ hostId,
140
+ slot,
141
+ selfIdentity,
142
+ fromFilters = [],
143
+ fetchMessages,
144
+ readPane,
145
+ injectKeys,
146
+ heartbeatHold,
147
+ postReply,
148
+ state,
149
+ log = () => {},
150
+ now = () => Date.now(),
151
+ maxSeenIds = 500,
152
+ maxTurnsPerHour = DEFAULT_SETTINGS.max_turns_per_hour,
153
+ maxLineLen = 4000,
154
+ heartbeatMaxAttempts = 3,
155
+ heartbeatBaseDelayMs = 2000,
156
+ sleep,
157
+ }) {
158
+ const heartbeat = await heartbeatWithBackoff({
159
+ heartbeatHold, log, sleep, maxAttempts: heartbeatMaxAttempts, baseDelayMs: heartbeatBaseDelayMs,
160
+ });
161
+
162
+ const paneText = await readPane();
163
+ const dialogPresent = paneHasDialog(paneText);
164
+
165
+ let nextState = { ...state, pending: [...(state.pending || [])] };
166
+
167
+ if (dialogPresent) {
168
+ if (nextState.stuck_dialog_since == null) {
169
+ nextState.stuck_dialog_since = now();
170
+ log('warn', `slot ${slot}: permission dialog detected in the resident's pane on ${hostId}; holding owner messages until it clears (never injecting Enter to resolve it).`);
171
+ }
172
+ } else if (nextState.stuck_dialog_since != null) {
173
+ log('info', `slot ${slot}: permission dialog cleared after ${Math.round((now() - nextState.stuck_dialog_since) / 1000)}s; resuming injection.`);
174
+ nextState.stuck_dialog_since = null;
175
+ }
176
+
177
+ const messages = await fetchMessages();
178
+ const firstRun = nextState.bootstrapped !== true;
179
+ const picked = pickNewMessages({ messages, seenIds: nextState.seen_ids, selfIdentity, fromFilters, firstRun });
180
+ nextState.seen_ids = boundSeenIds(picked.seenIds, maxSeenIds);
181
+ if (firstRun) {
182
+ nextState.bootstrapped = true;
183
+ log('info', `pump first run on ${slot}: seeded seen-set with ${picked.allSeen.length} existing message id(s), queuing none for injection.`);
184
+ }
185
+
186
+ const newPending = picked.toAnswer.filter((m) => !nextState.pending.some((p) => p.id === m.id));
187
+ nextState.pending = [...nextState.pending, ...newPending];
188
+ if (newPending.length > 0) {
189
+ log('info', `pump: queued ${newPending.length} new owner message(s) for ${slot}'s resident.`);
190
+ }
191
+
192
+ let injected = 0;
193
+ let capHit = false;
194
+
195
+ if (!dialogPresent) {
196
+ let timestamps = pruneOldTurns(nextState.injection_timestamps, now(), ERROR_NOTICE_WINDOW_MS);
197
+ const stillPending = [];
198
+ for (const message of nextState.pending) {
199
+ if (timestamps.length >= maxTurnsPerHour) {
200
+ capHit = true;
201
+ stillPending.push(message);
202
+ continue;
203
+ }
204
+ const lines = buildPumpLines({ slotName: slot, text: message.text, maxLen: maxLineLen });
205
+ for (const line of lines) {
206
+ await injectKeys(line);
207
+ }
208
+ timestamps = [...timestamps, now()];
209
+ injected += 1;
210
+ log('info', `pump: injected message ${message.id} from ${message.from} into ${slot}'s resident pane (${lines.length} line(s)).`);
211
+ }
212
+ nextState.pending = stillPending;
213
+ nextState.injection_timestamps = timestamps;
214
+ } else if (nextState.pending.length > 0) {
215
+ log('warn', `pump: ${nextState.pending.length} owner message(s) on ${slot} held, waiting for the permission dialog to clear.`);
216
+ }
217
+
218
+ if (capHit && nextState.pending.length > 0) {
219
+ const notice = shouldPostErrorNotice(nextState.overflow_notices, 'pump_rate_cap', now(), ERROR_NOTICE_WINDOW_MS);
220
+ nextState.overflow_notices = notice.notices;
221
+ if (notice.shouldPost) {
222
+ const text = `Pump injection cap reached (${maxTurnsPerHour}/hour) on slot ${slot}: ${nextState.pending.length} owner message(s) still queued, will resume as the hour rolls over.`;
223
+ await postReply(text);
224
+ log('warn', `pump: posted once-per-hour overflow notice on ${slot} (${nextState.pending.length} still queued).`);
225
+ }
226
+ }
227
+
228
+ return { state: nextState, injected, dialogPresent, pendingCount: nextState.pending.length, heartbeat };
229
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Resident seed prompt — QUOXPERTICUS-REVISIT P3 "Resident lifecycle v1".
3
+ *
4
+ * The seed is the one line of text typed into a freshly-deployed interactive
5
+ * `claude` tmux pane (via the dev-sessions `keys` route) that turns a bare
6
+ * shell into the host's resident: read the revival kit before acting, claim
7
+ * the slot, then say a short hello. This encodes the proven 2026-09-04
8
+ * revival recipe (read MEMORY.md + indexed memories + NOTES_TO_SELF.md
9
+ * before acting, then claim, then greet) as a template, parameterized by
10
+ * host and slot.
11
+ *
12
+ * `keys` is a single line per call (the collector appends Enter — see
13
+ * services/collector/lib/devSessionsProxy.js MAX_KEYS_LENGTH=4096), so the
14
+ * rendered seed collapses to ONE line: no literal newlines, which would
15
+ * otherwise submit a half-typed prompt early.
16
+ *
17
+ * LATER PHASE SEAM: a collector-stored per-org/per-host seed override is a
18
+ * follow-up (dashboard-editable, versioned). Until that lands,
19
+ * `loadSeedOverride()` is the only override point, reading a local file on
20
+ * the CONTROL side (the box running `quox dev resident deploy`) verbatim.
21
+ */
22
+
23
+ import { existsSync, readFileSync } from 'node:fs';
24
+ import { homedir } from 'node:os';
25
+ import { join } from 'node:path';
26
+
27
+ export function defaultSeedOverridePath() {
28
+ return join(homedir(), '.quox', 'resident', 'seed.md');
29
+ }
30
+
31
+ /**
32
+ * Read a local seed override file, if present. Returns null when the file
33
+ * is missing, unreadable, or empty — never throws, since a missing override
34
+ * simply means "use the built-in template", not an error.
35
+ *
36
+ * SEAM: a later phase replaces/augments this with a collector-stored
37
+ * config record (org/host-scoped, dashboard-editable). This function is the
38
+ * one place that phase needs to change.
39
+ */
40
+ export function loadSeedOverride(path = defaultSeedOverridePath()) {
41
+ try {
42
+ if (!existsSync(path)) return null;
43
+ const content = readFileSync(path, 'utf-8');
44
+ return content && content.trim() ? content : null;
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+
50
+ /** Collapse arbitrary text to one line safe for a single `keys` send. */
51
+ function oneLine(text) {
52
+ return String(text || '').replace(/\s*\r?\n+\s*/g, ' ').replace(/[ \t]{2,}/g, ' ').trim();
53
+ }
54
+
55
+ function builtInTemplate({ hostId, slotName }) {
56
+ // #1021: Claude names a project dir by its path with separators turned into '-'.
57
+ const projectDirName = (process.env.QUOX_HOST_HOME || homedir()).replace(/[\\/]/g, '-');
58
+ const claimCmd = `quox slot checkout ${slotName} --session resident-${slotName} --kind interactive --host ${hostId} --create`;
59
+ return (
60
+ `You are the resident Claude session for host ${hostId} (slot ${slotName}). ` +
61
+ `Before doing anything else: read ~/.claude/projects/${projectDirName}/memory/MEMORY.md and every ` +
62
+ `memory file it indexes, then read ~/NOTES_TO_SELF.md if it exists, then read any identity or ` +
63
+ `onboarding docs in your home directory for this host. Read all of it before you act on anything. ` +
64
+ `Once you have read everything, claim your slot with: ${claimCmd}. ` +
65
+ `Then post one short hello (1-2 lines, no em dashes, lead with outcome) into the slot channel ` +
66
+ `confirming you are up and what you found.`
67
+ );
68
+ }
69
+
70
+ /**
71
+ * Render the seed prompt for a host/slot pair, as one line.
72
+ * @param {object} params
73
+ * @param {string} params.hostId
74
+ * @param {string} params.slotName
75
+ * @param {string} [params.overrideText] - test seam; when omitted, falls
76
+ * back to loadSeedOverride().
77
+ */
78
+ export function renderSeedPrompt({ hostId, slotName, overrideText } = {}) {
79
+ if (!hostId) throw new Error('renderSeedPrompt: hostId is required');
80
+ if (!slotName) throw new Error('renderSeedPrompt: slotName is required');
81
+
82
+ const override = overrideText !== undefined ? overrideText : loadSeedOverride();
83
+ if (override) {
84
+ return oneLine(
85
+ override.replace(/\{\{?\s*hostId\s*\}?\}/g, hostId).replace(/\{\{?\s*slotName\s*\}?\}/g, slotName)
86
+ );
87
+ }
88
+ return oneLine(builtInTemplate({ hostId, slotName }));
89
+ }
90
+
91
+ /**
92
+ * Split a one-line seed into <= maxLen chunks, each sent as its own `keys`
93
+ * call (each call appends its own Enter). Under maxLen this is a single
94
+ * chunk — the preferred, normal path. Splitting a seed that overflows
95
+ * maxLen means the resident receives it as multiple separate turns rather
96
+ * than one line, which is a documented degrade, not silent truncation.
97
+ */
98
+ export function chunkSeedText(text, maxLen = 4000) {
99
+ const s = String(text || '');
100
+ if (s.length <= maxLen) return [s];
101
+ const chunks = [];
102
+ for (let i = 0; i < s.length; i += maxLen) {
103
+ chunks.push(s.slice(i, i + maxLen));
104
+ }
105
+ return chunks;
106
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The root `quox` program's global options, in one place so the binary
3
+ * (bin/quox.js) and the safety-flag test (test/safetyFlagsReachLeaf.test.js)
4
+ * build the SAME root. A test that re-typed this list would pass against a
5
+ * root the binary does not have.
6
+ */
7
+ import { mergeShadowedGlobals, assertDryRunHonoured } from './global-flags.js';
8
+
9
+ export function applyRootOptions(program) {
10
+ return program
11
+ .option('-o, --output <format>', 'output format: table, json, csv, quiet', 'table')
12
+ .option('--json', 'shorthand for --output json')
13
+ .option('-q, --quiet', 'suppress output, show IDs only')
14
+ .option('--org <slug|id>', 'override active organization (slug, name or ID; must match your session org)')
15
+ .option('-p, --profile <name>', 'use a specific config profile')
16
+ .option('--no-color', 'disable colored output')
17
+ .option('-v, --verbose', 'show detailed output')
18
+ .option('--debug', 'show HTTP request/response details')
19
+ .option('--dry-run', 'show what would happen without executing')
20
+ .option('-y, --yes', 'skip confirmation prompts')
21
+ .option('--insecure', 'allow self-signed SSL certificates');
22
+ }
23
+
24
+ /**
25
+ * The flag-delivery hooks every invocation needs, in order:
26
+ * 1. a value typed on the root or on a parent group reaches the leaf that
27
+ * declared the same option (commander binds it to the first command that
28
+ * knows it, so the leaf would otherwise read undefined or its default);
29
+ * 2. a --dry-run the leaf cannot honour is refused before anything runs.
30
+ */
31
+ export function installFlagDeliveryHooks(program) {
32
+ program.hook('preAction', (thisCommand, actionCommand) => {
33
+ mergeShadowedGlobals(thisCommand, actionCommand);
34
+ });
35
+ program.hook('preAction', (thisCommand, actionCommand) => {
36
+ assertDryRunHonoured(thisCommand, actionCommand);
37
+ });
38
+ return program;
39
+ }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * ROUTEROS-WIRE: engine for `quox router`, the read-only MikroTik RouterOS views.
3
+ *
4
+ * Every verb is one NETWATCH tool run through the governed tool route
5
+ * (POST /api/v1/tools/execute, agent_id netwatch). The collector's mikrotikExecutor
6
+ * reaches the org's configured router through the allowlisted RouterOS proxy; nothing
7
+ * here talks to a router directly and no verb writes. Formatters are pure so the human
8
+ * output is tested without a router.
9
+ */
10
+ import { resolveEndpoint } from '../api/endpoints.js';
11
+
12
+ /** verb -> the tool it runs. All read-only (aoclClassification layer 1, autonomy FULL). */
13
+ export const ROUTER_TOOLS = Object.freeze({
14
+ health: 'mikrotik_monitoring_health',
15
+ leases: 'mikrotik_dhcp_leases',
16
+ pools: 'mikrotik_dhcp_pool_status',
17
+ conflicts: 'mikrotik_dhcp_conflict_check',
18
+ 'free-ip': 'mikrotik_dhcp_find_free_ip',
19
+ vpn: 'mikrotik_vpn_health',
20
+ wireguard: 'mikrotik_vpn_wireguard_peers',
21
+ inventory: 'mikrotik_discovery_inventory',
22
+ });
23
+
24
+ export function makeRouterExec({ client, orgId }) {
25
+ return async function exec(tool, input = {}) {
26
+ const ep = resolveEndpoint('v1.tools.execute');
27
+ const body = { tool_name: tool, input, agent_id: 'netwatch' };
28
+ if (orgId) body.org_id = orgId;
29
+ const data = await client.post(ep.path, { service: ep.service, body });
30
+ if (data && data.success === false) {
31
+ const detail = data.detail ? ` (${data.detail})` : '';
32
+ const err = new Error(`${tool} via POST ${ep.path} returned success:false: ${data.error || 'unknown error'}${detail}`);
33
+ err.toolError = data.error || 'unknown error';
34
+ err.tool = tool;
35
+ throw err;
36
+ }
37
+ return routerPayload(data);
38
+ };
39
+ }
40
+
41
+ /** Unwrap the tool route envelope to the executor's own result object. */
42
+ export function routerPayload(data) {
43
+ let p = data;
44
+ for (let i = 0; i < 3 && p && typeof p === 'object' && !Array.isArray(p); i++) {
45
+ const inner = ['result', 'output', 'data'].map((k) => p[k]).find((v) => v !== undefined && v !== null);
46
+ if (inner === undefined) break;
47
+ p = inner;
48
+ }
49
+ if (typeof p === 'string') { try { p = JSON.parse(p); } catch { /* keep text */ } }
50
+ if (p && typeof p === 'object' && p.success === false) {
51
+ const err = new Error(`router read failed: ${p.error || 'unknown error'}${p.detail ? ` (${p.detail})` : ''}`);
52
+ err.toolError = p.error;
53
+ throw err;
54
+ }
55
+ return p;
56
+ }
57
+
58
+ const dash = (v) => (v === null || v === undefined || v === '' ? '-' : String(v));
59
+ const mib = (b) => (b === null || b === undefined ? '-' : `${Math.round(Number(b) / 1048576)} MiB`);
60
+ const pad = (rows) => {
61
+ const w = [];
62
+ for (const r of rows) r.forEach((c, i) => { w[i] = Math.max(w[i] || 0, String(c).length); });
63
+ return rows.map((r) => r.map((c, i) => (i === r.length - 1 ? String(c) : String(c).padEnd(w[i]))).join(' ').trimEnd());
64
+ };
65
+
66
+ export function healthLines(p) {
67
+ const s = p.summary || {};
68
+ return [
69
+ `Router health score: ${dash(p.score)}/100`,
70
+ `CPU load: ${s.cpu_load_pct === null || s.cpu_load_pct === undefined ? '-' : `${s.cpu_load_pct}%`}${s.cpu_count ? ` (${s.cpu_count} cpu)` : ''}`,
71
+ `Memory: ${s.memory_used_pct === null || s.memory_used_pct === undefined ? '-' : `${s.memory_used_pct}% used`} (${mib(s.memory_free_bytes)} free of ${mib(s.memory_total_bytes)})`,
72
+ `Temperature: ${s.temperature_c === null || s.temperature_c === undefined ? 'unknown (no sensor reported)' : `${s.temperature_c} C`}`,
73
+ `Uptime: ${dash(s.uptime)}`,
74
+ `RouterOS: ${dash(s.version)} board ${dash(s.board_name)} ${dash(s.architecture)}`,
75
+ ...(p.issues || []).map((i) => `! ${i}`),
76
+ ];
77
+ }
78
+
79
+ export function leaseLines(p) {
80
+ const rows = (p.leases || []).map((l) => [dash(l.address), dash(l['mac-address']), dash(l['host-name']), dash(l.status), l.dynamic === 'true' || l.dynamic === true ? 'dynamic' : 'static']);
81
+ return [`${p.total ?? rows.length} DHCP lease(s), ${p.bound ?? 0} bound`, ...(rows.length ? pad([['ADDRESS', 'MAC', 'HOST', 'STATUS', 'TYPE'], ...rows]) : [])];
82
+ }
83
+
84
+ export function poolLines(p) {
85
+ const rows = (p.pools || []).map((x) => [dash(x.name), dash(x.ranges), x.size, x.used, x.free, x.used_pct === null || x.used_pct === undefined ? '-' : `${x.used_pct}%`]);
86
+ return [
87
+ `${p.pool_count ?? rows.length} DHCP pool(s): ${p.total_used ?? 0} of ${p.total_size ?? 0} addresses used, ${p.total_avail ?? 0} free`,
88
+ ...(rows.length ? pad([['POOL', 'RANGES', 'SIZE', 'USED', 'FREE', 'USED%'], ...rows]) : []),
89
+ ];
90
+ }
91
+
92
+ export function conflictLines(p) {
93
+ const out = [p.plain_english || (p.healthy ? 'No IP address conflicts found.' : 'IP address conflicts found.')];
94
+ for (const c of p.conflicts || []) {
95
+ out.push(` ${c.address}: ${(c.claims || []).map((k) => `${k.mac} (${(k.seen_in || []).join(', ')})`).join(' vs ') || (c.macs || []).join(' vs ')}`);
96
+ }
97
+ if (p.limits) out.push(`Limit: ${p.limits}`);
98
+ for (const i of p.issues || []) out.push(`! ${i}`);
99
+ return out;
100
+ }
101
+
102
+ export function freeIpLines(p) {
103
+ const ips = p.free_ips || [];
104
+ return [
105
+ `${ips.length} free address(es) in pool ${dash(p.pool_searched)} (checked against DHCP leases${p.arp_checked ? ' and ARP' : ', ARP unavailable'}). Nothing was reserved.`,
106
+ ...ips.map((f) => ` ${f.address} (${dash(f.pool)})`),
107
+ ...(p.note ? [`! ${p.note}`] : []),
108
+ ];
109
+ }
110
+
111
+ function peerRow(x) {
112
+ return [dash(x.name || x.public_key), dash(x.interface), dash(x.endpoint), dash(x.allowed_address), dash(x.last_handshake), x.disabled ? 'disabled' : x.connected ? 'connected' : 'not connected'];
113
+ }
114
+
115
+ export function wireguardLines(p) {
116
+ const rows = (p.peers || []).map(peerRow);
117
+ return [
118
+ `${p.total ?? rows.length} WireGuard peer(s), ${p.connected ?? 0} connected`,
119
+ ...(rows.length ? pad([['PEER', 'IFACE', 'ENDPOINT', 'ALLOWED', 'HANDSHAKE', 'STATE'], ...rows]) : []),
120
+ ...(p.connected_rule ? [`Rule: ${p.connected_rule}`] : []),
121
+ ];
122
+ }
123
+
124
+ export function vpnLines(p) {
125
+ const h = p.health || {};
126
+ const out = [p.plain_english || `${p.tunnels_up ?? 0} of ${p.tunnels_total ?? 0} VPN tunnel(s) up.`, `Score: ${p.score === null || p.score === undefined ? '- (no tunnels)' : `${p.score}/100`}`];
127
+ for (const x of h.wireguard || []) out.push(` wg peer ${peerRow(x)[0]}: ${peerRow(x)[5]} (handshake ${dash(x.last_handshake)})`);
128
+ for (const x of h.ipsec || []) out.push(` ipsec ${dash(x.remote_address)}: ${dash(x.state)}, up ${dash(x.uptime)}`);
129
+ for (const i of p.issues || []) out.push(`! ${i}`);
130
+ return out;
131
+ }
132
+
133
+ export function inventoryLines(p) {
134
+ const routers = (p.routers || []).map((r) => [dash(r.identity || [r.platform, r.board_name].filter(Boolean).join(' ')), dash(r.role), dash(r.address), dash(r.version), dash(r.board_name)]);
135
+ const devices = (p.devices || []).map((d) => [dash(d.address), dash(d.mac), dash(d.host_name), dash(d.interface), d.seen_in_arp ? 'arp' : 'lease only']);
136
+ return [
137
+ `${p.router_count ?? routers.length} MikroTik router(s)`,
138
+ ...pad([['ROUTER', 'ROLE', 'ADDRESS', 'VERSION', 'BOARD'], ...routers]),
139
+ `${p.total ?? devices.length} connected device(s)`,
140
+ ...(devices.length ? pad([['ADDRESS', 'MAC', 'HOST', 'IFACE', 'SEEN'], ...devices]) : []),
141
+ ];
142
+ }
143
+
144
+ export const ROUTER_FORMATTERS = Object.freeze({
145
+ health: healthLines, leases: leaseLines, pools: poolLines, conflicts: conflictLines,
146
+ 'free-ip': freeIpLines, vpn: vpnLines, wireguard: wireguardLines, inventory: inventoryLines,
147
+ });
148
+
149
+ /** `quox router trust --show`: the fingerprint to compare out of band. Pure. */
150
+ export function formatTrust(d) {
151
+ if (!d || d.success === false) {
152
+ return [`Could not read the router certificate: ${(d && (d.error || d.detail)) || 'unknown error'}`, 'No credentials were sent.'];
153
+ }
154
+ const lines = [
155
+ `Router endpoint: ${d.host}:${d.port}`,
156
+ `Certificate SHA-256: ${d.cert_sha256_formatted}`,
157
+ `Public key SHA-256: ${d.spki_sha256_formatted}`,
158
+ `Subject: ${d.subject ? String(d.subject).replace(/\n/g, ', ') : '-'}`,
159
+ `Issuer: ${d.issuer ? String(d.issuer).replace(/\n/g, ', ') : '-'}${d.self_signed ? ' (self-signed)' : ''}`,
160
+ `Valid until: ${d.valid_to || '-'}`,
161
+ d.pin_configured
162
+ ? (d.matches_configured_pin ? 'MIKROTIK_TLS_PIN is set and MATCHES this endpoint.' : 'MIKROTIK_TLS_PIN is set and does NOT match this endpoint. Do not trust it until you know why.')
163
+ : 'MIKROTIK_TLS_PIN is not set, so the collector sends nothing to this router yet.',
164
+ 'No credentials were sent. Nothing was pinned.',
165
+ 'Compare the fingerprint with the one on the router itself (System > Certificates) over a channel you trust, then set MIKROTIK_TLS_PIN to the SHA-256 value.',
166
+ ];
167
+ return lines;
168
+ }