orangeslice 2.1.5 → 2.4.0-beta.0

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 (37) hide show
  1. package/README.md +20 -1
  2. package/dist/careers.d.ts +47 -0
  3. package/dist/careers.js +11 -0
  4. package/dist/cli.js +12 -1
  5. package/dist/index.d.ts +41 -0
  6. package/dist/index.js +29 -3
  7. package/dist/integrations.d.ts +60 -0
  8. package/dist/integrations.js +107 -0
  9. package/dist/ocean.d.ts +160 -0
  10. package/dist/ocean.js +23 -0
  11. package/dist/skills.d.ts +57 -0
  12. package/dist/skills.js +33 -0
  13. package/docs/data-enrichement/index.md +10 -2
  14. package/docs/integrations/gmail/createDraft.md +54 -0
  15. package/docs/integrations/gmail/fetchEmails.md +50 -0
  16. package/docs/integrations/gmail/fetchMessageByMessageId.md +36 -0
  17. package/docs/integrations/gmail/fetchMessageByThreadId.md +37 -0
  18. package/docs/integrations/gmail/getProfile.md +37 -0
  19. package/docs/integrations/gmail/index.md +19 -2
  20. package/docs/integrations/gmail/listLabels.md +34 -0
  21. package/docs/integrations/gmail/replyToThread.md +51 -0
  22. package/docs/integrations/index.md +14 -1
  23. package/docs/lookalike-search/index.md +24 -12
  24. package/docs/prospecting/index.md +2 -2
  25. package/docs/services/builtWith/index.md +2 -2
  26. package/docs/services/company/findCareersPage.md +137 -0
  27. package/docs/services/company/findCareersPage.ts +37 -0
  28. package/docs/services/company/linkedin/enrich.md +47 -1
  29. package/docs/services/company/scrapeCareersPage.md +150 -0
  30. package/docs/services/index.md +4 -2
  31. package/docs/services/integrations/index.md +128 -0
  32. package/docs/services/ocean/search/companies.ts +122 -119
  33. package/docs/services/person/linkedin/findUrl.md +2 -2
  34. package/docs/services/predictLeads/companyJobOpenings.ts +168 -94
  35. package/docs/services/skills/index.md +97 -0
  36. package/docs/services/web/search.md +29 -14
  37. package/package.json +1 -1
