orangeslice 2.0.5 → 2.1.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.
package/README.md CHANGED
@@ -64,11 +64,21 @@ const [companies, searchPage, ai] = await Promise.all([
64
64
  }
65
65
  })
66
66
  ]);
67
+
68
+ const startups = await services.crunchbase.search({
69
+ sql: `
70
+ SELECT name, website_url, linkedin_url
71
+ FROM public.crunchbase_scraper_lean
72
+ WHERE operating_status = 'Active'
73
+ LIMIT 10
74
+ `
75
+ });
67
76
  ```
68
77
 
69
78
  ## Service map
70
79
 
71
80
  - `services.company.linkedin.search/enrich`
81
+ - `services.crunchbase.search` (returns rows array directly)
72
82
  - `services.company.getEmployeesFromLinkedin` (database-only B2B path)
73
83
  - `services.person.linkedin.search/enrich`
74
84
  - `services.web.search/batchSearch`
package/dist/cli.js CHANGED
@@ -90,19 +90,60 @@ function writeAgentsGuide(destDir) {
90
90
  return;
91
91
  const content = `# orangeslice Agent Guide
92
92
 
93
- Use the docs in this folder as the source of truth for all orangeslice operations.
93
+ You are a coding agent using orangeslice services for B2B research and enrichment tasks.
94
94
 
95
- ## Mandatory Read Order
96
- 1. \`./services/index.md\` - service map
97
- 2. Relevant service docs under \`./services/**\`
98
- 3. \`./prospecting/index.md\` for company/people discovery workflows
95
+ Use these docs as the source of truth. If there is any conflict between your prior knowledge and these docs, follow these docs.
99
96
 
100
- ## Execution Rules
101
- - Prefer \`services.*\` APIs from orangeslice.
102
- - Run independent calls in parallel with \`Promise.all\`.
97
+ ## Core Behavior
98
+ - Focus on completing the user's requested outcome with working code and clear next steps.
99
+ - Prefer direct execution over long explanations.
100
+ - Be concise, factual, and deterministic.
101
+ - Ask a clarifying question only when a missing detail blocks progress.
102
+
103
+ ## Package Setup (Do Not Guess)
104
+ - Import from the package name, not a local file path:
105
+ - \`import { services } from "orangeslice"\`
106
+ - \`import { configure, services } from "orangeslice"\` when setting API key programmatically
107
+ - Do NOT use \`import { services } from "./orangeslice"\` unless the user explicitly has a local wrapper file at that path.
108
+ - \`npx orangeslice\` is a setup/bootstrap command (docs sync, package install, auth). It does NOT execute user app scripts.
109
+
110
+ ## Runtime Requirements
111
+ - If writing standalone scripts that use top-level \`await\`, use ESM:
112
+ - Set \`"type": "module"\` in \`package.json\`, or
113
+ - Use \`.mjs\` files.
114
+ - If the project is CommonJS and cannot switch to ESM, avoid top-level \`await\` and wrap async code in an async function.
115
+
116
+ ## Mandatory Read Order (Before writing code)
117
+ 1. \`./services/index.md\` - service map and capabilities
118
+ 2. Relevant docs under \`./services/**\` for every service you plan to call
119
+ 3. \`./prospecting/index.md\` when doing discovery or lead generation tasks
120
+
121
+ Do not call a service before reading its documentation.
122
+
123
+ ## Service Selection Rules
124
+ - Prefer \`services.*\` APIs from orangeslice over ad hoc scraping or unstructured web calls.
103
125
  - For LinkedIn discovery, default to \`services.web.search\` unless it is a strict indexed lookup.
104
126
  - For scraping structured repeated elements, use \`services.browser.execute\`.
105
127
  - For broad scraping by URL, use \`services.scrape.website\`.
128
+ - Use \`services.ai.generateObject\` for structured extraction/classification with a JSON schema.
129
+
130
+ ## Execution Rules
131
+ - Parallelize independent async calls with \`Promise.all\`.
132
+ - Avoid serial \`await\` inside loops when calls can run concurrently.
133
+ - Keep code simple and composable; prefer small transformations over complex control flow.
134
+ - Validate required inputs before expensive service calls.
135
+ - Return structured, machine-usable output whenever possible.
136
+
137
+ ## Reliability and Safety
138
+ - Do not invent service methods, params, or response shapes.
139
+ - If a call fails, report the likely cause and provide a concrete retry/fallback path.
140
+ - Do not expose secrets, API keys, or sensitive credentials in responses.
141
+ - Do not claim an action succeeded unless the result confirms it.
142
+
143
+ ## Response Style
144
+ - Briefly state what you are going to do, then do it.
145
+ - Summarize outputs and include only relevant details.
146
+ - When useful, provide a short "next actions" list.
106
147
  `;
