@oxygen-agent/cli 1.1011.493 → 1.1011.607
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.js +7 -0
- package/dist/crm-file-read-commands.d.ts +6 -0
- package/dist/crm-file-read-commands.js +16 -0
- package/dist/feed-read-flags.d.ts +3 -0
- package/dist/feed-read-flags.js +9 -0
- package/dist/functions-commands.js +2 -1
- package/dist/help.js +18 -5
- package/dist/index.js +168 -107
- package/dist/sender-limits-flags.js +6 -4
- package/dist/settings-commands.js +11 -11
- package/dist/whatsapp-account-flags.d.ts +4 -0
- package/dist/whatsapp-account-flags.js +18 -0
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +2 -2
- package/node_modules/@oxygen/shared/dist/billing.d.ts +41 -0
- package/node_modules/@oxygen/shared/dist/billing.js +56 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +21 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +254 -21
- package/node_modules/@oxygen/shared/dist/contracts/billing-owner-read.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/contracts/billing-owner-read.js +7 -0
- package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +4 -2
- package/node_modules/@oxygen/shared/dist/contracts/copilot-sessions.d.ts +11 -0
- package/node_modules/@oxygen/shared/dist/contracts/copilot-sessions.js +7 -0
- package/node_modules/@oxygen/shared/dist/contracts/crm-file-bytes.d.ts +33 -0
- package/node_modules/@oxygen/shared/dist/contracts/crm-file-bytes.js +12 -0
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +6 -3
- package/node_modules/@oxygen/shared/dist/contracts/function-library-read.d.ts +14 -0
- package/node_modules/@oxygen/shared/dist/contracts/function-library-read.js +16 -0
- package/node_modules/@oxygen/shared/dist/contracts/inbox-drafts-read.d.ts +45 -0
- package/node_modules/@oxygen/shared/dist/contracts/inbox-drafts-read.js +21 -0
- package/node_modules/@oxygen/shared/dist/contracts/limits-read.d.ts +29 -0
- package/node_modules/@oxygen/shared/dist/contracts/limits-read.js +121 -0
- package/node_modules/@oxygen/shared/dist/contracts/linkedin-invites-list.js +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/pipedrive-catalog.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/contracts/pipedrive-catalog.js +29 -0
- package/node_modules/@oxygen/shared/dist/contracts/publishing-reads.d.ts +181 -0
- package/node_modules/@oxygen/shared/dist/contracts/publishing-reads.js +62 -0
- package/node_modules/@oxygen/shared/dist/contracts/sender-limits.js +7 -5
- package/node_modules/@oxygen/shared/dist/contracts/sequence-list.d.ts +3 -0
- package/node_modules/@oxygen/shared/dist/contracts/sequence-list.js +8 -0
- package/node_modules/@oxygen/shared/dist/contracts/stored-read-window.d.ts +17 -0
- package/node_modules/@oxygen/shared/dist/contracts/stored-read-window.js +8 -0
- package/node_modules/@oxygen/shared/dist/contracts/table-feed-read.d.ts +67 -0
- package/node_modules/@oxygen/shared/dist/contracts/table-feed-read.js +19 -0
- package/node_modules/@oxygen/shared/dist/contracts/table-row-read.d.ts +30 -0
- package/node_modules/@oxygen/shared/dist/contracts/table-row-read.js +9 -0
- package/node_modules/@oxygen/shared/dist/contracts/whatsapp-reads.d.ts +111 -0
- package/node_modules/@oxygen/shared/dist/contracts/whatsapp-reads.js +33 -0
- package/node_modules/@oxygen/shared/dist/contracts/workspace-pages.d.ts +6 -5
- package/node_modules/@oxygen/shared/dist/contracts/workspace-pages.js +5 -5
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.d.ts +4 -1
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.generated.d.ts +17 -0
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.generated.js +26 -0
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.js +2 -32
- package/node_modules/@oxygen/shared/dist/feature-gates.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/feature-gates.js +6 -0
- package/node_modules/@oxygen/shared/dist/first-month-offer-window.d.ts +34 -0
- package/node_modules/@oxygen/shared/dist/first-month-offer-window.js +41 -0
- package/node_modules/@oxygen/shared/dist/first-month-offer.d.ts +42 -0
- package/node_modules/@oxygen/shared/dist/first-month-offer.js +56 -0
- package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +32 -1
- package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +68 -4
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.d.ts +6 -2
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +6 -1
- package/node_modules/@oxygen/shared/dist/sql-error.js +13 -2
- package/node_modules/@oxygen/shared/dist/stripe-price-catalog.d.ts +3 -0
- package/node_modules/@oxygen/shared/dist/stripe-price-catalog.js +17 -1
- 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/workspace-file-storage.d.ts +11 -1
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +29 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,9 +2,17 @@ import { knowledgeIndexReadContract, knowledgePageReadContract } from "@oxygen/s
|
|
|
2
2
|
import { DATABASE_ATTACH_CONTRACT, DATABASE_MIGRATE_CONTRACT, DATABASE_QUERY_CONTRACT } from "@oxygen/shared/contracts/database";
|
|
3
3
|
import { DATABASE_QUERY_FLAG_OVERLAY } from "./database-query-flags.js";
|
|
4
4
|
import { LINKEDIN_SENT_INVITES_QUERY_CONTRACT } from "@oxygen/shared/contracts/linkedin-invites-list";
|
|
5
|
+
import { PIPEDRIVE_CATALOG_QUERY_CONTRACT, pipedriveCatalogQueryParams } from "@oxygen/shared/contracts/pipedrive-catalog";
|
|
6
|
+
import { registerCrmFileReadCommands } from "./crm-file-read-commands.js";
|
|
7
|
+
import { WHATSAPP_ACCOUNTS_READ_CONTRACT } from "@oxygen/shared/contracts/whatsapp-reads";
|
|
8
|
+
import { WHATSAPP_ACCOUNT_FLAGS, whatsappAccountQuery } from "./whatsapp-account-flags.js";
|
|
9
|
+
import { BILLING_OWNER_READ_CONTRACT } from "@oxygen/shared/contracts/billing-owner-read";
|
|
10
|
+
import { publishingAmplificationListQueryContract, publishingAmplificationActionsQueryContract, publishingNotesQueryContract, publishingRevisionsQueryContract, publishingAnalyticsQueryContract, publishingReadQueryParams, } from "@oxygen/shared/contracts/publishing-reads";
|
|
11
|
+
import { COPILOT_SESSIONS_LIST_CONTRACT } from "@oxygen/shared/contracts/copilot-sessions";
|
|
5
12
|
import { registerCrmSupportFlags, readCrmNoteEditFields, readCrmPhotoFields, readCrmFileLabel, buildCrmObjectsPath, buildCrmConnectionsPath } from "./crm-support-flags.js";
|
|
6
13
|
import { addToolCatalogSearchWindowFlag, readToolCatalogSearchWindow } from "./tool-catalog-search-window.js";
|
|
7
14
|
import { createToolCatalogDetailCommand } from "./tool-catalog-detail-command.js";
|
|
15
|
+
import { LIMITS_READ_QUERY_CONTRACT, limitsReadQueryIssue } from "@oxygen/shared/contracts/limits-read";
|
|
8
16
|
import { execFileSync } from "node:child_process";
|
|
9
17
|
import { createHash, randomUUID } from "node:crypto";
|
|
10
18
|
import { closeSync, createReadStream, createWriteStream, existsSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
@@ -35,9 +43,12 @@ import { SEQUENCE_LIFECYCLES, sequenceDisplayStatus } from "@oxygen/shared/seque
|
|
|
35
43
|
import { operationPath } from "@oxygen/shared/contracts/operations";
|
|
36
44
|
import { SENDER_LIMITS_SET_OPERATION } from "@oxygen/shared/contracts/sender-limits";
|
|
37
45
|
import { addContractFlags, readContractFlags } from "./contract-flags.js";
|
|
46
|
+
import { addFeedReadFlags, feedReadWindowParams } from "./feed-read-flags.js";
|
|
38
47
|
import { TABLE_QUERY_GRID_CONTRACT } from "@oxygen/shared/contracts/table-query-grid";
|
|
39
48
|
import { INTEGRATION_CONNECT_ACCOUNT_CONTRACT } from "@oxygen/shared/contracts/integration-connect";
|
|
40
49
|
import { CRM_ENRICHMENT_READ_CONTRACT } from "@oxygen/shared/contracts/crm-enrichment-read";
|
|
50
|
+
import { INBOX_DRAFTS_READ_CONTRACT } from "@oxygen/shared/contracts/inbox-drafts-read";
|
|
51
|
+
import { SEQUENCE_LIST_PROJECTION_CONTRACT } from "@oxygen/shared/contracts/sequence-list";
|
|
41
52
|
import { buildSenderLimitsBody, parseWorkingDaysOption, SENDER_LIMITS_FLAG_OVERLAY } from "./sender-limits-flags.js";
|
|
42
53
|
import { readColumnDecisionFlags } from "./column-decision-options.js";
|
|
43
54
|
import { MANAGED_DATA_SUPPLIERS } from "@oxygen/shared/data-suppliers";
|
|
@@ -2964,6 +2975,13 @@ function buildPublishingAnalyticsPath(base, params) {
|
|
|
2964
2975
|
const suffix = [query.toString(), ...lists].filter(Boolean).join("&");
|
|
2965
2976
|
return suffix ? `${base}?${suffix}` : base;
|
|
2966
2977
|
}
|
|
2978
|
+
function publishingReadQueryPath(path, contract, options) {
|
|
2979
|
+
const [base = path, existing] = path.split("?", 2);
|
|
2980
|
+
const query = new URLSearchParams(existing);
|
|
2981
|
+
for (const [key, value] of publishingReadQueryParams(contract, readContractFlags(options, contract)))
|
|
2982
|
+
query.set(key, value);
|
|
2983
|
+
return query.size ? `${base}?${query}` : base;
|
|
2984
|
+
}
|
|
2967
2985
|
const PUBLISHING_ANALYTICS_ACCOUNT_HELP = "An account key from the summary's accounts list, or a LinkedIn sender id from `oxygen senders list`. Repeatable.";
|
|
2968
2986
|
/** Repeated and/or comma-separated `--account` values, trimmed and de-duplicated. */
|
|
2969
2987
|
function readPublishingAnalyticsAccounts(values) {
|
|
@@ -3953,6 +3971,7 @@ export function createProgram() {
|
|
|
3953
3971
|
};
|
|
3954
3972
|
});
|
|
3955
3973
|
});
|
|
3974
|
+
const billingOwnerReadFlags = { organization: { flag: "expected-organization", valueName: "<organization>" } };
|
|
3956
3975
|
program
|
|
3957
3976
|
.command("orgs")
|
|
3958
3977
|
.description("Switch between the organizations (workspaces) you belong to, and share one paid plan across them. If this workspace has no plan of its own, `orgs billing-link` puts it on a plan you already pay for somewhere else instead of buying a second subscription.")
|
|
@@ -3976,11 +3995,15 @@ export function createProgram() {
|
|
|
3976
3995
|
.action(async (organization, options) => {
|
|
3977
3996
|
await handleOrgUseAction(organization, options, "orgs select");
|
|
3978
3997
|
}))
|
|
3979
|
-
.addCommand(new Command("billing-owners")
|
|
3998
|
+
.addCommand(addContractFlags(new Command("billing-owners"), BILLING_OWNER_READ_CONTRACT, billingOwnerReadFlags)
|
|
3980
3999
|
.description("List the organizations that could pay for this workspace: which of the organizations you belong to can cover another workspace's credits, and for each of the rest, why it cannot. Read-only, 0 Oxygen credits.")
|
|
3981
4000
|
.option("--json", "Print a JSON envelope.")
|
|
3982
4001
|
.action(async (options) => {
|
|
3983
|
-
await handleAsyncAction("orgs billing-owners", options, () =>
|
|
4002
|
+
await handleAsyncAction("orgs billing-owners", options, () => {
|
|
4003
|
+
const fields = readContractFlags(options, BILLING_OWNER_READ_CONTRACT, billingOwnerReadFlags);
|
|
4004
|
+
const query = new URLSearchParams(Object.entries(fields).map(([key, value]) => [key, String(value)]));
|
|
4005
|
+
return requestOxygen(`/api/cli/orgs/billing-owners${query.size ? `?${query}` : ""}`);
|
|
4006
|
+
});
|
|
3984
4007
|
}))
|
|
3985
4008
|
.addCommand(new Command("billing-link")
|
|
3986
4009
|
.description("Cover a workspace with a plan you already pay for in another organization (one plan, many workspaces) instead of buying a second subscription. Omit --owner when exactly one of your organizations can pay and Oxygen will use it; `orgs billing-owners` lists them all. You must be an admin of BOTH organizations, the workspace being linked must not hold a live subscription of its own, and an org-bound key (oxy_live_…) can only link the workspace it is bound to. Credit billing moves, workspace data access does not; plan gates and sending seats then resolve through the owner, so the linked workspace connects senders against the owner's seats and seats are bought there. `plan_tier` in the response is the plan the workspace now runs on; `free` means the owner holds no active paid plan, so connecting senders stays blocked. From the owner, `billing usage --group-by workspace` reports the credits each linked workspace spent.")
|
|
@@ -4995,12 +5018,12 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
4995
5018
|
body: buildPublishingBoostBody(postId, options),
|
|
4996
5019
|
}));
|
|
4997
5020
|
}))
|
|
4998
|
-
.addCommand(new Command("revisions")
|
|
4999
|
-
.description("List a post's content history:
|
|
5021
|
+
.addCommand(addContractFlags(new Command("revisions"), publishingRevisionsQueryContract)
|
|
5022
|
+
.description("List a post's content history: newest-first revisions with their number, title, text, author, and time.")
|
|
5000
5023
|
.argument("<post_id>", "Scheduled post id.")
|
|
5001
5024
|
.option("--json", "Print a JSON envelope.")
|
|
5002
5025
|
.action(async (postId, options) => {
|
|
5003
|
-
await handleAsyncAction("publishing posts revisions", options, () => requestOxygen(`/api/cli/publishing/posts/${encodeURIComponent(postId)}/revisions
|
|
5026
|
+
await handleAsyncAction("publishing posts revisions", options, () => requestOxygen(publishingReadQueryPath(`/api/cli/publishing/posts/${encodeURIComponent(postId)}/revisions`, publishingRevisionsQueryContract, options)));
|
|
5004
5027
|
}))
|
|
5005
5028
|
.addCommand(new Command("restore")
|
|
5006
5029
|
.description("Roll a post's TEXT back to an earlier revision (see `publishing posts revisions`). The current text is snapshotted as a new revision first, so a restore is itself undoable. Refused once the post is publishing, published, or canceled — to bring a canceled post back, use `publishing posts uncancel`.")
|
|
@@ -5016,12 +5039,12 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
5016
5039
|
return requestOxygen(`/api/cli/publishing/posts/${encodeURIComponent(postId)}/revisions/${revision}/restore`, { method: "POST" });
|
|
5017
5040
|
});
|
|
5018
5041
|
}))
|
|
5019
|
-
.addCommand(new Command("notes")
|
|
5042
|
+
.addCommand(addContractFlags(new Command("notes"), publishingNotesQueryContract)
|
|
5020
5043
|
.description("List the internal review notes on a post. Notes are workspace-only — they are never published.")
|
|
5021
5044
|
.argument("<post_id>", "Scheduled post id.")
|
|
5022
5045
|
.option("--json", "Print a JSON envelope.")
|
|
5023
5046
|
.action(async (postId, options) => {
|
|
5024
|
-
await handleAsyncAction("publishing posts notes", options, () => requestOxygen(`/api/cli/publishing/posts/${encodeURIComponent(postId)}/comments
|
|
5047
|
+
await handleAsyncAction("publishing posts notes", options, () => requestOxygen(publishingReadQueryPath(`/api/cli/publishing/posts/${encodeURIComponent(postId)}/comments`, publishingNotesQueryContract, options)));
|
|
5025
5048
|
}))
|
|
5026
5049
|
.addCommand(new Command("note")
|
|
5027
5050
|
.description("Leave an internal review note on a post, or reply to one with --parent. Never published.")
|
|
@@ -5305,26 +5328,26 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
5305
5328
|
accounts: readPublishingAnalyticsAccounts(options.account),
|
|
5306
5329
|
})));
|
|
5307
5330
|
}))
|
|
5308
|
-
.addCommand(new Command("post")
|
|
5331
|
+
.addCommand(addContractFlags(new Command("post"), publishingAnalyticsQueryContract)
|
|
5309
5332
|
.description("One post's stored daily metric series, totals, growth curve, 24h/7d benchmarks against the workspace median, and earned-media value. EMV is impressions-derived; absent impressions return a reason instead of a made-up number. Read-only: no provider call, 0 credits.")
|
|
5310
5333
|
.argument("<post_id>", "Scheduled post id.")
|
|
5311
5334
|
.option("--since <date>", "ISO date to start the series from.")
|
|
5312
5335
|
.option("--json", "Print a JSON envelope.")
|
|
5313
5336
|
.action(async (postId, options) => {
|
|
5314
|
-
await handleAsyncAction("publishing analytics post", options, () => requestOxygen(buildPublishingAnalyticsPath(`/api/cli/publishing/analytics/posts/${encodeURIComponent(postId)}`, { since: readOption(options.since) })));
|
|
5337
|
+
await handleAsyncAction("publishing analytics post", options, () => requestOxygen(publishingReadQueryPath(buildPublishingAnalyticsPath(`/api/cli/publishing/analytics/posts/${encodeURIComponent(postId)}`, { since: readOption(options.since) }), publishingAnalyticsQueryContract, options)));
|
|
5315
5338
|
}))
|
|
5316
|
-
.addCommand(new Command("account")
|
|
5339
|
+
.addCommand(addContractFlags(new Command("account"), publishingAnalyticsQueryContract)
|
|
5317
5340
|
.description("Stored per-account metric series (followers) for connected LinkedIn and X publishing accounts, read once a day in the background. Read-only: no provider call, 0 credits.")
|
|
5318
5341
|
.option("--provider <provider>", "Channel to read: linkedin or x. Omit for every connected channel.")
|
|
5319
5342
|
.option("--since <date>", "ISO date to start the series from.")
|
|
5320
5343
|
.option("--account <key>", PUBLISHING_ANALYTICS_ACCOUNT_HELP, collectRepeatable, [])
|
|
5321
5344
|
.option("--json", "Print a JSON envelope.")
|
|
5322
5345
|
.action(async (options) => {
|
|
5323
|
-
await handleAsyncAction("publishing analytics account", options, () => requestOxygen(buildPublishingAnalyticsPath("/api/cli/publishing/analytics/account", {
|
|
5346
|
+
await handleAsyncAction("publishing analytics account", options, () => requestOxygen(publishingReadQueryPath(buildPublishingAnalyticsPath("/api/cli/publishing/analytics/account", {
|
|
5324
5347
|
provider: readOption(options.provider),
|
|
5325
5348
|
since: readOption(options.since),
|
|
5326
5349
|
accounts: readPublishingAnalyticsAccounts(options.account),
|
|
5327
|
-
})));
|
|
5350
|
+
}), publishingAnalyticsQueryContract, options)));
|
|
5328
5351
|
}))
|
|
5329
5352
|
.addCommand(new Command("emv")
|
|
5330
5353
|
.description("Set the workspace CPM used for earned-media value, per channel. EMV stays null on any channel whose provider does not expose impressions.")
|
|
@@ -5352,11 +5375,11 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
5352
5375
|
}))
|
|
5353
5376
|
.addCommand(new Command("amplification")
|
|
5354
5377
|
.description("Standing auto-engagement policies: the sender accounts you name react to and comment on every post in scope, paced and capped. Default OFF — a new policy is created disabled, and arming it is a separate, deliberate call.")
|
|
5355
|
-
.addCommand(new Command("list")
|
|
5378
|
+
.addCommand(addContractFlags(new Command("list"), publishingAmplificationListQueryContract)
|
|
5356
5379
|
.description("List amplification policies with their scope, caps, and enabled state.")
|
|
5357
5380
|
.option("--json", "Print a JSON envelope.")
|
|
5358
5381
|
.action(async (options) => {
|
|
5359
|
-
await handleAsyncAction("publishing amplification list", options, () => requestOxygen("/api/cli/publishing/amplification"));
|
|
5382
|
+
await handleAsyncAction("publishing amplification list", options, () => requestOxygen(publishingReadQueryPath("/api/cli/publishing/amplification", publishingAmplificationListQueryContract, options)));
|
|
5360
5383
|
}))
|
|
5361
5384
|
.addCommand(new Command("create")
|
|
5362
5385
|
.description("Create an amplification policy. It arms REAL public engagement from other people's connected accounts and, once enabled, draws credits each cycle (creating it spends nothing), so it is refused (exit 7) without --approved, --max-credits and --acknowledge-risk — that grant is recorded with who/when/what scope. The policy is created DISABLED: run `amplification enable` when you actually want it to run.")
|
|
@@ -5449,13 +5472,13 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
5449
5472
|
method: "DELETE",
|
|
5450
5473
|
}));
|
|
5451
5474
|
}))
|
|
5452
|
-
.addCommand(new Command("actions")
|
|
5475
|
+
.addCommand(addContractFlags(new Command("actions"), publishingAmplificationActionsQueryContract)
|
|
5453
5476
|
.description("The policy's action ledger: what each participant account did (or is about to do) on which post, with status and cost.")
|
|
5454
5477
|
.argument("<policy_id>", "Amplification policy id.")
|
|
5455
5478
|
.option("--status <status>", "Filter by planned, dispatched, failed, or skipped.")
|
|
5456
5479
|
.option("--json", "Print a JSON envelope.")
|
|
5457
5480
|
.action(async (policyId, options) => {
|
|
5458
|
-
await handleAsyncAction("publishing amplification actions", options, () => requestOxygen(buildPublishingAnalyticsPath(`/api/cli/publishing/amplification/${encodeURIComponent(policyId)}/actions`, { status: readOption(options.status) })));
|
|
5481
|
+
await handleAsyncAction("publishing amplification actions", options, () => requestOxygen(publishingReadQueryPath(buildPublishingAnalyticsPath(`/api/cli/publishing/amplification/${encodeURIComponent(policyId)}/actions`, { status: readOption(options.status) }), publishingAmplificationActionsQueryContract, options)));
|
|
5459
5482
|
})))
|
|
5460
5483
|
.addCommand(new Command("ideas")
|
|
5461
5484
|
.description("The content backlog: ideas parked for later, with no publish time and no AI spend.")
|
|
@@ -6796,16 +6819,16 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
6796
6819
|
})))
|
|
6797
6820
|
.addCommand(new Command("pipedrive")
|
|
6798
6821
|
.description("Import a Pipedrive saved people filter into a new Oxygen table, optionally suppressing a Pipedrive do-not-contact filter first. Connect Pipedrive with an API token first: `oxygen integrations connect pipedrive --api-key <token>`.")
|
|
6799
|
-
.addCommand(new Command("filters")
|
|
6800
|
-
.description("
|
|
6801
|
-
|
|
6822
|
+
.addCommand(addContractFlags(new Command("filters")
|
|
6823
|
+
.description("Read connected Pipedrive saved filters and custom-field definitions; no people imported. Four BYOK fees may apply. Use --approved with --max-credits to authorize this read; refusal returns the required cap without provider calls."), PIPEDRIVE_CATALOG_QUERY_CONTRACT, {
|
|
6824
|
+
query: { valueName: "<text>" },
|
|
6825
|
+
approved: { noNegate: true },
|
|
6826
|
+
max_credits: { valueName: "<n>" },
|
|
6827
|
+
})
|
|
6802
6828
|
.option("--json", "Print a JSON envelope.")
|
|
6803
6829
|
.action(async (options) => {
|
|
6804
6830
|
await handleAsyncAction("tables pipedrive filters", options, () => {
|
|
6805
|
-
const params =
|
|
6806
|
-
const query = readOption(options.query);
|
|
6807
|
-
if (query)
|
|
6808
|
-
params.set("query", query);
|
|
6831
|
+
const params = pipedriveCatalogQueryParams(readContractFlags(options, PIPEDRIVE_CATALOG_QUERY_CONTRACT, { approved: { noNegate: true } }));
|
|
6809
6832
|
const suffix = params.toString();
|
|
6810
6833
|
return requestOxygen(`/api/cli/tables/pipedrive-import${suffix ? `?${suffix}` : ""}`);
|
|
6811
6834
|
});
|
|
@@ -7116,11 +7139,11 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
7116
7139
|
});
|
|
7117
7140
|
}))
|
|
7118
7141
|
.addCommand(addContractFlags(new Command("query"), TABLE_QUERY_GRID_CONTRACT, {
|
|
7119
|
-
format: { valueName: "<format>", help: "
|
|
7142
|
+
format: { valueName: "<format>", help: "Grid cell previews/state: default 50, cap 200, optional stats/fill/window. Omit for raw filters, fields, search, sorts or offsets." },
|
|
7120
7143
|
})
|
|
7121
|
-
.description("
|
|
7144
|
+
.description("Read raw Table rows or --format grid cell previews/state with grid.nextCursor, in a JSON envelope. Use tables preview --summary-only for fill rates; tables export <table> --format table renders raw rows. Grid supports stats/fill/window options and refuses raw selectors.")
|
|
7122
7145
|
.argument("<table>", "Table id or slug.")
|
|
7123
|
-
.option("--limit <n>", "
|
|
7146
|
+
.option("--limit <n>", "Raw: default 100, native cap 2000. Grid: default 50, cap 200.")
|
|
7124
7147
|
.option("--cursor <cursor>", "Pagination cursor returned by a previous query. Walks forward one page at a time; mutually exclusive with --offset.")
|
|
7125
7148
|
.option("--offset <n>", "Skip this many rows and start there — jump straight to a row without paging to it. Mutually exclusive with --cursor.")
|
|
7126
7149
|
.option("--fields <columns>", "Comma-separated column keys or ids to include.")
|
|
@@ -7929,7 +7952,7 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
7929
7952
|
return requestOxygen(`/api/cli/tables/auto-run?${params.toString()}`);
|
|
7930
7953
|
})))
|
|
7931
7954
|
.addCommand(new Command("set")
|
|
7932
|
-
.description("Enable a standing auto-run: automatically queue the given columns for rows written to this table (imports, inserts, upserts). A queued cell that already has a value is skipped unless --force, so an upsert that touches an existing, filled row is not charged again.
|
|
7955
|
+
.description("Enable a standing auto-run: automatically queue the given columns for rows written to this table (imports, inserts, upserts), including workflow audience upserts and own-follower capture. A queued cell that already has a value is skipped unless --force, so an upsert that touches an existing, filled row is not charged again. Profile viewers use their separate `viewers enrichment --on` subscription. Default off.")
|
|
7933
7956
|
.argument("<table>", "Table id or slug.")
|
|
7934
7957
|
.requiredOption("--columns <csv>", "Comma-separated column keys to auto-run on newly written rows (tool, AI, formula, enrichment, bind, or lookup columns).")
|
|
7935
7958
|
.option("--sources <csv>", "Which write sources trigger the auto-run: any of import, insert, upsert, send. Defaults to all when omitted.")
|
|
@@ -8040,23 +8063,23 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
8040
8063
|
program
|
|
8041
8064
|
.command("feeds")
|
|
8042
8065
|
.description("Live tables: bind a scheduled provider harvest (a FEED) to a table so it keeps filling itself on a cadence, then inspect, pause, resume, run, or unbind it. A feed is part of Tables, not a new primitive — `oxygen tables webhook` is the inbound (push) half of the same ledger.")
|
|
8043
|
-
.addCommand(new Command("list")
|
|
8044
|
-
.description("List table feeds for the workspace, newest first. Read-only.")
|
|
8066
|
+
.addCommand(addFeedReadFlags(new Command("list")
|
|
8067
|
+
.description("List table feeds for the workspace, newest first. Optional summary/full windows resume with next-offset; exact settings use feeds get --projection value. Read-only.")
|
|
8045
8068
|
.argument("[table]", "Optional table id or slug to filter by.")
|
|
8046
8069
|
.option("--table <table>", "Table id or slug. Equivalent to the positional argument.")
|
|
8047
8070
|
.option("--transport <transport>", "pull (scheduled provider harvest) or push (inbound webhook).")
|
|
8048
8071
|
.option("--status <status>", "Filter by active or disabled.")
|
|
8049
8072
|
.option("--state <state>", "Filter by run state: idle, running, error, or exhausted.")
|
|
8050
|
-
.option("--limit <n>", "Maximum feeds to return. Defaults to
|
|
8073
|
+
.option("--limit <n>", "Maximum feeds to return (1-200). Defaults to 50.")
|
|
8051
8074
|
.option("--include-cursor", "Include the opaque provider page token in the cursor block.")
|
|
8052
8075
|
.option("--json", "Print a JSON envelope.")
|
|
8053
|
-
.action((table, options) => handleAsyncAction("feeds list", options, () => requestOxygen(feedsListPath(options, table)))))
|
|
8054
|
-
.addCommand(new Command("get")
|
|
8055
|
-
.description("Get one table feed
|
|
8076
|
+
.action((table, options) => handleAsyncAction("feeds list", options, () => requestOxygen(feedsListPath(options, table))))))
|
|
8077
|
+
.addCommand(addFeedReadFlags(new Command("get")
|
|
8078
|
+
.description("Get one table feed. Optional summary or lossless value JSON windows recover every public setting; resume value-cursor with the same feed/path/include-cursor. Read-only.")
|
|
8056
8079
|
.argument("<feed>", "Feed id or endpoint id.")
|
|
8057
8080
|
.option("--include-cursor", "Include the opaque provider page token in the cursor block.")
|
|
8058
8081
|
.option("--json", "Print a JSON envelope.")
|
|
8059
|
-
.action((feed, options) => handleAsyncAction("feeds get", options, () => requestOxygen(feedsGetPath(feed, options)))))
|
|
8082
|
+
.action((feed, options) => handleAsyncAction("feeds get", options, () => requestOxygen(feedsGetPath(feed, options)))), true))
|
|
8060
8083
|
.addCommand(new Command("bind")
|
|
8061
8084
|
.description("Bind a feed to a table: a scheduled provider harvest (pull), with --schema-json a typed-or-rejected push feed, or with --source rb2b the web-intent feed landing RB2B website-visit reveals as rows. A pull feed freezes the provider request into an existing table, then optionally arms a cadence so the table refills itself; arming a cadence on a paid provider is a STANDING spend grant — it requires --every, --max-credits, and --approved together, and without --every the feed is stored disarmed until you set one. A push feed is free: it returns a webhook URL and a secret shown once, refuses a delivery that does not match the schema with HTTP 422 (recorded in `oxygen feeds deliveries`, nothing written), and can create its table from the schema with --create <name>. `--source rb2b` is the web intent feed: OXYGEN already receives your RB2B website-visit reveals and records each one as a website_visit Signal, and this feed ALSO lands them as rows you can enrich, score, and sequence. It has no address, no schema, and no cadence to configure — the reveal shape is fixed — so binding is free and each reveal costs 0 credits on arrival. If RB2B is not connected to this workspace yet, rows only start once it is; there is no self-serve connect step yet.")
|
|
8062
8085
|
.argument("[table]", "Table id or slug that receives the rows. Required for a pull feed; a push or web-intent feed takes it or --create <name>.")
|
|
@@ -9451,11 +9474,13 @@ Examples:
|
|
|
9451
9474
|
.command("recipes")
|
|
9452
9475
|
.description("Business-case GTM playbooks: proven plays with prerequisites, credit posture, and approval gates spelled out. Whole-motion operator playbooks (TAM sourcing, LinkedIn content strategy, inbound-led outbound, signal-based outbound) are the oxygen-playbooks skill: oxygen skills get oxygen-playbooks --json.")
|
|
9453
9476
|
.addCommand(new Command("list")
|
|
9454
|
-
.description("
|
|
9455
|
-
.argument("[query]", "
|
|
9477
|
+
.description("Find a GTM play for a goal: recipes ranked by relevance to your words; --view summary for a compact index. Filter by business-case category, journey stage, or audience.")
|
|
9478
|
+
.argument("[query]", "Your goal in plain words, e.g. \"more pipeline from linkedin\" (ranked against title, trigger phrases, tags, and business case).")
|
|
9456
9479
|
.option("--category <category>", "Business-case category (e.g. start-here, pipeline-from-zero).")
|
|
9457
9480
|
.option("--stage <stage>", "Journey stage: day-1, day-7, day-30, or ongoing.")
|
|
9458
9481
|
.option("--audience <audience>", "founder, gtm-operator, or agency-operator.")
|
|
9482
|
+
.option("--view <view>", "summary (compact index to choose a play from) or full (default: every field).")
|
|
9483
|
+
.option("--limit <n>", "Return at most n recipes (1-50), after ranking.")
|
|
9459
9484
|
.option("--json", "Print a JSON envelope.")
|
|
9460
9485
|
.action(async (query, options) => {
|
|
9461
9486
|
await handleAsyncAction("recipes list", options, () => {
|
|
@@ -9471,6 +9496,12 @@ Examples:
|
|
|
9471
9496
|
const audience = readOption(options.audience);
|
|
9472
9497
|
if (audience)
|
|
9473
9498
|
params.set("audience", audience);
|
|
9499
|
+
const view = readOption(options.view);
|
|
9500
|
+
if (view)
|
|
9501
|
+
params.set("view", view);
|
|
9502
|
+
const limit = readOption(options.limit);
|
|
9503
|
+
if (limit)
|
|
9504
|
+
params.set("limit", limit);
|
|
9474
9505
|
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
9475
9506
|
return requestOxygen(`/api/cli/recipes${qs}`);
|
|
9476
9507
|
});
|
|
@@ -11180,11 +11211,22 @@ Examples:
|
|
|
11180
11211
|
.command("limits")
|
|
11181
11212
|
.description("Plan-tier limits, spend-safety defaults, per-channel sending limits, and storage capacity posture.")
|
|
11182
11213
|
.addHelpText("after", "\nRun `oxygen limits show --json` to see your current plan's API and webhook rates, Table and workspace storage headroom, and the next scheduled change. The $999 and $1,999 rows-per-Table increases remain held until capacity testing passes.\n")
|
|
11183
|
-
.addCommand(new Command("show")
|
|
11184
|
-
.description("Compare every plan size: price, monthly credits, volume bonus and price per 100 credits (`plan_bands`). Show your plan band and its limits beside every plan size's (API rate, webhook ingress, storage, import file size, columns per auto-run, signals feed, spend defaults incl. Agent and Copilot caps, fastest workflow schedule), the per-channel sending limits every plan shares (emails per mailbox, WhatsApp, LinkedIn, calls) and its workflow Code-step and run-attempt limits, plus Tables capacity usage naming the largest Table. Each plan size's storage says whether it is in force, scheduled to change on a date, or awaiting its rows-per-Table capacity test, with the values it moves to. `spend_safety_defaults.enterprise_agent_run_default` gives Enterprise the $1,999 band’s in-force Agent default with no contract override; `agent_run_approved_max_credits` is the maximum for an explicitly approved higher run. `spend_safety_defaults.applies_to` says what each spend default bounds: an unattended trigger delivery, a table auto-run batch, one Agent run, or the attended Copilot. `web_url` opens the same view in the web app (Settings → Plan limits), including any scheduled change. Read-only; spends no credits. Recovery: https://oxygen-agent.com/docs/safety/billing.")
|
|
11214
|
+
.addCommand(addContractFlags(new Command("show")
|
|
11215
|
+
.description("Compare every plan size: price, monthly credits, volume bonus and price per 100 credits (`plan_bands`). Show your plan band and its limits beside every plan size's (API rate, webhook ingress, storage, import file size, columns per auto-run, signals feed, spend defaults incl. Agent and Copilot caps, fastest workflow schedule), the per-channel sending limits every plan shares (emails per mailbox, WhatsApp, LinkedIn, calls) and its workflow Code-step and run-attempt limits, plus Tables capacity usage naming the largest Table. Each plan size's storage says whether it is in force, scheduled to change on a date, or awaiting its rows-per-Table capacity test, with the values it moves to. `spend_safety_defaults.enterprise_agent_run_default` gives Enterprise the $1,999 band’s in-force Agent default with no contract override; `agent_run_approved_max_credits` is the maximum for an explicitly approved higher run. `spend_safety_defaults.applies_to` says what each spend default bounds: an unattended trigger delivery, a table auto-run batch, one Agent run, or the attended Copilot. `web_url` opens the same view in the web app (Settings → Plan limits), including any scheduled change. Read-only; spends no credits. Recovery: https://oxygen-agent.com/docs/safety/billing."), LIMITS_READ_QUERY_CONTRACT)
|
|
11185
11216
|
.option("--json", "Print a JSON envelope.")
|
|
11186
11217
|
.action(async (options) => {
|
|
11187
|
-
await handleAsyncAction("limits show", options, () =>
|
|
11218
|
+
await handleAsyncAction("limits show", options, () => {
|
|
11219
|
+
const projection = readContractFlags(options, LIMITS_READ_QUERY_CONTRACT);
|
|
11220
|
+
const issue = limitsReadQueryIssue(projection);
|
|
11221
|
+
if (issue)
|
|
11222
|
+
throw new OxygenError("invalid_request", issue);
|
|
11223
|
+
const params = new URLSearchParams();
|
|
11224
|
+
for (const [key, value] of Object.entries(projection))
|
|
11225
|
+
if (value !== undefined)
|
|
11226
|
+
params.set(key, String(value));
|
|
11227
|
+
const suffix = params.toString();
|
|
11228
|
+
return requestOxygen(`/api/cli/limits${suffix ? `?${suffix}` : ""}`);
|
|
11229
|
+
});
|
|
11188
11230
|
}))
|
|
11189
11231
|
.addCommand(new Command("provider")
|
|
11190
11232
|
.description("Per-provider call-rate overrides and BYOK daily warning thresholds. With no subcommand, lists policies (same as `limits provider list`).")
|
|
@@ -11232,7 +11274,7 @@ Examples:
|
|
|
11232
11274
|
.command("billing")
|
|
11233
11275
|
.description("Plan and managed credit commands. Spend splits into FLEXIBLE (ad-hoc: enrichment, AI, automation — drawn from your free-to-spend balance) and FIXED recurring per-resource monthly commitments blocked out of it; see `billing commitments`. THREE CLOCKS, deliberately different: the CREDIT CYCLE that `billing allowance` reports against (your plan's monthly grant window); each resource's own COMMITMENT RENEWAL, anchored to the day you connected it, so `billing commitments --json` next_due_at rarely matches the cycle end; and your SUBSCRIPTION PERIOD in `billing balance` (annual plans span many credit cycles). A number from one clock will not reconcile against another. On a plan that pays for several workspaces, `billing usage --group-by workspace` totals what each one spent. To price a month of outreach before building it (leads, enrichment, sequence steps, senders), run `billing estimate`. Failed-payment grace, suspension, and recovery: https://oxygen-agent.com/docs/safety/billing.")
|
|
11234
11276
|
.addCommand(new Command("change")
|
|
11235
|
-
.description("Create
|
|
11277
|
+
.description("Create an approval link (`approval_url`) to start or change a plan; nothing changes until an organization admin confirms. Compare all six prices, credits and yearly terms with `billing balance` first. Use --interval month --term year for twelve discounted monthly payments, renewing for another year: the $99 plan costs $82.50 per month before tax, not $990 upfront. Credits arrive monthly after successful payment. On a workspace that has never had a plan, the Checkout for any monthly plan from $99 up carries the first-month offer, half its first month, while its 24-hour window is open (`offer`; `billing balance` lists every size under `first_month_offer.plans`): `amount_due_now` is then the discounted first month and `next_invoice_total` the regular price, both before tax. Creating that Checkout starts the window if nothing has started it yet. Upgrades during an annual commitment apply immediately with proration and keep its end date; downgrades and switching to a monthly commitment wait for the term end. Switching from a monthly commitment to yearly starts at the next monthly boundary. Existing prepaid yearly plans keep their cadence unless a new term is selected; transitions wait until their paid period ends. Omit interval and term to preserve the current contract. For an existing subscription, a yearly preview returns charges, start and term-end dates; nothing changes until separate confirmation.")
|
|
11236
11278
|
.requiredOption("--to <plan>",
|
|
11237
11279
|
// Derived from the catalog, never retyped: this help text is the only
|
|
11238
11280
|
// place a customer's agent learns which plans exist, and it named
|
|
@@ -11253,17 +11295,18 @@ Examples:
|
|
|
11253
11295
|
: "";
|
|
11254
11296
|
return `${key} ($${cents / 100}/mo${creditText})`;
|
|
11255
11297
|
}).join(", ")}.`)
|
|
11256
|
-
.option("--interval <interval>", "
|
|
11298
|
+
.option("--interval <interval>", "Payment cadence. Use month for new plans; choose --term year for a yearly commitment paid monthly. Omit to preserve an existing subscription's cadence, including prepaid yearly plans.")
|
|
11299
|
+
.option("--term <term>", "Commitment: month or year. Year means twelve discounted monthly payments, renewing annually. Downgrades wait for the term end; omit to preserve the current term.")
|
|
11257
11300
|
.option("--json", "Print a JSON envelope.")
|
|
11258
11301
|
.action(async (options) => {
|
|
11259
11302
|
await handleAsyncAction("billing change", options, () => requestOxygen("/api/cli/billing/change", {
|
|
11260
11303
|
method: "POST",
|
|
11261
11304
|
// Sent only when asked: the server keeps the subscriber's interval otherwise.
|
|
11262
|
-
body: { tier: options.to, ...(options.interval ? { interval: options.interval } : {}) },
|
|
11305
|
+
body: { tier: options.to, ...(options.interval ? { interval: options.interval } : {}), ...(options.term ? { term: options.term } : {}) },
|
|
11263
11306
|
}));
|
|
11264
11307
|
}))
|
|
11265
11308
|
.addCommand(new Command("balance")
|
|
11266
|
-
.description("
|
|
11309
|
+
.description("Compare all six subscription sizes in `available_plans`: monthly prices, yearly paid-monthly prices, credit allowances and savings. `annual_monthly_billing_available` says whether yearly paid monthly is offered; `annual_monthly_terms` explains the 12-month commitment, annual renewal, remaining payments after cancellation and plan-change timing. `first_month_offer` is the first-month offer this workspace can still claim: every size it applies to (`plans`, from `minimum_plan` up) with its first-month and regular price before tax, and `ends_at`, null until its 24-hour window starts. The window starts the first time the workspace meets the offer: the app showing it, signup from a browser that saw it on the website, or creating its Checkout with `billing change`. Also show the current plan, commitment, subscription entitlement, and managed credit balance: available and reserved credits, FIXED credits committed to recurring per-resource charges, and the FLEXIBLE free-to-spend remainder. `next_renewal` answers \"will I be charged?\": read `will_charge` first — true only when this workspace's own Stripe plan is going to charge its card, in which case `charge_at` and the subscription's recurring amount in money say when and roughly how much (the exact amount, with any promotion code or tax, is on the invoice); `period_ends_at` is the trial or period end either way, `cancellation_scheduled` says whether a cancellation is already set, and `stop_command` names the one command that changes it (`billing cancel`, undone by `billing resume`) or is null when nothing here can stop it (staff-invoiced, billed through another workspace, not active). `billing_interval` is payment cadence; `commitment` distinguishes yearly paid monthly from monthly and legacy prepaid yearly plans. Credits are granted monthly; `next_credit_grant_at` is the next grant. For a yearly commitment, cancellation stops annual renewal but remaining monthly payments continue until commitment.ends_at. `renewals_past_due` lists infrastructure renewals (managed-inbox domains, warm-ups) OXYGEN could NOT charge this month, what they cost, and the exact top-up that clears them — nothing is cancelled and billing retries automatically after a top-up. `warnings` also flags a recurring_shortfall when your connected infrastructure costs more per month than the plan grants. `partner_bonus` says whether this workspace earns the agency partner bonus (+15% credits a month for a published directory partner on $99 or above), how many credits, and why not when it does not; the bonus arrives as its own ledger line (`billing usage --category partner_bonus`), never inside plan.monthly_credits. A workspace linked to another organization (`orgs billing-link`) reads its billing at that owner: the balance is the owner's pool, `shared_billing` is true and `billing_owner_organization_id` names the owner. After failed-payment grace expires, mutations stop while read/export and billing recovery remain available. Recovery: https://oxygen-agent.com/billing. Policy: https://oxygen-agent.com/docs/safety/billing.")
|
|
11267
11310
|
.option("--json", "Print a JSON envelope.")
|
|
11268
11311
|
.action(async (options) => {
|
|
11269
11312
|
await handleAsyncAction("billing balance", options, () => requestOxygen("/api/cli/billing/balance"));
|
|
@@ -11457,7 +11500,7 @@ Examples:
|
|
|
11457
11500
|
}));
|
|
11458
11501
|
}))
|
|
11459
11502
|
.addCommand(new Command("cancel")
|
|
11460
|
-
.description("
|
|
11503
|
+
.description("Turn off subscription renewal. For a yearly commitment paid monthly, remaining monthly payments continue through commitment_ends_at; the plan stays usable until then. Other plans end at their paid period or trial end with no further subscription renewal charge. This command costs no Oxygen credits. Separately funded external resources keep renewing; preview `workspace infrastructure-exit` to end those too. Prints the end date; undo before then with billing resume.")
|
|
11461
11504
|
.option("--json", "Print a JSON envelope.")
|
|
11462
11505
|
.action(async (options) => {
|
|
11463
11506
|
await handleAsyncAction("billing cancel", options, () => requestOxygen("/api/cli/billing/cancel", {
|
|
@@ -11839,7 +11882,7 @@ Examples:
|
|
|
11839
11882
|
.option("--key <key>", "Scope key: category/provider/org id, or * for the default.", "*")
|
|
11840
11883
|
.requiredOption("--window <seconds>", "Window: 60, 3600, or 86400.")
|
|
11841
11884
|
.option("--limit-usd <usd>", "Ceiling in USD of OXYGEN's own provider cost.")
|
|
11842
|
-
.option("--limit-calls <n>", "Ceiling in call count
|
|
11885
|
+
.option("--limit-calls <n>", "Ceiling in call count: the signature loop detector, or the only ceiling that bounds a $0-priced provider.")
|
|
11843
11886
|
.option("--action <action>", "warn (meter + alert only) or block (enforce).", "warn")
|
|
11844
11887
|
.option("--notes <text>", "Why this ceiling is set where it is.")
|
|
11845
11888
|
.option("--json", "Print a JSON envelope.")
|
|
@@ -12439,11 +12482,15 @@ Examples:
|
|
|
12439
12482
|
return requestOxygen("/api/cli/copilot/sessions", { method: "POST", body });
|
|
12440
12483
|
});
|
|
12441
12484
|
}))
|
|
12442
|
-
.addCommand(new Command("list")
|
|
12443
|
-
.description("List
|
|
12444
|
-
.option("--json", "Print a JSON envelope.")
|
|
12485
|
+
.addCommand(addContractFlags(new Command("list")
|
|
12486
|
+
.description("List Workspace Copilot conversations by session status, newest first; this read starts no inference.")
|
|
12487
|
+
.option("--json", "Print a JSON envelope."), COPILOT_SESSIONS_LIST_CONTRACT)
|
|
12445
12488
|
.action(async (options) => {
|
|
12446
|
-
await handleAsyncAction("copilot list", options, () =>
|
|
12489
|
+
await handleAsyncAction("copilot list", options, () => {
|
|
12490
|
+
const selectors = readContractFlags(options, COPILOT_SESSIONS_LIST_CONTRACT);
|
|
12491
|
+
const query = new URLSearchParams(Object.entries(selectors).map(([key, value]) => [key, String(value)]));
|
|
12492
|
+
return requestOxygen(`/api/cli/copilot/sessions${query.size ? `?${query}` : ""}`);
|
|
12493
|
+
});
|
|
12447
12494
|
}))
|
|
12448
12495
|
.addCommand(new Command("get")
|
|
12449
12496
|
.description("Show one Workspace Copilot session: its state, turns, and event log. Use --after-seq to page through the event stream.")
|
|
@@ -13761,7 +13808,7 @@ Examples:
|
|
|
13761
13808
|
.description("View and adjust per-account daily action limits, working hours (the times LinkedIn actions may run) and the timezone they use.")
|
|
13762
13809
|
.option("--json", "Print a JSON envelope.")
|
|
13763
13810
|
.addCommand(new Command("get")
|
|
13764
|
-
.description("Show current limits, overrides, working hours (timezone, days, start, end), defaults, and
|
|
13811
|
+
.description("Show current limits, overrides, working hours (timezone, days, start, end), defaults, hard maximums, and the caution levels above which LinkedIn restriction risk rises, for an account. <id> accepts a sender account id, connection id, or Unipile account id.")
|
|
13765
13812
|
.argument("<id>", "Sender account id, connection id, or Unipile account id.")
|
|
13766
13813
|
.option("--json", "Print a JSON envelope.")
|
|
13767
13814
|
.action(async (id, options) => {
|
|
@@ -14193,7 +14240,7 @@ Examples:
|
|
|
14193
14240
|
});
|
|
14194
14241
|
})));
|
|
14195
14242
|
program.addCommand(new Command("engagement")
|
|
14196
|
-
.description("
|
|
14243
|
+
.description("For a LinkedIn audience campaign, find the matching play with `oxygen recipes list \"post engagers\" --view summary --json`: own, competitor, creator or keyword post engagers, company followers, or own followers. The Recipe and shared guide prepare qualified recipients and a disabled ongoing router. For one known post, read its engagers or harvest into a durable Table using its public URL or composite social_id from `oxygen posts get`; harvesting runs as a paced background drip. Existing intent captures are managed with `oxygen feeds list|pause|resume|run`; existing profile watchers use `oxygen tables watcher get`. To have teammates react to or comment on the workspace's own posts, use `oxygen publishing amplification --help`.")
|
|
14197
14244
|
.addCommand(new Command("engagers")
|
|
14198
14245
|
.description("Read one post's reactors and commenters right now as a de-duplicated people list ready to enroll. Pages both sources up to --max-pages (default 5, max 20) x 100 per page; `truncated: true` in the envelope means a cap stopped a source that still had more — raise --max-pages, or use `oxygen engagement harvest` for a post too big to read in one request. Nothing is sent and no credits are spent, but every page is one LinkedIn read against the sender account. --post is the composite social_id from `oxygen posts get` (NOT the activity URN).")
|
|
14199
14246
|
.requiredOption("--post <social_id>", "Composite post social_id from `oxygen posts get` (NOT the activity URN).")
|
|
@@ -14561,23 +14608,12 @@ Examples:
|
|
|
14561
14608
|
}))));
|
|
14562
14609
|
program.addCommand(new Command("whatsapp")
|
|
14563
14610
|
.description("Manage the org's connected WhatsApp accounts for Sequencer: list, connect, sync, inspect, disconnect, tune daily limits, and warm-send into an existing conversation. Cold outbound runs through a WhatsApp sequence (sequences create --channels whatsapp).")
|
|
14564
|
-
.addCommand(new Command("accounts")
|
|
14565
|
-
.description("
|
|
14566
|
-
.option("--status <status>", "Filter by account status: active, paused, disconnected, restricted, or credentials_required.")
|
|
14567
|
-
.option("--no-usage", "Skip today's per-account usage counts.")
|
|
14568
|
-
.option("--tag <tags>", "Comma-separated workspace tags — matches WhatsApp accounts carrying ANY of these tags (see `oxygen tags list`).")
|
|
14611
|
+
.addCommand(addContractFlags(new Command("accounts")
|
|
14612
|
+
.description("Read stored WhatsApp account health. Disconnected accounts are hidden by default; --include-disconnected includes retained accounts, --status selects one status. Presence is not send readiness. Use summary and exact account-id/value for long public metadata."), WHATSAPP_ACCOUNTS_READ_CONTRACT, WHATSAPP_ACCOUNT_FLAGS)
|
|
14569
14613
|
.option("--json", "Print a JSON envelope.")
|
|
14570
14614
|
.action(async (options) => {
|
|
14571
14615
|
await handleAsyncAction("whatsapp accounts", options, () => {
|
|
14572
|
-
const
|
|
14573
|
-
params.set("include_usage", options.usage === false ? "false" : "true");
|
|
14574
|
-
const status = readOption(options.status);
|
|
14575
|
-
if (status)
|
|
14576
|
-
params.set("status", status);
|
|
14577
|
-
const tags = splitCommaList(options.tag);
|
|
14578
|
-
if (tags.length > 0)
|
|
14579
|
-
params.set("tag", tags.join(","));
|
|
14580
|
-
const suffix = params.toString();
|
|
14616
|
+
const suffix = whatsappAccountQuery(options).toString();
|
|
14581
14617
|
return requestOxygen(`/api/cli/whatsapp/accounts${suffix ? `?${suffix}` : ""}`);
|
|
14582
14618
|
});
|
|
14583
14619
|
}))
|
|
@@ -15346,28 +15382,28 @@ Examples:
|
|
|
15346
15382
|
})))
|
|
15347
15383
|
.addCommand(new Command("drafts")
|
|
15348
15384
|
.description("The AI reply-agent draft queue (the approve-before-send review queue).")
|
|
15349
|
-
.addCommand(new Command("list")
|
|
15385
|
+
.addCommand(addContractFlags(new Command("list")
|
|
15350
15386
|
.description("List drafts awaiting review (queued + edited by default), across email + LinkedIn.")
|
|
15351
|
-
.option("--
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
|
|
15355
|
-
|
|
15387
|
+
.option("--json", "Print a JSON envelope."), INBOX_DRAFTS_READ_CONTRACT, {
|
|
15388
|
+
status: { valueName: "<keys>", help: "Comma-separated draft statuses (queued,edited,approved,sent,rejected).", parse: readOption },
|
|
15389
|
+
channel: { valueName: "<channel>", help: "Filter to one channel: email or linkedin.", parse: readOption },
|
|
15390
|
+
sequence_id: { valueName: "<id>", help: "Filter to one campaign (sequence) id (email drafts only).", parse: readOption },
|
|
15391
|
+
limit: { valueName: "<n>", help: "Maximum drafts to return.", parse: readOption },
|
|
15392
|
+
conversation_id: { valueName: "<id>", parse: readOption },
|
|
15393
|
+
draft_id: { valueName: "<id>", parse: readOption },
|
|
15394
|
+
})
|
|
15356
15395
|
.action(async (options) => {
|
|
15357
15396
|
await handleAsyncAction("inbox drafts list", options, () => {
|
|
15397
|
+
const query = readContractFlags(options, INBOX_DRAFTS_READ_CONTRACT, {
|
|
15398
|
+
status: { parse: readOption }, channel: { parse: readOption },
|
|
15399
|
+
sequence_id: { parse: readOption }, limit: { parse: readOption },
|
|
15400
|
+
conversation_id: { parse: readOption }, draft_id: { parse: readOption },
|
|
15401
|
+
});
|
|
15358
15402
|
const params = new URLSearchParams();
|
|
15359
|
-
const
|
|
15360
|
-
|
|
15361
|
-
|
|
15362
|
-
|
|
15363
|
-
if (channel)
|
|
15364
|
-
params.set("channel", channel);
|
|
15365
|
-
const sequenceId = readOption(options.sequenceId);
|
|
15366
|
-
if (sequenceId)
|
|
15367
|
-
params.set("sequence_id", sequenceId);
|
|
15368
|
-
const limit = readOption(options.limit);
|
|
15369
|
-
if (limit)
|
|
15370
|
-
params.set("limit", limit);
|
|
15403
|
+
for (const [key, value] of Object.entries(query)) {
|
|
15404
|
+
if (value !== undefined && value !== null)
|
|
15405
|
+
params.set(key, String(value));
|
|
15406
|
+
}
|
|
15371
15407
|
const suffix = params.toString();
|
|
15372
15408
|
return requestOxygen(`/api/cli/inbox/drafts${suffix ? `?${suffix}` : ""}`);
|
|
15373
15409
|
});
|
|
@@ -15556,15 +15592,15 @@ Examples:
|
|
|
15556
15592
|
});
|
|
15557
15593
|
})));
|
|
15558
15594
|
program.addCommand(new Command("sequences")
|
|
15559
|
-
.description("Multichannel outreach sequences: one enrollment per lead can span LinkedIn, email, WhatsApp, human call tasks, and connected-CRM tasks. LinkedIn steps dispatch natively
|
|
15560
|
-
.addCommand(new Command("list")
|
|
15595
|
+
.description("Multichannel outreach sequences: one enrollment per lead can span LinkedIn, email, WhatsApp, human call tasks, and connected-CRM tasks. LinkedIn steps dispatch natively within sender limits, with zero additional credits per action; connected-account monthly charges remain separate; email steps send natively or place/move/stop the lead in a bound Instantly campaign (BYOK); crm_task can exact-match/upsert/link CRM records and create a mapped external follow-up (HubSpot first), with replay-safe per-operation receipts. A reply always stops that lead, on every channel. To also pause the replier's colleagues, set `--company-reply-pause` on `create`/`update` (pick the reply categories); to pause or resume a company by hand or from a workflow (e.g. a meeting was booked), use `pause-company` / `resume-company` / `paused-companies`. A pause is resumable; a company do-not-contact entry (`suppressions`) is a permanent block.")
|
|
15596
|
+
.addCommand(addContractFlags(new Command("list")
|
|
15561
15597
|
.description("List sequences with persisted launch status kept separate from current operational state. Fleet counts report marked-active versus operationally-live (active with nonterminal work), and rows explain flowing, paced, paused, blocked, degraded, drained, or empty work without SQL. An active sequence whose every lead has finished shows drained (completed); leads enrolled into it later are contacted on its next dispatch tick. --stats joins each row's LIFETIME funnel.")
|
|
15562
15598
|
.option("--status <status>", "Filter by status: draft, active, paused, or archived.")
|
|
15563
15599
|
.addOption(new Option("--operational-state <state>", "Filter by current work state; fleet counts remain unfiltered: empty, drained, never_started (enrolled leads, never launched), blocked, degraded, flowing, paced, or paused (work waiting on a paused sequence).").choices(["empty", "drained", "never_started", "blocked", "degraded", "flowing", "paced", "paused"]))
|
|
15564
15600
|
.addOption(new Option("--lifecycle <lifecycle>", "Filter by the lifecycle a person reads; fleet counts remain unfiltered: draft, active, completed (active, every lead finished), paused, or archived.").choices([...SEQUENCE_LIFECYCLES]))
|
|
15565
15601
|
.addOption(new Option("--channel <channel>", "Only sequences carrying this channel.").choices(["email", "linkedin", "whatsapp"]))
|
|
15566
15602
|
.option("--tag <tag>", "Only sequences carrying this workspace tag (see `oxygen tags list`).")
|
|
15567
|
-
.option("--stats", "Attach lifetime stats per sequence (a stats pass per row — slower on large workspaces).")
|
|
15603
|
+
.option("--stats", "Attach lifetime stats per sequence (a stats pass per row — slower on large workspaces)."), SEQUENCE_LIST_PROJECTION_CONTRACT)
|
|
15568
15604
|
.option("--json", "Print a JSON envelope.")
|
|
15569
15605
|
.action(async (options) => {
|
|
15570
15606
|
await handleReadActionWithLens("sequences list", options, () => {
|
|
@@ -15586,6 +15622,10 @@ Examples:
|
|
|
15586
15622
|
params.set("tag", tag);
|
|
15587
15623
|
if (options.stats)
|
|
15588
15624
|
params.set("include", "stats");
|
|
15625
|
+
const projection = readContractFlags(options, SEQUENCE_LIST_PROJECTION_CONTRACT);
|
|
15626
|
+
const omit = typeof projection.omit === "string" ? readOption(projection.omit) : undefined;
|
|
15627
|
+
if (omit)
|
|
15628
|
+
params.set("omit", omit);
|
|
15589
15629
|
const suffix = params.toString();
|
|
15590
15630
|
return requestOxygen(`/api/cli/sequences${suffix ? `?${suffix}` : ""}`);
|
|
15591
15631
|
}, formatSequenceListHealth);
|
|
@@ -15844,7 +15884,7 @@ Examples:
|
|
|
15844
15884
|
.option("--channels <list>", "Comma-separated channels: linkedin,email,whatsapp,call,crm. Defaults to the channels the journey touches. crm_task currently supports HubSpot.")
|
|
15845
15885
|
.option("--whatsapp-cold-initiate", "WhatsApp: allow cold-initiating new chats (no prior conversation). Required to start a WhatsApp sequence live — WhatsApp via Unipile is unofficial WhatsApp Web, so cold-initiating is an explicit ban-risk opt-in.")
|
|
15846
15886
|
.option("--phone-column-key <key>", "WhatsApp: row_values key holding each lead's phone number (else falls back to phone/phone_number/mobile).")
|
|
15847
|
-
.option("--senders <ids>", "Comma-separated LinkedIn or WhatsApp sender account ids (or connection / Unipile ids; `
|
|
15887
|
+
.option("--senders <ids>", "Comma-separated LinkedIn or WhatsApp sender account ids (or connection / Unipile ids; `senders list` / `whatsapp accounts` list them). Optional at create; enroll and start require at least one when the journey has LinkedIn or WhatsApp steps (attach later with `sequences update --senders`).")
|
|
15848
15888
|
.option("--table <id>", "Source table id or slug whose rows supply {{column}} template values.")
|
|
15849
15889
|
.option("--from-table <id>", "Alias of --table: the source table id or slug this sequence draws its leads and {{column}} values from.")
|
|
15850
15890
|
.option("--enroll", "After creating the sequence, immediately enroll every row of the bound table (same as running `sequences enroll --from-table` next). Requires --table/--from-table. Enrolling spends no credits and sends nothing.")
|
|
@@ -15852,8 +15892,8 @@ Examples:
|
|
|
15852
15892
|
.option("--email-provider <provider>", "Omit for native email through connected Google/Microsoft mailboxes, including Microsoft SMTP transport. Set 'instantly' to bind an Instantly campaign with --email-connection and --email-definition-file. To push rows into a campaign you already run in Smartlead, lemlist, HeyReach or Instantly, use `oxygen sequences push-external`.")
|
|
15853
15893
|
.option("--email-connection <id>", "Instantly connection id for the email track. Defaults to the org's active Instantly connection.")
|
|
15854
15894
|
.option("--email-definition-file <path>", "Path to a JSON file with the email content spec (subjects/bodies/delays/subsequences) compiled to an Instantly campaign on start.")
|
|
15855
|
-
.option("--max-credits <n>", "
|
|
15856
|
-
.option("--max-live-sends <n>", "
|
|
15895
|
+
.option("--max-credits <n>", "Stored credit ceiling (0 or more); 0 is valid for a draft. Omit to leave the ceiling unset. Creating a draft sends nothing; live start has its own approval and recipient limits.")
|
|
15896
|
+
.option("--max-live-sends <n>", "Total live-send and CRM-write ceiling across channels, including LinkedIn invites/messages (positive integer). Required for live email/WhatsApp/CRM-task tracks; optional for LinkedIn.")
|
|
15857
15897
|
.option("--max-emails-per-mailbox-per-day <n>", "Per-mailbox daily email cap (positive integer) applied across the sending pool; also sets each mailbox's send pace (send window ÷ cap, catching up when a slot is missed). Daily capacity = this cap × sendable mailboxes; `sequences stats` reports it under daily_budget.")
|
|
15858
15898
|
.option("--schedule-timezone <tz>", "One sending schedule for every channel, the same as Launch's Sending schedule: IANA zone, e.g. Europe/Berlin. Give it with --schedule-days, --schedule-hours-start and --schedule-hours-end; it sets the email window and the LinkedIn/WhatsApp window together. Without it, email sends weekdays 09:00-17:00 UTC. To give one channel different hours, or to send email in each recipient's own timezone, run `sequences update` after creating (--send-window-file, --linkedin-*).")
|
|
15859
15899
|
.option("--schedule-days <days>", "Sending schedule days: mon-fri, mon,wed,fri, or ISO numbers 1-7 (1 = Monday).")
|
|
@@ -15895,7 +15935,7 @@ Examples:
|
|
|
15895
15935
|
const senders = readCsvOption(options.senders);
|
|
15896
15936
|
const tags = readCsvOption(options.tags);
|
|
15897
15937
|
const email = readCampaignEmailBinding(options);
|
|
15898
|
-
const maxCredits =
|
|
15938
|
+
const maxCredits = readCreditCeilingOrZero(options.maxCredits);
|
|
15899
15939
|
const maxLiveSends = readPositiveInt(options.maxLiveSends);
|
|
15900
15940
|
const settings = readSequenceSettings(options);
|
|
15901
15941
|
// `--from-table` is the phrase customers already use for enroll,
|
|
@@ -15966,14 +16006,14 @@ Examples:
|
|
|
15966
16006
|
.option("--phone-column-key <key>", "WhatsApp: row_values key holding each lead's phone number (else falls back to phone/phone_number/mobile).")
|
|
15967
16007
|
.option("--email-column-key <key>", "Native email: row_values key holding each lead's email (else falls back to email/email_address/work_email). Maps a non-English header (e.g. E-Mail).")
|
|
15968
16008
|
.option("--linkedin-url-column-key <key>", "LinkedIn: row_values key holding each lead's profile URL (else falls back to linkedin_url/linkedinUrl/linkedin/profile_url). \"\" clears it back to auto-detect.")
|
|
15969
|
-
.option("--senders <ids>", "Comma-separated LinkedIn or WhatsApp sender account ids (or connection / Unipile ids; `
|
|
16009
|
+
.option("--senders <ids>", "Comma-separated LinkedIn or WhatsApp sender account ids (or connection / Unipile ids; `senders list` / `whatsapp accounts` list them).")
|
|
15970
16010
|
.option("--source-table <idOrSlug>", "Bind the table this sequence enrolls leads from (id or slug). Only lands while the sequence has no source table yet (first-writer-wins); its rows become enrollable leads. Draft/paused only.")
|
|
15971
16011
|
.option("--email-provider <provider>", "Optional linked-campaign provider: 'instantly'. Omit to preserve the existing binding; --clear-email removes it on a draft. Native email needs no provider binding and sends through connected Google/Microsoft mailboxes, including Microsoft SMTP transport.")
|
|
15972
16012
|
.option("--email-connection <id>", "Instantly connection id for the email track.")
|
|
15973
16013
|
.option("--email-definition-file <path>", "Path to a JSON file with the email content spec (subjects/bodies/delays/subsequences).")
|
|
15974
16014
|
.option("--clear-email", "Remove the email binding from the sequence (draft only).")
|
|
15975
|
-
.option("--max-credits <n>", "
|
|
15976
|
-
.option("--max-live-sends <n>", "Draft-time
|
|
16015
|
+
.option("--max-credits <n>", "Stored draft credit ceiling (0 or more); 0 is valid. Changing a started sequence's cap requires its normal live-start approval.")
|
|
16016
|
+
.option("--max-live-sends <n>", "Draft-time total live-send and CRM-write ceiling across channels, including LinkedIn invites/messages (positive integer). After first start, change it through `sequences start`.")
|
|
15977
16017
|
.option("--max-emails-per-mailbox-per-day <n>", "Per-mailbox daily email cap (positive integer) applied across the sending pool; also sets each mailbox's send pace (send window ÷ cap, catching up when a slot is missed). Daily capacity = this cap × sendable mailboxes; `sequences stats` reports it under daily_budget.")
|
|
15978
16018
|
.option("--schedule-timezone <tz>", "One sending schedule for every channel, the same as Launch's Sending schedule: IANA zone, e.g. Europe/Berlin. Give it with --schedule-days, --schedule-hours-start and --schedule-hours-end; it sets the email window and the LinkedIn/WhatsApp window together. Use it instead of --send-window-file and the --linkedin-* flags. Email set to send in each recipient's own timezone keeps doing so, with this zone for leads that have none.")
|
|
15979
16019
|
.option("--schedule-days <days>", "Sending schedule days: mon-fri, mon,wed,fri, or ISO numbers 1-7 (1 = Monday).")
|
|
@@ -16042,7 +16082,7 @@ Examples:
|
|
|
16042
16082
|
if (options.linkedinUrlColumnKey !== undefined) {
|
|
16043
16083
|
body.linkedin_url_column_key = readOption(options.linkedinUrlColumnKey) ?? "";
|
|
16044
16084
|
}
|
|
16045
|
-
const maxCredits =
|
|
16085
|
+
const maxCredits = readCreditCeilingOrZero(options.maxCredits);
|
|
16046
16086
|
if (maxCredits !== undefined)
|
|
16047
16087
|
body.max_credits = maxCredits;
|
|
16048
16088
|
const maxLiveSends = readPositiveInt(options.maxLiveSends);
|
|
@@ -16178,13 +16218,13 @@ Examples:
|
|
|
16178
16218
|
.description("Preview or start a sequence. Without --approved this is the launch check and changes nothing: it returns launch_readiness (blockers such as a missing send cap or the WhatsApp cold-initiate opt-in, plus warnings), who sends each step (sender_journey: each step's From, its candidate inboxes and how a lead's inbox is picked; no leads needed), scans pending LinkedIn copy, and returns exact rendered recipient samples, character counts/limits, copy blockers, per-kind scope, sender/mailbox capacity, CRM readiness, and safety caps. Live start requires --approved; --max-credits is optional. Email/WhatsApp/CRM-task journeys additionally require --max-live-sends because their external actions cost 0 Oxygen credits. --dry-run is not this preview: it starts the sequence in simulated mode (it leaves draft), with no send, provider campaign, CRM task, or credits. The first live start also switches on the standing reply → CRM automation for the workspace.")
|
|
16179
16219
|
.argument("<sequence>", "Sequence id or slug.")
|
|
16180
16220
|
.option("--approved", "Approve and activate live. Without this flag, returns a preview only, including copy_preview rendered samples and blockers.")
|
|
16181
|
-
.option("--max-credits <n>", "Optional credit ceiling for the
|
|
16182
|
-
.option("--max-live-sends <n>", "
|
|
16221
|
+
.option("--max-credits <n>", "Optional positive credit ceiling. Omit for the free launch preview. Live start uses the saved ceiling when omitted; an unset ceiling means unbounded credits, not recipient approval.")
|
|
16222
|
+
.option("--max-live-sends <n>", "Total live-send and CRM-write ceiling across channels, including LinkedIn invites/messages (positive integer). Required for email, WhatsApp, or crm_task steps; optional for LinkedIn.")
|
|
16183
16223
|
.option("--dry-run", "Activate the sequence (status draft → active, startedAt set) in dry-run mode: every step advances with simulated actions — no sends, CRM writes, provider calls, or credits. This is NOT the preview: `sequences start` with no flags is the side-effect-free launch preview. A later live start (--approved) restarts those leads from the first step so they are contacted for real, except deleted source rows, a person another live campaign contacts under exclude_contacted, and older duplicates of a person.")
|
|
16184
16224
|
.option("--json", "Print a JSON envelope.")
|
|
16185
16225
|
.action(async (sequence, options) => {
|
|
16186
16226
|
await handleAsyncAction("sequences start", options, () => {
|
|
16187
|
-
const maxCredits = readPositiveNumber(options.maxCredits);
|
|
16227
|
+
const maxCredits = readPositiveNumber(options.maxCredits, "Omit --max-credits for the free launch preview. To set a live credit ceiling, pass a positive value after reviewing the preview; recipient approval remains separate.");
|
|
16188
16228
|
const maxLiveSends = readPositiveInt(options.maxLiveSends);
|
|
16189
16229
|
return requestOxygen(`/api/cli/sequences/${encodeURIComponent(sequence)}/start`, {
|
|
16190
16230
|
method: "POST",
|
|
@@ -17777,7 +17817,7 @@ Examples:
|
|
|
17777
17817
|
await handleAsyncAction("mailboxes get", options, () => requestOxygen(`/api/cli/mailboxes/${encodeURIComponent(mailbox)}`));
|
|
17778
17818
|
}))
|
|
17779
17819
|
.addCommand(new Command("delete")
|
|
17780
|
-
.description("Preview or approve removal of exact mailboxes from Oxygen at 0 credits. Deletion immediately removes them from sending but never deletes the underlying Google Workspace or Microsoft 365 accounts. It preserves conversation/message/delivery history and stops any separately listed 100-credit connected-mailbox commitment for future renewals;
|
|
17820
|
+
.description("Preview or approve removal of exact mailboxes from Oxygen at 0 credits. Deletion immediately removes them from sending but never deletes the underlying Google Workspace or Microsoft 365 accounts. It preserves conversation/message/delivery history and stops any separately listed 100-credit connected-mailbox commitment for future renewals; the preview reports whether that commitment is active and any separately billed OXYGEN Warm-up subscription. The current period is not refunded. Listed active/paused sequences keep their status but lose these senders and are not automatically paused. Without --cascade, a mailbox whose own warm-up or deliverability monitoring is still connected fails closed with its exact teardown steps; with --cascade, those add-ons are disconnected as part of the delete (their recurring credits stop too) and nothing is deleted unless every teardown is confirmed. A live managed DOMAIN subscription covers other mailboxes, so it fails closed unless the whole domain goes too: `oxygen domains delete <domain>`, or --include-domains when every inbox on it is selected.")
|
|
17781
17821
|
.requiredOption("--mailboxes <list>", "Comma-separated mailbox ids or addresses (maximum 500).")
|
|
17782
17822
|
.option("--approved", "Execute the fresh preview. Requires --plan-hash and --confirmation.")
|
|
17783
17823
|
.option("--plan-hash <hash>", "Fresh preview plan_hash.")
|
|
@@ -18070,13 +18110,25 @@ Examples:
|
|
|
18070
18110
|
body: patch,
|
|
18071
18111
|
});
|
|
18072
18112
|
});
|
|
18113
|
+
}))
|
|
18114
|
+
.addCommand(new Command("sync")
|
|
18115
|
+
.description("Read or control full Inbox, Sent and archive import for Gmail or Microsoft 365. History sync and CRM analysis are included in the mailbox’s 100-credit monthly connection. Spam, trash and drafts are excluded. Without --action, this is a free progress read; controls preview until --approved. Disconnect keeps imported mail and CRM records and stops sync and sends.")
|
|
18116
|
+
.argument("<mailbox>", "Mailbox ID or exact email address.")
|
|
18117
|
+
.option("--action <action>", "start, pause, resume or disconnect; omit to read progress.")
|
|
18118
|
+
.option("--approved", "Apply the reviewed control; default is preview.")
|
|
18119
|
+
.option("--dry-run", "Preview the control without changing sync.")
|
|
18120
|
+
.option("--json", "Print JSON.")
|
|
18121
|
+
.action(async (mailbox, options) => {
|
|
18122
|
+
await handleAsyncAction("mailboxes sync", options, () => options.action
|
|
18123
|
+
? requestOxygen("/api/cli/mailboxes/sync", { method: "POST", body: { mailbox, action: options.action, approved: options.approved === true, dry_run: options.dryRun === true } })
|
|
18124
|
+
: requestOxygen(`/api/cli/mailboxes/sync?${new URLSearchParams({ mailbox }).toString()}`));
|
|
18073
18125
|
}))
|
|
18074
18126
|
.addCommand(new Command("connect-oauth")
|
|
18075
|
-
.description("Connect
|
|
18127
|
+
.description("Connect Gmail/Microsoft 365 with --vendor composio to import all mail into Unibox and business activity into CRM. Each connected own mailbox costs 100 credits/month including history, sync and analysis; connection costs 0. Managed OAuth requires provider rollout readiness and exact registered addresses, max 10. Use mailboxes sync for progress and pause/resume/disconnect. Existing native OAuth remains available with --vendor oxygen. Warm-up retains its existing eligibility and pricing. Preview by default; pass --approved only after reviewing exact candidates. --vendor oxygen requires an exact address list and never discovers a domain directory. Microsoft supports --authorization-mode tenant (one administrator approval, up to 500 exact selected addresses, recommended for dedicated sending tenants) or individual (one account sign-in per mailbox, max 10 per run, recommended for ordinary company/personal mailboxes). For Microsoft's built-in roles, tenant mode requires a Global Administrator or Privileged Role Administrator because it requests the Microsoft Graph application Mail.Send and Mail.Read permissions; Application Administrator and Cloud Application Administrator cannot consent to Microsoft Graph application roles. Microsoft's application grant is tenant-wide at the provider; OXYGEN—not Microsoft—enforces the selected-address boundary at verification and send time unless the tenant separately configures Exchange Application RBAC. For non-managed Microsoft mailboxes, native-send approval does not authorize OXYGEN Warm-up: every mailbox still needs its own `mailboxes warmup microsoft` Outlook consent before standalone enrollment. Connecting authorization does not change whether a mailbox is active or disabled. Google uses individual OAuth. Source tokens, passwords, authenticator seeds, and one-time codes never transfer. Managed authorization paths remain available through their vendor options.")
|
|
18076
18128
|
.addHelpText("after", "\nDocs: https://oxygen-agent.com/docs/providers/mailboxes\n")
|
|
18077
18129
|
.option("--provider <provider>", "Mailbox provider to provision: google or microsoft.")
|
|
18078
|
-
.option("--vendor <vendor>", "Authorization path:
|
|
18079
|
-
.option("--mailboxes <list>", "Comma-separated exact mailbox addresses. Required for vendor=oxygen (tenant mode max 500; individual mode max 10).")
|
|
18130
|
+
.option("--vendor <vendor>", "Authorization path: composio for customer-owned Gmail/Microsoft 365 with managed consent, history and CRM; oxygen for native/tenant consent; zapmail (default) or inboxkit for provisioned inboxes.")
|
|
18131
|
+
.option("--mailboxes <list>", "Comma-separated exact mailbox addresses. Required for vendor=composio (max 10) or vendor=oxygen (tenant mode max 500; individual mode max 10).")
|
|
18080
18132
|
.option("--authorization-mode <mode>", "vendor=oxygen only: auto, tenant, or individual. Tenant is Microsoft-only and never scans the directory.")
|
|
18081
18133
|
.option("--domains <list>", "Comma-separated sending domains to limit an inboxkit run to. Omit to cover every domain in the pool.")
|
|
18082
18134
|
.option("--no-canary", "inboxkit only: fan out to every eligible mailbox on a domain that has never connected one, instead of firing a single canary first.")
|
|
@@ -19998,6 +20050,7 @@ Full trigger schema: oxygen workflows schema --subject trigger --json
|
|
|
19998
20050
|
registerSettingsCommands(program, handleAsyncAction);
|
|
19999
20051
|
registerCrmSupportFlags(program);
|
|
20000
20052
|
registerCustomerOperationCommands(program, handleAsyncAction);
|
|
20053
|
+
registerCrmFileReadCommands(program, handleAsyncAction);
|
|
20001
20054
|
// After the register* helpers: help groups are assigned to the commands that
|
|
20002
20055
|
// exist when this runs, and a family registered later fell into commander's
|
|
20003
20056
|
// trailing default "Commands:" list (the 2026-09-30 blind agent read the first
|
|
@@ -22114,7 +22167,7 @@ function isNetworkTimeoutError(error) {
|
|
|
22114
22167
|
return error instanceof OxygenError && error.code === "network_timeout";
|
|
22115
22168
|
}
|
|
22116
22169
|
function feedsListPath(options, tableArgument) {
|
|
22117
|
-
const params =
|
|
22170
|
+
const params = feedReadWindowParams(options);
|
|
22118
22171
|
const table = readOption(tableArgument) ?? readOption(options.table);
|
|
22119
22172
|
const transport = readOption(options.transport);
|
|
22120
22173
|
const status = readOption(options.status);
|
|
@@ -22136,7 +22189,8 @@ function feedsListPath(options, tableArgument) {
|
|
|
22136
22189
|
return `/api/cli/tables/feeds${query ? `?${query}` : ""}`;
|
|
22137
22190
|
}
|
|
22138
22191
|
function feedsGetPath(feed, options) {
|
|
22139
|
-
const params =
|
|
22192
|
+
const params = feedReadWindowParams(options, true);
|
|
22193
|
+
params.set("feed", feed);
|
|
22140
22194
|
if (options.includeCursor)
|
|
22141
22195
|
params.set("include", "cursor");
|
|
22142
22196
|
return `/api/cli/tables/feeds?${params.toString()}`;
|
|
@@ -22559,7 +22613,10 @@ function readCompaniesSearchRunBody(options, promptArg) {
|
|
|
22559
22613
|
const maxPages = readPositiveInt(options.maxPages);
|
|
22560
22614
|
const maxCredits = readPositiveNumber(options.maxCredits);
|
|
22561
22615
|
const targetCount = readPositiveInt(options.targetCount);
|
|
22562
|
-
|
|
22616
|
+
// With --source the server reads only --source-filters-json. Forward the planner's
|
|
22617
|
+
// filters anyway so the server refuses them by name instead of the CLI dropping
|
|
22618
|
+
// them and the run buying an unfiltered search (OXP-1.39.2 S6).
|
|
22619
|
+
const filters = prompt || options.source ? readCompanySearchFilters(options) : null;
|
|
22563
22620
|
return {
|
|
22564
22621
|
...(prompt ? { prompt } : {}),
|
|
22565
22622
|
...(plan ? { plan } : {}),
|
|
@@ -22716,7 +22773,7 @@ function readSearchPlanJson(value) {
|
|
|
22716
22773
|
}
|
|
22717
22774
|
// Table-driven Employee Finder help, written once and shared by `people search
|
|
22718
22775
|
// plan` and `people search run` so the price shape reads the same on both.
|
|
22719
|
-
const PEOPLE_SEARCH_FROM_TABLE_HELP = "Search every company LinkedIn URL in a table column for the people you describe — --titles, --exclude-titles, --seniorities, --job-functions, --education, location and --min-connections all apply per company.
|
|
22776
|
+
const PEOPLE_SEARCH_FROM_TABLE_HELP = "Search every company LinkedIn URL in a table column for the people you describe — --titles, --exclude-titles, --seniorities, --job-functions, --education, location and --min-connections all apply per company. 1 credit per company per page; rows land in a new people table with a company relation back to each source row. Pass the table id or slug plus --linkedin-url-column, and bound it with --company-limit. For up to 50 companies this is the expensive way: people search preview with company.linkedin_url samples them free and people search run --source people_search charges one page across all of them.";
|
|
22720
22777
|
const PEOPLE_SEARCH_LINKEDIN_COLUMN_HELP = "Column key in --from-table holding each company's LinkedIn URL.";
|
|
22721
22778
|
const PEOPLE_SEARCH_COMPANY_OFFSET_HELP = "Skip this many companies in --from-table. Use it to source the rest after a first-company preview.";
|
|
22722
22779
|
const PEOPLE_SEARCH_COMPANY_LIMIT_HELP = "Fetch people for at most this many companies from --from-table. Start with --company-limit 1.";
|
|
@@ -22811,9 +22868,13 @@ function readPeopleSearchRunBody(options) {
|
|
|
22811
22868
|
}
|
|
22812
22869
|
const maxCreditsNative = readPositiveNumber(options.maxCredits);
|
|
22813
22870
|
const nativeTarget = readPositiveInt(options.targetCount);
|
|
22871
|
+
// Forwarded so the server refuses them by name; the native lane reads only
|
|
22872
|
+
// --source-filters-json (OXP-1.39.2 S6).
|
|
22873
|
+
const plannerFilters = readPeopleSearchFilters(options);
|
|
22814
22874
|
return {
|
|
22815
22875
|
source: "people_search",
|
|
22816
22876
|
source_filters: parseJsonObject(readFileIfPresent(options.sourceFiltersJson)),
|
|
22877
|
+
...(plannerFilters ? { filters: plannerFilters } : {}),
|
|
22817
22878
|
...(readOption(options.tableName) ? { table_name: readOption(options.tableName) } : {}),
|
|
22818
22879
|
...(options.table ? { table: options.table } : {}),
|
|
22819
22880
|
...(options.project ? { project: options.project } : {}),
|
|
@@ -29437,14 +29498,14 @@ function readPositiveNumberOrZero(value) {
|
|
|
29437
29498
|
}
|
|
29438
29499
|
return parsed;
|
|
29439
29500
|
}
|
|
29440
|
-
function readPositiveNumber(value) {
|
|
29501
|
+
function readPositiveNumber(value, nextStep) {
|
|
29441
29502
|
const trimmed = value?.trim();
|
|
29442
29503
|
if (!trimmed)
|
|
29443
29504
|
return undefined;
|
|
29444
29505
|
const parsed = Number(trimmed);
|
|
29445
29506
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
29446
29507
|
throw new OxygenError("invalid_number", "Expected a positive number.", {
|
|
29447
|
-
details: { value },
|
|
29508
|
+
details: { value, ...(nextStep ? { next_step: nextStep } : {}) },
|
|
29448
29509
|
exitCode: 1,
|
|
29449
29510
|
});
|
|
29450
29511
|
}
|