@@ -0,0 +1,137 @@
1
+ # Find Company Careers Page
2
+
3
+ Resolve a company's official careers page and, when possible, return the underlying ATS jobs page instead.
4
+
5
+ This is best when you have a company website or a specific company page and want the canonical place to browse jobs.
6
+
7
+ ## Input Parameters
8
+
9
+ Provide **one** of:
10
+
11
+ | Parameter | Type | Required | Description |
12
+ | --------- | -------- | -------- | ------------------------------------------------------------------ |
13
+ | `website` | `string` | No | Company website or page URL, e.g. `stripe.com` or `https://ro.co/` |
14
+ | `url` | `string` | No | Alias for `website` |
15
+
16
+ **Optional:**
17
+
18
+ | Parameter | Type | Required | Description |
19
+ | --------- | -------- | -------- | ------------------------------------ |
20
+ | `timeout` | `string` | No | Batch timeout override, e.g. `"30m"` |
21
+
22
+ ## Output
23
+
24
+ ```typescript
25
+ {
26
+ inputUrl: string;
27
+ normalizedWebsiteUrl: string;
28
+ careerPageUrl: string | null;
29
+ pageType: "ats" | "official" | "not_found";
30
+ atsProvider: string | null;
31
+ detectionMethod:
32
+ | "input-ats"
33
+ | "homepage-ats-link"
34
+ | "homepage-careers-link"
35
+ | "deterministic-candidate"
36
+ | "candidate-ats-link"
37
+ | "embedded-ats"
38
+ | "candidate-redirect"
39
+ | "ats-unverified"
40
+ | "not-found";
41
+ checkedUrls: string[];
42
+ }
43
+ ```
44
+
45
+ ## Examples
46
+
47
+ ### Basic Careers Lookup
48
+
49
+ ```typescript
50
+ const result = await services.company.findCareersPage({
51
+ website: row.website
52
+ });
53
+
54
+ return result.careerPageUrl;
55
+ ```
56
+
57
+ ### Prefer ATS When Available
58
+
59
+ ```typescript
60
+ const result = await services.company.findCareersPage({
61
+ website: "https://plaid.com"
62
+ });
63
+
64
+ return {
65
+ url: result.careerPageUrl,
66
+ type: result.pageType,
67
+ ats: result.atsProvider
68
+ };
69
+ ```
70
+
71
+ ### Handle Not Found
72
+
73
+ ```typescript
74
+ const result = await services.company.findCareersPage({
75
+ website: row.website
76
+ });
77
+
78
+ if (result.pageType === "not_found") {
79
+ return null;
80
+ }
81
+
82
+ return result.careerPageUrl;
83
+ ```
84
+
85
+ ### Debug Why a Result Was Chosen
86
+
87
+ ```typescript
88
+ const result = await services.company.findCareersPage({
89
+ website: row.website
90
+ });
91
+
92
+ return {
93
+ careerPageUrl: result.careerPageUrl,
94
+ pageType: result.pageType,
95
+ atsProvider: result.atsProvider,
96
+ detectionMethod: result.detectionMethod,
97
+ checkedUrls: result.checkedUrls
98
+ };
99
+ ```
100
+
101
+ ## What It Detects
102
+
103
+ - Official careers pages like `https://company.com/careers`
104
+ - Careers subdomains like `https://careers.company.com/`
105
+ - ATS boards when discoverable from the company site
106
+ - Embedded/wrapped ATS pages when the company site hosts the jobs UI directly
107
+
108
+ Common ATS providers currently recognized include:
109
+
110
+ - `ashby`
111
+ - `greenhouse`
112
+ - `lever`
113
+ - `workday`
114
+ - `icims`
115
+ - `gem`
116
+ - `kula`
117
+ - `breezy`
118
+ - `bamboohr`
119
+ - `rippling`
120
+ - `personio`
121
+ - `phenom`
122
+ - `smartrecruiters`
123
+ - `successfactors`
124
+ - `jobvite`
125
+ - `recruitee`
126
+ - `teamtailor`
127
+ - `indeed`
128
+ - `bestjobs`
129
+ - `ejobs`
130
+
131
+ ## Key Rules
132
+
133
+ 1. **Pass the company website when possible** - homepage/company URLs usually produce the best canonical result.
134
+ 2. **ATS is preferred over generic careers pages** - if the company site clearly points to an ATS board, that board is returned.
135
+ 3. **Deep location/provider pages can still work** - the resolver attempts to collapse some subdomains and detail pages back to the parent organization's careers site.
136
+ 4. **`pageType: "official"` is still a success** - many enterprises host jobs on branded careers portals instead of a third-party ATS URL.
137
+ 5. **Use `checkedUrls` for debugging** - when a result looks wrong or missing, inspect the visited candidates.
@@ -0,0 +1,37 @@
1
+ interface FindCareersPageResult {
2
+ /** The original website or URL input */
3
+ inputUrl: string;
4
+ /** Canonical homepage/base URL used during discovery */
5
+ normalizedWebsiteUrl: string;
6
+ /** Best careers page URL found, or null when none was found */
7
+ careerPageUrl: string | null;
8
+ /** Whether the result points to an ATS board, an official careers page, or nothing */
9
+ pageType: "ats" | "official" | "not_found";
10
+ /** ATS provider when pageType is "ats" */
11
+ atsProvider: string | null;
12
+ /** How the page was discovered */
13
+ detectionMethod:
14
+ | "input-ats"
15
+ | "homepage-ats-link"
16
+ | "homepage-careers-link"
17
+ | "deterministic-candidate"
18
+ | "candidate-ats-link"
19
+ | "embedded-ats"
20
+ | "candidate-redirect"
21
+ | "ats-unverified"
22
+ | "not-found";
23
+ /** URLs checked while searching */
24
+ checkedUrls: string[];
25
+ }
26
+
27
+ /**
28
+ * Find the best careers page for a company website.
29
+ * Accepts a homepage URL/domain and returns either a canonical ATS board URL
30
+ * or an official careers page on the company site.
31
+ */
32
+ type findCareersPage = (params: {
33
+ /** Company website or homepage URL */
34
+ website?: string;
35
+ /** Alias for website. Provide website or url. */
36
+ url?: string;
37
+ }) => Promise<FindCareersPageResult>;
@@ -56,7 +56,15 @@ interface B2BCompany {
56
56
  }