107
148
  fs.writeFileSync(AGENTS_FILE, content, "utf8");
108
149
  }
@@ -0,0 +1,10 @@
1
+ export interface CrunchbaseSearchParams {
2
+ sql: string;
3
+ userId?: string;
4
+ }
5
+ /**
6
+ * Search the Crunchbase lean table using SQL.
7
+ *
8
+ * Returns rows directly (no envelope).
9
+ */
10
+ export declare function crunchbaseSearch<T = Record<string, unknown>>(params: CrunchbaseSearchParams): Promise<T[]>;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.crunchbaseSearch = crunchbaseSearch;
4
+ const api_1 = require("./api");
5
+ /**
6
+ * Search the Crunchbase lean table using SQL.
7
+ *
8
+ * Returns rows directly (no envelope).
9
+ */
10
+ async function crunchbaseSearch(params) {
11
+ const data = await (0, api_1.post)("/execute/crunchbase-sql", { sql: params.sql });
12
+ return data.rows ?? [];
13
+ }
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export { configure } from "./api";
2
2
  export type { OrangesliceConfig } from "./api";
3
3
  export { linkedinSearch } from "./b2b";
4
4
  export type { LinkedInSearchParams, LinkedInSearchResponse } from "./b2b";
5
+ export { crunchbaseSearch } from "./crunchbase";
6
+ export type { CrunchbaseSearchParams } from "./crunchbase";
5
7
  export { webSearch, webBatchSearch } from "./serp";
6
8
  export type { WebSearchQuery, WebSearchResult, WebSearchResponse, BatchWebSearchParams } from "./serp";
7
9
  export { generateObject } from "./generateObject";
