@elevasis/sdk 0.4.5 → 0.4.7
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/cli.cjs +829 -413
- package/dist/index.d.ts +79 -14
- package/dist/index.js +17 -12
- package/dist/templates.js +747 -0
- package/dist/types/templates.d.ts +1 -0
- package/dist/types/worker/index.d.ts +6 -0
- package/dist/types/worker/platform.d.ts +32 -0
- package/dist/worker/index.js +4701 -9
- package/package.json +10 -3
- package/reference/_index.md +95 -0
- package/reference/_navigation.md +104 -0
- package/reference/cli/index.mdx +497 -0
- package/reference/concepts/index.mdx +203 -0
- package/reference/deployment/api.mdx +297 -0
- package/reference/deployment/index.mdx +153 -0
- package/reference/developer/interaction-guidance.mdx +213 -0
- package/reference/framework/agent.mdx +175 -0
- package/reference/framework/documentation.mdx +92 -0
- package/reference/framework/index.mdx +95 -0
- package/reference/framework/memory.mdx +337 -0
- package/reference/framework/project-structure.mdx +294 -0
- package/reference/getting-started/index.mdx +148 -0
- package/reference/index.mdx +113 -0
- package/reference/platform-tools/examples.mdx +187 -0
- package/reference/platform-tools/index.mdx +182 -0
- package/reference/resources/index.mdx +289 -0
- package/reference/resources/patterns.mdx +341 -0
- package/reference/resources/types.mdx +207 -0
- package/reference/roadmap/index.mdx +147 -0
- package/reference/runtime/index.mdx +141 -0
- package/reference/runtime/limits.mdx +77 -0
- package/reference/security/credentials.mdx +141 -0
- package/reference/templates/data-enrichment.mdx +162 -0
- package/reference/templates/email-sender.mdx +135 -0
- package/reference/templates/lead-scorer.mdx +175 -0
- package/reference/templates/pdf-generator.mdx +151 -0
- package/reference/templates/recurring-job.mdx +189 -0
- package/reference/templates/text-classifier.mdx +147 -0
- package/reference/templates/web-scraper.mdx +135 -0
- package/reference/troubleshooting/common-errors.mdx +210 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Template: PDF Generator"
|
|
3
|
+
description: "PDF generation from structured data with platform storage upload -- render a PDF from a template and upload to platform storage"
|
|
4
|
+
loadWhen: "Applying the pdf-generator workflow template"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**Category:** Documents
|
|
8
|
+
|
|
9
|
+
**Platform Tools:** `pdf` (render), `storage` (upload)
|
|
10
|
+
|
|
11
|
+
**Credentials Required:**
|
|
12
|
+
|
|
13
|
+
- None required (pdf and storage are platform services, not integrations)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## What This Workflow Does
|
|
18
|
+
|
|
19
|
+
Receives structured data, renders a PDF using an HTML template, uploads the result to platform storage, and returns a signed URL for download. Suitable for invoices, reports, certificates, contracts, and any document that needs to be generated on demand and delivered as a downloadable file.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Input Schema
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
z.object({
|
|
27
|
+
templateHtml: z.string(), // HTML template with {variable} placeholders
|
|
28
|
+
data: z.record(z.unknown()), // Data to inject into the template
|
|
29
|
+
filename: z.string(), // Output filename (without .pdf extension)
|
|
30
|
+
expiresInSeconds: z.number().optional(), // Signed URL expiry (default: 3600)
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Output Schema
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
z.object({
|
|
38
|
+
downloadUrl: z.string(), // Signed URL for the generated PDF
|
|
39
|
+
storageKey: z.string(), // Storage key for future reference
|
|
40
|
+
expiresAt: z.string(), // ISO timestamp when URL expires
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Workflow Code Pattern
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import type { WorkflowDefinition } from '@elevasis/sdk'
|
|
50
|
+
import { StepType } from '@elevasis/sdk'
|
|
51
|
+
import { platform } from '@elevasis/sdk/worker'
|
|
52
|
+
import { z } from 'zod'
|
|
53
|
+
|
|
54
|
+
const inputSchema = z.object({
|
|
55
|
+
templateHtml: z.string(),
|
|
56
|
+
data: z.record(z.unknown()),
|
|
57
|
+
filename: z.string(),
|
|
58
|
+
expiresInSeconds: z.number().optional(),
|
|
59
|
+
})
|
|
60
|
+
const outputSchema = z.object({
|
|
61
|
+
downloadUrl: z.string(),
|
|
62
|
+
storageKey: z.string(),
|
|
63
|
+
expiresAt: z.string(),
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
type Input = z.infer<typeof inputSchema>
|
|
67
|
+
|
|
68
|
+
function renderTemplate(html: string, data: Record<string, unknown>): string {
|
|
69
|
+
return html.replace(/\{(\w+)\}/g, (_, key) => String(data[key] ?? ''))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const pdfGenerator: WorkflowDefinition = {
|
|
73
|
+
config: {
|
|
74
|
+
resourceId: 'pdf-generator',
|
|
75
|
+
name: 'PDF Generator',
|
|
76
|
+
type: 'workflow',
|
|
77
|
+
description: 'Generates a PDF from HTML template and uploads to storage',
|
|
78
|
+
version: '1.0.0',
|
|
79
|
+
status: 'dev',
|
|
80
|
+
},
|
|
81
|
+
contract: { inputSchema, outputSchema },
|
|
82
|
+
steps: {
|
|
83
|
+
render: {
|
|
84
|
+
id: 'render',
|
|
85
|
+
name: 'Render PDF',
|
|
86
|
+
description: 'Render HTML template to PDF buffer',
|
|
87
|
+
inputSchema,
|
|
88
|
+
outputSchema: z.object({ buffer: z.string(), filename: z.string(), expiresInSeconds: z.number() }),
|
|
89
|
+
handler: async (input) => {
|
|
90
|
+
const { templateHtml, data, filename, expiresInSeconds } = input as Input
|
|
91
|
+
const html = renderTemplate(templateHtml, data)
|
|
92
|
+
|
|
93
|
+
const result = await platform.call({
|
|
94
|
+
tool: 'pdf',
|
|
95
|
+
method: 'renderToBuffer',
|
|
96
|
+
params: { html },
|
|
97
|
+
}) as { buffer: string } // base64-encoded buffer
|
|
98
|
+
|
|
99
|
+
return { buffer: result.buffer, filename, expiresInSeconds: expiresInSeconds ?? 3600 }
|
|
100
|
+
},
|
|
101
|
+
next: { type: StepType.LINEAR, target: 'upload' },
|
|
102
|
+
},
|
|
103
|
+
upload: {
|
|
104
|
+
id: 'upload',
|
|
105
|
+
name: 'Upload PDF',
|
|
106
|
+
description: 'Upload rendered PDF to platform storage and get signed URL',
|
|
107
|
+
inputSchema: z.object({ buffer: z.string(), filename: z.string(), expiresInSeconds: z.number() }),
|
|
108
|
+
outputSchema,
|
|
109
|
+
handler: async (input, context) => {
|
|
110
|
+
const { buffer, filename, expiresInSeconds } = input as { buffer: string; filename: string; expiresInSeconds: number }
|
|
111
|
+
const key = `documents/${filename}-${Date.now()}.pdf`
|
|
112
|
+
|
|
113
|
+
await platform.call({
|
|
114
|
+
tool: 'storage',
|
|
115
|
+
method: 'upload',
|
|
116
|
+
params: {
|
|
117
|
+
key,
|
|
118
|
+
content: buffer,
|
|
119
|
+
contentType: 'application/pdf',
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const urlResult = await platform.call({
|
|
124
|
+
tool: 'storage',
|
|
125
|
+
method: 'createSignedUrl',
|
|
126
|
+
params: { key, expiresIn: expiresInSeconds },
|
|
127
|
+
}) as { signedUrl: string; expiresAt: string }
|
|
128
|
+
|
|
129
|
+
context.logger.info('PDF generated and uploaded', { key, filename })
|
|
130
|
+
return { downloadUrl: urlResult.signedUrl, storageKey: key, expiresAt: urlResult.expiresAt }
|
|
131
|
+
},
|
|
132
|
+
next: null,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
entryPoint: 'render',
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Adaptation Notes
|
|
142
|
+
|
|
143
|
+
- **Template format:** The simple `{variable}` placeholder system handles most cases. For complex documents, suggest the user write a more complete HTML template with inline CSS.
|
|
144
|
+
- **Filename:** The template appends a timestamp to avoid collisions. Adapt naming to the user's convention (e.g., `invoice-{invoiceId}`).
|
|
145
|
+
- **Expiry:** Default is 1 hour. For document delivery workflows, increase to 24-72 hours.
|
|
146
|
+
- **Data structure:** Ask the user what fields their document needs before defining the `data` schema. For typed use cases (invoices, reports), define a stricter input schema.
|
|
147
|
+
- **Skill adaptation:** For beginners, show a complete example HTML template and explain what "template variables" are.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
**Last Updated:** 2026-02-26
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Template: Recurring Job"
|
|
3
|
+
description: "Scheduler-triggered periodic workflow -- run a task on a schedule (daily, weekly, hourly, or custom cron)"
|
|
4
|
+
loadWhen: "Applying the recurring-job workflow template"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**Category:** Scheduling
|
|
8
|
+
|
|
9
|
+
**Platform Tools:** `scheduler` (create/manage schedule)
|
|
10
|
+
|
|
11
|
+
**Credentials Required:**
|
|
12
|
+
|
|
13
|
+
- None required (scheduler is a platform service, not an integration)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## What This Workflow Does
|
|
18
|
+
|
|
19
|
+
Two-part pattern for recurring jobs:
|
|
20
|
+
|
|
21
|
+
1. **Setup workflow** (`recurring-job-setup`): Creates a schedule entry that triggers the main job workflow on a recurring basis. Run once to activate.
|
|
22
|
+
2. **Main job workflow** (`recurring-job`): The actual work executed on each scheduled trigger.
|
|
23
|
+
|
|
24
|
+
The job workflow and setup workflow share a schedule key for idempotent schedule management.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Input Schema (Setup)
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
z.object({
|
|
32
|
+
cronExpression: z.string(), // Cron expression (e.g., '0 9 * * 1-5' for weekdays at 9am)
|
|
33
|
+
timezone: z.string().optional(), // Timezone (default: 'UTC')
|
|
34
|
+
jobInput: z.record(z.unknown()).optional(), // Input to pass to each job execution
|
|
35
|
+
})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Input Schema (Job)
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
z.object({
|
|
42
|
+
scheduledAt: z.string(), // ISO timestamp of the scheduled trigger
|
|
43
|
+
jobInput: z.record(z.unknown()).optional(), // Passed through from schedule creation
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Output Schema (Job)
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
z.object({
|
|
51
|
+
completed: z.boolean(),
|
|
52
|
+
processedAt: z.string(),
|
|
53
|
+
summary: z.string(),
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Workflow Code Pattern
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import type { WorkflowDefinition } from '@elevasis/sdk'
|
|
63
|
+
import { platform } from '@elevasis/sdk/worker'
|
|
64
|
+
import { z } from 'zod'
|
|
65
|
+
|
|
66
|
+
// Setup workflow -- run once to create the schedule
|
|
67
|
+
export const recurringJobSetup: WorkflowDefinition = {
|
|
68
|
+
config: {
|
|
69
|
+
resourceId: 'recurring-job-setup',
|
|
70
|
+
name: 'Recurring Job Setup',
|
|
71
|
+
type: 'workflow',
|
|
72
|
+
description: 'Creates or updates the schedule for the recurring job',
|
|
73
|
+
version: '1.0.0',
|
|
74
|
+
status: 'dev',
|
|
75
|
+
},
|
|
76
|
+
contract: {
|
|
77
|
+
inputSchema: z.object({
|
|
78
|
+
cronExpression: z.string(),
|
|
79
|
+
timezone: z.string().optional(),
|
|
80
|
+
jobInput: z.record(z.unknown()).optional(),
|
|
81
|
+
}),
|
|
82
|
+
outputSchema: z.object({
|
|
83
|
+
scheduleId: z.string(),
|
|
84
|
+
active: z.boolean(),
|
|
85
|
+
}),
|
|
86
|
+
},
|
|
87
|
+
steps: {
|
|
88
|
+
createSchedule: {
|
|
89
|
+
id: 'createSchedule',
|
|
90
|
+
name: 'Create Schedule',
|
|
91
|
+
description: 'Set up the recurring schedule',
|
|
92
|
+
inputSchema: z.object({ cronExpression: z.string(), timezone: z.string().optional(), jobInput: z.record(z.unknown()).optional() }),
|
|
93
|
+
outputSchema: z.object({ scheduleId: z.string(), active: z.boolean() }),
|
|
94
|
+
handler: async (input, context) => {
|
|
95
|
+
const { cronExpression, timezone, jobInput } = input as { cronExpression: string; timezone?: string; jobInput?: Record<string, unknown> }
|
|
96
|
+
|
|
97
|
+
const result = await platform.call({
|
|
98
|
+
tool: 'scheduler',
|
|
99
|
+
method: 'createSchedule',
|
|
100
|
+
params: {
|
|
101
|
+
resourceId: 'recurring-job',
|
|
102
|
+
cronExpression,
|
|
103
|
+
timezone: timezone ?? 'UTC',
|
|
104
|
+
input: { scheduledAt: new Date().toISOString(), jobInput: jobInput ?? {} },
|
|
105
|
+
idempotencyKey: 'recurring-job-schedule',
|
|
106
|
+
},
|
|
107
|
+
}) as { id: string }
|
|
108
|
+
|
|
109
|
+
context.logger.info('Schedule created', { scheduleId: result.id, cronExpression })
|
|
110
|
+
return { scheduleId: result.id, active: true }
|
|
111
|
+
},
|
|
112
|
+
next: null,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
entryPoint: 'createSchedule',
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Main job workflow -- triggered by the scheduler on each run
|
|
119
|
+
export const recurringJob: WorkflowDefinition = {
|
|
120
|
+
config: {
|
|
121
|
+
resourceId: 'recurring-job',
|
|
122
|
+
name: 'Recurring Job',
|
|
123
|
+
type: 'workflow',
|
|
124
|
+
description: 'Executes on each scheduled trigger',
|
|
125
|
+
version: '1.0.0',
|
|
126
|
+
status: 'dev',
|
|
127
|
+
},
|
|
128
|
+
contract: {
|
|
129
|
+
inputSchema: z.object({
|
|
130
|
+
scheduledAt: z.string(),
|
|
131
|
+
jobInput: z.record(z.unknown()).optional(),
|
|
132
|
+
}),
|
|
133
|
+
outputSchema: z.object({
|
|
134
|
+
completed: z.boolean(),
|
|
135
|
+
processedAt: z.string(),
|
|
136
|
+
summary: z.string(),
|
|
137
|
+
}),
|
|
138
|
+
},
|
|
139
|
+
steps: {
|
|
140
|
+
run: {
|
|
141
|
+
id: 'run',
|
|
142
|
+
name: 'Run Job',
|
|
143
|
+
description: 'Execute the recurring job logic',
|
|
144
|
+
inputSchema: z.object({ scheduledAt: z.string(), jobInput: z.record(z.unknown()).optional() }),
|
|
145
|
+
outputSchema: z.object({ completed: z.boolean(), processedAt: z.string(), summary: z.string() }),
|
|
146
|
+
handler: async (input, context) => {
|
|
147
|
+
const { scheduledAt, jobInput } = input as { scheduledAt: string; jobInput?: Record<string, unknown> }
|
|
148
|
+
|
|
149
|
+
context.logger.info('Recurring job started', { scheduledAt, jobInput })
|
|
150
|
+
|
|
151
|
+
// === REPLACE THIS SECTION WITH THE ACTUAL JOB LOGIC ===
|
|
152
|
+
// Example: fetch data, process it, send a report
|
|
153
|
+
const summary = `Job completed at ${new Date().toISOString()}`
|
|
154
|
+
// ======================================================
|
|
155
|
+
|
|
156
|
+
return { completed: true, processedAt: new Date().toISOString(), summary }
|
|
157
|
+
},
|
|
158
|
+
next: null,
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
entryPoint: 'run',
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Common Cron Expressions
|
|
168
|
+
|
|
169
|
+
| Schedule | Expression | Notes |
|
|
170
|
+
| --- | --- | --- |
|
|
171
|
+
| Every day at 9am (weekdays) | `0 9 * * 1-5` | Monday-Friday |
|
|
172
|
+
| Every day at midnight | `0 0 * * *` | UTC unless timezone set |
|
|
173
|
+
| Every hour | `0 * * * *` | On the hour |
|
|
174
|
+
| Every Monday at 8am | `0 8 * * 1` | |
|
|
175
|
+
| First of each month | `0 0 1 * *` | Midnight UTC |
|
|
176
|
+
| Every 15 minutes | `*/15 * * * *` | Frequent polling |
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Adaptation Notes
|
|
181
|
+
|
|
182
|
+
- **Job logic:** Replace the placeholder comment in the `run` handler with the actual job logic. Ask the user what the job should do before generating the full workflow.
|
|
183
|
+
- **Both workflows needed:** Remind the user to add both `recurringJob` and `recurringJobSetup` to their `src/index.ts` registry. Run `setup` once to activate; only `recurring-job` runs automatically thereafter.
|
|
184
|
+
- **Idempotency key:** The `recurring-job-schedule` key ensures re-running setup does not create duplicate schedules.
|
|
185
|
+
- **Timezone:** Always ask the user their preferred timezone. Defaults to UTC which may cause unexpected run times.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
**Last Updated:** 2026-02-26
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Template: Text Classifier"
|
|
3
|
+
description: "Multi-label text classification with structured output -- classify text into predefined categories using an LLM with JSON output"
|
|
4
|
+
loadWhen: "Applying the text-classifier workflow template"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**Category:** AI
|
|
8
|
+
|
|
9
|
+
**Platform Tools:** `llm` (structured generation)
|
|
10
|
+
|
|
11
|
+
**Credentials Required:**
|
|
12
|
+
|
|
13
|
+
- LLM API keys are resolved server-side from platform configuration (no credential name needed)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## What This Workflow Does
|
|
18
|
+
|
|
19
|
+
Classifies input text into one or more predefined categories using an LLM. Returns structured output with category assignments and confidence reasoning. Suitable for ticket categorization, email routing, content tagging, sentiment analysis, and any text classification task where the categories are known in advance.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Input Schema
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
z.object({
|
|
27
|
+
text: z.string(), // Text to classify
|
|
28
|
+
categories: z.array(z.string()), // Available category labels
|
|
29
|
+
multiLabel: z.boolean().optional(), // Allow multiple categories (default: false)
|
|
30
|
+
instructions: z.string().optional(), // Additional instructions for the LLM
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Output Schema
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
z.object({
|
|
38
|
+
categories: z.array(z.string()), // Assigned category labels
|
|
39
|
+
reasoning: z.string(), // LLM explanation of classification
|
|
40
|
+
confidence: z.enum(['high', 'medium', 'low']),
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Workflow Code Pattern
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import type { WorkflowDefinition } from '@elevasis/sdk'
|
|
50
|
+
import { platform } from '@elevasis/sdk/worker'
|
|
51
|
+
import { z } from 'zod'
|
|
52
|
+
|
|
53
|
+
const inputSchema = z.object({
|
|
54
|
+
text: z.string(),
|
|
55
|
+
categories: z.array(z.string()),
|
|
56
|
+
multiLabel: z.boolean().optional(),
|
|
57
|
+
instructions: z.string().optional(),
|
|
58
|
+
})
|
|
59
|
+
const outputSchema = z.object({
|
|
60
|
+
categories: z.array(z.string()),
|
|
61
|
+
reasoning: z.string(),
|
|
62
|
+
confidence: z.enum(['high', 'medium', 'low']),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
type Input = z.infer<typeof inputSchema>
|
|
66
|
+
|
|
67
|
+
export const textClassifier: WorkflowDefinition = {
|
|
68
|
+
config: {
|
|
69
|
+
resourceId: 'text-classifier',
|
|
70
|
+
name: 'Text Classifier',
|
|
71
|
+
type: 'workflow',
|
|
72
|
+
description: 'Classifies text into predefined categories using an LLM',
|
|
73
|
+
version: '1.0.0',
|
|
74
|
+
status: 'dev',
|
|
75
|
+
},
|
|
76
|
+
contract: { inputSchema, outputSchema },
|
|
77
|
+
steps: {
|
|
78
|
+
classify: {
|
|
79
|
+
id: 'classify',
|
|
80
|
+
name: 'Classify Text',
|
|
81
|
+
description: 'Use LLM to classify the input text',
|
|
82
|
+
inputSchema,
|
|
83
|
+
outputSchema,
|
|
84
|
+
handler: async (input) => {
|
|
85
|
+
const { text, categories, multiLabel, instructions } = input as Input
|
|
86
|
+
const mode = multiLabel ? 'one or more' : 'exactly one'
|
|
87
|
+
const categoryList = categories.map(c => `- ${c}`).join('\n')
|
|
88
|
+
|
|
89
|
+
const prompt = `Classify the following text into ${mode} of these categories:
|
|
90
|
+
${categoryList}
|
|
91
|
+
${instructions ? `\nAdditional instructions: ${instructions}` : ''}
|
|
92
|
+
|
|
93
|
+
Text to classify:
|
|
94
|
+
"${text}"
|
|
95
|
+
|
|
96
|
+
Return JSON with:
|
|
97
|
+
- categories: array of matching category names (from the list above only)
|
|
98
|
+
- reasoning: brief explanation of your classification
|
|
99
|
+
- confidence: "high" (very clear match), "medium" (reasonable but uncertain), or "low" (ambiguous)`
|
|
100
|
+
|
|
101
|
+
const result = await platform.call({
|
|
102
|
+
tool: 'llm',
|
|
103
|
+
method: 'generate',
|
|
104
|
+
params: {
|
|
105
|
+
provider: 'openai',
|
|
106
|
+
model: 'gpt-4o-mini',
|
|
107
|
+
messages: [{ role: 'user', content: prompt }],
|
|
108
|
+
responseSchema: {
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {
|
|
111
|
+
categories: { type: 'array', items: { type: 'string' } },
|
|
112
|
+
reasoning: { type: 'string' },
|
|
113
|
+
confidence: { type: 'string', enum: ['high', 'medium', 'low'] },
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
}) as { categories: string[]; reasoning: string; confidence: 'high' | 'medium' | 'low' }
|
|
118
|
+
|
|
119
|
+
// Validate that returned categories are from the allowed list
|
|
120
|
+
const validCategories = result.categories.filter(c => categories.includes(c))
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
categories: validCategories,
|
|
124
|
+
reasoning: result.reasoning,
|
|
125
|
+
confidence: result.confidence,
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
next: null,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
entryPoint: 'classify',
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Adaptation Notes
|
|
138
|
+
|
|
139
|
+
- **Category design:** Help the user define clear, mutually exclusive category names. Ambiguous categories produce inconsistent results.
|
|
140
|
+
- **Multi-label use:** Enable `multiLabel: true` when a single piece of text can legitimately belong to multiple categories (e.g., a support ticket about "billing AND account access").
|
|
141
|
+
- **Confidence routing:** Common pattern: use the output with `StepType.CONDITIONAL` routing. High confidence routes to automated processing; low confidence routes to human review.
|
|
142
|
+
- **Model selection:** `gpt-4o-mini` is fast and cost-effective for classification. For complex or nuanced categorization, suggest `gpt-4o`.
|
|
143
|
+
- **Skill adaptation:** For beginners, explain what "structured output" means and why the LLM returns JSON instead of free text.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
**Last Updated:** 2026-02-26
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Template: Web Scraper"
|
|
3
|
+
description: "Apify-based web scraper that stores results in Supabase -- fetch structured data from any website via Apify actors"
|
|
4
|
+
loadWhen: "Applying the web-scraper workflow template"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**Category:** Data Collection
|
|
8
|
+
|
|
9
|
+
**Platform Tools:** `apify` (run actor), `supabase` (insert results)
|
|
10
|
+
|
|
11
|
+
**Credentials Required:**
|
|
12
|
+
|
|
13
|
+
- `apify` -- Apify API token (bearer type)
|
|
14
|
+
- `my-database` -- Supabase project URL and service role key
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## What This Workflow Does
|
|
19
|
+
|
|
20
|
+
Runs an Apify actor to scrape structured data from a URL, then stores the scraped results in a Supabase table. The actor and table names are configurable. Suitable for extracting product listings, job postings, contact information, or any structured web content.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Input Schema
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
z.object({
|
|
28
|
+
actorId: z.string(), // Apify actor ID (e.g., 'apify/web-scraper')
|
|
29
|
+
startUrls: z.array(z.string()), // URLs to scrape
|
|
30
|
+
tableName: z.string(), // Supabase table to store results in
|
|
31
|
+
maxItems: z.number().optional(), // Maximum number of items to collect (default: 100)
|
|
32
|
+
})
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Output Schema
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
z.object({
|
|
39
|
+
itemsScraped: z.number(), // Total items scraped
|
|
40
|
+
itemsInserted: z.number(), // Items successfully inserted into Supabase
|
|
41
|
+
runId: z.string(), // Apify run ID for reference
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Workflow Code Pattern
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import type { WorkflowDefinition } from '@elevasis/sdk'
|
|
51
|
+
import { StepType } from '@elevasis/sdk'
|
|
52
|
+
import { platform, PlatformToolError } from '@elevasis/sdk/worker'
|
|
53
|
+
import { z } from 'zod'
|
|
54
|
+
|
|
55
|
+
const inputSchema = z.object({
|
|
56
|
+
actorId: z.string(),
|
|
57
|
+
startUrls: z.array(z.string()),
|
|
58
|
+
tableName: z.string(),
|
|
59
|
+
maxItems: z.number().optional(),
|
|
60
|
+
})
|
|
61
|
+
const outputSchema = z.object({
|
|
62
|
+
itemsScraped: z.number(),
|
|
63
|
+
itemsInserted: z.number(),
|
|
64
|
+
runId: z.string(),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
type Input = z.infer<typeof inputSchema>
|
|
68
|
+
|
|
69
|
+
export const webScraper: WorkflowDefinition = {
|
|
70
|
+
config: {
|
|
71
|
+
resourceId: 'web-scraper',
|
|
72
|
+
name: 'Web Scraper',
|
|
73
|
+
type: 'workflow',
|
|
74
|
+
description: 'Scrapes structured data via Apify and stores in Supabase',
|
|
75
|
+
version: '1.0.0',
|
|
76
|
+
status: 'dev',
|
|
77
|
+
},
|
|
78
|
+
contract: { inputSchema, outputSchema },
|
|
79
|
+
steps: {
|
|
80
|
+
scrape: {
|
|
81
|
+
id: 'scrape',
|
|
82
|
+
name: 'Run Apify Actor',
|
|
83
|
+
description: 'Execute the Apify actor and collect results',
|
|
84
|
+
inputSchema,
|
|
85
|
+
outputSchema: z.object({ items: z.array(z.unknown()), runId: z.string() }),
|
|
86
|
+
handler: async (input) => {
|
|
87
|
+
const { actorId, startUrls, maxItems } = input as Input
|
|
88
|
+
const result = await platform.call({
|
|
89
|
+
tool: 'apify',
|
|
90
|
+
method: 'runActor',
|
|
91
|
+
credential: 'apify',
|
|
92
|
+
params: { actorId, input: { startUrls: startUrls.map(url => ({ url })), maxItems: maxItems ?? 100 } },
|
|
93
|
+
}) as { items: unknown[]; runId: string }
|
|
94
|
+
return { items: result.items, runId: result.runId }
|
|
95
|
+
},
|
|
96
|
+
next: { type: StepType.LINEAR, target: 'store' },
|
|
97
|
+
},
|
|
98
|
+
store: {
|
|
99
|
+
id: 'store',
|
|
100
|
+
name: 'Store Results',
|
|
101
|
+
description: 'Insert scraped items into Supabase',
|
|
102
|
+
inputSchema: z.object({ items: z.array(z.unknown()), runId: z.string(), tableName: z.string() }),
|
|
103
|
+
outputSchema,
|
|
104
|
+
handler: async (input, context) => {
|
|
105
|
+
const { items, runId, tableName } = input as { items: unknown[]; runId: string; tableName: string }
|
|
106
|
+
const data = items.map(item => ({ ...(item as Record<string, unknown>), scraped_at: new Date().toISOString() }))
|
|
107
|
+
await platform.call({
|
|
108
|
+
tool: 'supabase',
|
|
109
|
+
method: 'insert',
|
|
110
|
+
credential: 'my-database',
|
|
111
|
+
params: { table: tableName, data },
|
|
112
|
+
})
|
|
113
|
+
context.logger.info('Stored scraped items', { count: data.length, table: tableName })
|
|
114
|
+
return { itemsScraped: items.length, itemsInserted: data.length, runId }
|
|
115
|
+
},
|
|
116
|
+
next: null,
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
entryPoint: 'scrape',
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Adaptation Notes
|
|
126
|
+
|
|
127
|
+
- **Credential names:** Replace `'apify'` and `'my-database'` with the credential names the user configured in the command center.
|
|
128
|
+
- **Table schema:** Ensure the Supabase table exists and has columns that match the scraped data fields. The agent should check `data/schema.ts` if it exists.
|
|
129
|
+
- **Actor selection:** Apify has hundreds of public actors. Common choices: `apify/web-scraper` (generic), `apify/cheerio-scraper` (fast HTML parsing), specialized actors for LinkedIn, Amazon, etc.
|
|
130
|
+
- **Error handling:** Add try/catch for `PlatformToolError` if partial failure tolerance is needed.
|
|
131
|
+
- **Skill adaptation:** For beginners, explain what Apify is and walk through actor selection before generating code.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
**Last Updated:** 2026-02-26
|