57
57
  ```
58
58
 
59
- > Note: company industry coverage in the LinkedIn B2B DB can be sparse. `industry` and `industries` may be `null`, generic, or missing even when the company record exists, so do not rely on them as the only company classification signal.
59
+ > Important: LinkedIn company `industry` / `industries` coverage in the B2B DB is very sparse and often too weak for enrichment. These fields may be `null`, generic, stale, or missing even when the company record exists. Treat them as lookup metadata only, not as a high-confidence classification source for enrichment workflows.
60
+ >
61
+ > Preferred pattern for enrichment/classification:
62
+ >
63
+ > 1. Start from the company `domain` when available
64
+ > 2. `services.scrape.website(...)` the company site or a relevant subpage
65
+ > 3. `services.ai.generateObject(...)` to classify the company from the scraped content
66
+ >
67
+ > Use LinkedIn enrich primarily for fast lookup fields like company identity, URL, headcount, location, and description. Do **not** build industry enrichment pipelines that depend mainly on LinkedIn `industry`.
60
68
 
61
69
  ### Extended (`extended: true`) - `B2BCompanyExtended`
62
70
 
@@ -282,6 +290,44 @@ return {
282
290
  };
283
291
  ```
284
292
 
293
+ ### Classify Industry from Domain, Not LinkedIn
294
+
295
+ If your goal is enrichment or categorization, prefer the company website over LinkedIn `industry`:
296
+
297
+ ```typescript
298
+ const company = await services.company.linkedin.enrich({
299
+ domain: row.domain
300
+ });
301
+
302
+ const { markdown } = await services.scrape.website({
303
+ url: `https://${row.domain}`
304
+ });
305
+
306
+ const { object } = await services.ai.generateObject({
307
+ prompt: `
308
+ Classify this company based on its website content.
309
+
310
+ Do not rely on LinkedIn industry because it is sparse and often too generic.
311
+ Use LinkedIn only as lightweight context for identity verification.
312
+
313
+ Domain: ${row.domain}
314
+ LinkedIn name: ${company?.name ?? "unknown"}
315
+ LinkedIn description: ${company?.description ?? "unknown"}
316
+
317
+ Website content:
318
+ ${markdown}
319
+ `,
320
+ schema: z.object({
321
+ industry: z.string().nullable(),
322
+ subindustry: z.string().nullable(),
323
+ businessModel: z.string().nullable(),
324
+ confidence: z.enum(["low", "medium", "high"])
325
+ })
326
+ });
327
+
328
+ return object;
329
+ ```
330
+
285
331
  ### Handle Missing Companies
286
332
 
287
333
  ```typescript