@@ -21,12 +23,16 @@ export type { PersonLinkedinFindUrlParams, CompanyLinkedinFindUrlParams, PersonC
21
23
  import { runApifyActor } from "./apify";
22
24
  import { linkedinSearch } from "./b2b";
23
25
  import { browserExecute } from "./browser";
26
+ import { crunchbaseSearch } from "./crunchbase";
24
27
  import { personLinkedinEnrich, personLinkedinFindUrl, personContactGet, companyLinkedinEnrich, companyLinkedinFindUrl, companyGetEmployeesFromLinkedin, geoParseAddress, builtWithLookupDomain, builtWithRelationships, builtWithSearchByTech } from "./expansion";
25
28
  import { scrapeWebsite } from "./firecrawl";
26
29
  import { generateObject } from "./generateObject";
27
30
  import { googleMapsScrape } from "./googleMaps";
28
31
  import { webBatchSearch, webSearch } from "./serp";
29
32
  export declare const services: {
33
+ crunchbase: {
34
+ search: typeof crunchbaseSearch;
35
+ };
30
36
  company: {
31
37
  linkedin: {
32
38
  findUrl: typeof companyLinkedinFindUrl;
package/dist/index.js CHANGED
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.services = exports.builtWithSearchByTech = exports.builtWithRelationships = exports.builtWithLookupDomain = exports.geoParseAddress = exports.companyGetEmployeesFromLinkedin = exports.companyLinkedinFindUrl = exports.companyLinkedinEnrich = exports.personContactGet = exports.personLinkedinFindUrl = exports.personLinkedinEnrich = exports.PREDICT_LEADS_OPERATION_IDS = exports.predictLeads = exports.executePredictLeads = exports.googleMapsScrape = exports.runApifyActor = exports.browserExecute = exports.scrapeWebsite = exports.generateObject = exports.webBatchSearch = exports.webSearch = exports.linkedinSearch = exports.configure = void 0;
3
+ exports.services = exports.builtWithSearchByTech = exports.builtWithRelationships = exports.builtWithLookupDomain = exports.geoParseAddress = exports.companyGetEmployeesFromLinkedin = exports.companyLinkedinFindUrl = exports.companyLinkedinEnrich = exports.personContactGet = exports.personLinkedinFindUrl = exports.personLinkedinEnrich = exports.PREDICT_LEADS_OPERATION_IDS = exports.predictLeads = exports.executePredictLeads = exports.googleMapsScrape = exports.runApifyActor = exports.browserExecute = exports.scrapeWebsite = exports.generateObject = exports.webBatchSearch = exports.webSearch = exports.crunchbaseSearch = exports.linkedinSearch = exports.configure = void 0;
4
4
  var api_1 = require("./api");
5
5
  Object.defineProperty(exports, "configure", { enumerable: true, get: function () { return api_1.configure; } });
6
6
  var b2b_1 = require("./b2b");
7
7
  Object.defineProperty(exports, "linkedinSearch", { enumerable: true, get: function () { return b2b_1.linkedinSearch; } });
8
+ var crunchbase_1 = require("./crunchbase");
9
+ Object.defineProperty(exports, "crunchbaseSearch", { enumerable: true, get: function () { return crunchbase_1.crunchbaseSearch; } });
8
10
  var serp_1 = require("./serp");
9
11
  Object.defineProperty(exports, "webSearch", { enumerable: true, get: function () { return serp_1.webSearch; } });
10
12
  Object.defineProperty(exports, "webBatchSearch", { enumerable: true, get: function () { return serp_1.webBatchSearch; } });
@@ -36,6 +38,7 @@ Object.defineProperty(exports, "builtWithSearchByTech", { enumerable: true, get:
36
38
  const apify_2 = require("./apify");
37
39
  const b2b_2 = require("./b2b");
38
40
  const browser_2 = require("./browser");
41
+ const crunchbase_2 = require("./crunchbase");
39
42
  const expansion_2 = require("./expansion");
40
43
  const firecrawl_2 = require("./firecrawl");
41
44
  const generateObject_2 = require("./generateObject");
@@ -43,6 +46,9 @@ const googleMaps_2 = require("./googleMaps");
43
46
  const predictLeads_2 = require("./predictLeads");
44
47
  const serp_2 = require("./serp");
45
48
  exports.services = {
49
+ crunchbase: {
50
+ search: crunchbase_2.crunchbaseSearch
51
+ },
46
52
  company: {
47
53
  linkedin: {
48
54
  findUrl: expansion_2.companyLinkedinFindUrl,
@@ -9,4 +9,4 @@ Typed functions for Gmail actions powered by Orange Slice Google integrations.
9
9
  ## Email
10
10
 
11
11
  - `integrations.gmail.sendEmail(input)` - Send an email through the connected Gmail account
12
- - Heavy rate limit: `sendEmail` is capped at **20 calls/day** per connected Gmail account
12
+ - Heavy rate limit: `sendEmail` is capped at **40 calls/day** per connected Gmail account
@@ -2,7 +2,7 @@
2
2
 
3
3
  Send an email from the connected Gmail account.
4
4
 
5
- > Rate limit note for AI: `integrations.gmail.sendEmail(...)` is heavily rate-limited to **20 calls/day** per connected Gmail account. Use sparingly and batch/aggregate where possible.
5
+ > Rate limit note for AI: `integrations.gmail.sendEmail(...)` is heavily rate-limited to **40 calls/day** per connected Gmail account. Use sparingly and batch/aggregate where possible.
6
6
 
7
7
  ```typescript
8
8
  // Basic email
@@ -25,18 +25,18 @@ await integrations.gmail.sendEmail({
25
25
 
26
26
  ## Input
27
27
 
28
- | Parameter | Type | Required | Description |
29
- | ------------------ | ----------- | -------- | ----------- |
30
- | `recipient_email` | `string` | No\* | Primary `To` recipient |
31
- | `extra_recipients` | `string[]` | No | Additional `To` recipients |
32
- | `cc` | `string[]` | No | CC recipients |
33
- | `bcc` | `string[]` | No | BCC recipients |
34
- | `subject` | `string` | No\* | Email subject |
35
- | `body` | `string` | No\* | Email body (plain text or HTML) |
36
- | `is_html` | `boolean` | No | Set to `true` when body is HTML |
37
- | `from_email` | `string` | No | Optional verified send-as alias |
38
- | `attachment` | `object` | No | Optional attachment payload |
39
- | `user_id` | `string` | No | Gmail user id (`"me"` by default) |
28
+ | Parameter | Type | Required | Description |
29
+ | ------------------ | ---------- | -------- | --------------------------------- |
30
+ | `recipient_email` | `string` | No\* | Primary `To` recipient |
31
+ | `extra_recipients` | `string[]` | No | Additional `To` recipients |
32
+ | `cc` | `string[]` | No | CC recipients |
33
+ | `bcc` | `string[]` | No | BCC recipients |
34
+ | `subject` | `string` | No\* | Email subject |
35
+ | `body` | `string` | No\* | Email body (plain text or HTML) |
36
+ | `is_html` | `boolean` | No | Set to `true` when body is HTML |
37
+ | `from_email` | `string` | No | Optional verified send-as alias |
38
+ | `attachment` | `object` | No | Optional attachment payload |
39
+ | `user_id` | `string` | No | Gmail user id (`"me"` by default) |
40
40
 
41
41
  \*Gmail requires at least one recipient (`recipient_email`, `cc`, or `bcc`) and at least one of `subject` or `body`.
42
42
 
@@ -16,6 +16,7 @@ description: Strategies for searching or finding people and companies. This is a
16
16
  Run queries with built-in filters when the criteria is searchable:
17
17
 
18
18
  - **Web search (`services.web.search`)** — **Default for LinkedIn**. Use for keywords, niche queries, fuzzy matching, anything descriptive.
19
+ - **Crunchbase (`services.crunchbase.search`)** — **Default for funding data**. Use for funding-stage, round type, amount, date windows, and investor-backed company discovery.
19
20
  - **LinkedIn B2B DB** — **Indexed lookups ONLY:** company by domain/slug/ID, employees at a known company (by company_id), basic funding (2-table join). Everything else = web search. See [QUICK_REF](./linkedin_data/QUICK_REF.md).
20
21
  - **Google Maps** — industry, location, ratings
21
22
  - **LinkedIn job search** — job filters, titles
@@ -83,11 +84,11 @@ When using qualification columns, think Circle & Star:
83
84
  | Source | Use When | Limitations |
84
85
  | ------------------------ | ----------------------------------------------------------- | -------------------------------------------------------- |
85
86
  | **Web Search (Default)** | **Everything else** — keywords, niche, fuzzy, specific | Requires verification columns for false positives. |
87
+ | **Crunchbase (Funding Default)** | Funding-focused prospecting: stage, round type, amount, recency, investors | Best for funding intelligence; use other sources for non-funding discovery criteria. |
86
88
  | **PredictLeads** | Company intelligence, buying signals, and structured company events at scale | Coverage varies by company/market; use web search for very niche long-tail discovery. |
87
89
  | **Niche Directory Scrape** | Well-defined categories with existing lists (see below) | Requires finding the right directory first. |
88
90
  | **LinkedIn B2B DB** | **Indexed lookups ONLY:** company by domain/slug/ID, employees at known company, basic 2-table funding. | **3s hard max. No keyword search, no LATERAL, no 3+ table joins.** Everything else = web search. |
89
91
  | **Google Maps** | Local/SMB, physical locations, restaurants, retail | Limited to businesses with physical presence. |
90
- | **NPI Database** | Healthcare providers | Healthcare only. Free. |
91
92
  | **Apify Actors** | Platform-specific scraping (Instagram, TikTok, job boards) | Per-platform setup. May break with platform changes. |
92
93
 
93
94
  ### PredictLeads: When It Is Better Than Everything Else
@@ -105,6 +106,12 @@ Prefer other sources when:
105
106
  - You need local storefront/SMB discovery -> use Google Maps
106
107
  - You need fast indexed LinkedIn lookups by known IDs/domain/company -> use LinkedIn B2B DB
107
108
 
109
+ ### Funding Prospecting Standard: Use Crunchbase First
110
+
111
+ For any request centered on funding data (for example: "Series A fintech companies", "companies that raised in the last 12 months", "recently funded startups"), use `services.crunchbase.search` as the **standard/default source**.
112
+
113
+ Use LinkedIn B2B DB funding joins only when the user explicitly needs a LinkedIn-only workflow or a narrow lookup tied to existing LinkedIn records. Otherwise, Crunchbase should be the first choice for funding-oriented discovery.
114
+
108
115
  ### Niche Directory Scraping — For Well-Defined Categories
109
116
 
110
117
  When users ask for companies in a **specific, well-defined niche** (e.g., "fast food chains", "Fortune 500 companies", "Y Combinator startups"), the best approach is often to **find and scrape a curated directory or list**.
@@ -260,27 +267,28 @@ Don't overthink it — just create 2-3 views that match the columns you built. S
260
267
 
261
268
  ## Examples
262
269
 
263
- | User Request | Approach | Why |
264
- | -------------------------------------------- | ---------------- | ------------------------------------------------------------------- |
265
- | "AI CRM companies" | Web search | Keyword query → `"AI CRM" site:linkedin.com/company` |
266
- | "Fintech startups" | Web search | Fuzzy/descriptive → `"fintech" "startup" site:linkedin.com/company` |
267
- | "SDRs at Series A companies" | Web search | Specific criteria → `"SDR" "Series A" site:linkedin.com/in` |
268
- | "Companies using Kubernetes" | Web search | Technology match `"Kubernetes" site:linkedin.com/company` |
269
- | "VPs who worked at Google" | Web search | Fuzzy history match → `"VP" "Google" site:linkedin.com/in` |
270
- | "1000 software engineers in Bay Area" | B2B DB | Simple title + location + high volume |
271
- | "All healthcare companies 100-500 employees" | B2B DB | Industry + size + high volume |
272
- | "Fast food chains that..." | Directory scrape | Scrape Wikipedia list `browser.execute` |
273
- | "Restaurants in Austin" | Google Maps | Local/SMB with physical presence |
274
- | "Companies hiring SDRs" | LinkedIn Jobs | Job search with title filter |
275
- | "Warehouses implementing WMS" | Circle + columns | Pull logistics companies add "WMS Score" column |
276
- | "Companies that recently switched CRMs" | Circle + columns | Pull SaaS companies → add "CRM Change Signals" column |
270
+ | User Request | Approach | Why |
271
+ | -------------------------------------------- | ---------------- | --------------------------------------------------------------------------- |
272
+ | "AI CRM companies" | Web search | Keyword query → `"AI CRM" site:linkedin.com/company` |
273
+ | "Fintech startups" | Web search | Fuzzy/descriptive → `"fintech" "startup" site:linkedin.com/company` |
274
+ | "SDRs at Series A companies" | Web search | Specific criteria → `"SDR" "Series A" site:linkedin.com/in` |
275
+ | "Series A/B companies raised last year" | Crunchbase | Funding-specific discovery is best handled via `services.crunchbase.search` |
276
+ | "Companies using Kubernetes" | Web search | Technology match → `"Kubernetes" site:linkedin.com/company` |
277
+ | "VPs who worked at Google" | Web search | Fuzzy history match `"VP" "Google" site:linkedin.com/in` |
278
+ | "1000 software engineers in Bay Area" | B2B DB | Simple title + location + high volume |
279
+ | "All healthcare companies 100-500 employees" | B2B DB | Industry + size + high volume |
280
+ | "Fast food chains that..." | Directory scrape | Scrape Wikipedia list → `browser.execute` |
281
+ | "Restaurants in Austin" | Google Maps | Local/SMB with physical presence |
282
+ | "Companies hiring SDRs" | LinkedIn Jobs | Job search with title filter |
283
+ | "Warehouses implementing WMS" | Circle + columns | Pull logistics companies → add "WMS Score" column |
284
+ | "Companies that recently switched CRMs" | Circle + columns | Pull SaaS companies → add "CRM Change Signals" column |
277
285
 
278
286
  ---
279
287
 
280
288
  ## Tools
281
289
 
282
290
  - **LinkedIn:** `services.company.linkedin.search({ sql: "SELECT ... FROM linkedin_company ..." })`, `services.person.linkedin.search({ sql: "SELECT ... FROM linkedin_profile ..." })` — **Lookup tool only, 3s max, 2-table joins max. Use web search for anything else.**
283
- - **Healthcare:** `healthcare.npi`
291
+ - **Funding:** `services.crunchbase.search({ sql: "SELECT ... FROM ... WHERE ..." })` — **Default for funding search and screening.**
284
292
  - **Local/SMB:** `googleMaps.scrape`
285
293
  - **Web:** `web.search` + `browser.execute`
286
294
  - **Platforms:** `services.apify.runActor`
@@ -17,8 +17,9 @@ type runActor = (params: {
17
17
 
18
18
  ## Credits & Pricing
19
19
 
20
- **Credits: variable (custom). ~5 credits reserved per expected item. Settled at exact `usageTotalUsd × 500`.**
20
+ **Credits: variable (custom). Reserved based on estimated items + compute. Settled at exact `usageTotalUsd × 500`.**
21
21
 
22
+ - **Reservation:** Tiered on `datasetListParams.limit` — first 1000 items at 5 credits ($0.01), beyond 1000 at 1 credit ($0.002). Minimum 50 credits.
22
23
  - **Allowed pricing models:** `FREE`, `PRICE_PER_DATASET_ITEM`, `PAY_PER_EVENT`
23
24
  - **Blocked:** `FLAT_PRICE_PER_MONTH` (rental actors) — will throw an error
24
25
  - **Credits conversion:** $0.002 per credit (e.g., $0.01 = 5 credits)
@@ -390,6 +390,38 @@ WHERE lc.company_size = '51-200 employees'
390
390
  - **Use `lc` alias** for company tables
391
391
  - **Default to US**: `lc.country_code = 'US'`
392
392
 
393
+ ## Return Type
394
+
395
+ `services.company.linkedin.search()` returns an object envelope:
396
+
397
+ ```typescript
398
+ {
399
+ rows: (Record < string, unknown > []);
400
+ count: number;
401
+ }
402
+ ```
403
+
404
+ - `rows`: Result rows from your SQL query, with exactly the columns you selected.
405
+ - `count`: Number of rows returned in `rows`.
406
+
407
+ Example:
408
+
409
+ ```typescript
410
+ const searchResult = await services.company.linkedin.search({
411
+ sql: `
412
+ SELECT
413
+ lc.company_name,
414
+ lc.domain,
415
+ 'https://www.linkedin.com/company/' || lc.universal_name AS lc_linkedin_url
416
+ FROM linkedin_company lc
417
+ WHERE lc.domain = 'stripe.com'
418
+ LIMIT 1
419
+ `
420
+ });
421
+
422
+ return searchResult.rows; // Most spreadsheet snippets should return rows
423
+ ```
424
+
393
425
  ---
394
426
 
395
427
  ## Table Aliases