@oxygen-agent/cli 1.948.1 → 1.987.20

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.
Files changed (84) hide show
  1. package/README.md +1 -1
  2. package/dist/admin-primary-providers-render.js +9 -1
  3. package/dist/cli-values.d.ts +14 -0
  4. package/dist/cli-values.js +26 -0
  5. package/dist/command-manifest.js +6 -0
  6. package/dist/functions-commands.js +33 -9
  7. package/dist/help.d.ts +21 -0
  8. package/dist/help.js +94 -0
  9. package/dist/index.js +1493 -297
  10. package/dist/knowledge-repository-commands.d.ts +6 -0
  11. package/dist/knowledge-repository-commands.js +198 -0
  12. package/dist/skills.js +20 -0
  13. package/dist/ugc-commands.d.ts +3 -6
  14. package/dist/ugc-commands.js +2 -1086
  15. package/node_modules/@oxygen/cli-ugc/dist/commands.d.ts +3 -0
  16. package/node_modules/@oxygen/cli-ugc/dist/commands.js +1178 -0
  17. package/node_modules/@oxygen/cli-ugc/dist/field-parser.d.ts +7 -0
  18. package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +25 -0
  19. package/node_modules/@oxygen/cli-ugc/dist/index.d.ts +14 -0
  20. package/node_modules/@oxygen/cli-ugc/dist/index.js +5 -0
  21. package/node_modules/@oxygen/cli-ugc/package.json +15 -0
  22. package/node_modules/@oxygen/formula/dist/expression.js +14 -1
  23. package/node_modules/@oxygen/formula/dist/formula-functions.js +71 -1
  24. package/node_modules/@oxygen/formula/dist/index.d.ts +1 -0
  25. package/node_modules/@oxygen/formula/dist/index.js +1 -0
  26. package/node_modules/@oxygen/formula/dist/value-cleaners.d.ts +69 -0
  27. package/node_modules/@oxygen/formula/dist/value-cleaners.js +374 -0
  28. package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +2 -0
  29. package/node_modules/@oxygen/shared/dist/billing.d.ts +27 -27
  30. package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +8 -0
  31. package/node_modules/@oxygen/shared/dist/capability-discovery.js +127 -18
  32. package/node_modules/@oxygen/shared/dist/column-output-fields.js +12 -4
  33. package/node_modules/@oxygen/shared/dist/copilot-errors.js +3 -0
  34. package/node_modules/@oxygen/shared/dist/copilot-journeys.d.ts +19 -1
  35. package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.d.ts +19 -0
  36. package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.js +26 -0
  37. package/node_modules/@oxygen/shared/dist/copilot-journeys.js +8 -41
  38. package/node_modules/@oxygen/shared/dist/copilot-playbooks.d.ts +18 -0
  39. package/node_modules/@oxygen/shared/dist/copilot-playbooks.js +43 -0
  40. package/node_modules/@oxygen/shared/dist/copilot-skills.d.ts +15 -0
  41. package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +31 -0
  42. package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +41 -0
  43. package/node_modules/@oxygen/shared/dist/copilot-skills.js +6 -0
  44. package/node_modules/@oxygen/shared/dist/inbox-avatar-url.d.ts +28 -0
  45. package/node_modules/@oxygen/shared/dist/inbox-avatar-url.js +57 -0
  46. package/node_modules/@oxygen/shared/dist/index.d.ts +5 -0
  47. package/node_modules/@oxygen/shared/dist/index.js +5 -0
  48. package/node_modules/@oxygen/shared/dist/knowledge-bases.d.ts +74 -0
  49. package/node_modules/@oxygen/shared/dist/knowledge-bases.js +456 -0
  50. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +32 -38
  51. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +38 -41
  52. package/node_modules/@oxygen/shared/dist/knowledge-repository.d.ts +22 -0
  53. package/node_modules/@oxygen/shared/dist/knowledge-repository.js +121 -0
  54. package/node_modules/@oxygen/shared/dist/knowledge-vault-markdown.d.ts +20 -0
  55. package/node_modules/@oxygen/shared/dist/knowledge-vault-markdown.js +155 -0
  56. package/node_modules/@oxygen/shared/dist/langfuse.d.ts +12 -1
  57. package/node_modules/@oxygen/shared/dist/langfuse.js +48 -8
  58. package/node_modules/@oxygen/shared/dist/mailbox-import.d.ts +10 -0
  59. package/node_modules/@oxygen/shared/dist/mailbox-import.js +53 -0
  60. package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +8 -0
  61. package/node_modules/@oxygen/shared/dist/plan-limits.js +8 -0
  62. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
  63. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +1 -1
  64. package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +24 -0
  65. package/node_modules/@oxygen/shared/dist/product-analytics-events.js +24 -0
  66. package/node_modules/@oxygen/shared/dist/recipes.d.ts +6 -0
  67. package/node_modules/@oxygen/shared/dist/recipes.js +23 -0
  68. package/node_modules/@oxygen/shared/dist/research-output-contract.d.ts +33 -1
  69. package/node_modules/@oxygen/shared/dist/research-output-contract.js +64 -2
  70. package/node_modules/@oxygen/shared/dist/sequence-crm-events.d.ts +1 -1
  71. package/node_modules/@oxygen/shared/dist/sequence-hubspot-sync.d.ts +1 -1
  72. package/node_modules/@oxygen/shared/dist/sequences.d.ts +152 -2
  73. package/node_modules/@oxygen/shared/dist/sequences.js +304 -4
  74. package/node_modules/@oxygen/shared/dist/ugc-amplification-identity.d.ts +2 -0
  75. package/node_modules/@oxygen/shared/dist/ugc-amplification-identity.js +24 -0
  76. package/node_modules/@oxygen/shared/dist/ugc.d.ts +8 -0
  77. package/node_modules/@oxygen/shared/dist/user-capability-routing.js +8 -1
  78. package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
  79. package/node_modules/@oxygen/shared/dist/version.js +3 -1
  80. package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +6 -2
  81. package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +15 -4
  82. package/node_modules/@oxygen/shared/package.json +25 -0
  83. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +15 -15
  84. package/package.json +6 -2
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Deterministic value cleaners for dirty GTM strings — company names, job
3
+ * titles, delimited lists, and email-address classification.
4
+ *
5
+ * Pure string functions with no I/O, no tenant context, and no dependencies,
6
+ * so the exact semantics that clean a cell in the worker also run in the
7
+ * browser (formula editor previews). Every list below is a fixed, auditable
8
+ * data set: nothing here calls a provider, spends a credit, or guesses with a
9
+ * model. When a deterministic list is not enough, the paid verifier columns
10
+ * are the upgrade — these helpers are the free first pass.
11
+ */
12
+ // ---------------------------------------------------------------------------
13
+ // Shared text helpers
14
+ // ---------------------------------------------------------------------------
15
+ /** Zero-width joiners, variation selectors, and the BOM — invisible, never meaningful in a cell. */
16
+ const INVISIBLE_PATTERN = /[​-‍⁠︎️⃣]/g;
17
+ /**
18
+ * Emoji, pictographs, and symbol runs. Deliberately Unicode-property based:
19
+ * a byte-range or "non-ASCII" filter would eat accented letters, and GTM data
20
+ * is full of Müller, Ørsted, and Škoda.
21
+ */
22
+ const PICTOGRAPH_PATTERN = /[\p{Extended_Pictographic}\p{Emoji_Presentation}\p{Emoji_Modifier}\p{So}]/gu;
23
+ /** Leading/trailing punctuation, symbols, quotes, and whitespace. Inner punctuation is preserved. */
24
+ const OUTER_NOISE_PATTERN = /^[\s\p{P}\p{S}]+|[\s\p{P}\p{S}]+$/gu;
25
+ function toText(value) {
26
+ if (value === null || value === undefined)
27
+ return "";
28
+ if (typeof value === "string")
29
+ return value;
30
+ if (typeof value === "number" || typeof value === "boolean")
31
+ return String(value);
32
+ try {
33
+ return JSON.stringify(value) ?? "";
34
+ }
35
+ catch {
36
+ return "";
37
+ }
38
+ }
39
+ function collapseWhitespace(value) {
40
+ return value.replace(INVISIBLE_PATTERN, "").replace(/\s+/gu, " ").trim();
41
+ }
42
+ function trimOuterNoise(value) {
43
+ return value.replace(OUTER_NOISE_PATTERN, "");
44
+ }
45
+ /** True when every cased letter in the text is upper-case (digits/punctuation ignored). */
46
+ export function isAllCaps(value) {
47
+ let sawCasedLetter = false;
48
+ for (const char of value) {
49
+ if (!/\p{L}/u.test(char))
50
+ continue;
51
+ const lower = char.toLowerCase();
52
+ const upper = char.toUpperCase();
53
+ if (lower === upper)
54
+ continue; // caseless script (CJK, etc.) — not evidence either way
55
+ sawCasedLetter = true;
56
+ if (char !== upper)
57
+ return false;
58
+ }
59
+ return sawCasedLetter;
60
+ }
61
+ /** Capitalizes the first letter of every word and lowercases the rest (Unicode-aware). */
62
+ export function titleCaseWords(value) {
63
+ return value.replace(/(\p{L})(\p{L}*)/gu, (_match, first, rest) => first.toUpperCase() + rest.toLowerCase());
64
+ }
65
+ // ---------------------------------------------------------------------------
66
+ // Company names
67
+ // ---------------------------------------------------------------------------
68
+ /**
69
+ * Legal/corporate suffixes, stored as punctuation-free lower-case keys so
70
+ * "L.L.C.", "llc", and "LLC," all reduce to `llc`. Multi-word suffixes are
71
+ * joined ("pty ltd" → `ptyltd`) and matched against the trailing 1–3 tokens.
72
+ */
73
+ export const COMPANY_LEGAL_SUFFIX_KEYS = new Set([
74
+ "inc", "incorporated", "corp", "corporation", "co", "company",
75
+ "ltd", "limited", "llc", "llp", "lp", "plc",
76
+ "gmbh", "ag", "sa", "sas", "sarl", "srl", "spa",
77
+ "bv", "nv", "oy", "ab", "as", "pty", "kg", "kk", "pte",
78
+ "sdnbhd", "ptyltd", "pteltd",
79
+ "ltda", "ug", "ev", "ou", "sro", "spzoo", "zrt", "kft", "doo",
80
+ "aps", "hf", "ehf", "ltee",
81
+ ]);
82
+ /** The longest suffix key measured in whitespace-separated tokens ("sp. z o.o."). */
83
+ const MAX_SUFFIX_TOKENS = 3;
84
+ /** Separators after which a company string turns into a tagline, not a name. */
85
+ const SHORT_FORM_SEPARATORS = [" - ", " – ", " — ", " | ", ":", " / ", "("];
86
+ const SHORT_FORM_MAX_WORDS = 3;
87
+ function suffixKey(token) {
88
+ return token.toLowerCase().replace(/[^\p{L}\p{N}]/gu, "");
89
+ }
90
+ function stripLegalSuffixes(tokens) {
91
+ let remaining = [...tokens];
92
+ // Bounded: every pass removes at least one token, so the token count is the budget.
93
+ for (let pass = 0; pass < tokens.length; pass += 1) {
94
+ while (remaining.length > 1 && suffixKey(remaining[remaining.length - 1] ?? "") === "") {
95
+ remaining = remaining.slice(0, -1);
96
+ }
97
+ let matched = false;
98
+ for (let span = Math.min(MAX_SUFFIX_TOKENS, remaining.length - 1); span >= 1; span -= 1) {
99
+ const key = remaining.slice(remaining.length - span).map(suffixKey).join("");
100
+ if (!key || !COMPANY_LEGAL_SUFFIX_KEYS.has(key))
101
+ continue;
102
+ remaining = remaining.slice(0, remaining.length - span);
103
+ matched = true;
104
+ break;
105
+ }
106
+ if (!matched)
107
+ break;
108
+ }
109
+ return remaining;
110
+ }
111
+ function cutAtShortFormSeparator(value) {
112
+ let cut = value.length;
113
+ for (const separator of SHORT_FORM_SEPARATORS) {
114
+ const index = value.indexOf(separator);
115
+ if (index > 0 && index < cut)
116
+ cut = index;
117
+ }
118
+ return value.slice(0, cut);
119
+ }
120
+ /**
121
+ * Canonical company name: legal suffixes removed, stray punctuation and quotes
122
+ * trimmed, whitespace collapsed. Casing is the author's unless the whole name
123
+ * shouts ("ACME CORP." → "Acme"), because "hubspot" is how that company writes
124
+ * itself. `short` additionally cuts a tagline after a separator and keeps at
125
+ * most three words — the form that fits in an email's first sentence.
126
+ *
127
+ * Blank input returns null.
128
+ */
129
+ export function normalizeCompanyName(value, mode = "display") {
130
+ const raw = collapseWhitespace(toText(value).replace(PICTOGRAPH_PATTERN, ""));
131
+ if (!raw)
132
+ return null;
133
+ const base = mode === "short" ? collapseWhitespace(cutAtShortFormSeparator(raw)) : raw;
134
+ if (!base)
135
+ return null;
136
+ const tokens = stripLegalSuffixes(base.split(" ").filter(Boolean));
137
+ let cleaned = collapseWhitespace(trimOuterNoise(tokens.join(" ")));
138
+ if (!cleaned)
139
+ cleaned = collapseWhitespace(trimOuterNoise(base));
140
+ if (!cleaned)
141
+ return null;
142
+ if (mode === "short") {
143
+ const words = cleaned.split(" ").filter(Boolean);
144
+ if (words.length > SHORT_FORM_MAX_WORDS) {
145
+ cleaned = words.slice(0, SHORT_FORM_MAX_WORDS).join(" ");
146
+ }
147
+ }
148
+ return isAllCaps(cleaned) ? titleCaseWords(cleaned) : cleaned;
149
+ }
150
+ // ---------------------------------------------------------------------------
151
+ // Job titles
152
+ // ---------------------------------------------------------------------------
153
+ /**
154
+ * Recruiting and self-promotion phrases that ride along in a scraped headline.
155
+ * Longest first so "we're hiring" is consumed before the bare "hiring".
156
+ */
157
+ export const JOB_TITLE_SLOGANS = [
158
+ "we are hiring",
159
+ "we're hiring",
160
+ "join our team",
161
+ "open to work",
162
+ "let's connect",
163
+ "looking for",
164
+ "now hiring",
165
+ "opentowork",
166
+ "join us",
167
+ "dm me",
168
+ "hiring",
169
+ ];
170
+ const SLOGAN_PATTERNS = JOB_TITLE_SLOGANS
171
+ .slice()
172
+ .sort((a, b) => b.length - a.length)
173
+ .map((phrase) => {
174
+ const body = phrase
175
+ .split("")
176
+ .map((char) => {
177
+ if (char === " ")
178
+ return "\\s+";
179
+ if (char === "'")
180
+ return "['’‘ʼ´]";
181
+ return char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
182
+ })
183
+ .join("");
184
+ // No lookbehind: Safari shipped it late and this engine runs in the browser.
185
+ return new RegExp(`(^|[^\\p{L}\\p{N}])(?:${body})(?![\\p{L}\\p{N}])`, "giu");
186
+ });
187
+ /**
188
+ * Separators that join a real title to a second clause. A bare `-`, `,`, `/`,
189
+ * or `&` is NOT one of them: "Account Executive - EMEA", "Sr. Director, Demand
190
+ * Generation", and "Director of Importing/Exporting" are whole titles.
191
+ */
192
+ const JOB_TITLE_SEPARATOR_PATTERN = /\s*\|\s*|\s*[•·]\s*|\s*::\s*|\s+[—–]\s+/u;
193
+ /**
194
+ * Canonical job title: emoji, hashtags, parentheticals, and hiring slogans
195
+ * removed, then only the first clause of a separator-joined headline kept.
196
+ * All-caps titles are title-cased; every other casing is the author's.
197
+ *
198
+ * Blank input, or input that was nothing but noise, returns null.
199
+ */
200
+ export function cleanJobTitle(value) {
201
+ let text = toText(value);
202
+ if (!text.trim())
203
+ return null;
204
+ text = text.replace(PICTOGRAPH_PATTERN, "").replace(INVISIBLE_PATTERN, "");
205
+ // Parentheticals, innermost-out; bounded so a pathological paste cannot spin.
206
+ for (let pass = 0; pass < 4; pass += 1) {
207
+ const next = text.replace(/\([^()]*\)/g, " ").replace(/\[[^[\]]*\]/g, " ");
208
+ if (next === text)
209
+ break;
210
+ text = next;
211
+ }
212
+ text = text.replace(/#[\p{L}\p{N}_-]+/gu, " ");
213
+ for (const pattern of SLOGAN_PATTERNS)
214
+ text = text.replace(pattern, "$1");
215
+ text = collapseWhitespace(text);
216
+ const segments = text.split(JOB_TITLE_SEPARATOR_PATTERN).map((part) => (part ?? "").trim());
217
+ const chosen = segments.find((part) => /[\p{L}\p{N}]/u.test(part)) ?? segments[0] ?? "";
218
+ const cleaned = collapseWhitespace(trimOuterNoise(chosen));
219
+ if (!cleaned)
220
+ return null;
221
+ return isAllCaps(cleaned) ? titleCaseWords(cleaned) : cleaned;
222
+ }
223
+ // ---------------------------------------------------------------------------
224
+ // Delimited lists
225
+ // ---------------------------------------------------------------------------
226
+ /** Tried in order when no separator is given; the first one present wins. */
227
+ const LIST_DELIMITERS = ["\n", ";", "|", ","];
228
+ function cleanListParts(parts) {
229
+ const seen = new Set();
230
+ const out = [];
231
+ for (const part of parts) {
232
+ const text = collapseWhitespace(toText(part));
233
+ if (!text || seen.has(text))
234
+ continue;
235
+ seen.add(text);
236
+ out.push(text);
237
+ }
238
+ return out;
239
+ }
240
+ /**
241
+ * Turns one messy cell into a clean array: trimmed, blank-free, de-duplicated
242
+ * in first-seen order. An existing array or a JSON-encoded array is cleaned in
243
+ * place; a string is split on the given separator, or on the first delimiter
244
+ * actually present (newline, `;`, `|`, `,`) when none is given.
245
+ *
246
+ * Blank input returns an empty array.
247
+ */
248
+ export function splitDelimitedList(value, separator) {
249
+ if (Array.isArray(value))
250
+ return cleanListParts(value);
251
+ if (value === null || value === undefined)
252
+ return [];
253
+ const text = typeof value === "string" ? value : toText(value);
254
+ const trimmed = text.trim();
255
+ if (!trimmed)
256
+ return [];
257
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
258
+ try {
259
+ const parsed = JSON.parse(trimmed);
260
+ if (Array.isArray(parsed))
261
+ return cleanListParts(parsed);
262
+ }
263
+ catch {
264
+ // Not JSON after all — fall through to delimiter splitting.
265
+ }
266
+ }
267
+ const explicit = typeof separator === "string" ? separator : "";
268
+ if (explicit)
269
+ return cleanListParts(trimmed.split(explicit));
270
+ if (/\r?\n/.test(trimmed))
271
+ return cleanListParts(trimmed.split(/\r?\n/));
272
+ for (const delimiter of LIST_DELIMITERS) {
273
+ if (delimiter !== "\n" && trimmed.includes(delimiter)) {
274
+ return cleanListParts(trimmed.split(delimiter));
275
+ }
276
+ }
277
+ return cleanListParts([trimmed]);
278
+ }
279
+ // ---------------------------------------------------------------------------
280
+ // Email classification
281
+ // ---------------------------------------------------------------------------
282
+ /** Consumer mailbox providers — a person, not a company domain. */
283
+ export const FREE_MAIL_DOMAINS = new Set([
284
+ "gmail.com", "googlemail.com",
285
+ "yahoo.com", "yahoo.co.uk", "yahoo.fr", "yahoo.de", "yahoo.co.jp", "yahoo.ca",
286
+ "yahoo.com.au", "yahoo.es", "yahoo.it", "ymail.com", "rocketmail.com",
287
+ "hotmail.com", "hotmail.co.uk", "hotmail.fr", "hotmail.de", "hotmail.it", "hotmail.es",
288
+ "outlook.com", "outlook.fr", "outlook.de", "outlook.es", "outlook.co.uk",
289
+ "live.com", "live.co.uk", "live.fr", "live.de", "msn.com",
290
+ "aol.com", "icloud.com", "me.com", "mac.com",
291
+ "protonmail.com", "proton.me", "pm.me",
292
+ "gmx.com", "gmx.de", "gmx.net", "gmx.at", "gmx.ch",
293
+ "web.de", "t-online.de", "freenet.de",
294
+ "mail.com", "mail.ru", "yandex.ru", "yandex.com",
295
+ "zoho.com", "fastmail.com", "hey.com", "tutanota.com", "tuta.io",
296
+ "orange.fr", "wanadoo.fr", "free.fr", "laposte.net", "sfr.fr",
297
+ "libero.it", "virgilio.it", "seznam.cz",
298
+ "wp.pl", "o2.pl", "onet.pl", "interia.pl",
299
+ "qq.com", "163.com", "126.com", "sina.com",
300
+ "naver.com", "daum.net", "hanmail.net", "rediffmail.com",
301
+ "bigpond.com", "optusnet.com.au", "telus.net", "shaw.ca", "sympatico.ca", "rogers.com",
302
+ "btinternet.com", "sky.com", "talktalk.net", "virginmedia.com", "blueyonder.co.uk",
303
+ "comcast.net", "verizon.net", "att.net", "sbcglobal.net", "cox.net",
304
+ "charter.net", "earthlink.net", "bellsouth.net",
305
+ "ziggo.nl", "kpnmail.nl", "home.nl", "telenet.be", "skynet.be", "bluewin.ch",
306
+ "uol.com.br", "bol.com.br", "terra.com.br",
307
+ ]);
308
+ /** Throwaway inbox providers — never worth a send, never worth a credit. */
309
+ export const DISPOSABLE_EMAIL_DOMAINS = new Set([
310
+ "mailinator.com", "guerrillamail.com", "guerrillamail.net",
311
+ "10minutemail.com", "10minutemail.net",
312
+ "tempmail.com", "temp-mail.org", "temp-mail.io", "tempr.email",
313
+ "yopmail.com", "yopmail.fr", "trashmail.com", "trashmail.me",
314
+ "getnada.com", "dispostable.com", "maildrop.cc", "sharklasers.com",
315
+ "throwawaymail.com", "fakeinbox.com", "mohmal.com", "mailnesia.com",
316
+ "spamgourmet.com", "discard.email", "mintemail.com", "emailondeck.com",
317
+ "burnermail.io", "tempail.com", "mytemp.email", "moakt.com",
318
+ "tmpmail.org", "tmpmail.net", "minutemail.com", "mailcatch.com",
319
+ "spam4.me", "harakirimail.com", "jetable.org", "mailexpire.com",
320
+ "throwam.com", "tempinbox.com", "inboxkitten.com",
321
+ ]);
322
+ /** Shared-inbox local parts — a department, not a person. */
323
+ export const ROLE_EMAIL_LOCAL_PARTS = new Set([
324
+ "info", "sales", "support", "help", "admin", "administrator",
325
+ "contact", "hello", "hi", "team", "office",
326
+ "billing", "invoices", "accounts", "accounting", "finance",
327
+ "marketing", "hr", "jobs", "careers", "recruiting",
328
+ "press", "media", "pr", "legal", "privacy", "security", "abuse",
329
+ "postmaster", "webmaster", "hostmaster",
330
+ "noreply", "no-reply", "donotreply", "do-not-reply",
331
+ "newsletter", "notifications", "notification",
332
+ "mail", "mailer-daemon", "service", "services",
333
+ "customerservice", "customer-service",
334
+ "enquiries", "inquiries", "enquiry", "inquiry",
335
+ "orders", "order", "partners", "partnerships",
336
+ "feedback", "welcome", "community",
337
+ "dev", "devops", "it", "ops", "engineering",
338
+ "alerts", "subscriptions",
339
+ ]);
340
+ const EMAIL_SYNTAX_PATTERN = /^[^\s@,;:<>"'()[\]\\]+@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/;
341
+ /** A domain matches a list when it IS a listed domain or a subdomain of one (mail.yahoo.com). */
342
+ function matchesDomainList(domain, list) {
343
+ if (list.has(domain))
344
+ return true;
345
+ const labels = domain.split(".");
346
+ for (let index = 1; index < labels.length - 1; index += 1) {
347
+ if (list.has(labels.slice(index).join(".")))
348
+ return true;
349
+ }
350
+ return false;
351
+ }
352
+ /**
353
+ * Classifies an email address from OXYGEN's built-in lists, in precedence
354
+ * order: a throwaway domain wins, then a shared-inbox local part, then a
355
+ * consumer mailbox provider, and anything left is a company address.
356
+ *
357
+ * Blank or syntactically invalid input returns null.
358
+ */
359
+ export function classifyEmailType(value) {
360
+ const email = toText(value).trim().toLowerCase();
361
+ if (!email || !EMAIL_SYNTAX_PATTERN.test(email))
362
+ return null;
363
+ const at = email.lastIndexOf("@");
364
+ const localPart = email.slice(0, at);
365
+ const domain = email.slice(at + 1);
366
+ if (matchesDomainList(domain, DISPOSABLE_EMAIL_DOMAINS))
367
+ return "disposable";
368
+ const untagged = localPart.split("+")[0] ?? localPart;
369
+ if (ROLE_EMAIL_LOCAL_PARTS.has(untagged) || ROLE_EMAIL_LOCAL_PARTS.has(localPart))
370
+ return "role";
371
+ if (matchesDomainList(domain, FREE_MAIL_DOMAINS))
372
+ return "personal";
373
+ return "work";
374
+ }
@@ -7,6 +7,8 @@ export type RecipeToolRunOptions = {
7
7
  optional?: boolean;
8
8
  /** Pin this call to one provider account for every replay/scheduled run. */
9
9
  connectionId?: string;
10
+ /** Pin the credential rail instead of automatically selecting a connected key. */
11
+ credentialMode?: "managed" | "user_api_key" | "user_oauth";
10
12
  };
11
13
  export type RecipeToolApi = {
12
14
  run: <T = unknown>(toolId: string, payload?: Record<string, unknown>, options?: RecipeToolRunOptions) => Promise<T>;
@@ -172,36 +172,43 @@ export declare const WALKTHROUGH_COMPLETION_BONUS_CREDITS = 1000;
172
172
  export declare const WALKTHROUGH_SCRAPE_MAX_CREDITS = 2000;
173
173
  export declare const BASE_PRICING_PLANS: {
174
174
  readonly free: {
175
+ readonly monthlyPriceCents: number | null;
176
+ readonly monthlyCredits: number | null;
177
+ readonly weeklyCreditsLimit: number | null;
178
+ readonly rolloverCap: number | null;
179
+ readonly byokEnabled: boolean;
180
+ readonly tier: "free";
181
+ readonly name: "Free";
175
182
  readonly monthlyAutomationActions: null;
176
183
  readonly automationOverageCentsPerMillion: null;
177
184
  readonly automationOverageEnabledDefault: false;
178
185
  readonly description: "Your CRM, tables, and workflows — free forever. Credits pay for enrichment and AI; connecting a sender, publishing, managed email infrastructure, and your own provider keys need a plan.";
179
186
  readonly ctaLabel: "Upgrade";
180
187
  readonly features: readonly ["CRM, Tables, Workflows, and Knowledge — unmetered", "Sequences: author, preview, and enroll", "Agents and Workspace Copilot", "Credits for enrichment and AI, topped up any time"];
188
+ };
189
+ readonly starter: {
181
190
  readonly monthlyPriceCents: number | null;
182
191
  readonly monthlyCredits: number | null;
183
192
  readonly weeklyCreditsLimit: number | null;
184
193
  readonly rolloverCap: number | null;
185
194
  readonly byokEnabled: boolean;
186
- readonly tier: "free";
187
- readonly name: "Free";
188
- };
189
- readonly starter: {
195
+ readonly tier: "starter";
196
+ readonly name: "Starter";
190
197
  readonly monthlyAutomationActions: null;
191
198
  readonly automationOverageCentsPerMillion: null;
192
199
  readonly automationOverageEnabledDefault: false;
193
200
  readonly description: "For founders running focused managed enrichment and AI columns.";
194
201
  readonly ctaLabel: "Choose Starter";
195
202
  readonly features: readonly ["99,000 credits every month", "All integrations", "Workflows", "Bring your own keys", "Standard support"];
203
+ };
204
+ readonly pro: {
196
205
  readonly monthlyPriceCents: number | null;
197
206
  readonly monthlyCredits: number | null;
198
207
  readonly weeklyCreditsLimit: number | null;
199
208
  readonly rolloverCap: number | null;
200
209
  readonly byokEnabled: boolean;
201
- readonly tier: "starter";
202
- readonly name: "Starter";
203
- };
204
- readonly pro: {
210
+ readonly tier: "pro";
211
+ readonly name: "Pro";
205
212
  readonly monthlyAutomationActions: null;
206
213
  readonly automationOverageCentsPerMillion: null;
207
214
  readonly automationOverageEnabledDefault: false;
@@ -210,30 +217,30 @@ export declare const BASE_PRICING_PLANS: {
210
217
  readonly highlighted: true;
211
218
  readonly ctaLabel: "Choose Pro";
212
219
  readonly features: readonly ["249,000 credits every month", "All integrations", "Workflows", "Bring your own keys", "Standard support"];
220
+ };
221
+ readonly team: {
213
222
  readonly monthlyPriceCents: number | null;
214
223
  readonly monthlyCredits: number | null;
215
224
  readonly weeklyCreditsLimit: number | null;
216
225
  readonly rolloverCap: number | null;
217
226
  readonly byokEnabled: boolean;
218
- readonly tier: "pro";
219
- readonly name: "Pro";
220
- };
221
- readonly team: {
227
+ readonly tier: "team";
228
+ readonly name: "Team";
222
229
  readonly monthlyAutomationActions: null;
223
230
  readonly automationOverageCentsPerMillion: null;
224
231
  readonly automationOverageEnabledDefault: false;
225
232
  readonly description: "For small GTM teams and agencies running shared workflows with their own AI keys.";
226
233
  readonly ctaLabel: "Choose Team";
227
234
  readonly features: readonly ["749,000 credits every month", "All integrations", "Workflows", "Bring your own keys", "Premium support"];
235
+ };
236
+ readonly scale: {
228
237
  readonly monthlyPriceCents: number | null;
229
238
  readonly monthlyCredits: number | null;
230
239
  readonly weeklyCreditsLimit: number | null;
231
240
  readonly rolloverCap: number | null;
232
241
  readonly byokEnabled: boolean;
233
- readonly tier: "team";
234
- readonly name: "Team";
235
- };
236
- readonly scale: {
242
+ readonly tier: "scale";
243
+ readonly name: "Scale";
237
244
  readonly monthlyAutomationActions: null;
238
245
  readonly automationOverageCentsPerMillion: null;
239
246
  readonly automationOverageEnabledDefault: false;
@@ -241,15 +248,15 @@ export declare const BASE_PRICING_PLANS: {
241
248
  readonly ctaLabel: "Contact sales";
242
249
  readonly contactHref: "mailto:priority@oxygen-agent.com";
243
250
  readonly features: readonly ["10,000,000+ managed credits / month", "Workflows", "Phone number enrichment on managed credits", "Custom provider routing and BYOK policies", "Dedicated onboarding"];
251
+ };
252
+ readonly enterprise: {
244
253
  readonly monthlyPriceCents: number | null;
245
254
  readonly monthlyCredits: number | null;
246
255
  readonly weeklyCreditsLimit: number | null;
247
256
  readonly rolloverCap: number | null;
248
257
  readonly byokEnabled: boolean;
249
- readonly tier: "scale";
250
- readonly name: "Scale";
251
- };
252
- readonly enterprise: {
258
+ readonly tier: "enterprise";
259
+ readonly name: "Enterprise";
253
260
  readonly monthlyAutomationActions: null;
254
261
  readonly automationOverageCentsPerMillion: null;
255
262
  readonly automationOverageEnabledDefault: false;
@@ -257,13 +264,6 @@ export declare const BASE_PRICING_PLANS: {
257
264
  readonly ctaLabel: "Talk to us";
258
265
  readonly contactHref: "mailto:priority@oxygen-agent.com";
259
266
  readonly features: readonly ["Everything in Team", "Custom usage limits", "Volume pricing", "Dedicated support"];
260
- readonly monthlyPriceCents: number | null;
261
- readonly monthlyCredits: number | null;
262
- readonly weeklyCreditsLimit: number | null;
263
- readonly rolloverCap: number | null;
264
- readonly byokEnabled: boolean;
265
- readonly tier: "enterprise";
266
- readonly name: "Enterprise";
267
267
  };
268
268
  };
269
269
  export declare const PUBLIC_PLAN_ORDER: readonly PlanTier[];
@@ -23,6 +23,12 @@ export type CapabilityRouteMatch = {
23
23
  card: CapabilityRouteCard;
24
24
  recommendedTools: string[];
25
25
  recommendedCommands: string[];
26
+ /**
27
+ * The one exact invocation for asks whose answer is a single command with a
28
+ * specific flag the help text alone did not teach. Present only when the
29
+ * intent is that narrow; `recommendedCommands[0]` is the same command.
30
+ */
31
+ exactCommand?: string;
26
32
  };
27
33
  export type PrimitiveRouteMatch = CapabilityRouteMatch & {
28
34
  card: PrimitiveRouteCard;
@@ -34,3 +40,5 @@ export declare function getCapabilityRouteMatch(id: string): CapabilityRouteMatc
34
40
  export declare function inferCapabilityRoute(query: string): CapabilityRouteMatch | null;
35
41
  export declare function inferPrimitiveRoute(query: string): PrimitiveRouteMatch | null;
36
42
  export declare function serializeCapabilityRoute(route: CapabilityRouteMatch | null): Record<string, unknown> | null;
43
+ /** The exact `companies search plan` invocation for the url_recovery intent. */
44
+ export declare const COMPANY_URL_RECOVERY_COMMAND = "oxygen companies search plan --prompt \"Find the websites for these companies: <company names>\" --source-intent url_recovery";