@superglue/cli 1.1.17 → 1.1.19
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/.claude-plugin/plugin.json +1 -1
- package/dist/cli.js +832 -23922
- package/dist/skills/superglue/SKILL.md +240 -27
- package/dist/skills/superglue/references/access-rules.md +137 -0
- package/dist/skills/superglue/references/data-handling.md +303 -0
- package/dist/skills/superglue/references/databases.md +7 -3
- package/dist/skills/superglue/references/file-handling.md +160 -0
- package/dist/skills/superglue/references/file-servers.md +13 -37
- package/dist/skills/superglue/references/http-apis.md +248 -0
- package/dist/skills/superglue/references/redis.md +4 -5
- package/package.json +2 -1
- package/skills/superglue/SKILL.md +240 -27
- package/skills/superglue/references/access-rules.md +137 -0
- package/skills/superglue/references/data-handling.md +303 -0
- package/skills/superglue/references/databases.md +7 -3
- package/skills/superglue/references/file-handling.md +160 -0
- package/skills/superglue/references/file-servers.md +13 -37
- package/skills/superglue/references/http-apis.md +248 -0
- package/skills/superglue/references/redis.md +4 -5
- package/dist/dist/cli.js +0 -24370
- package/dist/dist/skills/superglue/SKILL.md +0 -678
- package/dist/dist/skills/superglue/agents/openai.yaml +0 -7
- package/dist/dist/skills/superglue/assets/superglue.png +0 -0
- package/dist/dist/skills/superglue/references/databases.md +0 -249
- package/dist/dist/skills/superglue/references/file-servers.md +0 -136
- package/dist/dist/skills/superglue/references/integration.md +0 -279
- package/dist/dist/skills/superglue/references/redis.md +0 -190
- package/dist/dist/skills/superglue/references/transforms-and-output.md +0 -92
- package/dist/skills/superglue/references/transforms-and-output.md +0 -92
- package/skills/superglue/references/transforms-and-output.md +0 -92
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Access Rules (RBAC)
|
|
2
|
+
|
|
3
|
+
On superglue, roles define tool-level and system-level allowlists. Users can have multiple roles. Roles and access rules are only available on enterprise organizations. Non-enterprise orgs do not have RBAC — all users have admin access on personal organizations.
|
|
4
|
+
|
|
5
|
+
## Data Model
|
|
6
|
+
|
|
7
|
+
### Role
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
interface Role {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
tools: "ALL" | string[]; // tool allowlist
|
|
15
|
+
systems: "ALL" | Record<string, SystemPermission>; // system allowlist
|
|
16
|
+
isBaseRole?: boolean; // true for admin, member, enduser
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### SystemPermission
|
|
21
|
+
|
|
22
|
+
Each system entry is either a predefined access level or a custom rule:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
type SystemPermission = SystemAccessLevel | { rules: CustomRule[] };
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| Type | Meaning |
|
|
29
|
+
| ------------------ | ------------------------------------------------ |
|
|
30
|
+
| `"read-write"` | All HTTP methods allowed |
|
|
31
|
+
| `"read-only"` | Only GET and HEAD; POST/PUT/PATCH/DELETE blocked |
|
|
32
|
+
| `{ rules: [...] }` | Custom JS expressions that gate access |
|
|
33
|
+
|
|
34
|
+
Systems not listed in the role's `systems` map are DENIED, unless the system access is set to `"ALL"`.
|
|
35
|
+
|
|
36
|
+
### CustomRule
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
interface CustomRule {
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
expression?: string; // JS expression, receives stepConfig
|
|
43
|
+
isActive: boolean;
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Custom rules are inline in the systems map. A system either has a standard access level OR a custom rule, never both. Custom rules must return truthy to **allow** the request. If it returns falsy, throws, or has no expression, the request is blocked (fail-closed).
|
|
48
|
+
|
|
49
|
+
## Tool Permissions
|
|
50
|
+
|
|
51
|
+
- `tools: "ALL"` — every tool is allowed, including tools created in the future
|
|
52
|
+
- `tools: ["tool-id-1", "tool-id-2"]` — only these specific tools are allowed; new tools are NOT auto-included (but see Auto-Append below)
|
|
53
|
+
|
|
54
|
+
## System Permissions
|
|
55
|
+
|
|
56
|
+
- `systems: "ALL"` — every system allowed at READ_WRITE level, including future systems. Custom rules are not possible in this mode.
|
|
57
|
+
- `systems: { "gmail": "read-write", "stripe": "read-only" }` — SPECIFIC mode: per-system access levels; unlisted systems are denied.
|
|
58
|
+
- `systems: { "gmail": { rules: [{ name: "block-deletes", expression: "stepConfig.method !== 'DELETE'", isActive: true }] } }` — custom rule on a specific system
|
|
59
|
+
|
|
60
|
+
## Mutation Detection by Protocol
|
|
61
|
+
|
|
62
|
+
Read-only mode blocks mutating requests. How "mutating" is determined per protocol:
|
|
63
|
+
|
|
64
|
+
- **HTTP** — POST, PUT, PATCH, DELETE are mutating; GET and HEAD are read-only
|
|
65
|
+
- **Postgres** — SQL statements starting with or containing INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, TRUNCATE, etc. are mutating; everything else (SELECT) is read-only
|
|
66
|
+
- **FTP / SMB** — operations other than `list`, `get`, `exists`, `stat` are mutating
|
|
67
|
+
- **Redis** — commands not in the read-only set (GET, MGET, EXISTS, KEYS, SCAN, TTL, TYPE, HGET, HGETALL, LRANGE, SMEMBERS, SCARD, etc.) are mutating
|
|
68
|
+
- **Transform** — never mutating (pure data transformation, no external system)
|
|
69
|
+
|
|
70
|
+
On parse failure, all protocols default to **mutating** (fail-closed).
|
|
71
|
+
|
|
72
|
+
## Custom Rule Expressions
|
|
73
|
+
|
|
74
|
+
Custom rules only apply in SPECIFIC mode. If systems is `"ALL"`, no custom rules exist.
|
|
75
|
+
|
|
76
|
+
Expressions receive a **resolved** `stepConfig` object — all template variables (`<<variable>>` and `<<(sourceData) => ...>>` expressions) are substituted with their actual runtime values before the rule evaluates. This means rules see the real URL, headers, body, and query params, not raw templates.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
interface StepConfig {
|
|
80
|
+
url: string;
|
|
81
|
+
method: string;
|
|
82
|
+
headers: Record<string, string>;
|
|
83
|
+
queryParams: Record<string, string>;
|
|
84
|
+
body: unknown;
|
|
85
|
+
systemId: string;
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Example expressions:
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
// Block all POST requests to a specific endpoint
|
|
93
|
+
stepConfig.method !== "POST" || !stepConfig.url.includes("/admin");
|
|
94
|
+
|
|
95
|
+
// Only allow GET requests to /api/v1/read endpoints
|
|
96
|
+
stepConfig.method === "GET" && stepConfig.url.includes("/api/v1/read");
|
|
97
|
+
|
|
98
|
+
// Block requests with certain headers
|
|
99
|
+
!stepConfig.headers["x-dangerous-header"];
|
|
100
|
+
|
|
101
|
+
// Block a specific datasource ID in the resolved body
|
|
102
|
+
!(typeof stepConfig.body === "object" && stepConfig.body?.datasource?.id === "blocked-id");
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Multi-Role Semantics
|
|
106
|
+
|
|
107
|
+
Users can have multiple roles. Resolution is **union (most permissive wins) everywhere**:
|
|
108
|
+
|
|
109
|
+
| Layer | Semantics | Example |
|
|
110
|
+
| ------------ | ------------------------------ | --------------------------------------------------------------------------- |
|
|
111
|
+
| Tools | Union (most permissive wins) | If role A allows tool X and role B doesn't, tool X is allowed |
|
|
112
|
+
| Systems | Union (most permissive wins) | If role A gives READ_ONLY and role B gives READ_WRITE, result is READ_WRITE |
|
|
113
|
+
| Custom rules | Per-role union (any role wins) | If any role's complete rule set allows the request, it goes through |
|
|
114
|
+
|
|
115
|
+
Custom rule detail:
|
|
116
|
+
|
|
117
|
+
- **Within a single role**, all custom rules for that system must pass (AND logic)
|
|
118
|
+
- **Across roles**, evaluation is per-role: if any role fully allows the request (all its rules pass, or it has no custom rule for that system), the request is allowed (OR logic)
|
|
119
|
+
|
|
120
|
+
## Base Roles
|
|
121
|
+
|
|
122
|
+
Every user has exactly **one** base role. There are three:
|
|
123
|
+
|
|
124
|
+
- **`admin`** — Full access to everything. Bypasses all RBAC checks. The admin role is **immutable** — it cannot be edited at all, nor can it be re-assigned.
|
|
125
|
+
- **`member`** — Default for org team members. Starts with `tools: "ALL"`, `systems: "ALL"`. Tool and system allowlists can be narrowed to restrict access. Name and description cannot be changed. Cannot be deleted.
|
|
126
|
+
- **`enduser`** — Default for end users / portal consumers. Starts with `tools: []`, `systems: {}` (no access). Tool and system allowlists must be explicitly populated to grant access. Name and description cannot be changed. Cannot be deleted.
|
|
127
|
+
|
|
128
|
+
Base roles define the starting permissions that custom roles can extend or build on top of. Users can also have additional **custom roles** on top of their base role. Custom roles are fully editable (name, description, tools, systems) and can be created and deleted.
|
|
129
|
+
|
|
130
|
+
### Auto-append on resource creation
|
|
131
|
+
|
|
132
|
+
When a user creates a new tool or system, the backend automatically adds it to the **creator's base role** — but only if that base role uses SPECIFIC mode (not `"ALL"`) for the relevant field:
|
|
133
|
+
|
|
134
|
+
- New tool created → appended to the creator's base role's `tools` list (skipped if `tools: "ALL"`)
|
|
135
|
+
- New system created → appended to the creator's base role's `systems` map with `read-write` access (skipped if `systems: "ALL"`)
|
|
136
|
+
|
|
137
|
+
This only affects the base role, not additional custom roles. It ensures that a user on a restricted allowlist automatically gets access to resources they create.
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# Data Handling: Variables, Selectors & Transforms
|
|
2
|
+
|
|
3
|
+
Most tool building and editing failures trace back to incorrect variable syntax, wrong data selector returns, or misunderstanding of the tool result envelope.
|
|
4
|
+
|
|
5
|
+
## Variable Replacement (`<<>>` Syntax)
|
|
6
|
+
|
|
7
|
+
The `<<expression>>` syntax injects dynamic values into step config fields (URL, headers, body, queryParams).
|
|
8
|
+
|
|
9
|
+
### Simple Variables (top-level keys only)
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
<<userId>> payload keys
|
|
13
|
+
<<currentItem>> whole current loop item
|
|
14
|
+
<<page>> pagination variable
|
|
15
|
+
<<systemId_api_key>> system credential
|
|
16
|
+
<<systemId_url>> system base URL
|
|
17
|
+
<<sg_auth_email>> email of the authenticated user
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**CRITICAL**: Simple `<<varName>>` only works for top-level keys. NO dots, NO nesting.
|
|
21
|
+
|
|
22
|
+
- VALID: `<<userId>>`, `<<currentItem>>`, `<<page>>`, `<<stripe_api_key>>`, `<<stripe_url>>`, `<<sg_auth_email>>`
|
|
23
|
+
- INVALID: `<<currentItem.id>>`, `<<sourceData.userId>>`, `<<user.name>>` — these FAIL at runtime
|
|
24
|
+
|
|
25
|
+
### Arrow Function Expressions (for everything else)
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
<<(sourceData) => sourceData.currentItem.player_id>>
|
|
29
|
+
<<(sourceData) => sourceData.getUsers.data.map(u => u.id)>>
|
|
30
|
+
<<(sourceData) => JSON.stringify({ ids: sourceData.fetchUsers.data.map(u => u.id) })>>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Resolution Order
|
|
34
|
+
|
|
35
|
+
1. Direct lookup: check if expression is a top-level key in the merged variable object
|
|
36
|
+
2. Arrow function: execute in Deno subprocess sandbox with full data as `sourceData`
|
|
37
|
+
3. Failure: throws error listing available keys
|
|
38
|
+
|
|
39
|
+
### Variable Sources
|
|
40
|
+
|
|
41
|
+
All variables come from a single merged object:
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
{
|
|
45
|
+
...originalPayload, // user-provided input
|
|
46
|
+
...previousStepResults, // keyed by stepId
|
|
47
|
+
...systemCredentials, // namespaced: systemId_credKey
|
|
48
|
+
...systemUrls, // namespaced: systemId_url
|
|
49
|
+
sg_auth_email, // email of authenticated user (context variable)
|
|
50
|
+
currentItem, // current loop item (if in a loop)
|
|
51
|
+
page, offset, cursor, // pagination variables
|
|
52
|
+
limit, pageSize // pagination size
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Context Variables
|
|
57
|
+
|
|
58
|
+
Context variables provide information about the execution environment:
|
|
59
|
+
|
|
60
|
+
- `<<sg_auth_email>>` — Email address of the authenticated user who triggered the tool execution
|
|
61
|
+
|
|
62
|
+
**Use case**: Personalize tool outputs based on who is running the tool. For example:
|
|
63
|
+
|
|
64
|
+
- Different permissions in Tableau based on user
|
|
65
|
+
- User-specific account lookups
|
|
66
|
+
- Audit logging with user identity
|
|
67
|
+
|
|
68
|
+
**Important**: `sg_auth_email` is NOT available in scheduled executions (cron jobs). If a tool references `<<sg_auth_email>>` and runs via scheduler, it will fail with a clear error message.
|
|
69
|
+
|
|
70
|
+
### Return Type Handling
|
|
71
|
+
|
|
72
|
+
- Objects/arrays returned from expressions → JSON.stringified automatically
|
|
73
|
+
- Resolved values of `"undefined"` or `"null"` → stripped from headers and queryParams
|
|
74
|
+
|
|
75
|
+
### Special: Basic Auth
|
|
76
|
+
|
|
77
|
+
`"Authorization": "Basic <<user>>:<<pass>>"` is auto-converted to Base64. Do NOT manually encode.
|
|
78
|
+
|
|
79
|
+
## Credential Resolution
|
|
80
|
+
|
|
81
|
+
Credentials are available via `<<>>` syntax. Two sources, payload takes precedence:
|
|
82
|
+
|
|
83
|
+
1. **System credentials**: Stored on System, namespaced as `<<systemId_credKey>>`
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
// System id="stripe", credentials={ api_key: "sk_..." }
|
|
87
|
+
// Available as: <<stripe_api_key>>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
2. **Payload credentials**: Passed at runtime, accessed directly
|
|
91
|
+
```javascript
|
|
92
|
+
// Payload: { user_access_token: "abc" }
|
|
93
|
+
// Available as: <<user_access_token>>
|
|
94
|
+
// Or: <<(sourceData) => sourceData.user_access_token>>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
For OAuth systems, use `<<systemId_access_token>>` — the token value is auto-refreshed during tool execution, but the header itself must be present in the step config.
|
|
98
|
+
|
|
99
|
+
## System URL Resolution
|
|
100
|
+
|
|
101
|
+
System URLs are available via `<<systemId_url>>` syntax. This enables environment-agnostic tools:
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
// System id="salesforce", url="https://mycompany.salesforce.com"
|
|
105
|
+
// Available as: <<salesforce_url>>
|
|
106
|
+
|
|
107
|
+
// Step config:
|
|
108
|
+
{
|
|
109
|
+
"url": "<<salesforce_url>>/services/data/v58.0/sobjects/Account",
|
|
110
|
+
"headers": { "Authorization": "Bearer <<salesforce_access_token>>" }
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Why use system URL variables?**
|
|
115
|
+
|
|
116
|
+
- Same tool works across dev/prod environments without modification
|
|
117
|
+
- When switching execution mode, the system URL resolves to the appropriate environment
|
|
118
|
+
- Avoids hardcoding base URLs that differ between environments
|
|
119
|
+
|
|
120
|
+
## Data Selectors
|
|
121
|
+
|
|
122
|
+
Every step has a `dataSelector` — a JS function that controls execution mode and what `currentItem` is.
|
|
123
|
+
|
|
124
|
+
### Returns OBJECT → Single Execution
|
|
125
|
+
|
|
126
|
+
Step runs once. The object becomes `currentItem`.
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
(sourceData) => ({ userId: sourceData.userId })
|
|
130
|
+
(sourceData) => ({}) // run once with no specific input
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Result stored as:
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
sourceData.stepId = { currentItem: <object>, data: <response>, success: true }
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Returns ARRAY → Loop Execution
|
|
140
|
+
|
|
141
|
+
Step runs once per array element. Each element becomes `currentItem`.
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
(sourceData) => sourceData.getContacts.data.filter(c => c.active)
|
|
145
|
+
(sourceData) => sourceData.userIds // if userIds is an array from payload
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Result stored as:
|
|
149
|
+
|
|
150
|
+
```javascript
|
|
151
|
+
sourceData.stepId = [
|
|
152
|
+
{ currentItem: <item1>, data: <response1>, success: true },
|
|
153
|
+
{ currentItem: <item2>, data: <response2>, success: true },
|
|
154
|
+
...
|
|
155
|
+
]
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Empty arrays are valid — step just skips execution.
|
|
159
|
+
|
|
160
|
+
### Accessing currentItem in Step Config
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
<<currentItem>> // whole value
|
|
164
|
+
<<(sourceData) => sourceData.currentItem.id>> // property
|
|
165
|
+
<<(sourceData) => sourceData.currentItem.name.toUpperCase()>> // with transform
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Result Envelope
|
|
169
|
+
|
|
170
|
+
**Every** step result (request or transform) is wrapped identically:
|
|
171
|
+
|
|
172
|
+
### From Object Selector
|
|
173
|
+
|
|
174
|
+
```javascript
|
|
175
|
+
sourceData.stepId = {
|
|
176
|
+
currentItem: <selectorOutput>,
|
|
177
|
+
data: <apiResponse or transformResult>,
|
|
178
|
+
success: true
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Access:
|
|
182
|
+
sourceData.stepId.data // the response
|
|
183
|
+
sourceData.stepId.currentItem // what was passed in
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### From Array Selector
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
sourceData.stepId = [
|
|
190
|
+
{ currentItem: <item1>, data: <response1>, success: true },
|
|
191
|
+
{ currentItem: <item2>, data: <response2>, success: false, error: "..." },
|
|
192
|
+
...
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
// Access:
|
|
196
|
+
sourceData.stepId.map(item => item.data) // all responses
|
|
197
|
+
sourceData.stepId.flatMap(item => item.data.results) // nested arrays
|
|
198
|
+
sourceData.stepId.filter(item => item.success).map(i => i.data) // only successes
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### From Paginated Step
|
|
202
|
+
|
|
203
|
+
Paginated steps merge all pages server-side into a **single** result. The result is always a single envelope, NOT an array — even though multiple pages were fetched.
|
|
204
|
+
|
|
205
|
+
```javascript
|
|
206
|
+
sourceData.stepId = {
|
|
207
|
+
data: <merged response from all pages>,
|
|
208
|
+
success: true
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Access:
|
|
212
|
+
sourceData.stepId.data // the merged response
|
|
213
|
+
sourceData.stepId.data.items // if the API wraps results in .items
|
|
214
|
+
|
|
215
|
+
// WRONG — do NOT treat paginated results as an array of envelopes:
|
|
216
|
+
sourceData.stepId.map(...) // FAILS — stepId is an object, not an array
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## sourceData Structure
|
|
220
|
+
|
|
221
|
+
`sourceData` is the cumulative state available to data selectors, variable expressions, and transforms:
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
sourceData = {
|
|
225
|
+
// Original payload fields at ROOT level (NOT under .payload)
|
|
226
|
+
userId: "abc",
|
|
227
|
+
date: "2024-01-15",
|
|
228
|
+
companies: ["acme", "globex"],
|
|
229
|
+
|
|
230
|
+
// Previous step results, keyed by step ID
|
|
231
|
+
getUsers: { currentItem: {}, data: { users: [...] }, success: true },
|
|
232
|
+
fetchDetails: [
|
|
233
|
+
{ currentItem: { id: 1 }, data: { name: "Alice" }, success: true },
|
|
234
|
+
{ currentItem: { id: 2 }, data: { name: "Bob" }, success: true },
|
|
235
|
+
],
|
|
236
|
+
|
|
237
|
+
// Current item (only within a loop step's config)
|
|
238
|
+
currentItem: { id: 1 },
|
|
239
|
+
|
|
240
|
+
// Runtime file map — see file-handling skill for full reference
|
|
241
|
+
__files__: { ... }
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Treat `sourceData.__files__` as read-only. See the file-handling skill for file detection, the `RuntimeExecutionFile` shape, aliasing rules, and how to access files in transforms.
|
|
246
|
+
|
|
247
|
+
## Three Transformation Points
|
|
248
|
+
|
|
249
|
+
| Point | When it runs | Purpose |
|
|
250
|
+
| -------------------------------- | ------------------------ | ------------------------------------ |
|
|
251
|
+
| `dataSelector` | Before step executes | Controls input + single vs loop mode |
|
|
252
|
+
| Transform step (`transformCode`) | As the step itself | Reshape data between request steps |
|
|
253
|
+
| `outputTransform` | After all steps complete | Shape final tool output |
|
|
254
|
+
|
|
255
|
+
## Transform Steps
|
|
256
|
+
|
|
257
|
+
Transform steps use `type: "transform"` with a `transformCode` function. They do NOT have: systemId, url, method, headers, body, queryParams, or pagination.
|
|
258
|
+
|
|
259
|
+
Results are wrapped in the **same envelope** as request steps:
|
|
260
|
+
|
|
261
|
+
```javascript
|
|
262
|
+
sourceData.formatForInsert = { currentItem: <dataSelector output>, data: <transformCode result>, success: true }
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
If the step has a dataSelector returning an array, the transform runs once per item. See tool-building guidelines for when to use transform steps vs dataSelectors vs outputTransform.
|
|
266
|
+
|
|
267
|
+
### Returning Files From a Transform
|
|
268
|
+
|
|
269
|
+
Transform steps can produce files by returning a reserved `__files__` key in the result. See the file-handling skill for the full shape, aliasing rules, and access patterns.
|
|
270
|
+
|
|
271
|
+
## Output Transform
|
|
272
|
+
|
|
273
|
+
Final transformation shaping the tool's output. Runs after all steps complete. Can access cumulative step result data and payloads.
|
|
274
|
+
|
|
275
|
+
### Requirements
|
|
276
|
+
|
|
277
|
+
- Function signature: `(sourceData) => { ... }`
|
|
278
|
+
- **Must have a return statement**
|
|
279
|
+
- Async/await IS supported (same Deno sandbox as transform steps)
|
|
280
|
+
- NEVER include newlines or tabs in the code string
|
|
281
|
+
|
|
282
|
+
## JS Code Constraints (all transform points)
|
|
283
|
+
|
|
284
|
+
Runs in a Deno subprocess sandbox:
|
|
285
|
+
|
|
286
|
+
- 8192 MB memory limit, 10 min timeout (transforms/selectors), 3s timeout (pagination stops)
|
|
287
|
+
- Async/await IS supported in transforms
|
|
288
|
+
- No filesystem access — `--deny-read`, `--deny-write`
|
|
289
|
+
- No subprocess spawning — `--deny-run`
|
|
290
|
+
- Network access IS available (`--allow-net`) — transforms can make HTTP requests
|
|
291
|
+
- Environment variables are sandboxed — only safe vars (PATH, HOME, DENO_DIR) are visible, no server secrets
|
|
292
|
+
- JSON-serializable I/O only
|
|
293
|
+
- Must return a value (undefined returns null)
|
|
294
|
+
|
|
295
|
+
### Serialization Safety
|
|
296
|
+
|
|
297
|
+
All JS code is stored as a JSON string, then MessagePack-serialized to the Deno subprocess, then `eval()`'d. Patterns that break across this pipeline:
|
|
298
|
+
|
|
299
|
+
- **Regex literals with `/`** — `/https?:\/\/([^/]+)/` corrupts. Use `new URL(str).hostname` or `str.split("//")[1].split("/")[0]` instead.
|
|
300
|
+
- **Backslash-heavy patterns** — `\n`, `\t`, `\\` in regex/strings get misinterpreted. Prefer string methods (`.split()`, `.indexOf()`, `.includes()`) over regex.
|
|
301
|
+
- **Literal newlines** — code string must be single-line. Use `;` between statements.
|
|
302
|
+
|
|
303
|
+
**Rule of thumb**: prefer string methods over regex. When regex is unavoidable, use `new RegExp("pattern")` constructor over `/pattern/` literals.
|
|
@@ -13,7 +13,6 @@ Superglue supports both PostgreSQL and Microsoft SQL Server (including Azure SQL
|
|
|
13
13
|
type: "request",
|
|
14
14
|
systemId: "my_postgres_db",
|
|
15
15
|
url: "postgres://<<my_postgres_db_user>>:<<my_postgres_db_password>>@<<my_postgres_db_host>>:<<my_postgres_db_port>>/<<my_postgres_db_database>>",
|
|
16
|
-
method: "POST", // ignored for Postgres but required by schema
|
|
17
16
|
body: '{"query": "SELECT * FROM users WHERE id = $1", "params": [<<userId>>]}'
|
|
18
17
|
}
|
|
19
18
|
```
|
|
@@ -104,7 +103,6 @@ Use a data selector returning an array to execute a query per item:
|
|
|
104
103
|
type: "request",
|
|
105
104
|
systemId: "my_azure_sql",
|
|
106
105
|
url: "mssql://<<my_azure_sql_user>>:<<my_azure_sql_password>>@<<my_azure_sql_host>>:<<my_azure_sql_port>>/<<my_azure_sql_database>>",
|
|
107
|
-
method: "POST", // ignored for MSSQL but required by schema
|
|
108
106
|
body: '{"query": "SELECT * FROM users WHERE id = @param1", "params": [<<userId>>]}'
|
|
109
107
|
}
|
|
110
108
|
```
|
|
@@ -125,7 +123,13 @@ Both `mssql://` and `sqlserver://` protocols are supported. All credential varia
|
|
|
125
123
|
For Azure SQL Database, use the fully qualified server name:
|
|
126
124
|
|
|
127
125
|
```
|
|
128
|
-
mssql://myuser
|
|
126
|
+
mssql://myuser:mypassword@myserver.database.windows.net:1433/mydatabase
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Note: Azure SQL usernames often include `@servername` suffix (e.g., `myuser@myserver`). When using in URLs, encode the @ as `%40`:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
mssql://myuser%40myserver:mypassword@myserver.database.windows.net:1433/mydatabase
|
|
129
133
|
```
|
|
130
134
|
|
|
131
135
|
You can include connection parameters as query strings:
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# File Handling
|
|
2
|
+
|
|
3
|
+
How the runtime detects, parses, and exposes files across all protocol strategies (HTTP, FTP, SFTP, SMB).
|
|
4
|
+
|
|
5
|
+
## When Does a Response Become a File?
|
|
6
|
+
|
|
7
|
+
Every response is read as raw bytes. The runtime decides whether it routes to `producedFiles` (i.e. `sourceData.__files__`) or inline `data` using a priority chain — **not** based on size alone:
|
|
8
|
+
|
|
9
|
+
1. **Magic-byte detection** (primary) — the raw bytes are inspected for known binary signatures (ZIP `PK\x03\x04`, PDF `%PDF`, GZIP `\x1f\x8b`, plus internal ZIP structure checks for Excel/DOCX). If matched, the response is a file regardless of size or HTTP headers. A 1KB ZIP and a 500MB ZIP both go to `__files__`.
|
|
10
|
+
2. **`Content-Disposition: attachment`** — any response with `attachment` or `filename=` in this header becomes a file, even if byte detection found structured text.
|
|
11
|
+
3. **`application/octet-stream`** — explicit octet-stream MIME becomes a file, catching binary payloads whose bytes don't match a known signature.
|
|
12
|
+
4. **25MB size fallback** (last resort) — only applies when byte detection returned `RAW` (unrecognized format), the response is larger than 25MB, **and** the `Content-Type` is not clearly text-like (`text/*`, `application/json`, `*+json`, `application/xml`, etc.). This is the only path where size matters.
|
|
13
|
+
|
|
14
|
+
Structured text responses (JSON, XML, CSV, YAML, HTML) that pass none of the above checks go to `data` only — no file entry.
|
|
15
|
+
|
|
16
|
+
| Classification | Types | Where result lives |
|
|
17
|
+
| ------------------- | --------------------------- | -------------------------------------------------------------- |
|
|
18
|
+
| **Binary** | PDF, Excel, DOCX, ZIP, GZIP | `producedFiles` + `data` (extracted/parsed content) |
|
|
19
|
+
| **Structured text** | JSON, XML, CSV, YAML, HTML | `data` only (parsed to JS objects) |
|
|
20
|
+
| **RAW** | Unrecognized | File if attachment/octet-stream/25MB fallback, else raw string |
|
|
21
|
+
|
|
22
|
+
Binary responses expose `stepFileKeys` and populate `sourceData.__files__`. Structured text responses only populate `sourceData.stepId.data`.
|
|
23
|
+
|
|
24
|
+
File server `get` operations always produce files regardless of detected type — content goes into both `data` and `producedFiles`.
|
|
25
|
+
|
|
26
|
+
## Auto-Parsing
|
|
27
|
+
|
|
28
|
+
| Type | Parsed result |
|
|
29
|
+
| ----- | ------------------------------------------------------------- |
|
|
30
|
+
| JSON | Native JS object/array |
|
|
31
|
+
| CSV | Array of objects (header row = keys) |
|
|
32
|
+
| XML | JS object tree |
|
|
33
|
+
| HTML | JS object tree |
|
|
34
|
+
| YAML | Native JS object |
|
|
35
|
+
| Excel | Object keyed by sheet name → array of row objects (see below) |
|
|
36
|
+
| PDF | `{ textContent, structuredContent: [{ page, text }] }` |
|
|
37
|
+
| DOCX | Markdown string |
|
|
38
|
+
| ZIP | Object keyed by entry filename, each value recursively parsed |
|
|
39
|
+
| GZIP | Recursively parsed inner content |
|
|
40
|
+
| RAW | UTF-8 string fallback |
|
|
41
|
+
|
|
42
|
+
Detection is heuristic for text formats. YAML requires `---`, `%YAML`, or multiple `key: value` lines to be recognized. Short or ambiguous YAML can fall through to RAW and appear as a plain string instead of a parsed object — both in `.data` and `.extracted`.
|
|
43
|
+
|
|
44
|
+
### Excel rules
|
|
45
|
+
|
|
46
|
+
- Excel is NEVER a flat array. It is always `{ "SheetName": [ {row}, ... ] }` — even for single-sheet files.
|
|
47
|
+
- Access rows via `extracted.SheetName` or `extracted["Sheet1"]`, never `extracted[0]`.
|
|
48
|
+
- When producing xlsx, use namespace `http://schemas.openxmlformats.org/spreadsheetml/2006/main` (not the truncated path).
|
|
49
|
+
|
|
50
|
+
## file:: Reference Syntax
|
|
51
|
+
|
|
52
|
+
Use `file::<alias>.<suffix>` in step config `body` / `content` fields:
|
|
53
|
+
|
|
54
|
+
| Suffix | Returns | Use case |
|
|
55
|
+
| ------------ | --------------------------- | --------------------------------------- |
|
|
56
|
+
| `.raw` | Original bytes (Uint8Array) | SFTP/FTP put, HTTP raw body upload |
|
|
57
|
+
| `.base64` | Base64-encoded string | APIs expecting base64 inside JSON/XML |
|
|
58
|
+
| `.extracted` | Parsed content | Text fields needing parsed file content |
|
|
59
|
+
|
|
60
|
+
Rules:
|
|
61
|
+
|
|
62
|
+
- Bare `file::<key>` without a suffix is **NOT valid** — causes a runtime error
|
|
63
|
+
- File references are resolved **only in body/content fields**, not in headers, query params, or URLs
|
|
64
|
+
- For multi-file steps, use bracket notation: `file::stepId["report.csv"].raw`
|
|
65
|
+
- For loop iterations: `file::stepId[0].raw`
|
|
66
|
+
- Important: base64 access in transforms is capped at 500MB by design, so base64 encoded binary in transform steps is only available for files smaller than this
|
|
67
|
+
|
|
68
|
+
## File Aliasing
|
|
69
|
+
|
|
70
|
+
When a step produces files, the runtime first chooses a **root alias**:
|
|
71
|
+
|
|
72
|
+
- non-loop step: `stepId`
|
|
73
|
+
- loop iteration: `stepId[N]`
|
|
74
|
+
|
|
75
|
+
Then it decides whether that root needs a filename suffix:
|
|
76
|
+
|
|
77
|
+
- if that step/iteration produced **exactly one file**, the alias is just the root
|
|
78
|
+
- if that step/iteration produced **multiple files**, append `["filename.ext"]` to distinguish them
|
|
79
|
+
|
|
80
|
+
Examples:
|
|
81
|
+
|
|
82
|
+
- one direct download: `downloadReport`
|
|
83
|
+
- one loop iteration download: `downloadReport[0]`
|
|
84
|
+
- one batch step returning multiple files: `downloadReport["summary.csv"]`
|
|
85
|
+
- one loop iteration returning multiple files: `downloadReport[0]["summary.csv"]`
|
|
86
|
+
|
|
87
|
+
This is about **one file vs many files at that step/iteration**, not about whether the runtime knows the filename. File server `get` operations know the filename, but single-file outputs still use the plain step ID (or `stepId[N]`) alias.
|
|
88
|
+
|
|
89
|
+
Loop steps (those with a `dataSelector` that iterates) always use numeric indices — never filenames — even when each iteration fetches a named file. Filename brackets only appear when a single step or iteration returns multiple files in one response.
|
|
90
|
+
|
|
91
|
+
These aliases are used both in `file::` references and as keys in `sourceData.__files__`.
|
|
92
|
+
|
|
93
|
+
## Accessing Files in Transforms
|
|
94
|
+
|
|
95
|
+
Inside `transformCode` and `outputTransform`, use `sourceData.__files__` with the **runtime alias**:
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
sourceData.__files__.downloadStep; // correct (step ID for single file)
|
|
99
|
+
sourceData.__files__["doubled.csv"]; // wrong (local filename)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
For multi-file aliases with bracket notation, prefer discovering aliases from `stepFileKeys` or
|
|
103
|
+
`Object.keys(sourceData.__files__)` instead of hardcoding quoted alias strings inside the transform
|
|
104
|
+
code string. This avoids JSON/JS escaping mistakes like:
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
sourceData.__files__['fetchThreeFiles[".bashrc"]']; // brittle inside JSON-encoded transform strings
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Prefer:
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
(sourceData) => {
|
|
114
|
+
return (sourceData.fetchThreeFiles.stepFileKeys || []).map(function (alias) {
|
|
115
|
+
var f = sourceData.__files__[alias];
|
|
116
|
+
return {
|
|
117
|
+
alias: alias,
|
|
118
|
+
filename: f ? f.filename : null,
|
|
119
|
+
size: f ? f.size : null,
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`sourceData.__files__` is **read-only**. To produce new files from a transform, return them via a `__files__` key:
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
(sourceData) => ({
|
|
129
|
+
summary: "processed",
|
|
130
|
+
__files__: {
|
|
131
|
+
"output.csv": {
|
|
132
|
+
filename: "output.csv",
|
|
133
|
+
contentType: "text/csv",
|
|
134
|
+
raw: new TextEncoder().encode("a,b\n1,2\n"),
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Everything except `__files__` becomes the step's `data`. Single-file transforms get the step ID as alias; multi-file transforms use bracket notation.
|
|
141
|
+
|
|
142
|
+
### `raw` rules
|
|
143
|
+
|
|
144
|
+
- `raw` accepts: `Uint8Array | ArrayBuffer | number[] | string | object | null`.
|
|
145
|
+
- **String** → UTF-8 encoded. Pass a JSON string for structured data: `raw: JSON.stringify(data)`.
|
|
146
|
+
- **Object / array of objects** → automatically JSON-stringified to UTF-8 bytes. Works, but prefer explicit `JSON.stringify` with a `text/csv` or `application/json` contentType for clarity.
|
|
147
|
+
- **`null` / `undefined`** → produces an empty file (0 bytes).
|
|
148
|
+
- **`Uint8Array` / `ArrayBuffer` / `number[]`** → used as-is for binary content.
|
|
149
|
+
- For binary formats (xlsx, zip, pdf): pass through original bytes via `sourceData.__files__.stepId.raw` — never construct the internal file structure as a JS object.
|
|
150
|
+
|
|
151
|
+
## stepFileKeys
|
|
152
|
+
|
|
153
|
+
Each step result exposes `stepFileKeys` — an array of runtime aliases for files produced by that step:
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
sourceData.downloadStep.stepFileKeys; // ["downloadStep"]
|
|
157
|
+
sourceData.batchDownload[0].stepFileKeys; // ["batchDownload[0]"]
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Use `stepFileKeys` to dynamically discover which file aliases are available from a step.
|