@@ -0,0 +1,150 @@
1
+ # Scrape ATS Careers Page
2
+
3
+ Extract a standardized list of jobs from a supported **official ATS-hosted** careers page without using a browser when possible.
4
+
5
+ This is best when you already have an ATS careers page URL, or when you first resolved one with `services.company.findCareersPage` and now want the actual jobs.
6
+
7
+ ## Input Parameters
8
+
9
+ Provide **one** of:
10
+
11
+ | Parameter | Type | Required | Description |
12
+ | ---------------- | -------- | -------- | ----------------------------------------------------------------------------------------------- |
13
+ | `careersPageUrl` | `string` | No | Official ATS board URL or ATS job/detail URL, e.g. `https://job-boards.greenhouse.io/anthropic` |
14
+ | `url` | `string` | No | Alias for `careersPageUrl` |
15
+
16
+ **Optional:**
17
+
18
+ | Parameter | Type | Required | Description |
19
+ | --------- | -------- | -------- | ------------------------------------ |
20
+ | `timeout` | `string` | No | Batch timeout override, e.g. `"30m"` |
21
+
22
+ ## Output
23
+
24
+ ```typescript
25
+ {
26
+ status: "success" | "unsupported_url" | "unsupported_provider";
27
+ inputUrl: string;
28
+ normalizedBoardUrl: string | null;
29
+ atsProvider: string | null;
30
+ companyName: string | null;
31
+ source: "api" | "html" | null;
32
+ totalJobs: number;
33
+ jobs: Array<{
34
+ id: string;
35
+ title: string;
36
+ url: string;
37
+ applyUrl: string | null;
38
+ location: string | null;
39
+ locations: string[];
40
+ department: string | null;
41
+ team: string | null;
42
+ employmentType: string | null;
43
+ workplaceType: string | null;
44
+ postedAt: string | null;
45
+ postedText: string | null;
46
+ requisitionId: string | null;
47
+ }>;
48
+ checkedUrls: string[];
49
+ supportedProviders: string[];
50
+ message: string | null;
51
+ }
52
+ ```
53
+
54
+ ## Examples
55
+
56
+ ### Scrape Jobs From a Known ATS Board
57
+
58
+ ```typescript
59
+ const result = await services.company.scrapeCareersPage({
60
+ careersPageUrl: "https://job-boards.greenhouse.io/anthropic"
61
+ });
62
+
63
+ return result.jobs;
64
+ ```
65
+
66
+ ### Resolve Then Scrape
67
+
68
+ ```typescript
69
+ const careers = await services.company.findCareersPage({
70
+ website: row.website
71
+ });
72
+
73
+ if (!careers.careerPageUrl || careers.pageType !== "ats") {
74
+ return [];
75
+ }
76
+
77
+ const jobs = await services.company.scrapeCareersPage({
78
+ careersPageUrl: careers.careerPageUrl
79
+ });
80
+
81
+ return jobs.jobs;
82
+ ```
83
+
84
+ ### Return Lightweight Job Summaries
85
+
86
+ ```typescript
87
+ const result = await services.company.scrapeCareersPage({
88
+ careersPageUrl: row.careers_page
89
+ });
90
+
91
+ return result.jobs.map((job) => ({
92
+ title: job.title,
93
+ location: job.location,
94
+ department: job.department,
95
+ url: job.url
96
+ }));
97
+ ```
98
+
99
+ ### Handle Unsupported Providers Gracefully
100
+
101
+ ```typescript
102
+ const result = await services.company.scrapeCareersPage({
103
+ careersPageUrl: row.careers_page
104
+ });
105
+
106
+ if (result.status !== "success") {
107
+ return {
108
+ status: result.status,
109
+ provider: result.atsProvider,
110
+ message: result.message
111
+ };
112
+ }
113
+
114
+ return result.totalJobs;
115
+ ```
116
+
117
+ ### Pass a Job Detail URL
118
+
119
+ ```typescript
120
+ const result = await services.company.scrapeCareersPage({
121
+ careersPageUrl: "https://jobs.lever.co/mistral/2a357282-9d44-4b41-a249-c75ffe878ce2"
122
+ });
123
+
124
+ return {
125
+ board: result.normalizedBoardUrl,
126
+ jobs: result.totalJobs
127
+ };
128
+ ```
129
+
130
+ ## Supported Providers
131
+
132
+ Current browser-free implementations:
133
+
134
+ - `ashby`
135
+ - `breezy`
136
+ - `greenhouse`
137
+ - `lever`
138
+ - `recruitee`
139
+ - `rippling`
140
+ - `smartrecruiters`
141
+ - `workable`
142
+ - `workday`
143
+
144
+ ## Key Rules
145
+
146
+ 1. **Use this for official ATS pages** - this endpoint is not meant for generic `company.com/careers` pages unless they are clearly hosted by a supported ATS.
147
+ 2. **Prefer resolving first when starting from a company website** - use `services.company.findCareersPage` to find the canonical ATS URL, then pass that into this scraper.
148
+ 3. **Job/detail URLs are okay** - supported ATS detail URLs are normalized back to the board before scraping.
149
+ 4. **Treat `unsupported_provider` as expected** - it means the input was a recognized ATS, but this scraper does not implement that provider yet.
150
+ 5. **Use `checkedUrls` for debugging** - when counts or mappings look off, inspect the URLs that were actually queried.
@@ -1,7 +1,7 @@
1
1
  - **ai**: AI helpers (summaries, classifications, scoring).
