@oxygen-agent/cli 1.987.20 → 1.1010.1

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 (176) hide show
  1. package/README.md +1 -1
  2. package/dist/admin-primary-providers-render.d.ts +0 -2
  3. package/dist/admin-primary-providers-render.js +1 -1
  4. package/dist/browser-login.js +1 -4
  5. package/dist/column-decision-options.d.ts +20 -0
  6. package/dist/column-decision-options.js +54 -0
  7. package/dist/command-manifest.d.ts +3 -2
  8. package/dist/command-manifest.js +25 -2
  9. package/dist/credentials.d.ts +1 -1
  10. package/dist/functions-commands.js +13 -9
  11. package/dist/help.d.ts +8 -0
  12. package/dist/help.js +46 -0
  13. package/dist/index.js +2751 -242
  14. package/dist/knowledge-mirror.d.ts +2 -2
  15. package/dist/runtime.d.ts +0 -15
  16. package/dist/runtime.js +1 -1
  17. package/dist/search-ai-filter-notice.d.ts +17 -0
  18. package/dist/search-ai-filter-notice.js +38 -0
  19. package/dist/session.d.ts +4 -3
  20. package/dist/skills.d.ts +8 -7
  21. package/dist/skills.js +58 -20
  22. package/dist/transcript.d.ts +2 -1
  23. package/dist/util.d.ts +10 -1
  24. package/dist/util.js +14 -2
  25. package/node_modules/@oxygen/cli-ugc/dist/commands.js +296 -140
  26. package/node_modules/@oxygen/cli-ugc/dist/field-parser.d.ts +9 -0
  27. package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +34 -0
  28. package/node_modules/@oxygen/formula/dist/coerce.d.ts +10 -0
  29. package/node_modules/@oxygen/formula/dist/coerce.js +10 -0
  30. package/node_modules/@oxygen/formula/dist/formula-functions.js +65 -0
  31. package/node_modules/@oxygen/formula/dist/hash.d.ts +19 -0
  32. package/node_modules/@oxygen/formula/dist/hash.js +199 -0
  33. package/node_modules/@oxygen/formula/dist/value-cleaners.d.ts +6 -1
  34. package/node_modules/@oxygen/formula/dist/value-cleaners.js +10 -26
  35. package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +13 -0
  36. package/node_modules/@oxygen/shared/dist/array-utils.d.ts +5 -0
  37. package/node_modules/@oxygen/shared/dist/array-utils.js +11 -0
  38. package/node_modules/@oxygen/shared/dist/billing.d.ts +99 -22
  39. package/node_modules/@oxygen/shared/dist/billing.js +195 -40
  40. package/node_modules/@oxygen/shared/dist/byok-connect.js +5 -0
  41. package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +27 -0
  42. package/node_modules/@oxygen/shared/dist/capability-discovery.js +311 -28
  43. package/node_modules/@oxygen/shared/dist/cli-http-error.d.ts +8 -0
  44. package/node_modules/@oxygen/shared/dist/cli-http-error.js +8 -0
  45. package/node_modules/@oxygen/shared/dist/cli-result.js +1 -0
  46. package/node_modules/@oxygen/shared/dist/column-autofill.d.ts +52 -0
  47. package/node_modules/@oxygen/shared/dist/column-autofill.js +62 -0
  48. package/node_modules/@oxygen/shared/dist/column-decision.d.ts +50 -0
  49. package/node_modules/@oxygen/shared/dist/column-decision.js +228 -0
  50. package/node_modules/@oxygen/shared/dist/column-output-fields.js +2 -6
  51. package/node_modules/@oxygen/shared/dist/company-enrichment-fields.d.ts +113 -0
  52. package/node_modules/@oxygen/shared/dist/company-enrichment-fields.js +548 -0
  53. package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +6 -6
  54. package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +6 -6
  55. package/node_modules/@oxygen/shared/dist/cutover-freeze.d.ts +26 -0
  56. package/node_modules/@oxygen/shared/dist/cutover-freeze.js +52 -0
  57. package/node_modules/@oxygen/shared/dist/data-suppliers.d.ts +57 -0
  58. package/node_modules/@oxygen/shared/dist/data-suppliers.js +59 -0
  59. package/node_modules/@oxygen/shared/dist/deploy-env.d.ts +74 -0
  60. package/node_modules/@oxygen/shared/dist/deploy-env.js +82 -0
  61. package/node_modules/@oxygen/shared/dist/dnc-rules.d.ts +130 -0
  62. package/node_modules/@oxygen/shared/dist/dnc-rules.js +221 -0
  63. package/node_modules/@oxygen/shared/dist/enrichment-intents.d.ts +107 -0
  64. package/node_modules/@oxygen/shared/dist/enrichment-intents.js +809 -0
  65. package/node_modules/@oxygen/shared/dist/error-message.d.ts +1 -0
  66. package/node_modules/@oxygen/shared/dist/error-message.js +3 -0
  67. package/node_modules/@oxygen/shared/dist/error-redaction.js +1 -3
  68. package/node_modules/@oxygen/shared/dist/external-write-policy.d.ts +33 -0
  69. package/node_modules/@oxygen/shared/dist/external-write-policy.js +68 -0
  70. package/node_modules/@oxygen/shared/dist/format-percent.d.ts +8 -0
  71. package/node_modules/@oxygen/shared/dist/format-percent.js +13 -0
  72. package/node_modules/@oxygen/shared/dist/freemail-domains.d.ts +81 -0
  73. package/node_modules/@oxygen/shared/dist/freemail-domains.js +157 -0
  74. package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.d.ts +1 -0
  75. package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.js +1 -1
  76. package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +60 -4
  77. package/node_modules/@oxygen/shared/dist/hosted-ai.js +125 -10
  78. package/node_modules/@oxygen/shared/dist/index.d.ts +15 -0
  79. package/node_modules/@oxygen/shared/dist/index.js +15 -0
  80. package/node_modules/@oxygen/shared/dist/json-path.js +1 -3
  81. package/node_modules/@oxygen/shared/dist/knowledge-bases.js +1 -3
  82. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +2 -2
  83. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +2 -2
  84. package/node_modules/@oxygen/shared/dist/langfuse.d.ts +44 -1
  85. package/node_modules/@oxygen/shared/dist/langfuse.js +416 -14
  86. package/node_modules/@oxygen/shared/dist/linkedin-countries.d.ts +33 -0
  87. package/node_modules/@oxygen/shared/dist/linkedin-countries.js +361 -0
  88. package/node_modules/@oxygen/shared/dist/linkedin-country-timezones.d.ts +24 -0
  89. package/node_modules/@oxygen/shared/dist/linkedin-country-timezones.js +276 -0
  90. package/node_modules/@oxygen/shared/dist/linkedin-message-deletion.d.ts +2 -0
  91. package/node_modules/@oxygen/shared/dist/linkedin-message-deletion.js +5 -0
  92. package/node_modules/@oxygen/shared/dist/linkedin-post-keywords.d.ts +44 -0
  93. package/node_modules/@oxygen/shared/dist/linkedin-post-keywords.js +116 -0
  94. package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +96 -0
  95. package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +123 -0
  96. package/node_modules/@oxygen/shared/dist/llm-durable-capture.d.ts +24 -0
  97. package/node_modules/@oxygen/shared/dist/llm-durable-capture.js +89 -0
  98. package/node_modules/@oxygen/shared/dist/llm-prompts.d.ts +75 -0
  99. package/node_modules/@oxygen/shared/dist/llm-prompts.js +161 -0
  100. package/node_modules/@oxygen/shared/dist/log-sink-selector.d.ts +39 -0
  101. package/node_modules/@oxygen/shared/dist/log-sink-selector.js +56 -0
  102. package/node_modules/@oxygen/shared/dist/log.d.ts +1 -0
  103. package/node_modules/@oxygen/shared/dist/log.js +6 -1
  104. package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.d.ts +90 -0
  105. package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.js +130 -0
  106. package/node_modules/@oxygen/shared/dist/object-storage.d.ts +17 -0
  107. package/node_modules/@oxygen/shared/dist/object-storage.js +21 -0
  108. package/node_modules/@oxygen/shared/dist/operational-telemetry.d.ts +24 -0
  109. package/node_modules/@oxygen/shared/dist/operational-telemetry.js +73 -0
  110. package/node_modules/@oxygen/shared/dist/otlp-log-sink.d.ts +79 -0
  111. package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +366 -0
  112. package/node_modules/@oxygen/shared/dist/plan-capabilities.js +1 -0
  113. package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +23 -22
  114. package/node_modules/@oxygen/shared/dist/plan-limits.js +45 -18
  115. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +48 -41
  116. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +36 -25
  117. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +22 -22
  118. package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +40 -34
  119. package/node_modules/@oxygen/shared/dist/product-analytics-environment.js +9 -0
  120. package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +36 -2
  121. package/node_modules/@oxygen/shared/dist/product-analytics-events.js +36 -1
  122. package/node_modules/@oxygen/shared/dist/rate-window.d.ts +5 -0
  123. package/node_modules/@oxygen/shared/dist/rate-window.js +8 -0
  124. package/node_modules/@oxygen/shared/dist/redaction.js +4 -1
  125. package/node_modules/@oxygen/shared/dist/research-output-contract.js +1 -3
  126. package/node_modules/@oxygen/shared/dist/scraper-lane-credential.d.ts +18 -0
  127. package/node_modules/@oxygen/shared/dist/scraper-lane-credential.js +23 -0
  128. package/node_modules/@oxygen/shared/dist/search-vocab.js +4 -5
  129. package/node_modules/@oxygen/shared/dist/select-options.js +6 -1
  130. package/node_modules/@oxygen/shared/dist/sequence-failures.js +1 -5
  131. package/node_modules/@oxygen/shared/dist/sequences.d.ts +23 -0
  132. package/node_modules/@oxygen/shared/dist/sequences.js +115 -5
  133. package/node_modules/@oxygen/shared/dist/signup-lead-payload.d.ts +80 -0
  134. package/node_modules/@oxygen/shared/dist/signup-lead-payload.js +198 -0
  135. package/node_modules/@oxygen/shared/dist/social-capabilities.d.ts +6 -0
  136. package/node_modules/@oxygen/shared/dist/social-capabilities.js +25 -16
  137. package/node_modules/@oxygen/shared/dist/social-post-metrics-core.d.ts +32 -0
  138. package/node_modules/@oxygen/shared/dist/social-post-metrics-core.js +32 -0
  139. package/node_modules/@oxygen/shared/dist/social-post-metrics-linkedin.d.ts +31 -0
  140. package/node_modules/@oxygen/shared/dist/social-post-metrics-linkedin.js +103 -0
  141. package/node_modules/@oxygen/shared/dist/social-post-metrics-series.d.ts +96 -0
  142. package/node_modules/@oxygen/shared/dist/social-post-metrics-series.js +213 -0
  143. package/node_modules/@oxygen/shared/dist/social-post-metrics-x.d.ts +13 -0
  144. package/node_modules/@oxygen/shared/dist/social-post-metrics-x.js +78 -0
  145. package/node_modules/@oxygen/shared/dist/social-post-metrics.d.ts +36 -0
  146. package/node_modules/@oxygen/shared/dist/social-post-metrics.js +51 -0
  147. package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +22 -10
  148. package/node_modules/@oxygen/shared/dist/spend-safety.js +15 -21
  149. package/node_modules/@oxygen/shared/dist/sql-rows.d.ts +1 -0
  150. package/node_modules/@oxygen/shared/dist/sql-rows.js +3 -0
  151. package/node_modules/@oxygen/shared/dist/stripe-price-catalog.d.ts +36 -0
  152. package/node_modules/@oxygen/shared/dist/stripe-price-catalog.js +184 -0
  153. package/node_modules/@oxygen/shared/dist/stripe-subscription-kind.d.ts +41 -0
  154. package/node_modules/@oxygen/shared/dist/stripe-subscription-kind.js +44 -0
  155. package/node_modules/@oxygen/shared/dist/table-limits.d.ts +3 -0
  156. package/node_modules/@oxygen/shared/dist/table-limits.js +3 -0
  157. package/node_modules/@oxygen/shared/dist/telemetry-export-observer.d.ts +94 -0
  158. package/node_modules/@oxygen/shared/dist/telemetry-export-observer.js +298 -0
  159. package/node_modules/@oxygen/shared/dist/telemetry.d.ts +11 -0
  160. package/node_modules/@oxygen/shared/dist/telemetry.js +28 -2
  161. package/node_modules/@oxygen/shared/dist/type-guards.d.ts +22 -0
  162. package/node_modules/@oxygen/shared/dist/type-guards.js +35 -0
  163. package/node_modules/@oxygen/shared/dist/ugc.d.ts +22 -11
  164. package/node_modules/@oxygen/shared/dist/ugc.js +10 -0
  165. package/node_modules/@oxygen/shared/dist/value-readers.d.ts +21 -0
  166. package/node_modules/@oxygen/shared/dist/value-readers.js +59 -0
  167. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -0
  168. package/node_modules/@oxygen/shared/dist/version.generated.js +2 -0
  169. package/node_modules/@oxygen/shared/dist/version.js +8 -1
  170. package/node_modules/@oxygen/shared/dist/workspace-event-catalog.js +0 -23
  171. package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +29 -0
  172. package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +31 -0
  173. package/node_modules/@oxygen/shared/package.json +59 -0
  174. package/node_modules/@oxygen/workflows/dist/graph/expression.js +2 -5
  175. package/node_modules/@oxygen/workflows/dist/graph/params.js +1 -1
  176. package/package.json +3 -2
