@oxygen-agent/cli 1.1014.1035 → 1.1014.1087
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 +1 -0
- package/dist/commands/deliverability.js +2 -0
- package/dist/commands/sequences.js +30 -2
- package/dist/credentials.js +1 -1
- package/dist/customer-operation-commands.js +62 -3
- package/dist/help.js +1 -1
- package/dist/index.d.ts +0 -97
- package/dist/index.js +0 -98
- package/dist/workspace-placement-commands.d.ts +3 -0
- package/dist/workspace-placement-commands.js +32 -0
- package/node_modules/@oxygen/shared/dist/agent-sessions.js +1 -1
- package/node_modules/@oxygen/shared/dist/agents-enabled.js +5 -2
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +23 -5
- package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.js +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +1 -1
- package/node_modules/@oxygen/shared/dist/contracts/guided-activation.js +5 -5
- package/node_modules/@oxygen/shared/dist/contracts/guided-company-files.d.ts +30 -0
- package/node_modules/@oxygen/shared/dist/contracts/guided-company-files.js +53 -0
- package/node_modules/@oxygen/shared/dist/contracts/guided-first-sourcing.d.ts +120 -0
- package/node_modules/@oxygen/shared/dist/contracts/guided-first-sourcing.js +48 -0
- package/node_modules/@oxygen/shared/dist/contracts/guided-onboarding-journey.d.ts +267 -0
- package/node_modules/@oxygen/shared/dist/contracts/guided-onboarding-journey.js +63 -0
- package/node_modules/@oxygen/shared/dist/contracts/operations.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/contracts/operations.js +9 -4
- package/node_modules/@oxygen/shared/dist/contracts/table-query-raw.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/contracts/table-query-raw.js +3 -0
- package/node_modules/@oxygen/shared/dist/contracts/teammates.js +7 -7
- package/node_modules/@oxygen/shared/dist/email-tracking-token.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/email-tracking-token.js +1 -0
- package/node_modules/@oxygen/shared/dist/file-import.d.ts +46 -0
- package/node_modules/@oxygen/shared/dist/file-import.js +138 -0
- package/node_modules/@oxygen/shared/dist/guided-activation.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/guided-company-files.d.ts +13 -0
- package/node_modules/@oxygen/shared/dist/guided-company-files.js +14 -0
- package/node_modules/@oxygen/shared/dist/guided-first-sourcing.d.ts +116 -0
- package/node_modules/@oxygen/shared/dist/guided-first-sourcing.js +11 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-answers.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-answers.js +42 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-company-context.d.ts +22 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-company-context.js +38 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.d.ts +147 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.fixture.d.ts +12 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.fixture.js +83 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-journey.js +44 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-prospects.d.ts +7 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-prospects.js +13 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-signals.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/guided-onboarding-signals.js +34 -0
- package/node_modules/@oxygen/shared/dist/guided-trial-billing.d.ts +26 -0
- package/node_modules/@oxygen/shared/dist/guided-trial-billing.js +19 -0
- package/node_modules/@oxygen/shared/dist/user-capability-routing.js +78 -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/node_modules/@oxygen/shared/dist/workspace-placement-view.d.ts +149 -0
- package/node_modules/@oxygen/shared/dist/workspace-placement-view.js +101 -0
- package/node_modules/@oxygen/shared/dist/workspace-placement.d.ts +77 -0
- package/node_modules/@oxygen/shared/dist/workspace-placement.js +94 -0
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/command-manifest.js
CHANGED
|
@@ -262,6 +262,7 @@ const PREVIEW_BY_DEFAULT_COMMANDS = new Set([
|
|
|
262
262
|
"domains dns apply",
|
|
263
263
|
"mailboxes warmup enable",
|
|
264
264
|
"mailboxes warmup google",
|
|
265
|
+
"deliverability workspace-test run",
|
|
265
266
|
"deliverability placement-test run",
|
|
266
267
|
"deliverability placement-test send",
|
|
267
268
|
"managed-inboxes recover",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { registerWorkspacePlacementCommands } from "../workspace-placement-commands.js";
|
|
1
2
|
import { requestOxygen } from "../http-client.js";
|
|
2
3
|
import { readOption } from "../util.js";
|
|
3
4
|
import { Command, CommanderError, Option } from "commander";
|
|
@@ -95,4 +96,5 @@ export function registerDeliverabilityFamily(program, handleAsyncAction) {
|
|
|
95
96
|
.action(async (id, options) => {
|
|
96
97
|
await handleAsyncAction("deliverability placement-test get", options, () => requestOxygen(`/api/cli/deliverability/placement-tests/${encodeURIComponent(id)}`));
|
|
97
98
|
}))));
|
|
99
|
+
registerWorkspacePlacementCommands(program.commands.find(c => c.name() === "deliverability"), handleAsyncAction);
|
|
98
100
|
}
|
|
@@ -1593,6 +1593,7 @@ export function registerSequencesFamily(program, handleAsyncAction) {
|
|
|
1593
1593
|
.option("--senders <ids>", "Comma-separated LinkedIn or WhatsApp sender account ids (or connection / Unipile ids; `senders list` / `whatsapp accounts` list them). Optional at create; enroll and start require at least one when the journey has LinkedIn or WhatsApp steps (attach later with `sequences update --senders`).")
|
|
1594
1594
|
.option("--table <id>", "Source table id or slug whose rows supply {{column}} template values.")
|
|
1595
1595
|
.option("--activation-id <id>", "Guided authored LinkedIn draft: bind the creator's activation and exact First leads Table. Free; retries reuse the native draft.")
|
|
1596
|
+
.option("--activation-revision <n>", "Reviewed current activation revision, a positive safe integer; requires --activation-id. Native first creation checks it before mutation.")
|
|
1596
1597
|
.option("--from-table <id>", "Alias of --table: the source table id or slug this sequence draws its leads and {{column}} values from.")
|
|
1597
1598
|
.option("--enroll", "After creating the sequence, immediately enroll every row of the bound table (same as running `sequences enroll --from-table` next). Requires --table/--from-table. Enrolling spends no credits and sends nothing.")
|
|
1598
1599
|
.option("--url-column <key>", "Column key holding each lead's LinkedIn URL/provider id.")
|
|
@@ -1649,6 +1650,11 @@ export function registerSequencesFamily(program, handleAsyncAction) {
|
|
|
1649
1650
|
// so it names the same thing here rather than being a second
|
|
1650
1651
|
// concept; `--table` stays for every existing script.
|
|
1651
1652
|
const sourceTable = readOption(options.fromTable) ?? readOption(options.table);
|
|
1653
|
+
const activationId = readOption(options.activationId);
|
|
1654
|
+
const activationRevision = readPositiveInt(options.activationRevision);
|
|
1655
|
+
if (options.activationRevision !== undefined && (!activationId || !Number.isSafeInteger(activationRevision))) {
|
|
1656
|
+
throw new OxygenError("invalid_input", "--activation-revision needs --activation-id and a positive safe integer from current onboarding.", { exitCode: 1 });
|
|
1657
|
+
}
|
|
1652
1658
|
if (options.enroll && !sourceTable) {
|
|
1653
1659
|
throw new Error("--enroll needs a source table: pass --from-table <table> (or --table <table>).");
|
|
1654
1660
|
}
|
|
@@ -1662,7 +1668,8 @@ export function registerSequencesFamily(program, handleAsyncAction) {
|
|
|
1662
1668
|
...(senders.length > 0 ? { senders } : {}),
|
|
1663
1669
|
...(tags.length > 0 ? { tags } : {}),
|
|
1664
1670
|
...(sourceTable ? { source_table_id: sourceTable } : {}),
|
|
1665
|
-
...(
|
|
1671
|
+
...(activationId ? { activation_id: activationId } : {}),
|
|
1672
|
+
...(activationRevision !== undefined ? { activation_revision: activationRevision } : {}),
|
|
1666
1673
|
...(options.enroll ? { enroll_from_table: true } : {}),
|
|
1667
1674
|
...(readOption(options.urlColumn) ? { linkedin_url_column_key: readOption(options.urlColumn) } : {}),
|
|
1668
1675
|
...(email ? { email } : {}),
|
|
@@ -1709,6 +1716,9 @@ export function registerSequencesFamily(program, handleAsyncAction) {
|
|
|
1709
1716
|
.description("Update a sequence. Journey structure/channels/email binding are draft-only so revised scope receives a fresh launch approval; a started sequence may only re-time waits it already has (clear a delay by zeroing it, never by deleting the step — enrollments track position by index); sender pools can change while draft or paused; launch caps change through `sequences start` after first start. Name, throttles, the sending schedule, tags, the unsubscribe option (off by default), and open/click tracking toggles remain editable as allowed by the server. Pass only the fields you want to change.")
|
|
1710
1717
|
.addHelpText("after", "\nSenders: email_send.sender_profile_id sends one email from any inbox of a selected person (a lead already assigned to that person keeps its own inbox), and email_send.sender_mailbox_id from one exact inbox of a selected sender; a LinkedIn step's sender_profile_id sends it from a selected person (invite, message, inmail and withdraw_invite share one or none). Omit both to use the lead's assigned person. An email whose From changes needs its own subject_template. `sequences start <sequence>` without --approved shows who sends each step; nothing changes.\n")
|
|
1711
1718
|
.argument("<sequence>", "Sequence id or slug.")
|
|
1719
|
+
.option("--expected-revision <revision>", "Exact sequence_revision from sequences get; stale edits refuse. Not launch approval.")
|
|
1720
|
+
.option("--activation-id <id>", "Guided activation UUID; requires --activation-revision and --expected-revision.")
|
|
1721
|
+
.option("--activation-revision <revision>", "Current guided activation revision; requires --activation-id and --expected-revision.")
|
|
1712
1722
|
.option("--name <name>", "New human-readable sequence name.")
|
|
1713
1723
|
.option("--steps-file <path>", "Path to a JSON file: { \"steps\": [...] } replacing the journey. Once started, only wait timing may change: same steps, same order (zero a delay, never delete its step). Copy supports {{column}} interpolation; every step also takes {{sender_first_name}} / {{sender_last_name}} / {{sender_full_name}} for the person who sends it, and email adds {{sender_email}} (a same-named row column wins). A `branch` can also route on the lead's data with a data leaf { has_column: \"email\" } (true when that row_values column is non-empty; present:false for \"missing\").")
|
|
1714
1724
|
.option("--channels <list>", "Draft only: comma-separated channels: linkedin,email,whatsapp,call,crm.")
|
|
@@ -1771,6 +1781,18 @@ export function registerSequencesFamily(program, handleAsyncAction) {
|
|
|
1771
1781
|
.action(async (sequence, options) => {
|
|
1772
1782
|
await handleAsyncAction("sequences update", options, () => {
|
|
1773
1783
|
const body = {};
|
|
1784
|
+
const expectedRevision = options.expectedRevision;
|
|
1785
|
+
const activationId = options.activationId;
|
|
1786
|
+
const activationRevision = readPositiveInt(options.activationRevision);
|
|
1787
|
+
if (options.activationRevision !== undefined && activationRevision === undefined) {
|
|
1788
|
+
throw new OxygenError("invalid_number", "Expected a positive activation revision.", { exitCode: 1 });
|
|
1789
|
+
}
|
|
1790
|
+
if (expectedRevision !== undefined)
|
|
1791
|
+
body.expected_revision = expectedRevision;
|
|
1792
|
+
if (activationId !== undefined)
|
|
1793
|
+
body.activation_id = activationId;
|
|
1794
|
+
if (activationRevision !== undefined)
|
|
1795
|
+
body.activation_revision = activationRevision;
|
|
1774
1796
|
const name = readOption(options.name);
|
|
1775
1797
|
if (name)
|
|
1776
1798
|
body.name = name;
|
|
@@ -1834,9 +1856,15 @@ export function registerSequencesFamily(program, handleAsyncAction) {
|
|
|
1834
1856
|
.description("Get a sequence's definition, senders, status, and credit usage. --readiness adds the launch check — whether it CAN go live, why not, and the mailbox pool it would send from — without touching it; `sequences preview <sequence>` (a read; 0 credits) shows who sends each step (sender_journey).")
|
|
1835
1857
|
.argument("<sequence>", "Sequence id or slug.")
|
|
1836
1858
|
.option("--readiness", "Add launch_readiness: blockers (e.g. a missing send cap), warnings, and the resolved sending mailboxes. Read-only — sends nothing, changes nothing, spends nothing. Skips the LinkedIn rendered-copy scan, which stays in `sequences start` without --approved.")
|
|
1859
|
+
.addOption(new Option("--include <fields>", "Add readiness or your retained guided_proposal, comma-separated. Reading a proposal does not apply it or start the campaign.")
|
|
1860
|
+
.choices(["readiness", "guided_proposal", "readiness,guided_proposal", "guided_proposal,readiness"]))
|
|
1837
1861
|
.option("--json", "Print a JSON envelope.")
|
|
1838
1862
|
.action(async (sequence, options) => {
|
|
1839
|
-
|
|
1863
|
+
const include = new Set(options.include?.split(",") ?? []);
|
|
1864
|
+
if (options.readiness)
|
|
1865
|
+
include.add("readiness");
|
|
1866
|
+
const query = include.size ? `?include=${[...include].join(",")}` : "";
|
|
1867
|
+
await handleAsyncAction("sequences get", options, () => requestOxygen(`/api/cli/sequences/${encodeURIComponent(sequence)}${query}`));
|
|
1840
1868
|
}))
|
|
1841
1869
|
.addCommand(new Command("enroll")
|
|
1842
1870
|
.description("Enroll leads into a sequence. Enrolling spends no credits. An active sequence contacts the new leads on its next dispatch tick; a draft or paused one holds them until `sequences start`. --from-table enrolls every not-yet-enrolled row of the sequence's bound source table (up to 500 per run; the response's from_table.has_more says whether to run again) — for a LinkedIn sequence the profile is read from the bound table's URL column, where a full /in/ URL and a bare public handle carrying a hyphen or digit (ada-lovelace) both work, so no pre-resolved provider ids are needed; each handle Oxygen canonicalized is echoed in linkedin_handles_canonicalized (value + url) to check before start, and every row that still cannot be resolved is named in unresolved_linkedin_lead_details (row, value, reason) rather than only counted in unresolved_linkedin_leads. In a LinkedIn + email sequence a row with no usable profile but a recipient email is still enrolled for the email steps (its LinkedIn steps are skipped as no_linkedin_profile, a connection wait passes at once and a connection branch takes its not-connected path) and is listed in linkedin_unavailable_lead_details instead; unresolved_linkedin_leads names only rows that were not enrolled. --leads-file enrolls an explicit JSON list { leads: [...] }: for an email/WhatsApp sequence a lead identified only by an email (row_values.email) or phone (row_values.phone) is auto-filed as a row in the sequence's leads table (auto-creating and binding one if there is none), deduped by email/phone, so no table has to exist first (the table is returned as leads_table with a web_url); a LinkedIn + email sequence auto-files an email-only lead the same way; a LinkedIn lead needs a lead_provider_id, a lead_profile_url (a /in/ URL or a bare public handle with a hyphen or digit), or a table_row_id whose row carries one of those. A CRM-only lead needs a stable table_row_id or lead_provider_id in addition to mapped row_values; a HubSpot contact id used in crm_task associations identifies the destination record, not the Oxygen enrollment. When the sequence is bound to a source table, a lead's table_row_id auto-snapshots that row's columns (incl. AI/tool outputs) into row_values for {{column}} copy — explicit row_values win. Idempotent per table row (and per email/phone for auto-filed leads). WhatsApp-only campaigns also enroll each normalized number once across all source rows and enrollment statuses; mixed campaigns keep other channels and skip duplicate WhatsApp actions. Duplicate protection defaults to this campaign; --whatsapp-duplicate-scope on create/update can additionally block identical copy across all campaigns. The org do-not-contact list is always enforced; --exclude-contacted and --suppress-list add further opt-in skips (reported under skipped_by_reason). Leads already owned by a sender account (from an earlier real send) are routed back to that same account; a lead owned by a sender NOT on this sequence is skipped (bound_to_other_sender) unless --ignore-sender-bindings. This enrolls into an OXYGEN Sequence; to put the same rows into a campaign you already run in Smartlead, Instantly, lemlist or HeyReach, use `sequences push-external`. esp.pool_scope identifies the active workspace inventory used for routing and strict_deferred_leads estimates; these estimates omit sequence sender restrictions, readiness and daily limits. A zero estimate does not guarantee readiness. Review sequences preview before launch.")
|
package/dist/credentials.js
CHANGED
|
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync,
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
|
-
import { OxygenError } from "@oxygen/shared";
|
|
6
|
+
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
7
7
|
import { sameApiOrigin } from "./runtime.js";
|
|
8
8
|
const execFileAsync = promisify(execFile);
|
|
9
9
|
const SERVICE_NAME = "oxygen-cli";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { readFile, writeFile } from "node:fs/promises";
|
|
2
3
|
import { basename, extname, resolve } from "node:path";
|
|
3
4
|
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
@@ -31,8 +32,50 @@ const GROUP_PURPOSES = {
|
|
|
31
32
|
"notetaker notes": "Edit saved meeting notes.",
|
|
32
33
|
onboarding: "Save workspace discovery choices and resume guided onboarding.",
|
|
33
34
|
"onboarding activation": "Resume your dev-pilot activation and its native resources.",
|
|
35
|
+
"onboarding journey": "Resume private onboarding, save answers and navigate using native revisions.",
|
|
36
|
+
"onboarding first-sourcing": "Read and review the native first-leads search before approving a capped run.",
|
|
37
|
+
};
|
|
38
|
+
const GUIDED_JOURNEY_FLAGS = {
|
|
39
|
+
activationId: { flag: "activation-id", help: "Exact activation ID returned by onboarding activation start or status." },
|
|
40
|
+
revision: { help: "Current activation revision returned by onboarding journey get; refresh after a conflict." },
|
|
41
|
+
mutation_id: { help: "Stable ID for this exact change; reuse only to recover the same request." },
|
|
42
|
+
step: { help: "Current saved step from onboarding journey get; use the revision you reviewed." },
|
|
43
|
+
answers: { help: "JSON object keyed by the current step, containing that step's reviewed answers." },
|
|
44
|
+
paused: { help: "Pause at this saved step; --no-paused resumes without advancing.", negateHelp: "Resume the saved step and tour position without changing visited history." },
|
|
45
|
+
tour_active_stop: { help: "Exact tour panel to return to, separate from visited history; only at the tour step." },
|
|
46
|
+
};
|
|
47
|
+
const GUIDED_FIRST_SOURCING_FLAGS = {
|
|
48
|
+
activationId: GUIDED_JOURNEY_FLAGS.activationId,
|
|
49
|
+
revision: { help: "Current activation revision from onboarding first-sourcing get; refresh and review after a conflict." },
|
|
50
|
+
generation: { help: "Current journey generation from onboarding journey get; supply revision, generation and --timezone-hint together for nightly defaults." },
|
|
51
|
+
timezoneHint: { flag: "timezone-hint", aliases: ["timezoneHint"], help: "Provisional customer/browser IANA timezone; supply revision, generation and --timezone-hint together. Does not confirm or save a schedule." },
|
|
52
|
+
"bounds.targetCount": { flag: "bounds-target-count", help: "Positive lead target, at most 50000; must match the stored preview for run." },
|
|
53
|
+
"bounds.maxPages": { flag: "bounds-max-pages", help: "Positive provider-page ceiling, at most 1000; must match the stored preview for run." },
|
|
54
|
+
"bounds.maxCredits": { flag: "bounds-max-credits", help: "Positive credit ceiling; native current pricing and the stored preview apply." },
|
|
55
|
+
previewId: { flag: "preview-id", help: "Exact stored previewId returned by onboarding first-sourcing preview; pass unchanged." },
|
|
56
|
+
previewDigest: { flag: "preview-digest", help: "Exact lowercase previewDigest from the stored native preview; pass unchanged." },
|
|
57
|
+
approved: { help: "Approve this exact stored preview and its bounds; native billing, access and current revision still apply." },
|
|
34
58
|
};
|
|
35
59
|
const OPERATION_FLAGS = {
|
|
60
|
+
"onboarding first-sourcing get": GUIDED_FIRST_SOURCING_FLAGS,
|
|
61
|
+
"onboarding first-sourcing preview": GUIDED_FIRST_SOURCING_FLAGS,
|
|
62
|
+
"onboarding first-sourcing run": GUIDED_FIRST_SOURCING_FLAGS,
|
|
63
|
+
"onboarding company-files upload": {
|
|
64
|
+
activationId: GUIDED_JOURNEY_FLAGS.activationId,
|
|
65
|
+
revision: GUIDED_JOURNEY_FLAGS.revision,
|
|
66
|
+
upload_id: { help: "Stable UUID for these exact original file bytes; reuse only to recover the same upload." },
|
|
67
|
+
sha256: { help: "Lowercase SHA-256 of the original file bytes; native storage verifies it." },
|
|
68
|
+
size_bytes: { help: "Exact original file byte length, before base64 encoding." },
|
|
69
|
+
},
|
|
70
|
+
"onboarding journey get": GUIDED_JOURNEY_FLAGS,
|
|
71
|
+
"onboarding journey save": GUIDED_JOURNEY_FLAGS,
|
|
72
|
+
"onboarding journey advance": GUIDED_JOURNEY_FLAGS,
|
|
73
|
+
"onboarding journey back": GUIDED_JOURNEY_FLAGS,
|
|
74
|
+
"onboarding journey restart": GUIDED_JOURNEY_FLAGS,
|
|
75
|
+
"onboarding journey navigation": GUIDED_JOURNEY_FLAGS,
|
|
76
|
+
"onboarding journey prospects-preview": GUIDED_JOURNEY_FLAGS,
|
|
77
|
+
"onboarding journey billing": GUIDED_JOURNEY_FLAGS,
|
|
78
|
+
"onboarding journey checkout": GUIDED_JOURNEY_FLAGS,
|
|
36
79
|
"agent session activate": {
|
|
37
80
|
revision: { help: "Current session revision returned by agent session preview; must match the reviewed configuration." },
|
|
38
81
|
preview_token: { help: "Exact preview_token from agent session preview, binding reviewed resources, actions, schedule and credit caps. Pass unchanged; --approved is separate. Preview again after changes." },
|
|
@@ -85,12 +128,14 @@ export function registerCustomerOperationCommands(program, handle) {
|
|
|
85
128
|
if (spec.binary)
|
|
86
129
|
command.requiredOption("--output <path>", "Write the exact downloaded bytes to this local file.");
|
|
87
130
|
if (spec.multipart)
|
|
88
|
-
command.option("--file <path>", "Upload bytes from this local file. Omit the base64 flag; file name and content type default from the path."
|
|
131
|
+
command.option("--file <path>", "Upload bytes from this local file. Omit the base64 flag; file name and content type default from the path."
|
|
132
|
+
+ (spec.multipart.fileMetadata ? " Original SHA-256 and size are derived; explicit assertions must match." : ""));
|
|
89
133
|
for (const name of contract.required ?? []) {
|
|
90
134
|
const property = contract.properties[name];
|
|
91
135
|
const flag = `--${overlay[name]?.flag ?? name.replace(/_/g, "-")}`;
|
|
92
136
|
const option = command.options.find((candidate) => candidate.long === flag || candidate.long === `${flag}-json`);
|
|
93
|
-
const multipartAlternative = spec.multipart && [spec.multipart.contentField, spec.multipart.nameField, spec.multipart.typeField
|
|
137
|
+
const multipartAlternative = spec.multipart && [spec.multipart.contentField, spec.multipart.nameField, spec.multipart.typeField,
|
|
138
|
+
...(spec.multipart.fileMetadata ? [spec.multipart.fileMetadata.sha256Field, spec.multipart.fileMetadata.sizeBytesField] : [])].includes(name);
|
|
94
139
|
if (multipartAlternative) {
|
|
95
140
|
if (option)
|
|
96
141
|
option.description = `${option.description} Required unless supplied by --file.`.trim();
|
|
@@ -113,9 +158,22 @@ export function registerCustomerOperationCommands(program, handle) {
|
|
|
113
158
|
if (input[spec.multipart.contentField] !== undefined)
|
|
114
159
|
throw new OxygenError("invalid_request", "Choose --file or the base64 content flag, not both.", { exitCode: 2 });
|
|
115
160
|
const filePath = String(options.file);
|
|
116
|
-
|
|
161
|
+
const bytes = await readFile(filePath);
|
|
162
|
+
input[spec.multipart.contentField] = bytes.toString("base64");
|
|
117
163
|
input[spec.multipart.nameField] ??= basename(filePath);
|
|
118
164
|
input[spec.multipart.typeField] ??= uploadContentType(filePath);
|
|
165
|
+
const metadata = spec.multipart.fileMetadata;
|
|
166
|
+
if (metadata) {
|
|
167
|
+
const digest = createHash("sha256").update(bytes).digest("hex");
|
|
168
|
+
if (input[metadata.sha256Field] !== undefined && input[metadata.sha256Field] !== digest) {
|
|
169
|
+
throw new OxygenError("invalid_request", `${metadata.sha256Field} must match the original file bytes.`, { exitCode: 2 });
|
|
170
|
+
}
|
|
171
|
+
if (input[metadata.sizeBytesField] !== undefined && input[metadata.sizeBytesField] !== bytes.length) {
|
|
172
|
+
throw new OxygenError("invalid_request", `${metadata.sizeBytesField} must equal the original decoded byte length.`, { exitCode: 2 });
|
|
173
|
+
}
|
|
174
|
+
input[metadata.sha256Field] ??= digest;
|
|
175
|
+
input[metadata.sizeBytesField] ??= bytes.length;
|
|
176
|
+
}
|
|
119
177
|
}
|
|
120
178
|
const issues = operationInputIssues(spec, input);
|
|
121
179
|
if (issues.length)
|
|
@@ -188,6 +246,7 @@ function uploadContentType(filePath) {
|
|
|
188
246
|
const types = {
|
|
189
247
|
".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".webp": "image/webp", ".svg": "image/svg+xml",
|
|
190
248
|
".csv": "text/csv", ".json": "application/json", ".jsonl": "application/x-ndjson",
|
|
249
|
+
".txt": "text/plain", ".md": "text/markdown", ".markdown": "text/markdown", ".pdf": "application/pdf",
|
|
191
250
|
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
192
251
|
};
|
|
193
252
|
return types[extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
package/dist/help.js
CHANGED
|
@@ -170,7 +170,7 @@ export function applyOxygenHelp(program, binaryName, docsUrl = "https://oxygen-a
|
|
|
170
170
|
// first-touch surface names the skill that teaches the GTM loops.
|
|
171
171
|
` 2. ${binaryName} skills install --json load the skills that teach the GTM loops (automatic after login)`,
|
|
172
172
|
" Add --project to keep the skills inside the current project, including confined agent sessions.",
|
|
173
|
-
` 3. ${binaryName} context resolve --purpose onboarding --json load
|
|
173
|
+
` 3. ${binaryName} context resolve --purpose onboarding --json load context; may migrate the workspace schema`,
|
|
174
174
|
// Populated workspaces need the same first-win guidance as empty ones.
|
|
175
175
|
` Getting started? ${binaryName} skills get oxygen-onboarding --json`,
|
|
176
176
|
" It sharpens your offer and ICP, then ranks the warmest leads you can reach now.",
|
package/dist/index.d.ts
CHANGED
|
@@ -1,99 +1,2 @@
|
|
|
1
|
-
import "./admin-lead-magnet-commands.js";
|
|
2
|
-
import "./admin-primary-providers-render.js";
|
|
3
|
-
import "./auto-update.js";
|
|
4
|
-
import "./browser-login.js";
|
|
5
|
-
import "./cli-values.js";
|
|
6
|
-
import "./column-decision-options.js";
|
|
7
|
-
import "./column-run-notices.js";
|
|
8
|
-
import "./command-manifest.js";
|
|
9
|
-
import "./compact-list-flags.js";
|
|
10
|
-
import "./connectors-commands.js";
|
|
11
|
-
import "./contract-flags.js";
|
|
12
|
-
import "./credentials.js";
|
|
13
|
-
import "./crm-file-read-commands.js";
|
|
14
|
-
import "./crm-support-flags.js";
|
|
15
|
-
import "./customer-operation-commands.js";
|
|
16
|
-
import "./database-query-flags.js";
|
|
17
|
-
import "./diagnostics.js";
|
|
18
|
-
import "./feed-read-flags.js";
|
|
19
|
-
import "./functions-commands.js";
|
|
20
|
-
import "./help.js";
|
|
21
|
-
import "./home-standup.js";
|
|
22
|
-
import "./http-client.js";
|
|
23
|
-
import "./inbox-needs-reply-notice.js";
|
|
24
|
-
import "./json-output.js";
|
|
25
|
-
import "./knowledge-mirror.js";
|
|
26
|
-
import "./knowledge-repository-commands.js";
|
|
27
|
-
import "./local-custom-http-column.js";
|
|
28
|
-
import "./mailbox-ramp-notice.js";
|
|
29
|
-
import "./run-wait.js";
|
|
30
|
-
import "./runtime.js";
|
|
31
|
-
import "./search-ai-filter-notice.js";
|
|
32
|
-
import "./sender-limits-flags.js";
|
|
33
|
-
import "./session.js";
|
|
34
|
-
import "./settings-commands.js";
|
|
35
|
-
import "./skills.js";
|
|
36
|
-
import "./streamed-file-import.js";
|
|
37
|
-
import "./tool-catalog-detail-command.js";
|
|
38
|
-
import "./tool-catalog-search-window.js";
|
|
39
|
-
import "./transcript.js";
|
|
40
|
-
import "./ugc-commands.js";
|
|
41
|
-
import "./update.js";
|
|
42
|
-
import "./util.js";
|
|
43
|
-
import "./visual-commands.js";
|
|
44
|
-
import "./whatsapp-account-flags.js";
|
|
45
|
-
import "./workflow-plan-limit-notices.js";
|
|
46
|
-
import "./workflow-run-list-flags.js";
|
|
47
|
-
import "./workspace-skill-commands.js";
|
|
48
|
-
import "./workspace-utility-flags.js";
|
|
49
|
-
import "@oxygen/recipe-sdk";
|
|
50
|
-
import "@oxygen/shared";
|
|
51
|
-
import "@oxygen/shared/billing";
|
|
52
|
-
import "@oxygen/shared/company-reply-pause";
|
|
53
|
-
import "@oxygen/shared/contracts/billing-owner-read";
|
|
54
|
-
import "@oxygen/shared/contracts/copilot-sessions";
|
|
55
|
-
import "@oxygen/shared/contracts/crm-enrichment-read";
|
|
56
|
-
import "@oxygen/shared/contracts/database";
|
|
57
|
-
import "@oxygen/shared/contracts/inbox-drafts-read";
|
|
58
|
-
import "@oxygen/shared/contracts/integration-connect";
|
|
59
|
-
import "@oxygen/shared/contracts/knowledge-content-reads";
|
|
60
|
-
import "@oxygen/shared/contracts/limits-read";
|
|
61
|
-
import "@oxygen/shared/contracts/linkedin-invites-list";
|
|
62
|
-
import "@oxygen/shared/contracts/operations";
|
|
63
|
-
import "@oxygen/shared/contracts/pipedrive-catalog";
|
|
64
|
-
import "@oxygen/shared/contracts/publishing-reads";
|
|
65
|
-
import "@oxygen/shared/contracts/sender-limits";
|
|
66
|
-
import "@oxygen/shared/contracts/sequence-analytics-read";
|
|
67
|
-
import "@oxygen/shared/contracts/sequence-list";
|
|
68
|
-
import "@oxygen/shared/contracts/table-feed-update";
|
|
69
|
-
import "@oxygen/shared/contracts/table-list";
|
|
70
|
-
import "@oxygen/shared/contracts/table-query-grid";
|
|
71
|
-
import "@oxygen/shared/contracts/table-query-raw";
|
|
72
|
-
import "@oxygen/shared/contracts/whatsapp-reads";
|
|
73
|
-
import "@oxygen/shared/contracts/workflow-run-list";
|
|
74
|
-
import "@oxygen/shared/copilot-questions";
|
|
75
|
-
import "@oxygen/shared/data-suppliers";
|
|
76
|
-
import "@oxygen/shared/file-import";
|
|
77
|
-
import "@oxygen/shared/knowledge-constants";
|
|
78
|
-
import "@oxygen/shared/linkedin-sequences";
|
|
79
|
-
import "@oxygen/shared/mailbox-import";
|
|
80
|
-
import "@oxygen/shared/plan-band";
|
|
81
|
-
import "@oxygen/shared/pricing-sheet";
|
|
82
|
-
import "@oxygen/shared/select-options";
|
|
83
|
-
import "@oxygen/shared/sequence-lifecycle";
|
|
84
|
-
import "@oxygen/shared/table-field-types";
|
|
85
|
-
import "@oxygen/shared/table-filter-operators";
|
|
86
|
-
import "@oxygen/shared/table-run-summary";
|
|
87
|
-
import "@oxygen/shared/user-capability-routing";
|
|
88
|
-
import "@oxygen/workflows";
|
|
89
|
-
import "commander";
|
|
90
|
-
import "node:child_process";
|
|
91
|
-
import "node:crypto";
|
|
92
|
-
import "node:fs";
|
|
93
|
-
import "node:os";
|
|
94
|
-
import "node:path";
|
|
95
|
-
import "node:process";
|
|
96
|
-
import "node:readline/promises";
|
|
97
|
-
import "node:url";
|
|
98
1
|
export { createProgram } from "./command-families/program.js";
|
|
99
2
|
export { formatDisabledWorkflowNotices } from "./command-support.js";
|
package/dist/index.js
CHANGED
|
@@ -1,101 +1,3 @@
|
|
|
1
|
-
// Preserve pre-extraction runtime import initialization order.
|
|
2
|
-
import "./admin-lead-magnet-commands.js";
|
|
3
|
-
import "./admin-primary-providers-render.js";
|
|
4
|
-
import "./auto-update.js";
|
|
5
|
-
import "./browser-login.js";
|
|
6
|
-
import "./cli-values.js";
|
|
7
|
-
import "./column-decision-options.js";
|
|
8
|
-
import "./column-run-notices.js";
|
|
9
|
-
import "./command-manifest.js";
|
|
10
|
-
import "./compact-list-flags.js";
|
|
11
|
-
import "./connectors-commands.js";
|
|
12
|
-
import "./contract-flags.js";
|
|
13
|
-
import "./credentials.js";
|
|
14
|
-
import "./crm-file-read-commands.js";
|
|
15
|
-
import "./crm-support-flags.js";
|
|
16
|
-
import "./customer-operation-commands.js";
|
|
17
|
-
import "./database-query-flags.js";
|
|
18
|
-
import "./diagnostics.js";
|
|
19
|
-
import "./feed-read-flags.js";
|
|
20
|
-
import "./functions-commands.js";
|
|
21
|
-
import "./help.js";
|
|
22
|
-
import "./home-standup.js";
|
|
23
|
-
import "./http-client.js";
|
|
24
|
-
import "./inbox-needs-reply-notice.js";
|
|
25
|
-
import "./json-output.js";
|
|
26
|
-
import "./knowledge-mirror.js";
|
|
27
|
-
import "./knowledge-repository-commands.js";
|
|
28
|
-
import "./local-custom-http-column.js";
|
|
29
|
-
import "./mailbox-ramp-notice.js";
|
|
30
|
-
import "./run-wait.js";
|
|
31
|
-
import "./runtime.js";
|
|
32
|
-
import "./search-ai-filter-notice.js";
|
|
33
|
-
import "./sender-limits-flags.js";
|
|
34
|
-
import "./session.js";
|
|
35
|
-
import "./settings-commands.js";
|
|
36
|
-
import "./skills.js";
|
|
37
|
-
import "./streamed-file-import.js";
|
|
38
|
-
import "./tool-catalog-detail-command.js";
|
|
39
|
-
import "./tool-catalog-search-window.js";
|
|
40
|
-
import "./transcript.js";
|
|
41
|
-
import "./ugc-commands.js";
|
|
42
|
-
import "./update.js";
|
|
43
|
-
import "./util.js";
|
|
44
|
-
import "./visual-commands.js";
|
|
45
|
-
import "./whatsapp-account-flags.js";
|
|
46
|
-
import "./workflow-plan-limit-notices.js";
|
|
47
|
-
import "./workflow-run-list-flags.js";
|
|
48
|
-
import "./workspace-skill-commands.js";
|
|
49
|
-
import "./workspace-utility-flags.js";
|
|
50
|
-
import "@oxygen/recipe-sdk";
|
|
51
|
-
import "@oxygen/shared";
|
|
52
|
-
import "@oxygen/shared/billing";
|
|
53
|
-
import "@oxygen/shared/company-reply-pause";
|
|
54
|
-
import "@oxygen/shared/contracts/billing-owner-read";
|
|
55
|
-
import "@oxygen/shared/contracts/copilot-sessions";
|
|
56
|
-
import "@oxygen/shared/contracts/crm-enrichment-read";
|
|
57
|
-
import "@oxygen/shared/contracts/database";
|
|
58
|
-
import "@oxygen/shared/contracts/inbox-drafts-read";
|
|
59
|
-
import "@oxygen/shared/contracts/integration-connect";
|
|
60
|
-
import "@oxygen/shared/contracts/knowledge-content-reads";
|
|
61
|
-
import "@oxygen/shared/contracts/limits-read";
|
|
62
|
-
import "@oxygen/shared/contracts/linkedin-invites-list";
|
|
63
|
-
import "@oxygen/shared/contracts/operations";
|
|
64
|
-
import "@oxygen/shared/contracts/pipedrive-catalog";
|
|
65
|
-
import "@oxygen/shared/contracts/publishing-reads";
|
|
66
|
-
import "@oxygen/shared/contracts/sender-limits";
|
|
67
|
-
import "@oxygen/shared/contracts/sequence-analytics-read";
|
|
68
|
-
import "@oxygen/shared/contracts/sequence-list";
|
|
69
|
-
import "@oxygen/shared/contracts/table-feed-update";
|
|
70
|
-
import "@oxygen/shared/contracts/table-list";
|
|
71
|
-
import "@oxygen/shared/contracts/table-query-grid";
|
|
72
|
-
import "@oxygen/shared/contracts/table-query-raw";
|
|
73
|
-
import "@oxygen/shared/contracts/whatsapp-reads";
|
|
74
|
-
import "@oxygen/shared/contracts/workflow-run-list";
|
|
75
|
-
import "@oxygen/shared/copilot-questions";
|
|
76
|
-
import "@oxygen/shared/data-suppliers";
|
|
77
|
-
import "@oxygen/shared/file-import";
|
|
78
|
-
import "@oxygen/shared/knowledge-constants";
|
|
79
|
-
import "@oxygen/shared/linkedin-sequences";
|
|
80
|
-
import "@oxygen/shared/mailbox-import";
|
|
81
|
-
import "@oxygen/shared/plan-band";
|
|
82
|
-
import "@oxygen/shared/pricing-sheet";
|
|
83
|
-
import "@oxygen/shared/select-options";
|
|
84
|
-
import "@oxygen/shared/sequence-lifecycle";
|
|
85
|
-
import "@oxygen/shared/table-field-types";
|
|
86
|
-
import "@oxygen/shared/table-filter-operators";
|
|
87
|
-
import "@oxygen/shared/table-run-summary";
|
|
88
|
-
import "@oxygen/shared/user-capability-routing";
|
|
89
|
-
import "@oxygen/workflows";
|
|
90
|
-
import "commander";
|
|
91
|
-
import "node:child_process";
|
|
92
|
-
import "node:crypto";
|
|
93
|
-
import "node:fs";
|
|
94
|
-
import "node:os";
|
|
95
|
-
import "node:path";
|
|
96
|
-
import "node:process";
|
|
97
|
-
import "node:readline/promises";
|
|
98
|
-
import "node:url";
|
|
99
1
|
import { maybeScheduleBackgroundUpdate } from "./auto-update.js";
|
|
100
2
|
import { emitCliFailure, writeJson } from "./cli-output.js";
|
|
101
3
|
import { createProgram } from "./command-families/program.js";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { readJsonFileValue } from "./command-support.js";
|
|
4
|
+
import { requestOxygen } from "./http-client.js";
|
|
5
|
+
export function registerWorkspacePlacementCommands(root, handle) {
|
|
6
|
+
root.addCommand(new Command("workspace-test").description("Compare email copy placement in this workspace's connected Google/Microsoft receiving inboxes: Gmail Primary/Promotions and Microsoft Focused/Other. Native sends, 0 additional credits; preview and exact message approval required.")
|
|
7
|
+
.addCommand(new Command("run").description("Preview a workspace-only copy test. Input JSON: sender_mailbox_ids, receiver_mailbox_ids, variants [{key,subject,body_text,body_html?}]. Approve the same input with returned request_id/plan_hash/rendered_at and max_live_sends. Up to five variants and 50 messages; never buys provider tests or resumes campaigns.")
|
|
8
|
+
.requiredOption("--input <file>", "JSON test input file.").option("--approved", "Execute the exact reviewed matrix.")
|
|
9
|
+
.option("--rendered-at <timestamp>", "Rendering timestamp returned by preview; reuse when approving.").option("--tracking-opens", "Preview with open tracking on verified workspace domain.").option("--tracking-clicks", "Preview with click tracking on verified workspace domain.")
|
|
10
|
+
.option("--request-id <id>", "Request UUID from preview.").option("--plan <hash>", "Exact preview plan hash.").option("--max-live-sends <n>", "Maximum approved diagnostic emails (1–50).")
|
|
11
|
+
.option("--preview-cursor <id>", "Read the next manifest page using the same input, request ID and rendering time.").option("--preview-limit <n>", "Preview manifest page size (1–20); never use with approval.")
|
|
12
|
+
.option("--preview-probe-id <id>", "Read exact rendered copy for a preview probe; reuse request ID and rendering time.").option("--preview-part <part>", "body_text or body_html.").option("--preview-offset <n>", "Unicode character offset returned by copy preview.").option("--preview-copy-limit <n>", "Copy page size (1–3000 characters).")
|
|
13
|
+
.option("--allow-paused-probe", "Explicitly allow diagnostic sends from paused senders, preserving pauses.").option("--json", "Print JSON.")
|
|
14
|
+
.action(async (options) => handle("deliverability workspace-test run", options, () => {
|
|
15
|
+
const input = readJsonFileValue(resolve(options.input), "--input");
|
|
16
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
17
|
+
throw new Error("--input must contain a JSON object.");
|
|
18
|
+
return requestOxygen("/api/cli/deliverability/workspace-tests", { method: "POST", body: { ...input, approved: options.approved === true,
|
|
19
|
+
...(options.approved ? { approved: true } : {}), ...(options.requestId ? { request_id: options.requestId } : {}),
|
|
20
|
+
...(options.plan ? { plan_hash: options.plan } : {}), ...(options.renderedAt ? { rendered_at: options.renderedAt } : {}), ...(options.trackingOpens ? { tracking_opens: true } : {}), ...(options.trackingClicks ? { tracking_clicks: true } : {}), ...(options.maxLiveSends ? { max_live_sends: Number(options.maxLiveSends) } : {}),
|
|
21
|
+
...(options.allowPausedProbe ? { allow_paused_probe: true } : {}),
|
|
22
|
+
...(options.previewCursor ? { preview_cursor: options.previewCursor } : {}), ...(options.previewLimit !== undefined ? { preview_limit: Number(options.previewLimit) } : {}),
|
|
23
|
+
...(options.previewProbeId ? { preview_probe_id: options.previewProbeId } : {}), ...(options.previewPart ? { preview_part: options.previewPart } : {}), ...(options.previewOffset !== undefined ? { preview_offset: Number(options.previewOffset) } : {}), ...(options.previewCopyLimit !== undefined ? { preview_copy_limit: Number(options.previewCopyLimit) } : {}) } });
|
|
24
|
+
})))
|
|
25
|
+
.addCommand(new Command("copy").description("Read a Sequence email step and its copy variants for workspace placement testing. Resolve personalization before preview; no send or charge.").argument("<sequence-id>").option("--step-index <n>", "Zero-based email step index.", "0").option("--json", "Print JSON.")
|
|
26
|
+
.action(async (id, options) => handle("deliverability workspace-test copy", options, () => requestOxygen(`/api/cli/deliverability/workspace-tests/copy?sequence_id=${encodeURIComponent(id)}&step_index=${encodeURIComponent(options.stepIndex)}`))))
|
|
27
|
+
.addCommand(new Command("list").description("Read this workspace's recent copy/Primary/Focused placement tests. Saved state only: no send, provider refresh or charge.").option("--cursor <id>", "Next cursor returned by list.").option("--limit <n>", "Tests per page (1–20).").option("--json", "Print JSON.")
|
|
28
|
+
.action(async (options) => handle("deliverability workspace-test list", options, () => requestOxygen(`/api/cli/deliverability/workspace-tests?${new URLSearchParams({ ...options.cursor ? { cursor: options.cursor } : {}, ...options.limit !== undefined ? { limit: String(options.limit) } : {} })}`))))
|
|
29
|
+
.addCommand(new Command("get").description("Read one workspace copy test: variant comparison, per-receiver category, partial results and unknowns. No send or provider refresh.").argument("<id>").option("--cursor <id>", "Next receiver-result cursor.").option("--limit <n>", "Receiver results per page (1–20).")
|
|
30
|
+
.option("--probe <id>", "Read this probe's frozen message copy.").option("--part <part>", "body_text or body_html.").option("--offset <n>", "Returned Unicode character offset.").option("--copy-limit <n>", "Copy page size (1–3000 characters).").option("--json", "Print JSON.")
|
|
31
|
+
.action(async (id, options) => handle("deliverability workspace-test get", options, () => requestOxygen(`/api/cli/deliverability/workspace-tests/${encodeURIComponent(id)}?${new URLSearchParams({ ...options.cursor ? { cursor: options.cursor } : {}, ...options.limit !== undefined ? { limit: String(options.limit) } : {}, ...options.probe ? { probe_id: options.probe } : {}, ...options.part ? { body_part: options.part } : {}, ...options.offset !== undefined ? { offset: String(options.offset) } : {}, ...options.copyLimit !== undefined ? { copy_limit: String(options.copyLimit) } : {} })}`)))));
|
|
32
|
+
}
|
|
@@ -132,7 +132,7 @@ export function validateAgentSessionConfig(config, requireResources = true) {
|
|
|
132
132
|
if (config.guided_activation !== undefined && (!config.teammate_routine
|
|
133
133
|
|| !config.guided_activation || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(config.guided_activation.activation_id)
|
|
134
134
|
|| !["lead_scan", "morning_briefing"].includes(config.guided_activation.purpose))) {
|
|
135
|
-
fail("Guided routines require an activation, purpose and
|
|
135
|
+
fail("Guided routines require an activation, purpose and bound Oxygent chat.");
|
|
136
136
|
}
|
|
137
137
|
if (config.guided_activation?.purpose === "lead_scan") {
|
|
138
138
|
const policy = config.guided_activation.scan_policy;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveDeployEnv } from "./deploy-env.js";
|
|
1
|
+
import { isProductionData, resolveDeployEnv } from "./deploy-env.js";
|
|
2
2
|
/**
|
|
3
3
|
* The production workspaces Agents, Oxygents and their routines may run in:
|
|
4
4
|
* staff workspaces only (Tim, 2026-10-16). Adding a customer's workspace opens
|
|
@@ -40,7 +40,10 @@ export function agentsEnabledIn(organizationId, env = process.env) {
|
|
|
40
40
|
function deploymentTier(env) {
|
|
41
41
|
try {
|
|
42
42
|
const { tier } = resolveDeployEnv(env);
|
|
43
|
-
|
|
43
|
+
if (tier === "prod" || tier === "shadow")
|
|
44
|
+
return tier;
|
|
45
|
+
// Managed dev telemetry can still carry production data via NODE_ENV.
|
|
46
|
+
return isProductionData(env) ? "prod" : "other";
|
|
44
47
|
}
|
|
45
48
|
catch {
|
|
46
49
|
return "unknown";
|
|
@@ -1553,8 +1553,17 @@ function isOxygentChatIntent(query) {
|
|
|
1553
1553
|
return true;
|
|
1554
1554
|
if (/\b(chatgpt|claude|codex|oauth|mcp|connectors?|connected apps?)\b/.test(query))
|
|
1555
1555
|
return false;
|
|
1556
|
-
|
|
1557
|
-
|
|
1556
|
+
const chatPrivacy = /\b(chats?|conversations?|histor(?:y|ies)|visibility|shared|private|personal)\b/.test(query);
|
|
1557
|
+
if (/\bai (?:assistants?|teammates?)\b/.test(query))
|
|
1558
|
+
return chatPrivacy;
|
|
1559
|
+
// Human membership asks must not re-enter Oxygent mutations through the
|
|
1560
|
+
// generic fallback after the user-facing routine router excludes them.
|
|
1561
|
+
// Explicit Oxygent/AI teammate access above keeps its native owner.
|
|
1562
|
+
if (/\b(?:invite|members?|membership|permissions?|roles?|human|colleague|coworker)\b/.test(query))
|
|
1563
|
+
return false;
|
|
1564
|
+
return /\b(?:assistants?|teammates?)\b/.test(query)
|
|
1565
|
+
&& /\b(chats?|conversations?|histor(?:y|ies)|visibility)\b/.test(query)
|
|
1566
|
+
&& /\b(shared|private|personal|workspace|team|visibility)\b/.test(query);
|
|
1558
1567
|
}
|
|
1559
1568
|
/**
|
|
1560
1569
|
* The apps a person connected with OAuth and where each one works: "make
|
|
@@ -2409,9 +2418,10 @@ function isPublishingPostIntent(query) {
|
|
|
2409
2418
|
}
|
|
2410
2419
|
function recommendationsFor(card, query) {
|
|
2411
2420
|
if (card.id === "agents" && isOxygentChatIntent(query)) {
|
|
2421
|
+
const continuing = /\b(continue|resume|talk)\b|\b(?:read|send|post)\b.*\b(?:chat|conversation|message)s?\b/.test(query);
|
|
2412
2422
|
return {
|
|
2413
|
-
tools: ["oxygen_oxygents_create", "oxygen_oxygents_update", "oxygen_oxygents_access", "oxygen_oxygents_get", "oxygen_oxygents_message"],
|
|
2414
|
-
commands: ["oxygents create", "oxygents update", "oxygents access", "oxygents get", "oxygents message"],
|
|
2423
|
+
tools: ["oxygen_oxygents_create", "oxygen_oxygents_update", "oxygen_oxygents_access", ...(continuing ? ["oxygen_oxygents_get", "oxygen_oxygents_message"] : [])],
|
|
2424
|
+
commands: ["oxygents create", "oxygents update", "oxygents access", ...(continuing ? ["oxygents get", "oxygents message"] : [])],
|
|
2415
2425
|
};
|
|
2416
2426
|
}
|
|
2417
2427
|
if (card.id === "billing-and-budgets" && isPartnerBonusReadIntent(query)) {
|
|
@@ -3310,11 +3320,19 @@ function isConnectionPriceIntent(query) {
|
|
|
3310
3320
|
* it. Sending, network, profile and post asks keep their own owners.
|
|
3311
3321
|
*/
|
|
3312
3322
|
function isLinkedInAccountConnectIntent(query) {
|
|
3323
|
+
// Exact native command discovery has no LinkedIn noun to score.
|
|
3324
|
+
if (/^(?:oxygen )?senders connect$/.test(query))
|
|
3325
|
+
return true;
|
|
3313
3326
|
if (!/\b(linkedin|unipile)\b/.test(query))
|
|
3314
3327
|
return false;
|
|
3315
3328
|
if (/^(linkedin|unipile)( accounts?)?$/.test(query))
|
|
3316
3329
|
return true;
|
|
3317
|
-
|
|
3330
|
+
const authenticationUrl = /\b(?:authentication|authorization|oauth|auth)\b/.test(query)
|
|
3331
|
+
&& /\b(?:url|link)\b/.test(query)
|
|
3332
|
+
&& /\b(?:connect|reconnect|account|sender)\b/.test(query);
|
|
3333
|
+
if (/\burl\b/.test(query) && !authenticationUrl)
|
|
3334
|
+
return false;
|
|
3335
|
+
if (/\b(send|sending|enroll|sequence|campaign|cadence|messages?|dms?|invites?|invitations?|posts?|profiles?|network|connections|followers|viewers|limits|hours|ads?|scrape|research|company|companies|pac(?:e|ing)|spacing|spaced|spread|apart|throttl\w*)\b/.test(query))
|
|
3318
3336
|
return false;
|
|
3319
3337
|
return /\b(connect|reconnect|link|add|accounts?|senders?|seats?|free|plan)\b/.test(query);
|
|
3320
3338
|
}
|
|
@@ -23,7 +23,7 @@ function agentSessionOperation(id, verb, method, suffix, summary, fields = {}, r
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
export const AGENT_SESSION_OPERATIONS = [
|
|
26
|
-
agentSessionOperation("agent.sessions.templates", "templates", "GET", "/templates", "Discover eight ongoing Agent job templates, reviewed actions, required resources, the full config schema and a draft example.
|
|
26
|
+
agentSessionOperation("agent.sessions.templates", "templates", "GET", "/templates", "Discover eight ongoing Agent job templates, reviewed actions, required resources, the full config schema and a draft example. Dev only; requires a current non-client workspace member and enabled sessions. Legacy Agents remain available."),
|
|
27
27
|
agentSessionOperation("agent.sessions.list", "list", "GET", "", "List independent ongoing sessions under an Agent, including status, next check and spend."),
|
|
28
28
|
agentSessionOperation("agent.sessions.create", "create", "POST", "", "Create a draft ongoing session. Explicit caps are required; review and activate separately.", { config: AGENT_SESSION_CONFIG_CONTRACT }, ["config"]),
|
|
29
29
|
agentSessionOperation("agent.sessions.get", "get", "GET", "/{session}", "Read this session's configuration revision, conversation, memory, next check, approvals and budget reservations."),
|
|
@@ -91,7 +91,7 @@ export const CUSTOMER_OPERATIONS = [
|
|
|
91
91
|
operation("tables.import.csv", "/api/cli/tables/import-csv", "POST", "tables import-csv", "oxygen_tables_import_csv", "tables", "Import inline CSV into a hosted Table through its durable ingestion run. Use staged upload for large files.", { csv_text: text(), csv_base64: text(), table: text(), table_name: text(), project: text(), filename: text(), upsert_key: text(), skip_blank_cells: skipBlankCells(), batch_size: int({ minimum: 1, maximum: 5000 }) }, [], cliOnly),
|
|
92
92
|
operation("tables.import.prepare", "/api/cli/tables/import-url", "POST", "tables import-upload", "oxygen_tables_import_upload", "tables", "Prepare a scoped storage upload for a hosted Table import; uploads and imports are separate, inspectable steps.", { file_name: text(), content_type: text(), byte_length: int({ minimum: 1 }), trace_id: text(), format: enumOf(["csv", "json", "xlsx"]) }, ["file_name", "byte_length"]),
|
|
93
93
|
operation("tables.import.preview", "/api/cli/tables/import-preview", "POST", "tables import-preview", "oxygen_tables_import_preview", "tables", "Preview a staged file's headers, rows and existing-table mapping; writes no imported rows.", { storage_key: text(), file_name: text(), format: enumOf(["csv", "json", "xlsx"]), byte_length: int({ minimum: 1 }), table: text(), upsert_key: text() }, ["storage_key", "byte_length"], { mutates: false }),
|
|
94
|
-
operation("tables.import.staged", "/api/cli/tables/import-staged", "POST", "tables import-staged", "oxygen_tables_import_staged", "tables", "
|
|
94
|
+
operation("tables.import.staged", "/api/cli/tables/import-staged", "POST", "tables import-staged", "oxygen_tables_import_staged", "tables", "Import a staged file into a table or a new create_name table.", { table: text(), create_name: text(), project: text(), storage_key: text(), storage_bucket: text(), storage_provider: enumOf(["s3"]), file_name: text(), format: enumOf(["csv", "json", "xlsx"]), sha256: text(), upsert_key: text(), skip_blank_cells: skipBlankCells(), source_key_map: json, ignore_source_keys: list(), byte_length: int({ minimum: 1 }), row_count: int({ minimum: 1 }), max_concurrency: int({ minimum: 1, maximum: 1000 }), source: enumOf(["cli", "mcp", "web"]), batch_size: int({ minimum: 1, maximum: 5000 }) }, ["storage_key", "byte_length"]),
|
|
95
95
|
operation("tables.import.file", "/api/cli/tables/import-file", "POST", "tables import-file", "oxygen_tables_import_file", "tables", "Import small base64 file bytes through the shared multipart import API; use staged upload for large files.", { content_base64: text(), file_name: text(), content_type: text(), table: text(), table_name: text(), project: text(), format: enumOf(["csv", "json", "xlsx"]), sheet: text(), upsert_key: text(), source_key_map: json, source_hash: text(), batch_size: int({ minimum: 1, maximum: 5000 }), max_concurrency: int({ minimum: 1 }) }, ["content_base64", "file_name"], { multipart: { contentField: "content_base64", nameField: "file_name", typeField: "content_type", fileField: "file" } }),
|
|
96
96
|
operation("tables.link.suggest", "/api/cli/tables/link/suggest", "POST", "tables link-suggest", "oxygen_tables_link_suggest", "tables", "Plan column links and matching row counts between Tables; creates no relation. Apply with tables link --to (MCP oxygen_tables_link_bulk).", TABLE_LINK_SUGGEST_CONTRACT.properties, ["table", "to"], { mutates: false }),
|
|
97
97
|
operation("tables.recover_pending", "/api/cli/tables/recover-pending", "POST", "tables recover-pending", "oxygen_tables_recover_pending", "tables", "Recover BetterContact email/phone requests and read verification blockers; no new orders or imports.", TABLE_RECOVER_PENDING_CONTRACT.properties, ["table"], { ...mcpOnly, annotations: { openWorldHint: true } }),
|