2
2
  - **apify**: Run any of 10,000+ Apify actors for web scraping, social media, e-commerce, and more.
3
3
  - **browser**: Kernel browser automation - spin up cloud browsers, execute Playwright code, take screenshots. **Use this for scraping structured lists of repeated data** (e.g., product listings, search results, table rows) where you know the DOM structure. Also ideal for **intercepting network requests** to discover underlying APIs, then paginate those APIs directly in your code (faster & cheaper than clicking through pages). Perfect for JS-heavy sites that don't work with simple HTTP scraping.
4
- - **company**: company data (getting employees at the company, getting company data, getting open jobs).
4
+ - **company**: company data (getting employees at the company, finding careers pages, getting company data, getting open jobs).
5
5
  - **crunchbase**: SQL search over the lean Crunchbase company table (`public.crunchbase_scraper_lean`) for startup prospecting.
6
6
  - **person**: finding a persons linkedin url, enriching it from linkedin, contact info, and searching for specific people / groups on linkedin
7
7
  - **geo**: parsing address
@@ -9,5 +9,7 @@
9
9
  - **email**: send transactional notification emails through Orange Slice's managed sender.
10
10
  - **scrape**: website scraper, sitemap scraper
11
11
  - **web**: SERP
12
- - **predictLeads**: company intelligence datasets (financing events, technologies, products, job openings, news, and related company data).
12
+ - **predictLeads**: company intelligence datasets (financing events, technologies, products, job openings, news, and related company data). Use these as prospecting/enrichment signals, not source-of-truth validation for whether a known company is hiring right now.
13
+ - **integrations**: connect and manage third-party integrations (HubSpot, Salesforce, Attio, Gmail, Slack, Instantly, HeyReach). Use `integrations.connect(provider)` to open the browser OAuth flow from a script, or `integrations.create(...)` for API-key providers.
14
+ - **skills**: create and manage knowledge skills — reusable knowledge snippets (ICP, templates, product info) that guide AI agents.
13
15
  - **guides**: agent notes & operational docs (see [Error Handling Cheatsheet](../error-handling-cheatsheet.md))