@@ -0,0 +1,809 @@
1
+ // The ENRICHMENT INTENT TAXONOMY: every data type a customer can ask OXYGEN to
2
+ // fill (person profile, work email, phone, headcount, funding round, tech
3
+ // stack, decision makers, signals ...), as one ranked, client-safe list with
4
+ // customer-facing labels, the identity inputs each needs, the typed output
5
+ // fields it fills, which product surface materialises it, and an honest
6
+ // status (default | selectable | no_default | coming_soon | rejected).
7
+ //
8
+ // Why this exists (founder, 2026-09-19): the tool catalog exposes ~1,700
9
+ // provider operations, and neither the Workspace Copilot nor an external agent
10
+ // can pick the right one for "get me the funding round". The catalogue lets a
11
+ // customer name the DATA; the routing registry (packages/tools/src/routing-
12
+ // registry) names the provider chain. This leaf is the customer-facing half
13
+ // and carries NO provider names in labels or descriptions and NO prices —
14
+ // provider order and prices are projected server-side from the registry and
15
+ // the pricing resolver (apps/web/src/lib/enrichment-column-catalog.ts).
16
+ //
17
+ // Vendor cost structure (flat fee, unlimited, zero marginal cost) is internal
18
+ // margin information and must never appear in any string here
19
+ // (OXYGEN_PRODUCT.md, 2026-08-10); enrichment-intents.test.ts lints for it.
20
+ //
21
+ // CLIENT-SAFE LEAF: exported as the `@oxygen/shared/enrichment-intents`
22
+ // subpath, read by browser components and by @oxygen/tools (the registry
23
+ // binds `catalog.taxonomy_id` to these ids). It must never gain a runtime
24
+ // import; enrichment-intents.test.ts guards it.
25
+ export const ENRICHMENT_INTENT_GROUPS = ["person", "contact", "company", "decision_maker", "signal"];
26
+ export const ENRICHMENT_INTENT_GROUP_LABELS = {
27
+ person: "Person",
28
+ contact: "Contact",
29
+ company: "Company",
30
+ decision_maker: "Decision makers",
31
+ signal: "Signals",
32
+ };
33
+ const f = (key, label, semanticType, dataType = "text", derived) => ({ key, label, semanticType, dataType, ...(derived ? { derived: true } : {}) });
34
+ export const ENRICHMENT_INTENT_TAXONOMY = [
35
+ // ── Person ────────────────────────────────────────────────────────────────
36
+ {
37
+ id: "enrich_person_profile",
38
+ label: "Person profile",
39
+ description: "Fills a person's name, title, company, headline, location, bio, career history, education and skills from their LinkedIn profile.",
40
+ entity: "person",
41
+ group: "person",
42
+ kind: "profile",
43
+ rank: 10,
44
+ aliases: ["person profile", "enrich person", "enrich people", "linkedin profile data", "job title", "seniority", "current company", "work history", "person location"],
45
+ inputShapes: ["linkedin_url"],
46
+ outputFields: [
47
+ f("full_name", "Full name", "person.full_name"),
48
+ f("first_name", "First name", "person.first_name"),
49
+ f("last_name", "Last name", "person.last_name"),
50
+ f("job_title", "Job title", "person.job_title"),
51
+ f("headline", "Headline", "person.headline"),
52
+ f("current_company", "Current company", null),
53
+ f("current_company_linkedin_url", "Current company LinkedIn URL", "company.linkedin_url"),
54
+ f("job_start_date", "Job start date", null),
55
+ f("location", "Location", "person.location"),
56
+ f("bio", "Bio", "person.bio"),
57
+ f("linkedin_followers", "LinkedIn followers", "person.linkedin_followers", "numeric"),
58
+ f("education", "Education", null, "jsonb"),
59
+ f("school_name", "School", null),
60
+ ],
61
+ surfaces: { preset: "person_enrich", find: "find person", managedFunction: "enrich_person", catalogEntry: "preset:person_enrich", registryFunction: "enrichment", registryIntent: "enrich_person_profile" },
62
+ status: "default",
63
+ decision: "D47",
64
+ },
65
+ {
66
+ id: "find_linkedin_profile",
67
+ label: "LinkedIn profile URL",
68
+ description: "Resolves a person's LinkedIn profile from first/last name + company domain (or name), with identity validation on every hit.",
69
+ entity: "person",
70
+ group: "person",
71
+ kind: "enrichment",
72
+ rank: 20,
73
+ aliases: ["linkedin url", "linkedin urls", "find linkedin", "profile url", "resolve linkedin", "linkedin for each"],
74
+ inputShapes: ["name_company"],
75
+ outputFields: [
76
+ f("linkedin_url", "LinkedIn URL", "person.linkedin_url"),
77
+ f("linkedin_url_confidence", "Match confidence", null),
78
+ ],
79
+ surfaces: { capability: "linkedin_url", find: "find linkedin", managedFunction: "person_linkedin_url", catalogEntry: "capability:linkedin_url", registryFunction: "enrichment", registryIntent: "find_linkedin_profile" },
80
+ status: "default",
81
+ decision: "D46",
82
+ },
83
+ {
84
+ id: "person_company",
85
+ label: "Person's company",
86
+ description: "Adds the person's current company, then fills that company's domain, industry and headcount from the same profile.",
87
+ entity: "person",
88
+ group: "person",
89
+ kind: "template",
90
+ rank: 30,
91
+ aliases: ["person's company", "company of this person", "employer"],
92
+ inputShapes: ["linkedin_url"],
93
+ outputFields: [
94
+ f("current_company_linkedin_url", "Company LinkedIn URL", "company.linkedin_url"),
95
+ f("company_domain", "Company domain", "company.domain"),
96
+ f("company_industry", "Industry", "company.industry"),
97
+ f("company_headcount", "Headcount", "company.employee_count", "numeric"),
98
+ ],
99
+ surfaces: { catalogEntry: "recipe:person_company" },
100
+ status: "default",
101
+ },
102
+ // ── Contact ───────────────────────────────────────────────────────────────
103
+ {
104
+ id: "find_work_email",
105
+ label: "Work email",
106
+ description: "Finds a verified work email through a cost-ordered waterfall that bills only when an address is found.",
107
+ entity: "person",
108
+ group: "contact",
109
+ kind: "enrichment",
110
+ rank: 40,
111
+ aliases: ["work email", "business email", "email address", "find email", "emails for", "email finder", "with emails"],
112
+ inputShapes: ["linkedin_url", "name_domain", "first_last_domain"],
113
+ outputFields: [
114
+ f("email", "Work email", "person.work_email"),
115
+ f("email_status", "Deliverability", "person.email_status"),
116
+ ],
117
+ surfaces: { capability: "work_email", find: "find email", managedFunction: "work_email", catalogEntry: "capability:work_email", registryFunction: "enrichment", registryIntent: "find_work_email" },
118
+ status: "default",
119
+ },
120
+ {
121
+ id: "find_personal_email",
122
+ label: "Personal email",
123
+ description: "Finds a personal (non-work) email from a LinkedIn profile, billed only on a hit and graded before it lands.",
124
+ entity: "person",
125
+ group: "contact",
126
+ kind: "enrichment",
127
+ rank: 50,
128
+ aliases: ["personal email", "gmail", "home email", "private email", "consumer email"],
129
+ inputShapes: ["linkedin_url"],
130
+ outputFields: [f("personal_email", "Personal email", "person.personal_email")],
131
+ surfaces: { capability: "personal_email", find: "find personal-email", managedFunction: "personal_email", catalogEntry: "capability:personal_email", registryFunction: "enrichment", registryIntent: "find_personal_email" },
132
+ status: "default",
133
+ decision: "D43",
134
+ },
135
+ {
136
+ id: "find_phone",
137
+ label: "Phone",
138
+ description: "Finds a mobile or direct-dial number through a short cheapest-first waterfall that bills only on a hit, with optional line-type verification.",
139
+ entity: "person",
140
+ group: "contact",
141
+ kind: "enrichment",
142
+ rank: 60,
143
+ aliases: ["mobile number", "mobile phone", "cell phone", "direct dial", "work phone", "phone number", "phone numbers", "this person's mobile", "find phone", "find mobile", "a mobile"],
144
+ inputShapes: ["linkedin_url", "email", "name_domain"],
145
+ outputFields: [
146
+ f("mobile_phone", "Mobile phone", "person.mobile_phone"),
147
+ f("work_phone", "Work / direct-dial phone", "person.phone"),
148
+ f("phone_line_type", "Line type", null),
149
+ f("phone_carrier", "Carrier", null),
150
+ ],
151
+ surfaces: { capability: "mobile_phone", find: "find phone", managedFunction: "mobile_phone", catalogEntry: "capability:mobile_phone", registryFunction: "enrichment", registryIntent: "find_mobile_phone" },
152
+ status: "default",
153
+ decision: "D44",
154
+ },
155
+ {
156
+ id: "verify_email",
157
+ label: "Email verification",
158
+ description: "Grades addresses you already have as valid, invalid, catch-all or unknown; catch-all domains escalate once to a second verifier.",
159
+ entity: "person",
160
+ group: "contact",
161
+ kind: "verification",
162
+ rank: 70,
163
+ aliases: ["verify email", "verify these emails", "validate email", "email verification", "deliverability", "bounce check", "catch-all"],
164
+ inputShapes: ["email"],
165
+ outputFields: [f("email_verdict", "Verdict", "person.email_status")],
166
+ surfaces: { capability: "verify_email", find: "verify email", catalogEntry: "capability:verify_email", registryFunction: "enrichment", registryIntent: "verify_email" },
167
+ status: "default",
168
+ decision: "D45",
169
+ },
170
+ {
171
+ id: "verify_phone",
172
+ label: "Phone verification",
173
+ description: "Checks line type, carrier and reachability of numbers you already hold.",
174
+ entity: "person",
175
+ group: "contact",
176
+ kind: "verification",
177
+ rank: 80,
178
+ aliases: ["verify phone", "verify these phone numbers", "validate phone", "line type", "is this a mobile"],
179
+ inputShapes: ["phone"],
180
+ outputFields: [
181
+ f("phone_line_type", "Line type", null),
182
+ f("phone_carrier", "Carrier", null),
183
+ f("phone_verdict", "Verdict", null),
184
+ ],
185
+ surfaces: { registryFunction: "enrichment", registryIntent: "verify_phone" },
186
+ status: "selectable",
187
+ statusReason: "Runs today as the optional verification step of the Phone column; a standalone phone-verification column is a follow-up.",
188
+ },
189
+ // ── Company ───────────────────────────────────────────────────────────────
190
+ {
191
+ id: "enrich_company:identity",
192
+ label: "Company identity",
193
+ description: "Resolves a company's website domain and LinkedIn page from whichever one you have, or from the name.",
194
+ entity: "company",
195
+ group: "company",
196
+ kind: "enrichment",
197
+ rank: 100,
198
+ aliases: ["company domain", "company website", "website domain", "company linkedin page", "resolve domain", "company url"],
199
+ inputShapes: ["name", "domain", "linkedin_url"],
200
+ outputFields: [f("company_domain", "Domain", "company.domain"), f("company_linkedin_url", "LinkedIn URL", "company.linkedin_url")],
201
+ surfaces: { preset: "company_enrich", companyFields: ["domain", "linkedin_url"], find: "find company", managedFunction: "company_domain", catalogEntry: "preset:company_enrich", registryFunction: "enrichment", registryIntent: "enrich_company", registryInputShapes: ["field:domain", "field:linkedin_url"] },
202
+ status: "default",
203
+ decision: "D48",
204
+ },
205
+ {
206
+ id: "enrich_company:headcount",
207
+ label: "Headcount",
208
+ description: "Current employee count and size band.",
209
+ entity: "company",
210
+ group: "company",
211
+ kind: "enrichment",
212
+ rank: 110,
213
+ aliases: ["headcount", "employee count", "how many employees", "company size", "employees"],
214
+ inputShapes: ["domain", "linkedin_url"],
215
+ outputFields: [f("company_headcount", "Headcount", "company.employee_count", "numeric"), f("company_employee_range", "Size band", "company.employee_range", "text", true)],
216
+ surfaces: { preset: "company_enrich", companyFields: ["headcount"], managedFunction: "company_employee_count", catalogEntry: "preset:company_enrich", registryFunction: "enrichment", registryIntent: "enrich_company", registryInputShapes: ["field:headcount"] },
217
+ status: "default",
218
+ decision: "D48",
219
+ },
220
+ {
221
+ id: "enrich_company:industry",
222
+ label: "Industry",
223
+ description: "The company's industry classification.",
224
+ entity: "company",
225
+ group: "company",
226
+ kind: "enrichment",
227
+ rank: 120,
228
+ aliases: ["company industry", "which industry", "sector", "what industry"],
229
+ inputShapes: ["domain", "linkedin_url"],
230
+ outputFields: [f("company_industry", "Industry", "company.industry")],
231
+ surfaces: { preset: "company_enrich", companyFields: ["industry"], managedFunction: "company_industry", catalogEntry: "preset:company_enrich", registryFunction: "enrichment", registryIntent: "enrich_company", registryInputShapes: ["field:industry"] },
232
+ status: "default",
233
+ decision: "D48",
234
+ },
235
+ {
236
+ id: "enrich_company:company_profile",
237
+ label: "Company profile",
238
+ description: "Description, founded year, headquarters, company type, specialties, size band and logo from one profile lookup.",
239
+ entity: "company",
240
+ group: "company",
241
+ kind: "profile",
242
+ rank: 130,
243
+ aliases: ["company profile", "company description", "founded year", "headquarters", "company type", "specialties", "company logo", "firmographics"],
244
+ inputShapes: ["domain", "linkedin_url"],
245
+ outputFields: [
246
+ f("company_profile", "Company profile", null, "jsonb"),
247
+ f("company_description", "Description", "company.description", "text", true),
248
+ f("company_founded_year", "Founded year", "company.founded_year", "numeric", true),
249
+ f("company_hq_address", "HQ address", "company.hq_location", "text", true),
250
+ f("company_hq_country", "HQ country", "company.hq_country", "text", true),
251
+ f("company_type", "Company type", null, "text", true),
252
+ f("company_specialties", "Specialties", "company.specialties", "jsonb", true),
253
+ f("company_logo_url", "Logo", "company.logo_url", "text", true),
254
+ ],
255
+ surfaces: { preset: "company_enrich", companyFields: ["company_profile", "description", "founded_year", "hq_address", "hq_country", "company_type", "specialties", "logo_url"], managedFunction: "enrich_company", catalogEntry: "preset:company_enrich", registryFunction: "enrichment", registryIntent: "enrich_company", registryInputShapes: ["field:company_profile"] },
256
+ status: "default",
257
+ decision: "D48",
258
+ },
259
+ {
260
+ id: "enrich_company:funding",
261
+ label: "Funding",
262
+ description: "Total raised, latest round with type, amount and date, number of rounds and named investors.",
263
+ entity: "company",
264
+ group: "company",
265
+ kind: "enrichment",
266
+ rank: 140,
267
+ aliases: ["funding", "funding round", "last round", "latest round", "raised", "investors", "series a", "seed round", "total funding", "fundraising"],
268
+ inputShapes: ["domain"],
269
+ outputFields: [
270
+ f("company_funding", "Funding", null, "jsonb"),
271
+ f("company_funding_stage", "Funding stage", null, "text", true),
272
+ f("company_latest_funding_round", "Latest round", null, "jsonb", true),
273
+ f("company_total_funding", "Total funding", "company.funding_raised", "numeric", true),
274
+ f("company_funding_rounds_count", "Rounds", null, "numeric", true),
275
+ f("company_investors", "Investors", null, "jsonb", true),
276
+ ],
277
+ // Not in the company bundle (founder, 2026-09-24: one profile lookup, no
278
+ // opt-in fields); a caller names the fields on `find company`.
279
+ surfaces: { find: "find company --domain <domain> --fields funding,funding_stage,latest_funding_round,total_funding,funding_rounds_count,investors", companyFields: ["funding", "funding_stage", "latest_funding_round", "total_funding", "funding_rounds_count", "investors"], managedFunction: "company_total_funding", registryFunction: "enrichment", registryIntent: "enrich_company", registryInputShapes: ["field:funding"] },
280
+ status: "default",
281
+ decision: "D49",
282
+ },
283
+ {
284
+ id: "enrich_company:technologies",
285
+ label: "Tech stack",
286
+ description: "Technologies the company runs: from its job postings with confidence and first/last-seen dates, or from its website; plus a yes/no check for technologies you name.",
287
+ entity: "company",
288
+ group: "company",
289
+ kind: "enrichment",
290
+ rank: 150,
291
+ aliases: ["tech stack", "technologies they use", "technographics", "tools they use", "software they use", "built with"],
292
+ inputShapes: ["domain"],
293
+ outputFields: [f("company_technologies", "Technologies", "company.technologies", "jsonb"), f("company_technology_check", "Uses named technology", null, "boolean", true)],
294
+ surfaces: { preset: "company_tech_stack", companyFields: ["technologies", "technology_check"], managedFunction: "company_tech_stack", catalogEntry: "preset:company_tech_stack", registryFunction: "enrichment", registryIntent: "enrich_company", registryInputShapes: ["field:technologies"] },
295
+ status: "default",
296
+ decision: "D49",
297
+ },
298
+ {
299
+ id: "enrich_company:hiring_signals",
300
+ label: "Hiring signals",
301
+ description: "Open roles at the company: how many and which.",
302
+ entity: "company",
303
+ group: "company",
304
+ kind: "enrichment",
305
+ rank: 160,
306
+ aliases: ["open roles", "job openings", "are they hiring", "is hiring", "job postings", "hiring signals"],
307
+ inputShapes: ["domain", "linkedin_url"],
308
+ outputFields: [f("company_hiring_signals", "Hiring signals", null, "jsonb"), f("company_job_openings", "Open roles", null, "numeric", true)],
309
+ // Not in the company bundle (founder, 2026-09-24).
310
+ surfaces: { find: "find company --domain <domain> --fields hiring_signals,job_openings", companyFields: ["hiring_signals", "job_openings"], managedFunction: "company_job_openings", registryFunction: "enrichment", registryIntent: "enrich_company", registryInputShapes: ["field:hiring_signals"] },
311
+ status: "default",
312
+ },
313
+ {
314
+ id: "enrich_company:competitors",
315
+ label: "Competitors",
316
+ description: "Named competitors of the company.",
317
+ entity: "company",
318
+ group: "company",
319
+ kind: "enrichment",
320
+ rank: 170,
321
+ aliases: ["their competitors", "competitors of", "who competes with", "named competitors"],
322
+ inputShapes: ["domain"],
323
+ outputFields: [f("company_competitors", "Competitors", null, "jsonb")],
324
+ surfaces: {},
325
+ status: "coming_soon",
326
+ statusReason: "Coming soon (D51, 2026-09-19): the catalogue ships the default chains customers need most first; this data type returns once its lane is measured.",
327
+ decision: "D51",
328
+ candidateProviders: ["leadmagic"],
329
+ },
330
+ {
331
+ id: "enrich_company:corporate_structure",
332
+ label: "Corporate structure",
333
+ description: "Parent company and subsidiaries.",
334
+ entity: "company",
335
+ group: "company",
336
+ kind: "enrichment",
337
+ rank: 180,
338
+ aliases: ["parent company", "subsidiaries", "corporate structure", "owned by", "holding"],
339
+ inputShapes: ["domain"],
340
+ outputFields: [
341
+ f("company_corporate_structure", "Corporate structure", null, "jsonb"),
342
+ f("company_parent_company", "Parent company", null, "text", true),
343
+ f("company_subsidiaries", "Subsidiaries", null, "jsonb", true),
344
+ ],
345
+ surfaces: {},
346
+ status: "coming_soon",
347
+ statusReason: "Coming soon (D51, 2026-09-19): the catalogue ships the default chains customers need most first; this data type returns once its lane is measured.",
348
+ decision: "D51",
349
+ candidateProviders: ["discolike"],
350
+ },
351
+ {
352
+ id: "enrich_company:acquisitions",
353
+ label: "Acquisitions",
354
+ description: "Companies it acquired and acquisition events it was part of.",
355
+ entity: "company",
356
+ group: "company",
357
+ kind: "enrichment",
358
+ rank: 190,
359
+ aliases: ["acquisitions", "acquired", "m&a", "mergers"],
360
+ inputShapes: ["domain"],
361
+ outputFields: [f("company_acquisitions", "Acquisitions", null, "jsonb")],
362
+ surfaces: {},
363
+ status: "coming_soon",
364
+ statusReason: "Coming soon (D51, 2026-09-19): the catalogue ships the default chains customers need most first; this data type returns once its lane is measured.",
365
+ decision: "D51",
366
+ candidateProviders: ["predictleads", "leadmagic"],
367
+ },
368
+ {
369
+ id: "enrich_company:news",
370
+ label: "Recent news",
371
+ description: "Dated company announcements: launches, expansion, leadership, layoffs.",
372
+ entity: "company",
373
+ group: "company",
374
+ kind: "enrichment",
375
+ rank: 200,
376
+ aliases: ["recent news", "company news", "announcements", "press mentions", "what happened recently"],
377
+ inputShapes: ["domain"],
378
+ outputFields: [f("company_news", "News", null, "jsonb")],
379
+ surfaces: {},
380
+ status: "coming_soon",
381
+ statusReason: "Coming soon (D51, 2026-09-19): the catalogue ships the default chains customers need most first; this data type returns once its lane is measured.",
382
+ decision: "D51",
383
+ candidateProviders: ["predictleads", "leadmagic"],
384
+ },
385
+ {
386
+ id: "enrich_company:web_presence",
387
+ label: "Web presence",
388
+ description: "Crunchbase, GitHub, Facebook, Instagram and YouTube pages for the company.",
389
+ entity: "company",
390
+ group: "company",
391
+ kind: "enrichment",
392
+ rank: 210,
393
+ aliases: ["crunchbase url", "github url", "facebook page", "instagram page", "youtube channel", "social links"],
394
+ inputShapes: ["name", "domain"],
395
+ outputFields: [
396
+ f("company_crunchbase_url", "Crunchbase", null),
397
+ f("company_github_url", "GitHub", null),
398
+ f("company_facebook_url", "Facebook", "company.facebook_url"),
399
+ f("company_instagram_url", "Instagram", "company.instagram_url"),
400
+ f("company_youtube_url", "YouTube", null),
401
+ ],
402
+ surfaces: {},
403
+ status: "coming_soon",
404
+ statusReason: "Coming soon (D51, 2026-09-19): the catalogue ships the default chains customers need most first; this data type returns once its lane is measured.",
405
+ decision: "D51",
406
+ candidateProviders: ["serper"],
407
+ },
408
+ {
409
+ id: "domain_check",
410
+ label: "Domain status",
411
+ description: "Whether a domain is live, redirecting, parked or dead, reused free for 30 days.",
412
+ entity: "company",
413
+ group: "company",
414
+ kind: "enrichment",
415
+ rank: 220,
416
+ aliases: ["domain status", "is the website live", "dead domain", "parked domain"],
417
+ inputShapes: ["domain"],
418
+ outputFields: [f("domain_check", "Domain status", null), f("domain_check_label", "Domain status (label)", null, "text", true)],
419
+ surfaces: {},
420
+ status: "coming_soon",
421
+ statusReason: "Coming soon (D51, 2026-09-19): the free domain-status check leaves the catalogue until the default chains are re-measured.",
422
+ decision: "D51",
423
+ },
424
+ {
425
+ id: "company_revenue_estimate",
426
+ label: "Revenue (estimate)",
427
+ description: "Estimated annual revenue as a figure or a range; no source reports exact revenue for private companies.",
428
+ entity: "company",
429
+ group: "company",
430
+ kind: "enrichment",
431
+ rank: 230,
432
+ aliases: ["revenue", "annual revenue", "revenue estimate", "how much do they make", "arr", "reported revenue", "exact revenue", "financials", "company revenue"],
433
+ inputShapes: ["domain"],
434
+ outputFields: [f("company_revenue", "Revenue (estimate)", "company.revenue")],
435
+ // Not in the company bundle (founder, 2026-09-24).
436
+ surfaces: { find: "find company --domain <domain> --fields revenue", companyFields: ["revenue"], managedFunction: "company_revenue_estimate", registryFunction: "enrichment", registryIntent: "enrich_company", registryInputShapes: ["field:revenue"] },
437
+ status: "default",
438
+ decision: "D50",
439
+ },
440
+ {
441
+ id: "company_revenue_exact",
442
+ label: "Revenue (reported)",
443
+ description: "Reported revenue figures.",
444
+ entity: "company",
445
+ group: "company",
446
+ kind: "enrichment",
447
+ rank: 240,
448
+ aliases: [],
449
+ inputShapes: ["domain"],
450
+ outputFields: [],
451
+ surfaces: {},
452
+ status: "rejected",
453
+ statusReason: "Merged into Revenue (estimate): no provider in the catalogue reports exact revenue for private companies, so one Revenue row answers with a labelled estimate.",
454
+ decision: "D50",
455
+ },
456
+ {
457
+ id: "company_headcount_growth",
458
+ label: "Headcount growth",
459
+ description: "Employee growth over 6, 12 and 24 months.",
460
+ entity: "company",
461
+ group: "company",
462
+ kind: "enrichment",
463
+ rank: 250,
464
+ aliases: ["headcount growth", "employee growth", "growing team", "hiring velocity", "growth rate"],
465
+ inputShapes: ["domain"],
466
+ outputFields: [f("company_headcount_growth", "Headcount growth", null, "jsonb")],
467
+ surfaces: {},
468
+ status: "coming_soon",
469
+ statusReason: "Coming soon (D51, 2026-09-19): four wired providers document headcount growth; the default is being chosen.",
470
+ decision: "D34c",
471
+ candidateProviders: ["apollo", "peopledatalabs", "signalbase", "crustdata"],
472
+ },
473
+ {
474
+ id: "company_website_traffic",
475
+ label: "Website traffic",
476
+ description: "Estimated monthly visits to the company's website.",
477
+ entity: "company",
478
+ group: "company",
479
+ kind: "enrichment",
480
+ rank: 260,
481
+ aliases: ["website traffic", "monthly visits", "web traffic", "site visitors"],
482
+ inputShapes: ["domain"],
483
+ outputFields: [f("company_website_traffic", "Website traffic", null, "numeric")],
484
+ surfaces: { managedFunction: "website_traffic" },
485
+ status: "coming_soon",
486
+ statusReason: "Coming soon (D51, 2026-09-19): only a keyword-derived traffic estimate is wired today; a visits-grade source is being chosen.",
487
+ decision: "D34b",
488
+ candidateProviders: ["adyntel", "apollo"],
489
+ },
490
+ // ── Decision makers (search-shaped; the catalogue references them, it never adds a second chain) ──
491
+ {
492
+ id: "people_search:account_contacts",
493
+ label: "Decision makers at each account",
494
+ description: "Finds the right people at accounts you already hold, by role and seniority.",
495
+ entity: "person",
496
+ group: "decision_maker",
497
+ kind: "search",
498
+ rank: 300,
499
+ aliases: ["decision maker", "decision makers", "head of sales at", "people at these companies", "contacts at each account", "who should i contact", "find people at company"],
500
+ inputShapes: ["company_domain", "company_linkedin_url"],
501
+ outputFields: [f("full_name", "Full name", "person.full_name"), f("job_title", "Job title", "person.job_title"), f("linkedin_url", "LinkedIn URL", "person.linkedin_url")],
502
+ surfaces: { managedFunction: "find_people_at_company", registryFunction: "people_search", registryIntent: "account_contacts" },
503
+ status: "default",
504
+ },
505
+ {
506
+ id: "people_search:persona_search",
507
+ label: "People matching a persona",
508
+ description: "Builds a people list from title, seniority, department, location and company filters.",
509
+ entity: "person",
510
+ group: "decision_maker",
511
+ kind: "search",
512
+ rank: 310,
513
+ aliases: ["find people matching", "people search", "persona list", "prospect list", "leads matching"],
514
+ inputShapes: ["persona_filters"],
515
+ outputFields: [f("full_name", "Full name", "person.full_name"), f("job_title", "Job title", "person.job_title"), f("linkedin_url", "LinkedIn URL", "person.linkedin_url")],
516
+ surfaces: { registryFunction: "people_search", registryIntent: "persona_search" },
517
+ status: "default",
518
+ },
519
+ {
520
+ id: "people_search:reachable_persona",
521
+ label: "Reachable people",
522
+ description: "People matching a persona who already have an email or phone on file.",
523
+ entity: "person",
524
+ group: "decision_maker",
525
+ kind: "search",
526
+ rank: 320,
527
+ aliases: ["reachable", "with phone", "with email", "contactable people"],
528
+ inputShapes: ["persona_filters"],
529
+ outputFields: [f("full_name", "Full name", "person.full_name"), f("linkedin_url", "LinkedIn URL", "person.linkedin_url")],
530
+ surfaces: {},
531
+ status: "coming_soon",
532
+ statusReason: "Coming soon (D51, 2026-09-19): the catalogue ships the default chains customers need most first; this data type returns once its lane is measured.",
533
+ decision: "D51",
534
+ candidateProviders: ["contactout"],
535
+ },
536
+ {
537
+ id: "people_search:audience_sizing",
538
+ label: "Audience size",
539
+ description: "How many people match a persona before you buy any of them.",
540
+ entity: "person",
541
+ group: "decision_maker",
542
+ kind: "search",
543
+ rank: 330,
544
+ aliases: ["how many people", "audience size", "count people", "market size people"],
545
+ inputShapes: ["persona_filters"],
546
+ outputFields: [f("audience_count", "Matching people", null, "numeric")],
547
+ surfaces: {},
548
+ status: "coming_soon",
549
+ statusReason: "Coming soon (D51, 2026-09-19): the catalogue ships the default chains customers need most first; this data type returns once its lane is measured.",
550
+ decision: "D51",
551
+ candidateProviders: ["blitzapi"],
552
+ },
553
+ {
554
+ id: "company_search:find_lookalikes",
555
+ label: "Lookalike companies",
556
+ description: "Companies that resemble the accounts you already hold.",
557
+ entity: "company",
558
+ group: "decision_maker",
559
+ kind: "search",
560
+ rank: 340,
561
+ aliases: ["lookalike", "similar companies", "companies like", "more like these"],
562
+ inputShapes: ["seed_accounts"],
563
+ outputFields: [f("company_name", "Company", "company.name"), f("company_domain", "Domain", "company.domain")],
564
+ surfaces: {},
565
+ status: "coming_soon",
566
+ statusReason: "Coming soon (D51, 2026-09-19): the catalogue ships the default chains customers need most first; this data type returns once its lane is measured.",
567
+ decision: "D51",
568
+ candidateProviders: ["leadmagic"],
569
+ },
570
+ // ── Signals (streams and watch-lists; referenced) ─────────────────────────
571
+ {
572
+ id: "signals:funding_events",
573
+ label: "Funding events",
574
+ description: "Companies that raised a round, market-wide or across a watch-list.",
575
+ entity: "company",
576
+ group: "signal",
577
+ kind: "signal",
578
+ rank: 400,
579
+ aliases: ["just raised", "funding events", "new funding", "raised a round", "fundraising signal"],
580
+ inputShapes: ["market", "watch_list"],
581
+ outputFields: [f("event", "Funding event", null, "jsonb")],
582
+ surfaces: {},
583
+ status: "coming_soon",
584
+ statusReason: "Coming soon (D51, 2026-09-19): the catalogue ships the default chains customers need most first; this data type returns once its lane is measured.",
585
+ decision: "D51",
586
+ candidateProviders: ["predictleads"],
587
+ },
588
+ {
589
+ id: "signals:hiring_surge",
590
+ label: "Hiring surge",
591
+ description: "Companies actively hiring for given roles, departments or locations.",
592
+ entity: "company",
593
+ group: "signal",
594
+ kind: "signal",
595
+ rank: 410,
596
+ aliases: ["hiring surge", "companies hiring", "hiring for", "new job posts"],
597
+ inputShapes: ["market", "watch_list"],
598
+ outputFields: [f("event", "Hiring event", null, "jsonb")],
599
+ surfaces: {},
600
+ status: "coming_soon",
601
+ statusReason: "Coming soon (D51, 2026-09-19): the catalogue ships the default chains customers need most first; this data type returns once its lane is measured.",
602
+ decision: "D51",
603
+ candidateProviders: ["blitzapi", "theirstack"],
604
+ },
605
+ {
606
+ id: "signals:tech_adoption",
607
+ label: "Technology adoption",
608
+ description: "Companies using or newly adopting a named technology.",
609
+ entity: "company",
610
+ group: "signal",
611
+ kind: "signal",
612
+ rank: 420,
613
+ aliases: ["tech adoption", "started using", "companies using", "adopted"],
614
+ inputShapes: ["market", "watch_list"],
615
+ outputFields: [f("event", "Technology event", null, "jsonb")],
616
+ surfaces: {},
617
+ status: "coming_soon",
618
+ statusReason: "Coming soon (D51, 2026-09-19): the catalogue ships the default chains customers need most first; this data type returns once its lane is measured.",
619
+ decision: "D51",
620
+ candidateProviders: ["predictleads", "theirstack"],
621
+ },
622
+ {
623
+ id: "signals:news_events",
624
+ label: "News events",
625
+ description: "Dated company news across a market or a watch-list.",
626
+ entity: "company",
627
+ group: "signal",
628
+ kind: "signal",
629
+ rank: 430,
630
+ aliases: ["news events", "company news feed", "announcements feed"],
631
+ inputShapes: ["market", "watch_list"],
632
+ outputFields: [f("event", "News event", null, "jsonb")],
633
+ surfaces: {},
634
+ status: "coming_soon",
635
+ statusReason: "Coming soon (D51, 2026-09-19): the catalogue ships the default chains customers need most first; this data type returns once its lane is measured.",
636
+ decision: "D51",
637
+ candidateProviders: ["predictleads"],
638
+ },
639
+ {
640
+ id: "signals:acquisition_events",
641
+ label: "Acquisition events",
642
+ description: "Companies acquired or acquiring in a time window.",
643
+ entity: "company",
644
+ group: "signal",
645
+ kind: "signal",
646
+ rank: 440,
647
+ aliases: ["acquisition events", "got acquired", "acquired companies", "m&a signal"],
648
+ inputShapes: ["market", "watch_list"],
649
+ outputFields: [f("event", "Acquisition event", null, "jsonb")],
650
+ surfaces: {},
651
+ status: "coming_soon",
652
+ statusReason: "Coming soon (D51, 2026-09-19): the catalogue ships the default chains customers need most first; this data type returns once its lane is measured.",
653
+ decision: "D51",
654
+ candidateProviders: ["predictleads"],
655
+ },
656
+ {
657
+ id: "signals:job_change",
658
+ label: "Job changes",
659
+ description: "People on a watch-list who changed role or company.",
660
+ entity: "person",
661
+ group: "signal",
662
+ kind: "signal",
663
+ rank: 450,
664
+ aliases: ["job change", "changed jobs", "new role", "left the company", "promoted"],
665
+ inputShapes: ["watch_list"],
666
+ outputFields: [f("event", "Job-change event", null, "jsonb")],
667
+ surfaces: {},
668
+ status: "coming_soon",
669
+ statusReason: "Coming soon (D51, 2026-09-19): the catalogue ships the default chains customers need most first; this data type returns once its lane is measured.",
670
+ decision: "D51",
671
+ candidateProviders: ["leadmagic"],
672
+ },
673
+ {
674
+ id: "signals:signal_monitor",
675
+ label: "Signal monitor",
676
+ description: "Re-runs any signal on a cadence over a watch-list and writes only new events, under a recurring credit cap.",
677
+ entity: "company",
678
+ group: "signal",
679
+ kind: "signal",
680
+ rank: 460,
681
+ aliases: ["watch these companies", "alert me when", "recurring signal", "signal monitor"],
682
+ inputShapes: ["watch_list"],
683
+ outputFields: [f("event", "Signal event", null, "jsonb")],
684
+ surfaces: {},
685
+ status: "coming_soon",
686
+ statusReason: "Coming soon (D51, 2026-09-19): the catalogue ships the default chains customers need most first; this data type returns once its lane is measured.",
687
+ decision: "D51",
688
+ },
689
+ // ── Rejected (kept so nobody re-adds them silently; pruning is never deletion) ──
690
+ {
691
+ id: "reverse_phone_lookup",
692
+ label: "Person from phone number",
693
+ description: "Identify a person from a phone number.",
694
+ entity: "person",
695
+ group: "contact",
696
+ kind: "enrichment",
697
+ rank: 900,
698
+ aliases: ["reverse phone", "who owns this number"],
699
+ inputShapes: ["phone"],
700
+ outputFields: [],
701
+ surfaces: {},
702
+ status: "rejected",
703
+ statusReason: "No managed provider and no customer demand recorded.",
704
+ decision: "D33",
705
+ },
706
+ {
707
+ id: "whatsapp_number_validation",
708
+ label: "WhatsApp number check",
709
+ description: "Whether a number is on WhatsApp.",
710
+ entity: "person",
711
+ group: "contact",
712
+ kind: "verification",
713
+ rank: 910,
714
+ aliases: ["whatsapp check", "on whatsapp"],
715
+ inputShapes: ["phone"],
716
+ outputFields: [],
717
+ surfaces: {},
718
+ status: "rejected",
719
+ statusReason: "No managed provider; WhatsApp reachability belongs to the messaging channel, not enrichment.",
720
+ decision: "D33",
721
+ },
722
+ {
723
+ id: "linkedin_engagement_score",
724
+ label: "LinkedIn engagement score",
725
+ description: "A score of how engaged a person is on LinkedIn.",
726
+ entity: "person",
727
+ group: "person",
728
+ kind: "signal",
729
+ rank: 920,
730
+ aliases: ["engagement score", "linkedin activity score"],
731
+ inputShapes: ["linkedin_url"],
732
+ outputFields: [],
733
+ surfaces: {},
734
+ status: "rejected",
735
+ statusReason: "Owned by Posts and Signals (engagement telemetry), not by enrichment.",
736
+ decision: "D33",
737
+ },
738
+ {
739
+ id: "is_hiring_as_enrichment",
740
+ label: "Is hiring (flag)",
741
+ description: "A yes/no hiring flag as an enrichment column.",
742
+ entity: "company",
743
+ group: "company",
744
+ kind: "enrichment",
745
+ rank: 930,
746
+ aliases: [],
747
+ inputShapes: ["domain"],
748
+ outputFields: [],
749
+ surfaces: {},
750
+ status: "rejected",
751
+ statusReason: "Covered by Hiring signals (open roles) and the Hiring surge signal; a bare flag would duplicate both.",
752
+ decision: "D33",
753
+ },
754
+ ];
755
+ const COST_STRUCTURE_WORDS = /\b(flat[- ]fee|unlimited|zero[- ]marginal|\$0 marginal|subscription)\b/i;
756
+ /** True when a customer-facing string leaks vendor cost structure (OXYGEN_PRODUCT.md 2026-08-10). */
757
+ export function leaksCostStructure(text) {
758
+ return COST_STRUCTURE_WORDS.test(text);
759
+ }
760
+ export function listEnrichmentIntents(filter) {
761
+ const statuses = filter?.status === undefined ? null : Array.isArray(filter.status) ? filter.status : [filter.status];
762
+ return ENRICHMENT_INTENT_TAXONOMY
763
+ .filter((row) => (filter?.group ? row.group === filter.group : true))
764
+ .filter((row) => (filter?.entity ? row.entity === filter.entity : true))
765
+ .filter((row) => (statuses ? statuses.includes(row.status) : true))
766
+ .sort((a, b) => a.rank - b.rank)
767
+ .map((row) => ({ ...row }));
768
+ }
769
+ export function getEnrichmentIntent(id) {
770
+ return ENRICHMENT_INTENT_TAXONOMY.find((row) => row.id === id) ?? null;
771
+ }
772
+ function pointerForRows(rows) {
773
+ const live = rows.filter((row) => row.status !== "rejected");
774
+ if (live.length === 0)
775
+ return null;
776
+ if (live.length === 1) {
777
+ const row = live[0];
778
+ return { intent: row.id, group: row.group, exact_command: `oxygen enrichment catalog --intent ${row.id}` };
779
+ }
780
+ const group = live[0].group;
781
+ return { intent: null, group, exact_command: `oxygen enrichment catalog --group ${group}` };
782
+ }
783
+ /** Catalogue pointer for a column-catalogue entry id such as `preset:company_enrich` or `capability:work_email`. */
784
+ export function catalogPointerForEntry(entryId) {
785
+ return pointerForRows(ENRICHMENT_INTENT_TAXONOMY.filter((row) => row.surfaces.catalogEntry === entryId));
786
+ }
787
+ /** Catalogue pointer for one company enrichment field key such as `technologies` or `latest_funding_round`. */
788
+ export function catalogPointerForCompanyField(field) {
789
+ return pointerForRows(ENRICHMENT_INTENT_TAXONOMY.filter((row) => row.surfaces.companyFields?.includes(field) === true));
790
+ }
791
+ export function findEnrichmentIntentByAlias(query) {
792
+ const normalized = query.toLowerCase().replace(/[^a-z0-9&'\s-]/g, " ").replace(/\s+/g, " ").trim();
793
+ // Match the phrase as typed and with plain plurals folded ("personal emails"
794
+ // -> "personal email"), so an alias never has to list both forms.
795
+ const forms = [` ${normalized} `, ` ${normalized.replace(/\b([a-z]{4,}?)(?:ies|s)\b/g, (word, stem) => (word.endsWith("ies") ? `${stem}y` : stem))} `];
796
+ let best = null;
797
+ for (const row of ENRICHMENT_INTENT_TAXONOMY) {
798
+ if (row.status === "rejected")
799
+ continue;
800
+ for (const alias of row.aliases) {
801
+ if (!forms.some((form) => form.includes(` ${alias} `)))
802
+ continue;
803
+ if (!best || alias.length > best.length || (alias.length === best.length && row.rank < best.row.rank)) {
804
+ best = { row, length: alias.length };
805
+ }
806
+ }
807
+ }
808
+ return best ? { ...best.row } : null;
809
+ }