orangeslice 2.1.4 → 2.1.5

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/expansion.js CHANGED
@@ -71,17 +71,28 @@ async function companyLinkedinEnrich(params) {
71
71
  if (!shorthand && !url && !domain) {
72
72
  throw new Error("[orangeslice] company.linkedin.enrich: provide shorthand, url, or domain");
73
73
  }
74
- const where = [];
75
- if (shorthand)
76
- where.push(`slug = ${sqlString(shorthand)}`);
77
- if (url)
78
- where.push(`linkedin_url = ${sqlString(url)}`);
79
- if (domain)
80
- where.push(`lower(website) LIKE ${sqlString(`%${domain}%`)}`);
81
74
  const fields = extended
82
- ? "*"
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";
84
- const sql = `SELECT ${fields} FROM lkd_company WHERE ${where.join(" OR ")} LIMIT 1`;
75
+ ? "lkd.*"
76
+ : "lkd.name, lkd.slug, lkd.website, lkd.description, lkd.employee_count, lkd.follower_count, lkd.founded_year, lkd.locality, lkd.region, lkd.country_iso, lkd.country_name, lkd.type, lkd.size, lkd.ticker, lkd.specialties, lkd.linkedin_url, lkd.created_at, lkd.updated_at";
77
+ let sql;
78
+ if (domain) {
79
+ const bare = domain.replace(/^www\./, "");
80
+ sql = `SELECT ${fields}
81
+ FROM linkedin_company lc
82
+ JOIN lkd_company lkd ON lkd.linkedin_company_id = lc.id
83
+ WHERE lc.domain IN (${sqlString(bare)}, ${sqlString("www." + bare)})
84
+ ORDER BY CASE WHEN lc.website ~ ${sqlString("^https?://(www\\.)?" + bare.replace(/\./g, "\\.") + "(/([a-z]{2}(-[a-z]{2})?)?)?(\\?.*)?/?$")} THEN 0 ELSE 1 END,
85
+ lc.employee_count DESC NULLS LAST
86
+ LIMIT 1`;
87
+ }
88
+ else {
89
+ const where = [];
90
+ if (shorthand)
91
+ where.push(`lkd.slug = ${sqlString(shorthand)}`);
92
+ if (url)
93
+ where.push(`lkd.linkedin_url = ${sqlString(url)}`);
94
+ sql = `SELECT ${fields} FROM lkd_company lkd WHERE ${where.join(" OR ")} LIMIT 1`;
95
+ }
85
96
  const data = await (0, api_1.post)("/execute/sql", { sql });
86
97
  return data.rows?.[0] ?? null;
87
98
  }