@@ -0,0 +1,128 @@
1
+ ---
2
+ description: Connect and manage third-party integrations (HubSpot, Salesforce, Attio, Gmail, Slack, Instantly, HeyReach)
3
+ ---
4
+
5
+ # integrations — Integration Management
6
+
7
+ Connect, list, and manage third-party service integrations. Supports both OAuth providers (HubSpot, Salesforce, Attio, Gmail, Slack) and API-key providers (Instantly, HeyReach).
8
+
9
+ ## Quick start
10
+
11
+ ```typescript
12
+ import { integrations } from "orangeslice";
13
+
14
+ // Connect an OAuth provider (opens browser for authorization)
15
+ const hubspot = await integrations.connect("hubspot");
16
+ console.log(`Connected: ${hubspot.displayName}`);
17
+
18
+ // List all connected integrations
19
+ const { integrations: list } = await integrations.list();
20
+ console.log(list.map(i => `${i.provider}: ${i.displayName}`));
21
+
22
+ // Connect an API-key provider programmatically (no browser)
23
+ await integrations.create({
24
+ provider: "instantly",
25
+ apiKey: "inst_abc123...",
26
+ displayName: "My Instantly"
27
+ });
28
+ ```
29
+
30
+ ## Methods
31
+
32
+ ### `integrations.connect(provider, opts?)`
33
+
34
+ Opens the user's browser to complete OAuth authorization (or API key entry for Instantly/HeyReach). Polls until the user finishes, then returns the connected integration.
35
+
36
+ This is the recommended way to connect any integration from a script or agent.
37
+
38
+ **Parameters:**
39
+ - `provider` — One of: `"hubspot"`, `"salesforce"`, `"attio"`, `"gmail"`, `"slack"`, `"instantly"`, `"heyreach"`
40
+ - `opts.noBrowser` — If `true`, prints the URL instead of auto-opening the browser
41
+
42
+ **Returns:** `Integration` object
43
+
44
+ ```typescript
45
+ const salesforce = await integrations.connect("salesforce");
46
+ // Browser opens -> user authorizes -> returns when complete
47
+ ```
48
+
49
+ ### `integrations.list(opts?)`
50
+
51
+ List connected integrations for the current account.
52
+
53
+ **Parameters:**
54
+ - `opts.spreadsheetId` — Filter to a specific spreadsheet's integrations
55
+ - `opts.provider` — Filter by provider name
56
+
57
+ **Returns:** `{ integrations: Integration[] }`
58
+
59
+ ```typescript
60
+ const { integrations: all } = await integrations.list();
61
+ const { integrations: crms } = await integrations.list({ provider: "hubspot" });
62
+ ```
63
+
64
+ ### `integrations.get(id)`
65
+
66
+ Get a single integration by ID.
67
+
68
+ **Returns:** `Integration`
69
+
70
+ ### `integrations.create(opts)`
71
+
72
+ Programmatically create an API-key integration without opening a browser. Only works for API-key providers (`instantly`, `heyreach`). For OAuth providers, use `connect()` instead.
73
+
74
+ **Parameters:**
75
+ - `opts.provider` — `"instantly"` or `"heyreach"`
76
+ - `opts.apiKey` — The provider API key
77
+ - `opts.displayName` — Optional display name
78
+ - `opts.config` — Optional key-value config
79
+ - `opts.spreadsheetId` — Optional, scope to a specific spreadsheet
80
+
81
+ **Returns:** `Integration`
82
+
83
+ ### `integrations.update(id, fields)`
84
+
85
+ Update an existing integration.
86
+
87
+ **Parameters:**
88
+ - `fields.apiKey` — New API key
89
+ - `fields.displayName` — New display name
90
+ - `fields.isActive` — Enable/disable
91
+ - `fields.config` — New config object
92
+
93
+ **Returns:** `Integration`
94
+
95
+ ### `integrations.delete(id)`
96
+
97
+ Delete an integration by ID.
98
+
99
+ **Returns:** `{ success: boolean }`
100
+
101
+ ## Integration object
102
+
103
+ ```typescript
104
+ {
105
+ id: string;
106
+ provider: string; // "hubspot", "salesforce", etc.
107
+ displayName: string; // "HubSpot portal acme.hubspot.com"
108
+ isActive: boolean;
109
+ hasApiKey: boolean; // true if API key is set (key itself is never returned)
110
+ hasOauthToken: boolean; // true if OAuth token is set
111
+ createdAt: string;
112
+ updatedAt: string;
113
+ scope: "account" | "spreadsheet";
114
+ spreadsheetId: string | null;
115
+ }
116
+ ```
117
+
118
+ ## Supported providers
119
+
120
+ | Provider | Auth Type | Connect method |
121
+ | ----------- | --------- | ---------------------- |
122
+ | HubSpot | OAuth | `connect("hubspot")` |
123
+ | Salesforce | OAuth | `connect("salesforce")`|
124
+ | Attio | OAuth | `connect("attio")` |
125
+ | Gmail | OAuth | `connect("gmail")` |
126
+ | Slack | OAuth | `connect("slack")` |
127
+ | Instantly | API Key | `connect("instantly")` or `create(...)` |
128
+ | HeyReach | API Key | `connect("heyreach")` or `create(...)` |