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,569 @@
1
+ /**
2
+ * Setup commands: status, admin, config, license
3
+ */
4
+
5
+ import chalk from 'chalk';
6
+ import { formatDetail, printSuccess, printInfo } from '../core/output.js';
7
+ import { resolveEndpoint } from '../api/endpoints.js';
8
+ import { QuoxApiError, QuoxValidationError } from '../core/errors.js';
9
+ import { confirmDestructive } from '../core/confirm.js';
10
+ import { passwordProblem } from '../quoxon/onboarding/finish.js';
11
+
12
+ // The auth service's rule (services/auth/utils/passwordValidation.js), said up front.
13
+ export const PASSWORD_POLICY = 'Passwords need at least 12 characters, with an uppercase letter, a lowercase letter, a number and a special character (for example - or !).';
14
+
15
+ async function readStdinText() {
16
+ const chunks = [];
17
+ for await (const chunk of process.stdin) chunks.push(chunk);
18
+ return Buffer.concat(chunks).toString('utf8');
19
+ }
20
+
21
+ /**
22
+ * LICENSE-LAUNCH LL-8: read a license-key-v2 JSON object from a file or
23
+ * stdin, never from argv.
24
+ *
25
+ * Secret hygiene, same rule as `vault create` (fleet #391): argv is visible
26
+ * in the process list and lands in shell history, so there is deliberately no
27
+ * `--key <json>` flag. A file can be 0600 and shredded afterwards; stdin
28
+ * never touches disk.
29
+ *
30
+ * The RAW text is what gets posted, not a re-serialisation of the parsed
31
+ * object: the key is Ed25519-signed over its own bytes, and re-emitting it
32
+ * with different key order or spacing would invalidate the signature. The
33
+ * parse here is validation only.
34
+ *
35
+ * @returns {Promise<string>} the trimmed raw license key text
36
+ */
37
+ async function readLicenseKey(options) {
38
+ let raw;
39
+ let source;
40
+
41
+ if (options.stdin) {
42
+ source = 'stdin';
43
+ raw = await new Promise((resolve, reject) => {
44
+ let buf = '';
45
+ process.stdin.setEncoding('utf8');
46
+ process.stdin.on('data', c => { buf += c; });
47
+ process.stdin.on('end', () => resolve(buf));
48
+ process.stdin.on('error', reject);
49
+ });
50
+ } else if (options.keyFile) {
51
+ source = options.keyFile;
52
+ const { readFile } = await import('node:fs/promises');
53
+ try {
54
+ raw = await readFile(options.keyFile, 'utf8');
55
+ } catch (err) {
56
+ throw new QuoxValidationError(`Could not read license key from ${options.keyFile}: ${err.message}`);
57
+ }
58
+ } else {
59
+ // No prompt, ever. This runs from boot scripts on headless instances,
60
+ // where an inquirer prompt is a hang, not a question.
61
+ throw new QuoxValidationError(
62
+ 'No license key supplied. Pass --key-file <path>, or pipe the key and pass --stdin. '
63
+ + 'There is no --key flag: argv is visible in the process list and shell history.'
64
+ );
65
+ }
66
+
67
+ const text = (raw || '').trim();
68
+ if (!text) {
69
+ throw new QuoxValidationError(`License key from ${source} is empty.`);
70
+ }
71
+
72
+ let parsed;
73
+ try {
74
+ parsed = JSON.parse(text);
75
+ } catch (err) {
76
+ throw new QuoxValidationError(
77
+ `License key from ${source} is not valid JSON: ${err.message}. `
78
+ + 'Paste the whole license-key-v2 object from your purchase email.'
79
+ );
80
+ }
81
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
82
+ throw new QuoxValidationError(`License key from ${source} must be a JSON object.`);
83
+ }
84
+
85
+ return text;
86
+ }
87
+
88
+ /** Best available human name for one entry of a `bound` seat list. */
89
+ function boundEntryName(entry) {
90
+ if (typeof entry === 'string') return entry;
91
+ return entry?.instance_name
92
+ || entry?.bound_instance_name
93
+ || entry?.hostname
94
+ || entry?.instance_id
95
+ || 'unknown instance';
96
+ }
97
+
98
+ /**
99
+ * LICENSE-LAUNCH LL-3d: render the seat context the licensing endpoints send.
100
+ *
101
+ * The control plane's 409 carries `bound_instance_name`, and (after LL-2a)
102
+ * a `bound` list plus `max_servers`; the 402 carries the unlicensed `plugin`.
103
+ * core/client.js now hangs the whole body on `err.body`, so say WHICH box
104
+ * holds the seat and HOW MANY of the licensed servers are in use, instead of
105
+ * "Request failed with status 409". The raw body rides along on the returned
106
+ * error, so `--output json` still emits exactly what the server said.
107
+ *
108
+ * Returns the error unchanged when it carries no licensing context.
109
+ */
110
+ function enrichLicenseError(err) {
111
+ if (!(err instanceof QuoxApiError)) return err;
112
+
113
+ const body = err.body && typeof err.body === 'object' ? err.body : {};
114
+ const code = err.code || body.error;
115
+ const lines = [];
116
+
117
+ if (code === 'seat_taken' || code === 'seat_limit_reached') {
118
+ lines.push(code === 'seat_limit_reached'
119
+ ? 'This licence has no free seats left.'
120
+ : 'This licence seat is already bound to another instance.');
121
+
122
+ if (body.bound_instance_name) {
123
+ lines.push(` Seat held by: ${body.bound_instance_name}`);
124
+ }
125
+
126
+ const bound = Array.isArray(body.bound) ? body.bound : [];
127
+ const maxServers = Number.isFinite(Number(body.max_servers)) && body.max_servers != null
128
+ ? Number(body.max_servers)
129
+ : null;
130
+ if (bound.length) {
131
+ lines.push(maxServers != null
132
+ ? ` Seats: ${bound.length} of ${maxServers} servers in use`
133
+ : ` Seats: ${bound.length} servers in use`);
134
+ for (const entry of bound) lines.push(` - ${boundEntryName(entry)}`);
135
+ } else if (maxServers != null) {
136
+ lines.push(` Seats: ${maxServers} servers allowed by this licence`);
137
+ }
138
+
139
+ lines.push('Run `quox setup license deactivate` on the instance holding the seat '
140
+ + '(or release it in the quox.ai admin), then retry.');
141
+ } else if (code === 'LICENSE_REQUIRED') {
142
+ const plugin = body.plugin || body.requiredPlugin;
143
+ lines.push(plugin
144
+ ? `A licence entitlement is required for \`${plugin}\`.`
145
+ : 'A licence entitlement is required for this capability.');
146
+ lines.push('Activate a licence that covers it with `quox setup license activate --key-file <path>`, '
147
+ + 'then confirm with `quox setup license status`.');
148
+ } else {
149
+ return err;
150
+ }
151
+
152
+ return new QuoxApiError(err.status, err.code || code, lines.join('\n'), err.detail, err.body);
153
+ }
154
+
155
+ /** Pull whatever bound-server identity the activate response carries. */
156
+ function boundServerFields(lease, data) {
157
+ const out = {};
158
+ for (const key of ['instance_id', 'instance_name', 'bound_instance_name', 'server_id', 'hostname']) {
159
+ const value = lease?.[key] ?? data?.[key];
160
+ if (value != null && value !== '') out[key] = value;
161
+ }
162
+ return out;
163
+ }
164
+
165
+ /**
166
+ * LICENSE-LAUNCH LL-8-dash: how long a `link` run waits between polls when the
167
+ * server sends no interval. The control plane's own floor is 5s.
168
+ */
169
+ const DEFAULT_LINK_INTERVAL_SECONDS = 5;
170
+
171
+ export function registerSetupCommands(program, ctx) {
172
+ const { client, config, auth } = ctx;
173
+ // Injectable so tests drive the poll loop without waiting real seconds
174
+ // (same seam as devSupervisor.js).
175
+ const sleepFn = ctx.sleepFn || ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
176
+
177
+ const setup = program
178
+ .command('setup')
179
+ .description('First-time setup and configuration');
180
+
181
+ setup
182
+ .command('status')
183
+ .description('Check setup status')
184
+ .action(async () => {
185
+ const data = await client.get(resolveEndpoint('setup.status.list').path, { service: resolveEndpoint('setup.status.list').service, noAuth: true });
186
+ formatDetail(data, {
187
+ format: program.opts().output,
188
+ title: 'Setup Status',
189
+ });
190
+ });
191
+
192
+ setup
193
+ .command('admin')
194
+ .description('Create the initial admin account')
195
+ .option('--email <email>', 'admin email')
196
+ .option('--password <password>', 'admin password (visible in the process list; prefer --password-stdin)')
197
+ .option('--password-stdin', 'read the admin password from stdin (first line), never argv')
198
+ .option('--name <name>', 'admin display name')
199
+ .action(async (options) => {
200
+ let { email, password, name } = options;
201
+
202
+ // QUOXON-Z2H lane R: argv is visible in ps and shell history, so a
203
+ // script pipes the password in, same as `quox login --password-stdin`.
204
+ if (options.password && options.passwordStdin) {
205
+ throw new QuoxValidationError('Pass the password with --password or --password-stdin, not both.');
206
+ }
207
+ if (options.passwordStdin) {
208
+ const text = await (ctx.readStdin || readStdinText)();
209
+ password = String(text || '').split('\n')[0].replace(/\r$/, '');
210
+ if (!password) throw new QuoxValidationError('--password-stdin given but stdin was empty.');
211
+ }
212
+
213
+ if (!email || !password || !name) {
214
+ const { default: inquirer } = await import('inquirer');
215
+
216
+ if (!email) {
217
+ const ans = await inquirer.prompt([{
218
+ type: 'input', name: 'email', message: 'Admin email:',
219
+ validate: v => v.includes('@') || 'Enter a valid email',
220
+ }]);
221
+ email = ans.email;
222
+ }
223
+ if (!name) {
224
+ const ans = await inquirer.prompt([{
225
+ type: 'input', name: 'name', message: 'Display name:',
226
+ }]);
227
+ name = ans.name;
228
+ }
229
+ if (!password) {
230
+ const ans = await inquirer.prompt([{
231
+ type: 'password', name: 'password', message: `Password (${PASSWORD_POLICY}):`, mask: '*',
232
+ validate: (v) => { const bad = passwordProblem(v); return bad ? `${bad} ${PASSWORD_POLICY}` : true; },
233
+ }]);
234
+ password = ans.password;
235
+ }
236
+ }
237
+
238
+ // The policy is the FIRST thing a weak password meets, before any
239
+ // server call (the server enforces the same rule; this only says it sooner).
240
+ const problem = passwordProblem(password);
241
+ if (problem) throw new QuoxValidationError(`${PASSWORD_POLICY} ${problem} Nothing was sent to the server.`);
242
+
243
+ // services/auth/routes/setup.js POST /admin destructures `name` (not
244
+ // `display_name`) from req.body. Send both: `name` is what the endpoint
245
+ // reads; `display_name` is kept for forward-compat.
246
+ await client.post(resolveEndpoint('setup.admin').path, {
247
+ service: resolveEndpoint('setup.admin').service,
248
+ body: { email, password, name, display_name: name },
249
+ noAuth: true,
250
+ });
251
+ printSuccess(`Admin account created for ${email}.`);
252
+ printInfo('Run `quox login` to authenticate.');
253
+ });
254
+
255
+ setup
256
+ .command('config')
257
+ .description('Configure platform settings')
258
+ .option('--key <key>', 'config key')
259
+ .option('--value <value>', 'config value')
260
+ .action(async (options) => {
261
+ if (options.key && options.value) {
262
+ await client.post(resolveEndpoint('setup.config').path, {
263
+ service: resolveEndpoint('setup.config').service,
264
+ body: { [options.key]: options.value },
265
+ noAuth: true,
266
+ });
267
+ printSuccess(`Setup config ${options.key} set.`);
268
+ } else {
269
+ const data = await client.get(resolveEndpoint('setup.config.list').path, { service: resolveEndpoint('setup.config.list').service, noAuth: true });
270
+ formatDetail(data.config || data, { format: program.opts().output, title: 'Setup Config' });
271
+ }
272
+ });
273
+
274
+ // ── LICENSE-LAUNCH LL-8: license activation from the terminal ────────────
275
+ // Parity with the Settings > License surface in the dashboard, so a
276
+ // headless instance (the demo box redeploys every 15 minutes and comes
277
+ // back unactivated) can activate itself from a boot script.
278
+ const license = setup
279
+ .command('license')
280
+ .description('Activate, inspect and release this instance licence');
281
+
282
+ license
283
+ .command('activate')
284
+ .description('Activate a license-key-v2 key on this instance (instance admin)')
285
+ .option('--key-file <path>', 'read the license key JSON from a file (keep it 0600)')
286
+ .option('--stdin', 'read the license key JSON from stdin (never touches disk)')
287
+ .action(async (options) => {
288
+ auth.requireAuth();
289
+ const licenseKey = await readLicenseKey(options);
290
+
291
+ const ep = resolveEndpoint('setup.licensing.activate');
292
+ let data;
293
+ try {
294
+ data = await client.post(ep.path, { service: ep.service, body: { license_key: licenseKey } });
295
+ } catch (err) {
296
+ // The 409 body is { error: 'seat_taken'|'seat_limit_reached', ... }
297
+ // with no `message`. Say what the caller can DO, and name the box
298
+ // holding the seat, instead of "Request failed with status 409".
299
+ throw enrichLicenseError(err);
300
+ }
301
+
302
+ const lease = data.lease || {};
303
+ const summary = {
304
+ license_id: lease.license_id || null,
305
+ entitlements: lease.entitlements || [],
306
+ lease_expires_at: lease.expires_at || null,
307
+ ...boundServerFields(lease, data),
308
+ };
309
+ formatDetail(summary, { format: program.opts().output, title: 'Licence activated' });
310
+ });
311
+
312
+ // LICENSE-LAUNCH LL-8-dash: activation with nothing pasted into the box.
313
+ // The instance asks quox.ai for a code pair (through the auth service, so
314
+ // the device_code never leaves the server), prints the short code and the
315
+ // link, and polls until a logged-in customer approves it. Nothing prompts:
316
+ // this is meant to run in a boot script on a headless instance.
317
+ license
318
+ .command('link')
319
+ .description('Activate this instance by approving a short code on quox.ai (no key paste)')
320
+ .action(async () => {
321
+ auth.requireAuth();
322
+ const json = program.opts().output === 'json';
323
+ const emit = (event) => { if (json) console.log(JSON.stringify(event)); };
324
+
325
+ const startEp = resolveEndpoint('setup.licensing.device.start');
326
+ let started;
327
+ try {
328
+ started = await client.post(startEp.path, { service: startEp.service, body: {} });
329
+ } catch (err) {
330
+ throw enrichLicenseError(err);
331
+ }
332
+
333
+ const deviceCode = started.device_code;
334
+ if (!deviceCode || !started.user_code) {
335
+ throw new QuoxApiError(502, 'malformed_device_start',
336
+ 'The instance did not return an activation code pair. Check that it can reach the licence server.');
337
+ }
338
+
339
+ let interval = Number(started.interval) > 0 ? Number(started.interval) : DEFAULT_LINK_INTERVAL_SECONDS;
340
+ const expiresIn = Number(started.expires_in) > 0 ? Number(started.expires_in) : 900;
341
+ const deadline = Date.now() + expiresIn * 1000;
342
+
343
+ emit({
344
+ state: 'started',
345
+ user_code: started.user_code,
346
+ verification_url: started.verification_url || null,
347
+ expires_in: expiresIn,
348
+ interval,
349
+ });
350
+
351
+ if (!json) {
352
+ printInfo(`Open ${started.verification_url || 'https://quox.ai/link'} and enter this code:`);
353
+ console.log('');
354
+ console.log(chalk.bold.cyan(` ${started.user_code}`));
355
+ console.log('');
356
+ printInfo(`The code is good for ${Math.round(expiresIn / 60)} minutes. Waiting for approval...`);
357
+ }
358
+
359
+ const pollEp = resolveEndpoint('setup.licensing.device.poll');
360
+ let lastState = 'started';
361
+
362
+ while (Date.now() < deadline) {
363
+ await sleepFn(interval * 1000);
364
+
365
+ let result;
366
+ try {
367
+ result = await client.post(pollEp.path, {
368
+ service: pollEp.service,
369
+ body: { device_code: deviceCode, interval },
370
+ });
371
+ } catch (err) {
372
+ // 503 means quox.ai blipped, NOT that the activation died: the code
373
+ // is still live upstream and the customer may still be approving it.
374
+ // Anything else (409 seat_limit_reached, 403, 400) is terminal.
375
+ if (err instanceof QuoxApiError && err.status === 503) {
376
+ if (lastState !== 'unreachable') {
377
+ emit({ state: 'unreachable', message: err.message });
378
+ if (!json) printInfo('The licence server is unreachable right now. Still waiting...');
379
+ lastState = 'unreachable';
380
+ }
381
+ continue;
382
+ }
383
+ throw enrichLicenseError(err);
384
+ }
385
+
386
+ const state = result.state || 'pending';
387
+
388
+ if (state === 'slow_down') {
389
+ interval = Number(result.interval) > 0 ? Number(result.interval) : interval + 5;
390
+ } else if (Number(result.interval) > 0) {
391
+ interval = Number(result.interval);
392
+ }
393
+
394
+ if (state !== lastState) {
395
+ emit({ state, interval, ...(result.error ? { error: result.error } : {}) });
396
+ lastState = state;
397
+ }
398
+
399
+ if (state === 'approved') {
400
+ const lease = result.lease || {};
401
+ emit({ state: 'approved', lease });
402
+ if (!json) {
403
+ formatDetail({
404
+ license_id: lease.license_id || null,
405
+ entitlements: lease.entitlements || [],
406
+ lease_expires_at: lease.expires_at || null,
407
+ }, { format: program.opts().output, title: 'Licence activated' });
408
+ printSuccess('This instance is activated. No key was ever pasted into it.');
409
+ }
410
+ return;
411
+ }
412
+
413
+ if (state === 'denied') {
414
+ throw new QuoxApiError(403, 'access_denied',
415
+ 'The activation was denied on quox.ai. Nothing was activated on this instance.');
416
+ }
417
+ if (state === 'expired') {
418
+ throw new QuoxApiError(410, 'expired_token',
419
+ 'The code expired before it was approved. Run `quox setup license link` again for a fresh one.');
420
+ }
421
+ if (state === 'consumed') {
422
+ throw new QuoxApiError(409, 'consumed',
423
+ 'That code was already used. Run `quox setup license link` again for a fresh one.');
424
+ }
425
+ if (state === 'activation_failed') {
426
+ throw new QuoxApiError(502, result.error || 'activation_failed',
427
+ `The approval landed but the licence could not be stored on this instance: ${result.message || result.error || 'unknown error'}. `
428
+ + 'Run `quox setup license link` again.');
429
+ }
430
+ if (state === 'error') {
431
+ throw new QuoxApiError(502, result.error || 'device_poll_failed',
432
+ result.message || 'Device activation failed.');
433
+ }
434
+ }
435
+
436
+ throw new QuoxApiError(410, 'expired_token',
437
+ 'The code expired before it was approved. Run `quox setup license link` again for a fresh one.');
438
+ });
439
+
440
+ license
441
+ .command('status')
442
+ .description('Show this instance licence state: entitlements, lease expiry, freshness')
443
+ .action(async () => {
444
+ auth.requireAuth();
445
+ // Same read the dashboard licence gate uses
446
+ // (src/services/licenseValidator.js -> GET /api/setup/licensing/entitlements):
447
+ // verified, instance-bound, unexpired leases only.
448
+ const ep = resolveEndpoint('setup.licensing.entitlements.list');
449
+ let data;
450
+ try {
451
+ data = await client.get(ep.path, { service: ep.service });
452
+ } catch (err) {
453
+ throw enrichLicenseError(err);
454
+ }
455
+
456
+ const now = Date.now();
457
+ const licenses = (data.licenses || []).map((row) => {
458
+ const leaseExpiry = Date.parse(row.lease_expires_at);
459
+ let leaseStatus = 'unknown';
460
+ if (Number.isFinite(leaseExpiry)) {
461
+ const hours = Math.round((leaseExpiry - now) / 3600000);
462
+ leaseStatus = hours > 0 ? `fresh, renews within ${hours}h` : `stale, expired ${Math.abs(hours)}h ago`;
463
+ }
464
+ return {
465
+ license_id: row.license_id,
466
+ entitlements: row.entitlements || [],
467
+ expires_at: row.expires_at || null,
468
+ lease_expires_at: row.lease_expires_at || null,
469
+ lease_status: leaseStatus,
470
+ };
471
+ });
472
+
473
+ const summary = {
474
+ activated: licenses.length > 0,
475
+ instance_id: data.instance_id || null,
476
+ entitlements: data.entitlements || [],
477
+ licenses,
478
+ };
479
+
480
+ // LICENSE-LAUNCH LL-4c: the response carries the signed product catalogue
481
+ // (display metadata, present only once the owner installs the artifact
482
+ // generated on quox.ai) and the LL-3b org bands. Both are rendered as
483
+ // ADDITIVE fields: absent server-side means absent here, never a guessed
484
+ // tier or a fabricated band, and every pre-existing field is untouched so
485
+ // scripts parsing --json keep working.
486
+ const catalogTiers = new Map(
487
+ (Array.isArray(data.catalog) ? data.catalog : [])
488
+ .filter((row) => row && typeof row.id === 'string' && row.tier)
489
+ .map((row) => [row.id, row.tier])
490
+ );
491
+ if (catalogTiers.size > 0) {
492
+ summary.entitlement_tiers = summary.entitlements.map((id) => {
493
+ const tier = catalogTiers.get(id);
494
+ return tier ? `${id} (${tier})` : id;
495
+ });
496
+ }
497
+
498
+ const bands = (data.org_bands && typeof data.org_bands === 'object') ? data.org_bands : {};
499
+ const bandIds = Object.keys(bands);
500
+ if (bandIds.length > 0) {
501
+ if (data.org_id) summary.org_id = data.org_id;
502
+ summary.org_bands = bandIds.map((id) => {
503
+ const band = bands[id] || {};
504
+ // org_licensed is tri-state: true/false for a caller with org
505
+ // context, null for an instance-scoped session (see hasPlugin).
506
+ const state = band.org_licensed === true ? 'this org licensed'
507
+ : band.org_licensed === false ? 'this org NOT licensed'
508
+ : 'no org context';
509
+ return `${id}: ${band.assigned_count ?? 0} of ${band.allowance ?? 0} orgs (${state})`;
510
+ });
511
+ }
512
+
513
+ formatDetail(summary, { format: program.opts().output, title: 'Licence Status' });
514
+ });
515
+
516
+ license
517
+ .command('deactivate')
518
+ .description('Release this instance seat so the licence can be activated elsewhere')
519
+ .option('--license-id <id>', 'licence to release (required when several are held)')
520
+ .option('--yes', 'skip the confirmation prompt (for boot scripts and CI)')
521
+ .action(async (options) => {
522
+ auth.requireAuth();
523
+
524
+ let licenseId = options.licenseId;
525
+ if (!licenseId) {
526
+ const readEp = resolveEndpoint('setup.licensing.entitlements.list');
527
+ let state;
528
+ try {
529
+ state = await client.get(readEp.path, { service: readEp.service });
530
+ } catch (err) {
531
+ throw enrichLicenseError(err);
532
+ }
533
+ const held = state.licenses || [];
534
+ if (held.length === 0) {
535
+ throw new QuoxValidationError('No active license on this instance, so there is no seat to release.');
536
+ }
537
+ if (held.length > 1) {
538
+ throw new QuoxValidationError(
539
+ `This instance holds ${held.length} licences (${held.map(l => l.license_id).join(', ')}). `
540
+ + 'Pass --license-id <id> to say which seat to release.'
541
+ );
542
+ }
543
+ licenseId = held[0].license_id;
544
+ }
545
+
546
+ const confirmed = await confirmDestructive(
547
+ program,
548
+ { force: options.yes },
549
+ `Release the seat for ${licenseId} on this instance?`
550
+ );
551
+ if (!confirmed) {
552
+ printInfo('Aborted.');
553
+ return;
554
+ }
555
+
556
+ const ep = resolveEndpoint('setup.licensing.deactivate');
557
+ let data;
558
+ try {
559
+ data = await client.post(ep.path, { service: ep.service, body: { license_id: licenseId } });
560
+ } catch (err) {
561
+ throw enrichLicenseError(err);
562
+ }
563
+ printSuccess(
564
+ data.remote
565
+ ? `Seat released for ${licenseId} (control plane updated).`
566
+ : `Seat released locally for ${licenseId}. The control plane was not reachable, so the seat may still be held remotely.`
567
+ );
568
+ });
569
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * SKILLRIGHTS #478 Phase 10 — local SkillRights manifest construction for
3
+ * `quox skills protect`.
4
+ *
5
+ * The skill's CONTENT never leaves the machine: this builds the same
6
+ * `.skillrights.manifest.json` the open `skillrights` CLI produces (per-file
7
+ * SHA-256s plus a canonical-JSON hash of the core), and only that manifest
8
+ * is sent to the collector.
9
+ *
10
+ * CONSUMES THE OPEN FORMAT, NEVER FORKS IT: if the open CLI has already
11
+ * written a manifest in the directory, that file is used verbatim. The
12
+ * builder below exists only so a user without the open CLI installed can
13
+ * still protect a skill; its algorithm MUST stay byte-identical to
14
+ * skillrights/cli/lib/sign.js + lib/hash.js (canonical JSON = recursively
15
+ * sorted keys, no whitespace). The collector re-derives the hash from the
16
+ * manifest core and rejects a mismatch, so drift fails loudly rather than
17
+ * silently registering a hash nobody else can reproduce.
18
+ */
19
+
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
22
+ import { createHash } from 'node:crypto';
23
+
24
+ export const MANIFEST_NAME = '.skillrights.manifest.json';
25
+ export const SIG_NAME = `${MANIFEST_NAME}.sig`;
26
+ const RECEIPT_NAME = '.skillrights.receipt.json';
27
+ const EXCLUDE_DIRS = new Set(['.git', 'node_modules']);
28
+
29
+ function canonicalize(value) {
30
+ if (Array.isArray(value)) return value.map(canonicalize);
31
+ if (value && typeof value === 'object') {
32
+ const sorted = {};
33
+ for (const key of Object.keys(value).sort()) sorted[key] = canonicalize(value[key]);
34
+ return sorted;
35
+ }
36
+ return value;
37
+ }
38
+
39
+ export function canonicalJSON(value) {
40
+ return JSON.stringify(canonicalize(value));
41
+ }
42
+
43
+ export function sha256Hex(input) {
44
+ return createHash('sha256').update(input).digest('hex');
45
+ }
46
+
47
+ function walkFiles(rootDir, exclude) {
48
+ const results = [];
49
+ (function walk(dir) {
50
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
51
+ if (entry.isDirectory() && EXCLUDE_DIRS.has(entry.name)) continue;
52
+ const full = path.join(dir, entry.name);
53
+ const rel = path.relative(rootDir, full).split(path.sep).join('/');
54
+ if (exclude.has(rel)) continue;
55
+ if (entry.isDirectory()) walk(full);
56
+ else if (entry.isFile()) results.push(rel);
57
+ }
58
+ })(rootDir);
59
+ results.sort();
60
+ return results;
61
+ }
62
+
63
+ /**
64
+ * Reads YAML-ish frontmatter from SKILL.md, matching the open CLI's parser
65
+ * scope: a leading `---` block, `key: value` lines, optional quotes.
66
+ */
67
+ export function readSkillFrontmatter(dir) {
68
+ const file = path.join(dir, 'SKILL.md');
69
+ if (!fs.existsSync(file)) return {};
70
+ const text = fs.readFileSync(file, 'utf8');
71
+ const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
72
+ if (!match) return {};
73
+ const fields = {};
74
+ for (const line of match[1].split(/\r?\n/)) {
75
+ const kv = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
76
+ if (!kv) continue;
77
+ let value = kv[2].trim();
78
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
79
+ value = value.slice(1, -1);
80
+ }
81
+ fields[kv[1]] = value;
82
+ }
83
+ return fields;
84
+ }
85
+
86
+ /**
87
+ * Returns { manifest, source } where source is 'existing' (written by the
88
+ * open skillrights CLI) or 'computed' (built here, not written to disk).
89
+ */
90
+ export function loadOrBuildManifest(dir) {
91
+ const manifestPath = path.join(dir, MANIFEST_NAME);
92
+ if (fs.existsSync(manifestPath)) {
93
+ return { manifest: JSON.parse(fs.readFileSync(manifestPath, 'utf8')), source: 'existing' };
94
+ }
95
+
96
+ const exclude = new Set([MANIFEST_NAME, SIG_NAME, RECEIPT_NAME]);
97
+ const files = walkFiles(dir, exclude);
98
+ if (files.length === 0) throw new Error(`No files to protect in ${dir}`);
99
+
100
+ const fileHashes = {};
101
+ for (const rel of files) {
102
+ fileHashes[rel] = sha256Hex(fs.readFileSync(path.join(dir, rel)));
103
+ }
104
+ const front = readSkillFrontmatter(dir);
105
+ const core = {
106
+ version: 1,
107
+ created: new Date().toISOString(),
108
+ identifier: front.license || null,
109
+ author: front.author || null,
110
+ files: fileHashes,
111
+ };
112
+ return { manifest: { ...core, manifestSha256: sha256Hex(canonicalJSON(core)) }, source: 'computed' };
113
+ }