@superglue/cli 1.1.22 → 1.1.24
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 +69 -62
- package/dist/skills/superglue/SKILL.md +24 -21
- package/dist/skills/superglue/references/file-handling.md +66 -8
- package/dist/skills/superglue/references/http.md +5 -0
- package/dist/skills/superglue/references/odbc.md +162 -0
- package/dist/skills/superglue/references/sftp-smb.md +5 -0
- package/package.json +1 -1
- package/skills/superglue/SKILL.md +24 -21
- package/skills/superglue/references/file-handling.md +66 -8
- package/skills/superglue/references/http.md +5 -0
- package/skills/superglue/references/odbc.md +162 -0
- package/skills/superglue/references/sftp-smb.md +5 -0
|
@@ -139,23 +139,49 @@ Prefer discovering aliases dynamically via `stepFileKeys`:
|
|
|
139
139
|
|
|
140
140
|
### Producing Files From Transforms
|
|
141
141
|
|
|
142
|
-
Return a `__files__` key from a transform to produce new files:
|
|
142
|
+
Return a `__files__` key from a transform to produce new files. The **key name is the filename** — content type is inferred from the extension. Values can be plain strings, bytes, or full file objects:
|
|
143
143
|
|
|
144
144
|
```javascript
|
|
145
|
+
// Simplest: key = filename, value = string content (auto-encoded to bytes)
|
|
145
146
|
(sourceData) => ({
|
|
146
|
-
summary: "processed",
|
|
147
147
|
__files__: {
|
|
148
|
-
"
|
|
149
|
-
filename: "output.csv",
|
|
150
|
-
contentType: "text/csv",
|
|
151
|
-
raw: new TextEncoder().encode("a,b\n1,2\n"),
|
|
152
|
-
},
|
|
148
|
+
"report.csv": Papa.unparse(sourceData.step1.data),
|
|
153
149
|
},
|
|
154
|
-
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
// Multiple files with different types
|
|
153
|
+
(sourceData) => ({
|
|
154
|
+
__files__: {
|
|
155
|
+
"data.csv": Papa.unparse(sourceData.step1.data),
|
|
156
|
+
"config.yaml": yaml.dump(sourceData.step1.data),
|
|
157
|
+
},
|
|
158
|
+
summary: { rowCount: sourceData.step1.data.length },
|
|
159
|
+
})
|
|
155
160
|
```
|
|
156
161
|
|
|
162
|
+
Supported value types for `__files__` entries:
|
|
163
|
+
|
|
164
|
+
- **String** — auto-encoded to UTF-8 bytes. Filename and content type from the key.
|
|
165
|
+
- **Uint8Array / ArrayBuffer** — used as-is. Filename and content type from the key.
|
|
166
|
+
- **Object with `raw`** — `raw` is normalized (string/bytes/array), `filename` defaults to key if missing, `contentType` inferred from key extension if missing.
|
|
167
|
+
- **Full file object** — `{ filename, contentType, raw }` for explicit control (backwards compatible).
|
|
168
|
+
|
|
157
169
|
Everything except `__files__` becomes the step's `data`. Single-file transforms get the step ID as alias; multi-file transforms use bracket notation.
|
|
158
170
|
|
|
171
|
+
**Available libraries in transforms:** The following libraries are available for file generation in any transform code (step transforms and output transforms):
|
|
172
|
+
|
|
173
|
+
- `Papa` — [papaparse](https://www.papaparse.com/). Use `Papa.unparse(rows)` to generate CSV from an array of objects. Handles quoting, escaping, and special characters automatically.
|
|
174
|
+
- `XLSX` — [SheetJS](https://sheetjs.com/). Use `XLSX.utils.json_to_sheet(data)` + `XLSX.write(wb, { type: "array", bookType: "xlsx" })` to generate Excel files.
|
|
175
|
+
- `yaml` — [js-yaml](https://github.com/nodeca/js-yaml). Use `yaml.dump(obj)` to generate YAML.
|
|
176
|
+
|
|
177
|
+
Example — CSV with Papa:
|
|
178
|
+
|
|
179
|
+
```javascript
|
|
180
|
+
(sourceData) => ({
|
|
181
|
+
__files__: { "report.csv": Papa.unparse(sourceData.step1.data) },
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
159
185
|
**Auto-parsing of transform-produced files:** if `extracted` is omitted from the file object, the runtime runs the full detection and parsing pipeline on the raw bytes (same as for HTTP/file server responses). This means a CSV file produced with `raw: "a,b\n1,2"` will automatically get `extracted: [{a: "1", b: "2"}]`. To skip auto-parsing and keep the raw content as-is, set `extracted` explicitly (e.g. `extracted: null`).
|
|
160
186
|
|
|
161
187
|
**`raw` field rules:**
|
|
@@ -166,6 +192,36 @@ Everything except `__files__` becomes the step's `data`. Single-file transforms
|
|
|
166
192
|
- `null` / `undefined` — produces an empty file (0 bytes)
|
|
167
193
|
- 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
|
|
168
194
|
|
|
195
|
+
### Returning Files as Tool Output
|
|
196
|
+
|
|
197
|
+
To make files downloadable from the tool's API response, set `outputFile: true` on the step that produces the file. The output transform is only for shaping JSON data — it has nothing to do with files.
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"steps": [
|
|
202
|
+
{
|
|
203
|
+
"id": "fetchData",
|
|
204
|
+
"config": { "type": "request", "url": "https://api.example.com/report" }
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"id": "generateReport",
|
|
208
|
+
"config": {
|
|
209
|
+
"type": "transform",
|
|
210
|
+
"transformCode": "(sourceData) => ({ __files__: { \"report.csv\": Papa.unparse(sourceData.fetchData.data) } })"
|
|
211
|
+
},
|
|
212
|
+
"outputFile": true
|
|
213
|
+
}
|
|
214
|
+
],
|
|
215
|
+
"outputTransform": "(sourceData) => ({ rowCount: sourceData.fetchData.data.length })"
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Only steps with `outputFile: true` have their files stored to S3 and returned as downloadable artifacts. Steps without the flag keep their files internal (for inter-step passing only).
|
|
220
|
+
|
|
221
|
+
This works on any step type — transform steps, HTTP steps, SFTP/FTP steps. Just set `outputFile: true`.
|
|
222
|
+
|
|
223
|
+
Use `sg run download <runId>` to download file artifacts from the CLI.
|
|
224
|
+
|
|
169
225
|
## Common Pitfalls
|
|
170
226
|
|
|
171
227
|
- Using bare `file::<key>` without `.raw`, `.base64`, or `.extracted` suffix — causes a runtime error
|
|
@@ -174,6 +230,8 @@ Everything except `__files__` becomes the step's `data`. Single-file transforms
|
|
|
174
230
|
- Hardcoding bracket aliases like `sourceData.__files__["step[\"file.csv\"]"]` — prefer `stepFileKeys` or `Object.keys(sourceData.__files__)` to discover aliases dynamically
|
|
175
231
|
- Trying to construct binary file internals (xlsx, zip) as JS objects — always pass through original bytes via `.raw`
|
|
176
232
|
- Accessing `sourceData.__files__` in step configs — `file::` syntax is for step configs, `sourceData.__files__` is for transform code
|
|
233
|
+
- Manually base64-encoding file content with `btoa()` — never do this. Return raw bytes (Uint8Array) or strings directly. The runtime handles encoding automatically. Use `XLSX.write(wb, { type: "array" })` not `type: "buffer"` followed by manual btoa.
|
|
234
|
+
- Putting file logic in the output transform — the output transform is for JSON data only. Use `outputFile: true` on the step instead.
|
|
177
235
|
|
|
178
236
|
## Error Recovery
|
|
179
237
|
|
|
@@ -224,10 +224,15 @@ pagination: {
|
|
|
224
224
|
|
|
225
225
|
**Paginated step results:** all pages merge into a single result. Access via `sourceData.stepId.data` — it is NOT an array of per-page results. Do not call `.map()` on a paginated step result. Returns single object if one result, array if multiple (unwrapped from single-element array).
|
|
226
226
|
|
|
227
|
+
### File Output
|
|
228
|
+
|
|
229
|
+
When an HTTP step downloads a file (PDF, CSV, binary response), set `outputFile: true` on the step to make it downloadable by the user. Without the flag, downloaded files stay internal (only accessible to subsequent steps via `file::` references).
|
|
230
|
+
|
|
227
231
|
## Common Pitfalls
|
|
228
232
|
|
|
229
233
|
- Hardcoding base URLs that match the system's base URL instead of using `<<systemId_url>>`
|
|
230
234
|
- Forgetting auth headers entirely — nothing is injected automatically, including for OAuth systems (only token refresh is automatic)
|
|
235
|
+
- Forgetting `outputFile: true` on steps that download files the user wants
|
|
231
236
|
- Putting pagination variables in the config but not setting a `pagination` block, or vice versa
|
|
232
237
|
- Using `<<currentItem.id>>` — must use arrow function syntax: `<<(sourceData) => sourceData.currentItem.id>>`
|
|
233
238
|
- Mixing multiple `<<>>` expressions in one body string when the API expects nested JSON — use a transform step instead
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# ODBC
|
|
2
|
+
|
|
3
|
+
## Step Configuration
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
{
|
|
7
|
+
type: "request",
|
|
8
|
+
systemId: "my_sybase_db",
|
|
9
|
+
url: "odbc://<<my_sybase_db_username>>:<<my_sybase_db_password>>@<<my_sybase_db_host>>:<<my_sybase_db_port>>/<<my_sybase_db_database>>?DRIVER=<<my_sybase_db_driver>>",
|
|
10
|
+
body: '{"query": "SELECT * FROM users WHERE id = ?", "params": [<<userId>>]}'
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Configure the connection URL based on which credentials are stored in the system — use `sg system find` to check `storedCredentials` and reference them accordingly.
|
|
15
|
+
|
|
16
|
+
`method`, `headers`, `queryParams`, and `pagination` are HTTP-only fields — omit them for database steps.
|
|
17
|
+
|
|
18
|
+
### Fields
|
|
19
|
+
|
|
20
|
+
| Field | Required | Notes |
|
|
21
|
+
| ---------- | -------- | -------------------------------------------------------------- |
|
|
22
|
+
| `url` | yes | Connection string with `<<credential>>` placeholders |
|
|
23
|
+
| `body` | yes | JSON string with `query` and optional `params` |
|
|
24
|
+
| `systemId` | no | Links system credentials for `<<systemId_credKey>>` resolution |
|
|
25
|
+
|
|
26
|
+
### URL Format
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
odbc://user:password@host:port/database?DRIVER=DriverName
|
|
30
|
+
odbc://user:password@host:port/database?DRIVER=FreeTDS&TDS_Version=5.0
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The path contains the **database name**. The ODBC driver is specified via the `DRIVER` query parameter (defaults to `FreeTDS` if omitted). This format is consistent with other superglue database strategies (postgres, mssql) where the path is always the database.
|
|
34
|
+
|
|
35
|
+
**Supported drivers:**
|
|
36
|
+
|
|
37
|
+
| Driver | Database | Default Port |
|
|
38
|
+
| --------- | ---------------------------- | ------------ |
|
|
39
|
+
| `FreeTDS` | SAP ASE (Sybase), SQL Server | 5000 |
|
|
40
|
+
|
|
41
|
+
Additional query parameters (beyond `DRIVER`) are passed through to the ODBC connection string as key=value pairs.
|
|
42
|
+
|
|
43
|
+
### Body Format
|
|
44
|
+
|
|
45
|
+
JSON string with `query` and optional `params` (or `values`):
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"query": "SELECT * FROM users WHERE age > ? AND status = ?",
|
|
50
|
+
"params": [25, "active"]
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Authentication
|
|
55
|
+
|
|
56
|
+
Credentials are embedded directly in the connection URL using `<<systemId_credentialKey>>` placeholders. Nothing is injected automatically.
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
odbc://<<my_db_username>>:<<my_db_password>>@<<my_db_host>>:<<my_db_port>>/<<my_db_database>>?DRIVER=<<my_db_driver>>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Credential variables are resolved at runtime before connecting.
|
|
63
|
+
|
|
64
|
+
## ODBC Runtime Details
|
|
65
|
+
|
|
66
|
+
### Schema Discovery
|
|
67
|
+
|
|
68
|
+
superglue does not auto-introspect ODBC database schemas. Before building tools, always query the schema manually with `sg system call` to discover tables and columns.
|
|
69
|
+
|
|
70
|
+
**SAP ASE (Sybase):**
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"query": "SELECT o.name AS table_name, c.name AS column_name, t.name AS data_type, CASE WHEN c.status & 8 = 8 THEN 'YES' ELSE 'NO' END AS is_nullable FROM sysobjects o JOIN syscolumns c ON o.id = c.id JOIN systypes t ON c.usertype = t.usertype WHERE o.type = 'U' ORDER BY o.name, c.colid"
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Always verify table and column names against the actual schema before building tools.
|
|
79
|
+
|
|
80
|
+
### Parameterized Queries
|
|
81
|
+
|
|
82
|
+
Always use parameterized queries with positional `?` placeholders — prevents SQL injection. Parameters are bound positionally from the `params` array.
|
|
83
|
+
|
|
84
|
+
For numeric or boolean parameters in query params arrays, use unquoted placeholders: `[<<numericParam>>]` not `["<<numericParam>>"]`. The quotes force string resolution.
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{ "query": "SELECT * FROM orders WHERE name = ? AND customer_id = ?", "params": ["<<customerName>>", <<customerId>>] }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
With expressions:
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"query": "INSERT INTO logs (message, level) VALUES (?, ?)",
|
|
95
|
+
"params": ["<<(sourceData) => sourceData.message>>", "error"]
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Return Value
|
|
100
|
+
|
|
101
|
+
Returns an array of row objects with column names as keys.
|
|
102
|
+
|
|
103
|
+
- `SELECT`: array of matching rows
|
|
104
|
+
- `INSERT/UPDATE/DELETE`: empty array (SAP ASE does not support `RETURNING` — query the data separately if needed)
|
|
105
|
+
|
|
106
|
+
### Batch Operations
|
|
107
|
+
|
|
108
|
+
Use a data selector returning an array to execute a query per item:
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
// dataSelector: (sourceData) => sourceData.newUsers
|
|
112
|
+
// body: {"query": "INSERT INTO users (name) VALUES (?)", "params": ["<<(sourceData) => sourceData.currentItem.name>>"]}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Retry Behavior
|
|
116
|
+
|
|
117
|
+
Default retries from server config. On final failure, error includes a truncated query preview.
|
|
118
|
+
|
|
119
|
+
## SAP ASE (Sybase) Specifics
|
|
120
|
+
|
|
121
|
+
### Differences from SQL Server
|
|
122
|
+
|
|
123
|
+
SAP ASE uses Transact-SQL like SQL Server, but with key differences:
|
|
124
|
+
|
|
125
|
+
- **No `OUTPUT` clause** — use `SELECT @@identity` after INSERT to get the last inserted identity value
|
|
126
|
+
- **No `TOP` with `ORDER BY` guarantee** — use `SET ROWCOUNT n` before the query instead
|
|
127
|
+
- **`CONVERT` syntax differs** — `CONVERT(target_type, expression)` not `CONVERT(expression, style)`
|
|
128
|
+
- **String concatenation** — use `+` operator, not `CONCAT()` (unless ASE 16.0+)
|
|
129
|
+
- **Date functions** — use `GETDATE()` not `CURRENT_TIMESTAMP`, `DATEADD`/`DATEDIFF` syntax matches SQL Server
|
|
130
|
+
|
|
131
|
+
### TDS Protocol Version
|
|
132
|
+
|
|
133
|
+
FreeTDS defaults to TDS 5.0 for Sybase connections. If you encounter protocol errors, you can specify the version via query parameter:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
odbc://user:pass@host:5000/mydb?DRIVER=FreeTDS&TDS_Version=5.0
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
| TDS Version | Use Case |
|
|
140
|
+
| ----------- | -------------------------- |
|
|
141
|
+
| `5.0` | SAP ASE / Sybase (default) |
|
|
142
|
+
| `7.0` | SQL Server 7.0+ |
|
|
143
|
+
| `7.4` | SQL Server 2012+ |
|
|
144
|
+
|
|
145
|
+
## Common Pitfalls
|
|
146
|
+
|
|
147
|
+
- Using `@param1`, `@param2` (MSSQL syntax) or `$1`, `$2` (PostgreSQL syntax) instead of positional `?` placeholders — ODBC uses `?` for all parameter binding
|
|
148
|
+
- Forgetting that SAP ASE has no `OUTPUT`/`RETURNING` clause — mutations return empty arrays; query the data separately
|
|
149
|
+
- Using `CONCAT()` on older ASE versions — use `+` for string concatenation instead
|
|
150
|
+
- Driver name mismatch — the `DRIVER` query parameter must exactly match the section name in `odbcinst.ini` (case-sensitive). Use `DRIVER=FreeTDS`, not `DRIVER=freetds` or `DRIVER=FREETDS`
|
|
151
|
+
- Accessing step result directly instead of via `.data` — database results follow the same envelope as all steps: `sourceData.stepId.data`
|
|
152
|
+
|
|
153
|
+
## Error Recovery
|
|
154
|
+
|
|
155
|
+
When an ODBC step fails and the cause is not obvious:
|
|
156
|
+
|
|
157
|
+
1. **Read the error message** — superglue includes the a truncated query preview in the error. Check for syntax errors, missing columns, or type mismatches.
|
|
158
|
+
2. **Verify connection credentials** — use `sg system find` to confirm `storedCredentials` keys match the placeholders in your URL. Test connectivity with a simple `SELECT 1` via `sg system call`.
|
|
159
|
+
3. **Check database-side access** — connection refused or permission denied errors typically mean the database user lacks access. Verify with the user that the credentials have the required grants.
|
|
160
|
+
4. **Check driver availability** — if the error mentions "driver not found", the `DRIVER` query parameter does not match any registered driver in `/etc/odbcinst.ini`. Verify the driver name is correct and the driver is installed in the Docker image.
|
|
161
|
+
5. **Test the query directly** — use `sg system call` with a simplified version of the query to isolate whether the issue is the query itself, the parameters, or the connection.
|
|
162
|
+
6. **Check TDS version** — protocol errors may indicate a TDS version mismatch. Try adding `?TDS_Version=5.0` to the query parameters for SAP ASE connections.
|
|
@@ -134,9 +134,14 @@ Single operation returns the result directly. Array of operations returns an arr
|
|
|
134
134
|
|
|
135
135
|
Default retries from server config. On final failure, error includes the full operations list. Each retry creates a fresh connection.
|
|
136
136
|
|
|
137
|
+
### File Output
|
|
138
|
+
|
|
139
|
+
When the user wants to **download** a file fetched via SFTP/SMB/FTP, set `outputFile: true` on the step. This stores the file in S3 and returns a download link in the API response. Without the flag, fetched files stay internal (only accessible to subsequent steps via `file::` references).
|
|
140
|
+
|
|
137
141
|
## Common Pitfalls
|
|
138
142
|
|
|
139
143
|
- Using absolute filesystem paths instead of paths relative to the URL base path
|
|
144
|
+
- Forgetting `outputFile: true` on steps that download files the user wants — without it, the file is only available internally between steps
|
|
140
145
|
- Forgetting that SMB requires a share name as the first URL path segment
|
|
141
146
|
- Using `file::<key>` without a suffix in `put` content. Use `file::<key>.raw` to preserve original bytes
|
|
142
147
|
- Missing the double-data access pattern for `get` results: `sourceData.stepId.data` gives you the parsed file content (through the step envelope)
|