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,241 @@
1
+ /**
2
+ * Budget commands: CRUD for quoxflow budget definitions + rolled-up usage.
3
+ *
4
+ * quox budget list|get|create|update|delete|usage
5
+ *
6
+ * Backend: quoxflow (NOT the auth service, NOT proxied by collector) —
7
+ * quoxflow/src/api/routes/budgets.ts, mounted at /api/budgets. Org scoping
8
+ * is via `?org_id=` query param on GETs and an `org_id` body field on
9
+ * writes — see cli/lib/api/endpoints/budgets.js header for the full
10
+ * verified org-scoping mechanism (list requires+filters org_id; get/usage
11
+ * check org_id against the budget's owner when present; update/delete
12
+ * require it in the body and 403 on mismatch).
13
+ */
14
+
15
+ import { formatOutput, formatDetail, printSuccess, printWarning, printInfo } from '../core/output.js';
16
+ import { resolveOrgId } from './helpers.js';
17
+ import { resolveEndpoint } from '../api/endpoints.js';
18
+
19
+ const SCOPE_KINDS = ['org', 'objective', 'execution', 'iteration'];
20
+
21
+ function numOrUndefined(value) {
22
+ if (value === undefined) return undefined;
23
+ const n = Number(value);
24
+ if (Number.isNaN(n)) throw new Error(`Expected a number, got: ${value}`);
25
+ return n;
26
+ }
27
+
28
+ export function registerBudgetCommands(program, ctx) {
29
+ const { client, auth } = ctx;
30
+
31
+ const budget = program
32
+ .command('budget')
33
+ .description('Manage quoxflow spend/usage budgets (org, objective, execution, iteration scope)');
34
+
35
+ // ── budget list ─────────────────────────────────────────────────────────────
36
+
37
+ budget
38
+ .command('list')
39
+ .description('List budgets for the active org')
40
+ .option('--scope <kind>', `filter by scope kind (${SCOPE_KINDS.join('|')})`)
41
+ .option('--scope-id <id>', 'filter by scope id')
42
+ .action(async (options) => {
43
+ auth.requireAuth();
44
+ const orgId = resolveOrgId(auth, program);
45
+ const query = { org_id: orgId };
46
+ if (options.scope) query.scope = options.scope;
47
+ if (options.scopeId) query.scope_id = options.scopeId;
48
+
49
+ const data = await client.get(resolveEndpoint('budgets.list').path, {
50
+ service: resolveEndpoint('budgets.list').service,
51
+ query,
52
+ });
53
+ const budgets = data.data?.budgets || data.budgets || [];
54
+
55
+ formatOutput({
56
+ columns: ['id', 'scope_kind', 'scope_id', 'period_start', 'period_end', 'max_cost_usd_micro', 'max_tokens', 'hard_stop'],
57
+ headers: ['ID', 'Scope', 'Scope ID', 'Period Start', 'Period End', 'Max Cost (micro-USD)', 'Max Tokens', 'Hard Stop'],
58
+ rows: budgets,
59
+ }, { format: program.opts().output, emptyMessage: 'No budgets found.' });
60
+ });
61
+
62
+ // ── budget get ───────────────────────────────────────────────────────────────
63
+
64
+ budget
65
+ .command('get')
66
+ .description('Show budget details')
67
+ .argument('<id>', 'budget ID')
68
+ .action(async (id) => {
69
+ auth.requireAuth();
70
+ const orgId = resolveOrgId(auth, program);
71
+ const data = await client.get(resolveEndpoint('budgets.get', { id }).path, {
72
+ service: resolveEndpoint('budgets.get', { id }).service,
73
+ query: { org_id: orgId },
74
+ });
75
+ const b = data.data?.budget || data.budget || data;
76
+
77
+ formatDetail(b, {
78
+ format: program.opts().output,
79
+ title: `Budget: ${b.id || id}`,
80
+ });
81
+ });
82
+
83
+ // ── budget create ────────────────────────────────────────────────────────────
84
+
85
+ budget
86
+ .command('create')
87
+ .description('Create a budget')
88
+ .requiredOption('--scope <kind>', `scope kind (${SCOPE_KINDS.join('|')})`)
89
+ .requiredOption('--scope-id <id>', 'scope id (e.g. org id, objective id, execution id, iteration id)')
90
+ .requiredOption('--period-start <iso>', 'period start (ISO timestamp)')
91
+ .requiredOption('--period-end <iso>', 'period end (ISO timestamp)')
92
+ .option('--max-tokens <n>', 'max total tokens (in + out)')
93
+ .option('--max-cost-usd-micro <n>', 'max cost in micro-USD (1 USD = 1,000,000)')
94
+ .option('--max-tool-calls <n>', 'max tool calls')
95
+ .option('--max-duration-ms <n>', 'max wall-clock duration (ms)')
96
+ .option('--max-iterations <n>', 'max iterations')
97
+ .option('--no-hard-stop', 'do not hard-stop execution when exceeded (default: hard-stop enabled)')
98
+ .action(async (options) => {
99
+ auth.requireAuth();
100
+ const orgId = resolveOrgId(auth, program);
101
+
102
+ if (!SCOPE_KINDS.includes(options.scope)) {
103
+ throw new Error(`--scope must be one of: ${SCOPE_KINDS.join(', ')}`);
104
+ }
105
+
106
+ const body = {
107
+ org_id: orgId,
108
+ scope_kind: options.scope,
109
+ scope_id: options.scopeId,
110
+ period_start: options.periodStart,
111
+ period_end: options.periodEnd,
112
+ max_tokens: numOrUndefined(options.maxTokens),
113
+ max_cost_usd_micro: numOrUndefined(options.maxCostUsdMicro),
114
+ max_tool_calls: numOrUndefined(options.maxToolCalls),
115
+ max_duration_ms: numOrUndefined(options.maxDurationMs),
116
+ max_iterations: numOrUndefined(options.maxIterations),
117
+ hard_stop: options.hardStop,
118
+ };
119
+
120
+ const data = await client.post(resolveEndpoint('budgets.create').path, {
121
+ service: resolveEndpoint('budgets.create').service,
122
+ body,
123
+ });
124
+ const b = data.data?.budget || data.budget || data;
125
+ printSuccess(`Budget created (${b.id}).`);
126
+ formatDetail(b, { format: program.opts().output });
127
+ });
128
+
129
+ // ── budget update ────────────────────────────────────────────────────────────
130
+
131
+ budget
132
+ .command('update')
133
+ .description('Update a budget')
134
+ .argument('<id>', 'budget ID')
135
+ .option('--period-start <iso>', 'new period start (ISO timestamp)')
136
+ .option('--period-end <iso>', 'new period end (ISO timestamp)')
137
+ .option('--max-tokens <n>', 'new max total tokens')
138
+ .option('--max-cost-usd-micro <n>', 'new max cost in micro-USD')
139
+ .option('--max-tool-calls <n>', 'new max tool calls')
140
+ .option('--max-duration-ms <n>', 'new max wall-clock duration (ms)')
141
+ .option('--max-iterations <n>', 'new max iterations')
142
+ .option('--hard-stop <bool>', 'set hard_stop (true|false)')
143
+ .action(async (id, options) => {
144
+ auth.requireAuth();
145
+ const orgId = resolveOrgId(auth, program);
146
+
147
+ const patch = { org_id: orgId };
148
+ if (options.periodStart !== undefined) patch.period_start = options.periodStart;
149
+ if (options.periodEnd !== undefined) patch.period_end = options.periodEnd;
150
+ if (options.maxTokens !== undefined) patch.max_tokens = numOrUndefined(options.maxTokens);
151
+ if (options.maxCostUsdMicro !== undefined) patch.max_cost_usd_micro = numOrUndefined(options.maxCostUsdMicro);
152
+ if (options.maxToolCalls !== undefined) patch.max_tool_calls = numOrUndefined(options.maxToolCalls);
153
+ if (options.maxDurationMs !== undefined) patch.max_duration_ms = numOrUndefined(options.maxDurationMs);
154
+ if (options.maxIterations !== undefined) patch.max_iterations = numOrUndefined(options.maxIterations);
155
+ if (options.hardStop !== undefined) {
156
+ if (options.hardStop !== 'true' && options.hardStop !== 'false') {
157
+ throw new Error('--hard-stop must be "true" or "false".');
158
+ }
159
+ patch.hard_stop = options.hardStop === 'true';
160
+ }
161
+
162
+ if (Object.keys(patch).length === 1) {
163
+ throw new Error('Specify at least one field to update.');
164
+ }
165
+
166
+ const data = await client.patch(resolveEndpoint('budgets.update', { id }).path, {
167
+ service: resolveEndpoint('budgets.update', { id }).service,
168
+ body: patch,
169
+ });
170
+ const b = data.data?.budget || data.budget || data;
171
+ printSuccess('Budget updated.');
172
+ formatDetail(b, { format: program.opts().output });
173
+ });
174
+
175
+ // ── budget delete ────────────────────────────────────────────────────────────
176
+
177
+ budget
178
+ .command('delete')
179
+ .description('Delete a budget')
180
+ .argument('<id>', 'budget ID')
181
+ .option('--force', 'skip confirmation')
182
+ .action(async (id, options) => {
183
+ auth.requireAuth();
184
+ const orgId = resolveOrgId(auth, program);
185
+
186
+ if (!options.force && !program.opts().yes) {
187
+ const { default: inquirer } = await import('inquirer');
188
+ const ans = await inquirer.prompt([{
189
+ type: 'confirm',
190
+ name: 'confirm',
191
+ message: `Delete budget ${id}? This cannot be undone.`,
192
+ default: false,
193
+ }]);
194
+ if (!ans.confirm) {
195
+ printInfo('Cancelled.');
196
+ return;
197
+ }
198
+ }
199
+
200
+ await client.delete(resolveEndpoint('budgets.delete', { id }).path, {
201
+ service: resolveEndpoint('budgets.delete', { id }).service,
202
+ body: { org_id: orgId },
203
+ });
204
+ printSuccess('Budget deleted.');
205
+ });
206
+
207
+ // ── budget usage ─────────────────────────────────────────────────────────────
208
+
209
+ budget
210
+ .command('usage')
211
+ .description('Show rolled-up usage + daily breakdown for a budget')
212
+ .argument('<id>', 'budget ID')
213
+ .option('--since <iso>', 'filter usage since this ISO timestamp')
214
+ .option('--until <iso>', 'filter usage until this ISO timestamp')
215
+ .action(async (id, options) => {
216
+ auth.requireAuth();
217
+ const orgId = resolveOrgId(auth, program);
218
+ const query = { org_id: orgId };
219
+ if (options.since) query.since = options.since;
220
+ if (options.until) query.until = options.until;
221
+
222
+ const data = await client.get(resolveEndpoint('budgets.usage', { id }).path, {
223
+ service: resolveEndpoint('budgets.usage', { id }).service,
224
+ query,
225
+ });
226
+ const payload = data.data || data;
227
+
228
+ formatDetail(payload.usage, {
229
+ format: program.opts().output,
230
+ title: `Budget Usage: ${id}`,
231
+ });
232
+
233
+ if (program.opts().output !== 'json' && program.opts().output !== 'quiet') {
234
+ formatOutput({
235
+ columns: ['date', 'tokens_in', 'tokens_out', 'cost_usd_micro', 'tool_calls', 'duration_ms', 'iteration_count'],
236
+ headers: ['Date', 'Tokens In', 'Tokens Out', 'Cost (micro-USD)', 'Tool Calls', 'Duration (ms)', 'Iterations'],
237
+ rows: payload.daily || [],
238
+ }, { format: program.opts().output, emptyMessage: 'No daily usage recorded.' });
239
+ }
240
+ });
241
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Build commands — DEVSUITE-BUILD step 4 (`quox build`).
3
+ *
4
+ * The governed superset of the thin local `quox-build` client: dispatches a
5
+ * PRE-REGISTERED target on a dev server through the collector's dev-build
6
+ * control plane (oracle-signed envelope over Bastion to the host-side
7
+ * quoxagent dev.build executor), reads live executor state, and reads the
8
+ * durable receipts mirror. The local `quox-build` spool client and the
9
+ * Terminal 9 daemon are untouched and keep working; this group is how any
10
+ * authenticated session drives builds on ANY registered dev server.
11
+ *
12
+ * `quox build <target> --host <hostId>` dispatches. Named subcommands
13
+ * (status/targets/history/latest) win over a target of the same name — a
14
+ * target literally called "status" must be dispatched via
15
+ * `quox build dispatch status --host …`.
16
+ */
17
+
18
+ import { formatDetail, formatOutput, printSuccess, printWarning } from '../core/output.js';
19
+ import { resolveEndpoint } from '../api/endpoints.js';
20
+
21
+ export function registerBuildCommands(program, ctx) {
22
+ const { client, auth } = ctx;
23
+
24
+ const build = program
25
+ .command('build')
26
+ .description('QuoxBuild: dispatch and inspect governed builds on dev servers');
27
+
28
+ // Commander (non-positional mode) lets the PARENT consume an option it
29
+ // declares even when the token appears after a subcommand name, so the
30
+ // bare-target sugar's group-level --host/--stream/--phase/--msg would
31
+ // otherwise swallow the flags out from under every verb (found live on
32
+ // the first proof drive). Every verb therefore falls back to the group's
33
+ // own parsed opts.
34
+ const groupOpt = (options, key) => options[key] ?? build.opts()[key];
35
+
36
+ async function dispatch(target, options) {
37
+ auth.requireAuth();
38
+ options = {
39
+ host: groupOpt(options, 'host'),
40
+ stream: groupOpt(options, 'stream'),
41
+ phase: groupOpt(options, 'phase'),
42
+ msg: groupOpt(options, 'msg'),
43
+ };
44
+ if (!options.host) {
45
+ throw new Error('--host <hostId> is required (the dev server that runs the build)');
46
+ }
47
+ const body = { target };
48
+ const stream = options.stream || process.env.QUOX_STREAM;
49
+ const phase = options.phase || process.env.QUOX_PHASE;
50
+ if (stream) body.stream = stream;
51
+ if (phase) body.phase = phase;
52
+ if (options.msg) body.msg = options.msg;
53
+
54
+ const endpoint = resolveEndpoint('v1.dev.build.dispatch', { hostId: options.host });
55
+ const data = await client.post(endpoint.path, { service: endpoint.service, body });
56
+ formatDetail(data, { format: program.opts().output, title: `Build dispatched on ${options.host}` });
57
+ if (data && data.dispatched) {
58
+ printSuccess(`${(data.targets || [target]).join(', ')} queued on ${options.host} (coalesces within the queue window).`);
59
+ printWarning('Queued is not built: check `quox build status --host ' + options.host + '` and the receipt before claiming green.');
60
+ }
61
+ }
62
+
63
+ build
64
+ .command('dispatch')
65
+ .description('Queue a build of a pre-registered target on a dev server')
66
+ .argument('<target>', 'pre-registered target name on that host')
67
+ .option('--host <hostId>', 'dev server host_id')
68
+ .option('--stream <code>', 'roadmap stream code for the receipt (default: $QUOX_STREAM)')
69
+ .option('--phase <phase>', 'stream phase for the receipt (default: $QUOX_PHASE)')
70
+ .option('--msg <text>', 'what changed')
71
+ .action(dispatch);
72
+
73
+ build
74
+ .command('status')
75
+ .description('Live executor state on a dev server: readiness, prereqs, building-now, last outcomes')
76
+ .option('--host <hostId>', 'dev server host_id')
77
+ .action(async (options) => {
78
+ auth.requireAuth();
79
+ const host = groupOpt(options, 'host');
80
+ if (!host) throw new Error('--host <hostId> is required');
81
+ const endpoint = resolveEndpoint('v1.dev.build.status', { hostId: host });
82
+ const data = await client.get(endpoint.path, { service: endpoint.service });
83
+ formatDetail(data, { format: program.opts().output, title: `Build status: ${host}` });
84
+ });
85
+
86
+ build
87
+ .command('targets')
88
+ .description("List a dev server's pre-registered build targets")
89
+ .option('--host <hostId>', 'dev server host_id')
90
+ .action(async (options) => {
91
+ auth.requireAuth();
92
+ const host = groupOpt(options, 'host');
93
+ if (!host) throw new Error('--host <hostId> is required');
94
+ const endpoint = resolveEndpoint('v1.dev.build.targets', { hostId: host });
95
+ const data = await client.get(endpoint.path, { service: endpoint.service });
96
+ formatOutput(data.targets || [], {
97
+ format: program.opts().output,
98
+ columns: ['name', 'description', 'steps', 'verify_count'],
99
+ emptyMessage: 'No pre-registered targets on this host.',
100
+ });
101
+ });
102
+
103
+ build
104
+ .command('history')
105
+ .description('Durable build receipts (the witnessed mirror), newest first')
106
+ .option('--host <hostname>', 'filter to one host (receipt hostname)')
107
+ .option('--target <name>', 'filter to one target')
108
+ .option('--result <r>', 'filter by result (ok, FAIL, UNVERIFIED, INTERRUPTED)')
109
+ .option('--limit <n>', 'max rows (default 20)', (v) => parseInt(v, 10), 20)
110
+ .action(async (options) => {
111
+ auth.requireAuth();
112
+ const endpoint = resolveEndpoint('v1.build.receipts.list');
113
+ const params = new URLSearchParams();
114
+ const host = groupOpt(options, 'host');
115
+ if (host) params.set('host', host);
116
+ if (options.target) params.set('target', options.target);
117
+ if (options.result) params.set('result', options.result);
118
+ params.set('limit', String(options.limit));
119
+ const data = await client.get(`${endpoint.path}?${params}`, { service: endpoint.service });
120
+ formatOutput(data.receipts || [], {
121
+ format: program.opts().output,
122
+ columns: ['created_at', 'result', 'target', 'host', 'terminal', 'stream', 'phase', 'duration_seconds'],
123
+ emptyMessage: 'No build receipts recorded yet.',
124
+ });
125
+ });
126
+
127
+ build
128
+ .command('latest')
129
+ .description('Latest receipt per target (the build-status board)')
130
+ .action(async () => {
131
+ auth.requireAuth();
132
+ const endpoint = resolveEndpoint('v1.build.status.list');
133
+ const data = await client.get(endpoint.path, { service: endpoint.service });
134
+ formatOutput(data.targets || [], {
135
+ format: program.opts().output,
136
+ columns: ['target', 'result', 'host', 'created_at', 'duration_seconds'],
137
+ emptyMessage: 'No build receipts recorded yet.',
138
+ });
139
+ });
140
+
141
+ // Bare `quox build <target> --host …` sugar: dispatch without typing the
142
+ // verb, matching the muscle memory of the local quox-build client.
143
+ build
144
+ .argument('[target]', 'pre-registered target name (shorthand for `quox build dispatch <target>`)')
145
+ .option('--host <hostId>', 'dev server host_id')
146
+ .option('--stream <code>', 'roadmap stream code (default: $QUOX_STREAM)')
147
+ .option('--phase <phase>', 'stream phase (default: $QUOX_PHASE)')
148
+ .option('--msg <text>', 'what changed')
149
+ .action(async (target, options) => {
150
+ if (!target) {
151
+ build.help();
152
+ return;
153
+ }
154
+ await dispatch(target, options);
155
+ });
156
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Calendar-Q commands: connected calendars, agenda, coverage, manual sync,
3
+ * and (L2-C2) proposing/reconciling/reviewing approved event creation.
4
+ *
5
+ * There is deliberately NO `calendar create` command: creating an event
6
+ * always requires a human approval, resolved through the Approvals inbox
7
+ * (`quox inbox`) — not a second CLI doorway around that gate. `calendar
8
+ * propose` only ever builds the proposal (no external effect); an agent or a
9
+ * human approver is what actually triggers calendar_event_create, and that
10
+ * call is unconditionally gated by withApprovalGate regardless of caller.
11
+ */
12
+
13
+ import { formatOutput, formatDetail, printSuccess } from '../core/output.js';
14
+ import { resolveEndpoint } from '../api/endpoints.js';
15
+
16
+ function isJsonOutput(program) {
17
+ return program.opts().output === 'json';
18
+ }
19
+
20
+ function printJson(data) {
21
+ console.log(JSON.stringify(data, null, 2));
22
+ }
23
+
24
+ export function registerCalendarCommands(program, ctx) {
25
+ const { client, auth } = ctx;
26
+
27
+ const calendar = program
28
+ .command('calendar')
29
+ .description('Calendar-Q: connected calendars, agenda, coverage, sync');
30
+
31
+ // ── status ───────────────────────────────────────────────────────────
32
+
33
+ calendar
34
+ .command('status')
35
+ .description('Show connected Calendar-Q connections and their sync watermark')
36
+ .action(async () => {
37
+ auth.requireAuth();
38
+ const ep = resolveEndpoint('calendarq.status');
39
+ const data = await client.get(ep.path, { service: ep.service });
40
+ if (isJsonOutput(program)) return printJson(data);
41
+ formatOutput({
42
+ columns: ['connectionId', 'provider', 'resourceId', 'status', 'latestWatermarkTs'],
43
+ headers: ['ID', 'Provider', 'Resource', 'Status', 'Watermark'],
44
+ rows: data.connections || [],
45
+ }, { format: program.opts().output });
46
+ printSuccess(`${data.connectionsCount ?? 0} connection(s), ${data.layerCount ?? 0} layer(s).`);
47
+ });
48
+
49
+ // ── layers ───────────────────────────────────────────────────────────
50
+
51
+ calendar
52
+ .command('layers')
53
+ .description('List Calendar-Q layers')
54
+ .action(async () => {
55
+ auth.requireAuth();
56
+ const ep = resolveEndpoint('calendarq.layers');
57
+ const data = await client.get(ep.path, { service: ep.service });
58
+ if (isJsonOutput(program)) return printJson(data);
59
+ formatOutput({
60
+ columns: ['layerId', 'kind', 'displayName', 'visible', 'scope'],
61
+ headers: ['Layer', 'Kind', 'Name', 'Visible', 'Scope'],
62
+ rows: data.layers || [],
63
+ }, { format: program.opts().output });
64
+ });
65
+
66
+ // ── agenda ───────────────────────────────────────────────────────────
67
+
68
+ calendar
69
+ .command('agenda')
70
+ .description("Show a connection's agenda: occurrences plus coverage state")
71
+ .requiredOption('--connection <id>', 'Calendar-Q connection ID')
72
+ .option('--layers <csv>', 'comma-separated layer ids to restrict to')
73
+ .action(async (options) => {
74
+ auth.requireAuth();
75
+ const query = { connectionId: options.connection };
76
+ if (options.layers) query.layerIds = options.layers;
77
+ const ep = resolveEndpoint('calendarq.agenda');
78
+ const data = await client.get(ep.path, { service: ep.service, query });
79
+ if (isJsonOutput(program)) return printJson(data);
80
+ const coverage = data.coverage || {};
81
+ console.log(`Coverage: ${coverage.state} (watermark ${coverage.watermarkTs ?? 'n/a'}, window ${coverage.windowStartLocalDate} to ${coverage.windowEndLocalDateExclusive})`);
82
+ const occurrences = data.occurrences || [];
83
+ if (occurrences.length === 0) {
84
+ console.log('No occurrences in this window.');
85
+ return;
86
+ }
87
+ for (const occ of occurrences) console.log(occ.spokenForm);
88
+ });
89
+
90
+ // ── coverage ─────────────────────────────────────────────────────────
91
+
92
+ calendar
93
+ .command('coverage')
94
+ .description('Show just the coverage/freshness state for a connection')
95
+ .requiredOption('--connection <id>', 'Calendar-Q connection ID')
96
+ .action(async (options) => {
97
+ auth.requireAuth();
98
+ const ep = resolveEndpoint('calendarq.coverage');
99
+ const data = await client.get(ep.path, { service: ep.service, query: { connectionId: options.connection } });
100
+ if (isJsonOutput(program)) return printJson(data);
101
+ formatDetail(data.coverage || {}, { format: program.opts().output, title: 'Calendar-Q Coverage' });
102
+ });
103
+
104
+ // ── sync ─────────────────────────────────────────────────────────────
105
+
106
+ calendar
107
+ .command('sync')
108
+ .description('Trigger a manual sync for a connection (respects retired/backoff eligibility)')
109
+ .requiredOption('--connection <id>', 'Calendar-Q connection ID')
110
+ .action(async (options) => {
111
+ auth.requireAuth();
112
+ const ep = resolveEndpoint('calendarq.sync');
113
+ // A full-window provider fetch can run long; give it the same generous
114
+ // ceiling `quox mail sync` uses.
115
+ const data = await client.post(ep.path, { service: ep.service, body: { connectionId: options.connection }, timeout: 240000 });
116
+ if (isJsonOutput(program)) return printJson(data);
117
+ const result = data.result || {};
118
+ if (result.skipped) {
119
+ printSuccess(`Sync skipped for ${options.connection}: ${result.skipped}.`);
120
+ return;
121
+ }
122
+ if (result.failed) {
123
+ console.error(`Sync failed for ${options.connection}: ${result.error}`);
124
+ return;
125
+ }
126
+ const stats = result.stats || {};
127
+ printSuccess(`Synced ${options.connection}. ${stats.inserted ?? 0} occurrence(s), ${stats.omitted ?? 0} omitted, ${stats.changed ?? 0} changed.`);
128
+ });
129
+
130
+ // ── propose ──────────────────────────────────────────────────────────
131
+
132
+ calendar
133
+ .command('propose')
134
+ .description('Propose creating a single, non-recurring event (no external effect — nothing is created yet)')
135
+ .requiredOption('--connection <id>', 'Calendar-Q connection ID')
136
+ .requiredOption('--title <title>', 'Event title')
137
+ .requiredOption('--timezone <tz>', 'IANA timezone')
138
+ .option('--start <iso>', 'Start time, ISO 8601 (omit for --all-day)')
139
+ .option('--end <iso>', 'End time, ISO 8601, must be after --start (omit for --all-day)')
140
+ .option('--all-day', 'Create a single-day all-day event')
141
+ .option('--date <date>', "'YYYY-MM-DD', required with --all-day")
142
+ .option('--attendees <csv>', 'comma-separated attendee emails')
143
+ .option('--notify <policy>', "'none' or 'attendees'", 'none')
144
+ .option('--visibility <visibility>', "'default', 'private', or 'busy-only'", 'default')
145
+ .option('--description <text>', 'Event description')
146
+ .action(async (options) => {
147
+ auth.requireAuth();
148
+ const body = {
149
+ connectionId: options.connection,
150
+ title: options.title,
151
+ timezone: options.timezone,
152
+ notifyPolicy: options.notify,
153
+ visibility: options.visibility,
154
+ };
155
+ if (options.allDay) {
156
+ body.allDay = true;
157
+ body.dateOnly = options.date;
158
+ } else {
159
+ body.startTs = options.start ? Date.parse(options.start) : undefined;
160
+ body.endTs = options.end ? Date.parse(options.end) : undefined;
161
+ }
162
+ if (options.attendees) body.attendees = options.attendees.split(',').map((e) => ({ email: e.trim() })).filter((a) => a.email);
163
+ if (options.description) body.description = options.description;
164
+
165
+ const ep = resolveEndpoint('calendarq.propose');
166
+ const data = await client.post(ep.path, { service: ep.service, body });
167
+ if (isJsonOutput(program)) return printJson(data);
168
+ const p = data.proposal;
169
+ console.log(`Proposed "${p.title}" (proposalId ${p.proposalId}).`);
170
+ if ((data.overlaps || []).length > 0) {
171
+ console.log(`Disclosed conflicts: ${data.overlaps.map((o) => o.title).join(', ')}`);
172
+ } else {
173
+ console.log('No conflicts found against the latest synced calendar data.');
174
+ }
175
+ printSuccess('Nothing has been created yet. Approve via the Approvals inbox to actually create this event.');
176
+ });
177
+
178
+ // ── receipts ─────────────────────────────────────────────────────────
179
+
180
+ calendar
181
+ .command('receipts')
182
+ .description('Show Calendar-Q write-path receipts (newest first), each with its full state timeline')
183
+ .action(async () => {
184
+ auth.requireAuth();
185
+ const ep = resolveEndpoint('calendarq.receipts');
186
+ const data = await client.get(ep.path, { service: ep.service });
187
+ if (isJsonOutput(program)) return printJson(data);
188
+ const receipts = data.receipts || [];
189
+ if (receipts.length === 0) {
190
+ console.log('No receipts yet.');
191
+ return;
192
+ }
193
+ formatOutput({
194
+ columns: ['createdAt', 'kind', 'operationState', 'opId'],
195
+ headers: ['When', 'Kind', 'State', 'Operation'],
196
+ rows: receipts,
197
+ }, { format: program.opts().output });
198
+ });
199
+
200
+ // ── reconcile ────────────────────────────────────────────────────────
201
+
202
+ calendar
203
+ .command('reconcile <opId>')
204
+ .description("Resolve an 'attempted' or 'outcome_unknown' create against provider evidence (read-only — never creates/updates/deletes)")
205
+ .action(async (opId) => {
206
+ auth.requireAuth();
207
+ const ep = resolveEndpoint('calendarq.reconcile', { opId });
208
+ const data = await client.post(ep.path, { service: ep.service, body: {} });
209
+ if (isJsonOutput(program)) return printJson(data);
210
+ if (data.pending) {
211
+ console.log(`Still unresolved: ${data.error}. Try again later${data.nextEligibleAt ? ` (not before ${new Date(data.nextEligibleAt).toISOString()})` : ''}.`);
212
+ return;
213
+ }
214
+ printSuccess(`Reconciled ${opId}: ${data.outcome}${data.reason ? ` (${data.reason})` : ''}.`);
215
+ });
216
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * quox capabilities [name]
3
+ *
4
+ * What this Quox can do for your org right now (QUOXON E1): ready, needs setup,
5
+ * not available, planned, each with the server's honest reason and setup hint.
6
+ * Backend: collector GET /api/v1/capabilities (org from your token; 30 s cache).
7
+ * Same renderer as the terminal's /capabilities, so the two never disagree.
8
+ */
9
+
10
+ import { resolveEndpoint } from '../api/endpoints.js';
11
+ import { capabilityLines, findCapability, offerFor } from '../quoxon/capabilities.js';
12
+
13
+ export function registerCapabilitiesCommands(program, ctx) {
14
+ const { client, auth } = ctx;
15
+ program
16
+ .command('capabilities [name]')
17
+ .description('What this Quox can do for your org: ready, needs setup, not available, planned')
18
+ .action(async (name) => {
19
+ auth.requireAuth();
20
+ const ep = resolveEndpoint('v1.capabilities');
21
+ const data = await client.get(ep.path, { service: ep.service });
22
+ if (program.opts().output === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
23
+ const res = { ok: Array.isArray(data?.capabilities), capabilities: data?.capabilities, error: 'unexpected response' };
24
+ if (name) {
25
+ const c = findCapability(name, res);
26
+ if (!c) { console.log(`Nothing called "${name}". quox capabilities lists them.`); process.exitCode = 1; return; }
27
+ console.log(`${c.name}: ${c.intent}`);
28
+ console.log(`Proof: ${c.evidence || 'not stated'}`);
29
+ console.log(offerFor(c.id, res) || `✔ ${c.name} is ready here.`);
30
+ return;
31
+ }
32
+ for (const [t] of capabilityLines(res)) console.log(t);
33
+ });
34
+ }