@oxygen-agent/cli 1.1014.81 → 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 +141 -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";
|
|
@@ -1645,6 +1646,14 @@ function writeMaxCreditsHint(error) {
|
|
|
1645
1646
|
if (reservation === null)
|
|
1646
1647
|
return;
|
|
1647
1648
|
const nextStep = readDetailsString(error.details, "next_step");
|
|
1649
|
+
// A free workspace connecting LinkedIn is sent to the plan, not to a top-up (2026-10-11).
|
|
1650
|
+
if (readDetailsString(error.details, "remedy") === "subscribe") {
|
|
1651
|
+
const upgradeUrl = readDetailsString(error.details, "upgrade_url");
|
|
1652
|
+
process.stderr.write(`hint: this connect reserves ${reservation} credits a month; subscribe to the Oxygen plan, then connect again\n`);
|
|
1653
|
+
if (upgradeUrl)
|
|
1654
|
+
process.stderr.write(`hint: choose a plan at ${upgradeUrl}\n`);
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1648
1657
|
const topupUrl = readDetailsString(error.details, "topup_url");
|
|
1649
1658
|
process.stderr.write(`hint: this connect reserves ${reservation} credits a month; add credits, then connect again\n`);
|
|
1650
1659
|
if (nextStep)
|
|
@@ -3668,6 +3677,7 @@ export function createProgram() {
|
|
|
3668
3677
|
const program = new Command();
|
|
3669
3678
|
const binaryName = resolveCliBinaryName();
|
|
3670
3679
|
const directoryDocsUrl = `${defaultApiUrl()}/docs/agencies`;
|
|
3680
|
+
const billingDocsUrl = `${defaultApiUrl()}/docs/safety/billing`;
|
|
3671
3681
|
program
|
|
3672
3682
|
.name(binaryName)
|
|
3673
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.")
|
|
@@ -3947,10 +3957,13 @@ export function createProgram() {
|
|
|
3947
3957
|
.action(async (queryParts, options) => {
|
|
3948
3958
|
await handleAsyncAction("capabilities search", options, async () => {
|
|
3949
3959
|
const query = queryParts.join(" ");
|
|
3960
|
+
const route = inferUserCapabilityRoute(query);
|
|
3950
3961
|
return {
|
|
3951
3962
|
query,
|
|
3952
|
-
route: serializeCapabilityRoute(
|
|
3953
|
-
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"),
|
|
3954
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.`,
|
|
3955
3968
|
};
|
|
3956
3969
|
});
|
|
@@ -8524,9 +8537,8 @@ Examples:
|
|
|
8524
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.")
|
|
8525
8538
|
.option("--json", "Print a JSON envelope.")
|
|
8526
8539
|
.action(async (options) => {
|
|
8527
|
-
await handleAsyncAction("knowledge bootstrap", options, () =>
|
|
8528
|
-
|
|
8529
|
-
body: {
|
|
8540
|
+
await handleAsyncAction("knowledge bootstrap", options, () => {
|
|
8541
|
+
const parameters = {
|
|
8530
8542
|
mode: options.live || options.approved ? "live" : "dry_run",
|
|
8531
8543
|
...(readOption(options.domain) ? { domain: readOption(options.domain) } : {}),
|
|
8532
8544
|
...(readOption(options.linkedin) ? { linkedin_url: readOption(options.linkedin) } : {}),
|
|
@@ -8534,8 +8546,11 @@ Examples:
|
|
|
8534
8546
|
? { max_credits: readPositiveNumber(options.maxCredits) }
|
|
8535
8547
|
: {}),
|
|
8536
8548
|
...(options.force ? { force: true } : {}),
|
|
8537
|
-
}
|
|
8538
|
-
|
|
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
|
+
});
|
|
8539
8554
|
})
|
|
8540
8555
|
.addCommand(new Command("status")
|
|
8541
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.")
|
|
@@ -8548,10 +8563,7 @@ Examples:
|
|
|
8548
8563
|
...options,
|
|
8549
8564
|
json: options.json || Boolean(knowledgeBootstrapCommand.opts().json),
|
|
8550
8565
|
};
|
|
8551
|
-
await handleAsyncAction("knowledge bootstrap status", outputOptions, () => requestOxygen("/api/cli/knowledge/bootstrap"
|
|
8552
|
-
method: "POST",
|
|
8553
|
-
body: { status: true },
|
|
8554
|
-
}));
|
|
8566
|
+
await handleAsyncAction("knowledge bootstrap status", outputOptions, () => requestOxygen("/api/cli/knowledge/bootstrap?status=true"));
|
|
8555
8567
|
}));
|
|
8556
8568
|
program
|
|
8557
8569
|
.command("knowledge")
|
|
@@ -8688,13 +8700,10 @@ Examples:
|
|
|
8688
8700
|
.action(async (slugOrId, options) => {
|
|
8689
8701
|
await handleAsyncAction("knowledge page revisions", options, () => {
|
|
8690
8702
|
const limit = readPositiveInt(options.limit);
|
|
8691
|
-
return requestOxygen("/api/cli/knowledge/pages/revisions", {
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
...(limit !== undefined ? { limit } : {}),
|
|
8696
|
-
},
|
|
8697
|
-
});
|
|
8703
|
+
return requestOxygen(knowledgeReadPath("/api/cli/knowledge/pages/revisions", {
|
|
8704
|
+
...knowledgePageRefBody(slugOrId),
|
|
8705
|
+
...(limit !== undefined ? { limit } : {}),
|
|
8706
|
+
}));
|
|
8698
8707
|
});
|
|
8699
8708
|
}))
|
|
8700
8709
|
.addCommand(new Command("revision")
|
|
@@ -8710,13 +8719,10 @@ Examples:
|
|
|
8710
8719
|
details: { revision },
|
|
8711
8720
|
});
|
|
8712
8721
|
}
|
|
8713
|
-
return requestOxygen("/api/cli/knowledge/pages/revisions/get", {
|
|
8714
|
-
|
|
8715
|
-
|
|
8716
|
-
|
|
8717
|
-
revision: revisionNumber,
|
|
8718
|
-
},
|
|
8719
|
-
});
|
|
8722
|
+
return requestOxygen(knowledgeReadPath("/api/cli/knowledge/pages/revisions/get", {
|
|
8723
|
+
...knowledgePageRefBody(slugOrId),
|
|
8724
|
+
revision: revisionNumber,
|
|
8725
|
+
}));
|
|
8720
8726
|
});
|
|
8721
8727
|
})))
|
|
8722
8728
|
.addCommand(new Command("search")
|
|
@@ -8762,10 +8768,7 @@ Examples:
|
|
|
8762
8768
|
.option("--depth <n>", "Local-graph hop depth (1-3, default 1). Only applies with --center.")
|
|
8763
8769
|
.option("--json", "Print a JSON envelope.")
|
|
8764
8770
|
.action(async (options) => {
|
|
8765
|
-
await handleAsyncAction("knowledge graph", options, () => requestOxygen("/api/cli/knowledge/graph",
|
|
8766
|
-
method: "POST",
|
|
8767
|
-
body: buildKnowledgeGraphBody(options),
|
|
8768
|
-
}));
|
|
8771
|
+
await handleAsyncAction("knowledge graph", options, () => requestOxygen(knowledgeReadPath("/api/cli/knowledge/graph", buildKnowledgeGraphBody(options))));
|
|
8769
8772
|
}))
|
|
8770
8773
|
.addCommand(new Command("lint")
|
|
8771
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.")
|
|
@@ -8794,10 +8797,7 @@ Examples:
|
|
|
8794
8797
|
.option("--limit <n>", "Maximum log entries to return.")
|
|
8795
8798
|
.option("--json", "Print a JSON envelope.")
|
|
8796
8799
|
.action(async (options) => {
|
|
8797
|
-
await handleAsyncAction("knowledge log", options, () => requestOxygen("/api/cli/knowledge/log",
|
|
8798
|
-
method: "POST",
|
|
8799
|
-
body: buildKnowledgeLogListBody(options),
|
|
8800
|
-
}));
|
|
8800
|
+
await handleAsyncAction("knowledge log", options, () => requestOxygen(knowledgeReadPath("/api/cli/knowledge/log", buildKnowledgeLogListBody(options))));
|
|
8801
8801
|
}))
|
|
8802
8802
|
.addCommand(new Command("append")
|
|
8803
8803
|
.description("Append an entry to the knowledge log.")
|
|
@@ -8815,14 +8815,16 @@ Examples:
|
|
|
8815
8815
|
.addCommand(new Command("proposals")
|
|
8816
8816
|
.description("Draft change proposals against wiki pages, awaiting human review. Read-only, 0 credits.")
|
|
8817
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.")
|
|
8818
8819
|
.option("--json", "Print a JSON envelope.")
|
|
8819
8820
|
.action(async (options) => {
|
|
8820
|
-
await handleAsyncAction("knowledge proposals", options, () =>
|
|
8821
|
-
|
|
8822
|
-
|
|
8821
|
+
await handleAsyncAction("knowledge proposals", options, () => {
|
|
8822
|
+
const limit = readPositiveInt(options.limit);
|
|
8823
|
+
return requestOxygen(knowledgeReadPath("/api/cli/knowledge/proposals", {
|
|
8823
8824
|
...(readOption(options.status) ? { status: readOption(options.status) } : {}),
|
|
8824
|
-
|
|
8825
|
-
|
|
8825
|
+
...(limit !== undefined ? { limit } : {}),
|
|
8826
|
+
}));
|
|
8827
|
+
});
|
|
8826
8828
|
})
|
|
8827
8829
|
.addCommand(new Command("approve")
|
|
8828
8830
|
.description("Approve a knowledge proposal and apply it to the target page.")
|
|
@@ -10861,13 +10863,30 @@ Examples:
|
|
|
10861
10863
|
.addCommand(new Command("retry-failed")
|
|
10862
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.")
|
|
10863
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.")
|
|
10864
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.")
|
|
10865
10871
|
.option("--json", "Print a JSON envelope.")
|
|
10866
10872
|
.action(async (runId, options) => {
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
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
|
+
}));
|
|
10871
10890
|
}));
|
|
10872
10891
|
program
|
|
10873
10892
|
.command("table-ingestions")
|
|
@@ -11478,7 +11497,7 @@ Examples:
|
|
|
11478
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.")
|
|
11479
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")
|
|
11480
11499
|
.addCommand(addContractFlags(new Command("show")
|
|
11481
|
-
.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)
|
|
11482
11501
|
.option("--json", "Print a JSON envelope.")
|
|
11483
11502
|
.action(async (options) => {
|
|
11484
11503
|
await handleAsyncAction("limits show", options, () => {
|
|
@@ -11521,9 +11540,9 @@ Examples:
|
|
|
11521
11540
|
})));
|
|
11522
11541
|
program
|
|
11523
11542
|
.command("billing")
|
|
11524
|
-
.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 + ".")
|
|
11525
11544
|
.addCommand(new Command("change")
|
|
11526
|
-
.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.")
|
|
11527
11546
|
.requiredOption("--to <plan>",
|
|
11528
11547
|
// Derived from the catalog, never retyped: this help text is the only
|
|
11529
11548
|
// place a customer's agent learns which plans exist, and it named
|
|
@@ -11555,7 +11574,7 @@ Examples:
|
|
|
11555
11574
|
}));
|
|
11556
11575
|
}))
|
|
11557
11576
|
.addCommand(new Command("balance")
|
|
11558
|
-
.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.")
|
|
11559
11578
|
.option("--json", "Print a JSON envelope.")
|
|
11560
11579
|
.action(async (options) => {
|
|
11561
11580
|
await handleAsyncAction("billing balance", options, () => requestOxygen("/api/cli/billing/balance"));
|
|
@@ -11626,7 +11645,7 @@ Examples:
|
|
|
11626
11645
|
await handleAsyncAction("billing allowance", options, () => requestOxygen("/api/cli/billing/allowance"));
|
|
11627
11646
|
}))
|
|
11628
11647
|
.addCommand(new Command("usage")
|
|
11629
|
-
.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`.")
|
|
11630
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.")
|
|
11631
11650
|
.option("--days <n>", "Lookback window in days. Defaults to 30.")
|
|
11632
11651
|
.option("--from <iso>", "Only include ledger events at or after this ISO timestamp.")
|
|
@@ -11782,7 +11801,7 @@ Examples:
|
|
|
11782
11801
|
await handleAsyncAction("billing retain", options, () => requestOxygen("/api/cli/billing/cancel", { method: "POST", body: { action: "retain", offer_id: options.offer } }));
|
|
11783
11802
|
}))
|
|
11784
11803
|
.addCommand(new Command("topup")
|
|
11785
|
-
.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.")
|
|
11786
11805
|
.argument("[pack]", "A pack id from the list shown when you run without an amount.")
|
|
11787
11806
|
.option("--credits <n>", "Credits to buy (800-100,000 in 100-credit increments).")
|
|
11788
11807
|
.option("--json", "Print a JSON envelope.")
|
|
@@ -13403,7 +13422,7 @@ Examples:
|
|
|
13403
13422
|
.command("enrich-column")
|
|
13404
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).")
|
|
13405
13424
|
.addCommand(new Command("preview")
|
|
13406
|
-
.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).")
|
|
13407
13426
|
.argument("<table>", "Table id or slug.")
|
|
13408
13427
|
.option("--source-column <column>", "Source column key or id. For mobile_phone this is the LinkedIn URL column.")
|
|
13409
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.")
|
|
@@ -13495,7 +13514,7 @@ Examples:
|
|
|
13495
13514
|
.option("--company-domain <domain>", "Company apex domain, e.g. acme.com.")
|
|
13496
13515
|
.option("--company-name <name>", "Company name.")
|
|
13497
13516
|
.option("--mode <mode>", "dry_run (default) previews the plan and spends nothing; live spends credits and returns the answer.")
|
|
13498
|
-
.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.")
|
|
13499
13518
|
.option("--json", "Print a JSON envelope.")
|
|
13500
13519
|
.action(async (options) => {
|
|
13501
13520
|
await handleAsyncAction("find email", options, () => requestOxygen("/api/cli/find/run", { method: "POST", body: buildFindBody("email", options) }));
|
|
@@ -13508,7 +13527,7 @@ Examples:
|
|
|
13508
13527
|
.option("--company-domain <domain>", "Company apex domain, e.g. acme.com.")
|
|
13509
13528
|
.option("--company-name <name>", "Company name.")
|
|
13510
13529
|
.option("--mode <mode>", "dry_run (default) previews the plan and spends nothing; live spends credits and returns the answer.")
|
|
13511
|
-
.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.")
|
|
13512
13531
|
.option("--json", "Print a JSON envelope.")
|
|
13513
13532
|
.action(async (options) => {
|
|
13514
13533
|
await handleAsyncAction("find personal-email", options, () => requestOxygen("/api/cli/find/run", { method: "POST", body: buildFindBody("personal_email", options) }));
|
|
@@ -13522,7 +13541,7 @@ Examples:
|
|
|
13522
13541
|
.option("--company-name <name>", "Company name.")
|
|
13523
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).")
|
|
13524
13543
|
.option("--mode <mode>", "dry_run (default) previews the plan and spends nothing; live spends credits and returns the answer.")
|
|
13525
|
-
.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.")
|
|
13526
13545
|
.option("--json", "Print a JSON envelope.")
|
|
13527
13546
|
.action(async (options) => {
|
|
13528
13547
|
await handleAsyncAction("find phone", options, () => requestOxygen("/api/cli/find/run", { method: "POST", body: buildFindBody("phone", options) }));
|
|
@@ -13535,7 +13554,7 @@ Examples:
|
|
|
13535
13554
|
.option("--company-name <name>", "Company name.")
|
|
13536
13555
|
.option("--company-linkedin-url <url>", "Company LinkedIn URL.")
|
|
13537
13556
|
.option("--mode <mode>", "dry_run (default) previews the plan and spends nothing; live spends credits and returns the answer.")
|
|
13538
|
-
.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.")
|
|
13539
13558
|
.option("--json", "Print a JSON envelope.")
|
|
13540
13559
|
.action(async (options) => {
|
|
13541
13560
|
await handleAsyncAction("find linkedin", options, () => requestOxygen("/api/cli/find/run", { method: "POST", body: buildFindBody("linkedin", options) }));
|
|
@@ -13553,7 +13572,7 @@ Examples:
|
|
|
13553
13572
|
.option("--company-name <name>", "Company name.")
|
|
13554
13573
|
.option("--include-raw", "Also return the provider's raw profile payload (profile_raw) for fields the profile block does not carry.")
|
|
13555
13574
|
.option("--mode <mode>", "dry_run (default) previews the plan and spends nothing; live spends credits and returns the profile.")
|
|
13556
|
-
.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.")
|
|
13557
13576
|
.option("--json", "Print a JSON envelope.")
|
|
13558
13577
|
.action(async (options) => {
|
|
13559
13578
|
await handleAsyncAction("find person", options, () => requestOxygen("/api/cli/find/run", { method: "POST", body: buildFindBody("person", options) }));
|
|
@@ -13567,7 +13586,7 @@ Examples:
|
|
|
13567
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.")
|
|
13568
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).")
|
|
13569
13588
|
.option("--mode <mode>", "dry_run (default) previews the plan and spends nothing; live spends credits and returns the answer.")
|
|
13570
|
-
.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.")
|
|
13571
13590
|
.option("--json", "Print a JSON envelope.")
|
|
13572
13591
|
.action(async (options) => {
|
|
13573
13592
|
await handleAsyncAction("find company", options, () => requestOxygen("/api/cli/find/run", { method: "POST", body: buildFindBody("company", options) }));
|
|
@@ -16460,6 +16479,8 @@ Examples:
|
|
|
16460
16479
|
.option("--schedule-hours-start <HH:MM>", "Sending schedule start, local to --schedule-timezone, e.g. 09:00.")
|
|
16461
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.")
|
|
16462
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.')
|
|
16463
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.")
|
|
16464
16485
|
.option("--linkedin-days <days>", "LinkedIn sending schedule days: mon-fri, mon,wed,fri, or ISO numbers 1-7 (1 = Monday).")
|
|
16465
16486
|
.option("--linkedin-hours-start <HH:MM>", "LinkedIn sending schedule start, local to --linkedin-timezone, e.g. 06:45.")
|
|
@@ -29596,6 +29617,12 @@ function buildKnowledgePageListBody(options) {
|
|
|
29596
29617
|
...(limit !== undefined ? { limit } : {}),
|
|
29597
29618
|
};
|
|
29598
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
|
+
}
|
|
29599
29626
|
function buildKnowledgeGraphBody(options) {
|
|
29600
29627
|
const maxNodes = readPositiveInt(options.maxNodes);
|
|
29601
29628
|
const center = readOption(options.center);
|
|
@@ -29641,6 +29668,7 @@ const KNOWLEDGE_SYNC_DEFAULT_TTL_MINUTES = 15;
|
|
|
29641
29668
|
// 500 batches × the server's 100-page default is far above any v1 wiki; the cap
|
|
29642
29669
|
// only exists so a misbehaving server can never wedge the CLI in a loop.
|
|
29643
29670
|
const KNOWLEDGE_SYNC_MAX_BATCHES = 500;
|
|
29671
|
+
const KNOWLEDGE_SYNC_CATALOG_MAX_BATCHES = 500;
|
|
29644
29672
|
// Envelope error codes meaning this org can no longer be read with the stored
|
|
29645
29673
|
// token (revoked membership, expired key, org mismatch). The mirror is a local
|
|
29646
29674
|
// cache of tenant data and must not outlive access to it.
|
|
@@ -29771,6 +29799,43 @@ function applyKnowledgeSyncPage(dir, state, page, counters) {
|
|
|
29771
29799
|
};
|
|
29772
29800
|
counters.synced += 1;
|
|
29773
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
|
+
}
|
|
29774
29839
|
// `orgOverride` targets a specific org (used by `sync --all`): it names the mirror
|
|
29775
29840
|
// directory explicitly and pins X-Oxygen-Organization on every request in the
|
|
29776
29841
|
// loop, so each workspace clones into its own mirror regardless of the cached
|
|
@@ -29834,11 +29899,12 @@ options, orgOverride) {
|
|
|
29834
29899
|
lastSeen = { updated_at: page.updated_at, id: page.id };
|
|
29835
29900
|
}
|
|
29836
29901
|
if (!delta.has_more) {
|
|
29837
|
-
//
|
|
29838
|
-
//
|
|
29839
|
-
//
|
|
29840
|
-
|
|
29841
|
-
|
|
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)
|
|
29842
29908
|
state.cursor = `${lastSeen.updated_at}|${lastSeen.id}`;
|
|
29843
29909
|
completed = true;
|
|
29844
29910
|
break;
|
|
@@ -29860,19 +29926,18 @@ options, orgOverride) {
|
|
|
29860
29926
|
exitCode: 1,
|
|
29861
29927
|
});
|
|
29862
29928
|
}
|
|
29863
|
-
//
|
|
29864
|
-
//
|
|
29865
|
-
|
|
29866
|
-
|
|
29867
|
-
|
|
29868
|
-
|
|
29869
|
-
|
|
29870
|
-
|
|
29871
|
-
|
|
29872
|
-
|
|
29873
|
-
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)
|
|
29874
29939
|
writeGeneratedIndexFile(target.dir, index.index);
|
|
29875
|
-
if (
|
|
29940
|
+
if (logPage)
|
|
29876
29941
|
writeGeneratedLogFile(target.dir, logPage.entries);
|
|
29877
29942
|
state.last_sync_at = new Date().toISOString();
|
|
29878
29943
|
writeMirrorState(target.dir, state);
|
|
@@ -30561,6 +30626,10 @@ function readSequenceSettings(options) {
|
|
|
30561
30626
|
if (linkedinSchedule) {
|
|
30562
30627
|
settings.schedule = linkedinSchedule;
|
|
30563
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");
|
|
30564
30633
|
// Tri-state: `--no-whatsapp-cold-initiate` (update) turns the opt-in off.
|
|
30565
30634
|
if (options.whatsappColdInitiate !== undefined) {
|
|
30566
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";
|