@@ -0,0 +1,137 @@
1
+ # createWebhookFlow
2
+
3
+ Create a HubSpot workflow that sends a webhook from the user's HubSpot portal.
4
+
5
+ This helper is for OAuth-authorized workflow webhooks, not HubSpot's app-level Webhooks API.
6
+
7
+ ```typescript
8
+ const flow = await integrations.hubspot.createWebhookFlow({
9
+ name: "Orange Slice - Enrich Missing Contact Emails",
10
+ description: "Enroll new lead contacts missing email and send to Orange Slice",
11
+ objectTypeId: "0-1", // Contacts
12
+ webhookUrl: "https://www.orangeslice.ai/api/triggers/<trigger-id>/webhook",
13
+ method: "POST",
14
+ enrollmentCriteria: {
15
+ type: "EVENT_BASED",
16
+ shouldReEnroll: false,
17
+ eventFilterBranches: [
18
+ {
19
+ eventTypeId: "4-1463224", // CRM Object Created
20
+ operator: "HAS_COMPLETED",
21
+ filterBranchType: "UNIFIED_EVENTS",
22
+ filterBranchOperator: "AND",
23
+ filterBranches: [],
24
+ filters: []
25
+ }
26
+ ],
27
+ listMembershipFilterBranches: [],
28
+ refinementCriteria: {
29
+ filterBranchType: "AND",
30
+ filterBranchOperator: "AND",
31
+ filterBranches: [],
32
+ filters: [
33
+ {
34
+ property: "lifecyclestage",
35
+ filterType: "PROPERTY",
36
+ operation: {
37
+ operationType: "ENUMERATION",
38
+ operator: "IS_ANY_OF",
39
+ includeObjectsWithNoValueSet: false,
40
+ values: ["lead"]
41
+ }
42
+ },
43
+ {
44
+ property: "email",
45
+ filterType: "PROPERTY",
46
+ operation: {
47
+ operationType: "MULTISTRING",
48
+ operator: "IS_EQUAL_TO",
49
+ includeObjectsWithNoValueSet: true,
50
+ values: []
51
+ }
52
+ }
53
+ ]
54
+ }
55
+ }
56
+ });
57
+ ```
58
+
59
+ ## Input
60
+
61
+ ```typescript
62
+ {
63
+ name?: string;
64
+ description?: string;
65
+ type?: "CONTACT_FLOW" | "PLATFORM_FLOW";
66
+ objectTypeId: string;
67
+ isEnabled?: boolean;
68
+ webhookActionId?: string;
69
+ nextAvailableActionId?: string;
70
+ webhookUrl: string;
71
+ method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS" | "CONNECT" | "TRACE";
72
+ queryParams?: Array<{ name: string; value: any }>;
73
+ headers?: Array<{ name: string; value: any }>;
74
+ requestBody?: string | { type: "STATIC"; value: string };
75
+ authSettings?: {
76
+ type: "NONE" | "AUTH_KEY" | "SIGNATURE" | "OAUTH";
77
+ [key: string]: any;
78
+ };
79
+ enrollmentCriteria?: any;
80
+ enrollmentSchedule?: any;
81
+ timeWindows?: any[];
82
+ blockedDates?: any[];
83
+ suppressionListIds?: string[];
84
+ canEnrollFromSalesforce?: boolean;
85
+ customProperties?: Record<string, string>;
86
+ }
87
+ ```
88
+
89
+ ## Output
90
+
91
+ ```typescript
92
+ HubSpotFlow;
93
+ ```
94
+
95
+ ## Notes
96
+
97
+ This uses the HubSpot workflows API, not the app-level Webhooks API. The connected HubSpot user must authorize your app with the `automation` OAuth scope.
98
+
99
+ HubSpot's v4 workflow payload is stricter in practice than our local generated types suggest. The following details are important and were confirmed to work:
100
+
101
+ - Include top-level `flowType: "WORKFLOW"` when creating workflows via `createFlow(...)`.
102
+ - Use a webhook action with `type: "WEBHOOK"` rather than trying to guess an `actionTypeId`.
103
+ - If you pass `isEnabled: true`, this helper creates the workflow disabled first, then performs a second update call with the full workflow payload HubSpot expects for activation.
104
+ - HubSpot accepts a webhook action shape like:
105
+
106
+ ```typescript
107
+ {
108
+ type: "WEBHOOK",
109
+ actionId: "1",
110
+ method: "POST",
111
+ webhookUrl: "https://example.com/webhook",
112
+ requestBody: { type: "STATIC", value: "" },
113
+ queryParams: [],
114
+ headers: [],
115
+ authSettings: { type: "NONE", value: null }
116
+ }
117
+ ```
118
+
119
+ - For the contact lifecycle stage filter, use `operationType: "ENUMERATION"`.
120
+ - For "email is missing", HubSpot accepted:
121
+
122
+ ```typescript
123
+ {
124
+ property: "email",
125
+ filterType: "PROPERTY",
126
+ operation: {
127
+ operationType: "MULTISTRING",
128
+ operator: "IS_EQUAL_TO",
129
+ includeObjectsWithNoValueSet: true,
130
+ values: []
131
+ }
132
+ }
133
+ ```
134
+
135
+ - The CRM Object Created event trigger is `eventTypeId: "4-1463224"`.
136
+
137
+ If the helper fails, fall back to `integrations.hubspot.createFlow(...)` with the exact working payload shape above.
@@ -1,10 +1,10 @@
1
1
  ---
2
- description: HubSpot CRM - contacts, companies, deals, lists, workflows
2
+ description: HubSpot CRM and workflows
3
3
  ---
4
4
 
5
5
  # HubSpot Integration
6
6
 
7
- Typed functions for HubSpot CRM operations.
7
+ Typed functions for HubSpot CRM operations and workflows.
8
8
 
9
9
  ## Contacts
10
10
 
@@ -39,6 +39,8 @@ Typed functions for HubSpot CRM operations.
39
39
  - `integrations.hubspot.getFlow(flowId)` - Get workflow details by ID
