@oxygen-agent/cli 1.1014.136 → 1.1014.619

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 (293) hide show
  1. package/README.md +1 -1
  2. package/dist/admin-primary-providers-render.js +27 -19
  3. package/dist/cli-output.d.ts +12 -0
  4. package/dist/cli-output.js +395 -0
  5. package/dist/cli-paths.d.ts +1 -0
  6. package/dist/cli-paths.js +3 -0
  7. package/dist/cli-profile-context.d.ts +3 -0
  8. package/dist/cli-profile-context.js +13 -0
  9. package/dist/command-manifest.d.ts +15 -0
  10. package/dist/command-manifest.js +23 -1
  11. package/dist/command-support.d.ts +438 -0
  12. package/dist/command-support.js +1610 -0
  13. package/dist/commands/action-column.d.ts +3 -0
  14. package/dist/commands/action-column.js +43 -0
  15. package/dist/commands/activation.d.ts +3 -0
  16. package/dist/commands/activation.js +24 -0
  17. package/dist/commands/admin.d.ts +3 -0
  18. package/dist/commands/admin.js +347 -0
  19. package/dist/commands/ads.d.ts +3 -0
  20. package/dist/commands/ads.js +62 -0
  21. package/dist/commands/agent.d.ts +3 -0
  22. package/dist/commands/agent.js +274 -0
  23. package/dist/commands/api-keys.d.ts +3 -0
  24. package/dist/commands/api-keys.js +77 -0
  25. package/dist/commands/approvals.d.ts +3 -0
  26. package/dist/commands/approvals.js +518 -0
  27. package/dist/commands/auth.d.ts +3 -0
  28. package/dist/commands/auth.js +230 -0
  29. package/dist/commands/billing.d.ts +3 -0
  30. package/dist/commands/billing.js +357 -0
  31. package/dist/commands/blueprints.d.ts +3 -0
  32. package/dist/commands/blueprints.js +312 -0
  33. package/dist/commands/budget.d.ts +3 -0
  34. package/dist/commands/budget.js +55 -0
  35. package/dist/commands/callables.d.ts +3 -0
  36. package/dist/commands/callables.js +111 -0
  37. package/dist/commands/capabilities.d.ts +3 -0
  38. package/dist/commands/capabilities.js +44 -0
  39. package/dist/commands/cells.d.ts +3 -0
  40. package/dist/commands/cells.js +94 -0
  41. package/dist/commands/columns.d.ts +3 -0
  42. package/dist/commands/columns.js +1628 -0
  43. package/dist/commands/commands.d.ts +3 -0
  44. package/dist/commands/commands.js +45 -0
  45. package/dist/commands/comments.d.ts +3 -0
  46. package/dist/commands/comments.js +188 -0
  47. package/dist/commands/companies.d.ts +3 -0
  48. package/dist/commands/companies.js +312 -0
  49. package/dist/commands/connections.d.ts +3 -0
  50. package/dist/commands/connections.js +39 -0
  51. package/dist/commands/context.d.ts +3 -0
  52. package/dist/commands/context.js +170 -0
  53. package/dist/commands/copilot.d.ts +3 -0
  54. package/dist/commands/copilot.js +565 -0
  55. package/dist/commands/crm.d.ts +3 -0
  56. package/dist/commands/crm.js +1672 -0
  57. package/dist/commands/custom-integrations.d.ts +3 -0
  58. package/dist/commands/custom-integrations.js +140 -0
  59. package/dist/commands/dashboards.d.ts +3 -0
  60. package/dist/commands/dashboards.js +240 -0
  61. package/dist/commands/db.d.ts +3 -0
  62. package/dist/commands/db.js +190 -0
  63. package/dist/commands/deliverability.d.ts +3 -0
  64. package/dist/commands/deliverability.js +90 -0
  65. package/dist/commands/directory.d.ts +3 -0
  66. package/dist/commands/directory.js +101 -0
  67. package/dist/commands/domains.d.ts +3 -0
  68. package/dist/commands/domains.js +604 -0
  69. package/dist/commands/egress.d.ts +3 -0
  70. package/dist/commands/egress.js +130 -0
  71. package/dist/commands/email.d.ts +3 -0
  72. package/dist/commands/email.js +49 -0
  73. package/dist/commands/engagement.d.ts +3 -0
  74. package/dist/commands/engagement.js +189 -0
  75. package/dist/commands/enrich-column.d.ts +3 -0
  76. package/dist/commands/enrich-column.js +149 -0
  77. package/dist/commands/enrichment.d.ts +3 -0
  78. package/dist/commands/enrichment.js +49 -0
  79. package/dist/commands/feedback.d.ts +3 -0
  80. package/dist/commands/feedback.js +96 -0
  81. package/dist/commands/feeds.d.ts +3 -0
  82. package/dist/commands/feeds.js +486 -0
  83. package/dist/commands/find.d.ts +3 -0
  84. package/dist/commands/find.js +159 -0
  85. package/dist/commands/followers.d.ts +3 -0
  86. package/dist/commands/followers.js +39 -0
  87. package/dist/commands/formulas.d.ts +3 -0
  88. package/dist/commands/formulas.js +43 -0
  89. package/dist/commands/home.d.ts +3 -0
  90. package/dist/commands/home.js +22 -0
  91. package/dist/commands/inbox.d.ts +3 -0
  92. package/dist/commands/inbox.js +779 -0
  93. package/dist/commands/integrations.d.ts +3 -0
  94. package/dist/commands/integrations.js +290 -0
  95. package/dist/commands/knowledge.d.ts +3 -0
  96. package/dist/commands/knowledge.js +874 -0
  97. package/dist/commands/lead-sourcing.d.ts +3 -0
  98. package/dist/commands/lead-sourcing.js +52 -0
  99. package/dist/commands/limits.d.ts +3 -0
  100. package/dist/commands/limits.js +54 -0
  101. package/dist/commands/linkedin.d.ts +3 -0
  102. package/dist/commands/linkedin.js +275 -0
  103. package/dist/commands/login.d.ts +3 -0
  104. package/dist/commands/login.js +347 -0
  105. package/dist/commands/logout.d.ts +3 -0
  106. package/dist/commands/logout.js +12 -0
  107. package/dist/commands/mailboxes.d.ts +3 -0
  108. package/dist/commands/mailboxes.js +1290 -0
  109. package/dist/commands/managed-inboxes.d.ts +3 -0
  110. package/dist/commands/managed-inboxes.js +361 -0
  111. package/dist/commands/messages.d.ts +3 -0
  112. package/dist/commands/messages.js +78 -0
  113. package/dist/commands/notetaker.d.ts +3 -0
  114. package/dist/commands/notetaker.js +79 -0
  115. package/dist/commands/observability.d.ts +3 -0
  116. package/dist/commands/observability.js +125 -0
  117. package/dist/commands/orgs.d.ts +3 -0
  118. package/dist/commands/orgs.js +237 -0
  119. package/dist/commands/people.d.ts +3 -0
  120. package/dist/commands/people.js +353 -0
  121. package/dist/commands/posts.d.ts +3 -0
  122. package/dist/commands/posts.js +139 -0
  123. package/dist/commands/profiles.d.ts +3 -0
  124. package/dist/commands/profiles.js +250 -0
  125. package/dist/commands/projects.d.ts +3 -0
  126. package/dist/commands/projects.js +94 -0
  127. package/dist/commands/prompts.d.ts +3 -0
  128. package/dist/commands/prompts.js +84 -0
  129. package/dist/commands/publishing.d.ts +3 -0
  130. package/dist/commands/publishing.js +1419 -0
  131. package/dist/commands/recipes.d.ts +3 -0
  132. package/dist/commands/recipes.js +120 -0
  133. package/dist/commands/reviews.d.ts +3 -0
  134. package/dist/commands/reviews.js +75 -0
  135. package/dist/commands/rows.d.ts +3 -0
  136. package/dist/commands/rows.js +27 -0
  137. package/dist/commands/runs.d.ts +3 -0
  138. package/dist/commands/runs.js +37 -0
  139. package/dist/commands/search.d.ts +3 -0
  140. package/dist/commands/search.js +182 -0
  141. package/dist/commands/senders.d.ts +3 -0
  142. package/dist/commands/senders.js +393 -0
  143. package/dist/commands/sequences.d.ts +3 -0
  144. package/dist/commands/sequences.js +2322 -0
  145. package/dist/commands/session.d.ts +3 -0
  146. package/dist/commands/session.js +109 -0
  147. package/dist/commands/signals.d.ts +3 -0
  148. package/dist/commands/signals.js +289 -0
  149. package/dist/commands/signup-leads.d.ts +3 -0
  150. package/dist/commands/signup-leads.js +46 -0
  151. package/dist/commands/skills.d.ts +3 -0
  152. package/dist/commands/skills.js +50 -0
  153. package/dist/commands/sourcing.d.ts +3 -0
  154. package/dist/commands/sourcing.js +27 -0
  155. package/dist/commands/status.d.ts +3 -0
  156. package/dist/commands/status.js +51 -0
  157. package/dist/commands/supabase.d.ts +3 -0
  158. package/dist/commands/supabase.js +128 -0
  159. package/dist/commands/support.d.ts +3 -0
  160. package/dist/commands/support.js +651 -0
  161. package/dist/commands/suppressions.d.ts +3 -0
  162. package/dist/commands/suppressions.js +577 -0
  163. package/dist/commands/table-ingestions.d.ts +3 -0
  164. package/dist/commands/table-ingestions.js +145 -0
  165. package/dist/commands/table-runs.d.ts +3 -0
  166. package/dist/commands/table-runs.js +277 -0
  167. package/dist/commands/tables.d.ts +3 -0
  168. package/dist/commands/tables.js +3505 -0
  169. package/dist/commands/tags.d.ts +3 -0
  170. package/dist/commands/tags.js +147 -0
  171. package/dist/commands/tools.d.ts +3 -0
  172. package/dist/commands/tools.js +246 -0
  173. package/dist/commands/update.d.ts +3 -0
  174. package/dist/commands/update.js +106 -0
  175. package/dist/commands/verify.d.ts +3 -0
  176. package/dist/commands/verify.js +27 -0
  177. package/dist/commands/viewers.d.ts +3 -0
  178. package/dist/commands/viewers.js +72 -0
  179. package/dist/commands/voice.d.ts +3 -0
  180. package/dist/commands/voice.js +351 -0
  181. package/dist/commands/whatsapp.d.ts +3 -0
  182. package/dist/commands/whatsapp.js +226 -0
  183. package/dist/commands/whoami.d.ts +3 -0
  184. package/dist/commands/whoami.js +110 -0
  185. package/dist/commands/worker.d.ts +3 -0
  186. package/dist/commands/worker.js +60 -0
  187. package/dist/commands/workflows.d.ts +3 -0
  188. package/dist/commands/workflows.js +1752 -0
  189. package/dist/commands/workspace.d.ts +3 -0
  190. package/dist/commands/workspace.js +94 -0
  191. package/dist/compact-list-flags.d.ts +7 -0
  192. package/dist/compact-list-flags.js +12 -0
  193. package/dist/crm-support-flags.js +14 -5
  194. package/dist/customer-operation-commands.js +8 -6
  195. package/dist/diagnostic-uploader.d.ts +3 -1
  196. package/dist/diagnostic-uploader.js +29 -8
  197. package/dist/help.d.ts +5 -0
  198. package/dist/help.js +22 -0
  199. package/dist/index.d.ts +98 -1
  200. package/dist/index.js +331 -30719
  201. package/dist/local-module-loader.d.ts +4 -0
  202. package/dist/local-module-loader.js +156 -0
  203. package/dist/visual-commands.js +9 -5
  204. package/node_modules/@oxygen/cli-ugc/dist/commands.js +2 -2
  205. package/node_modules/@oxygen/shared/dist/agent-session-schedule.d.ts +36 -0
  206. package/node_modules/@oxygen/shared/dist/agent-session-schedule.js +69 -0
  207. package/node_modules/@oxygen/shared/dist/agent-sessions.d.ts +14 -5
  208. package/node_modules/@oxygen/shared/dist/agent-sessions.js +14 -3
  209. package/node_modules/@oxygen/shared/dist/capability-discovery.js +175 -14
  210. package/node_modules/@oxygen/shared/dist/compact-list-result.d.ts +6 -0
  211. package/node_modules/@oxygen/shared/dist/compact-list-result.js +27 -0
  212. package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.d.ts +12 -1
  213. package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.js +5 -2
  214. package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +4 -2
  215. package/node_modules/@oxygen/shared/dist/contracts/compact-list-reads.d.ts +57 -0
  216. package/node_modules/@oxygen/shared/dist/contracts/compact-list-reads.js +23 -0
  217. package/node_modules/@oxygen/shared/dist/contracts/crm-support.d.ts +52 -0
  218. package/node_modules/@oxygen/shared/dist/contracts/crm-support.js +43 -2
  219. package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +20 -16
  220. package/node_modules/@oxygen/shared/dist/contracts/knowledge-content-reads.js +1 -1
  221. package/node_modules/@oxygen/shared/dist/contracts/knowledge-proposal-reads.d.ts +49 -0
  222. package/node_modules/@oxygen/shared/dist/contracts/knowledge-proposal-reads.js +14 -0
  223. package/node_modules/@oxygen/shared/dist/contracts/knowledge-sync.js +1 -1
  224. package/node_modules/@oxygen/shared/dist/contracts/limits-read.js +60 -7
  225. package/node_modules/@oxygen/shared/dist/contracts/operations.d.ts +9 -1
  226. package/node_modules/@oxygen/shared/dist/contracts/operations.js +13 -5
  227. package/node_modules/@oxygen/shared/dist/contracts/publishing-reads.js +1 -1
  228. package/node_modules/@oxygen/shared/dist/contracts/teammates.d.ts +16 -0
  229. package/node_modules/@oxygen/shared/dist/contracts/teammates.js +115 -0
  230. package/node_modules/@oxygen/shared/dist/cost-estimate-view.js +21 -3
  231. package/node_modules/@oxygen/shared/dist/cost-estimate.d.ts +31 -1
  232. package/node_modules/@oxygen/shared/dist/cost-estimate.js +56 -15
  233. package/node_modules/@oxygen/shared/dist/diagnostic-credentials.d.ts +2 -0
  234. package/node_modules/@oxygen/shared/dist/diagnostic-credentials.js +2 -0
  235. package/node_modules/@oxygen/shared/dist/diagnostic-lock.js +71 -44
  236. package/node_modules/@oxygen/shared/dist/diagnostic-spool.d.ts +18 -0
  237. package/node_modules/@oxygen/shared/dist/diagnostic-spool.js +348 -0
  238. package/node_modules/@oxygen/shared/dist/diagnostic-storage-failure.d.ts +10 -0
  239. package/node_modules/@oxygen/shared/dist/diagnostic-storage-failure.js +42 -0
  240. package/node_modules/@oxygen/shared/dist/diagnostic-storage.d.ts +1 -1
  241. package/node_modules/@oxygen/shared/dist/diagnostic-storage.js +59 -213
  242. package/node_modules/@oxygen/shared/dist/diagnostics.js +3 -2
  243. package/node_modules/@oxygen/shared/dist/email-dsn.d.ts +16 -5
  244. package/node_modules/@oxygen/shared/dist/email-dsn.js +97 -1
  245. package/node_modules/@oxygen/shared/dist/emailguard-placement-probe.js +5 -0
  246. package/node_modules/@oxygen/shared/dist/enrichment-intents.js +4 -5
  247. package/node_modules/@oxygen/shared/dist/file-import.d.ts +8 -0
  248. package/node_modules/@oxygen/shared/dist/file-import.js +21 -2
  249. package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +30 -0
  250. package/node_modules/@oxygen/shared/dist/hosted-ai.js +20 -0
  251. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +6 -2
  252. package/node_modules/@oxygen/shared/dist/knowledge-seed-content.d.ts +17 -7
  253. package/node_modules/@oxygen/shared/dist/knowledge-seed-content.js +81 -17
  254. package/node_modules/@oxygen/shared/dist/langfuse.js +8 -4
  255. package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.d.ts +1 -1
  256. package/node_modules/@oxygen/shared/dist/mailbox-import.d.ts +6 -3
  257. package/node_modules/@oxygen/shared/dist/mailbox-import.js +9 -9
  258. package/node_modules/@oxygen/shared/dist/mcp-server-info.d.ts +8 -0
  259. package/node_modules/@oxygen/shared/dist/mcp-server-info.js +10 -0
  260. package/node_modules/@oxygen/shared/dist/object-storage.d.ts +10 -0
  261. package/node_modules/@oxygen/shared/dist/object-storage.js +14 -0
  262. package/node_modules/@oxygen/shared/dist/plan-limits-view.d.ts +5 -0
  263. package/node_modules/@oxygen/shared/dist/plan-limits-view.js +17 -1
  264. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
  265. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +1 -1
  266. package/node_modules/@oxygen/shared/dist/provider-selection.d.ts +15 -0
  267. package/node_modules/@oxygen/shared/dist/provider-selection.js +3 -0
  268. package/node_modules/@oxygen/shared/dist/repricing.d.ts +1 -1
  269. package/node_modules/@oxygen/shared/dist/repricing.js +2 -2
  270. package/node_modules/@oxygen/shared/dist/schedule-label.d.ts +6 -0
  271. package/node_modules/@oxygen/shared/dist/schedule-label.js +12 -0
  272. package/node_modules/@oxygen/shared/dist/sequence-failures.d.ts +1 -0
  273. package/node_modules/@oxygen/shared/dist/sequence-failures.js +20 -6
  274. package/node_modules/@oxygen/shared/dist/teammate-groups.d.ts +55 -0
  275. package/node_modules/@oxygen/shared/dist/teammate-groups.js +74 -0
  276. package/node_modules/@oxygen/shared/dist/teammate-messages.d.ts +52 -0
  277. package/node_modules/@oxygen/shared/dist/teammate-messages.js +88 -0
  278. package/node_modules/@oxygen/shared/dist/teammate-notes.d.ts +58 -0
  279. package/node_modules/@oxygen/shared/dist/teammate-notes.js +73 -0
  280. package/node_modules/@oxygen/shared/dist/teammate-routines.d.ts +95 -0
  281. package/node_modules/@oxygen/shared/dist/teammate-routines.js +266 -0
  282. package/node_modules/@oxygen/shared/dist/teammate-tasks.d.ts +56 -0
  283. package/node_modules/@oxygen/shared/dist/teammate-tasks.js +114 -0
  284. package/node_modules/@oxygen/shared/dist/teammates.d.ts +78 -0
  285. package/node_modules/@oxygen/shared/dist/teammates.js +140 -0
  286. package/node_modules/@oxygen/shared/dist/user-capability-routing.js +120 -0
  287. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  288. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  289. package/node_modules/@oxygen/shared/dist/visual-render.d.ts +9 -1
  290. package/node_modules/@oxygen/shared/dist/visual-render.js +12 -5
  291. package/node_modules/@oxygen/shared/dist/warmup-metrics.d.ts +5 -0
  292. package/node_modules/@oxygen/shared/dist/warmup-metrics.js +44 -0
  293. package/package.json +1 -1
