@oxygen-agent/cli 1.1014.136 → 1.1014.618
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 +1669 -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 +3492 -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 +17 -14
- 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.js +5 -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,140 @@
|
|
|
1
|
+
import { parseJsonObject } from "../cli-values.js";
|
|
2
|
+
import { requestOxygen } from "../http-client.js";
|
|
3
|
+
import { isRecord, readOption } from "../util.js";
|
|
4
|
+
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
const CUSTOM_HTTP_MANIFEST_DOCS_URL = "https://oxygen-agent.com/docs/providers/custom-http";
|
|
9
|
+
function readCustomIntegrationManifest(options) {
|
|
10
|
+
const manifestPath = readOption(options.manifest);
|
|
11
|
+
const manifestJson = readOption(options.manifestJson);
|
|
12
|
+
if (manifestPath && manifestJson) {
|
|
13
|
+
throw new OxygenError("invalid_request", "Pass either --manifest or --manifest-json, not both.", {
|
|
14
|
+
exitCode: 1,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
if (!manifestPath && !manifestJson) {
|
|
18
|
+
throw new OxygenError("invalid_request", "Pass --manifest or --manifest-json.", {
|
|
19
|
+
exitCode: 1,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
if (manifestJson)
|
|
23
|
+
return parseJsonObject(manifestJson);
|
|
24
|
+
const filePath = resolve(manifestPath ?? "");
|
|
25
|
+
let content;
|
|
26
|
+
try {
|
|
27
|
+
content = readFileSync(filePath, "utf8");
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
const notFound = error?.code === "ENOENT";
|
|
31
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
32
|
+
throw new OxygenError("invalid_request", notFound
|
|
33
|
+
? `Manifest file not found: ${filePath}. Pass --manifest <path.json> or --manifest-json '<json>'. See ${CUSTOM_HTTP_MANIFEST_DOCS_URL}`
|
|
34
|
+
: `Could not read manifest file ${filePath}: ${reason}. See ${CUSTOM_HTTP_MANIFEST_DOCS_URL}`, { details: { path: filePath }, exitCode: 1 });
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
return parseJsonObject(content);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error instanceof OxygenError) {
|
|
41
|
+
throw new OxygenError(error.code, `Manifest file ${filePath} is invalid: ${error.message} See ${CUSTOM_HTTP_MANIFEST_DOCS_URL}`, {
|
|
42
|
+
details: { path: filePath, ...(isRecord(error.details) ? error.details : {}) },
|
|
43
|
+
exitCode: error.exitCode,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const CUSTOM_INTEGRATION_SECRET_KEYS = ["API_KEY", "TOKEN", "PASSWORD", "SECRET"];
|
|
50
|
+
function readCustomIntegrationSecret(options) {
|
|
51
|
+
const secretsFile = readOption(options.secretsFile);
|
|
52
|
+
if (!secretsFile) {
|
|
53
|
+
throw new OxygenError("invalid_request", "Pass --secrets-file.", {
|
|
54
|
+
exitCode: 1,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const entries = parseEnvFile(readFileSync(resolve(secretsFile), "utf8"));
|
|
58
|
+
const keys = Object.keys(entries);
|
|
59
|
+
if (keys.length === 0) {
|
|
60
|
+
throw new OxygenError("invalid_request", "Secrets file has no KEY=VALUE entries.", {
|
|
61
|
+
details: { file: secretsFile },
|
|
62
|
+
exitCode: 1,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (keys.length === 1)
|
|
66
|
+
return entries[keys[0]];
|
|
67
|
+
const preferred = CUSTOM_INTEGRATION_SECRET_KEYS.find((key) => entries[key] !== undefined);
|
|
68
|
+
if (preferred)
|
|
69
|
+
return entries[preferred];
|
|
70
|
+
throw new OxygenError("invalid_request", "Secrets file is ambiguous: use a single entry or name one API_KEY, TOKEN, PASSWORD, or SECRET.", {
|
|
71
|
+
details: { file: secretsFile, keys },
|
|
72
|
+
exitCode: 1,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function parseEnvFile(content) {
|
|
76
|
+
const entries = {};
|
|
77
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
78
|
+
const line = rawLine.trim();
|
|
79
|
+
if (!line || line.startsWith("#"))
|
|
80
|
+
continue;
|
|
81
|
+
const separator = line.indexOf("=");
|
|
82
|
+
if (separator <= 0)
|
|
83
|
+
continue;
|
|
84
|
+
const key = line.slice(0, separator).trim().replace(/^export\s+/, "");
|
|
85
|
+
let value = line.slice(separator + 1).trim();
|
|
86
|
+
if ((value.startsWith("\"") && value.endsWith("\"") && value.length >= 2)
|
|
87
|
+
|| (value.startsWith("'") && value.endsWith("'") && value.length >= 2)) {
|
|
88
|
+
value = value.slice(1, -1);
|
|
89
|
+
}
|
|
90
|
+
if (key && value)
|
|
91
|
+
entries[key] = value;
|
|
92
|
+
}
|
|
93
|
+
return entries;
|
|
94
|
+
}
|
|
95
|
+
export function registerCustomIntegrationsFamily(program, handleAsyncAction) {
|
|
96
|
+
program
|
|
97
|
+
.command("custom-integrations")
|
|
98
|
+
.description("Custom HTTP integration commands.")
|
|
99
|
+
.addCommand(new Command("apply")
|
|
100
|
+
.description(`Create or update a custom HTTP integration manifest. Attach credentials with \`custom-integrations connect\`. Manifest schema + worked examples: ${CUSTOM_HTTP_MANIFEST_DOCS_URL}`)
|
|
101
|
+
.option("--manifest <path>", "Path to a custom HTTP manifest JSON file.")
|
|
102
|
+
.option("--manifest-json <json>", "Custom HTTP manifest as a JSON object.")
|
|
103
|
+
.option("--json", "Print a JSON envelope.")
|
|
104
|
+
.action(async (options) => {
|
|
105
|
+
await handleAsyncAction("custom-integrations apply", options, () => {
|
|
106
|
+
const manifest = readCustomIntegrationManifest(options);
|
|
107
|
+
return requestOxygen("/api/cli/custom-integrations", {
|
|
108
|
+
method: "POST",
|
|
109
|
+
body: { manifest },
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}))
|
|
113
|
+
.addCommand(new Command("connect")
|
|
114
|
+
.description("Attach the credential (bearer token, API key, or basic password) for an applied custom HTTP integration.")
|
|
115
|
+
.argument("<slug>", "Custom integration slug from the applied manifest.")
|
|
116
|
+
.option("--secrets-file <path>", "Path to a .env-style file holding the credential (a single entry, or one named API_KEY/TOKEN/PASSWORD/SECRET).")
|
|
117
|
+
.option("--json", "Print a JSON envelope.")
|
|
118
|
+
.action(async (slug, options) => {
|
|
119
|
+
await handleAsyncAction("custom-integrations connect", options, () => {
|
|
120
|
+
const apiKey = readCustomIntegrationSecret(options);
|
|
121
|
+
return requestOxygen("/api/cli/custom-integrations/connect", {
|
|
122
|
+
method: "POST",
|
|
123
|
+
body: { slug, api_key: apiKey },
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}))
|
|
127
|
+
.addCommand(new Command("list")
|
|
128
|
+
.description("List custom HTTP integrations for the active organization.")
|
|
129
|
+
.option("--slug <slug>", "Filter to one custom integration slug.")
|
|
130
|
+
.option("--json", "Print a JSON envelope.")
|
|
131
|
+
.action(async (options) => {
|
|
132
|
+
await handleAsyncAction("custom-integrations list", options, () => {
|
|
133
|
+
const params = new URLSearchParams();
|
|
134
|
+
if (readOption(options.slug))
|
|
135
|
+
params.set("slug", readOption(options.slug) ?? "");
|
|
136
|
+
const suffix = params.toString();
|
|
137
|
+
return requestOxygen(`/api/cli/custom-integrations${suffix ? `?${suffix}` : ""}`);
|
|
138
|
+
});
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { parseJsonObject } from "../cli-values.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
|
+
/**
|
|
7
|
+
* A widget configuration from `--config` or `--config-file`.
|
|
8
|
+
*
|
|
9
|
+
* Both are accepted because the shape of a chart or a code widget is more than
|
|
10
|
+
* anyone wants to quote on one shell line, and `--config-file` is how an agent
|
|
11
|
+
* hands over a configuration it just generated. The SHAPE is validated server
|
|
12
|
+
* side by the shared widget registry, never here: a second validator is how the
|
|
13
|
+
* CLI and the web start disagreeing about what a valid widget is.
|
|
14
|
+
*/
|
|
15
|
+
async function readDashboardWidgetConfig(options) {
|
|
16
|
+
const inline = readOption(options.config);
|
|
17
|
+
const path = readOption(options.configFile);
|
|
18
|
+
if (inline && path) {
|
|
19
|
+
throw new OxygenError("invalid_request", "Pass either --config or --config-file, not both.", { exitCode: 1 });
|
|
20
|
+
}
|
|
21
|
+
if (inline)
|
|
22
|
+
return parseJsonObject(inline);
|
|
23
|
+
if (path) {
|
|
24
|
+
const fs = await import("node:fs/promises");
|
|
25
|
+
return parseJsonObject(await fs.readFile(path, "utf8"));
|
|
26
|
+
}
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
export function registerDashboardsFamily(program, handleAsyncAction) {
|
|
30
|
+
program
|
|
31
|
+
.command("dashboards")
|
|
32
|
+
.description("Your saved dashboards, plus the built-in GTM funnel. `summary` reads the built-in Command Center funnel across the sequencer, unibox and CRM; everything else creates and edits dashboards you save yourself and open again later, from charts, saved table views, notes, embeds and live sources.")
|
|
33
|
+
.addCommand(new Command("summary")
|
|
34
|
+
.description("Show the GTM funnel summary: touches → replies → positive → meetings → deals → won, with this-period revenue, channel split, and current needs-action triage. Unread replies across channels and interested email conversations are current inbox counts, independent of the selected range.")
|
|
35
|
+
.option("--range <range>", "Preset range: 7d, 14d, 28d, 30d, 90d, 180d, 365d, or all. Defaults to 14d.")
|
|
36
|
+
.option("--from <date>", "Custom start date (YYYY-MM-DD).")
|
|
37
|
+
.option("--to <date>", "Custom end date (YYYY-MM-DD).")
|
|
38
|
+
.option("--cold-deal-days <n>", "Days of no activity before an open deal counts as cold. Defaults to 14.")
|
|
39
|
+
.option("--json", "Print a JSON envelope.")
|
|
40
|
+
.action(async (options) => {
|
|
41
|
+
await handleAsyncAction("dashboards summary", options, () => {
|
|
42
|
+
const params = new URLSearchParams();
|
|
43
|
+
const range = readOption(options.range);
|
|
44
|
+
const from = readOption(options.from);
|
|
45
|
+
const to = readOption(options.to);
|
|
46
|
+
const coldDealDays = readOption(options.coldDealDays);
|
|
47
|
+
if (range)
|
|
48
|
+
params.set("range", range);
|
|
49
|
+
if (from)
|
|
50
|
+
params.set("from", from);
|
|
51
|
+
if (to)
|
|
52
|
+
params.set("to", to);
|
|
53
|
+
if (coldDealDays)
|
|
54
|
+
params.set("cold_deal_days", coldDealDays);
|
|
55
|
+
const qs = params.toString();
|
|
56
|
+
return requestOxygen(`/api/cli/dashboards/summary${qs ? `?${qs}` : ""}`);
|
|
57
|
+
});
|
|
58
|
+
}))
|
|
59
|
+
.addCommand(new Command("list")
|
|
60
|
+
.description("List the dashboards saved in this workspace.")
|
|
61
|
+
.option("--tag <tag>", "Only dashboards carrying this workspace tag.")
|
|
62
|
+
.option("--json", "Print a JSON envelope.")
|
|
63
|
+
.action(async (options) => {
|
|
64
|
+
await handleAsyncAction("dashboards list", options, () => requestOxygen(`/api/cli/dashboards${options.tag ? `?tag=${encodeURIComponent(options.tag)}` : ""}`));
|
|
65
|
+
}))
|
|
66
|
+
.addCommand(new Command("create")
|
|
67
|
+
.description("Create a saved dashboard. It starts with one empty tab; add widgets to it with `dashboards widgets add`.")
|
|
68
|
+
.argument("<name>", "Display name, e.g. \"Monday numbers\".")
|
|
69
|
+
.option("--slug <slug>", "URL handle. Defaults to a slug derived from the name.")
|
|
70
|
+
.option("--description <text>", "What this dashboard answers.")
|
|
71
|
+
.option("--icon <icon>", "Icon name shown beside it in the CRM sidebar.")
|
|
72
|
+
.option("--default", "Open this one when the dashboards page is opened with no dashboard named.")
|
|
73
|
+
.option("--json", "Print a JSON envelope.")
|
|
74
|
+
.action(async (name, options) => {
|
|
75
|
+
await handleAsyncAction("dashboards create", options, () => {
|
|
76
|
+
const body = { name };
|
|
77
|
+
const slug = readOption(options.slug);
|
|
78
|
+
const description = readOption(options.description);
|
|
79
|
+
const icon = readOption(options.icon);
|
|
80
|
+
if (slug)
|
|
81
|
+
body.slug = slug;
|
|
82
|
+
if (description)
|
|
83
|
+
body.description = description;
|
|
84
|
+
if (icon)
|
|
85
|
+
body.icon = icon;
|
|
86
|
+
if (options.default)
|
|
87
|
+
body.is_default = true;
|
|
88
|
+
return requestOxygen("/api/cli/dashboards", { method: "POST", body });
|
|
89
|
+
});
|
|
90
|
+
}))
|
|
91
|
+
.addCommand(new Command("get")
|
|
92
|
+
.description("Read one dashboard with its tabs and widgets.")
|
|
93
|
+
.argument("<dashboard>", "Dashboard slug or id.")
|
|
94
|
+
.option("--json", "Print a JSON envelope.")
|
|
95
|
+
.action(async (dashboard, options) => {
|
|
96
|
+
await handleAsyncAction("dashboards get", options, () => requestOxygen(`/api/cli/dashboards/${encodeURIComponent(dashboard)}`));
|
|
97
|
+
}))
|
|
98
|
+
.addCommand(new Command("update")
|
|
99
|
+
.description("Rename a dashboard, change its description or icon, or make it the default.")
|
|
100
|
+
.argument("<dashboard>", "Dashboard slug or id.")
|
|
101
|
+
.option("--name <name>", "New display name.")
|
|
102
|
+
.option("--description <text>", "New description. Pass an empty string to clear it.")
|
|
103
|
+
.option("--icon <icon>", "New icon name.")
|
|
104
|
+
.option("--default", "Make this the default dashboard.")
|
|
105
|
+
.option("--json", "Print a JSON envelope.")
|
|
106
|
+
.action(async (dashboard, options) => {
|
|
107
|
+
await handleAsyncAction("dashboards update", options, () => {
|
|
108
|
+
const body = {};
|
|
109
|
+
if (options.name !== undefined)
|
|
110
|
+
body.name = options.name;
|
|
111
|
+
if (options.description !== undefined)
|
|
112
|
+
body.description = options.description;
|
|
113
|
+
if (options.icon !== undefined)
|
|
114
|
+
body.icon = options.icon;
|
|
115
|
+
if (options.default)
|
|
116
|
+
body.is_default = true;
|
|
117
|
+
return requestOxygen(`/api/cli/dashboards/${encodeURIComponent(dashboard)}`, {
|
|
118
|
+
method: "PATCH",
|
|
119
|
+
body,
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}))
|
|
123
|
+
.addCommand(new Command("delete")
|
|
124
|
+
.description("Archive a dashboard. Its slug becomes free to reuse; the definition is kept, not destroyed.")
|
|
125
|
+
.argument("<dashboard>", "Dashboard slug or id.")
|
|
126
|
+
.option("--json", "Print a JSON envelope.")
|
|
127
|
+
.action(async (dashboard, options) => {
|
|
128
|
+
await handleAsyncAction("dashboards delete", options, () => requestOxygen(`/api/cli/dashboards/${encodeURIComponent(dashboard)}`, {
|
|
129
|
+
method: "DELETE",
|
|
130
|
+
}));
|
|
131
|
+
}))
|
|
132
|
+
.addCommand(new Command("tabs")
|
|
133
|
+
.description("Tabs inside a dashboard.")
|
|
134
|
+
.addCommand(new Command("add")
|
|
135
|
+
.description("Add a tab to a dashboard.")
|
|
136
|
+
.argument("<dashboard>", "Dashboard slug or id.")
|
|
137
|
+
.argument("<title>", "Tab title.")
|
|
138
|
+
.option("--json", "Print a JSON envelope.")
|
|
139
|
+
.action(async (dashboard, title, options) => {
|
|
140
|
+
await handleAsyncAction("dashboards tab create", options, () => requestOxygen(`/api/cli/dashboards/${encodeURIComponent(dashboard)}/tabs`, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
body: { title },
|
|
143
|
+
}));
|
|
144
|
+
}))
|
|
145
|
+
.addCommand(new Command("remove")
|
|
146
|
+
.description("Remove a tab and every widget on it. A dashboard always keeps at least one tab.")
|
|
147
|
+
.argument("<dashboard>", "Dashboard slug or id.")
|
|
148
|
+
.argument("<tab_id>", "Tab id.")
|
|
149
|
+
.option("--json", "Print a JSON envelope.")
|
|
150
|
+
.action(async (dashboard, tabId, options) => {
|
|
151
|
+
await handleAsyncAction("dashboards tab delete", options, () => requestOxygen(`/api/cli/dashboards/${encodeURIComponent(dashboard)}/tabs?tab=${encodeURIComponent(tabId)}`, { method: "DELETE" }));
|
|
152
|
+
})))
|
|
153
|
+
.addCommand(new Command("widgets")
|
|
154
|
+
.description("Widgets on a dashboard: charts, saved views, embeds, notes, custom code, and live sources. `kinds` shows every configuration shape; `data` reads back the numbers a widget actually draws.")
|
|
155
|
+
.addCommand(new Command("kinds")
|
|
156
|
+
.description("List the widget kinds, what each one shows, whether it can cost credits, and an example configuration.")
|
|
157
|
+
.option("--json", "Print a JSON envelope.")
|
|
158
|
+
.action(async (options) => {
|
|
159
|
+
await handleAsyncAction("dashboards widget kinds", options, () => requestOxygen("/api/cli/dashboards/widget-kinds"));
|
|
160
|
+
}))
|
|
161
|
+
.addCommand(new Command("add")
|
|
162
|
+
.description("Add a widget. Kinds: chart, view, iframe, rich_text, code, live_source. The configuration shape depends on the kind — run `oxygen dashboards widgets kinds` to see each one with an example.")
|
|
163
|
+
.argument("<dashboard>", "Dashboard slug or id.")
|
|
164
|
+
.argument("<kind>", "chart | view | iframe | rich_text | code | live_source")
|
|
165
|
+
.argument("<title>", "Widget title.")
|
|
166
|
+
.option("--tab <tab_id>", "Tab to place it on. Defaults to the dashboard's first tab.")
|
|
167
|
+
.option("--config <json>", "Widget configuration as JSON.")
|
|
168
|
+
.option("--config-file <path>", "Read the configuration JSON from a file.")
|
|
169
|
+
.option("--position <json>", "Grid placement as JSON, e.g. '{\"x\":0,\"y\":0,\"w\":6,\"h\":4}'.")
|
|
170
|
+
.option("--json", "Print a JSON envelope.")
|
|
171
|
+
.action(async (dashboard, kind, title, options) => {
|
|
172
|
+
await handleAsyncAction("dashboards widget add", options, async () => {
|
|
173
|
+
const body = { kind, title };
|
|
174
|
+
const tab = readOption(options.tab);
|
|
175
|
+
if (tab)
|
|
176
|
+
body.tab = tab;
|
|
177
|
+
body.configuration = await readDashboardWidgetConfig(options);
|
|
178
|
+
const position = readOption(options.position);
|
|
179
|
+
if (position)
|
|
180
|
+
body.position = parseJsonObject(position);
|
|
181
|
+
return requestOxygen(`/api/cli/dashboards/${encodeURIComponent(dashboard)}/widgets`, { method: "POST", body });
|
|
182
|
+
});
|
|
183
|
+
}))
|
|
184
|
+
.addCommand(new Command("update")
|
|
185
|
+
.description("Change a widget's title, configuration or placement, or pause/resume a live source.")
|
|
186
|
+
.argument("<dashboard>", "Dashboard slug or id.")
|
|
187
|
+
.argument("<widget_id>", "Widget id.")
|
|
188
|
+
.option("--title <title>", "New title.")
|
|
189
|
+
.option("--config <json>", "Replacement configuration as JSON. Replaces the whole configuration, never merged.")
|
|
190
|
+
.option("--config-file <path>", "Read the replacement configuration JSON from a file.")
|
|
191
|
+
.option("--position <json>", "New grid placement as JSON.")
|
|
192
|
+
.option("--pause", "Stop a live-source widget refreshing. It keeps showing its last result.")
|
|
193
|
+
.option("--resume", "Let a paused live-source widget refresh again.")
|
|
194
|
+
.option("--json", "Print a JSON envelope.")
|
|
195
|
+
.action(async (dashboard, widgetId, options) => {
|
|
196
|
+
await handleAsyncAction("dashboards widget update", options, async () => {
|
|
197
|
+
if (options.pause && options.resume) {
|
|
198
|
+
throw new OxygenError("invalid_request", "Choose one of --pause or --resume, not both.", { exitCode: 1 });
|
|
199
|
+
}
|
|
200
|
+
const body = { widget: widgetId };
|
|
201
|
+
if (options.title !== undefined)
|
|
202
|
+
body.title = options.title;
|
|
203
|
+
if (readOption(options.config) || readOption(options.configFile)) {
|
|
204
|
+
body.configuration = await readDashboardWidgetConfig(options);
|
|
205
|
+
}
|
|
206
|
+
const position = readOption(options.position);
|
|
207
|
+
if (position)
|
|
208
|
+
body.position = parseJsonObject(position);
|
|
209
|
+
if (options.pause)
|
|
210
|
+
body.is_paused = true;
|
|
211
|
+
if (options.resume)
|
|
212
|
+
body.is_paused = false;
|
|
213
|
+
return requestOxygen(`/api/cli/dashboards/${encodeURIComponent(dashboard)}/widgets`, { method: "PATCH", body });
|
|
214
|
+
});
|
|
215
|
+
}))
|
|
216
|
+
.addCommand(new Command("remove")
|
|
217
|
+
.description("Remove a widget from a dashboard.")
|
|
218
|
+
.argument("<dashboard>", "Dashboard slug or id.")
|
|
219
|
+
.argument("<widget_id>", "Widget id.")
|
|
220
|
+
.option("--json", "Print a JSON envelope.")
|
|
221
|
+
.action(async (dashboard, widgetId, options) => {
|
|
222
|
+
await handleAsyncAction("dashboards widget remove", options, () => requestOxygen(`/api/cli/dashboards/${encodeURIComponent(dashboard)}/widgets?widget=${encodeURIComponent(widgetId)}`, { method: "DELETE" }));
|
|
223
|
+
}))
|
|
224
|
+
.addCommand(new Command("refresh")
|
|
225
|
+
.description("Refresh a live-source widget now. This is the only dashboard command that can cost credits, bounded by the ceiling saved on the widget. Opening a dashboard never spends.")
|
|
226
|
+
.argument("<dashboard>", "Dashboard slug or id.")
|
|
227
|
+
.argument("<widget_id>", "Widget id.")
|
|
228
|
+
.option("--json", "Print a JSON envelope.")
|
|
229
|
+
.action(async (dashboard, widgetId, options) => {
|
|
230
|
+
await handleAsyncAction("dashboards widget refresh", options, () => requestOxygen(`/api/cli/dashboards/${encodeURIComponent(dashboard)}/widgets/${encodeURIComponent(widgetId)}/refresh`, { method: "POST" }));
|
|
231
|
+
}))
|
|
232
|
+
.addCommand(new Command("data")
|
|
233
|
+
.description("Read the data behind one widget — the same numbers the web page draws.")
|
|
234
|
+
.argument("<dashboard>", "Dashboard slug or id.")
|
|
235
|
+
.argument("<widget_id>", "Widget id.")
|
|
236
|
+
.option("--json", "Print a JSON envelope.")
|
|
237
|
+
.action(async (dashboard, widgetId, options) => {
|
|
238
|
+
await handleAsyncAction("dashboards widget data", options, () => requestOxygen(`/api/cli/dashboards/${encodeURIComponent(dashboard)}/widgets/${encodeURIComponent(widgetId)}/data`));
|
|
239
|
+
})));
|
|
240
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { readPositiveInt } from "../cli-values.js";
|
|
2
|
+
import { addContractFlags, readContractFlags } from "../contract-flags.js";
|
|
3
|
+
import { DATABASE_QUERY_FLAG_OVERLAY } from "../database-query-flags.js";
|
|
4
|
+
import { requestOxygen } from "../http-client.js";
|
|
5
|
+
import { readOption } from "../util.js";
|
|
6
|
+
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
7
|
+
import { DATABASE_ATTACH_CONTRACT, DATABASE_MIGRATE_CONTRACT, DATABASE_QUERY_CONTRACT } from "@oxygen/shared/contracts/database";
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
export function registerDbFamily(program, handleAsyncAction) {
|
|
10
|
+
program
|
|
11
|
+
.command("db")
|
|
12
|
+
.description("Tenant database commands.")
|
|
13
|
+
.addCommand(new Command("status")
|
|
14
|
+
.description("Read-only, 0 credits: show the current database host and status, including historical Neon IDs, recorded region labels, and plan-included hosting; staff can pass global --org.")
|
|
15
|
+
.option("--json", "Print a JSON envelope.")
|
|
16
|
+
.action(async (options) => {
|
|
17
|
+
await handleAsyncAction("db status", options, () => requestOxygen("/api/cli/db/status"));
|
|
18
|
+
}))
|
|
19
|
+
.addCommand(new Command("provision")
|
|
20
|
+
.description("Provision the current organization on a registered PostgreSQL shard. Staff can pass global --org to repair another org.")
|
|
21
|
+
.option("--json", "Print a JSON envelope.")
|
|
22
|
+
.action(async (options) => {
|
|
23
|
+
await handleAsyncAction("db provision", options, () => requestOxygen("/api/cli/db/provision", { method: "POST", body: {} }));
|
|
24
|
+
}))
|
|
25
|
+
.addCommand(addContractFlags(new Command("migrate")
|
|
26
|
+
.description("Apply pending tenant database migrations for the current organization."), DATABASE_MIGRATE_CONTRACT, { rotate_credentials: { noNegate: true } })
|
|
27
|
+
.option("--json", "Print a JSON envelope.")
|
|
28
|
+
.action(async (options) => {
|
|
29
|
+
await handleAsyncAction("db migrate", options, () => requestOxygen("/api/cli/db/migrate", {
|
|
30
|
+
method: "POST",
|
|
31
|
+
body: readContractFlags(options, DATABASE_MIGRATE_CONTRACT, { rotate_credentials: { noNegate: true } }),
|
|
32
|
+
}));
|
|
33
|
+
}))
|
|
34
|
+
.addCommand(new Command("migrate-all")
|
|
35
|
+
.description("Apply pending tenant database migrations across all ready tenants (staff only).")
|
|
36
|
+
.option("--limit <n>", "Maximum tenants to migrate in this batch. Defaults to 50; hard cap is 500.")
|
|
37
|
+
.option("--dry-run", "List pending tenants without applying migrations.")
|
|
38
|
+
.option("--json", "Print a JSON envelope.")
|
|
39
|
+
.action(async (options) => {
|
|
40
|
+
await handleAsyncAction("db migrate-all", options, () => {
|
|
41
|
+
const limit = readPositiveInt(options.limit);
|
|
42
|
+
return requestOxygen("/api/cli/db/migrate-all", {
|
|
43
|
+
method: "POST",
|
|
44
|
+
body: {
|
|
45
|
+
...(limit !== undefined ? { limit } : {}),
|
|
46
|
+
...(options.dryRun ? { dry_run: true } : {}),
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}))
|
|
51
|
+
.addCommand(new Command("repair-preview")
|
|
52
|
+
.description("Preview tenant database repair issues without mutating production state (staff only).")
|
|
53
|
+
.option("--limit <n>", "Maximum tenant and provisioning issues to inspect. Defaults to 100; hard cap is 500.")
|
|
54
|
+
.option("--json", "Print a JSON envelope.")
|
|
55
|
+
.action(async (options) => {
|
|
56
|
+
await handleAsyncAction("db repair-preview", options, () => {
|
|
57
|
+
const limit = readPositiveInt(options.limit);
|
|
58
|
+
const query = new URLSearchParams();
|
|
59
|
+
if (limit !== undefined)
|
|
60
|
+
query.set("limit", String(limit));
|
|
61
|
+
const suffix = query.toString() ? `?${query.toString()}` : "";
|
|
62
|
+
return requestOxygen(`/api/cli/db/repair-preview${suffix}`);
|
|
63
|
+
});
|
|
64
|
+
}))
|
|
65
|
+
.addCommand(new Command("reconcile")
|
|
66
|
+
.description("Inspect tenant database registry drift. Staff only; defaults to dry-run.")
|
|
67
|
+
.option("--dry-run", "Report drift without applying repairs. This is the default.")
|
|
68
|
+
.option("--apply", "Apply safe registry repairs. Requires --confirm.")
|
|
69
|
+
.option("--confirm", "Confirm --apply for safe registry repairs.")
|
|
70
|
+
.option("--json", "Print a JSON envelope.")
|
|
71
|
+
.action(async (options) => {
|
|
72
|
+
await handleAsyncAction("db reconcile", options, () => {
|
|
73
|
+
if (options.apply && !options.confirm) {
|
|
74
|
+
throw new OxygenError("confirmation_required", "Refusing to apply tenant database reconciliation without --confirm.", { exitCode: 1 });
|
|
75
|
+
}
|
|
76
|
+
return requestOxygen("/api/cli/db/reconcile", {
|
|
77
|
+
method: "POST",
|
|
78
|
+
body: {
|
|
79
|
+
apply: Boolean(options.apply),
|
|
80
|
+
dry_run: !options.apply,
|
|
81
|
+
confirm: Boolean(options.confirm),
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}))
|
|
86
|
+
.addCommand(new Command("backfill-upsert-indexes")
|
|
87
|
+
.description("Create missing upsert-key secondary indexes on customer tables. Staff only; defaults to dry-run.")
|
|
88
|
+
.option("--dry-run", "Report the index backfill plan without creating indexes. This is the default.")
|
|
89
|
+
.option("--apply", "Create the missing indexes (CONCURRENTLY). Requires --confirm.")
|
|
90
|
+
.option("--confirm", "Confirm --apply for index creation.")
|
|
91
|
+
.option("--org <id>", "Limit the backfill to a single organization id.")
|
|
92
|
+
.option("--limit <n>", "Maximum ready tenants to inspect in this batch. Defaults to 25; hard cap is 200.")
|
|
93
|
+
.option("--json", "Print a JSON envelope.")
|
|
94
|
+
.action(async (options) => {
|
|
95
|
+
await handleAsyncAction("db backfill-upsert-indexes", options, () => {
|
|
96
|
+
if (options.apply && !options.confirm) {
|
|
97
|
+
throw new OxygenError("confirmation_required", "Refusing to create upsert-key indexes without --confirm.", { exitCode: 1 });
|
|
98
|
+
}
|
|
99
|
+
const limit = readPositiveInt(options.limit);
|
|
100
|
+
// `--org` is also a root option; Commander hands it to the root when
|
|
101
|
+
// both define it, so this subcommand's value arrives empty. Read both
|
|
102
|
+
// scopes (as backfill-table-order-indexes does) or the filter is lost
|
|
103
|
+
// and the backfill silently covers every tenant.
|
|
104
|
+
const organizationId = readOption(options.org) ?? readOption(program.opts().org);
|
|
105
|
+
return requestOxygen("/api/cli/db/backfill-upsert-indexes", {
|
|
106
|
+
method: "POST",
|
|
107
|
+
body: {
|
|
108
|
+
apply: Boolean(options.apply),
|
|
109
|
+
dry_run: !options.apply,
|
|
110
|
+
confirm: Boolean(options.confirm),
|
|
111
|
+
...(organizationId ? { organization_id: organizationId } : {}),
|
|
112
|
+
...(limit !== undefined ? { limit } : {}),
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}))
|
|
117
|
+
.addCommand(new Command("backfill-table-order-indexes")
|
|
118
|
+
.description("Converge stable row-order indexes on existing customer tables. Staff only; the default dry-run uses 0 credits and makes no database changes.")
|
|
119
|
+
.option("--dry-run", "Report readiness without creating indexes or spending credits. This is the default.")
|
|
120
|
+
.option("--apply", "Create missing indexes with CREATE INDEX CONCURRENTLY. Requires --confirm.")
|
|
121
|
+
.option("--confirm", "Confirm --apply for concurrent index creation.")
|
|
122
|
+
.option("--org <id>", "Limit the backfill to a single organization id.")
|
|
123
|
+
.option("--limit <n>", "Maximum ready tenants to inspect in this batch. Defaults to 25; hard cap is 200.")
|
|
124
|
+
.option("--json", "Print per-table details plus table_count, needs_preparation_count, in_progress_count, and error_count; pipe through `jq '.data | del(.tables)'` for a compact audit.")
|
|
125
|
+
.action(async (options) => {
|
|
126
|
+
await handleAsyncAction("db backfill-table-order-indexes", options, () => {
|
|
127
|
+
if (options.apply && !options.confirm) {
|
|
128
|
+
throw new OxygenError("confirmation_required", "Refusing to create table-order indexes without --confirm.", { exitCode: 1 });
|
|
129
|
+
}
|
|
130
|
+
const limit = readPositiveInt(options.limit);
|
|
131
|
+
// `--org` is also a root option (workspace override). Commander may
|
|
132
|
+
// attach it to the root command even when it appears after this
|
|
133
|
+
// subcommand, so read both scopes for this staff target filter.
|
|
134
|
+
const organizationId = readOption(options.org) ?? readOption(program.opts().org);
|
|
135
|
+
return requestOxygen("/api/cli/db/backfill-table-order-indexes", {
|
|
136
|
+
method: "POST",
|
|
137
|
+
body: {
|
|
138
|
+
apply: Boolean(options.apply),
|
|
139
|
+
dry_run: !options.apply,
|
|
140
|
+
confirm: Boolean(options.confirm),
|
|
141
|
+
...(organizationId ? { organization_id: organizationId } : {}),
|
|
142
|
+
...(limit !== undefined ? { limit } : {}),
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}))
|
|
147
|
+
.addCommand(new Command("cost-policy")
|
|
148
|
+
.description("Read-only, 0 credits. Show the retired Neon cost-policy status. Its managed field describes the policy, not database hosting; PostgreSQL has no per-workspace sleep control.")
|
|
149
|
+
.option("--json", "Print a JSON envelope.")
|
|
150
|
+
.action(async (options) => {
|
|
151
|
+
await handleAsyncAction("db cost-policy", options, () => requestOxygen("/api/cli/db/cost-policy"));
|
|
152
|
+
}))
|
|
153
|
+
.addCommand(new Command("reconcile-cost-policy")
|
|
154
|
+
.description("Retired compatibility command: report that Neon endpoint controls no longer apply.")
|
|
155
|
+
.option("--suspend-idle", "Retired compatibility flag; does not suspend PostgreSQL.")
|
|
156
|
+
.option("--json", "Print a JSON envelope.")
|
|
157
|
+
.action(async (options) => {
|
|
158
|
+
await handleAsyncAction("db reconcile-cost-policy", options, () => requestOxygen("/api/cli/db/reconcile-cost-policy", {
|
|
159
|
+
method: "POST",
|
|
160
|
+
body: {
|
|
161
|
+
...(options.suspendIdle ? { suspend_idle: true } : {}),
|
|
162
|
+
},
|
|
163
|
+
}));
|
|
164
|
+
}))
|
|
165
|
+
.addCommand(addContractFlags(new Command("attach")
|
|
166
|
+
.description("Attach an existing Postgres database as the current organization's tenant database.")
|
|
167
|
+
.requiredOption("--database-url <url>", "Owner connection string for the tenant Postgres database."), DATABASE_ATTACH_CONTRACT, { database_url: { omit: true } })
|
|
168
|
+
.option("--json", "Print a JSON envelope.")
|
|
169
|
+
.action(async (options) => {
|
|
170
|
+
await handleAsyncAction("db attach", options, () => requestOxygen("/api/cli/db/attach", {
|
|
171
|
+
method: "POST",
|
|
172
|
+
body: readContractFlags(options, DATABASE_ATTACH_CONTRACT),
|
|
173
|
+
}));
|
|
174
|
+
}))
|
|
175
|
+
.addCommand(addContractFlags(new Command("query")
|
|
176
|
+
.description("Run one read-only SQL statement against the current tenant database.")
|
|
177
|
+
.requiredOption("--sql <sql>", "Single SELECT, WITH, or EXPLAIN statement."), DATABASE_QUERY_CONTRACT, DATABASE_QUERY_FLAG_OVERLAY)
|
|
178
|
+
.option("--json", "Print a JSON envelope.")
|
|
179
|
+
.action(async (options) => {
|
|
180
|
+
await handleAsyncAction("db query", options, () => {
|
|
181
|
+
return requestOxygen("/api/cli/db/query", {
|
|
182
|
+
method: "POST",
|
|
183
|
+
body: {
|
|
184
|
+
sql: options.sql,
|
|
185
|
+
...readContractFlags(options, DATABASE_QUERY_CONTRACT, DATABASE_QUERY_FLAG_OVERLAY),
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
}));
|
|
190
|
+
}
|