40
40
  - `integrations.hubspot.createFlow(input)` - Create a new workflow
41
41
  - `integrations.hubspot.updateFlow(flowId, input)` - Update an existing workflow
42
+ - `integrations.hubspot.createWebhookFlow(input)` - Create a workflow containing a webhook action using OAuth `automation` scope. For advanced payloads, `createFlow(...)` may be more reliable.
43
+ - `integrations.hubspot.updateWebhookFlow(flowId, input)` - Update the webhook action in an existing workflow
42
44
  - `integrations.hubspot.deleteFlow(flowId)` - Delete a workflow
43
45
 
44
46
  ## Lists
@@ -49,6 +51,10 @@ Typed functions for HubSpot CRM operations.
49
51
 
50
52
  - `integrations.hubspot.listProperties(objectType, options?)` - List all property definitions for an object type
51
53
 
54
+ Use `createWebhookFlow` / `updateWebhookFlow` when a user authorizes your app with HubSpot OAuth and grants the `automation` scope.
55
+
56
+ In practice, HubSpot's workflows v4 API may require extra top-level fields such as `flowType`, `nextAvailableActionId`, and `crmObjectCreationStatus`, plus a strict `WEBHOOK` action payload. See `createWebhookFlow.md` for the currently working payload shape.
57
+
52
58
  ## Owners
53
59
 
54
60
  - `integrations.hubspot.listOwners(options?)` - List all owners (users) with pagination
@@ -3,12 +3,7 @@
3
3
  Update an existing workflow.
4
4
 
5
5
  ```typescript
6
- // First get the current flow to obtain revisionId
7
- const currentFlow = await integrations.hubspot.getFlow("12345678");
8
-
9
- // Update the flow
10
6
  const updatedFlow = await integrations.hubspot.updateFlow("12345678", {
11
- revisionId: currentFlow.revisionId,
12
7
  name: "Updated Workflow Name",
13
8
  isEnabled: true
14
9
  });
@@ -16,15 +11,15 @@ const updatedFlow = await integrations.hubspot.updateFlow("12345678", {
16
11
 
17
12
  ## Input
18
13
 
19
- | Parameter | Type | Description |
20
- | -------------------------- | ----------- | --------------------------------------------------------- |
21
- | `flowId` | `string` | The ID of the workflow to update |
22
- | `input.revisionId` | `string` | **Required** - Current revision ID for optimistic locking |
23
- | `input.name` | `string` | Updated name |
24
- | `input.description` | `string` | Updated description |
25
- | `input.isEnabled` | `boolean` | Enable/disable the workflow |
26
- | `input.actions` | `unknown[]` | Updated workflow actions |
27
- | `input.enrollmentCriteria` | `unknown` | Updated enrollment criteria |
14
+ | Parameter | Type | Description |
15
+ | -------------------------- | ----------- | ---------------------------------------------------------------------------------- |
16
+ | `flowId` | `string` | The ID of the workflow to update |
17
+ | `input.revisionId` | `string` | Optional current revision ID. If omitted, the helper fetches the latest flow first |
18
+ | `input.name` | `string` | Updated name |
19
+ | `input.description` | `string` | Updated description |
20
+ | `input.isEnabled` | `boolean` | Enable/disable the workflow |
21
+ | `input.actions` | `unknown[]` | Updated workflow actions |
22
+ | `input.enrollmentCriteria` | `unknown` | Updated enrollment criteria |
28
23
 
29
24
  ## Output
30
25
 
@@ -43,3 +38,7 @@ Returns the updated workflow object.
43
38
  updatedAt: string;
44
39
  }
45
40
  ```
