@oxygen-agent/cli 1.982.3 → 1.1003.12
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-primary-providers-render.d.ts +0 -2
- package/dist/admin-primary-providers-render.js +1 -1
- package/dist/browser-login.js +1 -4
- package/dist/command-manifest.d.ts +3 -2
- package/dist/command-manifest.js +10 -0
- package/dist/credentials.d.ts +1 -1
- package/dist/functions-commands.js +27 -7
- package/dist/help.d.ts +29 -0
- package/dist/help.js +139 -0
- package/dist/index.js +1875 -164
- package/dist/knowledge-mirror.d.ts +2 -2
- package/dist/runtime.d.ts +0 -15
- package/dist/runtime.js +1 -1
- package/dist/session.d.ts +4 -3
- package/dist/skills.d.ts +8 -7
- package/dist/skills.js +24 -10
- package/dist/transcript.d.ts +2 -1
- package/dist/ugc-commands.d.ts +3 -6
- package/dist/ugc-commands.js +2 -1200
- package/dist/util.d.ts +1 -1
- package/dist/util.js +1 -3
- package/node_modules/@oxygen/cli-ugc/dist/commands.d.ts +3 -0
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +1178 -0
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.d.ts +7 -0
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +25 -0
- package/node_modules/@oxygen/cli-ugc/dist/index.d.ts +14 -0
- package/node_modules/@oxygen/cli-ugc/dist/index.js +5 -0
- package/node_modules/@oxygen/cli-ugc/package.json +15 -0
- package/node_modules/@oxygen/formula/dist/coerce.d.ts +10 -0
- package/node_modules/@oxygen/formula/dist/coerce.js +10 -0
- package/node_modules/@oxygen/formula/dist/expression.js +14 -1
- package/node_modules/@oxygen/formula/dist/formula-functions.js +136 -1
- package/node_modules/@oxygen/formula/dist/hash.d.ts +19 -0
- package/node_modules/@oxygen/formula/dist/hash.js +199 -0
- package/node_modules/@oxygen/formula/dist/index.d.ts +1 -0
- package/node_modules/@oxygen/formula/dist/index.js +1 -0
- package/node_modules/@oxygen/formula/dist/value-cleaners.d.ts +74 -0
- package/node_modules/@oxygen/formula/dist/value-cleaners.js +358 -0
- package/node_modules/@oxygen/shared/dist/array-utils.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/array-utils.js +11 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +103 -47
- package/node_modules/@oxygen/shared/dist/billing.js +150 -40
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +17 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +114 -16
- package/node_modules/@oxygen/shared/dist/column-autofill.d.ts +52 -0
- package/node_modules/@oxygen/shared/dist/column-autofill.js +80 -0
- package/node_modules/@oxygen/shared/dist/column-output-fields.js +14 -10
- package/node_modules/@oxygen/shared/dist/company-enrichment-fields.d.ts +108 -0
- package/node_modules/@oxygen/shared/dist/company-enrichment-fields.js +545 -0
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.js +43 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +31 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +41 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.js +6 -0
- package/node_modules/@oxygen/shared/dist/deploy-env.d.ts +74 -0
- package/node_modules/@oxygen/shared/dist/deploy-env.js +82 -0
- package/node_modules/@oxygen/shared/dist/dnc-rules.d.ts +130 -0
- package/node_modules/@oxygen/shared/dist/dnc-rules.js +221 -0
- package/node_modules/@oxygen/shared/dist/enrichment-intents.d.ts +103 -0
- package/node_modules/@oxygen/shared/dist/enrichment-intents.js +819 -0
- package/node_modules/@oxygen/shared/dist/error-message.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/error-message.js +3 -0
- package/node_modules/@oxygen/shared/dist/error-redaction.js +1 -3
- package/node_modules/@oxygen/shared/dist/external-write-policy.d.ts +33 -0
- package/node_modules/@oxygen/shared/dist/external-write-policy.js +68 -0
- package/node_modules/@oxygen/shared/dist/format-percent.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/format-percent.js +13 -0
- package/node_modules/@oxygen/shared/dist/freemail-domains.d.ts +81 -0
- package/node_modules/@oxygen/shared/dist/freemail-domains.js +157 -0
- package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.js +1 -1
- package/node_modules/@oxygen/shared/dist/index.d.ts +14 -0
- package/node_modules/@oxygen/shared/dist/index.js +14 -0
- package/node_modules/@oxygen/shared/dist/json-path.js +1 -3
- package/node_modules/@oxygen/shared/dist/knowledge-bases.js +1 -3
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +17 -2
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +28 -6
- package/node_modules/@oxygen/shared/dist/langfuse.d.ts +12 -1
- package/node_modules/@oxygen/shared/dist/langfuse.js +57 -8
- package/node_modules/@oxygen/shared/dist/linkedin-countries.d.ts +32 -0
- package/node_modules/@oxygen/shared/dist/linkedin-countries.js +359 -0
- package/node_modules/@oxygen/shared/dist/log-sink-selector.d.ts +39 -0
- package/node_modules/@oxygen/shared/dist/log-sink-selector.js +56 -0
- package/node_modules/@oxygen/shared/dist/log.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/log.js +6 -1
- package/node_modules/@oxygen/shared/dist/object-storage.d.ts +17 -0
- package/node_modules/@oxygen/shared/dist/object-storage.js +21 -0
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.d.ts +54 -0
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +213 -0
- package/node_modules/@oxygen/shared/dist/plan-capabilities.js +1 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +23 -22
- package/node_modules/@oxygen/shared/dist/plan-limits.js +45 -18
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +48 -41
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +36 -25
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +22 -22
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +40 -34
- package/node_modules/@oxygen/shared/dist/product-analytics-environment.js +9 -0
- package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/product-analytics-events.js +15 -0
- package/node_modules/@oxygen/shared/dist/rate-window.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/rate-window.js +8 -0
- package/node_modules/@oxygen/shared/dist/research-output-contract.d.ts +33 -1
- package/node_modules/@oxygen/shared/dist/research-output-contract.js +65 -5
- package/node_modules/@oxygen/shared/dist/search-vocab.js +4 -5
- package/node_modules/@oxygen/shared/dist/select-options.js +6 -1
- package/node_modules/@oxygen/shared/dist/sequence-crm-events.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/sequence-failures.js +1 -5
- package/node_modules/@oxygen/shared/dist/sequence-hubspot-sync.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +49 -0
- package/node_modules/@oxygen/shared/dist/sequences.js +134 -4
- package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +22 -10
- package/node_modules/@oxygen/shared/dist/spend-safety.js +15 -21
- package/node_modules/@oxygen/shared/dist/sql-rows.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/sql-rows.js +3 -0
- package/node_modules/@oxygen/shared/dist/telemetry.js +9 -1
- package/node_modules/@oxygen/shared/dist/type-guards.d.ts +22 -0
- package/node_modules/@oxygen/shared/dist/type-guards.js +35 -0
- package/node_modules/@oxygen/shared/dist/value-readers.d.ts +21 -0
- package/node_modules/@oxygen/shared/dist/value-readers.js +59 -0
- package/node_modules/@oxygen/shared/dist/version.js +1 -1
- package/node_modules/@oxygen/shared/package.json +60 -0
- package/node_modules/@oxygen/workflows/dist/graph/expression.js +2 -5
- package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +15 -15
- package/node_modules/@oxygen/workflows/dist/graph/params.js +1 -1
- package/package.json +6 -3
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function parseField(value, field) {
|
|
2
|
+
if (field.kind === "array" || field.kind === "object") {
|
|
3
|
+
const parsed = JSON.parse(String(value));
|
|
4
|
+
if (field.kind === "array"
|
|
5
|
+
? !Array.isArray(parsed) ||
|
|
6
|
+
!parsed.every((item) => typeof item === "string")
|
|
7
|
+
: !parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
8
|
+
throw new Error(`${field.name} must be a JSON ${field.kind === "array" ? "array of strings" : "object"}.`);
|
|
9
|
+
return parsed;
|
|
10
|
+
}
|
|
11
|
+
if (field.kind === "number") {
|
|
12
|
+
const parsed = Number(value);
|
|
13
|
+
if (!Number.isFinite(parsed) || parsed < 0)
|
|
14
|
+
throw new Error(`${field.name} must be a nonnegative finite number.`);
|
|
15
|
+
return parsed;
|
|
16
|
+
}
|
|
17
|
+
if (field.kind === "boolean") {
|
|
18
|
+
if (value === true || value === "true")
|
|
19
|
+
return true;
|
|
20
|
+
if (value === false || value === "false")
|
|
21
|
+
return false;
|
|
22
|
+
throw new Error(`${field.name} must be true or false.`);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
/** The CLI owns authentication, transport, output and error presentation. */
|
|
3
|
+
export interface UgcCommandDependencies {
|
|
4
|
+
request: (path: string, options?: {
|
|
5
|
+
method: "POST";
|
|
6
|
+
body: Record<string, unknown>;
|
|
7
|
+
idempotencyKey?: string;
|
|
8
|
+
}) => Promise<unknown>;
|
|
9
|
+
handle: (command: string, options: {
|
|
10
|
+
json?: boolean;
|
|
11
|
+
}, action: () => Promise<unknown>) => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
/** Register the existing UGC command tree without performing requests. */
|
|
14
|
+
export declare function registerUgcCommands(program: Command, dependencies: UgcCommandDependencies): void;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oxygen/cli-ugc",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {}
|
|
15
|
+
}
|
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
* imported from a host package) so the engine stays dependency-free and
|
|
4
4
|
* browser-safe; tenant-db re-exports it from its own `coerce.ts` so the
|
|
5
5
|
* existing internal import path keeps working.
|
|
6
|
+
*
|
|
7
|
+
* Do NOT replace this with `export { isRecord } from "@oxygen/shared"`. This
|
|
8
|
+
* file is value-re-exported by ./index.ts, so that edge drags the whole
|
|
9
|
+
* @oxygen/shared root barrel into every browser bundle that touches the formula
|
|
10
|
+
* engine — packages/workflows/src/graph/browser-safety.test.ts fails on exactly
|
|
11
|
+
* that ("packages/formula/src/coerce.ts imports @oxygen/shared"). @oxygen/shared
|
|
12
|
+
* does export a `./type-guards` leaf, but importing it would still make this
|
|
13
|
+
* browser-safe package depend on @oxygen/shared, which the same guard forbids
|
|
14
|
+
* (packages/formula declares no @oxygen/* dependency at all); the duplicate body
|
|
15
|
+
* is deliberate.
|
|
6
16
|
*/
|
|
7
17
|
/** Loose record check: any non-array object (class instances included). */
|
|
8
18
|
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
* imported from a host package) so the engine stays dependency-free and
|
|
4
4
|
* browser-safe; tenant-db re-exports it from its own `coerce.ts` so the
|
|
5
5
|
* existing internal import path keeps working.
|
|
6
|
+
*
|
|
7
|
+
* Do NOT replace this with `export { isRecord } from "@oxygen/shared"`. This
|
|
8
|
+
* file is value-re-exported by ./index.ts, so that edge drags the whole
|
|
9
|
+
* @oxygen/shared root barrel into every browser bundle that touches the formula
|
|
10
|
+
* engine — packages/workflows/src/graph/browser-safety.test.ts fails on exactly
|
|
11
|
+
* that ("packages/formula/src/coerce.ts imports @oxygen/shared"). @oxygen/shared
|
|
12
|
+
* does export a `./type-guards` leaf, but importing it would still make this
|
|
13
|
+
* browser-safe package depend on @oxygen/shared, which the same guard forbids
|
|
14
|
+
* (packages/formula declares no @oxygen/* dependency at all); the duplicate body
|
|
15
|
+
* is deliberate.
|
|
6
16
|
*/
|
|
7
17
|
/** Loose record check: any non-array object (class instances included). */
|
|
8
18
|
export function isRecord(value) {
|
|
@@ -435,6 +435,13 @@ function readStringLiteral(expression, start, quote) {
|
|
|
435
435
|
expression,
|
|
436
436
|
});
|
|
437
437
|
}
|
|
438
|
+
/**
|
|
439
|
+
* Only the escapes the language actually defines are decoded. Everything else
|
|
440
|
+
* is preserved verbatim, backslash included, because a formula string literal
|
|
441
|
+
* is the only way to write a regex pattern: collapsing an unknown escape to the
|
|
442
|
+
* bare character silently turned `"\\s+"` into `s+`, so `regex_replace(x, "\\s+", "_")`
|
|
443
|
+
* matched a literal "s" and the pattern looked broken with nothing to debug.
|
|
444
|
+
*/
|
|
438
445
|
function decodeEscapedCharacter(char) {
|
|
439
446
|
switch (char) {
|
|
440
447
|
case "n":
|
|
@@ -443,8 +450,14 @@ function decodeEscapedCharacter(char) {
|
|
|
443
450
|
return "\r";
|
|
444
451
|
case "t":
|
|
445
452
|
return "\t";
|
|
453
|
+
case "\\":
|
|
454
|
+
return "\\";
|
|
455
|
+
case "\"":
|
|
456
|
+
return "\"";
|
|
457
|
+
case "'":
|
|
458
|
+
return "'";
|
|
446
459
|
default:
|
|
447
|
-
return char
|
|
460
|
+
return `\\${char}`;
|
|
448
461
|
}
|
|
449
462
|
}
|
|
450
463
|
function readNumberLiteral(expression, start) {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
2
2
|
import { walkJsonPath } from "@oxygen/shared/json-path";
|
|
3
|
+
import { resolveLinkedInCountry } from "@oxygen/shared/linkedin-countries";
|
|
4
|
+
import { md5Hex, sha256Hex } from "./hash.js";
|
|
3
5
|
import { normalizeDomain, normalizeEmail, normalizeLinkedinUrl, } from "./value-normalizers.js";
|
|
6
|
+
import { classifyEmailType, cleanJobTitle, normalizeCompanyName, splitDelimitedList, } from "./value-cleaners.js";
|
|
4
7
|
// Pattern LENGTH is not a safety bound and never was -- catastrophic backtracking
|
|
5
8
|
// is a function of pattern SHAPE, and `(a+)+$` hangs at 6 characters, so 256 already
|
|
6
9
|
// permitted an unbounded hang while rejecting harmless long literals. The real bound
|
|
@@ -129,7 +132,8 @@ function compileFormulaRegex(pattern, functionName, flags) {
|
|
|
129
132
|
return new RegExp(pattern, flags);
|
|
130
133
|
}
|
|
131
134
|
catch (error) {
|
|
132
|
-
throw formulaExpressionError("Invalid regular-expression pattern."
|
|
135
|
+
throw formulaExpressionError("Invalid regular-expression pattern. Patterns use JavaScript RegExp syntax without flags:"
|
|
136
|
+
+ " inline modifiers such as (?i) are not supported — apply lower() to the input or spell out both cases.", {
|
|
133
137
|
function: functionName,
|
|
134
138
|
pattern,
|
|
135
139
|
parse_error: error instanceof Error ? error.message : String(error),
|
|
@@ -576,6 +580,56 @@ const SPECS = [
|
|
|
576
580
|
examples: [{ expression: "title_case(\"acme CORP\")", result: "Acme Corp" }],
|
|
577
581
|
evaluate: (args) => asText(args[0]).replace(/([A-Za-z])([A-Za-z]*)/g, (_match, first, rest) => first.toUpperCase() + rest.toLowerCase()),
|
|
578
582
|
}),
|
|
583
|
+
spec({
|
|
584
|
+
name: "sha256",
|
|
585
|
+
category: "string",
|
|
586
|
+
signature: "sha256(text)",
|
|
587
|
+
description: "Hex-encoded SHA-256 digest of the text (UTF-8). Computed in-process, so it costs no credits. Blank input returns null.",
|
|
588
|
+
minArgs: 1,
|
|
589
|
+
maxArgs: 1,
|
|
590
|
+
examples: [{ expression: "sha256(\"abc\")", result: "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" }],
|
|
591
|
+
evaluate: (args) => blankToNull(args[0], (input) => sha256Hex(asText(input))),
|
|
592
|
+
}),
|
|
593
|
+
spec({
|
|
594
|
+
name: "md5",
|
|
595
|
+
category: "string",
|
|
596
|
+
signature: "md5(text)",
|
|
597
|
+
description: "Hex-encoded MD5 digest of the text (UTF-8). Computed in-process, so it costs no credits. Blank input returns null. MD5 exists here for ad-platform audience matching, never for anything security-bearing.",
|
|
598
|
+
minArgs: 1,
|
|
599
|
+
maxArgs: 1,
|
|
600
|
+
examples: [{ expression: "md5(\"abc\")", result: "900150983cd24fb0d6963f7d28e17f72" }],
|
|
601
|
+
evaluate: (args) => blankToNull(args[0], (input) => md5Hex(asText(input))),
|
|
602
|
+
}),
|
|
603
|
+
spec({
|
|
604
|
+
name: "email_hash",
|
|
605
|
+
category: "string",
|
|
606
|
+
signature: "email_hash(email, algorithm?)",
|
|
607
|
+
description: "Normalizes an email address and returns its hex digest — the hashed identifier ad platforms take for custom audiences. Algorithm is \"sha256\" (default) or \"md5\"; blank input returns null.",
|
|
608
|
+
minArgs: 1,
|
|
609
|
+
maxArgs: 2,
|
|
610
|
+
examples: [{ expression: "email_hash(\" Ada@Example.COM \")", result: "b5fc85e55755f9e0d030a10ab4429b6b2944855f9a0d60077fe832becbc41d72" }],
|
|
611
|
+
evaluate: (args) => {
|
|
612
|
+
// The algorithm is checked before the blank check on purpose: an unknown
|
|
613
|
+
// algorithm is an authoring mistake, not row data, so it must fail on
|
|
614
|
+
// every row rather than only on the rows that carry an address.
|
|
615
|
+
const algorithm = args.length > 1 && !isBlankFormulaValue(args[1])
|
|
616
|
+
? asText(args[1]).trim().toLowerCase()
|
|
617
|
+
: "sha256";
|
|
618
|
+
if (algorithm !== "sha256" && algorithm !== "md5") {
|
|
619
|
+
throw formulaExpressionError("email_hash supports the \"sha256\" and \"md5\" algorithms only.", {
|
|
620
|
+
function: "email_hash",
|
|
621
|
+
algorithm: args[1],
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
return blankToNull(args[0], (input) => {
|
|
625
|
+
// Trim + lowercase and nothing else. Meta, Google, and LinkedIn all
|
|
626
|
+
// specify exactly that normalization, so stripping dots or plus-tags
|
|
627
|
+
// here would hash an address the platform never matches.
|
|
628
|
+
const normalized = normalizeEmail(asText(input));
|
|
629
|
+
return algorithm === "md5" ? md5Hex(normalized) : sha256Hex(normalized);
|
|
630
|
+
});
|
|
631
|
+
},
|
|
632
|
+
}),
|
|
579
633
|
spec({
|
|
580
634
|
name: "pad_start",
|
|
581
635
|
category: "string",
|
|
@@ -638,6 +692,46 @@ const SPECS = [
|
|
|
638
692
|
return asText(args[0]).includes(asText(args[1]));
|
|
639
693
|
},
|
|
640
694
|
}),
|
|
695
|
+
spec({
|
|
696
|
+
name: "normalize_company_name",
|
|
697
|
+
category: "string",
|
|
698
|
+
signature: "normalize_company_name(text, mode?)",
|
|
699
|
+
description: "Canonical company name: legal suffixes removed (Inc, Ltd, GmbH, Pty Ltd, sp. z o.o., …, repeatedly), stray punctuation and quotes trimmed, whitespace collapsed, \"&\" and apostrophes kept. Casing is the author’s unless the whole name is upper-case, which is title-cased. mode \"short\" (default \"display\") also drops a tagline after \" - \", \" | \", \":\", \" / \" or \"(\" and keeps at most the first three words, for use in a first sentence. Blank input returns null.",
|
|
700
|
+
minArgs: 1,
|
|
701
|
+
maxArgs: 2,
|
|
702
|
+
examples: [
|
|
703
|
+
{ expression: "normalize_company_name(\"Acme, Inc.\")", result: "Acme" },
|
|
704
|
+
{ expression: "normalize_company_name(\"ACME CORP.\")", result: "Acme" },
|
|
705
|
+
{ expression: "normalize_company_name(\"Northwind Traders LLC - Logistics\", \"short\")", result: "Northwind Traders" },
|
|
706
|
+
],
|
|
707
|
+
evaluate: (args) => {
|
|
708
|
+
if (isBlankFormulaValue(args[0]))
|
|
709
|
+
return null;
|
|
710
|
+
const mode = isBlankFormulaValue(args[1]) ? "display" : asText(args[1]).trim().toLowerCase();
|
|
711
|
+
if (mode !== "display" && mode !== "short") {
|
|
712
|
+
throw formulaExpressionError("Unsupported company-name mode.", {
|
|
713
|
+
function: "normalize_company_name",
|
|
714
|
+
mode: args[1],
|
|
715
|
+
supported: ["display", "short"],
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
return normalizeCompanyName(asText(args[0]), mode);
|
|
719
|
+
},
|
|
720
|
+
}),
|
|
721
|
+
spec({
|
|
722
|
+
name: "clean_job_title",
|
|
723
|
+
category: "string",
|
|
724
|
+
signature: "clean_job_title(text)",
|
|
725
|
+
description: "Strips emoji, symbol runs, hashtags, parentheticals and hiring slogans (\"we’re hiring\", \"open to work\", \"looking for\", \"dm me\", …) from a scraped headline, then keeps only the first clause when it is joined by \" | \", \" • \", \" · \", \" — \", \" – \" or \" :: \". A bare \"-\", \",\", \"/\" or \"&\" never cuts, so \"Account Executive - EMEA\" and \"Director of Importing/Exporting\" survive whole. All-caps titles are title-cased; any other casing is the author’s. Blank or noise-only input returns null.",
|
|
726
|
+
minArgs: 1,
|
|
727
|
+
maxArgs: 1,
|
|
728
|
+
examples: [
|
|
729
|
+
{ expression: "clean_job_title(\"VP of Sales 🚀 | We’re hiring!\")", result: "VP of Sales" },
|
|
730
|
+
{ expression: "clean_job_title(\"Head of Growth (ex-Google)\")", result: "Head of Growth" },
|
|
731
|
+
{ expression: "clean_job_title(\"Sr. Director, Demand Generation — Hiring SDRs!\")", result: "Sr. Director, Demand Generation" },
|
|
732
|
+
],
|
|
733
|
+
evaluate: (args) => (isBlankFormulaValue(args[0]) ? null : cleanJobTitle(asText(args[0]))),
|
|
734
|
+
}),
|
|
641
735
|
// --- number ----------------------------------------------------------------
|
|
642
736
|
spec({
|
|
643
737
|
name: "number",
|
|
@@ -901,6 +995,19 @@ const SPECS = [
|
|
|
901
995
|
}
|
|
902
996
|
}),
|
|
903
997
|
}),
|
|
998
|
+
spec({
|
|
999
|
+
name: "linkedin_country",
|
|
1000
|
+
category: "url_email",
|
|
1001
|
+
signature: "linkedin_country(code_or_name)",
|
|
1002
|
+
description: "Canonical LinkedIn country name for an ISO alpha-2 code, a country name, or a known alias. Returns blank for anything unrecognized, so a select column never stores a value outside its own option list.",
|
|
1003
|
+
minArgs: 1,
|
|
1004
|
+
maxArgs: 1,
|
|
1005
|
+
examples: [
|
|
1006
|
+
{ expression: "linkedin_country(\"US\")", result: "United States" },
|
|
1007
|
+
{ expression: "linkedin_country(\"Congo - Kinshasa\")", result: "Congo (DRC)" },
|
|
1008
|
+
],
|
|
1009
|
+
evaluate: (args) => blankToNull(args[0], (input) => resolveLinkedInCountry(asText(input))),
|
|
1010
|
+
}),
|
|
904
1011
|
spec({
|
|
905
1012
|
name: "normalize_linkedin_url",
|
|
906
1013
|
category: "url_email",
|
|
@@ -911,6 +1018,21 @@ const SPECS = [
|
|
|
911
1018
|
examples: [{ expression: "normalize_linkedin_url(\"https://linkedin.com/in/Ada/\")", result: "https://linkedin.com/in/ada" }],
|
|
912
1019
|
evaluate: (args) => blankToNull(args[0], (input) => normalizeLinkedinUrl(asText(input))),
|
|
913
1020
|
}),
|
|
1021
|
+
spec({
|
|
1022
|
+
name: "email_type",
|
|
1023
|
+
category: "url_email",
|
|
1024
|
+
signature: "email_type(email)",
|
|
1025
|
+
description: "Classifies an address as \"disposable\", \"role\", \"personal\" (a consumer mailbox provider) or \"work\", from OXYGEN’s built-in domain and shared-inbox lists, in that precedence. Subdomains count as their parent domain and a +tag is ignored. Blank or syntactically invalid input returns null. When a fixed list is not enough, the paid bounceban.check verifier adds live username/domain typing and syntax validity for catch-all and SEG domains.",
|
|
1026
|
+
minArgs: 1,
|
|
1027
|
+
maxArgs: 1,
|
|
1028
|
+
examples: [
|
|
1029
|
+
{ expression: "email_type(\"jane@gmail.com\")", result: "personal" },
|
|
1030
|
+
{ expression: "email_type(\"info@stripe.com\")", result: "role" },
|
|
1031
|
+
{ expression: "email_type(\"alice@mailinator.com\")", result: "disposable" },
|
|
1032
|
+
{ expression: "email_type(\"mark@acme.com\")", result: "work" },
|
|
1033
|
+
],
|
|
1034
|
+
evaluate: (args) => (isBlankFormulaValue(args[0]) ? null : classifyEmailType(asText(args[0]))),
|
|
1035
|
+
}),
|
|
914
1036
|
// --- array -----------------------------------------------------------------
|
|
915
1037
|
spec({
|
|
916
1038
|
name: "first",
|
|
@@ -989,6 +1111,19 @@ const SPECS = [
|
|
|
989
1111
|
return result;
|
|
990
1112
|
},
|
|
991
1113
|
}),
|
|
1114
|
+
spec({
|
|
1115
|
+
name: "to_json_list",
|
|
1116
|
+
category: "array",
|
|
1117
|
+
signature: "to_json_list(text, separator?)",
|
|
1118
|
+
description: "Turns one messy cell into a clean array: every part trimmed, blanks dropped, duplicates removed in first-seen order. Without a separator the delimiter is auto-detected (newline, then \";\", then \"|\", then \",\"; otherwise a single item). An existing array or a JSON-encoded array string is cleaned in place. Blank input returns an empty array. Use split() when you want the raw parts, including blanks and duplicates.",
|
|
1119
|
+
minArgs: 1,
|
|
1120
|
+
maxArgs: 2,
|
|
1121
|
+
examples: [
|
|
1122
|
+
{ expression: "to_json_list(\"fintech, payments,saas\")", result: ["fintech", "payments", "saas"] },
|
|
1123
|
+
{ expression: "to_json_list(\"growth;plg\")", result: ["growth", "plg"] },
|
|
1124
|
+
],
|
|
1125
|
+
evaluate: (args) => splitDelimitedList(args[0], isBlankFormulaValue(args[1]) ? null : asText(args[1])),
|
|
1126
|
+
}),
|
|
992
1127
|
// --- json ------------------------------------------------------------------
|
|
993
1128
|
spec({
|
|
994
1129
|
name: "path",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SHA-256 and MD5, hand-rolled and dependency-free.
|
|
3
|
+
*
|
|
4
|
+
* WHY HAND-ROLLED: this package is bundled into the browser (formula editor
|
|
5
|
+
* previews, the visual workflow editor) and its tsconfig sets `types: []` on
|
|
6
|
+
* purpose, so `node:crypto` is not importable here; `crypto.subtle.digest` is
|
|
7
|
+
* async and formula evaluation is synchronous per row, so a Promise cannot be
|
|
8
|
+
* returned to a cell either. Both digests are therefore computed inline —
|
|
9
|
+
* the textbook algorithms (FIPS 180-4 / RFC 1321), UTF-8 in, lowercase hex out.
|
|
10
|
+
*
|
|
11
|
+
* NOT FOR SECRETS. These exist because the ad platforms (Meta, Google,
|
|
12
|
+
* LinkedIn) specify exactly SHA-256 or MD5 over a trimmed, lowercased address
|
|
13
|
+
* for customer-match audience uploads. Do not reach for them for anything that
|
|
14
|
+
* needs a password hash, a MAC, or collision resistance.
|
|
15
|
+
*/
|
|
16
|
+
/** Hex-encoded SHA-256 of the UTF-8 encoding of `input`. */
|
|
17
|
+
export declare function sha256Hex(input: string): string;
|
|
18
|
+
/** Hex-encoded MD5 of the UTF-8 encoding of `input`. */
|
|
19
|
+
export declare function md5Hex(input: string): string;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SHA-256 and MD5, hand-rolled and dependency-free.
|
|
3
|
+
*
|
|
4
|
+
* WHY HAND-ROLLED: this package is bundled into the browser (formula editor
|
|
5
|
+
* previews, the visual workflow editor) and its tsconfig sets `types: []` on
|
|
6
|
+
* purpose, so `node:crypto` is not importable here; `crypto.subtle.digest` is
|
|
7
|
+
* async and formula evaluation is synchronous per row, so a Promise cannot be
|
|
8
|
+
* returned to a cell either. Both digests are therefore computed inline —
|
|
9
|
+
* the textbook algorithms (FIPS 180-4 / RFC 1321), UTF-8 in, lowercase hex out.
|
|
10
|
+
*
|
|
11
|
+
* NOT FOR SECRETS. These exist because the ad platforms (Meta, Google,
|
|
12
|
+
* LinkedIn) specify exactly SHA-256 or MD5 over a trimmed, lowercased address
|
|
13
|
+
* for customer-match audience uploads. Do not reach for them for anything that
|
|
14
|
+
* needs a password hash, a MAC, or collision resistance.
|
|
15
|
+
*/
|
|
16
|
+
// `noUncheckedIndexedAccess` types every element read as possibly undefined.
|
|
17
|
+
// Every index below is provably in range (fixed-size constant tables and a
|
|
18
|
+
// 64-word schedule), so the reads go through these accessors rather than
|
|
19
|
+
// scattering assertions through the rounds.
|
|
20
|
+
function wordAt(words, index) {
|
|
21
|
+
return words[index];
|
|
22
|
+
}
|
|
23
|
+
function shiftAt(shifts, index) {
|
|
24
|
+
return shifts[index];
|
|
25
|
+
}
|
|
26
|
+
function rotateRight(value, bits) {
|
|
27
|
+
return ((value >>> bits) | (value << (32 - bits))) >>> 0;
|
|
28
|
+
}
|
|
29
|
+
function rotateLeft(value, bits) {
|
|
30
|
+
return ((value << bits) | (value >>> (32 - bits))) >>> 0;
|
|
31
|
+
}
|
|
32
|
+
function hex32BigEndian(value) {
|
|
33
|
+
return (value >>> 0).toString(16).padStart(8, "0");
|
|
34
|
+
}
|
|
35
|
+
function hex32LittleEndian(value) {
|
|
36
|
+
let out = "";
|
|
37
|
+
for (let byte = 0; byte < 4; byte += 1) {
|
|
38
|
+
out += ((value >>> (byte * 8)) & 0xff).toString(16).padStart(2, "0");
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Merkle-Damgård padding shared by both digests: append 0x80, zero-fill to a
|
|
44
|
+
* 64-byte boundary, then write the message length in bits into the last eight
|
|
45
|
+
* bytes (big-endian for SHA-256, little-endian for MD5).
|
|
46
|
+
*/
|
|
47
|
+
function padMessage(bytes, littleEndianLength) {
|
|
48
|
+
const paddedLength = (((bytes.length + 8) >> 6) + 1) << 6;
|
|
49
|
+
const padded = new Uint8Array(paddedLength);
|
|
50
|
+
padded.set(bytes);
|
|
51
|
+
padded[bytes.length] = 0x80;
|
|
52
|
+
const view = new DataView(padded.buffer);
|
|
53
|
+
const bitLength = bytes.length * 8;
|
|
54
|
+
const low = bitLength >>> 0;
|
|
55
|
+
const high = Math.floor(bitLength / 0x1_0000_0000);
|
|
56
|
+
if (littleEndianLength) {
|
|
57
|
+
view.setUint32(paddedLength - 8, low, true);
|
|
58
|
+
view.setUint32(paddedLength - 4, high, true);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
view.setUint32(paddedLength - 8, high, false);
|
|
62
|
+
view.setUint32(paddedLength - 4, low, false);
|
|
63
|
+
}
|
|
64
|
+
return view;
|
|
65
|
+
}
|
|
66
|
+
function utf8Bytes(input) {
|
|
67
|
+
return new TextEncoder().encode(input);
|
|
68
|
+
}
|
|
69
|
+
const SHA256_K = new Uint32Array([
|
|
70
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
71
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
72
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
73
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
74
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
75
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
76
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
77
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
78
|
+
]);
|
|
79
|
+
/** Hex-encoded SHA-256 of the UTF-8 encoding of `input`. */
|
|
80
|
+
export function sha256Hex(input) {
|
|
81
|
+
const view = padMessage(utf8Bytes(input), false);
|
|
82
|
+
const state = new Uint32Array([
|
|
83
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
|
84
|
+
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
85
|
+
]);
|
|
86
|
+
const schedule = new Uint32Array(64);
|
|
87
|
+
for (let offset = 0; offset < view.byteLength; offset += 64) {
|
|
88
|
+
for (let i = 0; i < 16; i += 1) {
|
|
89
|
+
schedule[i] = view.getUint32(offset + i * 4, false);
|
|
90
|
+
}
|
|
91
|
+
for (let i = 16; i < 64; i += 1) {
|
|
92
|
+
const previous = wordAt(schedule, i - 15);
|
|
93
|
+
const recent = wordAt(schedule, i - 2);
|
|
94
|
+
const s0 = rotateRight(previous, 7) ^ rotateRight(previous, 18) ^ (previous >>> 3);
|
|
95
|
+
const s1 = rotateRight(recent, 17) ^ rotateRight(recent, 19) ^ (recent >>> 10);
|
|
96
|
+
schedule[i] = (wordAt(schedule, i - 16) + s0 + wordAt(schedule, i - 7) + s1) >>> 0;
|
|
97
|
+
}
|
|
98
|
+
let a = wordAt(state, 0);
|
|
99
|
+
let b = wordAt(state, 1);
|
|
100
|
+
let c = wordAt(state, 2);
|
|
101
|
+
let d = wordAt(state, 3);
|
|
102
|
+
let e = wordAt(state, 4);
|
|
103
|
+
let f = wordAt(state, 5);
|
|
104
|
+
let g = wordAt(state, 6);
|
|
105
|
+
let h = wordAt(state, 7);
|
|
106
|
+
for (let i = 0; i < 64; i += 1) {
|
|
107
|
+
const s1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
|
|
108
|
+
const choice = (e & f) ^ (~e & g);
|
|
109
|
+
const temp1 = (h + s1 + choice + wordAt(SHA256_K, i) + wordAt(schedule, i)) >>> 0;
|
|
110
|
+
const s0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
|
|
111
|
+
const majority = (a & b) ^ (a & c) ^ (b & c);
|
|
112
|
+
const temp2 = (s0 + majority) >>> 0;
|
|
113
|
+
h = g;
|
|
114
|
+
g = f;
|
|
115
|
+
f = e;
|
|
116
|
+
e = (d + temp1) >>> 0;
|
|
117
|
+
d = c;
|
|
118
|
+
c = b;
|
|
119
|
+
b = a;
|
|
120
|
+
a = (temp1 + temp2) >>> 0;
|
|
121
|
+
}
|
|
122
|
+
state[0] = (wordAt(state, 0) + a) >>> 0;
|
|
123
|
+
state[1] = (wordAt(state, 1) + b) >>> 0;
|
|
124
|
+
state[2] = (wordAt(state, 2) + c) >>> 0;
|
|
125
|
+
state[3] = (wordAt(state, 3) + d) >>> 0;
|
|
126
|
+
state[4] = (wordAt(state, 4) + e) >>> 0;
|
|
127
|
+
state[5] = (wordAt(state, 5) + f) >>> 0;
|
|
128
|
+
state[6] = (wordAt(state, 6) + g) >>> 0;
|
|
129
|
+
state[7] = (wordAt(state, 7) + h) >>> 0;
|
|
130
|
+
}
|
|
131
|
+
let digest = "";
|
|
132
|
+
for (let i = 0; i < 8; i += 1)
|
|
133
|
+
digest += hex32BigEndian(wordAt(state, i));
|
|
134
|
+
return digest;
|
|
135
|
+
}
|
|
136
|
+
const MD5_K = new Uint32Array([
|
|
137
|
+
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
|
|
138
|
+
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
|
|
139
|
+
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
|
|
140
|
+
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
|
|
141
|
+
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
|
|
142
|
+
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
|
|
143
|
+
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
|
|
144
|
+
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
|
|
145
|
+
]);
|
|
146
|
+
const MD5_SHIFTS = [
|
|
147
|
+
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
|
|
148
|
+
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
|
|
149
|
+
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
|
|
150
|
+
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
|
|
151
|
+
];
|
|
152
|
+
/** Hex-encoded MD5 of the UTF-8 encoding of `input`. */
|
|
153
|
+
export function md5Hex(input) {
|
|
154
|
+
const view = padMessage(utf8Bytes(input), true);
|
|
155
|
+
const state = new Uint32Array([0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]);
|
|
156
|
+
const block = new Uint32Array(16);
|
|
157
|
+
for (let offset = 0; offset < view.byteLength; offset += 64) {
|
|
158
|
+
for (let i = 0; i < 16; i += 1) {
|
|
159
|
+
block[i] = view.getUint32(offset + i * 4, true);
|
|
160
|
+
}
|
|
161
|
+
let a = wordAt(state, 0);
|
|
162
|
+
let b = wordAt(state, 1);
|
|
163
|
+
let c = wordAt(state, 2);
|
|
164
|
+
let d = wordAt(state, 3);
|
|
165
|
+
for (let i = 0; i < 64; i += 1) {
|
|
166
|
+
let mixed;
|
|
167
|
+
let wordIndex;
|
|
168
|
+
if (i < 16) {
|
|
169
|
+
mixed = (b & c) | (~b & d);
|
|
170
|
+
wordIndex = i;
|
|
171
|
+
}
|
|
172
|
+
else if (i < 32) {
|
|
173
|
+
mixed = (d & b) | (~d & c);
|
|
174
|
+
wordIndex = (5 * i + 1) % 16;
|
|
175
|
+
}
|
|
176
|
+
else if (i < 48) {
|
|
177
|
+
mixed = b ^ c ^ d;
|
|
178
|
+
wordIndex = (3 * i + 5) % 16;
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
mixed = c ^ (b | ~d);
|
|
182
|
+
wordIndex = (7 * i) % 16;
|
|
183
|
+
}
|
|
184
|
+
const rotated = rotateLeft((mixed + a + wordAt(MD5_K, i) + wordAt(block, wordIndex)) >>> 0, shiftAt(MD5_SHIFTS, i));
|
|
185
|
+
a = d;
|
|
186
|
+
d = c;
|
|
187
|
+
c = b;
|
|
188
|
+
b = (b + rotated) >>> 0;
|
|
189
|
+
}
|
|
190
|
+
state[0] = (wordAt(state, 0) + a) >>> 0;
|
|
191
|
+
state[1] = (wordAt(state, 1) + b) >>> 0;
|
|
192
|
+
state[2] = (wordAt(state, 2) + c) >>> 0;
|
|
193
|
+
state[3] = (wordAt(state, 3) + d) >>> 0;
|
|
194
|
+
}
|
|
195
|
+
let digest = "";
|
|
196
|
+
for (let i = 0; i < 4; i += 1)
|
|
197
|
+
digest += hex32LittleEndian(wordAt(state, i));
|
|
198
|
+
return digest;
|
|
199
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
/** True when every cased letter in the text is upper-case (digits/punctuation ignored). */
|
|
13
|
+
export declare function isAllCaps(value: string): boolean;
|
|
14
|
+
/** Capitalizes the first letter of every word and lowercases the rest (Unicode-aware). */
|
|
15
|
+
export declare function titleCaseWords(value: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Legal/corporate suffixes, stored as punctuation-free lower-case keys so
|
|
18
|
+
* "L.L.C.", "llc", and "LLC," all reduce to `llc`. Multi-word suffixes are
|
|
19
|
+
* joined ("pty ltd" → `ptyltd`) and matched against the trailing 1–3 tokens.
|
|
20
|
+
*/
|
|
21
|
+
export declare const COMPANY_LEGAL_SUFFIX_KEYS: ReadonlySet<string>;
|
|
22
|
+
export type CompanyNameMode = "display" | "short";
|
|
23
|
+
/**
|
|
24
|
+
* Canonical company name: legal suffixes removed, stray punctuation and quotes
|
|
25
|
+
* trimmed, whitespace collapsed. Casing is the author's unless the whole name
|
|
26
|
+
* shouts ("ACME CORP." → "Acme"), because "hubspot" is how that company writes
|
|
27
|
+
* itself. `short` additionally cuts a tagline after a separator and keeps at
|
|
28
|
+
* most three words — the form that fits in an email's first sentence.
|
|
29
|
+
*
|
|
30
|
+
* Blank input returns null.
|
|
31
|
+
*/
|
|
32
|
+
export declare function normalizeCompanyName(value: unknown, mode?: CompanyNameMode): string | null;
|
|
33
|
+
/**
|
|
34
|
+
* Recruiting and self-promotion phrases that ride along in a scraped headline.
|
|
35
|
+
* Longest first so "we're hiring" is consumed before the bare "hiring".
|
|
36
|
+
*/
|
|
37
|
+
export declare const JOB_TITLE_SLOGANS: readonly string[];
|
|
38
|
+
/**
|
|
39
|
+
* Canonical job title: emoji, hashtags, parentheticals, and hiring slogans
|
|
40
|
+
* removed, then only the first clause of a separator-joined headline kept.
|
|
41
|
+
* All-caps titles are title-cased; every other casing is the author's.
|
|
42
|
+
*
|
|
43
|
+
* Blank input, or input that was nothing but noise, returns null.
|
|
44
|
+
*/
|
|
45
|
+
export declare function cleanJobTitle(value: unknown): string | null;
|
|
46
|
+
/**
|
|
47
|
+
* Turns one messy cell into a clean array: trimmed, blank-free, de-duplicated
|
|
48
|
+
* in first-seen order. An existing array or a JSON-encoded array is cleaned in
|
|
49
|
+
* place; a string is split on the given separator, or on the first delimiter
|
|
50
|
+
* actually present (newline, `;`, `|`, `,`) when none is given.
|
|
51
|
+
*
|
|
52
|
+
* Blank input returns an empty array.
|
|
53
|
+
*/
|
|
54
|
+
export declare function splitDelimitedList(value: unknown, separator?: string | null): string[];
|
|
55
|
+
/**
|
|
56
|
+
* Consumer mailbox providers — a person, not a company domain. Re-exported from
|
|
57
|
+
* `@oxygen/shared/freemail-domains`, which is now the ONE list; this module used to
|
|
58
|
+
* carry its own 101-entry copy and was the best-maintained of eight divergent forks.
|
|
59
|
+
* Its entries survive as the bulk of the shared broad tier.
|
|
60
|
+
*/
|
|
61
|
+
export declare const FREE_MAIL_DOMAINS: ReadonlySet<string>;
|
|
62
|
+
/** Throwaway inbox providers — never worth a send, never worth a credit. */
|
|
63
|
+
export declare const DISPOSABLE_EMAIL_DOMAINS: ReadonlySet<string>;
|
|
64
|
+
/** Shared-inbox local parts — a department, not a person. */
|
|
65
|
+
export declare const ROLE_EMAIL_LOCAL_PARTS: ReadonlySet<string>;
|
|
66
|
+
export type EmailType = "disposable" | "role" | "personal" | "work";
|
|
67
|
+
/**
|
|
68
|
+
* Classifies an email address from OXYGEN's built-in lists, in precedence
|
|
69
|
+
* order: a throwaway domain wins, then a shared-inbox local part, then a
|
|
70
|
+
* consumer mailbox provider, and anything left is a company address.
|
|
71
|
+
*
|
|
72
|
+
* Blank or syntactically invalid input returns null.
|
|
73
|
+
*/
|
|
74
|
+
export declare function classifyEmailType(value: unknown): EmailType | null;
|