package/README.md CHANGED
@@ -34,4 +34,4 @@ oxygen update
34
34
 
35
35
  For product documentation, visit https://oxygen-agent.com/docs. For support, visit https://oxygen-agent.com.
36
36
 
37
- Version: 1.1014.136
37
+ Version: 1.1014.619
@@ -1,17 +1,4 @@
1
- // Human rendering for `oxygen admin primary-providers`.
2
- //
3
- // The board answers three operator questions — does each primary provider work,
4
- // what does it cost us, and what stops a runaway — and until this file existed
5
- // the CLI answered them with ~1,500 lines of raw JSON while the MCP tool printed
6
- // a worst-first summary. Same route, same payload, two completely different
7
- // things to read. This renders the same facts the MCP text summary and the web
8
- // board render, so the three surfaces agree in front of a human, not only in
9
- // JSON.
10
- //
11
- // Every field is read defensively: the CLI receives untyped JSON from a server
12
- // that may be older or newer than the binary, and a board that silently drops a
13
- // column an operator is looking for is the exact failure this file exists to
14
- // stop. Unknown/absent reads as "—", never as zero.
1
+ import { formatProviderEnvRequirements } from "@oxygen/shared/provider-selection";
15
2
  function obj(value) {
16
3
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
17
4
  }
@@ -196,7 +183,7 @@ export function renderPrimaryProviderBoard(payload, options = {}) {
196
183
  const fallbacks = rowsWorstFirst(board.default_chain_fallbacks);
197
184
  if (fallbacks.length > 0) {
198
185
  out("");
199
- out("Default-chain fallbacks (not primary anywhere — a routed chain reaches them when its primary fails):");
186
+ out("Default-chain fallbacks and conditional companions (fallback, preflight and optional verification roles; not primary anywhere):");
200
187
  for (const row of fallbacks)
201
188
  out(renderProvider(row, now, binary));
202
189
  }
@@ -210,6 +197,19 @@ export function renderPrimaryProviderBoard(payload, options = {}) {
210
197
  for (const row of outsideChains)
211
198
  out(renderProvider(row, now, binary));
212
199
  }
200
+ const productRails = rowsWorstFirst(board.product_rails);
201
+ if (productRails.length) {
202
+ out("");
203
+ out("Product services (default, conditional and legacy rails):");
204
+ for (const row of productRails) {
205
+ const selection = obj(row.selection);
206
+ if (selection)
207
+ out(` ${str(selection.supplier)} · ${arr(selection.uses).join(", ")} · ${str(selection.selection)} · ${str(selection.condition)}`);
208
+ // Product services have their own operational controls; a data-provider
209
+ // spend breaker is not proof that their direct calls will stop.
210
+ out(renderProvider(row, now, binary, false));
211
+ }
212
+ }
213
213
  const intents = arr(board.intents_without_primary)
214
214
  .map((row) => obj(row))
215
215
  .filter((row) => row !== null);
@@ -280,7 +280,7 @@ function renderGuards(guards) {
280
280
  }
281
281
  return `Guards: ${parts.join(" · ")}`;
282
282
  }
