orangeslice 1.8.6 → 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 +5 -4
- package/dist/api.js +44 -27
- package/dist/apify.js +1 -1
- package/dist/b2b.js +1 -2
- package/dist/browser.js +1 -1
- package/dist/cli.js +76 -9
- 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/index.d.ts +7 -5
- package/dist/index.js +8 -6
- package/dist/serp.js +4 -2
- package/docs/integrations/gmail/index.md +12 -0
- package/docs/integrations/gmail/sendEmail.md +51 -0
- package/docs/integrations/index.md +24 -0
- package/docs/prospecting/index.md +25 -1
- package/docs/services/ai/generateObject.ts +2 -2
- package/docs/services/person/contact/get.ts +1 -0
- package/docs/services/person/linkedin/enrich.md +1 -0
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
interface
|
|
2
|
-
|
|
1
|
+
export interface OrangesliceConfig {
|
|
2
|
+
apiKey?: string;
|
|
3
|
+
baseUrl?: string;
|
|
3
4
|
}
|
|
4
|
-
export declare function
|
|
5
|
-
export
|
|
5
|
+
export declare function configure(opts: OrangesliceConfig): void;
|
|
6
|
+
export declare function post<T>(endpoint: string, payload: Record<string, unknown>): Promise<T>;
|
package/dist/api.js
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.configure = configure;
|
|
3
4
|
exports.post = post;
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
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.
|
|
8
9
|
*/
|
|
9
|
-
const
|
|
10
|
+
const DEFAULT_BASE_URL = "https://enrichly-production.up.railway.app";
|
|
10
11
|
const POLL_TIMEOUT_MS = 600000;
|
|
11
12
|
const DEFAULT_POLL_INTERVAL_MS = 1000;
|
|
13
|
+
const DEFAULT_INLINE_WAIT_MS = 5000;
|
|
14
|
+
const _config = {};
|
|
15
|
+
function configure(opts) {
|
|
16
|
+
if (opts.apiKey !== undefined)
|
|
17
|
+
_config.apiKey = opts.apiKey;
|
|
18
|
+
if (opts.baseUrl !== undefined)
|
|
19
|
+
_config.baseUrl = opts.baseUrl;
|
|
20
|
+
}
|
|
21
|
+
function resolveBaseUrl() {
|
|
22
|
+
return (_config.baseUrl || process.env.ORANGESLICE_BASE_URL || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
23
|
+
}
|
|
24
|
+
function resolveApiKey() {
|
|
25
|
+
return _config.apiKey || process.env.ORANGESLICE_API_KEY || "";
|
|
26
|
+
}
|
|
12
27
|
function sleep(ms) {
|
|
13
28
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
14
29
|
}
|
|
@@ -51,18 +66,14 @@ function isPendingResponse(body) {
|
|
|
51
66
|
return false;
|
|
52
67
|
return body.pending === true;
|
|
53
68
|
}
|
|
54
|
-
function
|
|
69
|
+
function resolvePollUrl(baseUrl, pending) {
|
|
55
70
|
if (pending.pollUrl) {
|
|
56
71
|
return new URL(pending.pollUrl, baseUrl).toString();
|
|
57
72
|
}
|
|
58
73
|
if (!pending.requestId) {
|
|
59
74
|
throw new Error("[orangeslice] pending response missing requestId");
|
|
60
75
|
}
|
|
61
|
-
|
|
62
|
-
const basePath = url.pathname.replace(/\/function\/?$/, "");
|
|
63
|
-
url.pathname = `${basePath}/function/result/${pending.requestId}`.replace(/\/{2,}/g, "/");
|
|
64
|
-
url.search = "";
|
|
65
|
-
return url.toString();
|
|
76
|
+
return `${baseUrl}/function/result/${pending.requestId}`;
|
|
66
77
|
}
|
|
67
78
|
async function fetchWithRedirect(url, init) {
|
|
68
79
|
let res = await fetch(url, { ...init, redirect: "manual" });
|
|
@@ -74,8 +85,8 @@ async function fetchWithRedirect(url, init) {
|
|
|
74
85
|
}
|
|
75
86
|
return res;
|
|
76
87
|
}
|
|
77
|
-
async function
|
|
78
|
-
const pollUrl =
|
|
88
|
+
async function pollUntilComplete(baseUrl, endpoint, pending) {
|
|
89
|
+
const pollUrl = resolvePollUrl(baseUrl, pending);
|
|
79
90
|
const timeoutAt = Date.now() + POLL_TIMEOUT_MS;
|
|
80
91
|
let pollAfterMs = typeof pending.pollAfterMs === "number" && pending.pollAfterMs > 0
|
|
81
92
|
? pending.pollAfterMs
|
|
@@ -91,40 +102,46 @@ async function pollBatchUntilComplete(baseUrl, functionId, pending) {
|
|
|
91
102
|
}
|
|
92
103
|
if (!res.ok) {
|
|
93
104
|
const message = asErrorMessage(data) || JSON.stringify(data);
|
|
94
|
-
throw new Error(`[orangeslice] ${
|
|
105
|
+
throw new Error(`[orangeslice] ${endpoint}: ${res.status} ${message}`);
|
|
95
106
|
}
|
|
96
107
|
const message = asErrorMessage(data);
|
|
97
108
|
if (message) {
|
|
98
|
-
throw new Error(`[orangeslice] ${
|
|
109
|
+
throw new Error(`[orangeslice] ${endpoint}: ${message}`);
|
|
99
110
|
}
|
|
100
111
|
return data;
|
|
101
112
|
}
|
|
102
|
-
throw new Error(`[orangeslice] ${
|
|
113
|
+
throw new Error(`[orangeslice] ${endpoint}: polling timed out after ${POLL_TIMEOUT_MS}ms`);
|
|
103
114
|
}
|
|
104
|
-
async function post(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
115
|
+
async function post(endpoint, payload) {
|
|
116
|
+
const baseUrl = resolveBaseUrl();
|
|
117
|
+
const apiKey = resolveApiKey();
|
|
118
|
+
if (!apiKey) {
|
|
119
|
+
throw new Error("[orangeslice] No API key configured. " +
|
|
120
|
+
"Set ORANGESLICE_API_KEY in your environment or call configure({ apiKey: 'osk_...' }).");
|
|
121
|
+
}
|
|
122
|
+
const url = `${baseUrl}${endpoint}`;
|
|
123
|
+
const body = JSON.stringify({ ...payload, inlineWaitMs: DEFAULT_INLINE_WAIT_MS });
|
|
124
|
+
const headers = {
|
|
125
|
+
"Content-Type": "application/json",
|
|
126
|
+
Authorization: `Bearer ${apiKey}`
|
|
127
|
+
};
|
|
110
128
|
const res = await fetchWithRedirect(url, {
|
|
111
129
|
method: "POST",
|
|
112
|
-
headers
|
|
130
|
+
headers,
|
|
113
131
|
body
|
|
114
132
|
});
|
|
115
133
|
if (!res.ok) {
|
|
116
|
-
let message = `${res.status}`;
|
|
117
134
|
const data = await readResponseBody(res);
|
|
118
|
-
message = asErrorMessage(data) || (typeof data === "string" ? data : JSON.stringify(data));
|
|
119
|
-
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}`);
|
|
120
137
|
}
|
|
121
138
|
const data = await readResponseBody(res);
|
|
122
139
|
if (isPendingResponse(data)) {
|
|
123
|
-
return
|
|
140
|
+
return pollUntilComplete(baseUrl, endpoint, data);
|
|
124
141
|
}
|
|
125
142
|
const errorMessage = asErrorMessage(data);
|
|
126
143
|
if (errorMessage) {
|
|
127
|
-
throw new Error(`[orangeslice] ${
|
|
144
|
+
throw new Error(`[orangeslice] ${endpoint}: ${errorMessage}`);
|
|
128
145
|
}
|
|
129
146
|
return data;
|
|
130
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
|
@@ -37,6 +37,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
37
37
|
const child_process_1 = require("child_process");
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
|
+
const readline = __importStar(require("readline"));
|
|
40
41
|
const LEGACY_DOCS_DIR = path.join(__dirname, "..", "docs");
|
|
41
42
|
const TARGET_DIR = path.join(process.cwd(), "orangeslice-docs");
|
|
42
43
|
const AGENTS_FILE = path.join(TARGET_DIR, "AGENTS.md");
|
|
@@ -110,6 +111,74 @@ function installOrangeslice(cwd) {
|
|
|
110
111
|
console.log(" Installing orangeslice...");
|
|
111
112
|
(0, child_process_1.execSync)("npm install orangeslice", { stdio: "inherit", cwd });
|
|
112
113
|
}
|
|
114
|
+
function prompt(question) {
|
|
115
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
116
|
+
return new Promise((resolve) => {
|
|
117
|
+
rl.question(question, (answer) => {
|
|
118
|
+
rl.close();
|
|
119
|
+
resolve(answer.trim());
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function readEnvFile(envPath) {
|
|
124
|
+
const entries = new Map();
|
|
125
|
+
if (!fs.existsSync(envPath))
|
|
126
|
+
return entries;
|
|
127
|
+
const lines = fs.readFileSync(envPath, "utf8").split("\n");
|
|
128
|
+
for (const line of lines) {
|
|
129
|
+
const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)/);
|
|
130
|
+
if (match)
|
|
131
|
+
entries.set(match[1], match[2]);
|
|
132
|
+
}
|
|
133
|
+
return entries;
|
|
134
|
+
}
|
|
135
|
+
function writeEnvVar(envPath, key, value) {
|
|
136
|
+
const entries = readEnvFile(envPath);
|
|
137
|
+
entries.set(key, value);
|
|
138
|
+
const lines = [];
|
|
139
|
+
if (fs.existsSync(envPath)) {
|
|
140
|
+
const original = fs.readFileSync(envPath, "utf8").split("\n");
|
|
141
|
+
let replaced = false;
|
|
142
|
+
for (const line of original) {
|
|
143
|
+
const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)/);
|
|
144
|
+
if (match && match[1] === key) {
|
|
145
|
+
lines.push(`${key}=${value}`);
|
|
146
|
+
replaced = true;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
lines.push(line);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (!replaced) {
|
|
153
|
+
if (lines.length > 0 && lines[lines.length - 1] !== "")
|
|
154
|
+
lines.push("");
|
|
155
|
+
lines.push(`${key}=${value}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
lines.push(`${key}=${value}`, "");
|
|
160
|
+
}
|
|
161
|
+
fs.writeFileSync(envPath, lines.join("\n"), "utf8");
|
|
162
|
+
}
|
|
163
|
+
async function setupApiKey(cwd) {
|
|
164
|
+
const envPath = path.join(cwd, ".env");
|
|
165
|
+
const existing = readEnvFile(envPath).get("ORANGESLICE_API_KEY");
|
|
166
|
+
if (existing) {
|
|
167
|
+
console.log(` ✓ API key already configured in .env (${existing.slice(0, 12)}...)\n`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
console.log(" API key required. Get one at: https://www.orangeslice.ai/dashboard/api-keys\n");
|
|
171
|
+
const key = await prompt(" Paste your API key (osk_...): ");
|
|
172
|
+
if (!key) {
|
|
173
|
+
console.log("\n ⚠ Skipped. Set ORANGESLICE_API_KEY in .env or call configure({ apiKey }) before using the SDK.\n");
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (!key.startsWith("osk_")) {
|
|
177
|
+
console.log("\n ⚠ Key doesn't start with osk_ — saving anyway. Double-check it's correct.\n");
|
|
178
|
+
}
|
|
179
|
+
writeEnvVar(envPath, "ORANGESLICE_API_KEY", key);
|
|
180
|
+
console.log(`\n ✓ API key saved to .env\n`);
|
|
181
|
+
}
|
|
113
182
|
async function main() {
|
|
114
183
|
console.log("\norangeslice\n");
|
|
115
184
|
const docsDir = resolveDocsDir();
|
|
@@ -124,29 +193,27 @@ async function main() {
|
|
|
124
193
|
ensurePackageJson(cwd);
|
|
125
194
|
installOrangeslice(cwd);
|
|
126
195
|
console.log(" ✓ Package installed in current directory\n");
|
|
196
|
+
// API key setup
|
|
197
|
+
await setupApiKey(cwd);
|
|
127
198
|
console.log("\nReady - services-style API\n");
|
|
128
|
-
console.log(" import { services } from 'orangeslice';\n");
|
|
199
|
+
console.log(" import { configure, services } from 'orangeslice';\n");
|
|
200
|
+
console.log(" // Option A: env var (loaded automatically from .env by your framework)");
|
|
201
|
+
console.log(" // ORANGESLICE_API_KEY=osk_... in .env\n");
|
|
202
|
+
console.log(" // Option B: programmatic");
|
|
203
|
+
console.log(" configure({ apiKey: 'osk_...' });\n");
|
|
129
204
|
console.log(" Agent setup (do this first):");
|
|
130
205
|
console.log(" Ask your agent to read:");
|
|
131
206
|
console.log(" 1) ./orangeslice-docs/AGENTS.md");
|
|
132
207
|
console.log(" 2) ./orangeslice-docs/services/index.md");
|
|
133
208
|
console.log(' Then tell it: "Use these docs as source of truth for all orangeslice operations."\n');
|
|
134
|
-
console.log(" Routing note:");
|
|
135
|
-
console.log(" - all services submit to the batch-service /function endpoint");
|
|
136
|
-
console.log(" - pending jobs poll batch-service /function/result endpoints");
|
|
137
|
-
console.log(" - this package currently includes only batch-backed services\n");
|
|
138
209
|
console.log(" // LinkedIn B2B SQL");
|
|
139
210
|
console.log(' const { rows } = await services.company.linkedin.search({ sql: "SELECT * FROM linkedin_company LIMIT 10" });\n');
|
|
140
211
|
console.log(" // Web search");
|
|
141
212
|
console.log(" const page = await services.web.search({ query: 'best CRM software' });\n");
|
|
142
|
-
console.log(" // Batched web search");
|
|
143
|
-
console.log(" const pages = await services.web.batchSearch({ queries: [{ query: 'site:linkedin.com/in stripe' }] });\n");
|
|
144
213
|
console.log(" // AI structured output");
|
|
145
214
|
console.log(" const { object } = await services.ai.generateObject({ prompt: '...', schema: {...} });\n");
|
|
146
215
|
console.log(" // Browser automation (Kernel)");
|
|
147
216
|
console.log(' const browser = await services.browser.execute({ code: "return await page.title();" });\n');
|
|
148
|
-
console.log(" // Apify actor");
|
|
149
|
-
console.log(" const actor = await services.apify.runActor({ actor: 'apify/web-scraper', input: {} });\n");
|
|
150
217
|
console.log(" Always parallelize independent calls with Promise.all.\n");
|
|
151
218
|
}
|
|
152
219
|
main().catch(console.error);
|
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/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { configure } from "./api";
|
|
2
|
+
export type { OrangesliceConfig } from "./api";
|
|
1
3
|
export { linkedinSearch } from "./b2b";
|
|
2
4
|
export type { LinkedInSearchParams, LinkedInSearchResponse } from "./b2b";
|
|
3
5
|
export { webSearch, webBatchSearch } from "./serp";
|
|
@@ -14,14 +16,14 @@ export { googleMapsScrape } from "./googleMaps";
|
|
|
14
16
|
export type { GoogleMapsScrapeParams } from "./googleMaps";
|
|
15
17
|
export { personLinkedinEnrich, personLinkedinFindUrl, personContactGet, companyLinkedinEnrich, companyLinkedinFindUrl, companyGetEmployeesFromLinkedin, geoParseAddress, builtWithLookupDomain, builtWithRelationships, builtWithSearchByTech } from "./expansion";
|
|
16
18
|
export type { PersonLinkedinFindUrlParams, CompanyLinkedinFindUrlParams, PersonContactGetParams, PersonContactGetResult, CompanyGetEmployeesFromLinkedinParams, CompanyGetEmployeesFromLinkedinResult, CompanyEmployeeFromB2B } from "./expansion";
|
|
19
|
+
import { runApifyActor } from "./apify";
|
|
17
20
|
import { linkedinSearch } from "./b2b";
|
|
18
|
-
import { webBatchSearch, webSearch } from "./serp";
|
|
19
|
-
import { generateObject } from "./generateObject";
|
|
20
|
-
import { scrapeWebsite } from "./firecrawl";
|
|
21
21
|
import { browserExecute } from "./browser";
|
|
22
|
-
import { runApifyActor } from "./apify";
|
|
23
|
-
import { googleMapsScrape } from "./googleMaps";
|
|
24
22
|
import { personLinkedinEnrich, personLinkedinFindUrl, personContactGet, companyLinkedinEnrich, companyLinkedinFindUrl, companyGetEmployeesFromLinkedin, geoParseAddress, builtWithLookupDomain, builtWithRelationships, builtWithSearchByTech } from "./expansion";
|
|
23
|
+
import { scrapeWebsite } from "./firecrawl";
|
|
24
|
+
import { generateObject } from "./generateObject";
|
|
25
|
+
import { googleMapsScrape } from "./googleMaps";
|
|
26
|
+
import { webBatchSearch, webSearch } from "./serp";
|
|
25
27
|
export declare const services: {
|
|
26
28
|
company: {
|
|
27
29
|
linkedin: {
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
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.googleMapsScrape = exports.runApifyActor = exports.browserExecute = exports.scrapeWebsite = exports.generateObject = exports.webBatchSearch = exports.webSearch = exports.linkedinSearch = 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.googleMapsScrape = exports.runApifyActor = exports.browserExecute = exports.scrapeWebsite = exports.generateObject = exports.webBatchSearch = exports.webSearch = exports.linkedinSearch = exports.configure = void 0;
|
|
4
|
+
var api_1 = require("./api");
|
|
5
|
+
Object.defineProperty(exports, "configure", { enumerable: true, get: function () { return api_1.configure; } });
|
|
4
6
|
var b2b_1 = require("./b2b");
|
|
5
7
|
Object.defineProperty(exports, "linkedinSearch", { enumerable: true, get: function () { return b2b_1.linkedinSearch; } });
|
|
6
8
|
var serp_1 = require("./serp");
|
|
@@ -27,14 +29,14 @@ Object.defineProperty(exports, "geoParseAddress", { enumerable: true, get: funct
|
|
|
27
29
|
Object.defineProperty(exports, "builtWithLookupDomain", { enumerable: true, get: function () { return expansion_1.builtWithLookupDomain; } });
|
|
28
30
|
Object.defineProperty(exports, "builtWithRelationships", { enumerable: true, get: function () { return expansion_1.builtWithRelationships; } });
|
|
29
31
|
Object.defineProperty(exports, "builtWithSearchByTech", { enumerable: true, get: function () { return expansion_1.builtWithSearchByTech; } });
|
|
32
|
+
const apify_2 = require("./apify");
|
|
30
33
|
const b2b_2 = require("./b2b");
|
|
31
|
-
const serp_2 = require("./serp");
|
|
32
|
-
const generateObject_2 = require("./generateObject");
|
|
33
|
-
const firecrawl_2 = require("./firecrawl");
|
|
34
34
|
const browser_2 = require("./browser");
|
|
35
|
-
const apify_2 = require("./apify");
|
|
36
|
-
const googleMaps_2 = require("./googleMaps");
|
|
37
35
|
const expansion_2 = require("./expansion");
|
|
36
|
+
const firecrawl_2 = require("./firecrawl");
|
|
37
|
+
const generateObject_2 = require("./generateObject");
|
|
38
|
+
const googleMaps_2 = require("./googleMaps");
|
|
39
|
+
const serp_2 = require("./serp");
|
|
38
40
|
exports.services = {
|
|
39
41
|
company: {
|
|
40
42
|
linkedin: {
|
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
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Gmail email sending via Google integration
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Gmail Integration
|
|
6
|
+
|
|
7
|
+
Typed functions for Gmail actions powered by Orange Slice Google integrations.
|
|
8
|
+
|
|
9
|
+
## Email
|
|
10
|
+
|
|
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
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# sendEmail
|
|
2
|
+
|
|
3
|
+
Send an email from the connected Gmail account.
|
|
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.
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// Basic email
|
|
9
|
+
const result = await integrations.gmail.sendEmail({
|
|
10
|
+
recipient_email: "jane@example.com",
|
|
11
|
+
subject: "Hello from Orange Slice",
|
|
12
|
+
body: "Hi Jane, this is a test email."
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// HTML email with additional recipients
|
|
16
|
+
await integrations.gmail.sendEmail({
|
|
17
|
+
recipient_email: "primary@example.com",
|
|
18
|
+
extra_recipients: ["secondary@example.com"],
|
|
19
|
+
cc: ["manager@example.com"],
|
|
20
|
+
subject: "Weekly digest",
|
|
21
|
+
body: "<h3>Weekly Digest</h3><p>Everything looks good.</p>",
|
|
22
|
+
is_html: true
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Input
|
|
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) |
|
|
40
|
+
|
|
41
|
+
\*Gmail requires at least one recipient (`recipient_email`, `cc`, or `bcc`) and at least one of `subject` or `body`.
|
|
42
|
+
|
|
43
|
+
## Output
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
{
|
|
47
|
+
successful: boolean;
|
|
48
|
+
data?: unknown;
|
|
49
|
+
error?: string;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
@@ -148,3 +148,27 @@ const invite = await integrations.slack.conversationsInviteShared({
|
|
|
148
148
|
```
|
|
149
149
|
|
|
150
150
|
See [slack/](./slack/) for all available functions.
|
|
151
|
+
|
|
152
|
+
### Gmail
|
|
153
|
+
|
|
154
|
+
Send emails from connected Google Gmail accounts.
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// Send a plain text email
|
|
158
|
+
const result = await integrations.gmail.sendEmail({
|
|
159
|
+
recipient_email: "john@example.com",
|
|
160
|
+
subject: "Quick update",
|
|
161
|
+
body: "Hey John - sharing a quick status update."
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Send HTML with CC recipients
|
|
165
|
+
await integrations.gmail.sendEmail({
|
|
166
|
+
recipient_email: "team@example.com",
|
|
167
|
+
cc: ["manager@example.com"],
|
|
168
|
+
subject: "Weekly summary",
|
|
169
|
+
body: "<h2>Weekly Summary</h2><p>All systems operational.</p>",
|
|
170
|
+
is_html: true
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
See [gmail/](./gmail/) for available functions.
|
|
@@ -216,7 +216,31 @@ type search = (params: {
|
|
|
216
216
|
}>;
|
|
217
217
|
```
|
|
218
218
|
|
|
219
|
-
|
|
219
|
+
## Create Useful Views After Prospecting
|
|
220
|
+
|
|
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
|
+
|
|
223
|
+
Use `ctx.sql()` with `CREATE VIEW ... ON ... AS SELECT ...`.
|
|
224
|
+
|
|
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.
|
|
226
|
+
|
|
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.
|
|
220
244
|
|
|
221
245
|
## Examples
|
|
222
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: {
|
|
@@ -94,6 +94,7 @@ interface B2BPersonExperience {
|
|
|
94
94
|
company_name: string;
|
|
95
95
|
company_domain: string | null;
|
|
96
96
|
company_linkedin_url: string | null;
|
|
97
|
+
company_employee_count: number | null; // Employee count of the company
|
|
97
98
|
locality: string | null; // City/region of the job
|
|
98
99
|
start_date: string | null; // "YYYY-MM-DD"
|
|
99
100
|
start_date_year: number | null;
|