@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.
- package/README.md +1 -1
- package/dist/admin-primary-providers-render.js +27 -19
- package/dist/cli-output.d.ts +12 -0
- package/dist/cli-output.js +395 -0
- package/dist/cli-paths.d.ts +1 -0
- package/dist/cli-paths.js +3 -0
- package/dist/cli-profile-context.d.ts +3 -0
- package/dist/cli-profile-context.js +13 -0
- package/dist/command-manifest.d.ts +15 -0
- package/dist/command-manifest.js +23 -1
- package/dist/command-support.d.ts +438 -0
- package/dist/command-support.js +1610 -0
- package/dist/commands/action-column.d.ts +3 -0
- package/dist/commands/action-column.js +43 -0
- package/dist/commands/activation.d.ts +3 -0
- package/dist/commands/activation.js +24 -0
- package/dist/commands/admin.d.ts +3 -0
- package/dist/commands/admin.js +347 -0
- package/dist/commands/ads.d.ts +3 -0
- package/dist/commands/ads.js +62 -0
- package/dist/commands/agent.d.ts +3 -0
- package/dist/commands/agent.js +274 -0
- package/dist/commands/api-keys.d.ts +3 -0
- package/dist/commands/api-keys.js +77 -0
- package/dist/commands/approvals.d.ts +3 -0
- package/dist/commands/approvals.js +518 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.js +230 -0
- package/dist/commands/billing.d.ts +3 -0
- package/dist/commands/billing.js +357 -0
- package/dist/commands/blueprints.d.ts +3 -0
- package/dist/commands/blueprints.js +312 -0
- package/dist/commands/budget.d.ts +3 -0
- package/dist/commands/budget.js +55 -0
- package/dist/commands/callables.d.ts +3 -0
- package/dist/commands/callables.js +111 -0
- package/dist/commands/capabilities.d.ts +3 -0
- package/dist/commands/capabilities.js +44 -0
- package/dist/commands/cells.d.ts +3 -0
- package/dist/commands/cells.js +94 -0
- package/dist/commands/columns.d.ts +3 -0
- package/dist/commands/columns.js +1628 -0
- package/dist/commands/commands.d.ts +3 -0
- package/dist/commands/commands.js +45 -0
- package/dist/commands/comments.d.ts +3 -0
- package/dist/commands/comments.js +188 -0
- package/dist/commands/companies.d.ts +3 -0
- package/dist/commands/companies.js +312 -0
- package/dist/commands/connections.d.ts +3 -0
- package/dist/commands/connections.js +39 -0
- package/dist/commands/context.d.ts +3 -0
- package/dist/commands/context.js +170 -0
- package/dist/commands/copilot.d.ts +3 -0
- package/dist/commands/copilot.js +565 -0
- package/dist/commands/crm.d.ts +3 -0
- package/dist/commands/crm.js +1672 -0
- package/dist/commands/custom-integrations.d.ts +3 -0
- package/dist/commands/custom-integrations.js +140 -0
- package/dist/commands/dashboards.d.ts +3 -0
- package/dist/commands/dashboards.js +240 -0
- package/dist/commands/db.d.ts +3 -0
- package/dist/commands/db.js +190 -0
- package/dist/commands/deliverability.d.ts +3 -0
- package/dist/commands/deliverability.js +90 -0
- package/dist/commands/directory.d.ts +3 -0
- package/dist/commands/directory.js +101 -0
- package/dist/commands/domains.d.ts +3 -0
- package/dist/commands/domains.js +604 -0
- package/dist/commands/egress.d.ts +3 -0
- package/dist/commands/egress.js +130 -0
- package/dist/commands/email.d.ts +3 -0
- package/dist/commands/email.js +49 -0
- package/dist/commands/engagement.d.ts +3 -0
- package/dist/commands/engagement.js +189 -0
- package/dist/commands/enrich-column.d.ts +3 -0
- package/dist/commands/enrich-column.js +149 -0
- package/dist/commands/enrichment.d.ts +3 -0
- package/dist/commands/enrichment.js +49 -0
- package/dist/commands/feedback.d.ts +3 -0
- package/dist/commands/feedback.js +96 -0
- package/dist/commands/feeds.d.ts +3 -0
- package/dist/commands/feeds.js +486 -0
- package/dist/commands/find.d.ts +3 -0
- package/dist/commands/find.js +159 -0
- package/dist/commands/followers.d.ts +3 -0
- package/dist/commands/followers.js +39 -0
- package/dist/commands/formulas.d.ts +3 -0
- package/dist/commands/formulas.js +43 -0
- package/dist/commands/home.d.ts +3 -0
- package/dist/commands/home.js +22 -0
- package/dist/commands/inbox.d.ts +3 -0
- package/dist/commands/inbox.js +779 -0
- package/dist/commands/integrations.d.ts +3 -0
- package/dist/commands/integrations.js +290 -0
- package/dist/commands/knowledge.d.ts +3 -0
- package/dist/commands/knowledge.js +874 -0
- package/dist/commands/lead-sourcing.d.ts +3 -0
- package/dist/commands/lead-sourcing.js +52 -0
- package/dist/commands/limits.d.ts +3 -0
- package/dist/commands/limits.js +54 -0
- package/dist/commands/linkedin.d.ts +3 -0
- package/dist/commands/linkedin.js +275 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.js +347 -0
- package/dist/commands/logout.d.ts +3 -0
- package/dist/commands/logout.js +12 -0
- package/dist/commands/mailboxes.d.ts +3 -0
- package/dist/commands/mailboxes.js +1290 -0
- package/dist/commands/managed-inboxes.d.ts +3 -0
- package/dist/commands/managed-inboxes.js +361 -0
- package/dist/commands/messages.d.ts +3 -0
- package/dist/commands/messages.js +78 -0
- package/dist/commands/notetaker.d.ts +3 -0
- package/dist/commands/notetaker.js +79 -0
- package/dist/commands/observability.d.ts +3 -0
- package/dist/commands/observability.js +125 -0
- package/dist/commands/orgs.d.ts +3 -0
- package/dist/commands/orgs.js +237 -0
- package/dist/commands/people.d.ts +3 -0
- package/dist/commands/people.js +353 -0
- package/dist/commands/posts.d.ts +3 -0
- package/dist/commands/posts.js +139 -0
- package/dist/commands/profiles.d.ts +3 -0
- package/dist/commands/profiles.js +250 -0
- package/dist/commands/projects.d.ts +3 -0
- package/dist/commands/projects.js +94 -0
- package/dist/commands/prompts.d.ts +3 -0
- package/dist/commands/prompts.js +84 -0
- package/dist/commands/publishing.d.ts +3 -0
- package/dist/commands/publishing.js +1419 -0
- package/dist/commands/recipes.d.ts +3 -0
- package/dist/commands/recipes.js +120 -0
- package/dist/commands/reviews.d.ts +3 -0
- package/dist/commands/reviews.js +75 -0
- package/dist/commands/rows.d.ts +3 -0
- package/dist/commands/rows.js +27 -0
- package/dist/commands/runs.d.ts +3 -0
- package/dist/commands/runs.js +37 -0
- package/dist/commands/search.d.ts +3 -0
- package/dist/commands/search.js +182 -0
- package/dist/commands/senders.d.ts +3 -0
- package/dist/commands/senders.js +393 -0
- package/dist/commands/sequences.d.ts +3 -0
- package/dist/commands/sequences.js +2322 -0
- package/dist/commands/session.d.ts +3 -0
- package/dist/commands/session.js +109 -0
- package/dist/commands/signals.d.ts +3 -0
- package/dist/commands/signals.js +289 -0
- package/dist/commands/signup-leads.d.ts +3 -0
- package/dist/commands/signup-leads.js +46 -0
- package/dist/commands/skills.d.ts +3 -0
- package/dist/commands/skills.js +50 -0
- package/dist/commands/sourcing.d.ts +3 -0
- package/dist/commands/sourcing.js +27 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.js +51 -0
- package/dist/commands/supabase.d.ts +3 -0
- package/dist/commands/supabase.js +128 -0
- package/dist/commands/support.d.ts +3 -0
- package/dist/commands/support.js +651 -0
- package/dist/commands/suppressions.d.ts +3 -0
- package/dist/commands/suppressions.js +577 -0
- package/dist/commands/table-ingestions.d.ts +3 -0
- package/dist/commands/table-ingestions.js +145 -0
- package/dist/commands/table-runs.d.ts +3 -0
- package/dist/commands/table-runs.js +277 -0
- package/dist/commands/tables.d.ts +3 -0
- package/dist/commands/tables.js +3505 -0
- package/dist/commands/tags.d.ts +3 -0
- package/dist/commands/tags.js +147 -0
- package/dist/commands/tools.d.ts +3 -0
- package/dist/commands/tools.js +246 -0
- package/dist/commands/update.d.ts +3 -0
- package/dist/commands/update.js +106 -0
- package/dist/commands/verify.d.ts +3 -0
- package/dist/commands/verify.js +27 -0
- package/dist/commands/viewers.d.ts +3 -0
- package/dist/commands/viewers.js +72 -0
- package/dist/commands/voice.d.ts +3 -0
- package/dist/commands/voice.js +351 -0
- package/dist/commands/whatsapp.d.ts +3 -0
- package/dist/commands/whatsapp.js +226 -0
- package/dist/commands/whoami.d.ts +3 -0
- package/dist/commands/whoami.js +110 -0
- package/dist/commands/worker.d.ts +3 -0
- package/dist/commands/worker.js +60 -0
- package/dist/commands/workflows.d.ts +3 -0
- package/dist/commands/workflows.js +1752 -0
- package/dist/commands/workspace.d.ts +3 -0
- package/dist/commands/workspace.js +94 -0
- package/dist/compact-list-flags.d.ts +7 -0
- package/dist/compact-list-flags.js +12 -0
- package/dist/crm-support-flags.js +14 -5
- package/dist/customer-operation-commands.js +8 -6
- package/dist/diagnostic-uploader.d.ts +3 -1
- package/dist/diagnostic-uploader.js +29 -8
- package/dist/help.d.ts +5 -0
- package/dist/help.js +22 -0
- package/dist/index.d.ts +98 -1
- package/dist/index.js +331 -30719
- package/dist/local-module-loader.d.ts +4 -0
- package/dist/local-module-loader.js +156 -0
- package/dist/visual-commands.js +9 -5
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +2 -2
- package/node_modules/@oxygen/shared/dist/agent-session-schedule.d.ts +36 -0
- package/node_modules/@oxygen/shared/dist/agent-session-schedule.js +69 -0
- package/node_modules/@oxygen/shared/dist/agent-sessions.d.ts +14 -5
- package/node_modules/@oxygen/shared/dist/agent-sessions.js +14 -3
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +175 -14
- package/node_modules/@oxygen/shared/dist/compact-list-result.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/compact-list-result.js +27 -0
- package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.d.ts +12 -1
- package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.js +5 -2
- package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +4 -2
- package/node_modules/@oxygen/shared/dist/contracts/compact-list-reads.d.ts +57 -0
- package/node_modules/@oxygen/shared/dist/contracts/compact-list-reads.js +23 -0
- package/node_modules/@oxygen/shared/dist/contracts/crm-support.d.ts +52 -0
- package/node_modules/@oxygen/shared/dist/contracts/crm-support.js +43 -2
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +20 -16
- package/node_modules/@oxygen/shared/dist/contracts/knowledge-content-reads.js +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/knowledge-proposal-reads.d.ts +49 -0
- package/node_modules/@oxygen/shared/dist/contracts/knowledge-proposal-reads.js +14 -0
- package/node_modules/@oxygen/shared/dist/contracts/knowledge-sync.js +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/limits-read.js +60 -7
- package/node_modules/@oxygen/shared/dist/contracts/operations.d.ts +9 -1
- package/node_modules/@oxygen/shared/dist/contracts/operations.js +13 -5
- package/node_modules/@oxygen/shared/dist/contracts/publishing-reads.js +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/teammates.d.ts +16 -0
- package/node_modules/@oxygen/shared/dist/contracts/teammates.js +115 -0
- package/node_modules/@oxygen/shared/dist/cost-estimate-view.js +21 -3
- package/node_modules/@oxygen/shared/dist/cost-estimate.d.ts +31 -1
- package/node_modules/@oxygen/shared/dist/cost-estimate.js +56 -15
- package/node_modules/@oxygen/shared/dist/diagnostic-credentials.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/diagnostic-credentials.js +2 -0
- package/node_modules/@oxygen/shared/dist/diagnostic-lock.js +71 -44
- package/node_modules/@oxygen/shared/dist/diagnostic-spool.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/diagnostic-spool.js +348 -0
- package/node_modules/@oxygen/shared/dist/diagnostic-storage-failure.d.ts +10 -0
- package/node_modules/@oxygen/shared/dist/diagnostic-storage-failure.js +42 -0
- package/node_modules/@oxygen/shared/dist/diagnostic-storage.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/diagnostic-storage.js +59 -213
- package/node_modules/@oxygen/shared/dist/diagnostics.js +3 -2
- package/node_modules/@oxygen/shared/dist/email-dsn.d.ts +16 -5
- package/node_modules/@oxygen/shared/dist/email-dsn.js +97 -1
- package/node_modules/@oxygen/shared/dist/emailguard-placement-probe.js +5 -0
- package/node_modules/@oxygen/shared/dist/enrichment-intents.js +4 -5
- package/node_modules/@oxygen/shared/dist/file-import.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/file-import.js +21 -2
- package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +30 -0
- package/node_modules/@oxygen/shared/dist/hosted-ai.js +20 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +6 -2
- package/node_modules/@oxygen/shared/dist/knowledge-seed-content.d.ts +17 -7
- package/node_modules/@oxygen/shared/dist/knowledge-seed-content.js +81 -17
- package/node_modules/@oxygen/shared/dist/langfuse.js +8 -4
- package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/mailbox-import.d.ts +6 -3
- package/node_modules/@oxygen/shared/dist/mailbox-import.js +9 -9
- package/node_modules/@oxygen/shared/dist/mcp-server-info.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/mcp-server-info.js +10 -0
- package/node_modules/@oxygen/shared/dist/object-storage.d.ts +10 -0
- package/node_modules/@oxygen/shared/dist/object-storage.js +14 -0
- package/node_modules/@oxygen/shared/dist/plan-limits-view.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/plan-limits-view.js +17 -1
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +1 -1
- package/node_modules/@oxygen/shared/dist/provider-selection.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/provider-selection.js +3 -0
- package/node_modules/@oxygen/shared/dist/repricing.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/repricing.js +2 -2
- package/node_modules/@oxygen/shared/dist/schedule-label.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/schedule-label.js +12 -0
- package/node_modules/@oxygen/shared/dist/sequence-failures.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/sequence-failures.js +20 -6
- package/node_modules/@oxygen/shared/dist/teammate-groups.d.ts +55 -0
- package/node_modules/@oxygen/shared/dist/teammate-groups.js +74 -0
- package/node_modules/@oxygen/shared/dist/teammate-messages.d.ts +52 -0
- package/node_modules/@oxygen/shared/dist/teammate-messages.js +88 -0
- package/node_modules/@oxygen/shared/dist/teammate-notes.d.ts +58 -0
- package/node_modules/@oxygen/shared/dist/teammate-notes.js +73 -0
- package/node_modules/@oxygen/shared/dist/teammate-routines.d.ts +95 -0
- package/node_modules/@oxygen/shared/dist/teammate-routines.js +266 -0
- package/node_modules/@oxygen/shared/dist/teammate-tasks.d.ts +56 -0
- package/node_modules/@oxygen/shared/dist/teammate-tasks.js +114 -0
- package/node_modules/@oxygen/shared/dist/teammates.d.ts +78 -0
- package/node_modules/@oxygen/shared/dist/teammates.js +140 -0
- package/node_modules/@oxygen/shared/dist/user-capability-routing.js +120 -0
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
- package/node_modules/@oxygen/shared/dist/visual-render.d.ts +9 -1
- package/node_modules/@oxygen/shared/dist/visual-render.js +12 -5
- package/node_modules/@oxygen/shared/dist/warmup-metrics.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/warmup-metrics.js +44 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
|
|
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
|
|
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 ${
|
|
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,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;
|
package/dist/command-manifest.js
CHANGED
|
@@ -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
|
-
//
|
|
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) {
|