283
- function renderProvider(row, now, binary) {
283
+ function renderProvider(row, now, binary, allowSpendHalt = true) {
284
284
  const lines = [];
285
285
  const posture = obj(row.posture) ?? {};
286
286
  const health = obj(row.health) ?? {};
@@ -290,6 +290,14 @@ function renderProvider(row, now, binary) {
290
290
  const limits = obj(row.limits) ?? {};
291
291
  const links = obj(row.links) ?? {};
292
292
  const provider = str(row.provider) ?? "?";
293
+ const requiredEnv = arr(row.required_env).map(str).filter((name) => name !== null);
294
+ if (!requiredEnv.length && str(row.managed_key_env_var))
295
+ requiredEnv.push(str(row.managed_key_env_var));
296
+ const alternatives = arr(row.required_env_any).map(str).filter((name) => name !== null);
297
+ const requirements = formatProviderEnvRequirements(requiredEnv, alternatives);
298
+ const configuration = row.key_configured === false
299
+ ? requiredEnv.length > 1 || alternatives.length ? `INCOMPLETE (requires ${requirements})` : `NOT SET (${requirements || "—"})`
300
+ : requirements || "customer-supplied";
293
301
  const level = str(posture.level) ?? "unknown";
294
302
  const functions = arr(row.functions).map((fn) => String(fn));
295
303
  // A catch-all escalation lane is the only route to its provider and is
@@ -313,7 +321,7 @@ function renderProvider(row, now, binary) {
313
321
  ].filter((part) => part !== null);
314
322
  lines.push(` - ${provider} [${level}]${functions.length > 0 ? ` ${functions.join("+")}` : ""}` +
315
323
  `${escalations.length > 0 ? ` (catch-all escalation: ${escalations.join(", ")})` : ""} · ` +
316
- `key ${row.key_configured === false ? `NOT SET (${str(row.managed_key_env_var) ?? "—"})` : str(row.managed_key_env_var) ?? "customer-supplied"} · ` +
324
+ `key ${configuration} · ` +
317
325
  `health ${healthStatus} (${healthDetail.join(", ")})`);
318
326
  const scope = str(traffic.scope) ?? "workspace_attributed";
319
327
  const trafficLine = row.traffic_7d === undefined
@@ -332,7 +340,7 @@ function renderProvider(row, now, binary) {
332
340
  const ceilings = arr(limits.ceilings).map((ceiling) => obj(ceiling)).filter((c) => c !== null);
333
341
  const worstCeiling = [...ceilings].sort((a, b) => (num(b.utilization) ?? -1) - (num(a.utilization) ?? -1))[0];
334
342
  const breaker = obj(limits.breaker);
335
- lines.push(` rate ${firstPolicy
343
+ lines.push(!allowSpendHalt ? " Service controls: inspect the owning product rail." : ` rate ${firstPolicy
336
344
  ? `${count(firstPolicy.limit)}${windowLabel(firstPolicy.window_seconds)}` +
337
345
  `${num(firstPolicy.max_in_flight) === null ? "" : ` · ${count(firstPolicy.max_in_flight)} in flight`}` +
338
346
  ` · ${firstPolicy.window_expired === true ? "idle" : `${count(firstPolicy.current)} this window`}` +
@@ -352,7 +360,7 @@ function renderProvider(row, now, binary) {
352
360
  ].filter((part) => part !== null);
353
361
  if (linkParts.length > 0)
354
362
  lines.push(` ${linkParts.join(" · ")}`);
355
- if (level === "critical" || level === "attention") {
363
+ if (allowSpendHalt && (level === "critical" || level === "attention")) {
356
364
  lines.push(` halt: ${binary} admin spend halt --scope provider --key ${provider}`);
357
365
  }
358
366
  return lines.join("\n");
@@ -0,0 +1,12 @@
1
+ export type JsonOptions = {
2
+ json?: boolean;
3
+ full?: boolean;
4
+ };
5
+ export declare function writeJson(value: unknown): void;
6
+ export declare function emitSuccess<T>(command: string, data: T, options: JsonOptions): void;
7
+ export declare function handleAsyncAction(command: string, options: JsonOptions, action: () => Promise<unknown>): Promise<void>;
8
+ export declare function emitCliFailure(command: string, error: unknown): void;
9
+ export declare function writeBillingNotices(command: string, data: unknown): void;
10
+ export declare function asPayloadRecord(value: unknown): Record<string, unknown> | null;
11
+ export declare function writeCreditsReceipt(data: unknown): void;
12
+ export declare function readDetailsNumber(details: unknown, key: string): number | null;
@@ -0,0 +1,395 @@
1
+ import { formatProviderLaneNotices } from "./column-run-notices.js";
2
+ import { recordCliDiagnosticFailure } from "./diagnostics.js";
3
+ import { formatInboxNeedsReplyNotice } from "./inbox-needs-reply-notice.js";
4
+ import { serializeCliJson } from "./json-output.js";
5
+ import { formatMailboxRampNotice } from "./mailbox-ramp-notice.js";
6
+ import { formatSearchAiFilterTotalsNotice } from "./search-ai-filter-notice.js";
7
+ import { isRecord } from "./util.js";
8
+ import { exitCodeForOxygenError, OxygenError, success, toFailure } from "@oxygen/shared/cli-result";
9
+ export function writeJson(value) {
10
+ process.stdout.write(serializeCliJson(value, process.argv.includes("--pretty")));
11
+ }
12
+ export function emitSuccess(command, data, options) {
13
+ if (options.json) {
14
+ writeJson(success(command, data));
15
+ return;
16
+ }
17
+ writeJson(data);
18
+ }
19
+ export async function handleAsyncAction(command, options, action) {
20
+ try {
21
+ const data = await action();
22
+ // BEFORE the envelope, deliberately: an unpaid renewal or a recurring shortfall is the
23
+ // reason the customer ran the command, and a warning printed after 150 lines of JSON
24
+ // has already scrolled away. stdout stays the clean machine-readable envelope — the
25
+ // same stdout/stderr split writeDryRunNotice and writeCreditsReceipt use.
26
+ writeBillingNotices(command, data);
27
+ emitSuccess(command, data, options);
28
+ writeDryRunNotice(data);
29
+ writeManagedProviderAvailabilityNotice(data);
30
+ writeAvatarWarning(data);
31
+ writeCreditsReceipt(data);
32
+ writeSearchAiFilterTotalsNotice(data);
33
+ writeInboxNeedsReplyNotice(data);
34
+ writeMailboxRampNotice(data);
35
+ }
36
+ catch (error) {
37
+ emitCliFailure(command, error);
38
+ }
39
+ }
40
+ // The preview-time face of the managed-provider credit bench (OXP-1.14.11).
41
+ // `companies search plan` / `companies search run --mode dry_run` carry
42
+ // `provider_availability` and `verify email` (dry_run) carries
43
+ // `catch_all_escalation_available`; both are in the envelope for --json readers,
44
+ // but a benched provider buried in a 150-line plan is exactly how a customer
45
+ // sizes and approves a run that Oxygen already knows will be refused. One line
46
+ // per benched provider on stderr, same stdout/stderr split as the dry-run notice.
47
+ function writeManagedProviderAvailabilityNotice(data) {
48
+ const payload = asPayloadRecord(data);
49
+ if (!payload)
50
+ return;
51
+ const availability = asPayloadRecord(payload.provider_availability);
52
+ // `columns run --dry-run` and `enrich-column preview` name their lanes: one
53
+ // line per lane that cannot run, led by the verdict (OXP-1.14.12).
54
+ if (availability && Array.isArray(availability.lanes)) {
55
+ for (const line of formatProviderLaneNotices(availability))
56
+ process.stderr.write(`${line}\n`);
57
+ return;
58
+ }
59
+ if (availability) {
60
+ const providers = asPayloadRecord(availability.providers) ?? {};
61
+ for (const [provider, entry] of Object.entries(providers)) {
62
+ const record = asPayloadRecord(entry);
63
+ if (record?.status !== "benched" && record?.status !== "unknown")
64
+ continue;
65
+ const message = typeof record.message === "string" ? record.message
66
+ : record.status === "unknown" ? `Oxygen could not check its managed ${provider} account availability.`
67
+ : `Oxygen's managed ${provider} account is benched.`;
68
+ const nextAction = typeof record.next_action === "string" ? ` ${record.next_action}` : "";
69
+ process.stderr.write(`! ${message}${nextAction}\n`);
70
+ }
71
+ return;
72
+ }
73
+ if ((payload.catch_all_escalation_available === false || payload.catch_all_escalation_availability === "unknown")
74
+ && typeof payload.next_action === "string") {
75
+ process.stderr.write(`! ${payload.next_action}\n`);
76
+ }
77
+ }
78
+ // Single-source the CLI failure-emit contract: write the machine-readable
79
+ // failure envelope to stdout, surface any spend-gate hint on stderr, and set the
80
+ // process exit code from the error. Command handlers that don't route through
81
+ // handleAsyncAction catch straight into this so the shape can't drift.
82
+ export function emitCliFailure(command, error) {
83
+ recordCliDiagnosticFailure(error);
84
+ writeJson(toFailure(command, error));
85
+ writeMaxCreditsHint(error);
86
+ process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
87
+ }
88
+ function writeDryRunNotice(data) {
89
+ if (!data || typeof data !== "object" || Array.isArray(data))
90
+ return;
91
+ const preview = data.preview;
92
+ if (!preview || typeof preview !== "object" || Array.isArray(preview))
93
+ return;
94
+ const block = preview;
95
+ if (typeof block.message === "string")
96
+ process.stderr.write(`${block.message}\n`);
97
+ if (typeof block.next_step === "string")
98
+ process.stderr.write(`${block.next_step}\n`);
99
+ const byok = readByokCredentialMode(data);
100
+ if (byok) {
101
+ const fee = asPayloadRecord(asPayloadRecord(data)?.credits)?.estimated_credits;
102
+ process.stderr.write(typeof fee === "number"
103
+ ? `Your own-key run (${byok}) is estimated at ${fee} Oxygen credits; your provider bills its call separately.\n`
104
+ : `Your own-key run (${byok}) may incur an Oxygen platform fee; check the current credit quote before running it live. Your provider bills its call separately.\n`);
105
+ }
106
+ }
107
+ /**
108
+ * The tool run's credential mode when the call is NOT on Oxygen's managed key
109
+ * — the one fact that decides whether a quoted credit estimate means anything.
110
+ * Null for managed runs and for payloads with no billing block at all.
111
+ */
112
+ function readByokCredentialMode(data) {
113
+ const result = asPayloadRecord(asPayloadRecord(data)?.result);
114
+ const billing = asPayloadRecord(asPayloadRecord(result?.meta)?.billing);
115
+ const mode = billing?.credential_mode;
116
+ if (mode !== "byok" && mode !== "user_api_key")
117
+ return null;
118
+ return mode;
119
+ }
120
+ // A sender-profile write that could not durably host the photo still succeeds —
121
+ // the profile is saved, just with a picture no inbox vendor will ever be able to
122
+ // fetch. That is the one failure mode nobody notices until the mailboxes ship
123
+ // faceless and the vendor refuses to change them, so the server's
124
+ // `avatar_warning` gets its own stderr line instead of living only inside the
125
+ // stdout envelope a human never reads.
126
+ function writeAvatarWarning(data) {
127
+ if (!data || typeof data !== "object" || Array.isArray(data))
128
+ return;
129
+ const warning = data.avatar_warning;
130
+ if (typeof warning === "string" && warning.trim())
131
+ process.stderr.write(`${warning}\n`);
132
+ }
133
+ // The human half of the unpaid-renewal surfaces: `billing balance` warnings, the
134
+ // `billing allowance` past_due segment, and the billing state of each managed domain in
135
+ // `domains list`. All three are already in the JSON envelope (and stay there for --json);
136
+ // these lines exist because none of them is legible at a glance in a payload, and the one
137
+ // customer this shipped for could read every number in the product and still not learn
138
+ // that 139,000 credits of renewals had failed.
139
+ export function writeBillingNotices(command, data) {
140
+ const payload = asPayloadRecord(data);
141
+ if (!payload)
142
+ return;
143
+ // Any list that carries connected accounts (senders, WhatsApp numbers, mailboxes,
144
+ // billing commitments) says so when some stopped sending because their monthly
145
+ // credit reservation could not be renewed (decision 2.8). Before the envelope, for
146
+ // the same reason as the renewal notices below.
147
+ const paused = asPayloadRecord(payload.reservations_paused);
148
+ if (paused && typeof paused.message === "string" && paused.message.trim()) {
149
+ process.stderr.write(`! ${paused.message}\n`);
150
+ }
151
+ if (command === "billing balance") {
152
+ for (const warning of readWarningMessages(payload))
153
+ process.stderr.write(`! ${warning}\n`);
154
+ return;
155
+ }
156
+ if (command === "billing allowance") {
157
+ const unpaid = asPayloadRecord(payload.renewals_past_due);
158
+ if (!unpaid)
159
+ return;
160
+ const count = typeof unpaid.count === "number" ? unpaid.count : 0;
161
+ const credits = typeof unpaid.credits_required === "number" ? unpaid.credits_required : 0;
162
+ // Said explicitly because it is the one number in this command that is NOT part of the
163
+ // pool: it is money the biller could not take, so it is not in total_credits.
164
+ process.stderr.write(`! renewals unpaid: ${count} item${count === 1 ? "" : "s"}, ${credits.toLocaleString("en-US")} credits — not included in total_credits. Run \`oxygen billing balance\` for the top-up amount.\n`);
165
+ return;
166
+ }
167
+ if (command === "domains list") {
168
+ const managed = Array.isArray(payload.managed_domains) ? payload.managed_domains : [];
169
+ const unpaid = managed
170
+ .map((row) => asPayloadRecord(row))
171
+ .filter((row) => row?.internal_billing_status === "past_due")
172
+ .map((row) => (typeof row.domain === "string" ? row.domain : "unknown"));
173
+ if (unpaid.length === 0)
174
+ return;
175
+ // The vendor keeps these live, so their `status` still reads `active` — which is
176
+ // exactly why naming them is worth a line.
177
+ process.stderr.write(`! renewal unpaid on ${unpaid.length} managed domain${unpaid.length === 1 ? "" : "s"}: ${unpaid.join(", ")} (see internal_billing_status)\n`);
178
+ }
179
+ }
180
+ function readWarningMessages(payload) {
181
+ if (!Array.isArray(payload.warnings))
182
+ return [];
183
+ return payload.warnings
184
+ .map((warning) => asPayloadRecord(warning)?.message)
185
+ .filter((message) => typeof message === "string" && message.trim().length > 0);
186
+ }
187
+ export function asPayloadRecord(value) {
188
+ return value && typeof value === "object" && !Array.isArray(value)
189
+ ? value
190
+ : null;
191
+ }
192
+ // Paid envelopes (push 3 legibility) carry a `credits` block: quote on
193
+ // dry_run, receipt on live, remaining balance on both. Mirror it as one
194
+ // stderr line so spend stays visible in a terminal without polluting the
195
+ // machine-read stdout envelope.
196
+ export function writeCreditsReceipt(data) {
197
+ if (!data || typeof data !== "object" || Array.isArray(data))
198
+ return;
199
+ const credits = data.credits;
200
+ if (!credits || typeof credits !== "object" || Array.isArray(credits))
201
+ return;
202
+ const block = credits;
203
+ const remaining = typeof block.credits_remaining === "number"
204
+ ? block.credits_remaining.toLocaleString("en-US")
205
+ : null;
206
+ if (typeof block.credits_used === "number") {
207
+ process.stderr.write(`spent ${block.credits_used.toLocaleString("en-US")} credits${remaining !== null ? `, ${remaining} remaining` : ""}\n`);
208
+ return;
209
+ }
210
+ if (typeof block.estimated_credits === "number") {
211
+ const byok = readByokCredentialMode(data);
212
+ process.stderr.write(byok
213
+ ? `estimated ${block.estimated_credits.toLocaleString("en-US")} Oxygen credits for a live run on your own key (${byok})${remaining !== null ? `, ${remaining} available` : ""}\n`
214
+ : `estimated ${block.estimated_credits.toLocaleString("en-US")} credits for a live run${remaining !== null ? `, ${remaining} available` : ""}\n`);
215
+ // Last, where an agent reading the tail of the output sees it: the
216
+ // estimate above is a ceiling, and only kept posts are charged
217
+ // (blind acceptance, 2026-09-25: agents read `| tail` and missed it).
218
+ // The same for what an existing table's saved auto-run adds to this run.
219
+ for (const key of ["ai_filter_reasons", "standing_auto_run"]) {
220
+ const summary = asPayloadRecord(data[key])?.summary;
221
+ if (typeof summary === "string")
222
+ process.stderr.write(`${summary}\n`);
223
+ }
224
+ }
225
+ }
226
+ function writeSearchAiFilterTotalsNotice(data) {
227
+ const notice = formatSearchAiFilterTotalsNotice(data);
228
+ if (notice)
229
+ process.stderr.write(`${notice}\n`);
230
+ }
231
+ function writeInboxNeedsReplyNotice(data) {
232
+ const notice = formatInboxNeedsReplyNotice(data);
233
+ if (notice)
234
+ process.stderr.write(`${notice}\n`);
235
+ }
236
+ function writeMailboxRampNotice(data) {
237
+ const notice = formatMailboxRampNotice(data);
238
+ if (notice)
239
+ process.stderr.write(`${notice}\n`);
240
+ }
241
+ // Paid live runs and ambiguous external-effect retries are refused server-side with typed safety-gate errors
242
+ // (max_credits_required, approval_required, spend_cap_required,
243
+ // spend_cap_too_low). Surface each as a one-line stderr hint with the concrete
244
+ // re-run flags so users don't have to dig values out of the JSON envelope
245
+ // (stderr keeps --json stdout machine-clean).
246
+ function writeMaxCreditsHint(error) {
247
+ if (!(error instanceof OxygenError))
248
+ return;
249
+ switch (error.code) {
250
+ // The plan wall. Unlike every other case in this switch, no flag on the
251
+ // command can unblock it — so the hint names the capability in the
252
+ // customer's own words, says what still works without upgrading, and gives
253
+ // the URL. A raw `upgrade_required` code with a JSON blob is the worst
254
+ // possible version of the single most important refusal in the product.
255
+ // The BYOK run gate keeps its own code but carries the same upgrade fields,
256
+ // including once a free workspace's seven-day key grace has ended. Without
257
+ // an upgrade_url (an older server) it has no hint, as before.
258
+ case "byok_requires_paid_plan":
259
+ case "upgrade_required": {
260
+ if (error.code === "byok_requires_paid_plan" && !readDetailsString(error.details, "upgrade_url"))
261
+ return;
262
+ const label = readDetailsString(error.details, "capability_label")
263
+ ?? "This capability";
264
+ const upgradeUrl = readDetailsString(error.details, "upgrade_url");
265
+ const nextStep = readDetailsString(error.details, "next_step");
266
+ // BYOK names the smallest plan that includes it ("the $99 plan or above").
267
+ const requirement = readDetailsString(error.details, "minimum_plan_label") ?? "a paid plan";
268
+ process.stderr.write(`hint: ${label} needs ${requirement}.\n`);
269
+ if (nextStep)
270
+ process.stderr.write(`hint: ${nextStep}\n`);
271
+ if (upgradeUrl)
272
+ process.stderr.write(`hint: upgrade at ${upgradeUrl}\n`);
273
+ return;
274
+ }
275
+ case "effect_unknown_approval_required": {
276
+ process.stderr.write("hint: verify every affected external destination in error.details, then re-run with --approved-effect-unknown only if another execution is safe\n");
277
+ return;
278
+ }
279
+ // A connect refused because the balance cannot carry the account's first
280
+ // monthly credit reservation. Only that shape carries reservation_credits;
281
+ // every other insufficient_credits keeps its own message.
282
+ case "insufficient_credits": {
283
+ const reservation = readDetailsNumber(error.details, "reservation_credits");
284
+ if (reservation === null)
285
+ return;
286
+ const nextStep = readDetailsString(error.details, "next_step");
287
+ const topupUrl = readDetailsString(error.details, "topup_url");
288
+ process.stderr.write(`hint: this connect reserves ${reservation} credits a month; add credits, then connect again\n`);
289
+ if (nextStep)
290
+ process.stderr.write(`hint: ${nextStep}\n`);
291
+ if (topupUrl)
292
+ process.stderr.write(`hint: top up at ${topupUrl}\n`);
293
+ return;
294
+ }
295
+ case "seats_retired": {
296
+ const nextStep = readDetailsString(error.details, "next_step");
297
+ process.stderr.write(`hint: sending seats are retired; connected accounts reserve credits instead${nextStep ? ` — see \`${nextStep}\`` : ""}\n`);
298
+ return;
299
+ }
300
+ case "max_credits_required": {
301
+ const recommended = readDetailsNumber(error.details, "recommended_max_credits");
302
+ if (recommended === null)
303
+ return;
304
+ process.stderr.write(`hint: re-run with --max-credits ${recommended} to approve the spend cap\n`);
305
+ return;
306
+ }
307
+ case "approval_required": {
308
+ // A COLLABORATION gate reuses this code but is not a spend gate: no flag
309
+ // on this command can unblock it, because the whole point is that a
310
+ // second party decides. Printing the "--approved --max-credits" hint here
311
+ // would send the operator hunting for a flag that does not exist, so the
312
+ // gate branch runs first and prints the command the OTHER person runs.
313
+ const gateKind = readDetailsString(error.details, "gate_kind");
314
+ if (gateKind) {
315
+ const nextStep = readDetailsString(error.details, "next_step");
316
+ process.stderr.write(`hint: blocked by the ${gateKind} approval gate — ${nextStep ?? "ask an assignee to decide it"}\n`);
317
+ const lastDecision = isRecord(error.details) && isRecord(error.details.last_decision)
318
+ ? error.details.last_decision
319
+ : null;
320
+ if (lastDecision) {
321
+ const note = readDetailsString(lastDecision, "note");
322
+ process.stderr.write(`hint: the last answer was ${readDetailsString(lastDecision, "status") ?? "recorded"}${note ? ` — "${note}"` : ""}; revise, then ask again\n`);
323
+ }
324
+ const requestUrl = readDetailsString(error.details, "pending_request_web_url")
325
+ ?? readDetailsString(error.details, "subject_web_url");
326
+ if (requestUrl)
327
+ process.stderr.write(`hint: ${requestUrl}\n`);
328
+ return;
329
+ }
330
+ // An Agent run or trigger asked for more than the default Agent run
331
+ // ceiling: the one extra flag is the approval, so name it with the numbers.
332
+ if (readDetailsString(error.details, "approval_kind") === "agent_ceiling_above_default") {
333
+ const requested = readDetailsNumber(error.details, "requested_max_credits");
334
+ const defaultMax = readDetailsNumber(error.details, "default_max_credits");
335
+ process.stderr.write(`hint: ${requested ?? "this"} credits is above your default Agent run ceiling of ${defaultMax ?? "your plan"}; re-run with --approve-above-default to approve it, or lower --max-credits\n`);
336
+ return;
337
+ }
338
+ if (error.message.startsWith("Public replies require --approved")) {
339
+ process.stderr.write("hint: inspect the exact preview, then re-run with its --content-hash <sha256> and --approved\n");
340
+ return;
341
+ }
342
+ // Free-but-irreversible gates (tables/columns delete --now) approve with
343
+ // --yes and spend nothing — the spend-flag hint would name flags the
344
+ // command does not take. The server message is the signal: it spells out
345
+ // "(CLI: --yes)" on those gates.
346
+ // Amplification grants name their own three flags and have no dry run.
347
+ if (error.message.includes("--acknowledge-risk")) {
348
+ process.stderr.write("hint: re-run with --approved --max-credits <n> --acknowledge-risk\n");
349
+ return;
350
+ }
351
+ if (error.message.includes("--yes")) {
352
+ process.stderr.write("hint: inspect the preview, then re-run with --yes to approve the permanent deletion\n");
353
+ return;
354
+ }
355
+ const estimated = readDetailsNumber(error.details, "estimated_credits");
356
+ process.stderr.write(`hint: inspect the dry run, then re-run with --approved --max-credits <n>${estimated !== null ? ` (estimated ~${estimated} credits)` : ""}\n`);
357
+ return;
358
+ }
359
+ case "spend_cap_required": {
360
+ const estimated = readDetailsNumber(error.details, "estimated_credits");
361
+ process.stderr.write(`hint: re-run with --max-credits ${estimated !== null ? estimated : "<n>"} to cap the spend\n`);
362
+ return;
363
+ }
364
+ case "spend_cap_too_low": {
365
+ const estimated = readDetailsNumber(error.details, "estimated_credits")
366
+ ?? readDetailsNumber(error.details, "estimated_max_credits");
367
+ if (estimated === null)
368
+ return;
369
+ process.stderr.write(`hint: the estimate is ${estimated} credits; re-run with --max-credits ${estimated} or higher\n`);
370
+ return;
371
+ }
372
+ case "copilot_send_timeout": {
373
+ const nextStep = readDetailsString(error.details, "next_step");
374
+ const interrupt = readDetailsString(error.details, "interrupt_command");
375
+ if (nextStep)
376
+ process.stderr.write(`hint: the turn is still running; read it with \`${nextStep}\`\n`);
377
+ if (interrupt)
378
+ process.stderr.write(`hint: stop it (and its spend) with \`${interrupt}\`\n`);
379
+ return;
380
+ }
381
+ default:
382
+ }
383
+ }
384
+ export function readDetailsNumber(details, key) {
385
+ if (!details || typeof details !== "object" || Array.isArray(details))
386
+ return null;
387
+ const value = details[key];
388
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
389
+ }
390
+ function readDetailsString(details, key) {
391
+ if (!isRecord(details))
392
+ return null;
393
+ const value = details[key];
394
+ return typeof value === "string" && value.trim() ? value.trim() : null;
395
+ }
@@ -0,0 +1 @@
1
+ export declare const CLI_MODULE_DIR: string;
@@ -0,0 +1,3 @@
1
+ import { dirname } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ export const CLI_MODULE_DIR = dirname(fileURLToPath(import.meta.url));
@@ -0,0 +1,3 @@
1
+ export type CliProfileSource = "flag" | "env" | "file" | "default";
2
+ export declare function recordCliProfileFlag(profile: string): void;
3
+ export declare function cliProfileSource(fallback: Exclude<CliProfileSource, "flag">): CliProfileSource;
@@ -0,0 +1,13 @@
1
+ // Capture the launch environment before Commander's preAction hook applies flags.
2
+ const initialProfileEnv = process.env.OXYGEN_PROFILE?.trim() || null;
3
+ let profileFlag = null;
4
+ export function recordCliProfileFlag(profile) {
5
+ profileFlag = profile;
6
+ }
7
+ export function cliProfileSource(fallback) {
8
+ if (profileFlag)
9
+ return "flag";
10
+ if (initialProfileEnv)
11
+ return "env";
12
+ return fallback;
13
+ }
@@ -21,6 +21,21 @@ export type CommandManifestEntry = {
21
21
  preview_by_default: boolean;
22
22
  json_supported: boolean;
23
23
  hidden: boolean;
24
+ execution_modes?: {
25
+ default: "live";
26
+ dry_run: {
27
+ credits: 0;
28
+ generates: false;
29
+ approval_required: false;
30
+ };
31
+ live: {
32
+ credits: number;
33
+ generates: true;
34
+ approval_required: true;
35
+ approval_scope: "exact_input_and_preview_cost";
36
+ minimum_max_credits: number;
37
+ };
38
+ };
24
39
  };
25
40
  export type CommandManifest = {
26
41
  binary: string;
@@ -1,4 +1,5 @@
1
1
  import { OPERATION_SPECS } from "@oxygen/shared/contracts/operations";
2
+ import { SPINTAX_GENERATION_CREDITS } from "@oxygen/shared/contracts/spintax";
2
3
  import { CLI_EXIT_CODE_TABLE, OXYGEN_VERSION, inferUserCapabilityRoute, serializeCapabilityRoute, } from "@oxygen/shared";
3
4
  const DEFAULT_COMMAND_SEARCH_LIMIT = 10;
4
5
  const MAX_COMMAND_SEARCH_LIMIT = 25;
@@ -108,8 +109,12 @@ const MUTATING_COMMANDS = new Set([
108
109
  // credits. The exact exceptions disclose writes without inventing spend.
109
110
  "billing balance",
110
111
  "billing seats",
111
- // The ledger audit reads that same balance first, with the same conditional writes.
112
+ // A sponsorship quote reads that same reconciling balance. It creates no
113
+ // reservation, but a due grant or sponsored-credit expiry can still write.
114
+ "ugc sponsorship preview",
115
+ // The ledger audit and usage read that same balance first, with conditional writes.
112
116
  "billing audit",
117
+ "billing usage",
113
118
  // Creates an approval or Checkout link; a first plan's Checkout can start the
114
119
  // first-month offer window. The plan changes only after confirmation.
115
120
  "billing change",
@@ -136,6 +141,9 @@ const MUTATING_COMMANDS = new Set([
136
141
  "viewers enrichment",
137
142
  // A noun leaf that switches a bought domain's yearly paid renewal on or off.
138
143
  "domains renewal",
144
+ // Runs the isolated renderer and writes its pages back as workspace files. It
145
+ // is free since 2026-10-14, so no --approved/--max-credits flag reports it.
146
+ "visuals render-start",
139
147
  // The plain standup view in an interactive terminal moves the user's Home
140
148
  // "last seen" marker, as a web Home visit does; `--json`, or output that is not
141
149
  // a terminal (an agent's shell, a pipe), is a pure read that leaves it alone
@@ -151,6 +159,7 @@ const MUTATING_COMMANDS = new Set([
151
159
  // mode, but a small number of approved operations are explicitly
152
160
  // zero-credit. Keep those exceptions exact so discovery never invents spend.
153
161
  const ZERO_CREDIT_APPROVAL_COMMANDS = new Set([
162
+ "domains dns apply",
154
163
  // The compatibility --approved flag never bypasses canonical proposal review.
155
164
  // Wiki revisions and canonical pin changes are free (Knowledge audit, 2026-10-06).
156
165
  "knowledge page upsert",
@@ -226,6 +235,7 @@ const READ_ONLY_COMMANDS = new Set([
226
235
  // invocation still previews, so machine-readable discovery must not imply that
227
236
  // calling the command without its confirmation flag writes anything.
228
237
  const PREVIEW_BY_DEFAULT_COMMANDS = new Set([
238
+ "domains dns apply",
229
239
  "mailboxes warmup enable",
230
240
  "mailboxes warmup google",
231
241
  "deliverability placement-test run",
@@ -445,6 +455,18 @@ function toManifestEntry(command, path) {
445
455
  PREVIEW_BY_DEFAULT_COMMANDS.has(commandName),
446
456
  json_supported: flagStrings.some((flags) => flags.includes("--json")),
447
457
  hidden: isHiddenCommand(command),
458
+ ...(commandName === "spintax generate" ? {
459
+ execution_modes: {
460
+ default: "live",
461
+ dry_run: { credits: 0, generates: false, approval_required: false },
462
+ live: {
463
+ credits: SPINTAX_GENERATION_CREDITS,
464
+ generates: true, approval_required: true,
465
+ approval_scope: "exact_input_and_preview_cost",
466
+ minimum_max_credits: SPINTAX_GENERATION_CREDITS,
467
+ },
468
+ },
469
+ } : {}),
448
470
  };
449
471
  }
450
472
  function isHiddenCommand(command) {