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 CHANGED
@@ -3,8 +3,4 @@ export interface OrangesliceConfig {
3
3
  baseUrl?: string;
4
4
  }
5
5
  export declare function configure(opts: OrangesliceConfig): void;
6
- interface PostOptions {
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
- * Batch-only routing:
7
- * - Submit all calls to Railway batch-service /function
8
- * - Poll batch-service /function/result on pending responses
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/function";
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 resolveBatchPollUrl(baseUrl, pending) {
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
- const url = new URL(baseUrl);
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 pollBatchUntilComplete(baseUrl, functionId, pending) {
92
- const pollUrl = resolveBatchPollUrl(baseUrl, pending);
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] ${functionId}: ${res.status} ${message}`);
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] ${functionId}: ${message}`);
109
+ throw new Error(`[orangeslice] ${endpoint}: ${message}`);
113
110
  }
114
111
  return data;
115
112
  }
116
- throw new Error(`[orangeslice] ${functionId}: polling timed out after ${POLL_TIMEOUT_MS}ms`);
113
+ throw new Error(`[orangeslice] ${endpoint}: polling timed out after ${POLL_TIMEOUT_MS}ms`);
117
114
  }
118
- async function post(functionId, payload, options = {}) {
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}?functionId=${functionId}`;
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] ${functionId}: ${res.status} ${message}`);
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 pollBatchUntilComplete(baseUrl, functionId, data);
140
+ return pollUntilComplete(baseUrl, endpoint, data);
146
141
  }
147
142
  const errorMessage = asErrorMessage(data);
148
143
  if (errorMessage) {
149
- throw new Error(`[orangeslice] ${functionId}: ${errorMessage}`);
144
+ throw new Error(`[orangeslice] ${endpoint}: ${errorMessage}`);
150
145
  }
151
146
  return data;
152
147
  }
package/dist/apify.js CHANGED
@@ -6,5 +6,5 @@ const api_1 = require("./api");
6
6
  * services.apify.runActor
7
7
  */
8
8
  async function runApifyActor(params) {
9
- return (0, api_1.post)("apify", { ...params });
9
+ return (0, api_1.post)("/execute/apify", { ...params });
10
10
  }
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
- // LinkedIn SQL should hit the direct function backend, not the queue proxy.
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
@@ -6,5 +6,5 @@ const api_1 = require("./api");
6
6
  * services.browser.execute
7
7
  */
8
8
  async function browserExecute(params) {
9
- return (0, api_1.post)("kernel", { ...params });
9
+ return (0, api_1.post)("/execute/kernel", { ...params });
10
10
  }
package/dist/cli.js CHANGED
File without changes
@@ -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)("b2b", { sql }, { direct: true });
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)("b2b", { sql }, { direct: true });
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)("linkedinFindProfileUrl", params);
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)("findLinkedinCompanyUrl", params);
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)("contactInfoWaterfall", { ...params });
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-get-employees-for-company", params);
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)("geo", params);
103
+ return (0, api_1.post)("/execute/parse-address", params);
116
104
  }
117
105
  async function builtWithLookupDomain(params) {
118
- return (0, api_1.post)("builtwithLookupDomain", params);
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)("builtwithRelationships", params);
109
+ return (0, api_1.post)("/execute/builtwith/relationships", params);
122
110
  }
123
111
  async function builtWithSearchByTech(params) {
124
- return (0, api_1.post)("builtwithSearchByTech", params);
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
- return (0, api_1.post)("firecrawl", { ...params });
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
  }
@@ -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)("generateObject", { ...options });
24
+ return (0, api_1.post)("/execute/llm", {
25
+ mode: "object",
26
+ prompt: options.prompt,
27
+ schemaJson: options.schema
28
+ });
25
29
  }
@@ -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
- // Single path by design: use googleMaps function proxy only.
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)("batchserp", { ...params });
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)("batchserp", { ...params });
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
- ## Prospecting Continuation Trigger (Get More Leads)
219
+ ## Create Useful Views After Prospecting
220
220
 
221
- For **every prospecting workflow**, aggressively default to creating a continuation trigger after the initial setup so the user can get more leads and run the workflow again without rebuilding anything.
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
- 1. Read `triggers-runtime`
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
- Only skip this when the user explicitly says they do not want a continuation trigger.
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
- * You should take advantage of how specific zod can get here.
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 Zod schema to generate the object from. must be z.object at the top level */
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: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orangeslice",
3
- "version": "1.8.6-beta.0",
3
+ "version": "2.0.0",
4
4
  "description": "B2B LinkedIn database prospector - 1.15B profiles, 85M companies",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",