@superglue/cli 1.1.18 → 1.1.20

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.
Files changed (30) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/dist/cli.js +832 -23922
  3. package/dist/skills/superglue/SKILL.md +240 -27
  4. package/dist/skills/superglue/references/access-rules.md +137 -0
  5. package/dist/skills/superglue/references/data-handling.md +303 -0
  6. package/dist/skills/superglue/references/databases.md +7 -3
  7. package/dist/skills/superglue/references/file-handling.md +160 -0
  8. package/dist/skills/superglue/references/file-servers.md +13 -37
  9. package/dist/skills/superglue/references/http-apis.md +248 -0
  10. package/dist/skills/superglue/references/redis.md +4 -5
  11. package/package.json +2 -1
  12. package/skills/superglue/SKILL.md +240 -27
  13. package/skills/superglue/references/access-rules.md +137 -0
  14. package/skills/superglue/references/data-handling.md +303 -0
  15. package/skills/superglue/references/databases.md +7 -3
  16. package/skills/superglue/references/file-handling.md +160 -0
  17. package/skills/superglue/references/file-servers.md +13 -37
  18. package/skills/superglue/references/http-apis.md +248 -0
  19. package/skills/superglue/references/redis.md +4 -5
  20. package/dist/dist/cli.js +0 -24370
  21. package/dist/dist/skills/superglue/SKILL.md +0 -678
  22. package/dist/dist/skills/superglue/agents/openai.yaml +0 -7
  23. package/dist/dist/skills/superglue/assets/superglue.png +0 -0
  24. package/dist/dist/skills/superglue/references/databases.md +0 -249
  25. package/dist/dist/skills/superglue/references/file-servers.md +0 -136
  26. package/dist/dist/skills/superglue/references/integration.md +0 -279
  27. package/dist/dist/skills/superglue/references/redis.md +0 -190
  28. package/dist/dist/skills/superglue/references/transforms-and-output.md +0 -92
  29. package/dist/skills/superglue/references/transforms-and-output.md +0 -92
  30. package/skills/superglue/references/transforms-and-output.md +0 -92
@@ -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@myserver:mypassword@myserver.database.windows.net:1433/mydatabase
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.
@@ -12,10 +12,14 @@ smb://user:password@host/sharename/basePath (port 445)
12
12
  smb://domain\user:password@host/sharename/ (domain auth)
13
13
  ```
14
14
 
15
- All support `<<variable>>` syntax in the URL.
15
+ All support `<<variable>>` syntax in the URL. **For password-based auth, explicitly include credentials in the connection URL** using `<<systemId_username>>` and `<<systemId_password>>` placeholders (e.g. `sftp://<<mySystem_username>>:<<mySystem_password>>@host/path`). Authentication is never automatic. For SFTP private-key auth, use `credentials.privateKey`/`credentials.passphrase` instead of URL placeholders.
16
16
 
17
17
  SMB requires a share name as the first path segment. Additional segments become a base path prepended to all operation paths.
18
18
 
19
+ ## System Credentials
20
+
21
+ Only store connection credentials: `host`, `port`, `username`, `password`, `privateKey`, `passphrase`, `domain` (SMB), `home_dir`. Paths and share names belong in the URL, not credentials.
22
+
19
23
  ## Path Handling
20
24
 
21
25
  **All operation paths are relative to the base path in the URL.** Do NOT use absolute filesystem paths.
@@ -63,19 +67,21 @@ JSON string — single operation or array of operations (batch):
63
67
  | `exists` | `path` | `{ exists: boolean, path }` |
64
68
  | `stat` | `path` | File metadata or `{ exists: false }` |
65
69
 
66
- ### `get` — Auto-Parsing
70
+ ### `get` — Step File Keys
67
71
 
68
- Downloaded files are all parsed automatically:
72
+ Downloaded files are added to the runtime file store. See the file-handling skill for the full reference on file detection, aliasing, and the `RuntimeExecutionFile` shape.
69
73
 
70
- - CSV, JSON, XML, Excel, PDF, etc. → parsed to JS objects
71
- - Falls back to UTF-8 string
72
- - SMB additionally detects binary files (returns `{ _binary: true, encoding: "base64", data: "..." }`)
74
+ - `data` contains the auto-parsed content (CSV -> objects, JSON -> parsed, PDF -> structured, etc.)
75
+ - the step result exposes `stepFileKeys`
76
+ - later steps can reference downloaded files via `file::<stepId>.raw` (exact bytes), `file::<stepId>.base64` (base64 string), or `file::<stepId>.extracted` (parsed content)
77
+ - for multi-file operations, use bracket notation: `file::<stepId>["report.csv"].raw`
73
78
 
74
79
  ### `put` — Content Handling
75
80
 
81
+ - `RawFileBytes` (from `file::<key>.raw`) → written as exact original bytes
76
82
  - String → written as-is
77
83
  - Buffer → written directly
78
- - Object/Array → `JSON.stringify` with 2-space indent
84
+ - Object/Array → JSON.stringified with 2-space indent
79
85
 
80
86
  ## Connection Details
81
87
 
@@ -104,33 +110,3 @@ For private systems via Secure Gateway, the host is `tunnelId.tunnel` (e.g., `my
104
110
  ## Return Value
105
111
 
106
112
  Single operation → result directly. Multiple operations → array of results.
107
-
108
- ## Common Patterns
109
-
110
- ### Download and process
111
-
112
- ```json
113
- { "operation": "get", "path": "incoming/<<(sourceData) => sourceData.currentItem.filename>>" }
114
- ```
115
-
116
- ### Upload results
117
-
118
- ```json
119
- {
120
- "operation": "put",
121
- "path": "output/results.json",
122
- "content": "<<(sourceData) => JSON.stringify(sourceData.processData.data)>>"
123
- }
124
- ```
125
-
126
- ### List then loop
127
-
128
- Step 1 lists files, step 2 uses data selector to loop over them:
129
-
130
- ```javascript
131
- // Step 1: list files
132
- // body: {"operation": "list", "path": "incoming"}
133
-
134
- // Step 2 dataSelector: (sourceData) => sourceData.listFiles.data.filter(f => f.type === "file")
135
- // body: {"operation": "get", "path": "<<(sourceData) => sourceData.currentItem.path>>"}
136
- ```