@oxygen-agent/cli 1.1014.86 → 1.1014.118
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 +4 -1
- package/dist/help.js +5 -0
- package/dist/index.js +133 -72
- package/dist/skills.js +2 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +122 -0
- package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +14 -4
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +6 -5
- package/node_modules/@oxygen/shared/dist/contracts/knowledge-sync.d.ts +19 -0
- package/node_modules/@oxygen/shared/dist/contracts/knowledge-sync.js +8 -0
- package/node_modules/@oxygen/shared/dist/contracts/publishing-reads.d.ts +7 -0
- package/node_modules/@oxygen/shared/dist/contracts/schema.d.ts +3 -0
- package/node_modules/@oxygen/shared/dist/contracts/schema.js +3 -0
- package/node_modules/@oxygen/shared/dist/contracts/table-feed-read.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/contracts/table-row-read.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/mcp-event-contract.d.ts +12 -0
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +1 -0
- package/node_modules/@oxygen/shared/dist/sending-schedule.d.ts +7 -0
- package/node_modules/@oxygen/shared/dist/sending-schedule.js +10 -0
- package/node_modules/@oxygen/shared/dist/user-capability-routing.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/user-capability-routing.js +8 -0
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/command-manifest.js
CHANGED
|
@@ -111,8 +111,11 @@ const MUTATING_COMMANDS = new Set([
|
|
|
111
111
|
// The ledger audit reads that same balance first, with the same conditional writes.
|
|
112
112
|
"billing audit",
|
|
113
113
|
// Creates an approval or Checkout link; a first plan's Checkout can start the
|
|
114
|
-
// first-month offer window. The plan changes only after
|
|
114
|
+
// first-month offer window. The plan changes only after confirmation.
|
|
115
115
|
"billing change",
|
|
116
|
+
// No amount is a read; an amount/pack creates Stripe billing resources.
|
|
117
|
+
// The manifest describes all invocations, without inventing a credit debit.
|
|
118
|
+
"billing topup",
|
|
116
119
|
"billing retain",
|
|
117
120
|
// --approved records a domain's renewal exit, or with --revoke resumes renewals.
|
|
118
121
|
"managed-inboxes exit",
|
package/dist/help.js
CHANGED
|
@@ -453,6 +453,11 @@ export function argvRequestsJson(argv) {
|
|
|
453
453
|
}
|
|
454
454
|
function buildUnknownCommandHint(parent, path, token) {
|
|
455
455
|
const names = parent.commands.map((command) => command.name()).filter((name) => name !== "help");
|
|
456
|
+
const firstWord = token.trim().split(/\s+/)[0] ?? "";
|
|
457
|
+
if (/\s/.test(token) && parent.commands.some((command) => command.name() === firstWord || command.aliases().includes(firstWord))) {
|
|
458
|
+
const name = path.split(" ").slice(1).concat(firstWord).join(" ");
|
|
459
|
+
return `Hint: The subcommand and its options arrived as one argument. Pass them as separate shell arguments; quote individual values only. Inspect the grammar with \`${path.split(" ")[0]} commands get '${name}' --json\`.`;
|
|
460
|
+
}
|
|
456
461
|
const lower = token.toLowerCase();
|
|
457
462
|
// A read intent on a budget must never point at the mutating `set` command.
|
|
458
463
|
const synonym = path.endsWith(" budget") && lower === "get" && names.includes("list")
|
package/dist/index.js
CHANGED
|
@@ -83,6 +83,7 @@ import { SEQUENCE_LIFECYCLES, sequenceDisplayStatus } from "@oxygen/shared/seque
|
|
|
83
83
|
import { lookupColumnDataType } from "@oxygen/shared/table-field-types";
|
|
84
84
|
import { FILTER_TREE_OPERATORS } from "@oxygen/shared/table-filter-operators";
|
|
85
85
|
import { tableRunSummary } from "@oxygen/shared/table-run-summary";
|
|
86
|
+
import { capabilityDiscoveryRecovery } from "@oxygen/shared/user-capability-routing";
|
|
86
87
|
import { assertRecipeBundleSafe, assertPortableWorkflowDefinition, assertWorkflowGraphManifest, assertWorkflowManifest, buildRecipeManifest, compileWorkflowDefinition, isAnyWorkflowManifest, isRecipeManifest, isWorkflowDefinition, isWorkflowGraphManifest, isWorkflowManifest, hashPortableWorkflowGraphManifest, WORKFLOW_GRAPH_COMPILER_VERSION, WORKFLOW_GRAPH_MANIFEST_VERSION, } from "@oxygen/workflows";
|
|
87
88
|
import { Command, CommanderError, Option } from "commander";
|
|
88
89
|
import { execFileSync } from "node:child_process";
|
|
@@ -3676,6 +3677,7 @@ export function createProgram() {
|
|
|
3676
3677
|
const program = new Command();
|
|
3677
3678
|
const binaryName = resolveCliBinaryName();
|
|
3678
3679
|
const directoryDocsUrl = `${defaultApiUrl()}/docs/agencies`;
|
|
3680
|
+
const billingDocsUrl = `${defaultApiUrl()}/docs/safety/billing`;
|
|
3679
3681
|
program
|
|
3680
3682
|
.name(binaryName)
|
|
3681
3683
|
.description("Revenue infrastructure for B2B startups — agent-operated GTM: tables, enrichment, sequences, workflows, CRM, knowledge. Everything here also runs in the web app and over MCP; every state-changing action returns a web_url deep-link.")
|
|
@@ -3955,10 +3957,13 @@ export function createProgram() {
|
|
|
3955
3957
|
.action(async (queryParts, options) => {
|
|
3956
3958
|
await handleAsyncAction("capabilities search", options, async () => {
|
|
3957
3959
|
const query = queryParts.join(" ");
|
|
3960
|
+
const route = inferUserCapabilityRoute(query);
|
|
3958
3961
|
return {
|
|
3959
3962
|
query,
|
|
3960
|
-
route: serializeCapabilityRoute(
|
|
3961
|
-
hint:
|
|
3963
|
+
route: serializeCapabilityRoute(route),
|
|
3964
|
+
hint: route
|
|
3965
|
+
? "Hydrate one exact CLI command with `oxygen commands get <exact-command> --json`, or one MCP tool with `oxygen_capabilities_schema`."
|
|
3966
|
+
: capabilityDiscoveryRecovery(query, "cli"),
|
|
3962
3967
|
alternate_search_hint: `If this route does not fit the outcome, run \`oxygen skills search ${JSON.stringify(query)} --json\` for a narrower operational play.`,
|
|
3963
3968
|
};
|
|
3964
3969
|
});
|
|
@@ -8532,9 +8537,8 @@ Examples:
|
|
|
8532
8537
|
.option("--force", "Re-arm past an existing once-ever marker, the one deliberate way to research the same workspace twice. Without --live it only previews the re-arm for free: nothing is cleared or written. With --live it replaces the marker with a new pass and spends again.")
|
|
8533
8538
|
.option("--json", "Print a JSON envelope.")
|
|
8534
8539
|
.action(async (options) => {
|
|
8535
|
-
await handleAsyncAction("knowledge bootstrap", options, () =>
|
|
8536
|
-
|
|
8537
|
-
body: {
|
|
8540
|
+
await handleAsyncAction("knowledge bootstrap", options, () => {
|
|
8541
|
+
const parameters = {
|
|
8538
8542
|
mode: options.live || options.approved ? "live" : "dry_run",
|
|
8539
8543
|
...(readOption(options.domain) ? { domain: readOption(options.domain) } : {}),
|
|
8540
8544
|
...(readOption(options.linkedin) ? { linkedin_url: readOption(options.linkedin) } : {}),
|
|
@@ -8542,8 +8546,11 @@ Examples:
|
|
|
8542
8546
|
? { max_credits: readPositiveNumber(options.maxCredits) }
|
|
8543
8547
|
: {}),
|
|
8544
8548
|
...(options.force ? { force: true } : {}),
|
|
8545
|
-
}
|
|
8546
|
-
|
|
8549
|
+
};
|
|
8550
|
+
return parameters.mode === "live"
|
|
8551
|
+
? requestOxygen("/api/cli/knowledge/bootstrap", { method: "POST", body: parameters })
|
|
8552
|
+
: requestOxygen(knowledgeReadPath("/api/cli/knowledge/bootstrap", parameters));
|
|
8553
|
+
});
|
|
8547
8554
|
})
|
|
8548
8555
|
.addCommand(new Command("status")
|
|
8549
8556
|
.description("Report the once-ever bootstrap marker for this workspace: status (queued, running, completed, partial, skipped, failed, or never attempted), when it ran, its system run id, the credits it actually spent, the domain researched, the reason it stopped, and the pages it wrote. Free, reads only, and the honest way to find out whether the automatic pass already happened before you spend on a manual one.")
|
|
@@ -8556,10 +8563,7 @@ Examples:
|
|
|
8556
8563
|
...options,
|
|
8557
8564
|
json: options.json || Boolean(knowledgeBootstrapCommand.opts().json),
|
|
8558
8565
|
};
|
|
8559
|
-
await handleAsyncAction("knowledge bootstrap status", outputOptions, () => requestOxygen("/api/cli/knowledge/bootstrap"
|
|
8560
|
-
method: "POST",
|
|
8561
|
-
body: { status: true },
|
|
8562
|
-
}));
|
|
8566
|
+
await handleAsyncAction("knowledge bootstrap status", outputOptions, () => requestOxygen("/api/cli/knowledge/bootstrap?status=true"));
|
|
8563
8567
|
}));
|
|
8564
8568
|
program
|
|
8565
8569
|
.command("knowledge")
|
|
@@ -8696,13 +8700,10 @@ Examples:
|
|
|
8696
8700
|
.action(async (slugOrId, options) => {
|
|
8697
8701
|
await handleAsyncAction("knowledge page revisions", options, () => {
|
|
8698
8702
|
const limit = readPositiveInt(options.limit);
|
|
8699
|
-
return requestOxygen("/api/cli/knowledge/pages/revisions", {
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
...(limit !== undefined ? { limit } : {}),
|
|
8704
|
-
},
|
|
8705
|
-
});
|
|
8703
|
+
return requestOxygen(knowledgeReadPath("/api/cli/knowledge/pages/revisions", {
|
|
8704
|
+
...knowledgePageRefBody(slugOrId),
|
|
8705
|
+
...(limit !== undefined ? { limit } : {}),
|
|
8706
|
+
}));
|
|
8706
8707
|
});
|
|
8707
8708
|
}))
|
|
8708
8709
|
.addCommand(new Command("revision")
|
|
@@ -8718,13 +8719,10 @@ Examples:
|
|
|
8718
8719
|
details: { revision },
|
|
8719
8720
|
});
|
|
8720
8721
|
}
|
|
8721
|
-
return requestOxygen("/api/cli/knowledge/pages/revisions/get", {
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
revision: revisionNumber,
|
|
8726
|
-
},
|
|
8727
|
-
});
|
|
8722
|
+
return requestOxygen(knowledgeReadPath("/api/cli/knowledge/pages/revisions/get", {
|
|
8723
|
+
...knowledgePageRefBody(slugOrId),
|
|
8724
|
+
revision: revisionNumber,
|
|
8725
|
+
}));
|
|
8728
8726
|
});
|
|
8729
8727
|
})))
|
|
8730
8728
|
.addCommand(new Command("search")
|
|
@@ -8770,10 +8768,7 @@ Examples:
|
|
|
8770
8768
|
.option("--depth <n>", "Local-graph hop depth (1-3, default 1). Only applies with --center.")
|
|
8771
8769
|
.option("--json", "Print a JSON envelope.")
|
|
8772
8770
|
.action(async (options) => {
|
|
8773
|
-
await handleAsyncAction("knowledge graph", options, () => requestOxygen("/api/cli/knowledge/graph",
|
|
8774
|
-
method: "POST",
|
|
8775
|
-
body: buildKnowledgeGraphBody(options),
|
|
8776
|
-
}));
|
|
8771
|
+
await handleAsyncAction("knowledge graph", options, () => requestOxygen(knowledgeReadPath("/api/cli/knowledge/graph", buildKnowledgeGraphBody(options))));
|
|
8777
8772
|
}))
|
|
8778
8773
|
.addCommand(new Command("lint")
|
|
8779
8774
|
.description("Read-only structural wiki health report, 0 credits: orphans (unfilled seed stubs excluded), dead-ends, unresolved links, contradictions (disputed pages, conflicting trust tags, competing live-copy pages), missing canonicals, untagged pages, duplicate titles, oversized hubs, unfilled seed stubs (total plus the most-linked-to), researchable stubs, decisions missing a reversal condition, stale pages (untouched or superseded by newer sources), oversized bodies, and aged proposals.")
|
|
@@ -8802,10 +8797,7 @@ Examples:
|
|
|
8802
8797
|
.option("--limit <n>", "Maximum log entries to return.")
|
|
8803
8798
|
.option("--json", "Print a JSON envelope.")
|
|
8804
8799
|
.action(async (options) => {
|
|
8805
|
-
await handleAsyncAction("knowledge log", options, () => requestOxygen("/api/cli/knowledge/log",
|
|
8806
|
-
method: "POST",
|
|
8807
|
-
body: buildKnowledgeLogListBody(options),
|
|
8808
|
-
}));
|
|
8800
|
+
await handleAsyncAction("knowledge log", options, () => requestOxygen(knowledgeReadPath("/api/cli/knowledge/log", buildKnowledgeLogListBody(options))));
|
|
8809
8801
|
}))
|
|
8810
8802
|
.addCommand(new Command("append")
|
|
8811
8803
|
.description("Append an entry to the knowledge log.")
|
|
@@ -8823,14 +8815,16 @@ Examples:
|
|
|
8823
8815
|
.addCommand(new Command("proposals")
|
|
8824
8816
|
.description("Draft change proposals against wiki pages, awaiting human review. Read-only, 0 credits.")
|
|
8825
8817
|
.option("--status <status>", "open, decided, or all. Defaults to open.")
|
|
8818
|
+
.option("--limit <n>", "Maximum proposals to return. Defaults to 50; server caps at 200.")
|
|
8826
8819
|
.option("--json", "Print a JSON envelope.")
|
|
8827
8820
|
.action(async (options) => {
|
|
8828
|
-
await handleAsyncAction("knowledge proposals", options, () =>
|
|
8829
|
-
|
|
8830
|
-
|
|
8821
|
+
await handleAsyncAction("knowledge proposals", options, () => {
|
|
8822
|
+
const limit = readPositiveInt(options.limit);
|
|
8823
|
+
return requestOxygen(knowledgeReadPath("/api/cli/knowledge/proposals", {
|
|
8831
8824
|
...(readOption(options.status) ? { status: readOption(options.status) } : {}),
|
|
8832
|
-
|
|
8833
|
-
|
|
8825
|
+
...(limit !== undefined ? { limit } : {}),
|
|
8826
|
+
}));
|
|
8827
|
+
});
|
|
8834
8828
|
})
|
|
8835
8829
|
.addCommand(new Command("approve")
|
|
8836
8830
|
.description("Approve a knowledge proposal and apply it to the target page.")
|
|
@@ -10869,13 +10863,30 @@ Examples:
|
|
|
10869
10863
|
.addCommand(new Command("retry-failed")
|
|
10870
10864
|
.description("Requeue failed items for a durable table action run. Items with an unconfirmed external effect stay blocked until their destination is verified and explicitly approved.")
|
|
10871
10865
|
.argument("<run_id>", "Table action run UUID.")
|
|
10866
|
+
.option("--dry-run", "Read the exact retry scope and approval hash without requeueing.")
|
|
10867
|
+
.option("--approved", "Approve the inspected retry scope and additional-credit ceiling.")
|
|
10868
|
+
.option("--retry-plan-hash <hash>", "Hash returned by the retry dry run.")
|
|
10869
|
+
.option("--max-additional-credits <n>", "Fresh credit allowance for this recovery; never raises the original lifetime ceiling.")
|
|
10872
10870
|
.option("--approved-effect-unknown", "After a human verifies every affected destination, explicitly allow re-executing items whose prior external effect could not be confirmed.")
|
|
10873
10871
|
.option("--json", "Print a JSON envelope.")
|
|
10874
10872
|
.action(async (runId, options) => {
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
10873
|
+
if (options.dryRun && (options.approved || options.retryPlanHash || options.maxAdditionalCredits !== undefined || options.approvedEffectUnknown)) {
|
|
10874
|
+
throw new OxygenError("invalid_request", "Use --dry-run separately from retry approval flags.", { exitCode: 2 });
|
|
10875
|
+
}
|
|
10876
|
+
const maxAdditionalCredits = options.maxAdditionalCredits === undefined ? undefined : Number(options.maxAdditionalCredits);
|
|
10877
|
+
if (maxAdditionalCredits !== undefined && (!options.maxAdditionalCredits?.trim() || !Number.isFinite(maxAdditionalCredits) || maxAdditionalCredits < 0)) {
|
|
10878
|
+
throw new OxygenError("invalid_request", "--max-additional-credits must be a nonnegative number.", { exitCode: 2 });
|
|
10879
|
+
}
|
|
10880
|
+
await handleAsyncAction("table-runs retry-failed", options, () => options.dryRun
|
|
10881
|
+
? requestOxygen(`/api/cli/table-action-runs/${encodeURIComponent(runId)}/retry-failed`, { method: "GET" })
|
|
10882
|
+
: requestOxygen(`/api/cli/table-action-runs/${encodeURIComponent(runId)}/retry-failed`, {
|
|
10883
|
+
method: "POST", body: {
|
|
10884
|
+
...(options.approvedEffectUnknown ? { approved_effect_unknown: true } : {}),
|
|
10885
|
+
...(options.approved !== undefined ? { approved: options.approved } : {}),
|
|
10886
|
+
...(options.retryPlanHash !== undefined ? { retry_plan_hash: options.retryPlanHash } : {}),
|
|
10887
|
+
...(maxAdditionalCredits !== undefined ? { max_additional_credits: maxAdditionalCredits } : {}),
|
|
10888
|
+
},
|
|
10889
|
+
}));
|
|
10879
10890
|
}));
|
|
10880
10891
|
program
|
|
10881
10892
|
.command("table-ingestions")
|
|
@@ -11486,7 +11497,7 @@ Examples:
|
|
|
11486
11497
|
.description("Plan-tier limits, spend-safety defaults, per-channel sending limits, and storage capacity posture. `limits show --section capacity` returns storage alone, including which Table is full.")
|
|
11487
11498
|
.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")
|
|
11488
11499
|
.addCommand(addContractFlags(new Command("show")
|
|
11489
|
-
.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:
|
|
11500
|
+
.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: " + billingDocsUrl + "."), LIMITS_READ_QUERY_CONTRACT)
|
|
11490
11501
|
.option("--json", "Print a JSON envelope.")
|
|
11491
11502
|
.action(async (options) => {
|
|
11492
11503
|
await handleAsyncAction("limits show", options, () => {
|
|
@@ -11529,9 +11540,9 @@ Examples:
|
|
|
11529
11540
|
})));
|
|
11530
11541
|
program
|
|
11531
11542
|
.command("billing")
|
|
11532
|
-
.description("Plan and managed credit commands. Credits reserved but not yet charged are listed hold by hold with `billing usage --meter holds`. 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:
|
|
11543
|
+
.description("Plan and managed credit commands. Credits reserved but not yet charged are listed hold by hold with `billing usage --meter holds`. 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: " + billingDocsUrl + ".")
|
|
11533
11544
|
.addCommand(new Command("change")
|
|
11534
|
-
.description("Create an approval link (`approval_url`) to start or change a plan;
|
|
11545
|
+
.description("Create an approval link (`approval_url`) to start or change a plan; no charge or plan change until confirmation. First purchase needs an admin/owner; existing-plan changes allow any unrestricted member. 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. Legacy prepaid yearly upgrades that keep their cadence apply immediately with proration; downgrades and term or cadence changes wait until the paid year 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.")
|
|
11535
11546
|
.requiredOption("--to <plan>",
|
|
11536
11547
|
// Derived from the catalog, never retyped: this help text is the only
|
|
11537
11548
|
// place a customer's agent learns which plans exist, and it named
|
|
@@ -11563,7 +11574,7 @@ Examples:
|
|
|
11563
11574
|
}));
|
|
11564
11575
|
}))
|
|
11565
11576
|
.addCommand(new Command("balance")
|
|
11566
|
-
.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:
|
|
11577
|
+
.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: " + defaultApiUrl() + "/billing. Policy: " + billingDocsUrl + ". 0 credits; reserves nothing. Due plan grants or credit expiry may apply.")
|
|
11567
11578
|
.option("--json", "Print a JSON envelope.")
|
|
11568
11579
|
.action(async (options) => {
|
|
11569
11580
|
await handleAsyncAction("billing balance", options, () => requestOxygen("/api/cli/billing/balance"));
|
|
@@ -11634,7 +11645,7 @@ Examples:
|
|
|
11634
11645
|
await handleAsyncAction("billing allowance", options, () => requestOxygen("/api/cli/billing/allowance"));
|
|
11635
11646
|
}))
|
|
11636
11647
|
.addCommand(new Command("usage")
|
|
11637
|
-
.description("Show credit ledger events (each tagged with its FIXED/FLEXIBLE spend_class), workflow step usage (automation_actions: credits per step, credits billed; writing rows is free), calls with your own API keys (byok_calls: the platform fee per call and the fee credits this month;
|
|
11648
|
+
.description("Show credit ledger events (each tagged with its FIXED/FLEXIBLE spend_class), workflow step usage (automation_actions: credits per step, credits billed; writing rows is free), calls with your own API keys (byok_calls: the platform fee per successful call and the fee credits this month; vendor charges follow your provider's terms), or connected LinkedIn and WhatsApp accounts (provider_seats: the rolling-30d peak and its implied connectivity cost; what each account is charged is its credit reservation in `billing commitments`). On a plan that pays for several workspaces (an agency covering client workspaces), `--group-by workspace` totals the credits each workspace spent (`by_workspace`) and `--workspace <id|slug>` narrows the ledger to one of them; a linked client workspace sees only its own spend. managed_credits_used counts captured credits; for where credits went by category, provider or tool, with shares, run `billing audit`.")
|
|
11638
11649
|
.option("--meter <meter>", "credits, automation_actions, byok_calls, provider_seats (rolling-30d peak connected accounts and the flat per-account charge; each account's reservation is listed by `billing commitments`), or holds (each held reservation behind reserved_credits in this workspace: credits, run, why it is held (an unmarked LinkedIn lookup hold cannot say whether its result was unknown or delivered), and automatic_return: LinkedIn lookup holds from 24 hours to 30 days old and explicitly unknown LeadMagic email-validation holds older than 24 hours are eligible while automatic return is switched on, and an hourly release returns it on a later pass with no fixed deadline; read-only, newest first, --days up to 365 and --limit apply). Defaults to credits.")
|
|
11639
11650
|
.option("--days <n>", "Lookback window in days. Defaults to 30.")
|
|
11640
11651
|
.option("--from <iso>", "Only include ledger events at or after this ISO timestamp.")
|
|
@@ -11790,7 +11801,7 @@ Examples:
|
|
|
11790
11801
|
await handleAsyncAction("billing retain", options, () => requestOxygen("/api/cli/billing/cancel", { method: "POST", body: { action: "retain", offer_id: options.offer } }));
|
|
11791
11802
|
}))
|
|
11792
11803
|
.addCommand(new Command("topup")
|
|
11793
|
-
.description("
|
|
11804
|
+
.description("No amount: read-only price quote, no checkout. With --credits or a pack: create Stripe Checkout and possibly billing resources; no charge until payment. Read tax_basis and review tax and the final total in Checkout. Prices are per 100 credits (`usd_per_100_credits`); credits arrive after confirmed payment and never expire. Plan credits cost less. `usd_per_1k_credits` is a deprecated name for the same per-100 price, not a price per 1,000 credits. `default_credits` is the web form's initial custom selection, not a purchase or minimum.")
|
|
11794
11805
|
.argument("[pack]", "A pack id from the list shown when you run without an amount.")
|
|
11795
11806
|
.option("--credits <n>", "Credits to buy (800-100,000 in 100-credit increments).")
|
|
11796
11807
|
.option("--json", "Print a JSON envelope.")
|
|
@@ -13411,7 +13422,7 @@ Examples:
|
|
|
13411
13422
|
.command("enrich-column")
|
|
13412
13423
|
.description("High-level table enrichment helpers. To see every enrichment a table's columns support, with per-row prices, before choosing one, run `tools search --for-table <table>` (0 credits).")
|
|
13413
13424
|
.addCommand(new Command("preview")
|
|
13414
|
-
.description("Preflight an enrichment column without provider calls or credit usage. A lane on your own key (BYOK) is quoted at OXYGEN's platform fee per call,
|
|
13425
|
+
.description("Preflight an enrichment column without provider calls or credit usage. A lane on your own key (BYOK) is quoted at OXYGEN's platform fee per successful call, including a successful miss; vendor charges follow your provider's terms (provider_cost_breakdown[].byok_platform_fee_credits_per_call).")
|
|
13415
13426
|
.argument("<table>", "Table id or slug.")
|
|
13416
13427
|
.option("--source-column <column>", "Source column key or id. For mobile_phone this is the LinkedIn URL column.")
|
|
13417
13428
|
.option("--full-name-column <column>", "Column key or id containing the person's full name for work_email. Pair with company-domain/name for Prospeo, LeadMagic, Hunter, Dropleads, ContactOut, BetterContact, or People Data Labs.")
|
|
@@ -13503,7 +13514,7 @@ Examples:
|
|
|
13503
13514
|
.option("--company-domain <domain>", "Company apex domain, e.g. acme.com.")
|
|
13504
13515
|
.option("--company-name <name>", "Company name.")
|
|
13505
13516
|
.option("--mode <mode>", "dry_run (default) previews the plan and spends nothing; live spends credits and returns the answer.")
|
|
13506
|
-
.option("--max-credits <credits>", "Spend ceiling in Oxygen credits
|
|
13517
|
+
.option("--max-credits <credits>", "Spend ceiling in Oxygen credits, including the platform fee on each successful own-key call, even on a miss. Your provider may separately bill your account under its own rules, outside this ceiling (cap own-key calls with `oxygen limits provider set`). Required for --mode live. Run the free dry_run first: it returns estimate.recommended_max_credits, the ceiling that lets the WHOLE resolved waterfall run, plus estimate.min_credits and a per-leg breakdown. When the resolved chain runs the guessed-address pass (the name+domain and first/last+domain profiles, not the LinkedIn-URL one) the block also carries estimate.pattern_guess_verification_credits — the verification checks that pass bills before any named provider is dialled. A lower ceiling does NOT stop the run: each lane priced above what is left of it is refused on its own (spend_cap_too_low) and the waterfall advances, so a cheaper lane further down the chain can still run and bill.")
|
|
13507
13518
|
.option("--json", "Print a JSON envelope.")
|
|
13508
13519
|
.action(async (options) => {
|
|
13509
13520
|
await handleAsyncAction("find email", options, () => requestOxygen("/api/cli/find/run", { method: "POST", body: buildFindBody("email", options) }));
|
|
@@ -13516,7 +13527,7 @@ Examples:
|
|
|
13516
13527
|
.option("--company-domain <domain>", "Company apex domain, e.g. acme.com.")
|
|
13517
13528
|
.option("--company-name <name>", "Company name.")
|
|
13518
13529
|
.option("--mode <mode>", "dry_run (default) previews the plan and spends nothing; live spends credits and returns the answer.")
|
|
13519
|
-
.option("--max-credits <credits>", "Spend ceiling in Oxygen credits
|
|
13530
|
+
.option("--max-credits <credits>", "Spend ceiling in Oxygen credits, including the platform fee on each successful own-key call, even on a miss. Your provider may separately bill your account under its own rules, outside this ceiling (cap own-key calls with `oxygen limits provider set`). Required for --mode live. Run the free dry_run first: it returns estimate.recommended_max_credits, the ceiling that lets the WHOLE resolved waterfall run, plus estimate.min_credits and a per-leg breakdown. A lower ceiling does NOT stop the run: each lane priced above what is left of it is refused on its own (spend_cap_too_low) and the waterfall advances, so a cheaper lane further down the chain can still run and bill.")
|
|
13520
13531
|
.option("--json", "Print a JSON envelope.")
|
|
13521
13532
|
.action(async (options) => {
|
|
13522
13533
|
await handleAsyncAction("find personal-email", options, () => requestOxygen("/api/cli/find/run", { method: "POST", body: buildFindBody("personal_email", options) }));
|
|
@@ -13530,7 +13541,7 @@ Examples:
|
|
|
13530
13541
|
.option("--company-name <name>", "Company name.")
|
|
13531
13542
|
.option("--verify", "Verify the found number with ClearoutPhone — adds line_type/carrier and keeps the number on a non-verdict (only a genuine 'not valid' is discarded).")
|
|
13532
13543
|
.option("--mode <mode>", "dry_run (default) previews the plan and spends nothing; live spends credits and returns the answer.")
|
|
13533
|
-
.option("--max-credits <credits>", "Spend ceiling in Oxygen credits
|
|
13544
|
+
.option("--max-credits <credits>", "Spend ceiling in Oxygen credits, including the platform fee on each successful own-key call, even on a miss. Your provider may separately bill your account under its own rules, outside this ceiling (cap own-key calls with `oxygen limits provider set`). Required for --mode live. Run the free dry_run first: it returns estimate.recommended_max_credits, the ceiling that lets the WHOLE resolved waterfall run, plus estimate.min_credits and a per-leg breakdown. A lower ceiling does NOT stop the run: each lane priced above what is left of it is refused on its own (spend_cap_too_low) and the waterfall advances, so a cheaper lane further down the chain can still run and bill.")
|
|
13534
13545
|
.option("--json", "Print a JSON envelope.")
|
|
13535
13546
|
.action(async (options) => {
|
|
13536
13547
|
await handleAsyncAction("find phone", options, () => requestOxygen("/api/cli/find/run", { method: "POST", body: buildFindBody("phone", options) }));
|
|
@@ -13543,7 +13554,7 @@ Examples:
|
|
|
13543
13554
|
.option("--company-name <name>", "Company name.")
|
|
13544
13555
|
.option("--company-linkedin-url <url>", "Company LinkedIn URL.")
|
|
13545
13556
|
.option("--mode <mode>", "dry_run (default) previews the plan and spends nothing; live spends credits and returns the answer.")
|
|
13546
|
-
.option("--max-credits <credits>", "Spend ceiling in Oxygen credits
|
|
13557
|
+
.option("--max-credits <credits>", "Spend ceiling in Oxygen credits, including the platform fee on each successful own-key call, even on a miss. Your provider may separately bill your account under its own rules, outside this ceiling (cap own-key calls with `oxygen limits provider set`). Required for --mode live. Run the free dry_run first: it returns estimate.recommended_max_credits, the ceiling that lets the WHOLE resolved waterfall run, plus estimate.min_credits and a per-leg breakdown. A lower ceiling does NOT stop the run: each lane priced above what is left of it is refused on its own (spend_cap_too_low) and the waterfall advances, so a cheaper lane further down the chain can still run and bill.")
|
|
13547
13558
|
.option("--json", "Print a JSON envelope.")
|
|
13548
13559
|
.action(async (options) => {
|
|
13549
13560
|
await handleAsyncAction("find linkedin", options, () => requestOxygen("/api/cli/find/run", { method: "POST", body: buildFindBody("linkedin", options) }));
|
|
@@ -13561,7 +13572,7 @@ Examples:
|
|
|
13561
13572
|
.option("--company-name <name>", "Company name.")
|
|
13562
13573
|
.option("--include-raw", "Also return the provider's raw profile payload (profile_raw) for fields the profile block does not carry.")
|
|
13563
13574
|
.option("--mode <mode>", "dry_run (default) previews the plan and spends nothing; live spends credits and returns the profile.")
|
|
13564
|
-
.option("--max-credits <credits>", "Spend ceiling in Oxygen credits
|
|
13575
|
+
.option("--max-credits <credits>", "Spend ceiling in Oxygen credits, including the platform fee on each successful own-key call, even on a miss. Your provider may separately bill your account under its own rules, outside this ceiling (cap own-key calls with `oxygen limits provider set`). Required for --mode live. Run the free dry_run first: estimate.recommended_max_credits funds the profile lookup plus, for an email or name input, every lane of the LinkedIn URL waterfall that resolves it.")
|
|
13565
13576
|
.option("--json", "Print a JSON envelope.")
|
|
13566
13577
|
.action(async (options) => {
|
|
13567
13578
|
await handleAsyncAction("find person", options, () => requestOxygen("/api/cli/find/run", { method: "POST", body: buildFindBody("person", options) }));
|
|
@@ -13575,7 +13586,7 @@ Examples:
|
|
|
13575
13586
|
.option("--list-fields", "Print the field catalog (category, description, provider count, per-row credits: free / fixed / ~estimated) and exit. Needs no identity; spends nothing.")
|
|
13576
13587
|
.option("--check-technologies <names>", "Comma-separated technology names for the technology_check field, answered yes/no against the detected stack (e.g. hubspot,salesforce).")
|
|
13577
13588
|
.option("--mode <mode>", "dry_run (default) previews the plan and spends nothing; live spends credits and returns the answer.")
|
|
13578
|
-
.option("--max-credits <credits>", "Spend ceiling in Oxygen credits
|
|
13589
|
+
.option("--max-credits <credits>", "Spend ceiling in Oxygen credits, including the platform fee on each successful own-key call, even on a miss. Your provider may separately bill your account under its own rules, outside this ceiling (cap own-key calls with `oxygen limits provider set`). Required for --mode live; 0 can still call providers on zero-credit lanes and reuse any priced lane this workspace already has a cached answer for (the other priced lanes are skipped as credit_ceiling_reached). Run the free dry_run first: it returns estimate.recommended_max_credits, the ceiling that funds every lane in the plan, and estimate.min_credits, what the plan costs if each field is answered by its first lane.")
|
|
13579
13590
|
.option("--json", "Print a JSON envelope.")
|
|
13580
13591
|
.action(async (options) => {
|
|
13581
13592
|
await handleAsyncAction("find company", options, () => requestOxygen("/api/cli/find/run", { method: "POST", body: buildFindBody("company", options) }));
|
|
@@ -16468,6 +16479,8 @@ Examples:
|
|
|
16468
16479
|
.option("--schedule-hours-start <HH:MM>", "Sending schedule start, local to --schedule-timezone, e.g. 09:00.")
|
|
16469
16480
|
.option("--schedule-hours-end <HH:MM>", "Sending schedule end (exclusive), e.g. 17:00. An email step's own send_window (on the step in --steps-file) still overrides it for that step. LinkedIn sends also stay inside each LinkedIn account's working hours (default 07:00-22:00 in the account's timezone; set with `oxygen senders limits set`); WhatsApp accounts and email mailboxes have no hours of their own.")
|
|
16470
16481
|
.option("--send-window-file <path>", "Email-only window JSON: { timezone, days?, start, end, timezone_mode?, recipient_timezone_column? }; days are ISO 1-7. New sequences default to weekdays 09:00-17:00 UTC. Use it to keep email on different hours from LinkedIn/WhatsApp, or to send in each recipient's own timezone (timezone_mode recipient, which --schedule-* then keeps); for one schedule across channels, use --schedule-* instead.")
|
|
16482
|
+
.option("--linkedin-invite-schedule <json>", 'LinkedIn invites only: { "timezone": "Europe/Berlin", "days": [1,2,3,4,5,6,7], "start": "09:00", "end": "17:00" }. Replaces the general Sequence schedule for invites; account working hours still apply. null restores the general schedule.')
|
|
16483
|
+
.option("--linkedin-message-schedule <json>", 'LinkedIn messages and InMail only: { "timezone": "Europe/Berlin", "days": [1,2,3,4,5], "start": "10:00", "end": "16:00" }. Replaces the general Sequence schedule for messages; account working hours still apply. null restores the general schedule.')
|
|
16471
16484
|
.option("--linkedin-timezone <tz>", "LinkedIn/WhatsApp-only sending schedule: IANA zone, e.g. Europe/Zurich. Set with --linkedin-days, --linkedin-hours-start and --linkedin-hours-end; for LinkedIn the window narrows each account's working hours, for WhatsApp it is the only hours limit. For one schedule across channels, use --schedule-* instead.")
|
|
16472
16485
|
.option("--linkedin-days <days>", "LinkedIn sending schedule days: mon-fri, mon,wed,fri, or ISO numbers 1-7 (1 = Monday).")
|
|
16473
16486
|
.option("--linkedin-hours-start <HH:MM>", "LinkedIn sending schedule start, local to --linkedin-timezone, e.g. 06:45.")
|
|
@@ -29604,6 +29617,12 @@ function buildKnowledgePageListBody(options) {
|
|
|
29604
29617
|
...(limit !== undefined ? { limit } : {}),
|
|
29605
29618
|
};
|
|
29606
29619
|
}
|
|
29620
|
+
function knowledgeReadPath(path, parameters) {
|
|
29621
|
+
const query = new URLSearchParams(Object.entries(parameters)
|
|
29622
|
+
.filter(([, value]) => value !== undefined)
|
|
29623
|
+
.map(([key, value]) => [key, String(value)]));
|
|
29624
|
+
return query.size ? `${path}?${query}` : path;
|
|
29625
|
+
}
|
|
29607
29626
|
function buildKnowledgeGraphBody(options) {
|
|
29608
29627
|
const maxNodes = readPositiveInt(options.maxNodes);
|
|
29609
29628
|
const center = readOption(options.center);
|
|
@@ -29649,6 +29668,7 @@ const KNOWLEDGE_SYNC_DEFAULT_TTL_MINUTES = 15;
|
|
|
29649
29668
|
// 500 batches × the server's 100-page default is far above any v1 wiki; the cap
|
|
29650
29669
|
// only exists so a misbehaving server can never wedge the CLI in a loop.
|
|
29651
29670
|
const KNOWLEDGE_SYNC_MAX_BATCHES = 500;
|
|
29671
|
+
const KNOWLEDGE_SYNC_CATALOG_MAX_BATCHES = 500;
|
|
29652
29672
|
// Envelope error codes meaning this org can no longer be read with the stored
|
|
29653
29673
|
// token (revoked membership, expired key, org mismatch). The mirror is a local
|
|
29654
29674
|
// cache of tenant data and must not outlive access to it.
|
|
@@ -29779,6 +29799,43 @@ function applyKnowledgeSyncPage(dir, state, page, counters) {
|
|
|
29779
29799
|
};
|
|
29780
29800
|
counters.synced += 1;
|
|
29781
29801
|
}
|
|
29802
|
+
async function readKnowledgeMirrorCatalog(requestOrg, mirrorPath) {
|
|
29803
|
+
let cursor = null;
|
|
29804
|
+
const seenCursors = new Set();
|
|
29805
|
+
const pages = new Map();
|
|
29806
|
+
for (let batch = 0; batch < KNOWLEDGE_SYNC_CATALOG_MAX_BATCHES; batch += 1) {
|
|
29807
|
+
const query = new URLSearchParams({ max_chars: "48000", limit: "1000", ...(cursor ? { cursor } : {}) });
|
|
29808
|
+
const window = await requestOxygen(`/api/cli/knowledge/index?${query}`, requestOrg);
|
|
29809
|
+
if (typeof window.has_more !== "boolean" || !Number.isInteger(window.max_chars) || window.max_chars < 2000 || window.max_chars > 48000 ||
|
|
29810
|
+
!Array.isArray(window.index?.pages) || window.index.capped !== window.has_more ||
|
|
29811
|
+
(window.next_cursor !== null && typeof window.next_cursor !== "string") || (!window.has_more && window.next_cursor !== null)) {
|
|
29812
|
+
throw new OxygenError("knowledge_sync_catalog_invalid", "The knowledge catalog did not return a complete bounded continuation receipt. The generated index was left unchanged.", {
|
|
29813
|
+
details: { catalog_cursor: cursor, mirror_path: mirrorPath }, exitCode: 1,
|
|
29814
|
+
});
|
|
29815
|
+
}
|
|
29816
|
+
for (const page of window.index.pages) {
|
|
29817
|
+
if (!page || typeof page.slug !== "string" || !page.slug || pages.has(page.slug)) {
|
|
29818
|
+
throw new OxygenError("knowledge_sync_catalog_changed", "The catalog repeated or omitted a page slug during live traversal. Retry sync to restart the catalog; the generated index was left unchanged.", {
|
|
29819
|
+
details: { catalog_cursor: cursor, mirror_path: mirrorPath }, exitCode: 1,
|
|
29820
|
+
});
|
|
29821
|
+
}
|
|
29822
|
+
pages.set(page.slug, page);
|
|
29823
|
+
}
|
|
29824
|
+
if (!window.has_more) {
|
|
29825
|
+
return { ...window, index: { ...window.index, pages: [...pages.values()] } };
|
|
29826
|
+
}
|
|
29827
|
+
if (!window.next_cursor || seenCursors.has(window.next_cursor)) {
|
|
29828
|
+
throw new OxygenError("knowledge_sync_catalog_stalled", "The catalog cursor did not advance; the generated index was left unchanged. Retry sync to restart the catalog from the preserved page watermark.", {
|
|
29829
|
+
details: { catalog_cursor: cursor, next_cursor: window.next_cursor, mirror_path: mirrorPath }, exitCode: 1,
|
|
29830
|
+
});
|
|
29831
|
+
}
|
|
29832
|
+
cursor = window.next_cursor;
|
|
29833
|
+
seenCursors.add(cursor);
|
|
29834
|
+
}
|
|
29835
|
+
throw new OxygenError("knowledge_sync_catalog_incomplete", "The catalog batch cap was reached before its terminal receipt. The generated index was left unchanged; retry sync to restart the catalog from the preserved page watermark.", {
|
|
29836
|
+
details: { catalog_cursor: cursor, mirror_path: mirrorPath, batch_cap: KNOWLEDGE_SYNC_CATALOG_MAX_BATCHES }, exitCode: 1,
|
|
29837
|
+
});
|
|
29838
|
+
}
|
|
29782
29839
|
// `orgOverride` targets a specific org (used by `sync --all`): it names the mirror
|
|
29783
29840
|
// directory explicitly and pins X-Oxygen-Organization on every request in the
|
|
29784
29841
|
// loop, so each workspace clones into its own mirror regardless of the cached
|
|
@@ -29842,11 +29899,12 @@ options, orgOverride) {
|
|
|
29842
29899
|
lastSeen = { updated_at: page.updated_at, id: page.id };
|
|
29843
29900
|
}
|
|
29844
29901
|
if (!delta.has_more) {
|
|
29845
|
-
//
|
|
29846
|
-
//
|
|
29847
|
-
//
|
|
29848
|
-
|
|
29849
|
-
|
|
29902
|
+
// The native SQL watermark retains timestamp precision that the public
|
|
29903
|
+
// updated_at Date can lose. Older servers lack last_cursor; retain their
|
|
29904
|
+
// compatibility fallback without claiming it preserves SQL precision.
|
|
29905
|
+
if (delta.last_cursor)
|
|
29906
|
+
state.cursor = delta.last_cursor;
|
|
29907
|
+
else if (lastSeen)
|
|
29850
29908
|
state.cursor = `${lastSeen.updated_at}|${lastSeen.id}`;
|
|
29851
29909
|
completed = true;
|
|
29852
29910
|
break;
|
|
@@ -29868,19 +29926,18 @@ options, orgOverride) {
|
|
|
29868
29926
|
exitCode: 1,
|
|
29869
29927
|
});
|
|
29870
29928
|
}
|
|
29871
|
-
//
|
|
29872
|
-
//
|
|
29873
|
-
|
|
29874
|
-
|
|
29875
|
-
|
|
29876
|
-
|
|
29877
|
-
|
|
29878
|
-
|
|
29879
|
-
|
|
29880
|
-
|
|
29881
|
-
if (!state.pages.index)
|
|
29929
|
+
// Save page progress before summary reads so a failed catalog can resume its
|
|
29930
|
+
// delta without declaring a fresh, fully completed mirror.
|
|
29931
|
+
state.last_sync_at = null;
|
|
29932
|
+
writeMirrorState(target.dir, state);
|
|
29933
|
+
// Traverse every native catalog window, including shared-only continuations.
|
|
29934
|
+
// Its scope excludes archived/proposal pages; it is a live keyset, not a
|
|
29935
|
+
// snapshot of every cloned page. Real `index`/`log` page bytes take priority.
|
|
29936
|
+
const index = state.pages.index ? null : await readKnowledgeMirrorCatalog(requestOrg, target.dir);
|
|
29937
|
+
const logPage = state.pages.log ? null : await requestOxygen("/api/cli/knowledge/log", requestOrg);
|
|
29938
|
+
if (index)
|
|
29882
29939
|
writeGeneratedIndexFile(target.dir, index.index);
|
|
29883
|
-
if (
|
|
29940
|
+
if (logPage)
|
|
29884
29941
|
writeGeneratedLogFile(target.dir, logPage.entries);
|
|
29885
29942
|
state.last_sync_at = new Date().toISOString();
|
|
29886
29943
|
writeMirrorState(target.dir, state);
|
|
@@ -30569,6 +30626,10 @@ function readSequenceSettings(options) {
|
|
|
30569
30626
|
if (linkedinSchedule) {
|
|
30570
30627
|
settings.schedule = linkedinSchedule;
|
|
30571
30628
|
}
|
|
30629
|
+
if (options.linkedinInviteSchedule !== undefined)
|
|
30630
|
+
settings.linkedin_invite_schedule = parseJsonValue(options.linkedinInviteSchedule, "--linkedin-invite-schedule");
|
|
30631
|
+
if (options.linkedinMessageSchedule !== undefined)
|
|
30632
|
+
settings.linkedin_message_schedule = parseJsonValue(options.linkedinMessageSchedule, "--linkedin-message-schedule");
|
|
30572
30633
|
// Tri-state: `--no-whatsapp-cold-initiate` (update) turns the opt-in off.
|
|
30573
30634
|
if (options.whatsappColdInitiate !== undefined) {
|
|
30574
30635
|
settings.whatsapp_cold_initiate = options.whatsappColdInitiate;
|
package/dist/skills.js
CHANGED
|
@@ -7,6 +7,7 @@ import { defaultApiUrl, loadCredentials, normalizeApiUrl } from "./credentials.j
|
|
|
7
7
|
import { requestOxygen } from "./http-client.js";
|
|
8
8
|
import { isRecord, readOption } from "./util.js";
|
|
9
9
|
import { resolveCmdShimInvocation } from "./windows-shim.js";
|
|
10
|
+
import { capabilityDiscoveryRecovery } from "@oxygen/shared/user-capability-routing";
|
|
10
11
|
const DEFAULT_SKILL_AGENTS = ["codex", "claude-code", "cursor"];
|
|
11
12
|
const SKILL_INDEX_PATH = "/.well-known/skills/index.json";
|
|
12
13
|
const SKILLS_ACCESS_API_PATH = "/api/cli/skills/access";
|
|
@@ -144,6 +145,7 @@ export async function searchAgentSkills(query, options, runtime = {}) {
|
|
|
144
145
|
return {
|
|
145
146
|
query: normalized,
|
|
146
147
|
route: serializeCapabilityRoute(route, { visibleSkills: index.skill_names }),
|
|
148
|
+
...(!route ? { hint: capabilityDiscoveryRecovery(normalized, "cli") } : {}),
|
|
147
149
|
total_matches: matches.length,
|
|
148
150
|
returned: Math.min(limit, matches.length),
|
|
149
151
|
skills: matches.slice(0, limit).map(({ skill, score, matching_documents }) => ({
|
|
@@ -1018,6 +1018,18 @@ const CRM_ENRICHMENT_READ_VIEW = {
|
|
|
1018
1018
|
};
|
|
1019
1019
|
/** The card a route reports: Publishing's own, or its UGC view for a creator-program ask. */
|
|
1020
1020
|
function routeCardFor(card, query) {
|
|
1021
|
+
const placementGuide = card.id === "sending-infrastructure" ? placementGuideRecommendation(query) : null;
|
|
1022
|
+
if (placementGuide)
|
|
1023
|
+
card = { ...card, posture: "read_only", execution: placementGuide.execution };
|
|
1024
|
+
const lookup = card.id === "tables" ? linkedinUrlFindingRecommendation(query) : null;
|
|
1025
|
+
if (lookup)
|
|
1026
|
+
card = { ...card, execution: lookup.execution };
|
|
1027
|
+
const pageResearch = card.id === "sourcing-and-provider-tools" ? pageScrapingRecommendation(query) : null;
|
|
1028
|
+
if (pageResearch)
|
|
1029
|
+
card = { ...card, execution: pageResearch.execution };
|
|
1030
|
+
const collaboration = card.id === "collaboration" ? collaborationRecommendation(query) : null;
|
|
1031
|
+
if (collaboration)
|
|
1032
|
+
card = { ...card, execution: collaboration.execution, posture: collaboration.posture };
|
|
1021
1033
|
if (card.id === "records" && isCrmEnrichmentConfigurationRead(query))
|
|
1022
1034
|
return { ...card, ...CRM_ENRICHMENT_READ_VIEW };
|
|
1023
1035
|
const crmMaintenance = card.id === "records" ? crmMaintenanceRecommendation(query) : null;
|
|
@@ -1115,6 +1127,8 @@ function routeCardFor(card, query) {
|
|
|
1115
1127
|
/** Search surfaces share the selected skill owner; a general router is fallback. */
|
|
1116
1128
|
function preferredSkillForIntent(card, query) {
|
|
1117
1129
|
if (card.id === "tables") {
|
|
1130
|
+
if (linkedinUrlFindingRecommendation(query))
|
|
1131
|
+
return "oxygen-enrichment";
|
|
1118
1132
|
if (/\bclay\b/.test(query) && /\b(import|export|migrat\w*|mov\w*)\b/.test(query))
|
|
1119
1133
|
return "oxygen-clay-migration";
|
|
1120
1134
|
if (isTableMaintenanceIntent(query))
|
|
@@ -1926,6 +1940,8 @@ function explicitCapabilityIntent(query) {
|
|
|
1926
1940
|
if (isWorkspacePagesIntent(query) || isWorkspaceSettingsIntent(query)) {
|
|
1927
1941
|
return ROUTE_BY_ID.get("workspace-access") ?? null;
|
|
1928
1942
|
}
|
|
1943
|
+
if (collaborationRecommendation(query))
|
|
1944
|
+
return ROUTE_BY_ID.get("collaboration") ?? null;
|
|
1929
1945
|
if (/\btables?\b/.test(query) && isSecondPartyDecisionIntent(query))
|
|
1930
1946
|
return ROUTE_BY_ID.get("collaboration") ?? null;
|
|
1931
1947
|
if (tableReadRecommendation(query) || tableMaintenanceRecommendation(query))
|
|
@@ -2009,6 +2025,8 @@ function explicitCapabilityIntent(query) {
|
|
|
2009
2025
|
}
|
|
2010
2026
|
if (isHostedWorkflowIntent(query))
|
|
2011
2027
|
return ROUTE_BY_PRIMITIVE.get("workflows") ?? null;
|
|
2028
|
+
if (placementGuideRecommendation(query))
|
|
2029
|
+
return ROUTE_BY_ID.get("sending-infrastructure") ?? null;
|
|
2012
2030
|
if (isCompanySourcingIntent(query))
|
|
2013
2031
|
return ROUTE_BY_ID.get("sourcing-and-provider-tools") ?? null;
|
|
2014
2032
|
if (isOwnedPostCommentIntent(query))
|
|
@@ -2053,6 +2071,8 @@ function explicitCapabilityIntent(query) {
|
|
|
2053
2071
|
// research input.
|
|
2054
2072
|
if (isNetNewLinkedInInitiation(query))
|
|
2055
2073
|
return ROUTE_BY_PRIMITIVE.get("sequences") ?? null;
|
|
2074
|
+
if (linkedinUrlFindingRecommendation(query))
|
|
2075
|
+
return ROUTE_BY_PRIMITIVE.get("tables") ?? null;
|
|
2056
2076
|
if (isPublicLinkedInRead(query))
|
|
2057
2077
|
return ROUTE_BY_ID.get("linkedin-public-research") ?? null;
|
|
2058
2078
|
if (/\b(my|our|own)\b.{0,40}\blinkedin\b.{0,40}\b(post )?engagers?\b/.test(query)) {
|
|
@@ -2134,6 +2154,10 @@ function explicitCapabilityIntent(query) {
|
|
|
2134
2154
|
// company_enrich preset instead.
|
|
2135
2155
|
if (isCompanyUrlRecoveryIntent(query))
|
|
2136
2156
|
return ROUTE_BY_ID.get("sourcing-and-provider-tools") ?? null;
|
|
2157
|
+
if (pageScrapingRecommendation(query))
|
|
2158
|
+
return ROUTE_BY_ID.get("sourcing-and-provider-tools") ?? null;
|
|
2159
|
+
if (/\bagency partners?\b/.test(query))
|
|
2160
|
+
return ROUTE_BY_ID.get("agency-directory") ?? null;
|
|
2137
2161
|
// A category-plus-place ask ("dental practices in Austin", "local businesses",
|
|
2138
2162
|
// "scrape Google Maps") is the companies-search `local` intent. A 2026-09-21
|
|
2139
2163
|
// blind user typing "find local businesses like dental practices in a city"
|
|
@@ -2212,6 +2236,18 @@ function isPublishingPostIntent(query) {
|
|
|
2212
2236
|
&& !(/\b(find|search|scrape|research)\b/.test(query) && /\b(about|mention|containing)\b/.test(query));
|
|
2213
2237
|
}
|
|
2214
2238
|
function recommendationsFor(card, query) {
|
|
2239
|
+
const placementGuide = card.id === "sending-infrastructure" ? placementGuideRecommendation(query) : null;
|
|
2240
|
+
if (placementGuide)
|
|
2241
|
+
return placementGuide;
|
|
2242
|
+
const lookup = card.id === "tables" ? linkedinUrlFindingRecommendation(query) : null;
|
|
2243
|
+
if (lookup)
|
|
2244
|
+
return lookup;
|
|
2245
|
+
const pageResearch = card.id === "sourcing-and-provider-tools" ? pageScrapingRecommendation(query) : null;
|
|
2246
|
+
if (pageResearch)
|
|
2247
|
+
return pageResearch;
|
|
2248
|
+
const collaboration = card.id === "collaboration" ? collaborationRecommendation(query) : null;
|
|
2249
|
+
if (collaboration)
|
|
2250
|
+
return collaboration;
|
|
2215
2251
|
if (card.id === "knowledge-graph" && isWorkspaceSkillIntent(query)) {
|
|
2216
2252
|
return { tools: ["oxygen_workspace_skills_list", "oxygen_workspace_skills_create", "oxygen_workspace_skills_import", "oxygen_workspace_skills_get", "oxygen_workspace_skills_update", "oxygen_workspace_skills_archive"],
|
|
2217
2253
|
commands: ["skills workspace list", "skills workspace create", "skills workspace import", "skills workspace get", "skills workspace update", "skills workspace archive"] };
|
|
@@ -2930,6 +2966,48 @@ function isRowClassificationIntent(query) {
|
|
|
2930
2966
|
function isCompanyResearchQuestionIntent(query) {
|
|
2931
2967
|
return /\b(founders?|founded by|parent compan(?:y|ies)|subsidiar(?:y|ies)|cloud provider|naics|column templates?|template catalog|ready[- ]made|research question|company research|offers? demos?|gives? demos?)\b/.test(query);
|
|
2932
2968
|
}
|
|
2969
|
+
/** Supporting placement instructions belong to deliverability, even when the ask names an inbox. */
|
|
2970
|
+
function placementGuideRecommendation(query) {
|
|
2971
|
+
if (!/\bplacement(?:[- ]tests?)?\b/.test(query)
|
|
2972
|
+
|| !/\b(guidance|guide|instructions|supporting|read|docs?|documentation)\b/.test(query)
|
|
2973
|
+
|| /\b(run|send|create|connect|enable)\b/.test(query))
|
|
2974
|
+
return null;
|
|
2975
|
+
return {
|
|
2976
|
+
tools: ["oxygen_skills_get"],
|
|
2977
|
+
commands: ["skills get"],
|
|
2978
|
+
exactCommand: "oxygen skills get oxygen-deliverability --file references/placement.md --json",
|
|
2979
|
+
exactMcpCall: { tool: "oxygen_skills_get", arguments: { name: "oxygen-deliverability", file: "references/placement.md" } },
|
|
2980
|
+
execution: "Read oxygen-deliverability, references/placement.md for placement-test guidance and references/emailguard.md for inbox monitoring. Instruction reads do not run a test or enroll a mailbox. Keep the live preview, sender scope, credit ceiling and separate approvals for provider provisioning and test sends.",
|
|
2981
|
+
};
|
|
2982
|
+
}
|
|
2983
|
+
/** OXP-43.72: pricing qualifies the lookup or scrape, rather than changing its owner. */
|
|
2984
|
+
function linkedinUrlFindingRecommendation(query) {
|
|
2985
|
+
if (!/\blinkedin\b/.test(query) || !/\b(urls?|links?)\b/.test(query)
|
|
2986
|
+
|| !/\b(find|finding|lookup|look up|resolve|missing)\b/.test(query)
|
|
2987
|
+
|| /\b(scrape\w*|posts?|comments?|engagers?|followers?|connect|inbox|send\w*|publish\w*)\b/.test(query)
|
|
2988
|
+
|| /\bcompany pages?\b/.test(query))
|
|
2989
|
+
return null;
|
|
2990
|
+
return {
|
|
2991
|
+
tools: ["oxygen_tools_search", "oxygen_columns_add", "oxygen_enrich_column_preview"],
|
|
2992
|
+
commands: ["tools search", "columns add", "enrich-column preview"],
|
|
2993
|
+
exactCommand: "oxygen tools search find_linkedin_url --json",
|
|
2994
|
+
exactMcpCall: { tool: "oxygen_tools_search", arguments: { query: "find_linkedin_url" } },
|
|
2995
|
+
execution: "Inspect the native LinkedIn URL waterfall and current pricing with tools search find_linkedin_url. Read oxygen-enrichment and its references/linkedin-url.md. Add the linkedin_url capability to existing rows, then preview the exact row scope for free before an approved run with a credit ceiling. Known-profile scraping is a separate operation.",
|
|
2996
|
+
};
|
|
2997
|
+
}
|
|
2998
|
+
function pageScrapingRecommendation(query) {
|
|
2999
|
+
if (!/\b(scrap(?:e|ing)|crawl\w*|fetch|extract)\b/.test(query)
|
|
3000
|
+
|| !/\b(pages?|websites?|web ?sites?|web content|urls?)\b/.test(query)
|
|
3001
|
+
|| /\b(linkedin|emails?|phones?|crm|records?)\b/.test(query))
|
|
3002
|
+
return null;
|
|
3003
|
+
return {
|
|
3004
|
+
tools: ["oxygen_tools_search", "oxygen_tools_get"],
|
|
3005
|
+
commands: ["tools search", "tools get"],
|
|
3006
|
+
exactCommand: "oxygen tools search firecrawl --json",
|
|
3007
|
+
exactMcpCall: { tool: "oxygen_tools_search", arguments: { query: "firecrawl" } },
|
|
3008
|
+
execution: "Discover public-page operations with tools search firecrawl, then hydrate one exact tool and its pricing. Read oxygen-sourcing and provider-runbooks/firecrawl.md. Preview the selected URLs and price before execution; live scraping requires approval for the exact URL scope and credit ceiling. For durable per-row research, use native Tables research columns. Discovery does not run a scrape.",
|
|
3009
|
+
};
|
|
3010
|
+
}
|
|
2933
3011
|
function isCompanyUrlRecoveryIntent(query) {
|
|
2934
3012
|
const urlNoun = /\b(websites?|web ?sites?|website urls?|domains?|urls?|homepages?)\b/;
|
|
2935
3013
|
const companyNoun = /\b(compan(?:y|ies)|accounts?|organi[sz]ations?|brands?|vendors?)\b/;
|
|
@@ -3109,6 +3187,50 @@ function isNetNewLinkedInInitiation(query) {
|
|
|
3109
3187
|
return false;
|
|
3110
3188
|
return !/\b(sequence|campaign|cadence|enroll|nurture|multi[ -]step|multi[ -]recipient|leads|contacts|recipients|audience|rotate senders?|stop on reply|day\s*\d+)\b/.test(query);
|
|
3111
3189
|
}
|
|
3190
|
+
/** Recommendations point to the mounted native owners, without conferring their write authority. */
|
|
3191
|
+
function collaborationRecommendation(query) {
|
|
3192
|
+
const memberRead = { tools: ["oxygen_workspace_members_list"], commands: ["approvals client list"],
|
|
3193
|
+
execution: "Read workspace member roles, Clerk profiles and pending invitations. This does not invite anyone or change access. Free.", posture: "read_only" };
|
|
3194
|
+
const member = /\b(members?|memberships?)\b/.test(query);
|
|
3195
|
+
const workspace = /\b(workspace|organization|org)\b/.test(query);
|
|
3196
|
+
const clientRole = /\bclient (?:role|access|login)\b/.test(query);
|
|
3197
|
+
const roleWrite = /\b(set|change|grant|clear|remove|revoke|restrict|promote|demote)\b/.test(query);
|
|
3198
|
+
if (clientRole && roleWrite && !/\b(crm|records?|contacts?|companies|api|oauth)\b/.test(query)) {
|
|
3199
|
+
const clear = /\b(clear|remove|revoke)\b/.test(query);
|
|
3200
|
+
return { tools: ["oxygen_members_client_role_set", ...memberRead.tools], commands: [clear ? "approvals client revoke" : "approvals client grant", ...memberRead.commands],
|
|
3201
|
+
execution: "An admin can set or clear the restricted client overlay; a pending invite is checked in Clerk. This sends no invitation and does not change the Clerk workspace role. Free.", posture: "workspace_write" };
|
|
3202
|
+
}
|
|
3203
|
+
if (workspace && member && roleWrite && /\b(roles?|admin|administrator)\b/.test(query))
|
|
3204
|
+
return {
|
|
3205
|
+
tools: ["oxygen_members_workspace_role_set", ...memberRead.tools], commands: ["orgs member-role", ...memberRead.commands],
|
|
3206
|
+
execution: "An admin changes a member's workspace role in Clerk before Oxygen mirrors it. Self-role changes and the last administrator are protected; the client overlay is separate. Free.", posture: "workspace_write",
|
|
3207
|
+
};
|
|
3208
|
+
if (((workspace && member) || clientRole || /\bwho is in (?:my|our|this|the) workspace\b/.test(query))
|
|
3209
|
+
&& !/\b(invite|add|delete|cancel|enable|disable|set|change|grant|clear|remove|revoke|restrict|promote|demote)\b/.test(query))
|
|
3210
|
+
return memberRead;
|
|
3211
|
+
if (/\b(?:approval|launch|sign ?off) gates?\b/.test(query)) {
|
|
3212
|
+
const write = /\b(arm|set|enable|disable|require|change|turn)\b/.test(query);
|
|
3213
|
+
return { tools: write ? ["oxygen_approvals_gate_set", "oxygen_approval_gates_list"] : ["oxygen_approval_gates_list", "oxygen_approvals_gate_set"],
|
|
3214
|
+
commands: write ? ["approvals gate set", "approvals gate list"] : ["approvals gate list", "approvals gate set"],
|
|
3215
|
+
execution: write ? "An admin configures the approval gate; this grants no approval. Launch gates a sequence; signoff gates a table. The owning primitive enforces it. Free."
|
|
3216
|
+
: "Read configured approval gates and effective state for an exact subject. Reading a gate grants no approval. Free.", posture: write ? "workspace_write" : "read_only" };
|
|
3217
|
+
}
|
|
3218
|
+
if (/\bcomments?\b/.test(query) && /\b(internal|workspace|teammate|client)\b/.test(query)
|
|
3219
|
+
&& !/\b(linkedin|public|published|posts?|publishing)\b/.test(query)) {
|
|
3220
|
+
if (/\b(edit|update|correct)\b/.test(query))
|
|
3221
|
+
return { tools: ["oxygen_comments_edit", "oxygen_comments_list"], commands: ["comments edit", "comments list"],
|
|
3222
|
+
execution: "Edit your own internal workspace comment by comment id; the author check and edit time are enforced by its native owner. Free.", posture: "workspace_write" };
|
|
3223
|
+
if (/\b(delete|retract|remove)\b/.test(query))
|
|
3224
|
+
return { tools: ["oxygen_comments_delete", "oxygen_comments_list"], commands: ["comments delete", "comments list"],
|
|
3225
|
+
execution: "Retract your own internal comment by comment id. Its audit placeholder remains; this does not delete a public post comment. Free.", posture: "workspace_write" };
|
|
3226
|
+
}
|
|
3227
|
+
if (/\b(cancel|withdraw|retract)\b/.test(query) && /\b(?:approval|sign ?off) requests?\b/.test(query))
|
|
3228
|
+
return {
|
|
3229
|
+
tools: ["oxygen_approvals_cancel", "oxygen_approvals_list"], commands: ["approvals cancel", "approvals list"],
|
|
3230
|
+
execution: "Withdraw an approval request you created, by request id; this grants no approval. The requester check remains enforced by the native owner. Free.", posture: "workspace_write",
|
|
3231
|
+
};
|
|
3232
|
+
return null;
|
|
3233
|
+
}
|
|
3112
3234
|
function isSecondPartyDecisionIntent(query) {
|
|
3113
3235
|
const decisionLanguage = /\b(sign ?off|signoff|approval request|request approval|approval gate|go no go)\b/.test(query);
|
|
3114
3236
|
const clientDecision = /\bclient\b.{0,20}\b(approv\w*|sign ?off|review|decide|decision|login|role)\b/.test(query)
|
|
@@ -97,15 +97,25 @@ export const BROWSER_OPERATIONS = [
|
|
|
97
97
|
operation("tables.resource.runs.cancel", `${run}/cancel`, "POST", "tables runs stop", "oxygen_table_resource_run_stop", "runs", "Cancel one durable Table run; never repeats external provider effects."),
|
|
98
98
|
operation("tables.resource.runs.pause", `${run}/pause`, "POST", "tables runs pause", "oxygen_table_resource_run_pause", "runs", "Pause one durable Table run without discarding its approved scope."),
|
|
99
99
|
operation("tables.resource.runs.resume", `${run}/resume`, "POST", "tables runs resume", "oxygen_table_resource_run_resume", "runs", "Resume a paused Table run within its existing approved scope and credit ceiling."),
|
|
100
|
-
operation("tables.resource.runs.retry", `${run}/retry-failed`, "POST", "tables runs retry", "oxygen_table_resource_run_retry", "runs", "Retry failed cells
|
|
100
|
+
operation("tables.resource.runs.retry", `${run}/retry-failed`, "POST", "tables runs retry", "oxygen_table_resource_run_retry", "runs", "Retry failed cells; a fresh allowance requires approved, retry_plan_hash and max_additional_credits. Uncertain effects require separate approval.", { approved_effect_unknown: bool(), approved: bool(), retry_plan_hash: text(), max_additional_credits: num({ minimum: 0 }) }, [], { spends: true }),
|
|
101
101
|
operation("tables.resource.runs.provider_summary", `${run}/provider-summary`, "GET", "tables runs provider-summary", "oxygen_table_resource_run_provider_summary", "runs", "Read a Table run's provider outcomes, fulfilled rows, costs and timing."),
|
|
102
102
|
operation("tables.resource.ingestions.cancel", `${table}/ingestions/{ingestion_run}/cancel`, "POST", "tables ingestion stop", "oxygen_table_resource_ingestion_stop", "runs", "Cancel this Table's active hosted ingestion run."),
|
|
103
103
|
operation("tables.resource.ingestions.retry", `${table}/ingestions/{ingestion_run}/retry-failed`, "POST", "tables ingestion retry", "oxygen_table_resource_ingestion_retry", "runs", "Retry failed items in this Table's hosted ingestion run."),
|
|
104
104
|
operation("tables.resource.import", "/api/cli/tables/import", "POST", "tables upload", "oxygen_table_resource_upload", "tables", "Preview or import small uploaded CSV, JSON or XLSX bytes through the exact browser import contract. Preview writes no rows.", upload, ["content_base64", "file_name"], { multipart }),
|
|
105
105
|
operation("billing.activate", "/api/cli/billing/activate", "POST", "billing activate", "oxygen_billing_activate", "billing", "Activate a trial subscription now and charge its saved payment method; administrator approval is required. An optional tier reprices the active trial in the same call.", { approved: bool(), tier: text() }, ["approved"], { spends: true }),
|
|
106
|
-
operation("billing.checkout", "/api/cli/billing/checkout", "POST", "billing checkout", "oxygen_billing_checkout", "billing", "Open
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
operation("billing.checkout", "/api/cli/billing/checkout", "POST", "billing checkout", "oxygen_billing_checkout", "billing", "Open browser Checkout; payment approves purchase. First purchase: admin/owner. New yearly plans use interval month, term year and the current terms_version.", {
|
|
107
|
+
plan: text("Plan key from billing balance available_plans, for example oxygen_49."),
|
|
108
|
+
interval: enumOf(["month", "year"], { description: "New purchases use month. year is legacy prepaid cadence, refused for new purchases." }),
|
|
109
|
+
term: enumOf(["month", "year"], { description: "year commits to twelve discounted monthly payments; month is the default." }),
|
|
110
|
+
terms_version: text("Required for term year: annual_monthly_terms.terms_version from billing balance."),
|
|
111
|
+
currency: text("Billing currency: usd (default)."),
|
|
112
|
+
trial: bool({ description: "Retired compatibility option; omit for a new paid subscription." }),
|
|
113
|
+
return_to: text("Same-origin path to return to after Checkout, for example /home."),
|
|
114
|
+
source: text("Optional upgrade-entry attribution; omit if unknown."),
|
|
115
|
+
blocked: text("Optional capability that led to the upgrade; omit if unknown."),
|
|
116
|
+
}, ["plan"]),
|
|
117
|
+
operation("billing.confirm", "/api/cli/billing/confirm", "POST", "billing confirm", "oxygen_billing_confirm", "billing", "Confirm a plan-change preview within 15 minutes; approved accepts payment, term and effective date. Unrestricted members allowed; restricted clients denied.", { token: text(), approved: bool() }, ["token", "approved"], { spends: true }),
|
|
118
|
+
operation("billing.portal", "/api/cli/billing/portal", "POST", "billing portal", "oxygen_billing_portal", "billing", "Create a Stripe billing-portal URL. Unrestricted members allowed; restricted clients denied.", { returnUrl: text() }),
|
|
109
119
|
operation("billing.ownership.get", "/api/cli/billing/ownership", "GET", "billing ownership get", "oxygen_billing_ownership_get", "billing", "Read the active workspace's shared billing owner, candidate owners and credit cap."),
|
|
110
120
|
operation("billing.ownership.set", "/api/cli/billing/ownership", "POST", "billing ownership set", "oxygen_billing_ownership_set", "billing", "Set the billing owner and optional monthly credit cap using the browser's administrator and workspace eligibility checks.", { billing_owner_organization_id: text(), monthly_credit_cap: nullable(num({ minimum: 0 })) }, ["billing_owner_organization_id"]),
|
|
111
121
|
operation("billing.ownership.clear", "/api/cli/billing/ownership", "DELETE", "billing ownership clear", "oxygen_billing_ownership_clear", "billing", "Restore this workspace's own billing ownership after the same administrator checks as the browser."),
|
|
@@ -8,6 +8,7 @@ import { COPILOT_SESSIONS_LIST_CONTRACT } from "./copilot-sessions.js";
|
|
|
8
8
|
import { CRM_FILE_ATTACH_CONTRACT, CRM_FILE_ATTACH_DESCRIPTION } from "./crm-support.js";
|
|
9
9
|
import { spintaxContract } from "./spintax.js";
|
|
10
10
|
import { betaFeaturesUpdateContract, emailDomainSuppressionListContract, emailDomainSuppressionRemoveContract, emailSuppressionRemoveContract, providerLimitListContract, providerLimitSelectorContract, providerLimitUpdateContract } from "./workspace-utilities.js";
|
|
11
|
+
import { knowledgeSyncReadContract } from "./knowledge-sync.js";
|
|
11
12
|
const text = (description) => str(description === undefined ? {} : { description });
|
|
12
13
|
const list = () => arr(str());
|
|
13
14
|
const json = { type: "object", additionalProperties: true };
|
|
@@ -34,8 +35,8 @@ export const CUSTOMER_OPERATIONS = [
|
|
|
34
35
|
operation("comments.edit", "/api/cli/collab/comments/{comment_id}", "PATCH", "comments edit", "oxygen_comments_edit", "collaboration", "Edit your own internal workspace comment; records the edit time.", { body: text() }, ["body"], mcpOnly),
|
|
35
36
|
operation("comments.delete", "/api/cli/collab/comments/{comment_id}", "DELETE", "comments delete", "oxygen_comments_delete", "collaboration", "Retract your own internal workspace comment while retaining its audit trail.", {}, [], mcpOnly),
|
|
36
37
|
operation("approvals.cancel", "/api/cli/collab/requests/{request_id}/cancel", "POST", "approvals cancel", "oxygen_approvals_cancel", "collaboration", "Withdraw an approval request you created; grants no approval.", { note: text() }, [], mcpOnly),
|
|
37
|
-
operation("members.client_role", "/api/cli/collab/members/role", "POST", "approvals client grant", "oxygen_members_client_role_set", "collaboration", "Set or clear
|
|
38
|
-
operation("members.workspace_role", "/api/cli/collab/members/workspace-role", "POST", "orgs member-role", "oxygen_members_workspace_role_set", "collaboration", "Change a member's workspace role; admin only and
|
|
38
|
+
operation("members.client_role", "/api/cli/collab/members/role", "POST", "approvals client grant", "oxygen_members_client_role_set", "collaboration", "Set or clear the restricted client overlay; admin only. May read pending Clerk invitations; sends no invitation. Free.", { email: text(), role: nullable(enumOf(["client"])) }, ["email", "role"], { ...mcpOnly, annotations: { openWorldHint: true } }),
|
|
39
|
+
operation("members.workspace_role", "/api/cli/collab/members/workspace-role", "POST", "orgs member-role", "oxygen_members_workspace_role_set", "collaboration", "Change a member's workspace role in Clerk; admin only, with self-role and last-administrator protection. Free.", { email: text(), role: enumOf(["admin", "member"]) }, ["email", "role"], { ...mcpOnly, annotations: { openWorldHint: true } }),
|
|
39
40
|
operation("copilot.sessions.list", "/api/cli/copilot/sessions", "GET", "copilot list", "oxygen_copilot_sessions_list", "agents", "List attended Copilot sessions in this workspace.", COPILOT_SESSIONS_LIST_CONTRACT.properties, [], mcpOnly),
|
|
40
41
|
operation("copilot.sessions.update", "/api/cli/copilot/sessions/{session_id}", "PATCH", "copilot policy set", "oxygen_copilot_session_update", "agents", "Set an attended Copilot session's approval and screen-follow policies. Paid auto-approval is explicit standing authorization.", { auto_approve_policy: enumOf(["off", "paid"]), surface_policy: enumOf(["off", "follow"]) }, [], { spends: true }),
|
|
41
42
|
operation("copilot.attachments.list", "/api/cli/copilot/sessions/{session_id}/attachments", "GET", "copilot attachments list", "oxygen_copilot_attachments_list", "agents", "List sent and unsent attachments in one Copilot session.", {}, []),
|
|
@@ -64,8 +65,8 @@ export const CUSTOMER_OPERATIONS = [
|
|
|
64
65
|
operation("directory.logo", "/api/cli/directory/listing/logo", "POST", "directory logo upload", "oxygen_directory_logo_upload", "crm", "Upload a public agency-directory logo and read back the updated listing; PNG, JPEG, WebP or SVG, at most 2 MB.", { content_base64: text(), file_name: text(), content_type: enumOf(["image/png", "image/jpeg", "image/webp", "image/svg+xml"]) }, ["content_base64", "file_name", "content_type"], { multipart: { contentField: "content_base64", nameField: "file_name", typeField: "content_type", fileField: "file" } }),
|
|
65
66
|
operation("domains.delete", "/api/cli/domains/delete", "POST", "domains delete", "oxygen_domains_delete", "deliverability", "Preview domain removal and renewal cancellation; execute only with the exact plan hash and confirmation.", { domains: list(), approved: bool(), plan_hash: text(), confirmation: text() }, ["domains"], mcpOnly),
|
|
66
67
|
operation("knowledge.pages.archive", "/api/cli/knowledge/pages/archive", "POST", "knowledge page archive", "oxygen_knowledge_page_archive", "knowledge", "Archive a knowledge page; content/history remain, links become unresolved. Pass expected_revision for the version you reviewed.", { slug: text(), id: text(), expected_revision: int({ minimum: 1 }) }, [], mcpOnly),
|
|
67
|
-
operation("knowledge.sync.manifest", "/api/cli/knowledge/sync/manifest", "POST", "knowledge sync-manifest", "oxygen_knowledge_sync_manifest", "knowledge", "Read
|
|
68
|
-
operation("knowledge.sync.delta", "/api/cli/knowledge/sync/delta", "POST", "knowledge sync", "oxygen_knowledge_sync_delta", "knowledge", "Read
|
|
68
|
+
operation("knowledge.sync.manifest", "/api/cli/knowledge/sync/manifest", "POST", "knowledge sync-manifest", "oxygen_knowledge_sync_manifest", "knowledge", "Read wiki revision/hash metadata. Follow next_cursor for remaining pages; scope is a live keyset, not a snapshot.", {}, [], { contract: knowledgeSyncReadContract, mutates: false }),
|
|
69
|
+
operation("knowledge.sync.delta", "/api/cli/knowledge/sync/delta", "POST", "knowledge sync-delta", "oxygen_knowledge_sync_delta", "knowledge", "Read complete wiki pages/tombstones. First call seeds missing starter pages. Follow next_cursor; an oversized page is refused without skipping. 0 credits.", {}, [], { contract: knowledgeSyncReadContract }),
|
|
69
70
|
operation("limits.provider.get", "/api/cli/limits/provider", "GET", "limits provider list", "oxygen_provider_limits_list", "billing", "Read provider request limits and BYOK warning defaults. MCP defaults to bounded summary pages; follow paging.next_offset with the same filter. HTTP/CLI omit pagination for the full legacy list. 0 credits.", {}, [], { contract: providerLimitListContract, generated: { cli: false, mcp: false } }),
|
|
70
71
|
operation("limits.provider.set", "/api/cli/limits/provider", "POST", "limits provider set", "oxygen_provider_limits_set", "billing", "Set an enforced provider request limit; admin or owner only. Approve the exact provider, operation and window: raising a cap may permit more provider use. 0 credits.", {}, [], { ...mcpOnly, contract: providerLimitUpdateContract }),
|
|
71
72
|
operation("limits.provider.delete", "/api/cli/limits/provider", "DELETE", "limits provider delete", "oxygen_provider_limits_delete", "billing", "Clear one provider request-limit override; admin or owner only. Approve the exact selector. Other operation overrides remain; the provider returns to its defaults, whose BYOK threshold may be observe-only. 0 credits.", {}, [], { ...mcpOnly, contract: providerLimitSelectorContract, queryFields: ["provider", "operation"] }),
|
|
@@ -107,7 +108,7 @@ export const CUSTOMER_OPERATIONS = [
|
|
|
107
108
|
operation("billing.invoices", "/api/cli/billing/invoices", "GET", "billing invoices", "oxygen_billing_invoices", "billing", "Read this workspace's billing invoices and their hosted payment links.", {}, [], mcpOnly),
|
|
108
109
|
operation("billing.seats.set", "/api/cli/billing/seats", "POST", "billing seats set", "oxygen_billing_seats_set", "billing", "Set purchased legacy sending-seat quantity. Retired seats cannot increase; reducing existing seats lowers the next renewal charge.", { seat_key: enumOf(["linkedin", "whatsapp", "phone_number", "email_sender"]), quantity: int({ minimum: 0 }) }, ["seat_key", "quantity"], mcpOnly),
|
|
109
110
|
operation("collaboration.gates.list", "/api/cli/collab/gates", "GET", "approvals gate list", "oxygen_approval_gates_list", "collaboration", "Read this workspace's approval gates, optionally for one exact subject.", { subject: text() }, [], mcpOnly),
|
|
110
|
-
operation("collaboration.members.list", "/api/cli/collab/members", "GET", "approvals client list", "oxygen_workspace_members_list", "collaboration", "Read workspace
|
|
111
|
+
operation("collaboration.members.list", "/api/cli/collab/members", "GET", "approvals client list", "oxygen_workspace_members_list", "collaboration", "Read workspace member roles, with Clerk profiles and pending invitations. Free.", {}, [], { ...mcpOnly, annotations: { openWorldHint: true } }),
|
|
111
112
|
operation("crm.connections", "/api/cli/crm/objects/{object}/records/{row_id}/connections", "GET", "crm connections", "oxygen_crm_connections", "crm", "Read a CRM record's connected conversations, sequence enrollments and related activity.", { conversation_limit: int({ minimum: 1 }), sequence_limit: int({ minimum: 1 }) }, [], mcpOnly),
|
|
112
113
|
operation("crm.tasks.list", "/api/cli/crm/tasks", "GET", "crm tasks list", "oxygen_crm_tasks_queue", "crm", "Read the workspace task queue by status, assignee and cursor.", { status: text(), assignee: text(), cursor: text(), limit: int({ minimum: 1 }) }, [], mcpOnly),
|
|
113
114
|
operation("crm.files.download", "/api/cli/crm/files/{attachment_id}/download", "GET", "crm files download", "oxygen_crm_file_download", "crm", "Download a tenant-owned record attachment by attachment_id. The native authenticated response contains its exact bytes.", {}, [], { binary: { maxBytes: 50 * 1024 * 1024 } }),
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Both sync readers expose complete native pages; no formatter may trim a
|
|
2
|
+
* page after its continuation position has been issued. */
|
|
3
|
+
export declare const knowledgeSyncReadContract: import("./schema.js").ContractObject<{
|
|
4
|
+
readonly cursor: import("./schema.js").StringProperty;
|
|
5
|
+
readonly limit: {
|
|
6
|
+
type: "integer";
|
|
7
|
+
description?: string;
|
|
8
|
+
minimum?: number;
|
|
9
|
+
maximum?: number;
|
|
10
|
+
"x-clamp"?: true;
|
|
11
|
+
};
|
|
12
|
+
readonly max_chars: {
|
|
13
|
+
type: "integer";
|
|
14
|
+
description?: string;
|
|
15
|
+
minimum?: number;
|
|
16
|
+
maximum?: number;
|
|
17
|
+
"x-clamp"?: true;
|
|
18
|
+
};
|
|
19
|
+
}, never>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { int, obj, str } from "./schema.js";
|
|
2
|
+
/** Both sync readers expose complete native pages; no formatter may trim a
|
|
3
|
+
* page after its continuation position has been issued. */
|
|
4
|
+
export const knowledgeSyncReadContract = obj({
|
|
5
|
+
cursor: str({ minLength: 1, maxLength: 256 }),
|
|
6
|
+
limit: int({ minimum: 1, maximum: 200 }),
|
|
7
|
+
max_chars: int({ minimum: 1000, maximum: 48000, description: "Complete receipt window; surface cap applies." }),
|
|
8
|
+
});
|
|
@@ -20,6 +20,7 @@ export declare const publishingReadProjectionContract: ContractObject<{
|
|
|
20
20
|
readonly description?: string;
|
|
21
21
|
readonly minLength?: number;
|
|
22
22
|
readonly maxLength?: number;
|
|
23
|
+
readonly pattern?: string;
|
|
23
24
|
readonly enum: readonly ["full", "summary", "value"];
|
|
24
25
|
};
|
|
25
26
|
readonly value_path: import("./schema.js").StringProperty;
|
|
@@ -40,6 +41,7 @@ export declare const publishingAmplificationListQueryContract: ContractObject<{
|
|
|
40
41
|
readonly description?: string;
|
|
41
42
|
readonly minLength?: number;
|
|
42
43
|
readonly maxLength?: number;
|
|
44
|
+
readonly pattern?: string;
|
|
43
45
|
readonly enum: readonly ["full", "summary", "value"];
|
|
44
46
|
};
|
|
45
47
|
readonly value_path: import("./schema.js").StringProperty;
|
|
@@ -57,6 +59,7 @@ export declare const publishingAmplificationListQueryContract: ContractObject<{
|
|
|
57
59
|
readonly description?: string;
|
|
58
60
|
readonly minLength?: number;
|
|
59
61
|
readonly maxLength?: number;
|
|
62
|
+
readonly pattern?: string;
|
|
60
63
|
readonly enum: readonly ["all", "tag", "post", "ugc_program", "ugc_peer_program"];
|
|
61
64
|
};
|
|
62
65
|
readonly limit: {
|
|
@@ -73,6 +76,7 @@ export declare const publishingAmplificationActionsQueryContract: ContractObject
|
|
|
73
76
|
readonly description?: string;
|
|
74
77
|
readonly minLength?: number;
|
|
75
78
|
readonly maxLength?: number;
|
|
79
|
+
readonly pattern?: string;
|
|
76
80
|
readonly enum: readonly ["full", "summary", "value"];
|
|
77
81
|
};
|
|
78
82
|
readonly value_path: import("./schema.js").StringProperty;
|
|
@@ -99,6 +103,7 @@ export declare const publishingNotesQueryContract: ContractObject<{
|
|
|
99
103
|
readonly description?: string;
|
|
100
104
|
readonly minLength?: number;
|
|
101
105
|
readonly maxLength?: number;
|
|
106
|
+
readonly pattern?: string;
|
|
102
107
|
readonly enum: readonly ["full", "summary", "value"];
|
|
103
108
|
};
|
|
104
109
|
readonly value_path: import("./schema.js").StringProperty;
|
|
@@ -127,6 +132,7 @@ export declare const publishingRevisionsQueryContract: ContractObject<{
|
|
|
127
132
|
readonly description?: string;
|
|
128
133
|
readonly minLength?: number;
|
|
129
134
|
readonly maxLength?: number;
|
|
135
|
+
readonly pattern?: string;
|
|
130
136
|
readonly enum: readonly ["full", "summary", "value"];
|
|
131
137
|
};
|
|
132
138
|
readonly value_path: import("./schema.js").StringProperty;
|
|
@@ -166,6 +172,7 @@ export declare const publishingAnalyticsQueryContract: ContractObject<{
|
|
|
166
172
|
readonly description?: string;
|
|
167
173
|
readonly minLength?: number;
|
|
168
174
|
readonly maxLength?: number;
|
|
175
|
+
readonly pattern?: string;
|
|
169
176
|
readonly enum: readonly ["full", "summary", "value"];
|
|
170
177
|
};
|
|
171
178
|
readonly value_path: import("./schema.js").StringProperty;
|
|
@@ -15,6 +15,8 @@ export type StringProperty = {
|
|
|
15
15
|
enum?: readonly string[];
|
|
16
16
|
minLength?: number;
|
|
17
17
|
maxLength?: number;
|
|
18
|
+
/** JSON Schema regular expression, evaluated with Unicode semantics. */
|
|
19
|
+
pattern?: string;
|
|
18
20
|
};
|
|
19
21
|
export type NumberProperty = {
|
|
20
22
|
type: "number" | "integer";
|
|
@@ -99,6 +101,7 @@ export declare function jsonValue(options?: Described): {
|
|
|
99
101
|
export declare function str(options?: Described & {
|
|
100
102
|
minLength?: number;
|
|
101
103
|
maxLength?: number;
|
|
104
|
+
pattern?: string;
|
|
102
105
|
}): StringProperty;
|
|
103
106
|
export declare function enumOf<const V extends string>(values: readonly V[], options?: Described): {
|
|
104
107
|
type: "string";
|
|
@@ -210,6 +210,9 @@ function walkValue(schema, value, path, walk) {
|
|
|
210
210
|
else if (schema.maxLength !== undefined && value.length > schema.maxLength) {
|
|
211
211
|
walk.issues.push({ path, message: `${path} must be at most ${schema.maxLength} characters.` });
|
|
212
212
|
}
|
|
213
|
+
else if (schema.pattern !== undefined && !new RegExp(schema.pattern, "u").test(value)) {
|
|
214
|
+
walk.issues.push({ path, message: `${path} must match the required pattern.` });
|
|
215
|
+
}
|
|
213
216
|
return value;
|
|
214
217
|
}
|
|
215
218
|
case "integer":
|
|
@@ -11,6 +11,7 @@ export declare const TABLE_ROW_READ_WINDOW_FIELDS: {
|
|
|
11
11
|
readonly enum?: readonly string[];
|
|
12
12
|
readonly minLength?: number;
|
|
13
13
|
readonly maxLength?: number;
|
|
14
|
+
readonly pattern?: string;
|
|
14
15
|
readonly description: "JSON Pointer; empty=root.";
|
|
15
16
|
};
|
|
16
17
|
readonly value_cursor: {
|
|
@@ -18,6 +19,7 @@ export declare const TABLE_ROW_READ_WINDOW_FIELDS: {
|
|
|
18
19
|
readonly enum?: readonly string[];
|
|
19
20
|
readonly minLength?: number;
|
|
20
21
|
readonly maxLength?: number;
|
|
22
|
+
readonly pattern?: string;
|
|
21
23
|
readonly description: "Resume with identical selection/path.";
|
|
22
24
|
};
|
|
23
25
|
readonly max_chars: {
|
|
@@ -94,6 +94,7 @@ declare const REPLY_CLASSIFIED_PAYLOAD: ContractObject<{
|
|
|
94
94
|
enum?: readonly string[];
|
|
95
95
|
minLength?: number;
|
|
96
96
|
maxLength?: number;
|
|
97
|
+
pattern?: string;
|
|
97
98
|
format: "date-time";
|
|
98
99
|
};
|
|
99
100
|
readonly url: {
|
|
@@ -102,6 +103,7 @@ declare const REPLY_CLASSIFIED_PAYLOAD: ContractObject<{
|
|
|
102
103
|
enum?: readonly string[];
|
|
103
104
|
minLength?: number;
|
|
104
105
|
maxLength?: number;
|
|
106
|
+
pattern?: string;
|
|
105
107
|
format: "uri";
|
|
106
108
|
};
|
|
107
109
|
}, "channel" | "classification" | "conversation_id" | "enrollment_id" | "occurred_at" | "record_id" | "sequence_id" | "url">;
|
|
@@ -143,6 +145,7 @@ declare const ENROLLMENT_ENDED_PAYLOAD: ContractObject<{
|
|
|
143
145
|
enum?: readonly string[];
|
|
144
146
|
minLength?: number;
|
|
145
147
|
maxLength?: number;
|
|
148
|
+
pattern?: string;
|
|
146
149
|
format: "date-time";
|
|
147
150
|
};
|
|
148
151
|
readonly url: {
|
|
@@ -151,6 +154,7 @@ declare const ENROLLMENT_ENDED_PAYLOAD: ContractObject<{
|
|
|
151
154
|
enum?: readonly string[];
|
|
152
155
|
minLength?: number;
|
|
153
156
|
maxLength?: number;
|
|
157
|
+
pattern?: string;
|
|
154
158
|
format: "uri";
|
|
155
159
|
};
|
|
156
160
|
}, "enrollment_id" | "occurred_at" | "outcome" | "sequence_id" | "steps_sent" | "url">;
|
|
@@ -205,6 +209,7 @@ declare const MEETING_COMPLETED_PAYLOAD: ContractObject<{
|
|
|
205
209
|
enum?: readonly string[];
|
|
206
210
|
minLength?: number;
|
|
207
211
|
maxLength?: number;
|
|
212
|
+
pattern?: string;
|
|
208
213
|
format: "date-time";
|
|
209
214
|
};
|
|
210
215
|
readonly url: {
|
|
@@ -213,6 +218,7 @@ declare const MEETING_COMPLETED_PAYLOAD: ContractObject<{
|
|
|
213
218
|
enum?: readonly string[];
|
|
214
219
|
minLength?: number;
|
|
215
220
|
maxLength?: number;
|
|
221
|
+
pattern?: string;
|
|
216
222
|
format: "uri";
|
|
217
223
|
};
|
|
218
224
|
}, "activity_id" | "duration_seconds" | "occurred_at" | "record_ids" | "session_id" | "url">;
|
|
@@ -277,6 +283,7 @@ declare const RECORD_STAGE_CHANGED_PAYLOAD: ContractObject<{
|
|
|
277
283
|
enum?: readonly string[];
|
|
278
284
|
minLength?: number;
|
|
279
285
|
maxLength?: number;
|
|
286
|
+
pattern?: string;
|
|
280
287
|
format: "date-time";
|
|
281
288
|
};
|
|
282
289
|
readonly url: {
|
|
@@ -285,6 +292,7 @@ declare const RECORD_STAGE_CHANGED_PAYLOAD: ContractObject<{
|
|
|
285
292
|
enum?: readonly string[];
|
|
286
293
|
minLength?: number;
|
|
287
294
|
maxLength?: number;
|
|
295
|
+
pattern?: string;
|
|
288
296
|
format: "uri";
|
|
289
297
|
};
|
|
290
298
|
}, "activity_id" | "from_stage_id" | "object" | "occurred_at" | "pipeline_id" | "record_id" | "to_stage_id" | "url">;
|
|
@@ -323,6 +331,7 @@ declare const RUN_SETTLED_PAYLOAD: ContractObject<{
|
|
|
323
331
|
enum?: readonly string[];
|
|
324
332
|
minLength?: number;
|
|
325
333
|
maxLength?: number;
|
|
334
|
+
pattern?: string;
|
|
326
335
|
format: "date-time";
|
|
327
336
|
};
|
|
328
337
|
readonly url: {
|
|
@@ -331,6 +340,7 @@ declare const RUN_SETTLED_PAYLOAD: ContractObject<{
|
|
|
331
340
|
enum?: readonly string[];
|
|
332
341
|
minLength?: number;
|
|
333
342
|
maxLength?: number;
|
|
343
|
+
pattern?: string;
|
|
334
344
|
format: "uri";
|
|
335
345
|
};
|
|
336
346
|
}, "credits_used" | "kind" | "occurred_at" | "run_id" | "status" | "url">;
|
|
@@ -362,6 +372,7 @@ declare const APPROVAL_REQUESTED_PAYLOAD: ContractObject<{
|
|
|
362
372
|
enum?: readonly string[];
|
|
363
373
|
minLength?: number;
|
|
364
374
|
maxLength?: number;
|
|
375
|
+
pattern?: string;
|
|
365
376
|
format: "date-time";
|
|
366
377
|
};
|
|
367
378
|
readonly url: {
|
|
@@ -370,6 +381,7 @@ declare const APPROVAL_REQUESTED_PAYLOAD: ContractObject<{
|
|
|
370
381
|
enum?: readonly string[];
|
|
371
382
|
minLength?: number;
|
|
372
383
|
maxLength?: number;
|
|
384
|
+
pattern?: string;
|
|
373
385
|
format: "uri";
|
|
374
386
|
};
|
|
375
387
|
}, "approval_id" | "kind" | "occurred_at" | "run_id" | "url">;
|
|
@@ -60,7 +60,7 @@ export type OtlpLogSinkOptions = {
|
|
|
60
60
|
* `worker.log_shipper.stats` is the Axiom shipper's once-a-minute loss accounting,
|
|
61
61
|
* which by design never reaches Axiom (OXP-1.6.126).
|
|
62
62
|
*/
|
|
63
|
-
export declare const OTLP_MONITOR_INFO_EVENTS: readonly ["diagnostics.capture_stored", "diagnostics.invocation_completed", "diagnostics.canary_completed", "diagnostics.digest_completed", "tenant.migrations.cron.done", "outbound_send.sent", "worker_workflow_cron_overlap_skipped", "worker_workflow_cron_enqueued", "copilot.turn.finished", "worker.queue.heartbeat", "worker.queue.ledger", "worker.queue.redis", "worker.queue.unregistered_kind", "automation_usage.reconcile_completed", "provider_health.cron_completed", "provider_balances.cron_completed", "cost_snapshot.cron_completed", "worker.egress_order_reconcile_sweep", "posthog_canary.round_trip_ok", "web.log_volume", "worker.log_shipper.stats"];
|
|
63
|
+
export declare const OTLP_MONITOR_INFO_EVENTS: readonly ["diagnostics.capture_stored", "diagnostics.invocation_completed", "diagnostics.canary_completed", "diagnostics.digest_completed", "tenant.migrations.cron.done", "outbound_send.sent", "worker_workflow_cron_overlap_skipped", "worker_workflow_cron_enqueued", "copilot.turn.finished", "worker.queue.heartbeat", "worker.queue.depth", "worker.queue.ledger", "worker.queue.redis", "worker.queue.unregistered_kind", "automation_usage.reconcile_completed", "provider_health.cron_completed", "provider_balances.cron_completed", "cost_snapshot.cron_completed", "worker.egress_order_reconcile_sweep", "posthog_canary.round_trip_ok", "web.log_volume", "worker.log_shipper.stats"];
|
|
64
64
|
export type OtlpLogSinkHandle = {
|
|
65
65
|
sink: LogSink;
|
|
66
66
|
flush: () => Promise<void>;
|
|
@@ -15,6 +15,13 @@ export type SendingSchedule = {
|
|
|
15
15
|
start: string;
|
|
16
16
|
end: string;
|
|
17
17
|
};
|
|
18
|
+
export type LinkedInSequenceSchedules = {
|
|
19
|
+
schedule?: SendingSchedule | null;
|
|
20
|
+
linkedin_invite_schedule?: SendingSchedule | null;
|
|
21
|
+
linkedin_message_schedule?: SendingSchedule | null;
|
|
22
|
+
};
|
|
23
|
+
/** Action windows replace the general sequence window; account hours still narrow them. */
|
|
24
|
+
export declare function sequenceSocialSchedule(settings: LinkedInSequenceSchedules, actionKind: string, network?: string): SendingSchedule | null | undefined;
|
|
18
25
|
/**
|
|
19
26
|
* The settings fragment that applies one schedule to every channel. The email
|
|
20
27
|
* window keeps its recipient-timezone fields; the social window takes the same
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/** Action windows replace the general sequence window; account hours still narrow them. */
|
|
2
|
+
export function sequenceSocialSchedule(settings, actionKind, network = "linkedin") {
|
|
3
|
+
if (network === "linkedin") {
|
|
4
|
+
if (actionKind === "invite")
|
|
5
|
+
return settings.linkedin_invite_schedule ?? settings.schedule;
|
|
6
|
+
if (actionKind === "message" || actionKind === "inmail")
|
|
7
|
+
return settings.linkedin_message_schedule ?? settings.schedule;
|
|
8
|
+
}
|
|
9
|
+
return settings.schedule;
|
|
10
|
+
}
|
|
1
11
|
/**
|
|
2
12
|
* The settings fragment that applies one schedule to every channel. The email
|
|
3
13
|
* window keeps its recipient-timezone fields; the social window takes the same
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import { type CapabilityRouteMatch } from "./capability-discovery.js";
|
|
2
|
+
/** A null route has no command to hydrate yet. Keep recovery bounded and executable. */
|
|
3
|
+
export declare function capabilityDiscoveryRecovery(query: string, surface: "cli" | "mcp"): string;
|
|
2
4
|
export declare function inferUserCapabilityRoute(query: string): CapabilityRouteMatch | null;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { getCapabilityRoute, getCapabilityRouteMatch, inferCapabilityRoute, } from "./capability-discovery.js";
|
|
2
|
+
/** A null route has no command to hydrate yet. Keep recovery bounded and executable. */
|
|
3
|
+
export function capabilityDiscoveryRecovery(query, surface) {
|
|
4
|
+
if (surface === "mcp") {
|
|
5
|
+
return 'No capability owner matched. Inspect the returned tools, or load oxygen_capabilities_schema({"names":["oxygen_tools_search"]}) and search the provider catalog with specific keywords. Discovery does not execute a provider operation.';
|
|
6
|
+
}
|
|
7
|
+
const quoted = `'${query.replace(/'/g, "'\\''")}'`;
|
|
8
|
+
return `No capability owner matched. Run \`oxygen commands search ${quoted} --limit 5 --json\`, then hydrate a returned name with \`oxygen commands get '<returned-name>' --json\`.`;
|
|
9
|
+
}
|
|
2
10
|
// Provider brands are intentionally not duplicated into the static capability
|
|
3
11
|
// catalog. A short "connect <provider>" query would otherwise match Tables via
|
|
4
12
|
// its relationship-oriented "connect" term. Correct that narrow ambiguity at
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const OXYGEN_BUILD_VERSION = "1.1014.
|
|
1
|
+
export declare const OXYGEN_BUILD_VERSION = "1.1014.118";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// GENERATED by scripts/ci/version-stamp.mjs from VERSION + git history. Do not edit; do not commit.
|
|
2
|
-
export const OXYGEN_BUILD_VERSION = "1.1014.
|
|
2
|
+
export const OXYGEN_BUILD_VERSION = "1.1014.118";
|