@oxygen-agent/cli 1.1011.855 → 1.1011.974
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/admin-lead-magnet-commands.d.ts +7 -0
- package/dist/admin-lead-magnet-commands.js +128 -0
- package/dist/command-manifest.js +11 -0
- package/dist/functions-commands.js +1 -1
- package/dist/index.js +179 -132
- package/dist/workspace-utility-flags.d.ts +7 -0
- package/dist/workspace-utility-flags.js +45 -0
- package/node_modules/@oxygen/shared/dist/billing-anchors.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/billing-anchors.js +7 -1
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +135 -4
- package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +3 -3
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +10 -8
- package/node_modules/@oxygen/shared/dist/contracts/limits-read.js +11 -1
- package/node_modules/@oxygen/shared/dist/contracts/schema.js +10 -6
- package/node_modules/@oxygen/shared/dist/contracts/spintax.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/contracts/spintax.js +11 -0
- package/node_modules/@oxygen/shared/dist/contracts/workspace-utilities.d.ts +162 -0
- package/node_modules/@oxygen/shared/dist/contracts/workspace-utilities.js +46 -0
- package/node_modules/@oxygen/shared/dist/credit-topup-review.d.ts +11 -0
- package/node_modules/@oxygen/shared/dist/credit-topup-review.js +3 -0
- package/node_modules/@oxygen/shared/dist/emailguard-placement-probe.d.ts +70 -0
- package/node_modules/@oxygen/shared/dist/emailguard-placement-probe.js +161 -0
- package/node_modules/@oxygen/shared/dist/future-signup-events.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/future-signup-events.js +4 -0
- package/node_modules/@oxygen/shared/dist/partner-coupon-bonus.d.ts +45 -0
- package/node_modules/@oxygen/shared/dist/partner-coupon-bonus.js +57 -0
- package/node_modules/@oxygen/shared/dist/table-filter-operators.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/table-filter-operators.js +50 -0
- package/node_modules/@oxygen/shared/dist/telemetry.js +4 -0
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +1 -1
- package/node_modules/@oxygen/workflows/dist/index.d.ts +1 -0
- package/node_modules/@oxygen/workflows/dist/index.js +1 -0
- package/node_modules/@oxygen/workflows/dist/recipe-timestamp.d.ts +22 -0
- package/node_modules/@oxygen/workflows/dist/recipe-timestamp.js +72 -0
- package/package.json +1 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
/** Existing native leaves retain their names and default-list alias. */
|
|
3
|
+
export declare function addProviderLimitFlags(command: Command, operation: "list" | "set" | "delete"): Command;
|
|
4
|
+
export declare function providerLimitBody(options: object): Record<string, unknown>;
|
|
5
|
+
export declare function providerLimitQuery(options: object, operation: "list" | "delete"): string;
|
|
6
|
+
export declare function addSuppressionListFlags(command: Command, kind: "addresses" | "domains"): Command;
|
|
7
|
+
export declare function suppressionListQuery(options: object, kind: "addresses" | "domains"): string;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
2
|
+
import { validateContract } from "@oxygen/shared/contracts/schema";
|
|
3
|
+
import { emailDomainSuppressionListContract, emailSuppressionListContract, providerLimitListContract, providerLimitSelectorContract, providerLimitUpdateContract } from "@oxygen/shared/contracts/workspace-utilities";
|
|
4
|
+
import { addContractFlags, readContractFlags } from "./contract-flags.js";
|
|
5
|
+
/** Existing native leaves retain their names and default-list alias. */
|
|
6
|
+
export function addProviderLimitFlags(command, operation) {
|
|
7
|
+
const contract = operation === "set" ? providerLimitUpdateContract : operation === "delete" ? providerLimitSelectorContract : providerLimitListContract;
|
|
8
|
+
addContractFlags(command, contract, {
|
|
9
|
+
provider: { valueName: "<provider>" }, operation: { valueName: "<operation>" },
|
|
10
|
+
limit: { valueName: "<n>" }, window_seconds: { valueName: "<n>" }, offset: { valueName: "<n>" }, max_chars: { valueName: "<n>" },
|
|
11
|
+
});
|
|
12
|
+
for (const field of contract.required ?? [])
|
|
13
|
+
command.options.find((option) => option.long === `--${field.replace(/_/g, "-")}`)?.makeOptionMandatory();
|
|
14
|
+
return command;
|
|
15
|
+
}
|
|
16
|
+
export function providerLimitBody(options) {
|
|
17
|
+
const body = readContractFlags(options, providerLimitUpdateContract);
|
|
18
|
+
const parsed = validateContract(providerLimitUpdateContract, body, { unknown: "reject", overMax: "reject" });
|
|
19
|
+
if (!parsed.ok)
|
|
20
|
+
throw new OxygenError("invalid_request", parsed.issues.map((issue) => issue.message).join(" "), { exitCode: 2 });
|
|
21
|
+
return body;
|
|
22
|
+
}
|
|
23
|
+
export function providerLimitQuery(options, operation) {
|
|
24
|
+
return query(input(options, operation === "list" ? providerLimitListContract : providerLimitSelectorContract));
|
|
25
|
+
}
|
|
26
|
+
export function addSuppressionListFlags(command, kind) {
|
|
27
|
+
return addContractFlags(command, suppressionContract(kind), {
|
|
28
|
+
reason: { valueName: "<reason>" }, search: { valueName: "<text>" }, limit: { valueName: "<n>" }, offset: { valueName: "<n>" }, max_chars: { valueName: "<n>" },
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export function suppressionListQuery(options, kind) {
|
|
32
|
+
return query(input(options, suppressionContract(kind)));
|
|
33
|
+
}
|
|
34
|
+
function suppressionContract(kind) {
|
|
35
|
+
return kind === "addresses" ? emailSuppressionListContract : emailDomainSuppressionListContract;
|
|
36
|
+
}
|
|
37
|
+
function input(options, contract) {
|
|
38
|
+
const parsed = validateContract(contract, readContractFlags(options, contract), { unknown: "reject", overMax: "clamp" });
|
|
39
|
+
if (!parsed.ok)
|
|
40
|
+
throw new OxygenError("invalid_request", parsed.issues.map((issue) => issue.message).join(" "), { exitCode: 2 });
|
|
41
|
+
return parsed.value;
|
|
42
|
+
}
|
|
43
|
+
function query(args) {
|
|
44
|
+
return new URLSearchParams(Object.entries(args).filter(([, value]) => value !== null).map(([key, value]) => [key, String(value)])).toString();
|
|
45
|
+
}
|
|
@@ -50,6 +50,12 @@ export declare function occurrencesInPeriod(from: Date, to: Date, anchorDay: num
|
|
|
50
50
|
* not such a period: it grants monthly slices, so its caller passes the current
|
|
51
51
|
* slice's length (see recomputeOrgCommitmentBlock, P-23).
|
|
52
52
|
*
|
|
53
|
+
* A calendar month runs 28-31 days, so months are counted in 31-day steps: every
|
|
54
|
+
* monthly period holds one month, as OXYGEN_PRODUCT.md states ("one more month of
|
|
55
|
+
* every fixed resource"). Counting in 30-day steps held two months through every
|
|
56
|
+
* 31-day period, refusing customers' own credits as committed (staff decision
|
|
57
|
+
* 2026-10-06).
|
|
58
|
+
*
|
|
53
59
|
* Returns at least 1 — a missing/zero/garbage period length must never collapse
|
|
54
60
|
* the block to nothing, which would silently disable the whole feature.
|
|
55
61
|
*/
|
|
@@ -113,6 +113,12 @@ export function occurrencesInPeriod(from, to, anchorDay) {
|
|
|
113
113
|
* not such a period: it grants monthly slices, so its caller passes the current
|
|
114
114
|
* slice's length (see recomputeOrgCommitmentBlock, P-23).
|
|
115
115
|
*
|
|
116
|
+
* A calendar month runs 28-31 days, so months are counted in 31-day steps: every
|
|
117
|
+
* monthly period holds one month, as OXYGEN_PRODUCT.md states ("one more month of
|
|
118
|
+
* every fixed resource"). Counting in 30-day steps held two months through every
|
|
119
|
+
* 31-day period, refusing customers' own credits as committed (staff decision
|
|
120
|
+
* 2026-10-06).
|
|
121
|
+
*
|
|
116
122
|
* Returns at least 1 — a missing/zero/garbage period length must never collapse
|
|
117
123
|
* the block to nothing, which would silently disable the whole feature.
|
|
118
124
|
*/
|
|
@@ -120,7 +126,7 @@ export function blockPeriodsForSubscription(periodDays) {
|
|
|
120
126
|
if (typeof periodDays !== "number" || !Number.isFinite(periodDays) || periodDays <= 0) {
|
|
121
127
|
return 1;
|
|
122
128
|
}
|
|
123
|
-
return Math.max(1, Math.ceil(periodDays /
|
|
129
|
+
return Math.max(1, Math.ceil(periodDays / 31));
|
|
124
130
|
}
|
|
125
131
|
/**
|
|
126
132
|
* Days between two instants, floored. Small helper so callers deriving
|
|
@@ -88,8 +88,8 @@ export const OXYGEN_CAPABILITY_ROUTES = [
|
|
|
88
88
|
notFor: "Authorizing a specific provider call or external write; that action still needs its own preview and approval.",
|
|
89
89
|
execution: "Read billing state or set a hosted budget policy; exact action ceilings remain primitive-owned.",
|
|
90
90
|
posture: "mixed",
|
|
91
|
-
gatewayTools: ["oxygen_billing_balance", "oxygen_billing_usage", "oxygen_billing_estimate", "oxygen_budget_list", "oxygen_customer_credit_prices"],
|
|
92
|
-
gatewayCommands: ["billing balance", "billing usage", "billing estimate", "budget list", "billing invoices", "pricing customer-credit-prices"],
|
|
91
|
+
gatewayTools: ["oxygen_billing_balance", "oxygen_billing_usage", "oxygen_billing_audit", "oxygen_billing_estimate", "oxygen_budget_list", "oxygen_customer_credit_prices"],
|
|
92
|
+
gatewayCommands: ["billing balance", "billing usage", "billing audit", "billing estimate", "budget list", "billing invoices", "pricing customer-credit-prices"],
|
|
93
93
|
skills: ["oxygen-gtm", "oxygen-diagnostics"],
|
|
94
94
|
endpointSections: ["billing", "budget", "limits", "pricing"],
|
|
95
95
|
intentTerms: ["billing", "credits", "credit balance", "customer credit prices", "credit price catalog", "usage", "spend", "cost", "cost estimate", "monthly cost", "budget", "subscription", "invoice", "vat", "vat number", "tax id", "cancel plan", "change plan", "upgrade", "downgrade", "subscription plans", "annual plan", "yearly commitment", "yearly saving", "annual discount"],
|
|
@@ -206,7 +206,7 @@ export const OXYGEN_CAPABILITY_ROUTES = [
|
|
|
206
206
|
primitive: "records",
|
|
207
207
|
owns: "Canonical companies, people, deals, identities, relationships, activities, notes, tasks (reminders), files, merges, and CRM sync state.",
|
|
208
208
|
notFor: "Disposable sourcing, enrichment, or scoring work; use Tables before promotion.",
|
|
209
|
-
execution: "Operate the hosted native CRM and preserve field/relationship provenance. Importing FROM a connected CRM lands here via `crm sync import <provider> --object contacts|companies
|
|
209
|
+
execution: "Operate the hosted native CRM and preserve field/relationship provenance. Importing FROM a connected CRM lands here via `crm sync import <provider> --object <object>` (HubSpot contacts|companies, Attio people|companies, Pipedrive persons|organizations|deals), which pulls the whole object and has no list/segment filter. To import ONE HubSpot saved list or ONE Pipedrive saved filter into a new table the user can filter and enrich — optionally suppressing a do-not-contact list/filter first — use `tables hubspot import` or `tables pipedrive import` instead. Open work across the workspace is one read: `crm tasks list`.",
|
|
210
210
|
posture: "workspace_write",
|
|
211
211
|
gatewayTools: ["oxygen_crm_record_search", "oxygen_crm_record_get"],
|
|
212
212
|
gatewayCommands: ["crm search", "crm get", "crm describe", "crm tasks list", "crm notes list"],
|
|
@@ -260,7 +260,11 @@ export const OXYGEN_CAPABILITY_ROUTES = [
|
|
|
260
260
|
gatewayCommands: ["inbox send", "email send", "messages query", "unibox metadata"],
|
|
261
261
|
skills: ["oxygen-unibox"],
|
|
262
262
|
endpointSections: ["email", "inbox", "message-reviews", "messages", "unibox"],
|
|
263
|
-
|
|
263
|
+
// "inbox view" / "default view" / "conversations by sender" route here for
|
|
264
|
+
// `inbox views`. Added after the 2026-10-05 blind acceptance: asked to save a
|
|
265
|
+
// filtered inbox as the team's view, `capabilities search` recommended
|
|
266
|
+
// `inbox send` first and the agent found the views commands only by name.
|
|
267
|
+
intentTerms: ["message", "messages", "reply", "email", "inbox", "unibox", "inbox metadata", "conversation", "thread", "direct email", "draft reply", "one off", "email history", "mail history", "analyze emails", "email crm", "inbox view", "unibox view", "saved inbox view", "default inbox view", "conversations by sender", "sender filter"],
|
|
264
268
|
},
|
|
265
269
|
{
|
|
266
270
|
id: "posts",
|
|
@@ -1014,12 +1018,29 @@ function routeCardFor(card, query) {
|
|
|
1014
1018
|
const contactFind = card.id === "sourcing-and-provider-tools" ? oneShotContactFindingRecommendation(query) : null;
|
|
1015
1019
|
if (contactFind)
|
|
1016
1020
|
card = { ...card, execution: contactFind.execution };
|
|
1021
|
+
const crmSample = card.id === "records" ? crmSampleRecordRecommendation(query) : null;
|
|
1022
|
+
if (crmSample)
|
|
1023
|
+
return { ...card, execution: crmSample.execution };
|
|
1017
1024
|
const crmContactRead = card.id === "records" ? storedCrmContactReadRecommendation(query) : null;
|
|
1018
1025
|
if (crmContactRead)
|
|
1019
1026
|
card = { ...card, posture: "read_only", execution: crmContactRead.execution };
|
|
1020
1027
|
const replyDraftRead = card.primitive === "messages" ? storedReplyDraftReadRecommendation(query) : null;
|
|
1021
1028
|
if (replyDraftRead)
|
|
1022
1029
|
card = { ...card, posture: "read_only", execution: replyDraftRead.execution };
|
|
1030
|
+
const providerRead = card.id === "billing-and-budgets" ? providerPolicyReadRecommendation(query) : null;
|
|
1031
|
+
if (providerRead)
|
|
1032
|
+
card = { ...card, posture: "read_only", execution: providerRead.execution };
|
|
1033
|
+
const spendRead = card.id === "billing-and-budgets" && !providerRead ? spendBreakdownRecommendation(query) : null;
|
|
1034
|
+
if (spendRead)
|
|
1035
|
+
card = { ...card, posture: "read_only", execution: spendRead.execution };
|
|
1036
|
+
const blockRead = card.primitive === "sequences" ? sendingBlockReadRecommendation(query) : null;
|
|
1037
|
+
if (blockRead)
|
|
1038
|
+
card = { ...card, posture: "read_only", execution: blockRead.execution };
|
|
1039
|
+
if (card.primitive === "sequences" && !blockRead && isSpintaxIntent(query))
|
|
1040
|
+
card = {
|
|
1041
|
+
...card, posture: "paid_read",
|
|
1042
|
+
execution: "Generate wording variations with the native Spintax owner; sending is separate. Hydrate oxygen_spintax_generate. External MCP uses oxygen_capabilities_call, including its explicit free mode=dry_run preview; built-in agents invoke the native capability under their current policy. Omitted mode is live and costs 3 credits. After preview, live requires approval of the exact text with approved=true and max_credits>=3. Failed generation refunds the reservation.",
|
|
1043
|
+
};
|
|
1023
1044
|
if (card.id === "knowledge-graph" && isWorkspaceVoiceIntent(query))
|
|
1024
1045
|
card = { ...card, execution: WORKSPACE_VOICE_EXECUTION };
|
|
1025
1046
|
if (card.primitive === "workflows" && isRecurringCompanyMonitoringIntent(query))
|
|
@@ -1564,6 +1585,33 @@ function storedReplyDraftReadRecommendation(query) {
|
|
|
1564
1585
|
const commands = tools.map(tool => tool === "oxygen_whatsapp_drafts_list" ? "whatsapp drafts list" : "inbox drafts list");
|
|
1565
1586
|
return { tools, commands, execution: "Hydrate the selected native draft-read schema. External MCP clients use oxygen_capabilities_read for an unlisted target; built-in agents invoke the native capability. oxygen_inbox_drafts_list covers email/LinkedIn; oxygen_whatsapp_drafts_list covers WhatsApp. When the channel is unknown, inspect both schemas before selecting. Filter by the native conversation and CSV status controls; personal composer drafts stay private. Historical states are current draft statuses, not a transition audit. Generation, edits, approval and sending are separate actions." };
|
|
1566
1587
|
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Seeded sample records, and keeping one as real (OXP-6.3.14). In the
|
|
1590
|
+
* 2026-10-06 blind acceptance the records route answered "mark a sample deal as
|
|
1591
|
+
* a real deal" with search/get alone, and `commands search "sample"` ranked
|
|
1592
|
+
* `crm keep` third, behind `formulas validate` and `sequences start`. A sample
|
|
1593
|
+
* OF something (rows, a run, a batch, copy) is a test slice that other owners
|
|
1594
|
+
* keep; seed/demo/example name samples only beside data or records, so "seed
|
|
1595
|
+
* companies" and "demo deals" keep their sourcing and pipeline meanings.
|
|
1596
|
+
*/
|
|
1597
|
+
function crmSampleRecordRecommendation(query) {
|
|
1598
|
+
const record = /\b(records?|deals?|compan(?:y|ies)|contacts?|people|persons?|accounts?|ceos?|opportunit(?:y|ies))\b/;
|
|
1599
|
+
const named = /\b(?:crm|record) keep\b/.test(query);
|
|
1600
|
+
const slice = /\b(tables?|rows?|columns?|cells?|spreadsheets?|enrich\w*|runs?|batch(?:es)?|previews?|formulas?|sequences?|campaigns?|messages?|posts?|copy|templates?|prompts?|recipes?|workflows?|agents?)\b/.test(query)
|
|
1601
|
+
|| /\bsamples? (?:of|size|run|batch)\b|\b(?:small|random|test|representative) samples?\b/.test(query);
|
|
1602
|
+
const asReal = /\bas (?:an? )?real\b/.test(query) && record.test(query);
|
|
1603
|
+
const sample = (/\bsamples?\b/.test(query) && (/^samples?$/.test(query) || record.test(query) || /\b(data|pipeline|crm)\b/.test(query)))
|
|
1604
|
+
|| /\b(?:seed(?:ed)?|demo|example) (?:data|records?)\b/.test(query)
|
|
1605
|
+
|| /\bseeded (?:deals?|compan(?:y|ies)|contacts?|people|persons?|accounts?|pipeline)\b/.test(query)
|
|
1606
|
+
|| /\b(?:dummy|placeholder|fake) (?:data|records?|deals?|compan(?:y|ies)|contacts?|people|persons?|accounts?)\b/.test(query);
|
|
1607
|
+
if (!named && (slice || (!asReal && !sample)))
|
|
1608
|
+
return null;
|
|
1609
|
+
return {
|
|
1610
|
+
tools: ["oxygen_crm_record_keep", "oxygen_crm_pipeline", "oxygen_crm_record_search", "oxygen_crm_record_get"],
|
|
1611
|
+
commands: ["crm keep", "crm pipeline", "crm search", "crm get"],
|
|
1612
|
+
execution: "A new workspace's seeded companies, CEOs and deals read _sample: true (sample: true on suggest hits and crm pipeline deals); pipeline, dashboard and chart totals leave them out and say how many. Keep one as real only when the user says it is: crm keep <object> <record> takes a row id or an identity such as stripe.com. It is free and idempotent, has no undo, and keeps that record alone: a kept deal's company and contact stay samples until each is kept. MCP runs oxygen_crm_record_keep through oxygen_capabilities_call.",
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1567
1615
|
/** Saved contact facts belong to Records, regardless of email/phone channel words. */
|
|
1568
1616
|
function storedCrmContactReadRecommendation(query) {
|
|
1569
1617
|
if (!/\b(crm|records?)\b/.test(query)
|
|
@@ -1610,6 +1658,55 @@ function tableFeedRecommendation(rawQuery) {
|
|
|
1610
1658
|
return { tools: ["oxygen_feeds_bind", "oxygen_feeds_list"], commands: ["feeds bind", "feeds list"], execution: "Bind the native Table row-supply feed. Inspect source, cadence and standing credit scope first; pull feeds are distinct from inbound webhook endpoints. Feed tools are executable through the core gateways or Signals pack." };
|
|
1611
1659
|
return { tools: ["oxygen_feeds_list", "oxygen_feeds_get", "oxygen_feeds_update"], commands: ["feeds list", "feeds get", "feeds update"], execution: "Inspect the existing Table feed with feeds_get(include_route=true), then update it in place with oxygen_feeds_update (Signals pack or core write gateway). Preserve the complete frozen request; source replacement resets the cursor. Scheduled replacements require one executable lane; fallbacks require no cadence. Changed cadence, increased paid cap or replaced paid source can require renewed standing authorization. Configuration is free; future refreshes retain their approved cycle ceiling." };
|
|
1612
1660
|
}
|
|
1661
|
+
function providerPolicyReadRecommendation(query) {
|
|
1662
|
+
if (!/\bproviders?\b/.test(query)
|
|
1663
|
+
|| !/\b(limits?|request caps?|rate caps?|overrides?)\b/.test(query)
|
|
1664
|
+
|| /\b(set|setting|change|changing|update|updating|delete|deleting|remove|removing|clear|clearing|raise|raising|lower|lowering|reset|resetting|enrich\w*|find|search companies|send\w*|scrape\w*)\b/.test(query))
|
|
1665
|
+
return null;
|
|
1666
|
+
return {
|
|
1667
|
+
tools: ["oxygen_provider_limits_list"], commands: ["limits provider list"],
|
|
1668
|
+
execution: "Read saved provider request-limit overrides and the plan's BYOK daily default with its enforced or observe-only mode. Hydrate oxygen_provider_limits_list; external MCP uses oxygen_capabilities_read, built-in agents invoke the native capability. MCP pages are bounded; continue at paging.next_offset with the same provider filter. Reads cost 0 credits. Setting a policy or removing its exact selector is a separate approved admin/owner write; removing one does not clear other operation overrides.",
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* "Where did our credits go": the audit ranks spend areas with shares over the
|
|
1673
|
+
* whole window. Blind agents read the raw usage ledger or left for the docs to
|
|
1674
|
+
* find it (OXP-43.63, 2026-10-06). Balance, buying, pricing and budget asks
|
|
1675
|
+
* keep their own readers.
|
|
1676
|
+
*/
|
|
1677
|
+
function spendBreakdownRecommendation(query) {
|
|
1678
|
+
if (!/\b(credits?|spend|spent|spending)\b/.test(query)
|
|
1679
|
+
|| !/\b(where|went|breakdown|break down|broken down|by (?:category|provider|tool|model|source|run|workspace)|categor(?:y|ies)|areas?|biggest|largest|audit)\b/.test(query)
|
|
1680
|
+
|| /\b(left|remaining|balance|buy|purchase|top ?up|refill|invoices?|budgets?|limits?|caps?|estimate|prices?|pricing|plans?|subscription|cancel)\b/.test(query))
|
|
1681
|
+
return null;
|
|
1682
|
+
return {
|
|
1683
|
+
tools: ["oxygen_billing_audit", "oxygen_billing_usage"], commands: ["billing audit", "billing usage"],
|
|
1684
|
+
execution: "Read where managed credits went with oxygen_billing_audit: spend_by_category ranks each area's credits and share of managed_credits_spent, largest first, over the whole window; group_by provider, tool, model, run or workspace narrows it. Spent is reserved minus released, so it includes open holds; oxygen_billing_usage lists the ledger and its managed_credits_used counts captured credits only. External MCP uses oxygen_capabilities_read; built-in agents invoke the native capability. Reads cost 0 credits.",
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
function sendingBlockReadRecommendation(query) {
|
|
1688
|
+
if (!/\b(emails?|address(?:es)?|domains?)\b/.test(query)
|
|
1689
|
+
|| !/\b(suppressions?|sending blocks?|blocked)\b/.test(query)
|
|
1690
|
+
|| /\b(add|adding|import|importing|remove|removing|delete|deleting|clear|clearing|unblock|unblocking|send|sending now|reply|replies|inbox|unibox|dns|websites?)\b/.test(query))
|
|
1691
|
+
return null;
|
|
1692
|
+
const domain = /\bdomains?\b/.test(query);
|
|
1693
|
+
const address = /\baddress(?:es)?\b/.test(query) || !domain;
|
|
1694
|
+
return {
|
|
1695
|
+
tools: [...(address ? ["oxygen_suppressions_addresses_list"] : []), ...(domain ? ["oxygen_suppressed_domains_list"] : [])],
|
|
1696
|
+
commands: [...(address ? ["suppressions addresses"] : []), ...(domain ? ["suppressions domains"] : [])],
|
|
1697
|
+
execution: "Read the separate email-address and domain sending-block ledgers. Hydrate the selected native schemas; their reason enums differ. External MCP uses oxygen_capabilities_read; built-in agents invoke the native reads. MCP defaults to bounded summary pages with explicit omitted fields; continue at paging.next_offset with the same filters. Reads cost 0 credits. Removing exact approved keys is a separate write that can restore future sending eligibility; other suppression ledgers still apply and enrollments do not resume automatically.",
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
function isSpintaxIntent(query) {
|
|
1701
|
+
// Copy variation belongs to the native spintax owner; campaign A/B stats and
|
|
1702
|
+
// unrelated physical variants keep their existing owners.
|
|
1703
|
+
if (/\bspintax\b/.test(query))
|
|
1704
|
+
return true;
|
|
1705
|
+
if (/\b(posts?|publishing|ugc|visuals?|images?|videos?|scoreboard|stats|statistics|performance|winners?)\b/.test(query))
|
|
1706
|
+
return false;
|
|
1707
|
+
return /\b(wording|copy|message|draft|subject(?: line)?)\b.{0,45}\b(variations?|variants?|paraphrase|rewrite)\b/.test(query)
|
|
1708
|
+
|| /\b(paraphrase|rewrite|variations?)\b.{0,45}\b(wording|copy|message|draft|subject(?: line)?)\b/.test(query);
|
|
1709
|
+
}
|
|
1613
1710
|
function explicitCapabilityIntent(query) {
|
|
1614
1711
|
if (sentLinkedInInvitationReadRecommendation(query))
|
|
1615
1712
|
return ROUTE_BY_ID.get("connected-linkedin") ?? null;
|
|
@@ -1617,6 +1714,8 @@ function explicitCapabilityIntent(query) {
|
|
|
1617
1714
|
return ROUTE_BY_ID.get("records") ?? null;
|
|
1618
1715
|
if (crmMaintenanceRecommendation(query))
|
|
1619
1716
|
return ROUTE_BY_ID.get("records") ?? null;
|
|
1717
|
+
if (crmSampleRecordRecommendation(query))
|
|
1718
|
+
return ROUTE_BY_ID.get("records") ?? null;
|
|
1620
1719
|
if (databaseRecommendation(query))
|
|
1621
1720
|
return ROUTE_BY_ID.get("platform-operations") ?? null;
|
|
1622
1721
|
if (oneShotContactFindingRecommendation(query))
|
|
@@ -1627,9 +1726,17 @@ function explicitCapabilityIntent(query) {
|
|
|
1627
1726
|
return ROUTE_BY_PRIMITIVE.get("messages") ?? null;
|
|
1628
1727
|
if (tableFeedRecommendation(query))
|
|
1629
1728
|
return ROUTE_BY_PRIMITIVE.get("tables") ?? null;
|
|
1729
|
+
if (providerPolicyReadRecommendation(query))
|
|
1730
|
+
return ROUTE_BY_ID.get("billing-and-budgets") ?? null;
|
|
1731
|
+
if (sendingBlockReadRecommendation(query))
|
|
1732
|
+
return ROUTE_BY_PRIMITIVE.get("sequences") ?? null;
|
|
1733
|
+
if (isSpintaxIntent(query))
|
|
1734
|
+
return ROUTE_BY_PRIMITIVE.get("sequences") ?? null;
|
|
1630
1735
|
if (isConnectedAppsIntent(query) || isConnectedAppEventsIntent(query) || isConnectedAppSpendIntent(query)) {
|
|
1631
1736
|
return ROUTE_BY_ID.get("workspace-access") ?? null;
|
|
1632
1737
|
}
|
|
1738
|
+
if (spendBreakdownRecommendation(query))
|
|
1739
|
+
return ROUTE_BY_ID.get("billing-and-budgets") ?? null;
|
|
1633
1740
|
// Ahead of every primitive rule: the pages a workspace hides are NAMED after
|
|
1634
1741
|
// primitives ("hide Publishing and Workflows"), which those rules would claim.
|
|
1635
1742
|
if (isWorkspacePagesIntent(query) || isWorkspaceSettingsIntent(query)) {
|
|
@@ -1920,12 +2027,24 @@ function recommendationsFor(card, query) {
|
|
|
1920
2027
|
const contactFind = card.id === "sourcing-and-provider-tools" ? oneShotContactFindingRecommendation(query) : null;
|
|
1921
2028
|
if (contactFind)
|
|
1922
2029
|
return contactFind;
|
|
2030
|
+
const crmSample = card.id === "records" ? crmSampleRecordRecommendation(query) : null;
|
|
2031
|
+
if (crmSample)
|
|
2032
|
+
return crmSample;
|
|
1923
2033
|
const crmContactRead = card.id === "records" ? storedCrmContactReadRecommendation(query) : null;
|
|
1924
2034
|
if (crmContactRead)
|
|
1925
2035
|
return crmContactRead;
|
|
1926
2036
|
const replyDraftRead = card.primitive === "messages" ? storedReplyDraftReadRecommendation(query) : null;
|
|
1927
2037
|
if (replyDraftRead)
|
|
1928
2038
|
return replyDraftRead;
|
|
2039
|
+
const providerRead = card.id === "billing-and-budgets" ? providerPolicyReadRecommendation(query) : null;
|
|
2040
|
+
if (providerRead)
|
|
2041
|
+
return providerRead;
|
|
2042
|
+
const spendRead = card.id === "billing-and-budgets" ? spendBreakdownRecommendation(query) : null;
|
|
2043
|
+
if (spendRead)
|
|
2044
|
+
return spendRead;
|
|
2045
|
+
const blockRead = card.primitive === "sequences" ? sendingBlockReadRecommendation(query) : null;
|
|
2046
|
+
if (blockRead)
|
|
2047
|
+
return blockRead;
|
|
1929
2048
|
const sent = card.id === "connected-linkedin" ? sentLinkedInInvitationReadRecommendation(query) : null;
|
|
1930
2049
|
if (sent)
|
|
1931
2050
|
return sent;
|
|
@@ -2334,6 +2453,15 @@ function recommendationsFor(card, query) {
|
|
|
2334
2453
|
}
|
|
2335
2454
|
}
|
|
2336
2455
|
if (card.primitive === "messages") {
|
|
2456
|
+
// A saved, shared or default inbox view is the views commands, never a send.
|
|
2457
|
+
// "view my inbox" stays a read; the noun needs "views" or a save/default/share word.
|
|
2458
|
+
if ((/\bviews\b/.test(query) && /\b(inbox|unibox)\b/.test(query))
|
|
2459
|
+
|| (/\bview\b/.test(query) && /\b(saved?|default|shared|team)\b/.test(query))) {
|
|
2460
|
+
return {
|
|
2461
|
+
tools: ["oxygen_inbox_views_list", "oxygen_inbox_view_create", "oxygen_inbox_view_update"],
|
|
2462
|
+
commands: ["inbox views list", "inbox views create", "inbox views update"],
|
|
2463
|
+
};
|
|
2464
|
+
}
|
|
2337
2465
|
if (/\b(message )?reviews?\b/.test(query)) {
|
|
2338
2466
|
return {
|
|
2339
2467
|
tools: ["oxygen_reviews_list", "oxygen_reviews_next", "oxygen_reviews_decide"],
|
|
@@ -2349,6 +2477,9 @@ function recommendationsFor(card, query) {
|
|
|
2349
2477
|
}
|
|
2350
2478
|
}
|
|
2351
2479
|
if (card.primitive === "sequences") {
|
|
2480
|
+
if (isSpintaxIntent(query)) {
|
|
2481
|
+
return { tools: ["oxygen_spintax_generate"], commands: ["spintax generate"] };
|
|
2482
|
+
}
|
|
2352
2483
|
if (isSequenceScheduleIntent(query) || isSequenceUnsubscribeIntent(query)) {
|
|
2353
2484
|
return {
|
|
2354
2485
|
tools: ["oxygen_sequences_update", "oxygen_sequences_get"],
|
|
@@ -37,14 +37,14 @@ export const BROWSER_OPERATIONS = [
|
|
|
37
37
|
operation("billing.commitments.view", "/api/cli/billing/commitments/view", "GET", "billing commitments view", "oxygen_billing_commitments_view", "billing", "Read the complete fixed-credit commitment view and linked billing-owner display, without enumerating a child's sibling resources."),
|
|
38
38
|
operation("billing.invoices.view", "/api/cli/billing/invoices/view", "GET", "billing invoices view", "oxygen_billing_invoices_view", "billing", "Read the browser invoice-history projection, including exact native timestamps, amounts and signed Stripe invoice links."),
|
|
39
39
|
operation("billing.summary", "/api/cli/billing/summary", "GET", "billing summary", "oxygen_billing_summary", "billing", "Read the complete non-writing wallet, plan, seat-capacity and billing-owner summary."),
|
|
40
|
-
operation("billing.usage.view", "/api/cli/billing/usage/view", "GET", "billing usage view", "oxygen_billing_usage_view", "billing", "
|
|
40
|
+
operation("billing.usage.view", "/api/cli/billing/usage/view", "GET", "billing usage view", "oxygen_billing_usage_view", "billing", "Credit spend over 7, 30 or 90 days by category and provider, each with its share, plus permitted linked-workspace rollups. totalSpent includes open holds; billing usage managed_credits_used counts captured credits only. Other windows or groupings: billing audit.", { days: enumOf(["7", "30", "90"]), workspace: text() }),
|
|
41
41
|
operation("integrations.managed_endpoints", "/api/cli/integrations/composio/endpoints", "GET", "integrations managed-endpoints", "oxygen_integration_managed_endpoints", "integrations", "Read a managed integration's full action catalog and parameter schemas.", { integration_id: text() }, ["integration_id"]),
|
|
42
42
|
operation("integrations.openrouter_models", "/api/cli/integrations/openrouter/models", "GET", "integrations openrouter-models", "oxygen_openrouter_models", "integrations", "Read model and pricing metadata using this workspace's connected OpenRouter account; credentials are never returned."),
|
|
43
43
|
operation("integrations.status", "/api/cli/integrations/status", "GET", "integrations status", "oxygen_integration_status", "integrations", "Read active connection counts, sender connection state and the workspace's usable BYOK plan entitlement."),
|
|
44
44
|
operation("pricing.customer_credit_prices", "/api/cli/pricing/customer-credit-prices", "GET", "pricing customer-credit-prices", "oxygen_customer_credit_prices", "workspace", "Read the customer credit-price catalog, quantity decisions and price provenance used by the Table picker."),
|
|
45
45
|
operation("sequences.contacts.rows", "/api/cli/sequences/{sequence}/contacts/rows", "GET", "sequences contacts rows", "oxygen_sequence_contacts_rows", "sequences", "Read the full contact grid projection with exact contact-state filter and native synthetic status columns.", { contact_state: enumOf(["all", "not_contacted", "contacted", "connected", "replied", "positive_reply"]), limit: int({ minimum: 1, maximum: 2000 }), cursor: text(), offset: int({ minimum: 0 }), f: text(), s: text() }),
|
|
46
46
|
operation("sequences.leads.rows", "/api/cli/sequences/{sequence}/leads/rows", "GET", "sequences leads rows", "oxygen_sequence_leads_rows", "sequences", "Read the complete enrolled-lead grid projection, including sequence status, step, action and failure columns.", { limit: int({ minimum: 1, maximum: 2000 }), cursor: text(), offset: int({ minimum: 0 }), f: text(), s: text() }),
|
|
47
|
-
operation("unibox.metadata", "/api/cli/unibox/metadata", "GET", "unibox metadata", "oxygen_unibox_metadata", "inbox", "Read the complete authenticated inbox account, filter and tag metadata."),
|
|
47
|
+
operation("unibox.metadata", "/api/cli/unibox/metadata", "GET", "unibox metadata", "oxygen_unibox_metadata", "inbox", "Read the complete authenticated inbox account, filter and tag metadata, including each sender profile (person) and its id."),
|
|
48
48
|
operation("workflows.observed_output", "/api/cli/workflows/{workflow}/observed-output", "GET", "workflows observed-output", "oxygen_workflow_observed_output", "workflows", "Inspect observed output paths and bounded samples from an exact run or recent matching workflow revision.", { run_id: text(), source_hash: text() }),
|
|
49
49
|
operation("workflows.run.events", "/api/cli/workflows/{workflow}/runs/{run}/events", "GET", "workflows runs events", "oxygen_workflow_run_events", "workflows", "Read the exact Workflow run's bounded event history and whether it is still active.", { limit: int({ minimum: 1, maximum: 1000 }) }),
|
|
50
50
|
operation("workflows.run.step_statuses", "/api/cli/workflows/{workflow}/runs/{run}/step-statuses", "GET", "workflows runs step-statuses", "oxygen_workflow_run_step_statuses", "workflows", "Read the current Workflow run status and every step status for immediate execution readback."),
|
|
@@ -52,7 +52,7 @@ export const BROWSER_OPERATIONS = [
|
|
|
52
52
|
operation("tables.resource.edit", table, "PATCH", "tables edit", "oxygen_table_resource_edit", "tables", "Rename a Table or move it to a workspace project; returns the saved Table.", { name: text(), project_id: text() }),
|
|
53
53
|
operation("tables.resource.archive", table, "DELETE", "tables archive", "oxygen_tables_archive", "tables", "Archive a Table indefinitely without dropping its physical data or scheduling a purge.", {}, [], nativeCli),
|
|
54
54
|
operation("tables.resource.duplicate", `${table}/duplicate`, "POST", "tables duplicate", "oxygen_tables_duplicate", "tables", "Duplicate a Table with its columns and optionally its row values.", { name: text(), include_rows: bool() }, [], nativeCli),
|
|
55
|
-
operation("tables.resource.activity", `${table}/activity`, "GET", "tables changes", "oxygen_table_resource_activity", "tables", "Read a Table's
|
|
55
|
+
operation("tables.resource.activity", `${table}/activity`, "GET", "tables changes", "oxygen_table_resource_activity", "tables", "Read a Table's live runs and queue health.", { limit: int({ minimum: 1 }) }),
|
|
56
56
|
operation("tables.resource.column_stats", `${table}/column-stats`, "GET", "tables column-stats", "oxygen_table_column_stats", "tables", "Read per-column completion and provider statistics; large Tables use labeled samples."),
|
|
57
57
|
operation("tables.resource.fill_progress", `${table}/fill-progress`, "GET", "tables fill-progress", "oxygen_table_fill_progress", "tables", "Read hosted Table fill progress and any actionable ingestion failure."),
|
|
58
58
|
operation("tables.resource.kind_estimates", "/api/cli/tables/column-kind-estimates", "GET", "columns kind-estimates", "oxygen_column_kind_estimates", "tables", "Read managed credit estimates for available Table column kinds."),
|
|
@@ -6,6 +6,8 @@ import { TABLE_FAVORITE_CONTRACT, TABLE_FAVORITES_LIST_CONTRACT, TABLE_ITEM_RESO
|
|
|
6
6
|
import { BILLING_OWNER_READ_CONTRACT } from "./billing-owner-read.js";
|
|
7
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
|
+
import { spintaxContract } from "./spintax.js";
|
|
10
|
+
import { betaFeaturesUpdateContract, emailDomainSuppressionListContract, emailDomainSuppressionRemoveContract, emailSuppressionRemoveContract, providerLimitListContract, providerLimitSelectorContract, providerLimitUpdateContract } from "./workspace-utilities.js";
|
|
9
11
|
const text = (description) => str(description === undefined ? {} : { description });
|
|
10
12
|
const list = () => arr(str());
|
|
11
13
|
const json = { type: "object", additionalProperties: true };
|
|
@@ -63,9 +65,9 @@ export const CUSTOMER_OPERATIONS = [
|
|
|
63
65
|
operation("knowledge.pages.archive", "/api/cli/knowledge/pages/archive", "POST", "knowledge page archive", "oxygen_knowledge_page_archive", "knowledge", "Archive a knowledge page while preserving its history.", { slug: text(), id: text(), expected_revision: int({ minimum: 1 }) }, [], mcpOnly),
|
|
64
66
|
operation("knowledge.sync.manifest", "/api/cli/knowledge/sync/manifest", "POST", "knowledge sync-manifest", "oxygen_knowledge_sync_manifest", "knowledge", "Read the wiki revision and content-hash inventory without downloading instruction bodies.", {}, [], { mutates: false }),
|
|
65
67
|
operation("knowledge.sync.delta", "/api/cli/knowledge/sync/delta", "POST", "knowledge sync", "oxygen_knowledge_sync_delta", "knowledge", "Read one resumable batch of wiki pages and tombstones, bounded by the server's byte budget.", { cursor: text(), limit: int({ minimum: 1, maximum: 200 }) }, [], { ...mcpOnly, mutates: false }),
|
|
66
|
-
operation("limits.provider.get", "/api/cli/limits/provider", "GET", "limits provider list", "oxygen_provider_limits_list", "billing", "Read
|
|
67
|
-
operation("limits.provider.set", "/api/cli/limits/provider", "POST", "limits provider set", "oxygen_provider_limits_set", "billing", "Set
|
|
68
|
-
operation("limits.provider.delete", "/api/cli/limits/provider", "DELETE", "limits provider delete", "oxygen_provider_limits_delete", "billing", "Clear
|
|
68
|
+
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 } }),
|
|
69
|
+
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 }),
|
|
70
|
+
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"] }),
|
|
69
71
|
operation("mailboxes.emailguard.auto_enroll.get", "/api/cli/mailboxes/emailguard/auto-enroll", "GET", "mailboxes emailguard auto-enroll", "oxygen_mailboxes_emailguard_auto_enroll_get", "deliverability", "Read the workspace's automatic monitoring enrollment grant and last sweep.", {}, [], mcpOnly),
|
|
70
72
|
operation("mailboxes.emailguard.auto_enroll.set", "/api/cli/mailboxes/emailguard/auto-enroll", "POST", "mailboxes emailguard auto-enroll", "oxygen_mailboxes_emailguard_auto_enroll_set", "deliverability", "Preview, arm or disarm automatic mailbox monitoring. Arming recurring spend requires approved=true and max_credits_per_cycle.", { enabled: bool(), approved: bool(), max_credits_per_cycle: num({ minimum: 0 }), run: bool(), dry_run: bool() }, [], { ...mcpOnly, spends: true }),
|
|
71
73
|
operation("mailboxes.warmup_profiles.get", "/api/cli/mailboxes/warmup-profile", "GET", "mailboxes warmup profile", "oxygen_mailboxes_warmup_profiles", "deliverability", "Read available OXYGEN Warm-up profiles and current bindings.", {}, [], mcpOnly),
|
|
@@ -76,8 +78,8 @@ export const CUSTOMER_OPERATIONS = [
|
|
|
76
78
|
operation("publishing.drafts.update", "/api/cli/publishing/drafts/{draft_id}", "PATCH", "publishing drafts edit", "oxygen_publishing_draft_update", "publishing", "Edit a queued publishing draft's text without publishing it.", { text: text(), variant_index: int({ minimum: 0 }) }, ["text"], mcpOnly),
|
|
77
79
|
operation("publishing.mentions.search", "/api/cli/publishing/mentions/search", "POST", "publishing mentions search", "oxygen_publishing_mentions_search", "publishing", "Find LinkedIn company or person identities for a mention; free, subject to the connected account's read quota.", { query: text(), kind: enumOf(["company", "person"]), connection_id: text() }, ["query"], { mutates: false }),
|
|
78
80
|
operation("settings.beta.get", "/api/cli/settings/beta-features", "GET", "settings beta-features get", "oxygen_settings_beta_features_get", "workspace", "Read this workspace's beta-navigation opt-in."),
|
|
79
|
-
operation("settings.beta.set", "/api/cli/settings/beta-features", "POST", "settings beta-features set", "oxygen_settings_beta_features_set", "workspace", "Enable or disable beta navigation for
|
|
80
|
-
operation("spintax.generate", "/api/cli/spintax", "POST", "spintax generate", "oxygen_spintax_generate", "sequences", "Generate spintax
|
|
81
|
+
operation("settings.beta.set", "/api/cli/settings/beta-features", "POST", "settings beta-features set", "oxygen_settings_beta_features_set", "workspace", "Enable or disable beta navigation for every workspace member; admin or owner only. Approve the preference change. This does not disable routes. 0 credits.", {}, [], { contract: betaFeaturesUpdateContract }),
|
|
82
|
+
operation("spintax.generate", "/api/cli/spintax", "POST", "spintax generate", "oxygen_spintax_generate", "sequences", "Generate wording or copy variations as spintax: live (default) costs 3 credits and requires approval and a credit ceiling; explicit dry_run is a free preview. Preserve merge variables; send nothing. Live requires approved:true and max_credits>=3 after preview; failed generation refunds the reservation.", {}, [], { contract: spintaxContract, defaults: { mode: "live" }, spends: true }),
|
|
81
83
|
operation("table_action_items.resolve", "/api/cli/table-action-items/{item_id}/resolve", "POST", "table-actions resolve", "oxygen_table_action_item_resolve", "tables", "Record a human-verified uncertain Table action outcome without repeating it. Use cells resolve for row/column coordinates.", TABLE_ITEM_RESOLVE_CONTRACT.properties, ["outcome"]),
|
|
82
84
|
operation("table_ingestions.append", "/api/cli/table-ingestion-runs/{ingestion_run_id}/items", "POST", "table-ingestions append", "oxygen_table_ingestions_append_items", "runs", "Append bounded ingestion items to an existing run; preserves the run's approved scope.", { items: rows(), max_attempts: int({ minimum: 1 }) }, ["items"], cliOnly),
|
|
83
85
|
operation("tables.favorites.get", "/api/cli/tables/favorite", "GET", "tables favorites", "oxygen_tables_favorites", "tables", "Read starred Tables/folders (per-member preference; free (0 credits)); next_cursor. Catalog stars differ.", TABLE_FAVORITES_LIST_CONTRACT.properties, [], { generated: { cli: true, mcp: false } }),
|
|
@@ -118,9 +120,9 @@ export const CUSTOMER_OPERATIONS = [
|
|
|
118
120
|
operation("orgs.billing_owners", "/api/cli/orgs/billing-owners", "GET", "orgs billing-owners", "oxygen_workspace_billing_owners", "workspace", "Read the current workspace's billing owner, entitlement and payer candidates, including ineligibility reasons. Optional organization asserts the current workspace. Free.", BILLING_OWNER_READ_CONTRACT.properties, [], mcpOnly),
|
|
119
121
|
operation("search.runs.items", "/api/cli/search/runs/{search_run_id}/items", "GET", "search runs items", "oxygen_search_run_items", "enrichment", "Read durable search ingestion items by run id and optional status.", { status: text(), limit: int({ minimum: 1 }) }, [], mcpOnly),
|
|
120
122
|
operation("senders.checkpoints", "/api/cli/senders/checkpoints", "GET", "senders checkpoints", "oxygen_sender_checkpoints", "sequences", "Read connected sender authentication checkpoints that need human completion.", {}, [], mcpOnly),
|
|
121
|
-
operation("suppressions.domains.list", "/api/cli/suppressions/domains", "GET", "suppressions domains", "oxygen_suppressed_domains_list", "sequences", "Read domain-wide
|
|
122
|
-
operation("suppressions.domains.remove", "/api/cli/suppressions/domains", "DELETE", "suppressions remove-domain", "oxygen_suppressed_domains_remove", "sequences", "Remove one domain-wide
|
|
123
|
-
operation("suppressions.addresses.remove", "/api/cli/suppressions/addresses", "DELETE", "suppressions remove-address", "oxygen_suppressed_addresses_remove", "sequences", "Remove one email-address
|
|
123
|
+
operation("suppressions.domains.list", "/api/cli/suppressions/domains", "GET", "suppressions domains", "oxygen_suppressed_domains_list", "sequences", "Read domain-wide sending blocks, newest first. Domain reasons differ from address reasons. MCP defaults to bounded summary pages; follow paging.next_offset with the same filters. 0 credits.", {}, [], { contract: emailDomainSuppressionListContract, generated: { cli: false, mcp: false } }),
|
|
124
|
+
operation("suppressions.domains.remove", "/api/cli/suppressions/domains", "DELETE", "suppressions remove-domain", "oxygen_suppressed_domains_remove", "sequences", "Remove one domain-wide sending block or up to 500 exact domains after approval of the keys. Can restore future sending eligibility; other suppression ledgers still apply. This does not resume enrollments automatically. 0 credits.", {}, [], { ...mcpOnly, contract: emailDomainSuppressionRemoveContract }),
|
|
125
|
+
operation("suppressions.addresses.remove", "/api/cli/suppressions/addresses", "DELETE", "suppressions remove-address", "oxygen_suppressed_addresses_remove", "sequences", "Remove one email-address sending block or up to 500 exact addresses after approval of the keys. Can restore future sending eligibility; other suppression ledgers still apply. This does not resume enrollments automatically. 0 credits.", {}, [], { ...mcpOnly, contract: emailSuppressionRemoveContract }),
|
|
124
126
|
operation("table_action_runs.list", "/api/cli/table-action-runs", "GET", "table-runs list", "oxygen_table_action_runs_list", "runs", "Read durable Table action runs, optionally filtered by status or trace.", { table: text(), status: text(), trace_id: text(), limit: int({ minimum: 1 }) }, ["table"], mcpOnly),
|
|
125
127
|
operation("tables.columns.fields", "/api/cli/tables/columns/fields", "GET", "columns fields", "oxygen_table_column_fields", "tables", "Read the output fields available from one hosted Table column.", { table: text(), column: text() }, ["table", "column"], mcpOnly),
|
|
126
128
|
operation("tools.favorites", "/api/cli/tools/favorites", "GET", "tools favorites", "oxygen_tool_favorites", "enrichment", "Read this member's favorite tool catalog entries in the active workspace.", {}, [], mcpOnly),
|
|
@@ -12,7 +12,7 @@ export function limitsReadQueryIssue(query) {
|
|
|
12
12
|
}
|
|
13
13
|
const record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
14
14
|
const pick = (value, keys) => Object.fromEntries(keys.filter(key => Object.hasOwn(value, key)).map(key => [key, value[key]]));
|
|
15
|
-
const COMMON = ["organization_id", "plan_band", "plan_band_label", "plan_tier", "limits_tier", "source", "entitled", "shared_billing", "web_url", "deepLink"];
|
|
15
|
+
const COMMON = ["organization_id", "plan_band", "plan_band_label", "plan_tier", "limits_tier", "source", "entitled", "plan_resolution", "shared_billing", "web_url", "deepLink"];
|
|
16
16
|
const SECTION_FIELDS = { api: "api_rate_limits", webhooks: "webhook_ingress", spend: "spend_safety_defaults", sending: "sending_limits", workflow: "workflow_limits" };
|
|
17
17
|
function capacityProjection(value) {
|
|
18
18
|
const capacity = record(value), usage = record(capacity.usage), reconciled = usage.row_usage_reconciled === true;
|
|
@@ -119,5 +119,15 @@ export function projectLimitsRead(data, query) {
|
|
|
119
119
|
delete bounded[key];
|
|
120
120
|
markOmitted(key);
|
|
121
121
|
}
|
|
122
|
+
// Both links open the limits page. Drop duplicate navigation last when a
|
|
123
|
+
// long app URL crowds out the protected workspace and plan scope.
|
|
124
|
+
for (const key of ["deepLink", "web_url"]) {
|
|
125
|
+
if (JSON.stringify(bounded).length <= maxChars)
|
|
126
|
+
break;
|
|
127
|
+
if (Object.hasOwn(bounded, key)) {
|
|
128
|
+
delete bounded[key];
|
|
129
|
+
markOmitted(key);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
122
132
|
return bounded;
|
|
123
133
|
}
|
|
@@ -118,18 +118,22 @@ function childPath(parent, key) {
|
|
|
118
118
|
}
|
|
119
119
|
function walkObject(schema, value, path, walk) {
|
|
120
120
|
const out = {};
|
|
121
|
+
const set = (key, entry) => {
|
|
122
|
+
// JSON keys remain own data properties, including an explicit __proto__.
|
|
123
|
+
Object.defineProperty(out, key, { value: entry, enumerable: true, writable: true, configurable: true });
|
|
124
|
+
};
|
|
121
125
|
const properties = schema.properties ?? {};
|
|
122
126
|
for (const key of schema.required ?? []) {
|
|
123
|
-
if (value[key] === undefined)
|
|
127
|
+
if (!Object.hasOwn(value, key) || value[key] === undefined)
|
|
124
128
|
walk.issues.push({ path: childPath(path, key), message: `${childPath(path, key)} is required.` });
|
|
125
129
|
}
|
|
126
130
|
for (const [key, entry] of Object.entries(value)) {
|
|
127
131
|
if (entry === undefined)
|
|
128
132
|
continue;
|
|
129
133
|
const at = childPath(path, key);
|
|
130
|
-
const property = properties[key];
|
|
134
|
+
const property = Object.hasOwn(properties, key) ? properties[key] : undefined;
|
|
131
135
|
if (property !== undefined) {
|
|
132
|
-
|
|
136
|
+
set(key, walkValue(property, entry, at, walk));
|
|
133
137
|
continue;
|
|
134
138
|
}
|
|
135
139
|
const extra = schema.additionalProperties;
|
|
@@ -139,14 +143,14 @@ function walkObject(schema, value, path, walk) {
|
|
|
139
143
|
}
|
|
140
144
|
else {
|
|
141
145
|
walk.unknownKeys.push(at);
|
|
142
|
-
|
|
146
|
+
set(key, entry);
|
|
143
147
|
}
|
|
144
148
|
}
|
|
145
149
|
else if (extra && typeof extra === "object") {
|
|
146
|
-
|
|
150
|
+
set(key, walkValue(extra, entry, at, walk));
|
|
147
151
|
}
|
|
148
152
|
else {
|
|
149
|
-
|
|
153
|
+
set(key, entry);
|
|
150
154
|
}
|
|
151
155
|
}
|
|
152
156
|
return out;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const SPINTAX_GENERATION_CREDITS = 3;
|
|
2
|
+
export declare const spintaxContract: import("./schema.js").ContractObject<{
|
|
3
|
+
readonly text: import("./schema.js").StringProperty;
|
|
4
|
+
readonly section: import("./schema.js").StringProperty;
|
|
5
|
+
readonly mode: {
|
|
6
|
+
type: "string";
|
|
7
|
+
enum: readonly ("dry_run" | "live")[];
|
|
8
|
+
description?: string;
|
|
9
|
+
};
|
|
10
|
+
readonly approved: import("./schema.js").BooleanProperty;
|
|
11
|
+
readonly max_credits: {
|
|
12
|
+
type: "number";
|
|
13
|
+
description?: string;
|
|
14
|
+
minimum?: number;
|
|
15
|
+
maximum?: number;
|
|
16
|
+
"x-clamp"?: true;
|
|
17
|
+
};
|
|
18
|
+
}, "text">;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { bool, enumOf, num, obj, str } from "./schema.js";
|
|
2
|
+
// The native managed service charges this fixed amount for a usable spin.
|
|
3
|
+
// Preview, reservation, settlement and agent-visible schemas share this owner.
|
|
4
|
+
export const SPINTAX_GENERATION_CREDITS = 3;
|
|
5
|
+
export const spintaxContract = obj({
|
|
6
|
+
text: str({ minLength: 1, maxLength: 4000, description: "Outreach copy to vary; merge variables are preserved." }),
|
|
7
|
+
section: str({ maxLength: 80, description: "Optional section label, e.g. Subject." }),
|
|
8
|
+
mode: enumOf(["dry_run", "live"], { description: `Omitted mode is live. Explicit dry_run previews the fixed ${SPINTAX_GENERATION_CREDITS}-credit cost without generation; live requires approval and a ceiling.` }),
|
|
9
|
+
approved: bool({ description: `True only after approval of the exact text and ${SPINTAX_GENERATION_CREDITS}-credit cost.` }),
|
|
10
|
+
max_credits: num({ minimum: 0, description: `Live ceiling must cover the fixed ${SPINTAX_GENERATION_CREDITS}-credit cost.` }),
|
|
11
|
+
}, { required: ["text"] });
|