@oxygen-agent/cli 1.275.1 → 1.284.3
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/command-manifest.d.ts +30 -0
- package/dist/command-manifest.js +70 -0
- package/dist/help.d.ts +2 -0
- package/dist/help.js +79 -0
- package/dist/http-client.d.ts +1 -0
- package/dist/http-client.js +13 -0
- package/dist/index.js +388 -24
- package/dist/knowledge-mirror.d.ts +22 -0
- package/dist/knowledge-mirror.js +56 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/billing.js +6 -0
- package/node_modules/@oxygen/shared/dist/cli-result.d.ts +3 -0
- package/node_modules/@oxygen/shared/dist/cli-result.js +65 -0
- package/node_modules/@oxygen/shared/dist/deprecation-registry.d.ts +12 -0
- package/node_modules/@oxygen/shared/dist/deprecation-registry.js +58 -0
- package/node_modules/@oxygen/shared/dist/knowledge-markdown.d.ts +25 -0
- package/node_modules/@oxygen/shared/dist/knowledge-markdown.js +74 -0
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,14 +7,16 @@ import { createInterface } from "node:readline/promises";
|
|
|
7
7
|
import { stdin as input, stdout as output } from "node:process";
|
|
8
8
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
9
9
|
import { Command, Option } from "commander";
|
|
10
|
-
import {
|
|
10
|
+
import { applyOxygenHelp } from "./help.js";
|
|
11
|
+
import { buildCommandManifest } from "./command-manifest.js";
|
|
12
|
+
import { AGENCY_DIRECTORY_REGIONS, AGENCY_DIRECTORY_SERVICES, formatCellForDisplay, formatPublicBudgetScopes, exitCodeForOxygenError, isVersionGreater, isVersionLess, OXYGEN_VERSION, OxygenError, parseKnowledgePageMarkdown, success, toFailure, } from "@oxygen/shared";
|
|
11
13
|
import { inferImportColumnLabels, inferRowsFileFormat, normalizeImportColumnKey, normalizeRowsForNewTable, normalizeRowsFormat, parseRowsFileBuffer, } from "@oxygen/shared/file-import";
|
|
12
14
|
import { assertRecipeBundleSafe, assertWorkflowManifest, buildRecipeManifest, compileWorkflowDefinition, isAnyWorkflowManifest, isRecipeManifest, isWorkflowDefinition, isWorkflowManifest, } from "@oxygen/workflows";
|
|
13
15
|
import { isRecipeDefinition } from "@oxygen/recipe-sdk";
|
|
14
16
|
import { createBrowserLoginSession, openBrowser } from "./browser-login.js";
|
|
15
17
|
import { clearCredentials, defaultApiUrl, listCredentialProfiles, loadCredentials, normalizeApiUrl, pickProfileNameForIdentity, pickProfileNameForUserSession, resolveActiveProfile, saveCredentials, switchCredentialProfile, updateActiveOrganizationForProfile, } from "./credentials.js";
|
|
16
18
|
import { ensureFreshCliForApiUrl, requestOxygen } from "./http-client.js";
|
|
17
|
-
import { acquireMirrorLock, clearConflictFiles, deletePageFile, emptyMirrorState, findMirrorSlugByPageId, isFileDirty, listConflictFiles, markMirrorStale, mirrorExists, purgeMirror, resetMirrorForFullResync, quarantineDirtyFile, readMirrorState, releaseMirrorLock, resolveDefaultConfigDir, resolveMirrorDir, writeGeneratedIndexFile, writeGeneratedLogFile, writeMirrorState, writePageFile, } from "./knowledge-mirror.js";
|
|
19
|
+
import { acquireMirrorLock, clearConflictFiles, deletePageFile, emptyMirrorState, findMirrorSlugByPageId, isFileDirty, listConflictFiles, localPageSha256, markMirrorStale, mirrorExists, pageFilePath, planMirrorPush, purgeMirror, resetMirrorForFullResync, quarantineDirtyFile, readMirrorState, releaseMirrorLock, resolveDefaultConfigDir, resolveMirrorDir, writeGeneratedIndexFile, writeGeneratedLogFile, writeMirrorState, writePageFile, } from "./knowledge-mirror.js";
|
|
18
20
|
import { waitForCliRun } from "./run-wait.js";
|
|
19
21
|
import { runLocalCustomHttpColumn } from "./local-custom-http-column.js";
|
|
20
22
|
import { captureCurrentTranscript, collectFeedbackEnvironment, TranscriptCaptureError, } from "./transcript.js";
|
|
@@ -119,12 +121,35 @@ async function handleAsyncAction(command, options, action) {
|
|
|
119
121
|
try {
|
|
120
122
|
const data = await action();
|
|
121
123
|
emitSuccess(command, data, options);
|
|
124
|
+
writeCreditsReceipt(data);
|
|
122
125
|
}
|
|
123
126
|
catch (error) {
|
|
124
127
|
const failure = toFailure(command, error);
|
|
125
128
|
writeJson(failure);
|
|
126
129
|
writeMaxCreditsHint(error);
|
|
127
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
130
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Paid envelopes (push 3 legibility) carry a `credits` block: quote on
|
|
134
|
+
// dry_run, receipt on live, remaining balance on both. Mirror it as one
|
|
135
|
+
// stderr line so spend stays visible in a terminal without polluting the
|
|
136
|
+
// machine-read stdout envelope.
|
|
137
|
+
function writeCreditsReceipt(data) {
|
|
138
|
+
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
139
|
+
return;
|
|
140
|
+
const credits = data.credits;
|
|
141
|
+
if (!credits || typeof credits !== "object" || Array.isArray(credits))
|
|
142
|
+
return;
|
|
143
|
+
const block = credits;
|
|
144
|
+
const remaining = typeof block.credits_remaining === "number"
|
|
145
|
+
? block.credits_remaining.toLocaleString("en-US")
|
|
146
|
+
: null;
|
|
147
|
+
if (typeof block.credits_used === "number") {
|
|
148
|
+
process.stderr.write(`spent ${block.credits_used.toLocaleString("en-US")} credits${remaining !== null ? `, ${remaining} remaining` : ""}\n`);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (typeof block.estimated_credits === "number") {
|
|
152
|
+
process.stderr.write(`estimated ${block.estimated_credits.toLocaleString("en-US")} credits for a live run${remaining !== null ? `, ${remaining} available` : ""}\n`);
|
|
128
153
|
}
|
|
129
154
|
}
|
|
130
155
|
// Paid live runs are refused server-side with typed spend-gate errors
|
|
@@ -1069,7 +1094,7 @@ export function createProgram() {
|
|
|
1069
1094
|
const binaryName = resolveCliBinaryName();
|
|
1070
1095
|
program
|
|
1071
1096
|
.name(binaryName)
|
|
1072
|
-
.description("
|
|
1097
|
+
.description("Revenue infrastructure for B2B startups — agent-operated GTM: tables, enrichment, sequences, workflows, CRM, knowledge. MCP + CLI first; every state-changing action returns a web_url deep-link.")
|
|
1073
1098
|
.version(OXYGEN_VERSION)
|
|
1074
1099
|
.option("--profile <name>", "Use a stored CLI profile for this command.")
|
|
1075
1100
|
.option("--org <organization>", "Use an organization id, Clerk org id, or slug for this command.");
|
|
@@ -1201,9 +1226,17 @@ export function createProgram() {
|
|
|
1201
1226
|
.command("whoami")
|
|
1202
1227
|
.description("Show the current Oxygen CLI identity.")
|
|
1203
1228
|
.option("--json", "Print a JSON envelope.")
|
|
1229
|
+
.option("--summary-json", "Print a compact identity envelope with no credential-shaped fields (safe for health probes and logs).")
|
|
1204
1230
|
.action(async (options) => {
|
|
1205
1231
|
await handleWhoamiAction(options);
|
|
1206
1232
|
});
|
|
1233
|
+
program
|
|
1234
|
+
.command("commands")
|
|
1235
|
+
.description("Print the machine-readable command manifest: every command with flags, spend/mutation markers, and the reserved exit-code table.")
|
|
1236
|
+
.option("--json", "Print a JSON envelope.")
|
|
1237
|
+
.action(async (options) => {
|
|
1238
|
+
await handleAsyncAction("commands", options, async () => buildCommandManifest(program, binaryName));
|
|
1239
|
+
});
|
|
1207
1240
|
program
|
|
1208
1241
|
.command("onboarding")
|
|
1209
1242
|
.description("Onboarding helpers.")
|
|
@@ -1239,14 +1272,26 @@ export function createProgram() {
|
|
|
1239
1272
|
requireAuth: false,
|
|
1240
1273
|
enforceMinimumCliVersion: false,
|
|
1241
1274
|
});
|
|
1275
|
+
// Explicit skew semantics: in_sync=false alone forced every ops sweep
|
|
1276
|
+
// to hand-interpret whether a version gap was safe (fleet friction,
|
|
1277
|
+
// recurring since 2026-06-18). `skew` names the direction and
|
|
1278
|
+
// `compatible` answers the only question that matters: is this CLI at
|
|
1279
|
+
// or above the server's enforced minimum?
|
|
1280
|
+
const minimum = server.minimum_cli_version ?? null;
|
|
1242
1281
|
return {
|
|
1243
1282
|
client_version: OXYGEN_VERSION,
|
|
1244
1283
|
api_url: apiUrl,
|
|
1245
1284
|
server_version: server.server_version,
|
|
1246
|
-
minimum_cli_version:
|
|
1285
|
+
minimum_cli_version: minimum,
|
|
1247
1286
|
sha: server.sha,
|
|
1248
1287
|
region: server.region,
|
|
1249
1288
|
in_sync: server.server_version === OXYGEN_VERSION,
|
|
1289
|
+
skew: isVersionGreater(OXYGEN_VERSION, server.server_version)
|
|
1290
|
+
? "client_ahead"
|
|
1291
|
+
: isVersionLess(OXYGEN_VERSION, server.server_version)
|
|
1292
|
+
? "client_behind"
|
|
1293
|
+
: "in_sync",
|
|
1294
|
+
compatible: minimum === null || !isVersionLess(OXYGEN_VERSION, minimum),
|
|
1250
1295
|
};
|
|
1251
1296
|
});
|
|
1252
1297
|
});
|
|
@@ -3160,6 +3205,28 @@ export function createProgram() {
|
|
|
3160
3205
|
method: "POST",
|
|
3161
3206
|
body: {},
|
|
3162
3207
|
}));
|
|
3208
|
+
}))
|
|
3209
|
+
.addCommand(new Command("synthesize")
|
|
3210
|
+
.description("Synthesize knowledge from workspace evidence (one low-cost AI call): campaign learnings from auto-filed outreach outcomes, or a voice guide from sent messages. Previews by default; --approved writes the result.")
|
|
3211
|
+
.option("--kind <kind>", "What to synthesize: campaign_learnings (default) or voice.", "campaign_learnings")
|
|
3212
|
+
.option("--sequence <sequence_id>", "Scope campaign learnings to one sequence's learnings page. Omit for the org-wide [[outreach-learnings]] page.")
|
|
3213
|
+
.option("--approved", "Write the synthesized result (learnings page revision / pinned voice guide) instead of previewing.")
|
|
3214
|
+
.option("--body <markdown>", "With --approved: save this edited body verbatim instead of re-synthesizing.")
|
|
3215
|
+
.option("--title <title>", "Optional title for a newly created page/asset.")
|
|
3216
|
+
.option("--limit <n>", "Voice only: how many recent sent messages to learn from (max 200).")
|
|
3217
|
+
.option("--json", "Print a JSON envelope.")
|
|
3218
|
+
.action(async (options) => {
|
|
3219
|
+
await handleAsyncAction("knowledge synthesize", options, () => requestOxygen("/api/cli/knowledge/synthesize", {
|
|
3220
|
+
method: "POST",
|
|
3221
|
+
body: {
|
|
3222
|
+
kind: options.kind ?? "campaign_learnings",
|
|
3223
|
+
...(readOption(options.sequence) ? { sequence: readOption(options.sequence) } : {}),
|
|
3224
|
+
...(options.approved ? { approved: true } : {}),
|
|
3225
|
+
...(readOption(options.body) ? { body: readOption(options.body) } : {}),
|
|
3226
|
+
...(readOption(options.title) ? { title: readOption(options.title) } : {}),
|
|
3227
|
+
...(readPositiveInt(options.limit) !== undefined ? { limit: readPositiveInt(options.limit) } : {}),
|
|
3228
|
+
},
|
|
3229
|
+
}));
|
|
3163
3230
|
}))
|
|
3164
3231
|
.addCommand(new Command("sync")
|
|
3165
3232
|
.description("Clone or refresh the local knowledge mirror: server-rendered page markdown under the CLI config dir.")
|
|
@@ -3169,6 +3236,13 @@ export function createProgram() {
|
|
|
3169
3236
|
.option("--json", "Print a JSON envelope.")
|
|
3170
3237
|
.action(async (options) => {
|
|
3171
3238
|
await handleAsyncAction("knowledge sync", options, () => runKnowledgeMirrorSync(options));
|
|
3239
|
+
}))
|
|
3240
|
+
.addCommand(new Command("push")
|
|
3241
|
+
.description("Push locally edited or newly created mirror pages back to the workspace wiki. Edits to canonical voice/brand/positioning pages become approval proposals instead of writing live.")
|
|
3242
|
+
.option("--dry-run", "List what would be pushed without writing anything.")
|
|
3243
|
+
.option("--json", "Print a JSON envelope.")
|
|
3244
|
+
.action(async (options) => {
|
|
3245
|
+
await handleAsyncAction("knowledge push", options, () => runKnowledgeMirrorPush(options));
|
|
3172
3246
|
}))
|
|
3173
3247
|
.addCommand(new Command("status")
|
|
3174
3248
|
.description("Report the local knowledge mirror: path, page count, last sync, quarantined conflicts.")
|
|
@@ -5721,6 +5795,7 @@ export function createProgram() {
|
|
|
5721
5795
|
.option("--profile-views-per-day <n>", "Daily profile views cap.")
|
|
5722
5796
|
.option("--follows-per-day <n>", "Daily follows cap.")
|
|
5723
5797
|
.option("--likes-per-day <n>", "Daily likes cap.")
|
|
5798
|
+
.option("--comments-per-day <n>", "Daily comments cap.")
|
|
5724
5799
|
.option("--total-actions-per-day <n>", "Daily cap across all send/action types.")
|
|
5725
5800
|
.option("--relations-reads-per-day <n>", "Daily cap on relations/connections list reads (scrape protection).")
|
|
5726
5801
|
.option("--messages-reads-per-day <n>", "Daily cap on chat and message-history reads.")
|
|
@@ -7281,6 +7356,95 @@ export function createProgram() {
|
|
|
7281
7356
|
});
|
|
7282
7357
|
});
|
|
7283
7358
|
})));
|
|
7359
|
+
program.addCommand(new Command("managed-inboxes")
|
|
7360
|
+
.description("Managed whitelabel sending inboxes on the OXYGEN-managed Cold Mail Reseller (CMR) account: subscribe a domain + N mailboxes (google/microsoft) as a recurring MONTHLY subscription billed to Oxygen credits, list/get your subscriptions, and cancel. Subscribe/cancel are approval-gated (preview → re-run with --approved --quote).")
|
|
7361
|
+
.addCommand(new Command("subscribe")
|
|
7362
|
+
.description("Subscribe a domain + mailboxes as a managed monthly CMR inbox subscription. WITHOUT --approved this prints a priced PREVIEW with a quote_id (nothing ordered or charged); re-run with --approved --quote <id> to place the order. Fails closed until the managed CMR wallet (CMR_RESELLER_API_KEY) + founder-signed pricing (CMR_MANAGED_INBOX_CREDITS_PER_MAILBOX) are configured.")
|
|
7363
|
+
.requiredOption("--domain <domain>", "Sending domain to register + host the mailboxes (e.g. send.acme.com).")
|
|
7364
|
+
.requiredOption("--provider <provider>", "Mailbox provider: google or microsoft.")
|
|
7365
|
+
.option("--mailboxes <json>", "JSON array of mailboxes: [{\"username\",\"first_name\",\"last_name\",\"profile_picture?\"}].")
|
|
7366
|
+
.option("--file <path>", "Path to a JSON file { \"mailboxes\": [...] } (alternative to --mailboxes).")
|
|
7367
|
+
.option("--years <n>", "Years to register the domain for (positive whole number). Defaults to 1.")
|
|
7368
|
+
.option("--billing <path>", "Path to a JSON file with the CMR billing address (required on the FIRST subscribe for an org that has no CMR account yet).")
|
|
7369
|
+
.option("--zapshield", "Add the Zapshield deliverability-protection add-on to the domain (a one-time per-domain add-on billed once its price is founder-signed; recorded but not charged until then).")
|
|
7370
|
+
.option("--approved", "Place the order (requires --quote). Without it, a priced preview is returned.")
|
|
7371
|
+
.option("--quote <id>", "The quote_id from a fresh preview. Required with --approved.")
|
|
7372
|
+
.option("--json", "Print a JSON envelope.")
|
|
7373
|
+
.action(async (options) => {
|
|
7374
|
+
await handleAsyncAction("managed-inboxes subscribe", options, () => {
|
|
7375
|
+
const domain = readOption(options.domain);
|
|
7376
|
+
const provider = readOption(options.provider);
|
|
7377
|
+
if (!domain)
|
|
7378
|
+
throw new Error("--domain is required.");
|
|
7379
|
+
if (!provider)
|
|
7380
|
+
throw new Error("--provider is required (google or microsoft).");
|
|
7381
|
+
let mailboxes;
|
|
7382
|
+
const mailboxesJson = readOption(options.mailboxes);
|
|
7383
|
+
const filePath = readOption(options.file);
|
|
7384
|
+
if (mailboxesJson) {
|
|
7385
|
+
mailboxes = JSON.parse(mailboxesJson);
|
|
7386
|
+
}
|
|
7387
|
+
else if (filePath) {
|
|
7388
|
+
const parsed = readJsonFileValue(resolve(filePath), "--file");
|
|
7389
|
+
mailboxes = parsed.mailboxes ?? [];
|
|
7390
|
+
}
|
|
7391
|
+
else {
|
|
7392
|
+
throw new Error("Provide --mailboxes <json> or --file <path>.");
|
|
7393
|
+
}
|
|
7394
|
+
const years = readOption(options.years);
|
|
7395
|
+
const billingPath = readOption(options.billing);
|
|
7396
|
+
const billing = billingPath ? readJsonFileValue(resolve(billingPath), "--billing") : undefined;
|
|
7397
|
+
const quote = readOption(options.quote);
|
|
7398
|
+
return requestOxygen("/api/cli/managed-inboxes/subscribe", {
|
|
7399
|
+
method: "POST",
|
|
7400
|
+
body: {
|
|
7401
|
+
domain,
|
|
7402
|
+
provider,
|
|
7403
|
+
mailboxes,
|
|
7404
|
+
...(years ? { years: Number(years) } : {}),
|
|
7405
|
+
...(billing ? { billing } : {}),
|
|
7406
|
+
...(options.zapshield ? { zapshield: true } : {}),
|
|
7407
|
+
...(options.approved ? { approved: true } : {}),
|
|
7408
|
+
...(quote ? { quote_id: quote } : {}),
|
|
7409
|
+
},
|
|
7410
|
+
});
|
|
7411
|
+
});
|
|
7412
|
+
}))
|
|
7413
|
+
.addCommand(new Command("list")
|
|
7414
|
+
.description("List the org's managed inbox subscriptions with provider, mailbox count, monthly price, CMR lifecycle status, and internal billing posture. Read-only, 0 Oxygen credits.")
|
|
7415
|
+
.option("--status <status>", "Filter by status: pending, active, renewing, past_due, expired, cancelled, or failed.")
|
|
7416
|
+
.option("--json", "Print a JSON envelope.")
|
|
7417
|
+
.action(async (options) => {
|
|
7418
|
+
await handleAsyncAction("managed-inboxes list", options, () => {
|
|
7419
|
+
const params = new URLSearchParams();
|
|
7420
|
+
const status = readOption(options.status);
|
|
7421
|
+
if (status)
|
|
7422
|
+
params.set("status", status);
|
|
7423
|
+
const suffix = params.toString();
|
|
7424
|
+
return requestOxygen(`/api/cli/managed-inboxes${suffix ? `?${suffix}` : ""}`);
|
|
7425
|
+
});
|
|
7426
|
+
}))
|
|
7427
|
+
.addCommand(new Command("get")
|
|
7428
|
+
.description("Get one managed inbox subscription by domain (provider, mailbox count, monthly price, CMR subscription id, lifecycle status, auto-renew, period end, internal billing status). Read-only, 0 Oxygen credits.")
|
|
7429
|
+
.argument("<domain>", "The managed inbox domain (e.g. send.acme.com).")
|
|
7430
|
+
.option("--json", "Print a JSON envelope.")
|
|
7431
|
+
.action(async (domain, options) => {
|
|
7432
|
+
await handleAsyncAction("managed-inboxes get", options, () => requestOxygen(`/api/cli/managed-inboxes/${encodeURIComponent(domain)}`));
|
|
7433
|
+
}))
|
|
7434
|
+
.addCommand(new Command("cancel")
|
|
7435
|
+
.description("Cancel a managed inbox subscription by domain (stops CMR's recurring monthly charge and frees the domain at period end). WITHOUT --approved prints a preview; re-run with --approved to cancel. Cancelling is free — 0 Oxygen credits.")
|
|
7436
|
+
.argument("<domain>", "The managed inbox domain to cancel.")
|
|
7437
|
+
.option("--approved", "Actually cancel (otherwise a preview is returned).")
|
|
7438
|
+
.option("--json", "Print a JSON envelope.")
|
|
7439
|
+
.action(async (domain, options) => {
|
|
7440
|
+
await handleAsyncAction("managed-inboxes cancel", options, () => requestOxygen("/api/cli/managed-inboxes/cancel", {
|
|
7441
|
+
method: "POST",
|
|
7442
|
+
body: {
|
|
7443
|
+
domain,
|
|
7444
|
+
...(options.approved ? { approved: true } : {}),
|
|
7445
|
+
},
|
|
7446
|
+
}));
|
|
7447
|
+
})));
|
|
7284
7448
|
program.addCommand(new Command("mailboxes")
|
|
7285
7449
|
.description("Native email sending pool: register/refresh Google/Microsoft mailboxes a campaign rotates over, pause/disable inboxes, and delegate warmup to Instantly (BYOK — Instantly bills your account, 0 Oxygen credits).")
|
|
7286
7450
|
.addCommand(new Command("list")
|
|
@@ -7527,6 +7691,28 @@ export function createProgram() {
|
|
|
7527
7691
|
const suffix = params.toString();
|
|
7528
7692
|
return requestOxygen(`/api/cli/mailboxes/warmup/status${suffix ? `?${suffix}` : ""}`);
|
|
7529
7693
|
});
|
|
7694
|
+
}))
|
|
7695
|
+
.addCommand(new Command("provision")
|
|
7696
|
+
.description("Export sending mailboxes into the Instantly workspace via Zapmail (the prerequisite for warmup), then let the worker enable warmup once the export completes. Targets the whole pool unless --mailboxes is given. Uses the org's Zapmail connection (BYOK) or the OXYGEN-managed wallet.")
|
|
7697
|
+
.option("--mailboxes <list>", "Comma-separated mailbox ids or addresses to provision. Omit for the whole pool.")
|
|
7698
|
+
.option("--connection <id>", "Zapmail connection id. Defaults to the org's active Zapmail connection (or the managed wallet).")
|
|
7699
|
+
.option("--force", "Re-export mailboxes already exporting/warming/active (default skips them).")
|
|
7700
|
+
.option("--dry-run", "Simulate without calling Zapmail (reports the mailboxes that would be exported).")
|
|
7701
|
+
.option("--json", "Print a JSON envelope.")
|
|
7702
|
+
.action(async (options) => {
|
|
7703
|
+
await handleAsyncAction("mailboxes warmup provision", options, () => {
|
|
7704
|
+
const mailboxes = readCsvOption(options.mailboxes);
|
|
7705
|
+
const connection = readOption(options.connection);
|
|
7706
|
+
return requestOxygen("/api/cli/mailboxes/warmup/provision", {
|
|
7707
|
+
method: "POST",
|
|
7708
|
+
body: {
|
|
7709
|
+
...(mailboxes.length > 0 ? { mailboxes } : {}),
|
|
7710
|
+
...(connection ? { connection_id: connection } : {}),
|
|
7711
|
+
...(options.force ? { force: true } : {}),
|
|
7712
|
+
...(options.dryRun ? { dry_run: true } : {}),
|
|
7713
|
+
},
|
|
7714
|
+
});
|
|
7715
|
+
});
|
|
7530
7716
|
})))
|
|
7531
7717
|
.addCommand(new Command("order")
|
|
7532
7718
|
.description("Order NEW sending mailboxes (domain + inboxes + warmup) on the OXYGEN-managed Zapmail wallet, billed to Oxygen credits. Without --approved returns a priced preview + quote_id. FAILS CLOSED (409) until managed ordering is enabled.")
|
|
@@ -8194,6 +8380,7 @@ export function createProgram() {
|
|
|
8194
8380
|
.action(async (options) => {
|
|
8195
8381
|
await handleAsyncAction("skills install", options, () => installAgentSkills(options));
|
|
8196
8382
|
}));
|
|
8383
|
+
applyOxygenHelp(program, binaryName);
|
|
8197
8384
|
return program;
|
|
8198
8385
|
}
|
|
8199
8386
|
/**
|
|
@@ -10771,6 +10958,27 @@ async function handleWhoamiAction(options) {
|
|
|
10771
10958
|
enforceMinimumCliVersion: false,
|
|
10772
10959
|
});
|
|
10773
10960
|
const context = await resolveActiveProfileWithSource();
|
|
10961
|
+
// Compact, credential-free identity shape for health probes and ops
|
|
10962
|
+
// sweeps: the full envelope carries apiKey token prefix/suffix metadata,
|
|
10963
|
+
// which forced every automated consumer to sanitize before logging
|
|
10964
|
+
// (recurring fleet friction since 2026-06-18).
|
|
10965
|
+
if (options.summaryJson) {
|
|
10966
|
+
writeJson(success("whoami", {
|
|
10967
|
+
user: { id: identity.user.id, email: identity.user.email },
|
|
10968
|
+
organization: {
|
|
10969
|
+
id: identity.organization.id,
|
|
10970
|
+
name: identity.organization.name,
|
|
10971
|
+
slug: identity.organization.slug,
|
|
10972
|
+
},
|
|
10973
|
+
auth_type: identity.authType ?? identity.apiKey?.kind ?? null,
|
|
10974
|
+
onboarding_complete: identity.onboarding?.complete ?? null,
|
|
10975
|
+
profile: context.resolution.exists ? context.resolution.name : null,
|
|
10976
|
+
profile_source: context.source,
|
|
10977
|
+
api_url: context.resolution.credentials?.apiUrl ?? null,
|
|
10978
|
+
cli_version: OXYGEN_VERSION,
|
|
10979
|
+
}));
|
|
10980
|
+
return;
|
|
10981
|
+
}
|
|
10774
10982
|
if (context.resolution.exists) {
|
|
10775
10983
|
const existingCredentials = context.resolution.credentials;
|
|
10776
10984
|
const cached = existingCredentials?.identity;
|
|
@@ -10806,7 +11014,7 @@ async function handleWhoamiAction(options) {
|
|
|
10806
11014
|
catch (error) {
|
|
10807
11015
|
const failure = toFailure("whoami", error);
|
|
10808
11016
|
writeJson(failure);
|
|
10809
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
11017
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
10810
11018
|
}
|
|
10811
11019
|
}
|
|
10812
11020
|
async function handleOnboardingStartAction(options) {
|
|
@@ -10821,7 +11029,7 @@ async function handleOnboardingStartAction(options) {
|
|
|
10821
11029
|
catch (error) {
|
|
10822
11030
|
const failure = toFailure("onboarding start", error);
|
|
10823
11031
|
writeJson(failure);
|
|
10824
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
11032
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
10825
11033
|
}
|
|
10826
11034
|
}
|
|
10827
11035
|
async function handleOnboardingResetAction(options) {
|
|
@@ -10845,7 +11053,7 @@ async function handleOnboardingResetAction(options) {
|
|
|
10845
11053
|
catch (error) {
|
|
10846
11054
|
const failure = toFailure("onboarding reset", error);
|
|
10847
11055
|
writeJson(failure);
|
|
10848
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
11056
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
10849
11057
|
}
|
|
10850
11058
|
}
|
|
10851
11059
|
function formatOnboardingReset(data) {
|
|
@@ -10903,7 +11111,7 @@ async function handleLoginAction(options) {
|
|
|
10903
11111
|
catch (error) {
|
|
10904
11112
|
const failure = toFailure("login", error);
|
|
10905
11113
|
writeJson(failure);
|
|
10906
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
11114
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
10907
11115
|
}
|
|
10908
11116
|
}
|
|
10909
11117
|
async function handleAuthUseTokenAction(options) {
|
|
@@ -10933,7 +11141,7 @@ async function handleAuthUseTokenAction(options) {
|
|
|
10933
11141
|
writeJson(failure);
|
|
10934
11142
|
else
|
|
10935
11143
|
process.stderr.write(`${failure.error.message}\n`);
|
|
10936
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
11144
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
10937
11145
|
}
|
|
10938
11146
|
}
|
|
10939
11147
|
async function handleAuthDoctorAction(options) {
|
|
@@ -10948,7 +11156,7 @@ async function handleAuthDoctorAction(options) {
|
|
|
10948
11156
|
catch (error) {
|
|
10949
11157
|
const failure = toFailure("auth doctor", error);
|
|
10950
11158
|
writeJson(failure);
|
|
10951
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
11159
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
10952
11160
|
}
|
|
10953
11161
|
}
|
|
10954
11162
|
async function runAuthDoctor() {
|
|
@@ -11039,7 +11247,7 @@ async function handleOrgUseAction(organization, options, command) {
|
|
|
11039
11247
|
catch (error) {
|
|
11040
11248
|
const failure = toFailure(command, error);
|
|
11041
11249
|
writeJson(failure);
|
|
11042
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
11250
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
11043
11251
|
}
|
|
11044
11252
|
}
|
|
11045
11253
|
async function handleProfilesListAction(options) {
|
|
@@ -11058,7 +11266,7 @@ async function handleProfilesListAction(options) {
|
|
|
11058
11266
|
catch (error) {
|
|
11059
11267
|
const failure = toFailure("profiles list", error);
|
|
11060
11268
|
writeJson(failure);
|
|
11061
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
11269
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
11062
11270
|
}
|
|
11063
11271
|
}
|
|
11064
11272
|
async function handleProfilesUseAction(profile, options) {
|
|
@@ -11079,7 +11287,7 @@ async function handleProfilesUseAction(profile, options) {
|
|
|
11079
11287
|
catch (error) {
|
|
11080
11288
|
const failure = toFailure("profiles use", error);
|
|
11081
11289
|
writeJson(failure);
|
|
11082
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
11290
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
11083
11291
|
}
|
|
11084
11292
|
}
|
|
11085
11293
|
async function handleProfilesEnvAction(profile, options) {
|
|
@@ -11118,7 +11326,7 @@ async function handleProfilesEnvAction(profile, options) {
|
|
|
11118
11326
|
catch (error) {
|
|
11119
11327
|
const failure = toFailure("profiles env", error);
|
|
11120
11328
|
writeJson(failure);
|
|
11121
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
11329
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
11122
11330
|
}
|
|
11123
11331
|
}
|
|
11124
11332
|
async function handleProfilesCurrentAction(options) {
|
|
@@ -11142,7 +11350,7 @@ async function handleProfilesCurrentAction(options) {
|
|
|
11142
11350
|
catch (error) {
|
|
11143
11351
|
const failure = toFailure("profiles current", error);
|
|
11144
11352
|
writeJson(failure);
|
|
11145
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
11353
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
11146
11354
|
}
|
|
11147
11355
|
}
|
|
11148
11356
|
function shellQuote(value) {
|
|
@@ -11171,7 +11379,7 @@ async function handleLogoutAction(options) {
|
|
|
11171
11379
|
catch (error) {
|
|
11172
11380
|
const failure = toFailure("logout", error);
|
|
11173
11381
|
writeJson(failure);
|
|
11174
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
11382
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
11175
11383
|
}
|
|
11176
11384
|
}
|
|
11177
11385
|
async function handleUpdateAction(options) {
|
|
@@ -11186,7 +11394,7 @@ async function handleUpdateAction(options) {
|
|
|
11186
11394
|
catch (error) {
|
|
11187
11395
|
const failure = toFailure("update", error);
|
|
11188
11396
|
writeJson(failure);
|
|
11189
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
11397
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
11190
11398
|
}
|
|
11191
11399
|
}
|
|
11192
11400
|
function buildApiKeyCreateBody(options) {
|
|
@@ -11797,7 +12005,7 @@ async function handleSequenceSignalAction(sequence, options) {
|
|
|
11797
12005
|
const failure = toFailure("sequences signal", error);
|
|
11798
12006
|
writeJson(failure);
|
|
11799
12007
|
writeMaxCreditsHint(error);
|
|
11800
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
12008
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
11801
12009
|
}
|
|
11802
12010
|
}
|
|
11803
12011
|
async function handleSequenceVariantsAction(sequence, options) {
|
|
@@ -11834,7 +12042,7 @@ async function handleSequenceVariantsAction(sequence, options) {
|
|
|
11834
12042
|
const failure = toFailure("sequences variants", error);
|
|
11835
12043
|
writeJson(failure);
|
|
11836
12044
|
writeMaxCreditsHint(error);
|
|
11837
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
12045
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
11838
12046
|
}
|
|
11839
12047
|
}
|
|
11840
12048
|
function formatVariantCell(value) {
|
|
@@ -11965,7 +12173,7 @@ async function handleTablesTidySuggestAction(table, options) {
|
|
|
11965
12173
|
const failure = toFailure("tables tidy-suggest", error);
|
|
11966
12174
|
writeJson(failure);
|
|
11967
12175
|
writeMaxCreditsHint(error);
|
|
11968
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
12176
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
11969
12177
|
}
|
|
11970
12178
|
}
|
|
11971
12179
|
function formatTidyCount(value) {
|
|
@@ -12348,7 +12556,7 @@ async function handleSupportAdminWorkflowAction(ticketId, options) {
|
|
|
12348
12556
|
catch (error) {
|
|
12349
12557
|
const failure = toFailure("support admin workflow", error);
|
|
12350
12558
|
writeJson(failure);
|
|
12351
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
12559
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
12352
12560
|
}
|
|
12353
12561
|
}
|
|
12354
12562
|
async function handleSupportAdminUpdateAction(ticketId, options) {
|
|
@@ -12364,7 +12572,7 @@ async function handleSupportAdminUpdateAction(ticketId, options) {
|
|
|
12364
12572
|
catch (error) {
|
|
12365
12573
|
const failure = toFailure("support admin update", error);
|
|
12366
12574
|
writeJson(failure);
|
|
12367
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
12575
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
12368
12576
|
}
|
|
12369
12577
|
}
|
|
12370
12578
|
function formatSupportAdminWorkflowStep(styles, label, status, artifactLabel, artifact) {
|
|
@@ -12915,6 +13123,152 @@ async function runKnowledgeMirrorSync(options) {
|
|
|
12915
13123
|
releaseMirrorLock(target.dir);
|
|
12916
13124
|
}
|
|
12917
13125
|
}
|
|
13126
|
+
// One page's push: parse the local file, guard identity, upsert with the manifest
|
|
13127
|
+
// revision as the optimistic-concurrency base, and translate the two expected
|
|
13128
|
+
// gate outcomes (approval proposal, revision conflict) into statuses.
|
|
13129
|
+
async function pushOneMirrorPage(dir, state, candidate) {
|
|
13130
|
+
const { slug, kind, record } = candidate;
|
|
13131
|
+
let contents;
|
|
13132
|
+
try {
|
|
13133
|
+
contents = readFileSync(pageFilePath(dir, slug), "utf8");
|
|
13134
|
+
}
|
|
13135
|
+
catch {
|
|
13136
|
+
return { slug, kind, status: "skipped", reason: "file_unreadable" };
|
|
13137
|
+
}
|
|
13138
|
+
const parsed = parseKnowledgePageMarkdown(contents);
|
|
13139
|
+
if (record && parsed.id && parsed.id !== record.id) {
|
|
13140
|
+
return { slug, kind, status: "skipped", reason: "id_mismatch_with_manifest" };
|
|
13141
|
+
}
|
|
13142
|
+
if (parsed.slug && parsed.slug !== slug) {
|
|
13143
|
+
return { slug, kind, status: "skipped", reason: "frontmatter_slug_differs_from_filename" };
|
|
13144
|
+
}
|
|
13145
|
+
const body = {
|
|
13146
|
+
slug,
|
|
13147
|
+
body: parsed.body,
|
|
13148
|
+
...(parsed.title ? { title: parsed.title } : {}),
|
|
13149
|
+
...(parsed.type ? { type: parsed.type } : {}),
|
|
13150
|
+
...(parsed.status ? { status: parsed.status } : {}),
|
|
13151
|
+
...(parsed.tags ? { tags: parsed.tags } : {}),
|
|
13152
|
+
// A frontmatter block owns the summary line: pushing an emptied summary
|
|
13153
|
+
// clears it server-side; a plain new file leaves summary untouched.
|
|
13154
|
+
...(parsed.hasFrontmatter ? { summary: parsed.summary } : {}),
|
|
13155
|
+
...(record ? { expected_revision: record.revision } : {}),
|
|
13156
|
+
};
|
|
13157
|
+
try {
|
|
13158
|
+
const data = await requestOxygen("/api/cli/knowledge/pages/upsert", { method: "POST", body });
|
|
13159
|
+
const page = data.page ?? {};
|
|
13160
|
+
if (typeof page.id === "string" && typeof page.revision === "number") {
|
|
13161
|
+
// Record the pushed bytes as the manifest truth so the follow-up sync
|
|
13162
|
+
// overwrites this file with the server rendering instead of quarantining
|
|
13163
|
+
// the very edit we just pushed.
|
|
13164
|
+
const localSha = localPageSha256(dir, slug);
|
|
13165
|
+
if (localSha) {
|
|
13166
|
+
state.pages[slug] = { id: page.id, revision: page.revision, content_sha256: localSha };
|
|
13167
|
+
}
|
|
13168
|
+
}
|
|
13169
|
+
return {
|
|
13170
|
+
slug,
|
|
13171
|
+
kind,
|
|
13172
|
+
status: "pushed",
|
|
13173
|
+
...(typeof page.revision === "number" ? { revision: page.revision } : {}),
|
|
13174
|
+
};
|
|
13175
|
+
}
|
|
13176
|
+
catch (error) {
|
|
13177
|
+
if (error instanceof OxygenError && error.code === "knowledge_approval_required") {
|
|
13178
|
+
const details = (error.details ?? {});
|
|
13179
|
+
return {
|
|
13180
|
+
slug,
|
|
13181
|
+
kind,
|
|
13182
|
+
status: "proposal_created",
|
|
13183
|
+
...(typeof details.proposalId === "string" ? { proposal_id: details.proposalId } : {}),
|
|
13184
|
+
...(typeof details.proposalSlug === "string" ? { proposal_slug: details.proposalSlug } : {}),
|
|
13185
|
+
reason: typeof details.reason === "string" ? details.reason : "sensitive_page",
|
|
13186
|
+
};
|
|
13187
|
+
}
|
|
13188
|
+
if (error instanceof OxygenError && error.code === "knowledge_page_conflict") {
|
|
13189
|
+
return { slug, kind, status: "conflict", reason: "revision_moved_run_knowledge_sync_first" };
|
|
13190
|
+
}
|
|
13191
|
+
if (error instanceof OxygenError) {
|
|
13192
|
+
return { slug, kind, status: "failed", reason: error.code };
|
|
13193
|
+
}
|
|
13194
|
+
throw error;
|
|
13195
|
+
}
|
|
13196
|
+
}
|
|
13197
|
+
// `oxygen knowledge push` — the write half of the local mirror: locally edited
|
|
13198
|
+
// (dirty) and brand-new mirror files upsert back to the workspace wiki through
|
|
13199
|
+
// the same gate as every other write surface, then a delta sync converges the
|
|
13200
|
+
// mirror onto the server rendering. This is what lets a local agent (Claude
|
|
13201
|
+
// Code, Codex) work the wiki as plain markdown files.
|
|
13202
|
+
async function runKnowledgeMirrorPush(options) {
|
|
13203
|
+
const target = await resolveKnowledgeMirrorTarget();
|
|
13204
|
+
const state = readMirrorState(target.dir);
|
|
13205
|
+
if (!state) {
|
|
13206
|
+
throw new OxygenError("knowledge_mirror_missing", "No local knowledge mirror exists for this organization yet. Run `oxygen knowledge sync` first.", {
|
|
13207
|
+
details: { mirror_path: target.dir },
|
|
13208
|
+
exitCode: 1,
|
|
13209
|
+
});
|
|
13210
|
+
}
|
|
13211
|
+
const plan = planMirrorPush(target.dir, state);
|
|
13212
|
+
if (options.dryRun) {
|
|
13213
|
+
return {
|
|
13214
|
+
dry_run: true,
|
|
13215
|
+
candidates: plan.candidates.map(({ slug, kind, record }) => ({
|
|
13216
|
+
slug,
|
|
13217
|
+
kind,
|
|
13218
|
+
...(record ? { base_revision: record.revision } : {}),
|
|
13219
|
+
})),
|
|
13220
|
+
skipped: plan.skipped,
|
|
13221
|
+
mirror_path: target.dir,
|
|
13222
|
+
web_url: target.webUrl,
|
|
13223
|
+
};
|
|
13224
|
+
}
|
|
13225
|
+
// Hold the mirror lock through the push phase so a concurrent sync can't
|
|
13226
|
+
// rewrite files or the manifest mid-push; released before the convergence
|
|
13227
|
+
// sync below, which takes the lock itself.
|
|
13228
|
+
if (!acquireMirrorLock(target.dir)) {
|
|
13229
|
+
throw new OxygenError("knowledge_sync_locked", "Another knowledge sync is running for this mirror. Retry shortly; a crashed sync's lock self-clears after 10 minutes.", {
|
|
13230
|
+
details: { mirror_path: target.dir },
|
|
13231
|
+
exitCode: 1,
|
|
13232
|
+
});
|
|
13233
|
+
}
|
|
13234
|
+
const outcomes = [];
|
|
13235
|
+
let pushedCount = 0;
|
|
13236
|
+
try {
|
|
13237
|
+
for (const candidate of plan.candidates) {
|
|
13238
|
+
outcomes.push(await pushOneMirrorPage(target.dir, state, candidate));
|
|
13239
|
+
}
|
|
13240
|
+
pushedCount = outcomes.filter((outcome) => outcome.status === "pushed").length;
|
|
13241
|
+
if (pushedCount > 0) {
|
|
13242
|
+
writeMirrorState(target.dir, state);
|
|
13243
|
+
}
|
|
13244
|
+
}
|
|
13245
|
+
finally {
|
|
13246
|
+
releaseMirrorLock(target.dir);
|
|
13247
|
+
}
|
|
13248
|
+
// Converge: pull the server's rendering of everything just pushed (plus any
|
|
13249
|
+
// proposal draft pages the gate filed) so local bytes match the server again.
|
|
13250
|
+
let syncedAfter = false;
|
|
13251
|
+
if (pushedCount > 0 || outcomes.some((outcome) => outcome.status === "proposal_created")) {
|
|
13252
|
+
await runKnowledgeMirrorSync({});
|
|
13253
|
+
syncedAfter = true;
|
|
13254
|
+
}
|
|
13255
|
+
return {
|
|
13256
|
+
pushed: pushedCount,
|
|
13257
|
+
proposals: outcomes.filter((outcome) => outcome.status === "proposal_created"),
|
|
13258
|
+
conflicts: outcomes.filter((outcome) => outcome.status === "conflict"),
|
|
13259
|
+
failures: outcomes.filter((outcome) => outcome.status === "failed"),
|
|
13260
|
+
skipped: [
|
|
13261
|
+
...plan.skipped,
|
|
13262
|
+
...outcomes
|
|
13263
|
+
.filter((outcome) => outcome.status === "skipped")
|
|
13264
|
+
.map(({ slug, reason }) => ({ file: `${slug}.md`, reason: reason ?? "skipped" })),
|
|
13265
|
+
],
|
|
13266
|
+
outcomes,
|
|
13267
|
+
synced_after: syncedAfter,
|
|
13268
|
+
mirror_path: target.dir,
|
|
13269
|
+
web_url: target.webUrl,
|
|
13270
|
+
};
|
|
13271
|
+
}
|
|
12918
13272
|
async function runKnowledgeMirrorStatus(options) {
|
|
12919
13273
|
const target = await resolveKnowledgeMirrorTarget();
|
|
12920
13274
|
const clearedConflicts = options.clearConflicts ? clearConflictFiles(target.dir) : undefined;
|
|
@@ -12948,7 +13302,7 @@ async function handleKnowledgeMirrorPrintPathAction() {
|
|
|
12948
13302
|
catch (error) {
|
|
12949
13303
|
const failure = toFailure("knowledge status", error);
|
|
12950
13304
|
writeJson(failure);
|
|
12951
|
-
process.exitCode = error instanceof OxygenError ? error
|
|
13305
|
+
process.exitCode = error instanceof OxygenError ? exitCodeForOxygenError(error) : 1;
|
|
12952
13306
|
}
|
|
12953
13307
|
}
|
|
12954
13308
|
// Knowledge page routes accept either a UUID (`id`) or a slug (`slug`); split
|
|
@@ -13030,6 +13384,7 @@ options) {
|
|
|
13030
13384
|
setLimit("profile_views_per_day", options.profileViewsPerDay);
|
|
13031
13385
|
setLimit("follows_per_day", options.followsPerDay);
|
|
13032
13386
|
setLimit("likes_per_day", options.likesPerDay);
|
|
13387
|
+
setLimit("comments_per_day", options.commentsPerDay);
|
|
13033
13388
|
setLimit("total_actions_per_day", options.totalActionsPerDay);
|
|
13034
13389
|
setLimit("relations_reads_per_day", options.relationsReadsPerDay);
|
|
13035
13390
|
setLimit("messages_reads_per_day", options.messagesReadsPerDay);
|
|
@@ -13192,4 +13547,13 @@ function muteTokenEcho() {
|
|
|
13192
13547
|
return () => undefined;
|
|
13193
13548
|
}
|
|
13194
13549
|
}
|
|
13550
|
+
// Piping CLI output into a consumer that exits early (`oxygen whoami --json |
|
|
13551
|
+
// head -c 20`) otherwise crashes with a raw EPIPE stack after the consumer
|
|
13552
|
+
// closes the pipe; a closed pipe is normal shell behavior, not an error.
|
|
13553
|
+
process.stdout.on("error", (error) => {
|
|
13554
|
+
if (error.code === "EPIPE")
|
|
13555
|
+
process.exit(0);
|
|
13556
|
+
process.stderr.write(`stdout error: ${error.message}\n`);
|
|
13557
|
+
process.exit(1);
|
|
13558
|
+
});
|
|
13195
13559
|
await createProgram().parseAsync(process.argv);
|