@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
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readCsvOption } from "../command-support.js";
|
|
2
|
+
import { requestOxygen } from "../http-client.js";
|
|
3
|
+
import { readOption } from "../util.js";
|
|
4
|
+
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
export function registerActionColumnFamily(program, handleAsyncAction) {
|
|
7
|
+
program
|
|
8
|
+
.command("action-column")
|
|
9
|
+
.description("Workspace table action-column setup commands.")
|
|
10
|
+
.addCommand(new Command("add-provider")
|
|
11
|
+
.description("Add a catalog-backed provider action picker column to a workspace table.")
|
|
12
|
+
.argument("[table]", "Table id or slug.")
|
|
13
|
+
.requiredOption("--provider <provider>", "Provider id, such as attio.")
|
|
14
|
+
.option("--table <table>", "Table id or slug.")
|
|
15
|
+
.option("--label <label>", "Display label for the new column.")
|
|
16
|
+
.option("--key <key>", "Optional stable column key. Defaults to <provider>_actions.")
|
|
17
|
+
.option("--operation-filter <csv>", "Optional comma-separated operation, tool id, category, or tag filter.")
|
|
18
|
+
.option("--dry-run", "Preview the provider action column without creating it.")
|
|
19
|
+
.option("--json", "Print a JSON envelope.")
|
|
20
|
+
.action(async (table, options) => {
|
|
21
|
+
await handleAsyncAction("action-column add-provider", options, () => {
|
|
22
|
+
const tableId = readOption(table) ?? readOption(options.table);
|
|
23
|
+
if (!tableId) {
|
|
24
|
+
throw new OxygenError("invalid_request", "Pass a table argument or --table <table>.", {
|
|
25
|
+
exitCode: 1,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return requestOxygen("/api/cli/action-columns/provider", {
|
|
29
|
+
method: "POST",
|
|
30
|
+
body: {
|
|
31
|
+
table: tableId,
|
|
32
|
+
provider: options.provider,
|
|
33
|
+
...(readOption(options.label) ? { label: readOption(options.label) } : {}),
|
|
34
|
+
...(readOption(options.key) ? { key: readOption(options.key) } : {}),
|
|
35
|
+
...(readOption(options.operationFilter)
|
|
36
|
+
? { operation_filter: readCsvOption(options.operationFilter) }
|
|
37
|
+
: {}),
|
|
38
|
+
...(options.dryRun ? { dry_run: true } : {}),
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { requestOxygen } from "../http-client.js";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
export function registerActivationFamily(program, handleAsyncAction) {
|
|
4
|
+
const ACTIVATION_DESCRIPTION = "Where this workspace stands in the prescribed activation play (inbound-led outbound) and the ONE thing to do next: each step's state, what blocks a blocked one, the exact CLI / MCP tool / API call for the next step, and an honest pacing note — LinkedIn is read on a metered drip and a sender's warm-up ramp, not the size of your list, sets how fast anyone is contacted. Read-only, 0 credits. Read the play itself with `oxygen recipes list --stage day-1 --json`.";
|
|
5
|
+
// Bare `oxygen activation` is the spelling a founder guesses; `activation state`
|
|
6
|
+
// is the exact name discovery routes to (it is a gatewayCommand of the
|
|
7
|
+
// onboarding-and-copilot capability route). Both call the one read.
|
|
8
|
+
const activationCommand = program
|
|
9
|
+
.command("activation", { hidden: true })
|
|
10
|
+
// Backward-compatible exact invocation only. The fixed play is no longer a
|
|
11
|
+
// first-touch CLI surface; users choose motions in Recipes or Blueprints.
|
|
12
|
+
.description(ACTIVATION_DESCRIPTION)
|
|
13
|
+
.option("--json", "Print a JSON envelope.")
|
|
14
|
+
.action(async (options) => {
|
|
15
|
+
await handleAsyncAction("activation state", options, () => requestOxygen("/api/cli/activation/state"));
|
|
16
|
+
});
|
|
17
|
+
activationCommand.addCommand(new Command("state")
|
|
18
|
+
.description(ACTIVATION_DESCRIPTION)
|
|
19
|
+
.option("--json", "Print a JSON envelope.")
|
|
20
|
+
.action(async (options) => {
|
|
21
|
+
const outputOptions = { ...options, json: options.json || Boolean(activationCommand.opts().json) };
|
|
22
|
+
await handleAsyncAction("activation state", outputOptions, () => requestOxygen("/api/cli/activation/state"));
|
|
23
|
+
}), { hidden: true });
|
|
24
|
+
}
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { renderPrimaryProviderBoard } from "../admin-primary-providers-render.js";
|
|
2
|
+
import { emitCliFailure, emitSuccess } from "../cli-output.js";
|
|
3
|
+
import { readPositiveInt } from "../cli-values.js";
|
|
4
|
+
import { buildDirectoryProfileBody, readCsvOption, readPositiveInteger } from "../command-support.js";
|
|
5
|
+
import { requestOxygen } from "../http-client.js";
|
|
6
|
+
import { readOption } from "../util.js";
|
|
7
|
+
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
8
|
+
import { AGENCY_DIRECTORY_REGIONS, AGENCY_DIRECTORY_SERVICES } from "@oxygen/shared/directory";
|
|
9
|
+
import { Command, Option } from "commander";
|
|
10
|
+
/** A --value for the pricing book: a number, true/false for a charge's active flag, or "none" for no plan limit. */
|
|
11
|
+
function readPriceBookValue(raw) {
|
|
12
|
+
if (raw === undefined)
|
|
13
|
+
return undefined;
|
|
14
|
+
const value = raw.trim();
|
|
15
|
+
if (value === "true")
|
|
16
|
+
return true;
|
|
17
|
+
if (value === "false")
|
|
18
|
+
return false;
|
|
19
|
+
if (value === "none" || value === "null")
|
|
20
|
+
return null;
|
|
21
|
+
const number = Number(value);
|
|
22
|
+
return value !== "" && Number.isFinite(number) ? number : value;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A signed, finite number (`--expected-divergence`). Unlike its positive and
|
|
26
|
+
* non-negative siblings the sign is the payload here — a negative divergence is
|
|
27
|
+
* the meter counter under-reading its own ledger — so only a non-finite value is
|
|
28
|
+
* rejected. Parsed client-side so a typo fails before a request that would
|
|
29
|
+
* otherwise reach a billing ledger is sent at all.
|
|
30
|
+
*/
|
|
31
|
+
function readSignedNumber(value) {
|
|
32
|
+
const trimmed = value?.trim();
|
|
33
|
+
if (!trimmed)
|
|
34
|
+
return undefined;
|
|
35
|
+
const parsed = Number(trimmed);
|
|
36
|
+
if (!Number.isFinite(parsed)) {
|
|
37
|
+
throw new OxygenError("invalid_number", "Expected a finite number.", {
|
|
38
|
+
details: { value },
|
|
39
|
+
exitCode: 1,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return parsed;
|
|
43
|
+
}
|
|
44
|
+
export function registerAdminFamily(program, handleAsyncAction, binaryName) {
|
|
45
|
+
program
|
|
46
|
+
.command("admin")
|
|
47
|
+
.description("Staff-only commands.")
|
|
48
|
+
.addCommand(new Command("kpis")
|
|
49
|
+
.description("Show the company conversion funnel (visitors → signups → activation on any surface, web, CLI or MCP → paid, with step rates and agent connected beside it), self-reported signup-channel attribution, new and positive-paid MRR/ARR, and paid churn separated from trial loss. OXYGEN staff only; workspace admin role alone does not grant access.")
|
|
50
|
+
.addOption(new Option("--range <range>", "Range for acquisition, the funnel's signup cohort, new revenue, and churn; current MRR/ARR and scheduled cancellations remain point-in-time.")
|
|
51
|
+
.choices(["7d", "30d", "90d"])
|
|
52
|
+
.default("30d"))
|
|
53
|
+
.option("--json", "Print a JSON envelope.")
|
|
54
|
+
.action(async (options) => {
|
|
55
|
+
await handleAsyncAction("admin kpis", options, () => {
|
|
56
|
+
const range = readOption(options.range);
|
|
57
|
+
const suffix = range && range !== "30d" ? `?range=${encodeURIComponent(range)}` : "";
|
|
58
|
+
return requestOxygen(`/api/cli/admin/kpis${suffix}`);
|
|
59
|
+
});
|
|
60
|
+
}))
|
|
61
|
+
.addCommand(new Command("egress")
|
|
62
|
+
.description("Read-only Fly email-egress overview: shared IP, dedicated workspace apps and IPs, liveness, image drift, billing, and stuck orders. OXYGEN staff only.")
|
|
63
|
+
.option("--json", "Print a JSON envelope.")
|
|
64
|
+
.action(async (options) => {
|
|
65
|
+
await handleAsyncAction("admin egress", options, () => requestOxygen("/api/cli/admin/egress"));
|
|
66
|
+
}))
|
|
67
|
+
.addCommand(new Command("costs")
|
|
68
|
+
.description("Show provider costs (COGS) per workspace. Staff only.")
|
|
69
|
+
.option("--top <n>", "Limit number of workspace columns. Defaults to all.")
|
|
70
|
+
.option("--credential-mode <mode>", "managed (default; real COGS), byok (customer-supplied credentials), or all.", "managed")
|
|
71
|
+
.option("--json", "Print a JSON envelope.")
|
|
72
|
+
.action(async (options) => {
|
|
73
|
+
await handleAsyncAction("admin costs", options, () => {
|
|
74
|
+
const params = new URLSearchParams();
|
|
75
|
+
const top = readPositiveInt(options.top);
|
|
76
|
+
if (top)
|
|
77
|
+
params.set("top", String(top));
|
|
78
|
+
const credentialMode = readOption(options.credentialMode);
|
|
79
|
+
if (credentialMode && credentialMode !== "managed") {
|
|
80
|
+
params.set("credential_mode", credentialMode);
|
|
81
|
+
}
|
|
82
|
+
const suffix = params.toString() ? `?${params.toString()}` : "";
|
|
83
|
+
return requestOxygen(`/api/cli/admin/costs${suffix}`);
|
|
84
|
+
});
|
|
85
|
+
}))
|
|
86
|
+
.addCommand(new Command("price-book")
|
|
87
|
+
.description("The runtime prices charging reads from the control DB: recurring charges, plan allowances and scalars, each with its committed default and whether staff may edit it. An edit reaches every instance within 60 seconds. Staff only.")
|
|
88
|
+
.addCommand(new Command("list")
|
|
89
|
+
.description("List every pricing-book row with its stored value, what charging bills now, the committed default, and who reads it. Read-only.")
|
|
90
|
+
.option("--json", "Print a JSON envelope.")
|
|
91
|
+
.action(async (options) => {
|
|
92
|
+
await handleAsyncAction("admin price-book list", options, () => requestOxygen("/api/cli/admin/costs?section=pricing_book"));
|
|
93
|
+
}))
|
|
94
|
+
.addCommand(new Command("set")
|
|
95
|
+
.description("Set one editable pricing-book field. A charge takes --field credits or active; a plan takes monthly_credits, weekly_credits_limit or rollover_cap (value none for no limit); a scalar takes value. Rows charging does not read from the book are refused.")
|
|
96
|
+
.requiredOption("--table <table>", "charge, plan, or scalar.")
|
|
97
|
+
.requiredOption("--key <key>", "The row key, e.g. addon.egress_dedicated_ip, oxygen_499, markup.copilot_inference.")
|
|
98
|
+
.requiredOption("--field <field>", "credits | active (charge); monthly_credits | weekly_credits_limit | rollover_cap (plan); value (scalar).")
|
|
99
|
+
.requiredOption("--value <value>", "A non-negative number, true/false for active, or none for no plan limit.")
|
|
100
|
+
.option("--json", "Print a JSON envelope.")
|
|
101
|
+
.action(async (options) => {
|
|
102
|
+
await handleAsyncAction("admin price-book set", options, () => requestOxygen("/api/cli/admin/costs", {
|
|
103
|
+
method: "POST",
|
|
104
|
+
body: {
|
|
105
|
+
price_table: options.table,
|
|
106
|
+
price_key: options.key,
|
|
107
|
+
price_field: options.field,
|
|
108
|
+
value: readPriceBookValue(options.value),
|
|
109
|
+
},
|
|
110
|
+
}));
|
|
111
|
+
}))
|
|
112
|
+
.addCommand(new Command("restore")
|
|
113
|
+
.description("Put a pricing-book row back to its committed default. For a plan, --field restores one allowance field; without it all three are restored.")
|
|
114
|
+
.requiredOption("--table <table>", "charge, plan, or scalar.")
|
|
115
|
+
.requiredOption("--key <key>", "The row key.")
|
|
116
|
+
.option("--field <field>", "Plan only: monthly_credits, weekly_credits_limit or rollover_cap.")
|
|
117
|
+
.option("--json", "Print a JSON envelope.")
|
|
118
|
+
.action(async (options) => {
|
|
119
|
+
await handleAsyncAction("admin price-book restore", options, () => requestOxygen("/api/cli/admin/costs", {
|
|
120
|
+
method: "POST",
|
|
121
|
+
body: {
|
|
122
|
+
price_table: options.table,
|
|
123
|
+
price_key: options.key,
|
|
124
|
+
...(options.field ? { price_field: options.field } : {}),
|
|
125
|
+
restore: true,
|
|
126
|
+
},
|
|
127
|
+
}));
|
|
128
|
+
})))
|
|
129
|
+
.addCommand(new Command("primary-providers")
|
|
130
|
+
.description("Provider board: primary managed data providers, inference, fallbacks, conditional companions and product services with default/conditional/legacy selection and required configuration. Per provider: health, 7d traffic, stored balance and wallet runway (not subscription quota), 30d/7d COGS, rate policy, spend ceilings, breaker, posture and snapshot freshness. Configured credentials prove neither health nor plan access. Reads stored snapshots without provider calls; --refresh re-runs zero-credit snapshots first. Staff only.")
|
|
131
|
+
.option("--refresh", "Re-run the zero-credit snapshot work the three crons run (provider health probes, managed balances, cost snapshot) before reading the board. No paid provider call and no credit spend; takes up to a few minutes.")
|
|
132
|
+
.option("--stages <csv>", "Limit --refresh to these stages: health, balances, costs. Defaults to all three.")
|
|
133
|
+
.option("--force", "Refresh even inside the server's cooldown on re-probing every managed vendor. Only with --refresh.")
|
|
134
|
+
.option("--json", "Print a JSON envelope.")
|
|
135
|
+
.action(async (options) => {
|
|
136
|
+
const stages = readCsvOption(options.stages);
|
|
137
|
+
if (stages.length > 0 && !options.refresh) {
|
|
138
|
+
// Fail with the envelope rather than reading a board the caller
|
|
139
|
+
// believes it just refreshed.
|
|
140
|
+
emitCliFailure("admin primary-providers", new OxygenError("invalid_request", "--stages only applies to --refresh. Add --refresh to re-run those snapshots.", { exitCode: 2 }));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (options.force && !options.refresh) {
|
|
144
|
+
// Same reason as --stages: --force only relaxes the refresh
|
|
145
|
+
// cooldown, so on its own it reads the very board the caller
|
|
146
|
+
// believes it just forced a re-probe of.
|
|
147
|
+
emitCliFailure("admin primary-providers", new OxygenError("invalid_request", "--force only applies to --refresh. Add --refresh to re-run those snapshots.", { exitCode: 2 }));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
let failed = false;
|
|
151
|
+
const board = await requestOxygen("/api/cli/admin/primary-providers", options.refresh
|
|
152
|
+
? {
|
|
153
|
+
method: "POST",
|
|
154
|
+
// The route budgets maxDuration = 300 and the balance stage
|
|
155
|
+
// walks every fetcher sequentially at 15s each, so the
|
|
156
|
+
// default 120s client budget aborts a refresh the server
|
|
157
|
+
// finishes — the operator reads a failure for work that
|
|
158
|
+
// succeeded and re-runs the outbound probes.
|
|
159
|
+
timeoutMs: 300_000,
|
|
160
|
+
body: {
|
|
161
|
+
refresh: true,
|
|
162
|
+
...(stages.length > 0 ? { stages } : {}),
|
|
163
|
+
...(options.force ? { force: true } : {}),
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
: undefined).catch((error) => {
|
|
167
|
+
emitCliFailure("admin primary-providers", error);
|
|
168
|
+
failed = true;
|
|
169
|
+
return null;
|
|
170
|
+
});
|
|
171
|
+
// Only a transport failure ends the command silently; an empty
|
|
172
|
+
// payload still gets rendered (or enveloped) rather than exiting 0
|
|
173
|
+
// with nothing printed.
|
|
174
|
+
if (failed)
|
|
175
|
+
return;
|
|
176
|
+
if (options.json) {
|
|
177
|
+
emitSuccess("admin primary-providers", board, options);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
// Without this the default invocation printed ~1,500 lines of raw
|
|
181
|
+
// JSON while the MCP tool printed a worst-first summary of the same
|
|
182
|
+
// payload — same board, two different products.
|
|
183
|
+
process.stdout.write(`${renderPrimaryProviderBoard(board, { binary: binaryName })}\n`);
|
|
184
|
+
}))
|
|
185
|
+
.addCommand(new Command("spend")
|
|
186
|
+
.description("Global managed-provider spend limiter: burn, ceilings, breakers. Staff only.")
|
|
187
|
+
.option("--json", "Print a JSON envelope.")
|
|
188
|
+
.action(async (options) => {
|
|
189
|
+
await handleAsyncAction("admin spend", options, () => requestOxygen("/api/cli/admin/spend"));
|
|
190
|
+
})
|
|
191
|
+
.addCommand(new Command("set-limit")
|
|
192
|
+
.description("Set a ceiling for one scope + window. action=warn meters only; block arms it.")
|
|
193
|
+
.requiredOption("--scope <kind>", "global, category, provider, organization, or signature.")
|
|
194
|
+
.option("--key <key>", "Scope key: category/provider/org id, or * for the default.", "*")
|
|
195
|
+
.requiredOption("--window <seconds>", "Window: 60, 3600, or 86400.")
|
|
196
|
+
.option("--limit-usd <usd>", "Ceiling in USD of OXYGEN's own provider cost.")
|
|
197
|
+
.option("--limit-calls <n>", "Ceiling in call count: the signature loop detector, or the only ceiling that bounds a $0-priced provider.")
|
|
198
|
+
.option("--action <action>", "warn (meter + alert only) or block (enforce).", "warn")
|
|
199
|
+
.option("--notes <text>", "Why this ceiling is set where it is.")
|
|
200
|
+
.option("--json", "Print a JSON envelope.")
|
|
201
|
+
.action(async (options) => {
|
|
202
|
+
await handleAsyncAction("admin spend set-limit", options, () => requestOxygen("/api/cli/admin/spend/limits", {
|
|
203
|
+
method: "POST",
|
|
204
|
+
body: {
|
|
205
|
+
scope_kind: readOption(options.scope),
|
|
206
|
+
scope_key: readOption(options.key) ?? "*",
|
|
207
|
+
window_seconds: Number(readOption(options.window)),
|
|
208
|
+
...(readOption(options.limitUsd) ? { limit_usd: Number(readOption(options.limitUsd)) } : {}),
|
|
209
|
+
...(readOption(options.limitCalls) ? { limit_calls: Number(readOption(options.limitCalls)) } : {}),
|
|
210
|
+
action: readOption(options.action) ?? "warn",
|
|
211
|
+
...(readOption(options.notes) ? { notes: readOption(options.notes) } : {}),
|
|
212
|
+
},
|
|
213
|
+
}));
|
|
214
|
+
}))
|
|
215
|
+
.addCommand(new Command("halt")
|
|
216
|
+
.description("Force a breaker OPEN — the platform kill switch for managed provider spend. --scope managed_credit --key <provider> benches Oxygen's managed account for one provider (the same bench a vendor 402 opens).")
|
|
217
|
+
.option("--scope <kind>", "Scope kind to halt: global, category, provider, organization, signature, or managed_credit (key = provider id). Defaults to global.", "global")
|
|
218
|
+
.option("--key <key>", "Scope key. Defaults to * (everything in that scope). For --scope managed_credit, the provider id (serper, bounceban, ...).", "*")
|
|
219
|
+
.option("--reason <text>", "Why the halt was applied.")
|
|
220
|
+
.option("--json", "Print a JSON envelope.")
|
|
221
|
+
.action(async (options) => {
|
|
222
|
+
await handleAsyncAction("admin spend halt", options, () => requestOxygen("/api/cli/admin/spend/breakers", {
|
|
223
|
+
method: "POST",
|
|
224
|
+
body: {
|
|
225
|
+
action: "force_open",
|
|
226
|
+
scope_kind: readOption(options.scope) ?? "global",
|
|
227
|
+
scope_key: readOption(options.key) ?? "*",
|
|
228
|
+
...(readOption(options.reason) ? { reason: readOption(options.reason) } : {}),
|
|
229
|
+
},
|
|
230
|
+
}));
|
|
231
|
+
}))
|
|
232
|
+
.addCommand(new Command("resume")
|
|
233
|
+
.description("Close a breaker and clear its backoff, resuming managed provider spend. After funding a managed provider account, --scope managed_credit --key <provider> closes its credit bench fleet-wide instead of waiting out the window.")
|
|
234
|
+
.option("--scope <kind>", "Scope kind to resume: global, category, provider, organization, signature, or managed_credit (key = provider id). Defaults to global.", "global")
|
|
235
|
+
.option("--key <key>", "Scope key. Defaults to *. For --scope managed_credit, the provider id (serper, bounceban, ...).", "*")
|
|
236
|
+
.option("--reason <text>", "Why the breaker was reset.")
|
|
237
|
+
.option("--json", "Print a JSON envelope.")
|
|
238
|
+
.action(async (options) => {
|
|
239
|
+
await handleAsyncAction("admin spend resume", options, () => requestOxygen("/api/cli/admin/spend/breakers", {
|
|
240
|
+
method: "POST",
|
|
241
|
+
body: {
|
|
242
|
+
action: "reset",
|
|
243
|
+
scope_kind: readOption(options.scope) ?? "global",
|
|
244
|
+
scope_key: readOption(options.key) ?? "*",
|
|
245
|
+
...(readOption(options.reason) ? { reason: readOption(options.reason) } : {}),
|
|
246
|
+
},
|
|
247
|
+
}));
|
|
248
|
+
})))
|
|
249
|
+
.addCommand(new Command("directory")
|
|
250
|
+
.description("Curate the public agency directory (invite, prefill, delist). Staff only.")
|
|
251
|
+
.addCommand(new Command("list")
|
|
252
|
+
.description("List every agency directory listing across all organizations.")
|
|
253
|
+
.option("--json", "Print a JSON envelope.")
|
|
254
|
+
.action(async (options) => {
|
|
255
|
+
await handleAsyncAction("admin directory list", options, () => requestOxygen("/api/cli/admin/directory"));
|
|
256
|
+
}))
|
|
257
|
+
.addCommand(new Command("enable")
|
|
258
|
+
.description("Invite an organization to the directory, optionally prefilling its profile.")
|
|
259
|
+
.argument("<organization>", "Organization id, slug, or Clerk org id.")
|
|
260
|
+
.option("--name <text>", "Agency display name (defaults to the organization name).")
|
|
261
|
+
.option("--slug <slug>", "Public URL slug (defaults to a slugified name).")
|
|
262
|
+
.option("--logo-url <url>", "Logo image URL.")
|
|
263
|
+
.option("--tagline <text>", "One-line pitch shown on the directory card.")
|
|
264
|
+
.option("--description <text>", "Full profile description (plain text; blank lines separate paragraphs).")
|
|
265
|
+
.option("--description-file <path>", "Path to a plain-text file with the description.")
|
|
266
|
+
.option("--website <url>", "Company website URL.")
|
|
267
|
+
.option("--company-linkedin <url>", "Company LinkedIn page URL.")
|
|
268
|
+
.option("--founders-json <json>", "JSON array of founding-team members: [{\"name\", \"title\", \"linkedin_url\"}].")
|
|
269
|
+
.option("--booking-url <url>", "Book-a-call URL (Cal.com, Calendly, ...).")
|
|
270
|
+
.option("--contact-email <email>", "Public contact email.")
|
|
271
|
+
.option("--services <csv>", `Comma-separated services from the curated set: ${AGENCY_DIRECTORY_SERVICES.join(", ")}.`)
|
|
272
|
+
.option("--regions <csv>", `Comma-separated regions from the curated set: ${AGENCY_DIRECTORY_REGIONS.join(", ")}.`)
|
|
273
|
+
.option("--featured", "Pin the listing to the top of the public directory.")
|
|
274
|
+
.option("--profile-json <json>", "JSON object with any listing fields; flags override it.")
|
|
275
|
+
.option("--json", "Print a JSON envelope.")
|
|
276
|
+
.action(async (organization, options) => {
|
|
277
|
+
await handleAsyncAction("admin directory enable", options, async () => {
|
|
278
|
+
const profile = await buildDirectoryProfileBody(options);
|
|
279
|
+
if (options.featured)
|
|
280
|
+
profile.featured = true;
|
|
281
|
+
return requestOxygen("/api/cli/admin/directory/enable", {
|
|
282
|
+
method: "POST",
|
|
283
|
+
body: { organization, profile },
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
}))
|
|
287
|
+
.addCommand(new Command("delist")
|
|
288
|
+
.description("Remove an organization's listing from the public directory (kill switch).")
|
|
289
|
+
.argument("<organization>", "Organization id, slug, or Clerk org id.")
|
|
290
|
+
.option("--json", "Print a JSON envelope.")
|
|
291
|
+
.action(async (organization, options) => {
|
|
292
|
+
await handleAsyncAction("admin directory delist", options, () => requestOxygen("/api/cli/admin/directory/delist", {
|
|
293
|
+
method: "POST",
|
|
294
|
+
body: { organization },
|
|
295
|
+
}));
|
|
296
|
+
})))
|
|
297
|
+
.addCommand(new Command("automation-usage")
|
|
298
|
+
.description("Automation-actions meter integrity: the windows whose counter disagrees with their event ledger, and the audited adjustment that closes one. Staff only.")
|
|
299
|
+
.addCommand(new Command("divergences")
|
|
300
|
+
.description("List every automation-usage window whose counter and event ledger disagree, including windows that have outlived the detector's lookback and can only be cleared by a repair. Writes no state — but the scan IS the detector, so each call re-emits its aged-out divergence log event once per window that has outlived the lookback (tagged surface=cli, so operator reads stay separable from the 2-hourly cron's own emissions); the envelope reports what it emitted. Bounds default to the cron's; narrow them if the scan approaches its budget on a large estate.")
|
|
301
|
+
.option("--lookback-days <days>", "Narrow the recent-window scan to this many days back. Defaults to the detector's own lookback. The aged-out scan has no time floor either way, so no divergence can hide behind a shorter lookback.")
|
|
302
|
+
.option("--limit <windows>", "Cap the recent-window scan. Defaults to the detector's own cap. Truncation is reported as `truncated`, never silent.")
|
|
303
|
+
.option("--aged-out-limit <windows>", "Cap the aged-out scan, which is ordered oldest-first so a cap only ever drops the newest rows. Reported as `aged_out_truncated`.")
|
|
304
|
+
.option("--json", "Print a JSON envelope.")
|
|
305
|
+
.action(async (options) => {
|
|
306
|
+
await handleAsyncAction("admin automation-usage divergences", options, () => {
|
|
307
|
+
const params = new URLSearchParams();
|
|
308
|
+
const lookbackDays = readPositiveInteger(options.lookbackDays);
|
|
309
|
+
const limit = readPositiveInteger(options.limit);
|
|
310
|
+
const agedOutLimit = readPositiveInteger(options.agedOutLimit);
|
|
311
|
+
if (lookbackDays !== undefined)
|
|
312
|
+
params.set("lookback_days", String(lookbackDays));
|
|
313
|
+
if (limit !== undefined)
|
|
314
|
+
params.set("limit", String(limit));
|
|
315
|
+
if (agedOutLimit !== undefined)
|
|
316
|
+
params.set("aged_out_limit", String(agedOutLimit));
|
|
317
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
318
|
+
return requestOxygen(`/api/cli/admin/automation-usage${qs}`);
|
|
319
|
+
});
|
|
320
|
+
}))
|
|
321
|
+
.addCommand(new Command("reconcile")
|
|
322
|
+
.description("Write the one audited adjustment that closes a window's counter/ledger divergence. Deliberate by construction: it needs an actor (your staff identity), a reason, and the divergence you observed — the repair refuses if the live numbers have moved since, derives the delta itself, and replays a retry into the same single ledger row. The raise-only counter is never touched; the ledger is brought to meet it, which for a counter reading BELOW its ledger means appending a negative adjustment that writes those actions off the meter of record. The ledger is append-only: the entry cannot be removed afterwards, only offset by a further adjustment. Run `admin automation-usage divergences` first — that listing's `divergence` is the exact quantity this will append.")
|
|
323
|
+
.requiredOption("--organization <id>", "Organization id the divergence was reported for.")
|
|
324
|
+
.requiredOption("--window-start <iso>", "Exact period start of the divergent window, e.g. 2026-07-01T00:00:00Z. A rounded value matches no window rather than repairing a neighbouring one.")
|
|
325
|
+
.requiredOption("--expected-divergence <actions>", "The divergence you are repairing (counter - ledger), signed, straight from `admin automation-usage divergences`. Required so a repair computed off a stale alert aborts instead of writing the wrong delta.")
|
|
326
|
+
.requiredOption("--reason <text>", "Why this correction is being made. Recorded on the ledger event — no adjustment enters the books anonymously.")
|
|
327
|
+
.option("--incident-ref <ref>", "Incident this repairs, e.g. OXY-4100.")
|
|
328
|
+
.option("--idempotency-key <key>", "Pin the replay key. Defaults to (billing owner, window, divergence), which already makes a retry safe.")
|
|
329
|
+
.option("--json", "Print a JSON envelope.")
|
|
330
|
+
.action(async (options) => {
|
|
331
|
+
await handleAsyncAction("admin automation-usage reconcile", options, () => {
|
|
332
|
+
const incidentRef = readOption(options.incidentRef);
|
|
333
|
+
const idempotencyKey = readOption(options.idempotencyKey);
|
|
334
|
+
return requestOxygen("/api/cli/admin/automation-usage", {
|
|
335
|
+
method: "POST",
|
|
336
|
+
body: {
|
|
337
|
+
organization: readOption(options.organization),
|
|
338
|
+
window_start: readOption(options.windowStart),
|
|
339
|
+
expected_divergence: readSignedNumber(options.expectedDivergence),
|
|
340
|
+
reason: readOption(options.reason),
|
|
341
|
+
...(incidentRef ? { incident_ref: incidentRef } : {}),
|
|
342
|
+
...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}),
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
})));
|
|
347
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { splitCommaList } from "../command-support.js";
|
|
2
|
+
import { requestOxygen } from "../http-client.js";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
export function registerAdsFamily(program, handleAsyncAction) {
|
|
5
|
+
program
|
|
6
|
+
.command("ads")
|
|
7
|
+
.description("Saved Ads intelligence searches. Saving or tagging a query costs nothing; run its platform and query through the matching adyntel.* tool with the normal preview and credit gate.")
|
|
8
|
+
.addCommand(new Command("searches")
|
|
9
|
+
.description("Save reusable Ads queries for 0 credits. Saving never runs a provider search. Example: oxygen ads searches save --name 'Competitor ads' --platform facebook_ads --company-domain acme.com --tags competitor-watch.")
|
|
10
|
+
.addCommand(new Command("list")
|
|
11
|
+
.description("List saved Ads search definitions, optionally filtered by tag.")
|
|
12
|
+
.option("--tag <label>", "Only saved searches carrying this tag.")
|
|
13
|
+
.option("--json", "Print a JSON envelope.")
|
|
14
|
+
.action(async (options) => {
|
|
15
|
+
await handleAsyncAction("ads searches list", options, () => requestOxygen(`/api/cli/ads/searches${options.tag ? `?tag=${encodeURIComponent(options.tag)}` : ""}`));
|
|
16
|
+
}))
|
|
17
|
+
.addCommand(new Command("get")
|
|
18
|
+
.description("Read one saved Ads search definition without running it.")
|
|
19
|
+
.argument("<id>")
|
|
20
|
+
.option("--json", "Print a JSON envelope.")
|
|
21
|
+
.action(async (id, options) => {
|
|
22
|
+
await handleAsyncAction("ads searches get", options, () => requestOxygen(`/api/cli/ads/searches/${encodeURIComponent(id)}`));
|
|
23
|
+
}))
|
|
24
|
+
.addCommand(new Command("save")
|
|
25
|
+
.alias("create")
|
|
26
|
+
.description("Save a named Ads search query for 0 credits; no provider call.")
|
|
27
|
+
.requiredOption("--name <name>", "Name for the saved search.")
|
|
28
|
+
.requiredOption("--platform <platform>", "Domain: facebook_ads, google_ads, linkedin_ads (use --company-domain). Keyword: facebook_ad_search, linkedin_keyword_search, tiktok_search (use --keyword).")
|
|
29
|
+
.option("--company-domain <domain>", "Required for facebook_ads, google_ads, and linkedin_ads; e.g. acme.com.")
|
|
30
|
+
.option("--keyword <keyword>", "Required for facebook_ad_search, linkedin_keyword_search, and tiktok_search.")
|
|
31
|
+
.option("--country-code <code>", "Optional two-letter country code, e.g. US.")
|
|
32
|
+
.option("--tags <labels>", "Comma-separated workspace tags.")
|
|
33
|
+
.option("--json", "Print a JSON envelope.")
|
|
34
|
+
.action(async (options) => {
|
|
35
|
+
await handleAsyncAction("ads searches save", options, () => requestOxygen("/api/cli/ads/searches", { method: "POST", body: {
|
|
36
|
+
name: options.name, platform: options.platform, company_domain: options.companyDomain,
|
|
37
|
+
keyword: options.keyword, country_code: options.countryCode, tags: splitCommaList(options.tags),
|
|
38
|
+
} }));
|
|
39
|
+
}))
|
|
40
|
+
.addCommand(new Command("update")
|
|
41
|
+
.description("Replace a saved search's name or query; keep its tags. No provider call.")
|
|
42
|
+
.argument("<id>")
|
|
43
|
+
.requiredOption("--name <name>", "Name for the saved search.")
|
|
44
|
+
.requiredOption("--platform <platform>", "Domain: facebook_ads, google_ads, linkedin_ads (use --company-domain). Keyword: facebook_ad_search, linkedin_keyword_search, tiktok_search (use --keyword).")
|
|
45
|
+
.option("--company-domain <domain>", "Required for facebook_ads, google_ads, and linkedin_ads; e.g. acme.com.")
|
|
46
|
+
.option("--keyword <keyword>", "Required for facebook_ad_search, linkedin_keyword_search, and tiktok_search.")
|
|
47
|
+
.option("--country-code <code>", "Optional two-letter country code, e.g. US.")
|
|
48
|
+
.option("--json", "Print a JSON envelope.")
|
|
49
|
+
.action(async (id, options) => {
|
|
50
|
+
await handleAsyncAction("ads searches update", options, () => requestOxygen(`/api/cli/ads/searches/${encodeURIComponent(id)}`, { method: "PATCH", body: {
|
|
51
|
+
name: options.name, platform: options.platform, company_domain: options.companyDomain,
|
|
52
|
+
keyword: options.keyword, country_code: options.countryCode,
|
|
53
|
+
} }));
|
|
54
|
+
}))
|
|
55
|
+
.addCommand(new Command("delete")
|
|
56
|
+
.description("Archive a saved search; hide it from lists and tag footprints.")
|
|
57
|
+
.argument("<id>")
|
|
58
|
+
.option("--json", "Print a JSON envelope.")
|
|
59
|
+
.action(async (id, options) => {
|
|
60
|
+
await handleAsyncAction("ads searches delete", options, () => requestOxygen(`/api/cli/ads/searches/${encodeURIComponent(id)}`, { method: "DELETE" }));
|
|
61
|
+
})));
|
|
62
|
+
}
|