41
+
42
+ ## Notes
43
+
44
+ This helper now fetches the current workflow and preserves HubSpot-required top-level fields such as `type`, `objectTypeId`, `flowType`, `nextAvailableActionId`, and `crmObjectCreationStatus` before sending the update request.
@@ -0,0 +1,52 @@
1
+ # updateWebhookFlow
2
+
3
+ Update the webhook action in an existing HubSpot workflow.
4
+
5
+ ```typescript
6
+ const updated = await integrations.hubspot.updateWebhookFlow("12345678", {
7
+ webhookUrl: "https://example.com/webhooks/hubspot/v2",
8
+ isEnabled: true
9
+ });
10
+ ```
11
+
12
+ ## Input
13
+
14
+ ```typescript
15
+ flowId: string
16
+
17
+ input: {
18
+ name?: string;
19
+ description?: string;
20
+ isEnabled?: boolean;
21
+ webhookUrl?: string;
22
+ method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS" | "CONNECT" | "TRACE";
23
+ queryParams?: Array<{ name: string; value: any }>;
24
+ headers?: Array<{ name: string; value: any }>;
25
+ requestBody?: string;
26
+ authSettings?: {
27
+ type: "AUTH_KEY" | "SIGNATURE" | "OAUTH";
28
+ [key: string]: any;
29
+ };
30
+ enrollmentCriteria?: any;
31
+ enrollmentSchedule?: any;
32
+ timeWindows?: any[];
33
+ blockedDates?: any[];
34
+ suppressionListIds?: string[];
35
+ canEnrollFromSalesforce?: boolean;
36
+ customProperties?: Record<string, string>;
37
+ }
38
+ ```
39
+
40
+ ## Output
41
+
42
+ ```typescript
43
+ HubSpotFlow;
44
+ ```
45
+
46
+ ## Notes
47
+
48
+ This helper fetches the current workflow, preserves its existing revision and non-webhook settings, and updates the first webhook action it finds.
49
+
50
+ When working with HubSpot workflow webhooks, prefer preserving any existing top-level workflow fields such as `flowType`, `nextAvailableActionId`, and `crmObjectCreationStatus`. HubSpot's v4 workflow API is sensitive to omitted fields during updates.
51
+
52
+ This helper now preserves those top-level workflow fields automatically before sending the `updateFlow(...)` request.
@@ -6,7 +6,7 @@ Access external APIs through `integrations.<provider>.<function>()`.
6
6
 
7
7
  ### HubSpot
8
8
 
9
- CRM operations for contacts, companies, and deals.
9
+ CRM operations plus workflows.
10
10
 
11
11
  ```typescript
12
12
  // Create a contact
@@ -22,6 +22,55 @@ const deals = await integrations.hubspot.searchDeals({
22
22
  }
23
23
  ]
24
24
  });
25
+
26
+ // Create a workflow that sends a webhook from the user's HubSpot portal
27
+ await integrations.hubspot.createWebhookFlow({
28
+ name: "Orange Slice - Enrich Missing Contact Emails",
29
+ objectTypeId: "0-1",
30
+ webhookUrl: "https://www.orangeslice.ai/api/triggers/<trigger-id>/webhook",
31
+ enrollmentCriteria: {
32
+ type: "EVENT_BASED",
33
+ shouldReEnroll: false,
34
+ eventFilterBranches: [
35
+ {
36
+ eventTypeId: "4-1463224",
37
+ operator: "HAS_COMPLETED",
38
+ filterBranchType: "UNIFIED_EVENTS",
39
+ filterBranchOperator: "AND",
40
+ filterBranches: [],
41
+ filters: []
42
+ }
43
+ ],
44
+ listMembershipFilterBranches: [],
45
+ refinementCriteria: {
46
+ filterBranchType: "AND",
47
+ filterBranchOperator: "AND",
48
+ filterBranches: [],
49
+ filters: [
50
+ {
51
+ property: "lifecyclestage",
52
+ filterType: "PROPERTY",
53
+ operation: {
54
+ operationType: "ENUMERATION",
55
+ operator: "IS_ANY_OF",
56
+ includeObjectsWithNoValueSet: false,
57
+ values: ["lead"]
58
+ }
59
+ },
60
+ {
61
+ property: "email",
62
+ filterType: "PROPERTY",
63
+ operation: {
64
+ operationType: "MULTISTRING",
65
+ operator: "IS_EQUAL_TO",
66
+ includeObjectsWithNoValueSet: true,
67
+ values: []
68
+ }
69
+ }
70
+ ]
71
+ }
72
+ }
73
+ });
25
74
  ```
26
75
 
27
76
  See [hubspot/](./hubspot/) for all available functions.
@@ -25,6 +25,10 @@ Typed functions for Salesforce CRM operations using SOQL queries and the REST AP
25
25
  - `integrations.salesforce.describeGlobal()` - List all available SObjects
26
26
  - `integrations.salesforce.describeSObject(sobject)` - Get schema for an SObject
27
27
 
