orangeslice 1.8.6-beta.0 → 2.0.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.
- package/dist/api.d.ts +1 -5
- package/dist/api.js +20 -25
- package/dist/apify.js +1 -1
- package/dist/b2b.js +1 -2
- package/dist/browser.js +1 -1
- package/dist/cli.js +0 -0
- package/dist/expansion.d.ts +0 -9
- package/dist/expansion.js +10 -22
- package/dist/firecrawl.js +16 -1
- package/dist/generateObject.js +5 -1
- package/dist/googleMaps.js +1 -2
- package/dist/serp.js +4 -2
- package/docs/prospecting/index.md +21 -7
- package/docs/services/ai/generateObject.ts +2 -2
- package/docs/services/person/contact/get.ts +1 -0
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -3,8 +3,4 @@ export interface OrangesliceConfig {
|
|
|
3
3
|
baseUrl?: string;
|
|
4
4
|
}
|
|
5
5
|
export declare function configure(opts: OrangesliceConfig): void;
|
|
6
|
-
|
|
7
|
-
direct?: boolean;
|
|
8
|
-
}
|
|
9
|
-
export declare function post<T>(functionId: string, payload: Record<string, unknown>, options?: PostOptions): Promise<T>;
|
|
10
|
-
export {};
|
|
6
|
+
export declare function post<T>(endpoint: string, payload: Record<string, unknown>): Promise<T>;
|
package/dist/api.js
CHANGED
|
@@ -3,13 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.configure = configure;
|
|
4
4
|
exports.post = post;
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
6
|
+
* Transport layer for orangeslice SDK.
|
|
7
|
+
* Calls batch-service execute routes directly with API key auth.
|
|
8
|
+
* Polls /function/result/:requestId for async operations.
|
|
9
9
|
*/
|
|
10
|
-
const DEFAULT_BASE_URL = "https://enrichly-production.up.railway.app
|
|
10
|
+
const DEFAULT_BASE_URL = "https://enrichly-production.up.railway.app";
|
|
11
11
|
const POLL_TIMEOUT_MS = 600000;
|
|
12
12
|
const DEFAULT_POLL_INTERVAL_MS = 1000;
|
|
13
|
+
const DEFAULT_INLINE_WAIT_MS = 5000;
|
|
13
14
|
const _config = {};
|
|
14
15
|
function configure(opts) {
|
|
15
16
|
if (opts.apiKey !== undefined)
|
|
@@ -18,7 +19,7 @@ function configure(opts) {
|
|
|
18
19
|
_config.baseUrl = opts.baseUrl;
|
|
19
20
|
}
|
|
20
21
|
function resolveBaseUrl() {
|
|
21
|
-
return _config.baseUrl || process.env.ORANGESLICE_BASE_URL || DEFAULT_BASE_URL;
|
|
22
|
+
return (_config.baseUrl || process.env.ORANGESLICE_BASE_URL || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
22
23
|
}
|
|
23
24
|
function resolveApiKey() {
|
|
24
25
|
return _config.apiKey || process.env.ORANGESLICE_API_KEY || "";
|
|
@@ -65,18 +66,14 @@ function isPendingResponse(body) {
|
|
|
65
66
|
return false;
|
|
66
67
|
return body.pending === true;
|
|
67
68
|
}
|
|
68
|
-
function
|
|
69
|
+
function resolvePollUrl(baseUrl, pending) {
|
|
69
70
|
if (pending.pollUrl) {
|
|
70
71
|
return new URL(pending.pollUrl, baseUrl).toString();
|
|
71
72
|
}
|
|
72
73
|
if (!pending.requestId) {
|
|
73
74
|
throw new Error("[orangeslice] pending response missing requestId");
|
|
74
75
|
}
|
|
75
|
-
|
|
76
|
-
const basePath = url.pathname.replace(/\/function\/?$/, "");
|
|
77
|
-
url.pathname = `${basePath}/function/result/${pending.requestId}`.replace(/\/{2,}/g, "/");
|
|
78
|
-
url.search = "";
|
|
79
|
-
return url.toString();
|
|
76
|
+
return `${baseUrl}/function/result/${pending.requestId}`;
|
|
80
77
|
}
|
|
81
78
|
async function fetchWithRedirect(url, init) {
|
|
82
79
|
let res = await fetch(url, { ...init, redirect: "manual" });
|
|
@@ -88,8 +85,8 @@ async function fetchWithRedirect(url, init) {
|
|
|
88
85
|
}
|
|
89
86
|
return res;
|
|
90
87
|
}
|
|
91
|
-
async function
|
|
92
|
-
const pollUrl =
|
|
88
|
+
async function pollUntilComplete(baseUrl, endpoint, pending) {
|
|
89
|
+
const pollUrl = resolvePollUrl(baseUrl, pending);
|
|
93
90
|
const timeoutAt = Date.now() + POLL_TIMEOUT_MS;
|
|
94
91
|
let pollAfterMs = typeof pending.pollAfterMs === "number" && pending.pollAfterMs > 0
|
|
95
92
|
? pending.pollAfterMs
|
|
@@ -105,26 +102,25 @@ async function pollBatchUntilComplete(baseUrl, functionId, pending) {
|
|
|
105
102
|
}
|
|
106
103
|
if (!res.ok) {
|
|
107
104
|
const message = asErrorMessage(data) || JSON.stringify(data);
|
|
108
|
-
throw new Error(`[orangeslice] ${
|
|
105
|
+
throw new Error(`[orangeslice] ${endpoint}: ${res.status} ${message}`);
|
|
109
106
|
}
|
|
110
107
|
const message = asErrorMessage(data);
|
|
111
108
|
if (message) {
|
|
112
|
-
throw new Error(`[orangeslice] ${
|
|
109
|
+
throw new Error(`[orangeslice] ${endpoint}: ${message}`);
|
|
113
110
|
}
|
|
114
111
|
return data;
|
|
115
112
|
}
|
|
116
|
-
throw new Error(`[orangeslice] ${
|
|
113
|
+
throw new Error(`[orangeslice] ${endpoint}: polling timed out after ${POLL_TIMEOUT_MS}ms`);
|
|
117
114
|
}
|
|
118
|
-
async function post(
|
|
119
|
-
void options;
|
|
115
|
+
async function post(endpoint, payload) {
|
|
120
116
|
const baseUrl = resolveBaseUrl();
|
|
121
117
|
const apiKey = resolveApiKey();
|
|
122
118
|
if (!apiKey) {
|
|
123
119
|
throw new Error("[orangeslice] No API key configured. " +
|
|
124
120
|
"Set ORANGESLICE_API_KEY in your environment or call configure({ apiKey: 'osk_...' }).");
|
|
125
121
|
}
|
|
126
|
-
const url = `${baseUrl}
|
|
127
|
-
const body = JSON.stringify(payload);
|
|
122
|
+
const url = `${baseUrl}${endpoint}`;
|
|
123
|
+
const body = JSON.stringify({ ...payload, inlineWaitMs: DEFAULT_INLINE_WAIT_MS });
|
|
128
124
|
const headers = {
|
|
129
125
|
"Content-Type": "application/json",
|
|
130
126
|
Authorization: `Bearer ${apiKey}`
|
|
@@ -135,18 +131,17 @@ async function post(functionId, payload, options = {}) {
|
|
|
135
131
|
body
|
|
136
132
|
});
|
|
137
133
|
if (!res.ok) {
|
|
138
|
-
let message = `${res.status}`;
|
|
139
134
|
const data = await readResponseBody(res);
|
|
140
|
-
message = asErrorMessage(data) || (typeof data === "string" ? data : JSON.stringify(data));
|
|
141
|
-
throw new Error(`[orangeslice] ${
|
|
135
|
+
const message = asErrorMessage(data) || (typeof data === "string" ? data : JSON.stringify(data));
|
|
136
|
+
throw new Error(`[orangeslice] ${endpoint}: ${res.status} ${message}`);
|
|
142
137
|
}
|
|
143
138
|
const data = await readResponseBody(res);
|
|
144
139
|
if (isPendingResponse(data)) {
|
|
145
|
-
return
|
|
140
|
+
return pollUntilComplete(baseUrl, endpoint, data);
|
|
146
141
|
}
|
|
147
142
|
const errorMessage = asErrorMessage(data);
|
|
148
143
|
if (errorMessage) {
|
|
149
|
-
throw new Error(`[orangeslice] ${
|
|
144
|
+
throw new Error(`[orangeslice] ${endpoint}: ${errorMessage}`);
|
|
150
145
|
}
|
|
151
146
|
return data;
|
|
152
147
|
}
|
package/dist/apify.js
CHANGED
package/dist/b2b.js
CHANGED
|
@@ -11,8 +11,7 @@ const api_1 = require("./api");
|
|
|
11
11
|
* });
|
|
12
12
|
*/
|
|
13
13
|
async function linkedinSearch(params) {
|
|
14
|
-
|
|
15
|
-
const data = await (0, api_1.post)("b2b", { ...params }, { direct: true });
|
|
14
|
+
const data = await (0, api_1.post)("/execute/sql", { sql: params.sql });
|
|
16
15
|
return {
|
|
17
16
|
rows: data.rows ?? [],
|
|
18
17
|
rowCount: data.rowCount ?? 0,
|
package/dist/browser.js
CHANGED
package/dist/cli.js
CHANGED
|
File without changes
|
package/dist/expansion.d.ts
CHANGED
|
@@ -61,17 +61,8 @@ export interface PersonContactGetResult {
|
|
|
61
61
|
}
|
|
62
62
|
export declare function personLinkedinEnrich(params: Record<string, unknown>): Promise<unknown>;
|
|
63
63
|
export declare function companyLinkedinEnrich(params: Record<string, unknown>): Promise<unknown>;
|
|
64
|
-
/**
|
|
65
|
-
* Find a LinkedIn person profile URL from name/title/company context.
|
|
66
|
-
*/
|
|
67
64
|
export declare function personLinkedinFindUrl(params: PersonLinkedinFindUrlParams): Promise<string | null>;
|
|
68
|
-
/**
|
|
69
|
-
* Find a LinkedIn company URL from website/company context.
|
|
70
|
-
*/
|
|
71
65
|
export declare function companyLinkedinFindUrl(params: CompanyLinkedinFindUrlParams): Promise<string | null>;
|
|
72
|
-
/**
|
|
73
|
-
* Run contact waterfall through Inngest and poll until completion.
|
|
74
|
-
*/
|
|
75
66
|
export declare function personContactGet(params: PersonContactGetParams): Promise<PersonContactGetResult>;
|
|
76
67
|
export declare function companyGetEmployeesFromLinkedin(params: CompanyGetEmployeesFromLinkedinParams): Promise<CompanyGetEmployeesFromLinkedinResult>;
|
|
77
68
|
export declare function geoParseAddress(params: Record<string, unknown>): Promise<unknown>;
|
package/dist/expansion.js
CHANGED
|
@@ -32,9 +32,6 @@ async function personLinkedinEnrich(params) {
|
|
|
32
32
|
if (!url && !username) {
|
|
33
33
|
throw new Error("[orangeslice] person.linkedin.enrich: provide url or username");
|
|
34
34
|
}
|
|
35
|
-
// Root cause fix:
|
|
36
|
-
// Use indexed slug_key64 lookup first, then join into lkd_profile by profile_id.
|
|
37
|
-
// This avoids full scans/timeouts on lkd_profile slug filters.
|
|
38
35
|
const sql = extended
|
|
39
36
|
? `SELECT lkd.*
|
|
40
37
|
FROM linkedin_profile_slug ps
|
|
@@ -63,7 +60,7 @@ async function personLinkedinEnrich(params) {
|
|
|
63
60
|
JOIN lkd_profile lkd ON lkd.profile_id = ps.linkedin_profile_id
|
|
64
61
|
WHERE ps.slug_key64 = key64(${sqlString(username)})
|
|
65
62
|
LIMIT 1`;
|
|
66
|
-
const data = await (0, api_1.post)("
|
|
63
|
+
const data = await (0, api_1.post)("/execute/sql", { sql });
|
|
67
64
|
return data.rows?.[0] ?? null;
|
|
68
65
|
}
|
|
69
66
|
async function companyLinkedinEnrich(params) {
|
|
@@ -85,41 +82,32 @@ async function companyLinkedinEnrich(params) {
|
|
|
85
82
|
? "*"
|
|
86
83
|
: "name, slug, website, description, employee_count, follower_count, founded_year, locality, region, country_iso, country_name, type, size, ticker, specialties, linkedin_url, created_at, updated_at";
|
|
87
84
|
const sql = `SELECT ${fields} FROM lkd_company WHERE ${where.join(" OR ")} LIMIT 1`;
|
|
88
|
-
const data = await (0, api_1.post)("
|
|
85
|
+
const data = await (0, api_1.post)("/execute/sql", { sql });
|
|
89
86
|
return data.rows?.[0] ?? null;
|
|
90
87
|
}
|
|
91
|
-
/**
|
|
92
|
-
* Find a LinkedIn person profile URL from name/title/company context.
|
|
93
|
-
*/
|
|
94
88
|
async function personLinkedinFindUrl(params) {
|
|
95
|
-
const url = await (0, api_1.post)("
|
|
89
|
+
const url = await (0, api_1.post)("/execute/linkedin-find-profile-url", params);
|
|
96
90
|
return typeof url === "string" && url.trim().length > 0 ? url : null;
|
|
97
91
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Find a LinkedIn company URL from website/company context.
|
|
100
|
-
*/
|
|
101
92
|
async function companyLinkedinFindUrl(params) {
|
|
102
|
-
const url = await (0, api_1.post)("
|
|
93
|
+
const url = await (0, api_1.post)("/execute/find-linkedin-company-url", params);
|
|
103
94
|
return typeof url === "string" && url.trim().length > 0 ? url : null;
|
|
104
95
|
}
|
|
105
|
-
/**
|
|
106
|
-
* Run contact waterfall through Inngest and poll until completion.
|
|
107
|
-
*/
|
|
108
96
|
async function personContactGet(params) {
|
|
109
|
-
return (0, api_1.post)("
|
|
97
|
+
return (0, api_1.post)("/execute/contact-waterfall", { ...params });
|
|
110
98
|
}
|
|
111
99
|
async function companyGetEmployeesFromLinkedin(params) {
|
|
112
|
-
return (0, api_1.post)("b2b-
|
|
100
|
+
return (0, api_1.post)("/execute/b2b-company-employees", params);
|
|
113
101
|
}
|
|
114
102
|
async function geoParseAddress(params) {
|
|
115
|
-
return (0, api_1.post)("
|
|
103
|
+
return (0, api_1.post)("/execute/parse-address", params);
|
|
116
104
|
}
|
|
117
105
|
async function builtWithLookupDomain(params) {
|
|
118
|
-
return (0, api_1.post)("
|
|
106
|
+
return (0, api_1.post)("/execute/builtwith/lookup-domain", params);
|
|
119
107
|
}
|
|
120
108
|
async function builtWithRelationships(params) {
|
|
121
|
-
return (0, api_1.post)("
|
|
109
|
+
return (0, api_1.post)("/execute/builtwith/relationships", params);
|
|
122
110
|
}
|
|
123
111
|
async function builtWithSearchByTech(params) {
|
|
124
|
-
return (0, api_1.post)("
|
|
112
|
+
return (0, api_1.post)("/execute/builtwith/search-by-tech", params);
|
|
125
113
|
}
|
package/dist/firecrawl.js
CHANGED
|
@@ -6,5 +6,20 @@ const api_1 = require("./api");
|
|
|
6
6
|
* services.scrape.website
|
|
7
7
|
*/
|
|
8
8
|
async function scrapeWebsite(params) {
|
|
9
|
-
|
|
9
|
+
const p = params.params ?? {};
|
|
10
|
+
return (0, api_1.post)("/execute/firecrawl", {
|
|
11
|
+
url: params.url,
|
|
12
|
+
limit: p.limit ?? 1,
|
|
13
|
+
scrapeOptions: {
|
|
14
|
+
formats: ["markdown", "links"],
|
|
15
|
+
timeout: 30000,
|
|
16
|
+
skipTlsVerification: true,
|
|
17
|
+
onlyMainContent: false,
|
|
18
|
+
removeBase64Images: true,
|
|
19
|
+
blockAds: true,
|
|
20
|
+
storeInCache: true,
|
|
21
|
+
maxAge: 172800000,
|
|
22
|
+
...p
|
|
23
|
+
}
|
|
24
|
+
});
|
|
10
25
|
}
|
package/dist/generateObject.js
CHANGED
|
@@ -21,5 +21,9 @@ const api_1 = require("./api");
|
|
|
21
21
|
* // { object: { company: "Apple Inc", year: 1976, founder: "Steve Jobs" } }
|
|
22
22
|
*/
|
|
23
23
|
async function generateObject(options) {
|
|
24
|
-
return (0, api_1.post)("
|
|
24
|
+
return (0, api_1.post)("/execute/llm", {
|
|
25
|
+
mode: "object",
|
|
26
|
+
prompt: options.prompt,
|
|
27
|
+
schemaJson: options.schema
|
|
28
|
+
});
|
|
25
29
|
}
|
package/dist/googleMaps.js
CHANGED
|
@@ -3,6 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.googleMapsScrape = googleMapsScrape;
|
|
4
4
|
const api_1 = require("./api");
|
|
5
5
|
async function googleMapsScrape(params) {
|
|
6
|
-
|
|
7
|
-
return (0, api_1.post)("googleMaps", { ...params });
|
|
6
|
+
return (0, api_1.post)("/execute/google-maps", { ...params });
|
|
8
7
|
}
|
package/dist/serp.js
CHANGED
|
@@ -10,7 +10,7 @@ const api_1 = require("./api");
|
|
|
10
10
|
* const page = await webSearch({ query: "best CRM software 2025" });
|
|
11
11
|
*/
|
|
12
12
|
async function webSearch(params) {
|
|
13
|
-
return (0, api_1.post)("
|
|
13
|
+
return (0, api_1.post)("/execute/serp", { ...params });
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* services.web.batchSearch
|
|
@@ -19,6 +19,8 @@ async function webSearch(params) {
|
|
|
19
19
|
* const pages = await webBatchSearch({ queries: [{ query: "site:linkedin.com/in stripe" }] });
|
|
20
20
|
*/
|
|
21
21
|
async function webBatchSearch(params) {
|
|
22
|
-
const data = await (0, api_1.post)("
|
|
22
|
+
const data = await (0, api_1.post)("/execute/serp-batch", {
|
|
23
|
+
queries: params.queries
|
|
24
|
+
});
|
|
23
25
|
return Array.isArray(data) ? data : data.results ?? [];
|
|
24
26
|
}
|
|
@@ -216,17 +216,31 @@ type search = (params: {
|
|
|
216
216
|
}>;
|
|
217
217
|
```
|
|
218
218
|
|
|
219
|
-
##
|
|
219
|
+
## Create Useful Views After Prospecting
|
|
220
220
|
|
|
221
|
-
|
|
221
|
+
Once a prospecting workflow is set up, **always create a few views**. Views show up as colored tabs in the sheet bar so switching is instant.
|
|
222
222
|
|
|
223
|
-
|
|
224
|
-
2. Create or update a continuation trigger named `Get more <topic> leads` (fallback: `Get more leads`)
|
|
225
|
-
3. Configure the trigger to continue the same prospecting strategy (next pages / additional query batches / source expansion) and run the workflow
|
|
223
|
+
Use `ctx.sql()` with `CREATE VIEW ... ON ... AS SELECT ...`.
|
|
226
224
|
|
|
227
|
-
|
|
225
|
+
**Prefer sorted views over filtered views.** A sorted view keeps ALL rows visible but groups the best ones at the top — the user sees qualified leads first and can scroll to see the rest. Filtered views hide rows, making it hard to judge how much work went into finding the qualified ones.
|
|
228
226
|
|
|
229
|
-
|
|
227
|
+
```ts
|
|
228
|
+
// Sorted view — qualified at top, rest below (preferred)
|
|
229
|
+
await ctx.sql(
|
|
230
|
+
`CREATE VIEW "Best First" ON "Companies" AS SELECT * FROM "Companies" ORDER BY "Qualified" DESC, "Score" DESC`
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// Sorted by score
|
|
234
|
+
await ctx.sql(`CREATE VIEW "By Score" ON "Companies" AS SELECT * FROM "Companies" ORDER BY "Score" DESC`);
|
|
235
|
+
|
|
236
|
+
// Grouped by category
|
|
237
|
+
await ctx.sql(`CREATE VIEW "By Industry" ON "Companies" AS SELECT * FROM "Companies" ORDER BY "Industry" ASC`);
|
|
238
|
+
|
|
239
|
+
// Only use WHERE filters for hard exclusions
|
|
240
|
+
await ctx.sql(`CREATE VIEW "Errors Only" ON "Companies" AS SELECT * FROM "Companies" WHERE "Status" = 'failed'`);
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Don't overthink it — just create 2-3 views that match the columns you built. Skip only if there's genuinely nothing to sort/group on.
|
|
230
244
|
|
|
231
245
|
## Examples
|
|
232
246
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Generate an object using AI.
|
|
5
5
|
* Does NOT have internet access.
|
|
6
6
|
* IMPORTANT: Always incorporate the user's guidelines (i.e. no fabrication, writing style, format) into your prompt. See index.md.
|
|
7
|
-
*
|
|
7
|
+
* Use a regular JSON schema object for `schema`.
|
|
8
8
|
*
|
|
9
9
|
* ⚠️ IMPORTANT: ONE CALL PER ITEM ⚠️
|
|
10
10
|
* NEVER batch multiple items together and ask the AI to return an array of results.
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
type generateObject = (params: {
|
|
22
22
|
/** The prompt to generate the object from */
|
|
23
23
|
prompt: string;
|
|
24
|
-
/** A
|
|
24
|
+
/** A JSON schema object describing the output shape */
|
|
25
25
|
schema: any;
|
|
26
26
|
/** Optional model override. All models currently route to gpt-5-mini */
|
|
27
27
|
model?: "gpt-5-mini";
|
|
@@ -16,6 +16,7 @@ interface ContactInfoResponse {
|
|
|
16
16
|
/**
|
|
17
17
|
* Get contact info (email, phone) for a person via LinkedIn URL or name+company.
|
|
18
18
|
* This takes up to 10min. Returns work/personal/unknown variants prioritized in that order.
|
|
19
|
+
* If this is in a column, you should run it last as it takes a while.
|
|
19
20
|
* CRITICAL: Never duplicate calls with identical params across columns.
|
|
20
21
|
*/
|
|
21
22
|
type get = (params: {
|