quoxcli 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +142 -2
  3. package/bin/quox.js +292 -0
  4. package/bin/quoxon.js +8 -0
  5. package/lib/api/endpoints/admin.js +125 -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/agents.js +56 -0
  9. package/lib/api/endpoints/assistants.js +50 -0
  10. package/lib/api/endpoints/audit.js +14 -0
  11. package/lib/api/endpoints/auth.js +50 -0
  12. package/lib/api/endpoints/awareness.js +34 -0
  13. package/lib/api/endpoints/backup-schedules.js +20 -0
  14. package/lib/api/endpoints/backups.js +86 -0
  15. package/lib/api/endpoints/brain.js +140 -0
  16. package/lib/api/endpoints/budgets.js +62 -0
  17. package/lib/api/endpoints/calendarq.js +71 -0
  18. package/lib/api/endpoints/catalog.js +14 -0
  19. package/lib/api/endpoints/chat.js +54 -0
  20. package/lib/api/endpoints/consent.js +26 -0
  21. package/lib/api/endpoints/contactsq.js +66 -0
  22. package/lib/api/endpoints/conversations.js +32 -0
  23. package/lib/api/endpoints/custom-roles.js +92 -0
  24. package/lib/api/endpoints/divisions.js +56 -0
  25. package/lib/api/endpoints/domains.js +38 -0
  26. package/lib/api/endpoints/engagement.js +231 -0
  27. package/lib/api/endpoints/engine.js +11 -0
  28. package/lib/api/endpoints/entity.js +50 -0
  29. package/lib/api/endpoints/executions.js +32 -0
  30. package/lib/api/endpoints/file.js +20 -0
  31. package/lib/api/endpoints/files.js +26 -0
  32. package/lib/api/endpoints/github.js +21 -0
  33. package/lib/api/endpoints/governance.js +43 -0
  34. package/lib/api/endpoints/hat.js +36 -0
  35. package/lib/api/endpoints/inbox.js +32 -0
  36. package/lib/api/endpoints/inboxq.js +120 -0
  37. package/lib/api/endpoints/index.js +228 -0
  38. package/lib/api/endpoints/invites.js +19 -0
  39. package/lib/api/endpoints/llm-policy.js +43 -0
  40. package/lib/api/endpoints/lucid.js +89 -0
  41. package/lib/api/endpoints/members.js +32 -0
  42. package/lib/api/endpoints/memory.js +75 -0
  43. package/lib/api/endpoints/mfa.js +32 -0
  44. package/lib/api/endpoints/mon.js +178 -0
  45. package/lib/api/endpoints/notesq.js +43 -0
  46. package/lib/api/endpoints/objectives.js +62 -0
  47. package/lib/api/endpoints/orgs.js +38 -0
  48. package/lib/api/endpoints/plugins.js +134 -0
  49. package/lib/api/endpoints/powers.js +75 -0
  50. package/lib/api/endpoints/prompts.js +69 -0
  51. package/lib/api/endpoints/qlarity.js +122 -0
  52. package/lib/api/endpoints/quoxbox.js +55 -0
  53. package/lib/api/endpoints/quoxchat.js +54 -0
  54. package/lib/api/endpoints/quoxflow.js +32 -0
  55. package/lib/api/endpoints/quoxplan.js +114 -0
  56. package/lib/api/endpoints/quoxseo.js +108 -0
  57. package/lib/api/endpoints/reach.js +47 -0
  58. package/lib/api/endpoints/runs.js +38 -0
  59. package/lib/api/endpoints/safety.js +46 -0
  60. package/lib/api/endpoints/scim.js +46 -0
  61. package/lib/api/endpoints/scopes.js +14 -0
  62. package/lib/api/endpoints/security.js +69 -0
  63. package/lib/api/endpoints/setup.js +90 -0
  64. package/lib/api/endpoints/skills.js +180 -0
  65. package/lib/api/endpoints/sso.js +67 -0
  66. package/lib/api/endpoints/tags.js +32 -0
  67. package/lib/api/endpoints/teams.js +56 -0
  68. package/lib/api/endpoints/telephony.js +46 -0
  69. package/lib/api/endpoints/tg.js +56 -0
  70. package/lib/api/endpoints/tools.js +50 -0
  71. package/lib/api/endpoints/traces.js +157 -0
  72. package/lib/api/endpoints/triggers.js +70 -0
  73. package/lib/api/endpoints/user-library.js +41 -0
  74. package/lib/api/endpoints/users.js +20 -0
  75. package/lib/api/endpoints/v1.js +499 -0
  76. package/lib/api/endpoints/vault.js +192 -0
  77. package/lib/api/endpoints/voice.js +22 -0
  78. package/lib/api/endpoints/volt.js +84 -0
  79. package/lib/api/endpoints/ward.js +40 -0
  80. package/lib/api/endpoints/webhooks.js +46 -0
  81. package/lib/api/endpoints/workflows.js +162 -0
  82. package/lib/api/endpoints/x.js +24 -0
  83. package/lib/api/endpoints.js +15 -0
  84. package/lib/commands/admin.js +359 -0
  85. package/lib/commands/aee.js +99 -0
  86. package/lib/commands/agent.js +201 -0
  87. package/lib/commands/agentAttach.js +1440 -0
  88. package/lib/commands/approvals.js +47 -0
  89. package/lib/commands/assistant.js +114 -0
  90. package/lib/commands/auth.js +458 -0
  91. package/lib/commands/awareness.js +88 -0
  92. package/lib/commands/backup.js +373 -0
  93. package/lib/commands/brain.js +994 -0
  94. package/lib/commands/budget.js +241 -0
  95. package/lib/commands/build.js +156 -0
  96. package/lib/commands/calendar.js +216 -0
  97. package/lib/commands/capabilities.js +34 -0
  98. package/lib/commands/chat.js +240 -0
  99. package/lib/commands/claude.js +354 -0
  100. package/lib/commands/config-cmd.js +106 -0
  101. package/lib/commands/consent.js +84 -0
  102. package/lib/commands/contacts.js +967 -0
  103. package/lib/commands/contactsSelftest.js +132 -0
  104. package/lib/commands/conversation.js +74 -0
  105. package/lib/commands/department.js +75 -0
  106. package/lib/commands/dev.js +620 -0
  107. package/lib/commands/devResident.js +314 -0
  108. package/lib/commands/devSupervisor.js +1050 -0
  109. package/lib/commands/discord.js +96 -0
  110. package/lib/commands/doctor.js +181 -0
  111. package/lib/commands/domain.js +114 -0
  112. package/lib/commands/done.js +277 -0
  113. package/lib/commands/engagement.js +586 -0
  114. package/lib/commands/entity.js +119 -0
  115. package/lib/commands/facebook.js +212 -0
  116. package/lib/commands/file.js +128 -0
  117. package/lib/commands/fleet.js +882 -0
  118. package/lib/commands/governance.js +638 -0
  119. package/lib/commands/gripe.js +324 -0
  120. package/lib/commands/hat.js +205 -0
  121. package/lib/commands/helpers.js +79 -0
  122. package/lib/commands/inbox.js +122 -0
  123. package/lib/commands/instagram.js +264 -0
  124. package/lib/commands/integration.js +100 -0
  125. package/lib/commands/inventory.js +115 -0
  126. package/lib/commands/invites.js +45 -0
  127. package/lib/commands/licence.js +280 -0
  128. package/lib/commands/linkedin.js +178 -0
  129. package/lib/commands/llm-policy.js +117 -0
  130. package/lib/commands/llm.js +45 -0
  131. package/lib/commands/lucid.js +591 -0
  132. package/lib/commands/mail.js +531 -0
  133. package/lib/commands/memory.js +284 -0
  134. package/lib/commands/mon.js +514 -0
  135. package/lib/commands/monitoring.js +86 -0
  136. package/lib/commands/motion.js +22 -0
  137. package/lib/commands/notes.js +1246 -0
  138. package/lib/commands/notesBatch.js +160 -0
  139. package/lib/commands/notesBridges.js +56 -0
  140. package/lib/commands/notesLinks.js +60 -0
  141. package/lib/commands/notification.js +57 -0
  142. package/lib/commands/nucleus.js +92 -0
  143. package/lib/commands/objective.js +229 -0
  144. package/lib/commands/onboard.js +328 -0
  145. package/lib/commands/open.js +56 -0
  146. package/lib/commands/org.js +266 -0
  147. package/lib/commands/plan.js +906 -0
  148. package/lib/commands/plugin-doctor.js +1091 -0
  149. package/lib/commands/plugin.js +539 -0
  150. package/lib/commands/policy.js +78 -0
  151. package/lib/commands/powers.js +296 -0
  152. package/lib/commands/prompt.js +350 -0
  153. package/lib/commands/qlarity.js +690 -0
  154. package/lib/commands/quoxbox.js +270 -0
  155. package/lib/commands/quoxchat.js +266 -0
  156. package/lib/commands/quoxseo.js +340 -0
  157. package/lib/commands/reach.js +158 -0
  158. package/lib/commands/role.js +286 -0
  159. package/lib/commands/run.js +132 -0
  160. package/lib/commands/safety.js +107 -0
  161. package/lib/commands/scim.js +88 -0
  162. package/lib/commands/scope.js +58 -0
  163. package/lib/commands/security.js +157 -0
  164. package/lib/commands/service.js +89 -0
  165. package/lib/commands/settings.js +99 -0
  166. package/lib/commands/setup.js +543 -0
  167. package/lib/commands/skillManifest.js +113 -0
  168. package/lib/commands/skillRightsFormat.js +38 -0
  169. package/lib/commands/skills.js +644 -0
  170. package/lib/commands/slot.js +1198 -0
  171. package/lib/commands/sso.js +229 -0
  172. package/lib/commands/status.js +137 -0
  173. package/lib/commands/tag.js +91 -0
  174. package/lib/commands/team.js +140 -0
  175. package/lib/commands/tg.js +240 -0
  176. package/lib/commands/tiktok.js +218 -0
  177. package/lib/commands/tool.js +216 -0
  178. package/lib/commands/trace.js +525 -0
  179. package/lib/commands/trigger.js +128 -0
  180. package/lib/commands/vault.js +1424 -0
  181. package/lib/commands/voice.js +182 -0
  182. package/lib/commands/volt.js +528 -0
  183. package/lib/commands/watch.js +115 -0
  184. package/lib/commands/webhook.js +137 -0
  185. package/lib/commands/workflow.js +503 -0
  186. package/lib/commands/x.js +439 -0
  187. package/lib/core/actingAs.js +136 -0
  188. package/lib/core/aee.js +53 -0
  189. package/lib/core/agentAlias.js +163 -0
  190. package/lib/core/approvalContext.js +61 -0
  191. package/lib/core/auth.js +350 -0
  192. package/lib/core/banner.js +143 -0
  193. package/lib/core/brainPrime.js +90 -0
  194. package/lib/core/callerSource.js +129 -0
  195. package/lib/core/claudeLs.js +94 -0
  196. package/lib/core/client.js +584 -0
  197. package/lib/core/completions.js +163 -0
  198. package/lib/core/config.js +551 -0
  199. package/lib/core/confirm.js +37 -0
  200. package/lib/core/deeplinks.js +76 -0
  201. package/lib/core/doneEngineFetch.js +219 -0
  202. package/lib/core/errors.js +132 -0
  203. package/lib/core/global-flags.js +90 -0
  204. package/lib/core/governedSession.js +402 -0
  205. package/lib/core/lazyCommands.js +119 -0
  206. package/lib/core/liveSessionsReport.js +112 -0
  207. package/lib/core/org-override.js +52 -0
  208. package/lib/core/output.js +239 -0
  209. package/lib/core/prodEnv.js +7 -0
  210. package/lib/core/quoxglyph.js +158 -0
  211. package/lib/core/residentAlarm.js +282 -0
  212. package/lib/core/residentAuth.js +243 -0
  213. package/lib/core/residentPump.js +229 -0
  214. package/lib/core/residentSeed.js +104 -0
  215. package/lib/core/sessionHeartbeat.js +192 -0
  216. package/lib/core/sessionsOverview.js +160 -0
  217. package/lib/core/slotKeeper.js +282 -0
  218. package/lib/core/supervisorEngine.js +864 -0
  219. package/lib/core/systemdInstaller.js +256 -0
  220. package/lib/core/version.js +87 -0
  221. package/lib/core/websiteSession.js +148 -0
  222. package/lib/lucid/LucidIntro.jsx +105 -0
  223. package/lib/lucid/blind.js +71 -0
  224. package/lib/lucid/deck.js +32 -0
  225. package/lib/lucid/fmt.js +139 -0
  226. package/lib/lucid/sitrep.js +173 -0
  227. package/lib/plugins/loader.js +93 -0
  228. package/lib/quoxon/activity.js +104 -0
  229. package/lib/quoxon/api.js +473 -0
  230. package/lib/quoxon/app.js +1262 -0
  231. package/lib/quoxon/capabilities.js +75 -0
  232. package/lib/quoxon/commands.js +154 -0
  233. package/lib/quoxon/connect.js +467 -0
  234. package/lib/quoxon/connectCard.js +28 -0
  235. package/lib/quoxon/context.js +68 -0
  236. package/lib/quoxon/copy.js +33 -0
  237. package/lib/quoxon/demo.fixtures.js +24 -0
  238. package/lib/quoxon/demo.js +35 -0
  239. package/lib/quoxon/egg.js +36 -0
  240. package/lib/quoxon/events.js +458 -0
  241. package/lib/quoxon/gallery.js +223 -0
  242. package/lib/quoxon/indicator.js +193 -0
  243. package/lib/quoxon/intent.js +41 -0
  244. package/lib/quoxon/keys.js +121 -0
  245. package/lib/quoxon/main.js +244 -0
  246. package/lib/quoxon/markdown.js +317 -0
  247. package/lib/quoxon/model.js +205 -0
  248. package/lib/quoxon/motion/clock.js +69 -0
  249. package/lib/quoxon/motion/color.js +35 -0
  250. package/lib/quoxon/motion/frame.js +14 -0
  251. package/lib/quoxon/motion/reveal.js +54 -0
  252. package/lib/quoxon/motion/ring.js +79 -0
  253. package/lib/quoxon/objects/agent.js +61 -0
  254. package/lib/quoxon/objects/crystal.js +100 -0
  255. package/lib/quoxon/objects/fetch.js +170 -0
  256. package/lib/quoxon/objects/index.js +85 -0
  257. package/lib/quoxon/objects/ladder.js +71 -0
  258. package/lib/quoxon/objects/memory.js +44 -0
  259. package/lib/quoxon/objects/model.js +118 -0
  260. package/lib/quoxon/objects/proof.js +110 -0
  261. package/lib/quoxon/objects/skill.js +105 -0
  262. package/lib/quoxon/objects/view.js +76 -0
  263. package/lib/quoxon/objects/workflow.js +119 -0
  264. package/lib/quoxon/observers.js +42 -0
  265. package/lib/quoxon/onboarding/copy.js +235 -0
  266. package/lib/quoxon/onboarding/credential.js +95 -0
  267. package/lib/quoxon/onboarding/detect.js +22 -0
  268. package/lib/quoxon/onboarding/finish.js +119 -0
  269. package/lib/quoxon/onboarding/index.js +67 -0
  270. package/lib/quoxon/onboarding/installRunner.js +225 -0
  271. package/lib/quoxon/onboarding/intent.js +53 -0
  272. package/lib/quoxon/onboarding/journey.js +73 -0
  273. package/lib/quoxon/onboarding/preflight.js +142 -0
  274. package/lib/quoxon/onboarding/runner.js +58 -0
  275. package/lib/quoxon/onboarding/view.js +625 -0
  276. package/lib/quoxon/print.js +94 -0
  277. package/lib/quoxon/qart.js +379 -0
  278. package/lib/quoxon/qglyph.js +143 -0
  279. package/lib/quoxon/qhello.js +58 -0
  280. package/lib/quoxon/qmotion.js +111 -0
  281. package/lib/quoxon/render.js +648 -0
  282. package/lib/quoxon/roster.js +221 -0
  283. package/lib/quoxon/route.js +38 -0
  284. package/lib/quoxon/rules.js +130 -0
  285. package/lib/quoxon/session.js +68 -0
  286. package/lib/quoxon/skills.js +30 -0
  287. package/lib/quoxon/slash/compact.js +90 -0
  288. package/lib/quoxon/slash/ledger.js +83 -0
  289. package/lib/quoxon/slash/listView.js +28 -0
  290. package/lib/quoxon/slash/palette.js +104 -0
  291. package/lib/quoxon/slash/paletteController.js +59 -0
  292. package/lib/quoxon/slash/panels.js +101 -0
  293. package/lib/quoxon/slash/picker.js +38 -0
  294. package/lib/quoxon/slash/restart.js +144 -0
  295. package/lib/quoxon/slash/resume.js +59 -0
  296. package/lib/quoxon/slash/settings.js +50 -0
  297. package/lib/quoxon/sound/index.js +186 -0
  298. package/lib/quoxon/sound/mapping.js +119 -0
  299. package/lib/quoxon/sound/output.js +52 -0
  300. package/lib/quoxon/sound/synth.js +122 -0
  301. package/lib/quoxon/term.js +106 -0
  302. package/lib/quoxon/textBuffer.js +242 -0
  303. package/lib/quoxon/theme.js +253 -0
  304. package/lib/quoxon/transcript.js +150 -0
  305. package/lib/quoxon/web/qface.js +81 -0
  306. package/lib/quoxon/welcome.js +52 -0
  307. package/lib/tui/app.jsx +289 -0
  308. package/lib/tui/components/Breadcrumb.jsx +42 -0
  309. package/lib/tui/components/ChatInput.jsx +68 -0
  310. package/lib/tui/components/Confirm.jsx +32 -0
  311. package/lib/tui/components/DataTable.jsx +270 -0
  312. package/lib/tui/components/DetailPanel.jsx +241 -0
  313. package/lib/tui/components/HelpOverlay.jsx +67 -0
  314. package/lib/tui/components/Layout.jsx +129 -0
  315. package/lib/tui/components/Palette.jsx +141 -0
  316. package/lib/tui/components/SearchInput.jsx +125 -0
  317. package/lib/tui/components/Sparkline.jsx +55 -0
  318. package/lib/tui/components/StatusBar.jsx +133 -0
  319. package/lib/tui/components/Toast.jsx +52 -0
  320. package/lib/tui/hooks/useApi.js +44 -0
  321. package/lib/tui/hooks/useHealth.js +90 -0
  322. package/lib/tui/hooks/usePolledApi.js +86 -0
  323. package/lib/tui/hooks/useTerminalSize.js +46 -0
  324. package/lib/tui/index.js +56 -0
  325. package/lib/tui/intro/Intro.jsx +332 -0
  326. package/lib/tui/jsx-loader.js +32 -0
  327. package/lib/tui/keys.js +87 -0
  328. package/lib/tui/registry.js +49 -0
  329. package/lib/tui/theme.js +103 -0
  330. package/lib/tui/views/AdminView.jsx +398 -0
  331. package/lib/tui/views/AgentView.jsx +204 -0
  332. package/lib/tui/views/BackupView.jsx +225 -0
  333. package/lib/tui/views/BrainView.jsx +223 -0
  334. package/lib/tui/views/ChatView.jsx +468 -0
  335. package/lib/tui/views/FilesView.jsx +218 -0
  336. package/lib/tui/views/FleetView.jsx +234 -0
  337. package/lib/tui/views/GovernanceView.jsx +453 -0
  338. package/lib/tui/views/HealthView.jsx +216 -0
  339. package/lib/tui/views/InboxView.jsx +228 -0
  340. package/lib/tui/views/LucidDeck.jsx +727 -0
  341. package/lib/tui/views/MainMenu.jsx +123 -0
  342. package/lib/tui/views/MemoryView.jsx +377 -0
  343. package/lib/tui/views/ObjectivesView.jsx +230 -0
  344. package/lib/tui/views/OrgView.jsx +229 -0
  345. package/lib/tui/views/ToolView.jsx +221 -0
  346. package/lib/tui/views/VaultView.jsx +422 -0
  347. package/lib/tui/views/WorkflowView.jsx +329 -0
  348. package/package.json +72 -4
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Role commands: org custom roles (RBAC) + agent/tool permission rules
3
+ *
4
+ * quox role list|get|create|delete
5
+ * quox role capability add|remove
6
+ * quox role agent list|set|clear
7
+ * quox role tool list|set|clear
8
+ *
9
+ * Backend: services/auth/routes/customRoles.js, mounted at plain `/api`
10
+ * (org-scoped IN the URL: /api/orgs/:orgId/custom-roles...). Enterprise-tier
11
+ * + ORG_MANAGE_ROLES gated — expect 402/403 on non-enterprise orgs.
12
+ *
13
+ * Capability grants are subject to a server-side privilege-escalation
14
+ * guard: for capabilities in the known Capabilities enum, the caller must
15
+ * already hold the capability themselves or the grant is rejected with
16
+ * `forbidden_escalation` (customRoles.js:166-188). Free-form custom
17
+ * capability strings bypass this check (org-defined extensions).
18
+ */
19
+
20
+ import { formatOutput, formatDetail, printSuccess, printInfo } from '../core/output.js';
21
+ import { resolveOrgId } from './helpers.js';
22
+ import { resolveEndpoint } from '../api/endpoints.js';
23
+
24
+ export function registerRoleCommands(program, ctx) {
25
+ const { client, auth } = ctx;
26
+
27
+ const role = program
28
+ .command('role')
29
+ .description('Manage org custom roles (enterprise — RBAC)');
30
+
31
+ // ── role list ────────────────────────────────────────────────────────────────
32
+
33
+ role
34
+ .command('list')
35
+ .description('List custom roles for the active org')
36
+ .action(async () => {
37
+ auth.requireAuth();
38
+ const orgId = resolveOrgId(auth, program);
39
+ const ep = resolveEndpoint('custom-roles.list', { orgId });
40
+ const data = await client.get(ep.path, { service: ep.service });
41
+ const rows = data.roles || data || [];
42
+
43
+ formatOutput({
44
+ columns: ['id', 'slug', 'name', 'description', 'created_at'],
45
+ headers: ['ID', 'Slug', 'Name', 'Description', 'Created'],
46
+ rows,
47
+ }, { format: program.opts().output, emptyMessage: 'No custom roles configured.' });
48
+ });
49
+
50
+ // ── role get ─────────────────────────────────────────────────────────────────
51
+
52
+ role
53
+ .command('get')
54
+ .description('Show a custom role and its granted capabilities')
55
+ .argument('<roleId>', 'role ID')
56
+ .action(async (roleId) => {
57
+ auth.requireAuth();
58
+ const orgId = resolveOrgId(auth, program);
59
+ const ep = resolveEndpoint('custom-roles.get', { orgId, roleId });
60
+ const data = await client.get(ep.path, { service: ep.service });
61
+
62
+ formatDetail(data.role || {}, {
63
+ format: program.opts().output,
64
+ title: `Role: ${data.role?.name || roleId}`,
65
+ fields: ['id', 'slug', 'name', 'description', 'created_at'],
66
+ });
67
+ const capabilities = data.capabilities || [];
68
+ if (program.opts().output !== 'json') {
69
+ formatOutput({
70
+ columns: ['capability', 'scope_id'],
71
+ headers: ['Capability', 'Scope'],
72
+ rows: capabilities,
73
+ }, { format: program.opts().output, emptyMessage: 'No capabilities granted.' });
74
+ }
75
+ });
76
+
77
+ // ── role create ──────────────────────────────────────────────────────────────
78
+
79
+ role
80
+ .command('create')
81
+ .description('Create a custom role')
82
+ .requiredOption('--slug <slug>', 'unique role slug')
83
+ .requiredOption('--name <name>', 'role name')
84
+ .option('--description <text>', 'role description')
85
+ .action(async (options) => {
86
+ auth.requireAuth();
87
+ const orgId = resolveOrgId(auth, program);
88
+ const body = { slug: options.slug, name: options.name };
89
+ if (options.description) body.description = options.description;
90
+
91
+ const ep = resolveEndpoint('custom-roles.create', { orgId });
92
+ const data = await client.post(ep.path, { service: ep.service, body });
93
+ printSuccess(`Role "${options.name}" created.`);
94
+ formatDetail(data.role || data, { format: program.opts().output });
95
+ });
96
+
97
+ // ── role delete ──────────────────────────────────────────────────────────────
98
+
99
+ role
100
+ .command('delete')
101
+ .description('Delete a custom role')
102
+ .argument('<roleId>', 'role ID')
103
+ .option('--force', 'skip confirmation')
104
+ .action(async (roleId, options) => {
105
+ auth.requireAuth();
106
+ const orgId = resolveOrgId(auth, program);
107
+
108
+ if (!options.force && !program.opts().yes) {
109
+ const { default: inquirer } = await import('inquirer');
110
+ const ans = await inquirer.prompt([{
111
+ type: 'confirm',
112
+ name: 'confirm',
113
+ message: `Delete role ${roleId}? Members assigned this role lose its capabilities immediately.`,
114
+ default: false,
115
+ }]);
116
+ if (!ans.confirm) {
117
+ printInfo('Cancelled.');
118
+ return;
119
+ }
120
+ }
121
+
122
+ const ep = resolveEndpoint('custom-roles.delete', { orgId, roleId });
123
+ await client.delete(ep.path, { service: ep.service });
124
+ printSuccess('Role deleted.');
125
+ });
126
+
127
+ // ── role capability ─────────────────────────────────────────────────────────
128
+
129
+ const capability = role
130
+ .command('capability')
131
+ .description('Manage capability grants on a role');
132
+
133
+ capability
134
+ .command('add')
135
+ .description('Grant a capability to a role')
136
+ .argument('<roleId>', 'role ID')
137
+ .argument('<capability>', 'capability string (e.g. "agents.manage")')
138
+ .option('--scope-id <scopeId>', 'restrict the grant to a specific scope')
139
+ .action(async (roleId, cap, options) => {
140
+ auth.requireAuth();
141
+ const orgId = resolveOrgId(auth, program);
142
+ const body = { capability: cap };
143
+ if (options.scopeId) body.scope_id = options.scopeId;
144
+
145
+ const ep = resolveEndpoint('custom-roles.capabilities.add', { orgId, roleId });
146
+ await client.post(ep.path, { service: ep.service, body });
147
+ printSuccess(`Capability "${cap}" granted.`);
148
+ });
149
+
150
+ capability
151
+ .command('remove')
152
+ .description('Revoke a capability from a role')
153
+ .argument('<roleId>', 'role ID')
154
+ .argument('<capability>', 'capability string')
155
+ .option('--scope-id <scopeId>', 'scope ID (must match the grant\'s scope)')
156
+ .action(async (roleId, cap, options) => {
157
+ auth.requireAuth();
158
+ const orgId = resolveOrgId(auth, program);
159
+ const query = { capability: cap };
160
+ if (options.scopeId) query.scope_id = options.scopeId;
161
+
162
+ const ep = resolveEndpoint('custom-roles.capabilities.remove', { orgId, roleId });
163
+ await client.delete(ep.path, { service: ep.service, query });
164
+ printSuccess(`Capability "${cap}" revoked.`);
165
+ });
166
+
167
+ // ── role agent ───────────────────────────────────────────────────────────────
168
+
169
+ const agent = role
170
+ .command('agent')
171
+ .description('Manage agent access rules on a role');
172
+
173
+ agent
174
+ .command('list')
175
+ .description('List agent access rules for a role')
176
+ .argument('<roleId>', 'role ID')
177
+ .action(async (roleId) => {
178
+ auth.requireAuth();
179
+ const orgId = resolveOrgId(auth, program);
180
+ const ep = resolveEndpoint('custom-roles.agents.list', { orgId, roleId });
181
+ const data = await client.get(ep.path, { service: ep.service });
182
+ const rows = data.permissions || data || [];
183
+
184
+ formatOutput({
185
+ columns: ['agent_id', 'scope_id', 'allowed'],
186
+ headers: ['Agent ID', 'Scope', 'Allowed'],
187
+ rows,
188
+ }, { format: program.opts().output, emptyMessage: 'No agent access rules set for this role.' });
189
+ });
190
+
191
+ agent
192
+ .command('set')
193
+ .description('Set (allow or deny) a role\'s access to an agent')
194
+ .argument('<roleId>', 'role ID')
195
+ .argument('<agentId>', 'agent ID')
196
+ .requiredOption('--allowed <bool>', 'true|false')
197
+ .option('--scope-id <scopeId>', 'restrict the rule to a specific scope')
198
+ .action(async (roleId, agentId, options) => {
199
+ auth.requireAuth();
200
+ const orgId = resolveOrgId(auth, program);
201
+ if (options.allowed !== 'true' && options.allowed !== 'false') {
202
+ throw new Error('--allowed must be "true" or "false".');
203
+ }
204
+ const body = { agent_id: agentId, allowed: options.allowed === 'true' };
205
+ if (options.scopeId) body.scope_id = options.scopeId;
206
+
207
+ const ep = resolveEndpoint('custom-roles.agents.set', { orgId, roleId });
208
+ await client.put(ep.path, { service: ep.service, body });
209
+ printSuccess(`Agent access rule set (${body.allowed ? 'allowed' : 'denied'}).`);
210
+ });
211
+
212
+ agent
213
+ .command('clear')
214
+ .description('Remove an agent access rule from a role')
215
+ .argument('<roleId>', 'role ID')
216
+ .argument('<agentId>', 'agent ID')
217
+ .option('--scope-id <scopeId>', 'scope ID (must match the rule\'s scope)')
218
+ .action(async (roleId, agentId, options) => {
219
+ auth.requireAuth();
220
+ const orgId = resolveOrgId(auth, program);
221
+ const query = { agent_id: agentId };
222
+ if (options.scopeId) query.scope_id = options.scopeId;
223
+
224
+ const ep = resolveEndpoint('custom-roles.agents.clear', { orgId, roleId });
225
+ await client.delete(ep.path, { service: ep.service, query });
226
+ printSuccess('Agent access rule cleared.');
227
+ });
228
+
229
+ // ── role tool ────────────────────────────────────────────────────────────────
230
+
231
+ const tool = role
232
+ .command('tool')
233
+ .description('Manage tool access rules on a role');
234
+
235
+ tool
236
+ .command('list')
237
+ .description('List tool access rules for a role')
238
+ .argument('<roleId>', 'role ID')
239
+ .action(async (roleId) => {
240
+ auth.requireAuth();
241
+ const orgId = resolveOrgId(auth, program);
242
+ const ep = resolveEndpoint('custom-roles.tools.list', { orgId, roleId });
243
+ const data = await client.get(ep.path, { service: ep.service });
244
+ const rows = data.permissions || data || [];
245
+
246
+ formatOutput({
247
+ columns: ['tool_id', 'allowed'],
248
+ headers: ['Tool ID', 'Allowed'],
249
+ rows,
250
+ }, { format: program.opts().output, emptyMessage: 'No tool access rules set for this role.' });
251
+ });
252
+
253
+ tool
254
+ .command('set')
255
+ .description('Set (allow or deny) a role\'s access to a tool')
256
+ .argument('<roleId>', 'role ID')
257
+ .argument('<toolId>', 'tool ID')
258
+ .requiredOption('--allowed <bool>', 'true|false')
259
+ .action(async (roleId, toolId, options) => {
260
+ auth.requireAuth();
261
+ const orgId = resolveOrgId(auth, program);
262
+ if (options.allowed !== 'true' && options.allowed !== 'false') {
263
+ throw new Error('--allowed must be "true" or "false".');
264
+ }
265
+ const body = { tool_id: toolId, allowed: options.allowed === 'true' };
266
+
267
+ const ep = resolveEndpoint('custom-roles.tools.set', { orgId, roleId });
268
+ await client.put(ep.path, { service: ep.service, body });
269
+ printSuccess(`Tool access rule set (${body.allowed ? 'allowed' : 'denied'}).`);
270
+ });
271
+
272
+ tool
273
+ .command('clear')
274
+ .description('Remove a tool access rule from a role')
275
+ .argument('<roleId>', 'role ID')
276
+ .argument('<toolId>', 'tool ID')
277
+ .action(async (roleId, toolId) => {
278
+ auth.requireAuth();
279
+ const orgId = resolveOrgId(auth, program);
280
+ const query = { tool_id: toolId };
281
+
282
+ const ep = resolveEndpoint('custom-roles.tools.clear', { orgId, roleId });
283
+ await client.delete(ep.path, { service: ep.service, query });
284
+ printSuccess('Tool access rule cleared.');
285
+ });
286
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Run commands: list, get, create, cancel, resume
3
+ */
4
+
5
+ import { formatOutput, formatDetail, printSuccess, printWarning } from '../core/output.js';
6
+ import { resolveOrgId, parseJSON } from './helpers.js';
7
+ import { resolveEndpoint } from '../api/endpoints.js';
8
+
9
+ export function registerRunCommands(program, ctx) {
10
+ const { client, auth } = ctx;
11
+
12
+ const run = program
13
+ .command('run')
14
+ .description('Manage workflow runs');
15
+
16
+ // fleet #465: runs live in two stores. The engine (quoxflow) is the
17
+ // execution store `workflow run`/apply-triggered runs actually land in;
18
+ // the auth-service 'runs.*' store is a separate model quoxflow never
19
+ // writes to. `list`/`get` below try the engine FIRST and fall back to the
20
+ // auth-service store, labeling which store answered.
21
+
22
+ run
23
+ .command('list')
24
+ .description('List runs')
25
+ .option('--status <status>', 'filter by status')
26
+ .option('--workflow <id>', 'filter by workflow')
27
+ .option('--limit <n>', 'max results', '50')
28
+ .action(async (options) => {
29
+ auth.requireAuth();
30
+ const orgId = resolveOrgId(auth, program);
31
+
32
+ let rows, source;
33
+ try {
34
+ const ep = resolveEndpoint('executions.list');
35
+ const data = await client.get(ep.path, { service: ep.service });
36
+ const items = data.data || data.items || (Array.isArray(data) ? data : []);
37
+ rows = items.map((item) => ({ ...item, source: 'engine' }));
38
+ source = 'engine';
39
+ } catch (err) {
40
+ printWarning(`Engine (quoxflow) execution list failed, falling back to the auth-service store: ${err.message}`);
41
+ const query = { limit: options.limit };
42
+ if (options.status) query.status = options.status;
43
+ if (options.workflow) query.workflow_id = options.workflow;
44
+ const ep = resolveEndpoint('runs.list', { orgId });
45
+ const data = await client.get(ep.path, { service: ep.service, query });
46
+ const items = data.items || data.runs || (Array.isArray(data) ? data : []);
47
+ rows = items.map((item) => ({ ...item, source: 'auth' }));
48
+ source = 'auth';
49
+ }
50
+
51
+ formatOutput({
52
+ // fleet #392: runs table has finished_at, not completed_at.
53
+ columns: ['id', 'workflow_name', 'status', 'source', 'started_at', 'finished_at'],
54
+ headers: ['ID', 'Workflow', 'Status', 'Source', 'Started', 'Completed'],
55
+ rows,
56
+ }, { format: program.opts().output, emptyMessage: `No results (${source} store).` });
57
+ });
58
+
59
+ run
60
+ .command('get')
61
+ .description('Get run details')
62
+ .argument('<runId>', 'run ID')
63
+ .action(async (runId) => {
64
+ auth.requireAuth();
65
+ const orgId = resolveOrgId(auth, program);
66
+
67
+ let data, source;
68
+ try {
69
+ const ep = resolveEndpoint('executions.get', { executionId: runId });
70
+ const res = await client.get(ep.path, { service: ep.service });
71
+ data = res.data || res;
72
+ source = 'engine';
73
+ } catch (err) {
74
+ if (err.status !== 404) throw err;
75
+ try {
76
+ const ep = resolveEndpoint('runs.get', { orgId, pollRunId: runId });
77
+ const res = await client.get(ep.path, { service: ep.service });
78
+ data = res.run || res;
79
+ source = 'auth';
80
+ } catch (err2) {
81
+ if (err2.status !== 404) throw err2;
82
+ throw new Error(
83
+ `Run "${runId}" was not found in either the engine (quoxflow) execution store or the auth-service run store.`
84
+ );
85
+ }
86
+ }
87
+
88
+ formatDetail({ ...data, source }, { format: program.opts().output });
89
+ });
90
+
91
+ run
92
+ .command('create')
93
+ .description('Start a new workflow run')
94
+ .requiredOption('--workflow <id>', 'workflow ID')
95
+ .option('--input <json>', 'input data as JSON')
96
+ .action(async (options) => {
97
+ auth.requireAuth();
98
+ const orgId = resolveOrgId(auth, program);
99
+ const body = { workflow_id: options.workflow };
100
+ if (options.input) body.context = parseJSON(options.input, '--input');
101
+ const data = await client.post(resolveEndpoint('runs.create', { orgId }).path, { service: resolveEndpoint('runs.create', { orgId }).service, body });
102
+ printSuccess(`Run started: ${data.run?.id || data.id || 'ok'}`);
103
+ });
104
+
105
+ run
106
+ .command('cancel')
107
+ .description('Cancel a running workflow')
108
+ .argument('<runId>', 'run ID')
109
+ .option('--reason <text>', 'cancellation reason')
110
+ .action(async (runId, options) => {
111
+ auth.requireAuth();
112
+ const orgId = resolveOrgId(auth, program);
113
+ const body = {};
114
+ if (options.reason) body.reason = options.reason;
115
+ await client.post(resolveEndpoint('runs.cancel', { orgId, runId }).path, { service: resolveEndpoint('runs.cancel', { orgId, runId }).service, body });
116
+ printSuccess('Run cancelled.');
117
+ });
118
+
119
+ run
120
+ .command('resume')
121
+ .description('Resume a paused run')
122
+ .argument('<runId>', 'run ID')
123
+ .option('--response <json>', 'human response payload as JSON')
124
+ .action(async (runId, options) => {
125
+ auth.requireAuth();
126
+ const orgId = resolveOrgId(auth, program);
127
+ const body = {};
128
+ if (options.response) body.response = parseJSON(options.response, '--response');
129
+ await client.post(resolveEndpoint('runs.resume', { orgId, runId }).path, { service: resolveEndpoint('runs.resume', { orgId, runId }).service, body });
130
+ printSuccess('Run resumed.');
131
+ });
132
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Safety commands: kill switch (per-agent E-STOP) + instance DEFCON.
3
+ *
4
+ * quox safety status org halt state: DEFCON + active E-STOPs
5
+ * quox safety gate <agentId> the enforcement verdict a dispatcher sees
6
+ * quox safety estop <agentId> activate the per-agent kill switch
7
+ * quox safety release <agentId> release it
8
+ * quox safety defcon <LEVEL> set instance DEFCON (GREEN..RED)
9
+ * quox safety history append-only audit trail of every flip
10
+ *
11
+ * Backend: collector routes/safetyGate.js (quox#503, PR #505; dispatch
12
+ * chokepoint coverage quox#506). Mutations REQUIRE a human admin login:
13
+ * the server rejects agent/resident/service tokens by design, so if
14
+ * `estop`/`release`/`defcon` return 403 admin_required under a resident
15
+ * profile, that is the governance boundary working: log in as a human
16
+ * (`quox login`), do not look for a bypass.
17
+ *
18
+ * Closes the quox#507 parity gap: the 2026-09-02 live E-STOP proof drive
19
+ * had to be scripted with raw curl because these verbs did not exist.
20
+ */
21
+
22
+ import { formatOutput, formatDetail, printSuccess, printWarning } from '../core/output.js';
23
+ import { resolveEndpoint } from '../api/endpoints.js';
24
+
25
+ export function registerSafetyCommands(program, ctx) {
26
+ const { client, auth } = ctx;
27
+
28
+ const safety = program
29
+ .command('safety')
30
+ .description('Kill switch (per-agent E-STOP) and instance DEFCON (human-admin gated mutations)');
31
+
32
+ safety
33
+ .command('status')
34
+ .description('Org halt state: DEFCON level + active per-agent E-STOPs')
35
+ .action(async () => {
36
+ auth.requireAuth();
37
+ const ep = resolveEndpoint('safety.status');
38
+ const data = await client.get(ep.path, { service: ep.service });
39
+ formatDetail(data, { format: program.opts().output });
40
+ });
41
+
42
+ safety
43
+ .command('gate')
44
+ .description('The enforcement verdict a dispatcher gets for this agent right now')
45
+ .argument('<agentId>', 'agent id, e.g. quox, sentinel')
46
+ .action(async (agentId) => {
47
+ auth.requireAuth();
48
+ const ep = resolveEndpoint('safety.gate', { agentId });
49
+ const data = await client.get(ep.path, { service: ep.service });
50
+ formatDetail(data, { format: program.opts().output });
51
+ if (data.blocked) printWarning(`Dispatch for ${agentId} is BLOCKED: ${data.reason}`);
52
+ });
53
+
54
+ safety
55
+ .command('estop')
56
+ .description('Activate the per-agent kill switch (HUMAN admin only, enforced server-side)')
57
+ .argument('<agentId>', 'agent id to halt')
58
+ .option('--reason <text>', 'why (lands in the audit trail)')
59
+ .action(async (agentId, options) => {
60
+ auth.requireAuth();
61
+ const ep = resolveEndpoint('safety.estop', { agentId });
62
+ const data = await client.post(ep.path, { service: ep.service, body: { reason: options.reason || null } });
63
+ printSuccess(`E-STOP ACTIVE for ${agentId}${data.was_active ? ' (was already active)' : ''}: new dispatch is blocked at every chokepoint; live loop sessions end at the next turn boundary.`);
64
+ });
65
+
66
+ safety
67
+ .command('release')
68
+ .description('Release a per-agent kill switch (HUMAN admin only, enforced server-side)')
69
+ .argument('<agentId>', 'agent id to release')
70
+ .action(async (agentId) => {
71
+ auth.requireAuth();
72
+ const ep = resolveEndpoint('safety.release', { agentId });
73
+ await client.delete(ep.path, { service: ep.service });
74
+ printSuccess(`E-STOP released for ${agentId}.`);
75
+ });
76
+
77
+ safety
78
+ .command('defcon')
79
+ .description('Set instance DEFCON: GREEN | BLUE | AMBER | ORANGE (escalate) | RED (full halt)')
80
+ .argument('<level>', 'GREEN, BLUE, AMBER, ORANGE or RED')
81
+ .option('--reason <text>', 'why (lands in the audit trail)')
82
+ .action(async (level, options) => {
83
+ auth.requireAuth();
84
+ const ep = resolveEndpoint('safety.defcon');
85
+ const data = await client.post(ep.path, { service: ep.service, body: { level: level.toUpperCase(), reason: options.reason || null } });
86
+ printSuccess(`DEFCON ${data.old_level} -> ${data.new_level}.`);
87
+ });
88
+
89
+ safety
90
+ .command('history')
91
+ .description('Append-only audit trail of every E-STOP and DEFCON change')
92
+ .option('--agent <agentId>', 'filter to one agent')
93
+ .option('--limit <n>', 'max rows', '50')
94
+ .action(async (options) => {
95
+ auth.requireAuth();
96
+ const ep = resolveEndpoint('safety.history');
97
+ const qs = new URLSearchParams();
98
+ if (options.agent) qs.set('agent_id', options.agent);
99
+ qs.set('limit', options.limit);
100
+ const data = await client.get(`${ep.path}?${qs}`, { service: ep.service });
101
+ formatOutput({
102
+ columns: ['created_at', 'scope', 'action', 'agent_id', 'new_value', 'actor_label', 'reason'],
103
+ headers: ['At', 'Scope', 'Action', 'Agent', 'Value', 'Actor', 'Reason'],
104
+ rows: data.history || [],
105
+ }, { format: program.opts().output, emptyMessage: 'No safety state changes recorded.' });
106
+ });
107
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * SCIM commands: org SCIM bearer tokens (IdP-driven directory sync auth)
3
+ *
4
+ * quox scim tokens list|create|delete
5
+ *
6
+ * Backend: services/auth/routes/webhooks.js, mounted at plain `/api`
7
+ * (org-scoped IN the URL: /api/orgs/:orgId/scim/tokens...). Enterprise-tier
8
+ * + ORG_MANAGE_SETTINGS gated — expect 402/403 on non-enterprise orgs.
9
+ *
10
+ * The plaintext bearer token is returned ONLY on create — it is never
11
+ * retrievable again after that response (only its SHA-256 is stored). We
12
+ * print it prominently and warn the user to record it now.
13
+ */
14
+
15
+ import { formatOutput, printSuccess, printWarning } from '../core/output.js';
16
+ import { resolveOrgId } from './helpers.js';
17
+ import { resolveEndpoint } from '../api/endpoints.js';
18
+
19
+ function printToken(token) {
20
+ printWarning('Record this bearer token now — it will not be shown again:');
21
+ console.log(` ${token}`);
22
+ }
23
+
24
+ export function registerScimCommands(program, ctx) {
25
+ const { client, auth } = ctx;
26
+
27
+ const scim = program
28
+ .command('scim')
29
+ .description('Manage org SCIM bearer tokens (enterprise — IdP directory sync)');
30
+
31
+ const tokens = scim
32
+ .command('tokens')
33
+ .description('Manage SCIM bearer tokens');
34
+
35
+ // ── scim tokens list ────────────────────────────────────────────────────────
36
+
37
+ tokens
38
+ .command('list')
39
+ .description('List SCIM tokens for the active org')
40
+ .action(async () => {
41
+ auth.requireAuth();
42
+ const orgId = resolveOrgId(auth, program);
43
+ const ep = resolveEndpoint('scim.tokens.list', { orgId });
44
+ const data = await client.get(ep.path, { service: ep.service });
45
+ const rows = data.tokens || data || [];
46
+
47
+ formatOutput({
48
+ columns: ['id', 'label', 'created_at', 'last_used_at', 'revoked_at'],
49
+ headers: ['ID', 'Label', 'Created', 'Last Used', 'Revoked'],
50
+ rows,
51
+ }, { format: program.opts().output, emptyMessage: 'No SCIM tokens configured.' });
52
+ });
53
+
54
+ // ── scim tokens create ──────────────────────────────────────────────────────
55
+
56
+ tokens
57
+ .command('create')
58
+ .description('Create a SCIM bearer token')
59
+ .option('--label <label>', 'human-readable label')
60
+ .action(async (options) => {
61
+ auth.requireAuth();
62
+ const orgId = resolveOrgId(auth, program);
63
+ const body = {};
64
+ if (options.label) body.label = options.label;
65
+
66
+ const ep = resolveEndpoint('scim.tokens.create', { orgId });
67
+ // NOTE: the backend response here is the raw `{ id, token }` shape,
68
+ // not wrapped under a named key like every other create route in
69
+ // this CLI — see endpoints/scim.js's header note.
70
+ const data = await client.post(ep.path, { service: ep.service, body });
71
+ printSuccess(`SCIM token created (id: ${data.id}).`);
72
+ if (data.token) printToken(data.token);
73
+ });
74
+
75
+ // ── scim tokens delete ──────────────────────────────────────────────────────
76
+
77
+ tokens
78
+ .command('delete')
79
+ .description('Revoke a SCIM token')
80
+ .argument('<tokenId>', 'token ID')
81
+ .action(async (tokenId) => {
82
+ auth.requireAuth();
83
+ const orgId = resolveOrgId(auth, program);
84
+ const ep = resolveEndpoint('scim.tokens.delete', { orgId, tokenId });
85
+ await client.delete(ep.path, { service: ep.service });
86
+ printSuccess('SCIM token revoked.');
87
+ });
88
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Scope commands
3
+ *
4
+ * quox scope list
5
+ *
6
+ * Scopes are read-only projections of the org/division/department/team
7
+ * hierarchy — there is no backend write route for scopes. To create,
8
+ * update, or delete a scope, use `quox org`, `quox division`,
9
+ * `quox department`, or `quox team` instead.
10
+ */
11
+
12
+ import { formatOutput } from '../core/output.js';
13
+ import { resolveOrgId } from './helpers.js';
14
+ import { resolveEndpoint } from '../api/endpoints.js';
15
+
16
+ export function registerScopeCommands(program, ctx) {
17
+ const { client, auth } = ctx;
18
+
19
+ const scope = program
20
+ .command('scope')
21
+ .description('View scopes (read-only — use `quox org`/`quox division`/`quox team` to manage)');
22
+
23
+ scope
24
+ .command('list')
25
+ .description('List scopes')
26
+ .action(async () => {
27
+ auth.requireAuth();
28
+ const orgId = resolveOrgId(auth, program);
29
+ // fleet #392: the route returns a nested {tree} unless flat=true is
30
+ // sent, and it was never sent — so `data.scopes` was undefined,
31
+ // `data` fell through as the object {tree: ...}, and `.map()` threw
32
+ // TypeError. This command crashed rather than merely showing dashes.
33
+ // Columns also renamed to the real ones (name_cache/scope_type);
34
+ // `resources` has no counterpart at all, so `path` and `depth` are
35
+ // shown instead of inventing a zero.
36
+ const data = await client.get(resolveEndpoint('scopes.list', { orgId }).path, {
37
+ service: resolveEndpoint('scopes.list', { orgId }).service,
38
+ query: { flat: 'true' },
39
+ });
40
+ const scopes = Array.isArray(data.scopes) ? data.scopes : [];
41
+ // fleet #399.3: `depth` is a scope-TYPE ordinal (services/auth/
42
+ // migrations/000-baseline.sql: "0=org, 1=division, 2=department,
43
+ // 3=team"), not a count of path segments below root. A team can
44
+ // legitimately show Depth 3 while its `path` reads only two levels
45
+ // deep. Header made self-explanatory rather than recomputing depth.
46
+ formatOutput({
47
+ columns: ['id', 'name', 'type', 'path', 'depth'],
48
+ headers: ['ID', 'Name', 'Type', 'Path', 'Depth (0=org,1=div,2=dept,3=team)'],
49
+ rows: scopes.map(s => ({
50
+ id: s.id,
51
+ name: s.name_cache,
52
+ type: s.scope_type,
53
+ path: s.path,
54
+ depth: s.depth,
55
+ })),
56
+ }, { format: program.opts().output, emptyMessage: 'No scopes found.' });
57
+ });
58
+ }