28
+ ## Generic Request Operations
29
+
30
+ - `integrations.salesforce.request(input)` - Make an authenticated request to any Salesforce endpoint
31
+
28
32
  ## Collection/Batch Operations
29
33
 
30
34
  - `integrations.salesforce.createRecords(input)` - Create up to 200 records
@@ -0,0 +1,58 @@
1
+ const result = await integrations.salesforce.request({
2
+ path: "/services/data/v62.0/actions/standard",
3
+ });
4
+
5
+ const compositeResult = await integrations.salesforce.request({
6
+ method: "POST",
7
+ path: "/services/data/v62.0/composite/",
8
+ body: {
9
+ allOrNone: true,
10
+ compositeRequest: [
11
+ {
12
+ method: "GET",
13
+ url: "/services/data/v62.0/sobjects/Lead/00Qxx000001abcDEAY",
14
+ referenceId: "lead"
15
+ }
16
+
17
+ ]
18
+
19
+ }
20
+ });
21
+
22
+ const apexResult = await integrations.salesforce.request({
23
+ method: "POST",
24
+ path: "/services/apexrest/leadconvert/00Qxx000001abcDEAY",
25
+ body: {
26
+ doNotCreateOpportunity: true
27
+ }
28
+ });
29
+
30
+ Use `request()` when Salesforce has an endpoint that Orange Slice does not expose as a first-class helper.
31
+
32
+ ## Input
33
+
34
+ | Field | Type | Required | Description |
35
+ | --------- | ------------------------------------------------------------------ | -------- | -------------------------------------------------------- |
36
+ | `path` | `string` | Yes | Absolute URL or instance-relative path starting with `/` |
37
+ | `method` | `"GET" \| "POST" \| "PUT" \| "PATCH" \| "DELETE"` | No | HTTP method. Defaults to `GET` |
38
+ | `query` | `Record<string, string \| number \| boolean \| null \| undefined>` | No | Query params appended to the URL |
39
+ | `headers` | `Record<string, string>` | No | Additional request headers |
40
+ | `body` | `unknown` | No | Request body. Objects are JSON-stringified automatically |
41
+
42
+ ## Response
43
+
44
+ Returns:
45
+
46
+ ```ts
47
+ {
48
+ status: number;
49
+ headers: Record<string, string>;
50
+ data: unknown;
51
+ }
52
+ ```
53
+
54
+ ## Notes
55
+
56
+ - OAuth and the Salesforce instance URL are handled automatically.
57
+ - `path` must be a full URL or start with `/`.
58
+ - This is the escape hatch for non-CRUD Salesforce APIs such as Apex REST, composite APIs, quick actions, and other special endpoints.
@@ -1,4 +1,4 @@
1
- /** Credits: 1 (standard) */
1
+ /** Credits: 1 (low) or 10 (medium) */
2
2
 
3
3
  /**
4
4
  * Generate an object using AI.
@@ -17,12 +17,16 @@
17
17
  * for each one individually. The runtime handles parallelization for you.
18
18
  *
19
19
  * The field will be present in the output but can have a null value when the AI has no data.
20
+ *
21
+ * Intelligence guidance:
22
+ * - Prefer `low` for classification, tagging, extraction, normalization, and other constrained schema-filling tasks.
23
+ * - Prefer `medium` for more nuanced generation quality, like writing outreach hooks, personalized messaging, or other higher-judgment copy that still returns structured fields.
20
24
  */
21
25
  type generateObject = (params: {
22
26
  /** The prompt to generate the object from */
23
27
  prompt: string;
24
28
  /** A JSON schema object describing the output shape */
25
29
  schema: any;
26
- /** Optional model override. All models currently route to gpt-5-mini */
27
- model?: "gpt-5-mini";
30
+ /** Optional intelligence level. Defaults to "low" (gpt-5-mini, 1 credit). Prefer `low` for classification/extraction and `medium` for higher-quality writing tasks like outreach (gemini-3-flash-preview, 10 credits). */
31
+ intelligence?: "low" | "medium";
28
32
  }) => Promise<{ object: Record<string, any> }>;
@@ -1,14 +1,18 @@
1
- /** Credits: 1 (standard) */
1
+ /** Credits: 1 (low) or 10 (medium) */
2
2
 
3
3
  /**
4
4
  * Generate text using AI, optionally with web search.
5
5
  * IMPORTANT: Always incorporate the user's guidelines (i.e. no fabrication, writing style, format) into your prompt. See index.md.
6
+ *
7
+ * Intelligence guidance:
8
+ * - Prefer `low` for classification-like prompts, terse transformations, extraction-adjacent formatting, and other constrained text tasks.
9
+ * - Prefer `medium` for higher-quality writing tasks like outreach, hooks, personalized messaging, and nuanced copy generation.
6
10
  */
7
11
  type generateText = (params: {
8
12
  /** The prompt to generate the text from */
9
13
  prompt: string;
10
14
  /** Whether to enable web search */
11
15
  enableWebSearch?: boolean;
12
- /** Optional model override. All models currently route to gpt-5-mini */
13
- model?: "gpt-5-mini";
16
+ /** Optional intelligence level. Defaults to "low" (gpt-5-mini, 1 credit). Prefer `low` for constrained text tasks and `medium` for higher-quality writing like outreach (gemini-3-flash-preview, 10 credits). */
17
+ intelligence?: "low" | "medium";
14
18
  }) => Promise<{ text: string }>;
@@ -49,7 +49,6 @@ interface B2BCompany {
49
49
  company_size: string | null; // Size range label
50
50
  ticker: string | null; // Stock ticker
51
51
  logo: string | null; // Logo URL
52
- specialties: string[] | null; // Company specialties
53
52
  twitter_handle: string | null; // Twitter handle
54
53
  linkedin_url: string | null; // Full LinkedIn URL
55
54
  created_at: string | null; // Record created timestamp
@@ -5,11 +5,21 @@ description: Trigger mental model + runtime API + webhook shape access. Read bef
5
5
 
6
6
  # Triggers Runtime (Agent)
7
7
 
8
- ## What Triggers Are For
8
+ ## Core Rule: Triggers Push Data, Columns Do Work
9
9
 
10
- - Triggers are spreadsheet-level automations that run TypeScript code.
11
- - A trigger starts from one of: manual run, cron schedule, webhook.
12
- - Use triggers for orchestration; use sheet columns for row-level compute.
10
+ **Triggers are thin data ingesters, NOT processing engines.** Push bare minimum data into rows; all enrichment, AI, scoring, and transformation belongs in code columns.
11
+
12
+ The spreadsheet is a live workspace, not a log. Columns make work visible, debuggable, retryable per-row, and composable across data sources. Processing inside triggers hides failures in run logs.
13
+
14
+ **Trigger code should only:**
15
+
16
+ - Parse/extract fields from `ctx.trigger.payload`
17
+ - Light dedup (`SELECT` to check if row exists)
18
+ - `addRows()` with raw data + `{ run: true }` to kick off columns
19
+ - Paginate via self-invocation for large ingestion
20
+ - Route to the right sheet based on payload type
21
+
22
+ **Never put in trigger code:** `services.*` enrichment, AI calls, scoring, or per-row transformation loops — the sheet IS the loop.
13
23
 
14
24
  ## Webhook Data Model (from DB schema)
15
25
 
@@ -175,11 +185,22 @@ interface Ctx {
175
185
  ## Minimal Example
176
186
 
177
187
  ```ts
188
+ // Inspect recent webhook payloads to understand shape
178
189
  const t = await ctx.triggers.byName("Inbound Lead Webhook");
179
190
  const recent = await t.webhooks.list({ limit: 10 });
180
191
  const samplePayload = recent[0]?.payload;
181
192
 
193
+ // Trigger code: push minimal data, let columns do the work
182
194
  await t.update({
183
- code: `const body = ctx.trigger.payload; return { ok: true, body };`
195
+ code: `
196
+ const leads = Array.isArray(ctx.trigger.payload) ? ctx.trigger.payload : [ctx.trigger.payload];
197
+ const sheet = await ctx.sheet("Inbound Leads");
198
+ await sheet.addRows(
199
+ leads.map(l => ({ "Name": l.name, "Email": l.email, "Source": "webhook" })),
200
+ { run: true }
201
+ );
202
+ return { pushed: leads.length };
203
+ `
184
204
  });
205
+ // Then create enrichment columns on "Inbound Leads" to do the actual work
185
206
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orangeslice",
3
- "version": "2.1.4",
3
+ "version": "2.1.5",
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",