@superglue/cli 1.1.20 → 1.1.22

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 (32) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +1 -7
  3. package/dist/cli.js +303 -74
  4. package/dist/skills/superglue/SKILL.md +313 -621
  5. package/dist/skills/superglue/references/file-handling.md +116 -90
  6. package/dist/skills/superglue/references/graphql.md +131 -0
  7. package/dist/skills/superglue/references/http.md +246 -0
  8. package/dist/skills/superglue/references/integration.md +4 -2
  9. package/dist/skills/superglue/references/mssql.md +207 -0
  10. package/dist/skills/superglue/references/postgres.md +154 -0
  11. package/dist/skills/superglue/references/redis.md +68 -118
  12. package/dist/skills/superglue/references/sftp-smb.md +156 -0
  13. package/dist/skills/superglue/references/superglue-info.md +91 -0
  14. package/package.json +1 -1
  15. package/skills/superglue/SKILL.md +313 -621
  16. package/skills/superglue/references/file-handling.md +116 -90
  17. package/skills/superglue/references/graphql.md +131 -0
  18. package/skills/superglue/references/http.md +246 -0
  19. package/skills/superglue/references/integration.md +4 -2
  20. package/skills/superglue/references/mssql.md +207 -0
  21. package/skills/superglue/references/postgres.md +154 -0
  22. package/skills/superglue/references/redis.md +68 -118
  23. package/skills/superglue/references/sftp-smb.md +156 -0
  24. package/skills/superglue/references/superglue-info.md +91 -0
  25. package/dist/skills/superglue/references/data-handling.md +0 -303
  26. package/dist/skills/superglue/references/databases.md +0 -253
  27. package/dist/skills/superglue/references/file-servers.md +0 -112
  28. package/dist/skills/superglue/references/http-apis.md +0 -248
  29. package/skills/superglue/references/data-handling.md +0 -303
  30. package/skills/superglue/references/databases.md +0 -253
  31. package/skills/superglue/references/file-servers.md +0 -112
  32. package/skills/superglue/references/http-apis.md +0 -248
@@ -1,128 +1,145 @@
1
1
  # File Handling
2
2
 
3
- How the runtime detects, parses, and exposes files across all protocol strategies (HTTP, FTP, SFTP, SMB).
3
+ How the runtime detects, parses, and exposes files across all protocol strategies (HTTP, SFTP/FTP, SMB).
4
4
 
5
- ## When Does a Response Become a File?
5
+ ## Step Configuration
6
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:
7
+ File handling is not a standalone step type — it is a cross-cutting runtime behavior that applies to any step that produces or consumes files. The `file::<alias>.<suffix>` syntax is used in step config `body` / `content` fields to reference files from previous steps or user uploads.
8
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.
9
+ ```typescript
10
+ // Reference a file downloaded by a previous step
11
+ { "body": "file::downloadStep.raw" }
13
12
 
14
- Structured text responses (JSON, XML, CSV, YAML, HTML) that pass none of the above checks go to `data` only — no file entry.
13
+ // Reference a user-uploaded file
14
+ { "body": '{"fileBase64": "file::document.base64"}' }
15
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 |
16
+ // Reference a specific file from a multi-file step
17
+ { "content": 'file::batchDownload["report.csv"].raw' }
18
+ ```
21
19
 
22
- Binary responses expose `stepFileKeys` and populate `sourceData.__files__`. Structured text responses only populate `sourceData.stepId.data`.
20
+ ### file:: Reference Syntax
23
21
 
24
- File server `get` operations always produce files regardless of detected type — content goes into both `data` and `producedFiles`.
22
+ Use `file::<alias>.<suffix>` in step config `body` / `content` fields:
25
23
 
26
- ## Auto-Parsing
24
+ | Suffix | Returns | Use case |
25
+ | ------------ | --------------------------- | --------------------------------------- |
26
+ | `.raw` | Original bytes (Uint8Array) | SFTP/FTP put, HTTP raw body upload |
27
+ | `.base64` | Base64-encoded string | APIs expecting base64 inside JSON/XML |
28
+ | `.extracted` | Parsed content | Text fields needing parsed file content |
27
29
 
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 |
30
+ Constraints:
41
31
 
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`.
32
+ - Bare `file::<key>` without a suffix is invalid and causes a runtime error
33
+ - File references resolve only in body/content fields, not in headers, query params, or URLs
34
+ - Multi-file steps use bracket notation: `file::stepId["report.csv"].raw`
35
+ - Loop iterations use numeric index: `file::stepId[0].raw`
43
36
 
44
- ### Excel rules
37
+ ## Authentication
45
38
 
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).
39
+ N/A — file handling is a runtime behavior, not a protocol. Authentication is handled by the protocol skill that produces or consumes the file (http, sftp-smb, etc.).
49
40
 
50
- ## file:: Reference Syntax
41
+ ## File Handling Runtime Details
51
42
 
52
- Use `file::<alias>.<suffix>` in step config `body` / `content` fields:
43
+ ### Detection Priority
53
44
 
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 |
45
+ Every response is read as raw bytes. The runtime classifies it using this priority chain:
59
46
 
60
- Rules:
47
+ 1. **Magic-byte detection** — known binary signatures (ZIP `PK\x03\x04`, PDF `%PDF`, GZIP `\x1f\x8b`, plus internal ZIP structure checks for Excel/DOCX)
48
+ 2. **`Content-Disposition: attachment`** — `attachment` or `filename=` in this header forces file treatment
49
+ 3. **`application/octet-stream`** — explicit octet-stream MIME catches unlabeled binary payloads
50
+ 4. **25MB size fallback** — only when byte detection returned `RAW`, the response exceeds 25MB, and `Content-Type` is not text-like (`text/*`, `application/json`, `*+json`, `application/xml`)
61
51
 
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
52
+ File server `get` operations always produce files regardless of detected type.
67
53
 
68
- ## File Aliasing
54
+ ### Classification
69
55
 
70
- When a step produces files, the runtime first chooses a **root alias**:
56
+ | Classification | Types | Destination |
57
+ | ------------------- | --------------------------- | -------------------------------------------------------------- |
58
+ | **Binary** | PDF, Excel, DOCX, ZIP, GZIP | `producedFiles` + `data` (extracted/parsed content) |
59
+ | **Structured text** | JSON, XML, CSV, YAML, HTML | `data` only (parsed to JS objects) |
60
+ | **RAW** | Unrecognized | File if attachment/octet-stream/25MB fallback, else raw string |
71
61
 
72
- - non-loop step: `stepId`
73
- - loop iteration: `stepId[N]`
62
+ Binary responses expose `stepFileKeys` and populate `sourceData.__files__`. Structured text responses only populate `sourceData.stepId.data`.
63
+
64
+ ### Auto-Parsing
65
+
66
+ | Type | Parsed result |
67
+ | ----- | -------------------------------------------------------------- |
68
+ | JSON | Native JS object/array |
69
+ | CSV | Array of objects (header row = keys) |
70
+ | XML | JS object tree |
71
+ | HTML | JS object tree |
72
+ | YAML | Native JS object |
73
+ | Excel | Object keyed by sheet name, each value is array of row objects |
74
+ | PDF | `{ textContent, structuredContent: [{ page, text }] }` |
75
+ | DOCX | Markdown string |
76
+ | ZIP | Object keyed by entry filename, each value recursively parsed |
77
+ | GZIP | Recursively parsed inner content |
78
+ | RAW | UTF-8 string fallback |
74
79
 
75
- Then it decides whether that root needs a filename suffix:
80
+ **Excel rules:**
76
81
 
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
82
+ - Excel is never a flat array. It is always `{ "SheetName": [ {row}, ... ] }` — even for single-sheet files
83
+ - Access rows via `extracted.SheetName` or `extracted["Sheet1"]`, never `extracted[0]`
84
+ - When producing xlsx, use namespace `http://schemas.openxmlformats.org/spreadsheetml/2006/main` (not the truncated path)
79
85
 
80
- Examples:
86
+ ### File Aliasing
81
87
 
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"]`
88
+ When a step produces files, the runtime assigns an alias:
86
89
 
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.
90
+ | Scenario | Alias format |
91
+ | --------------------------- | --------------------------- |
92
+ | Single file, non-loop | `stepId` |
93
+ | Single file, loop iteration | `stepId[N]` |
94
+ | Multiple files, non-loop | `stepId["filename.ext"]` |
95
+ | Multiple files, loop | `stepId[N]["filename.ext"]` |
88
96
 
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.
97
+ Loop steps 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
98
 
91
99
  These aliases are used both in `file::` references and as keys in `sourceData.__files__`.
92
100
 
93
- ## Accessing Files in Transforms
101
+ ### stepFileKeys
94
102
 
95
- Inside `transformCode` and `outputTransform`, use `sourceData.__files__` with the **runtime alias**:
103
+ Each step result exposes `stepFileKeys` — an array of runtime aliases for files produced by that step:
96
104
 
97
105
  ```javascript
98
- sourceData.__files__.downloadStep; // correct (step ID for single file)
99
- sourceData.__files__["doubled.csv"]; // wrong (local filename)
106
+ sourceData.downloadStep.stepFileKeys; // ["downloadStep"]
107
+ sourceData.batchDownload[0].stepFileKeys; // ["batchDownload[0]"]
100
108
  ```
101
109
 
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:
110
+ ### Accessing Files in Transforms
111
+
112
+ Inside `transformCode` and `outputTransform`, use `sourceData.__files__` with the runtime alias. Treat `sourceData.__files__` as read-only — the runtime rebuilds it after every step.
105
113
 
106
114
  ```javascript
107
- sourceData.__files__['fetchThreeFiles[".bashrc"]']; // brittle inside JSON-encoded transform strings
115
+ sourceData.__files__.downloadStep; // single-file step
116
+ sourceData.__files__["stepId[0]"]; // loop iteration
108
117
  ```
109
118
 
110
- Prefer:
119
+ Available properties on each file object: `filename`, `contentType`, `size`, `raw`, `extracted`, `fileType`, `parseError`.
120
+
121
+ **base64 access:** `base64` is a lazy getter — it is not stored on the file object by default. Access it directly:
122
+
123
+ ```javascript
124
+ sourceData.__files__.myStep.base64; // computes and returns base64 string on first access
125
+ ```
126
+
127
+ The base64 value is computed on first access and cached for subsequent reads. It will not appear in `Object.keys()` or `JSON.stringify()` output (non-enumerable). Throws if the file exceeds 500MB — use `.raw` for large files.
128
+
129
+ Prefer discovering aliases dynamically via `stepFileKeys`:
111
130
 
112
131
  ```javascript
113
132
  (sourceData) => {
114
133
  return (sourceData.fetchThreeFiles.stepFileKeys || []).map(function (alias) {
115
134
  var f = sourceData.__files__[alias];
116
- return {
117
- alias: alias,
118
- filename: f ? f.filename : null,
119
- size: f ? f.size : null,
120
- };
135
+ return { alias: alias, filename: f ? f.filename : null, size: f ? f.size : null };
121
136
  });
122
137
  };
123
138
  ```
124
139
 
125
- `sourceData.__files__` is **read-only**. To produce new files from a transform, return them via a `__files__` key:
140
+ ### Producing Files From Transforms
141
+
142
+ Return a `__files__` key from a transform to produce new files:
126
143
 
127
144
  ```javascript
128
145
  (sourceData) => ({
@@ -139,22 +156,31 @@ Prefer:
139
156
 
140
157
  Everything except `__files__` becomes the step's `data`. Single-file transforms get the step ID as alias; multi-file transforms use bracket notation.
141
158
 
142
- ### `raw` rules
159
+ **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`).
143
160
 
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.
161
+ **`raw` field rules:**
150
162
 
151
- ## stepFileKeys
163
+ - `Uint8Array` / `ArrayBuffer` / typed arrays — used as-is for binary content
164
+ - String — UTF-8 encoded. Pass a JSON string for structured data: `raw: JSON.stringify(data)`
165
+ - Object / array — auto JSON-stringified to UTF-8 bytes. Prefer explicit `JSON.stringify` with an appropriate `contentType` for clarity
166
+ - `null` / `undefined` — produces an empty file (0 bytes)
167
+ - 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
152
168
 
153
- Each step result exposes `stepFileKeys` — an array of runtime aliases for files produced by that step:
169
+ ## Common Pitfalls
154
170
 
155
- ```javascript
156
- sourceData.downloadStep.stepFileKeys; // ["downloadStep"]
157
- sourceData.batchDownload[0].stepFileKeys; // ["batchDownload[0]"]
158
- ```
171
+ - Using bare `file::<key>` without `.raw`, `.base64`, or `.extracted` suffix — causes a runtime error
172
+ - Placing file references in headers or query params instead of body/content fields — they only resolve in body
173
+ - Expecting Excel parsed output to be a flat array — it is always `{ "SheetName": [...] }` even for single-sheet files
174
+ - Hardcoding bracket aliases like `sourceData.__files__["step[\"file.csv\"]"]` — prefer `stepFileKeys` or `Object.keys(sourceData.__files__)` to discover aliases dynamically
175
+ - Trying to construct binary file internals (xlsx, zip) as JS objects — always pass through original bytes via `.raw`
176
+ - Accessing `sourceData.__files__` in step configs — `file::` syntax is for step configs, `sourceData.__files__` is for transform code
177
+
178
+ ## Error Recovery
179
+
180
+ When a file-related step fails:
159
181
 
160
- Use `stepFileKeys` to dynamically discover which file aliases are available from a step.
182
+ 1. **File input failure** - Ask the user whether the file is present in the session state and verify whether you can access the file at all
183
+ 2. **Check the file:: reference** — verify the alias matches a step that actually produces files. Use `stepFileKeys` on the producing step's result to see available aliases.
184
+ 3. **Verify the suffix** — bare `file::<key>` without `.raw`, `.base64`, or `.extracted` always fails. Choose the suffix based on how the consuming step needs the data.
185
+ 4. **Check the producing step** — if `stepFileKeys` is empty or undefined, the producing step may not have returned a file. Verify the response is binary or has `Content-Disposition: attachment` (for HTTP), or that the operation is `get` (for file servers).
186
+ 5. **Check aliasing for loops** — loop iterations use numeric indices (`stepId[0]`), not filenames. If the alias doesn't match, run the producing step and inspect the result to see the actual aliases assigned.
@@ -0,0 +1,131 @@
1
+ # GraphQL
2
+
3
+ GraphQL APIs use HTTP POST under the hood. This skill covers the GraphQL-specific conventions; the http skill covers the underlying HTTP runtime behavior.
4
+
5
+ ## Step Configuration
6
+
7
+ ```typescript
8
+ {
9
+ type: "request",
10
+ systemId: "my_graphql_api",
11
+ url: "<<my_graphql_api_url>>/graphql",
12
+ method: "POST",
13
+ headers: {
14
+ "Authorization": "Bearer <<my_graphql_api_access_token>>",
15
+ "Content-Type": "application/json"
16
+ },
17
+ body: '{"query": "query GetUser($id: ID!) { user(id: $id) { name email } }", "variables": {"id": "<<userId>>"}}'
18
+ }
19
+ ```
20
+
21
+ ### Fields
22
+
23
+ | Field | Required | Notes |
24
+ | ---------- | -------- | ------------------------------------------------------ |
25
+ | `url` | yes | GraphQL endpoint — typically `/graphql` |
26
+ | `method` | yes | Always `POST` |
27
+ | `headers` | yes | Must include `Content-Type: application/json` and auth |
28
+ | `body` | yes | JSON string with `query` and optional `variables` |
29
+ | `systemId` | no | Links system credentials and URL |
30
+
31
+ ### Body Format
32
+
33
+ The body is always a JSON string with:
34
+
35
+ ```json
36
+ {
37
+ "query": "query { ... }",
38
+ "variables": { ... }
39
+ }
40
+ ```
41
+
42
+ For dynamic variables, use `<<>>` expressions:
43
+
44
+ ```json
45
+ {
46
+ "query": "query GetOrders($customerId: ID!, $limit: Int) { orders(customerId: $customerId, first: $limit) { id total } }",
47
+ "variables": {
48
+ "customerId": "<<customerId>>",
49
+ "limit": "<<(sourceData) => sourceData.pageSize || 50>>"
50
+ }
51
+ }
52
+ ```
53
+
54
+ For complex variable objects, use a preceding transform step to build the variables, then reference with `<<(sourceData) => JSON.stringify(sourceData.prepStep.data)>>` as the entire body.
55
+
56
+ ## Authentication
57
+
58
+ Same as HTTP — credentials are placed in headers using `<<systemId_credentialKey>>` syntax. Nothing is injected automatically.
59
+
60
+ ## GraphQL Runtime Details
61
+
62
+ ### Queries vs Mutations
63
+
64
+ - **Queries** (read-only): set `modify: false` on the step (this is the default)
65
+ - **Mutations** (write): set `modify: true`
66
+
67
+ Both use `method: "POST"` — do not rely on HTTP method to distinguish read vs write.
68
+
69
+ ### Response Shape
70
+
71
+ GraphQL APIs return `{ "data": { ... }, "errors": [ ... ] }`. The superglue step result wraps this in the standard envelope:
72
+
73
+ ```javascript
74
+ sourceData.stepId.data; // → { data: { user: { name: "Alice" } }, errors: [...] }
75
+ ```
76
+
77
+ Access the actual data via `sourceData.stepId.data.data.user` (double `.data` — one for the envelope, one for the GraphQL response).
78
+
79
+ ### Pagination
80
+
81
+ GraphQL APIs typically use cursor-based pagination (Relay-style). Use superglue's `cursorBased` pagination:
82
+
83
+ ```typescript
84
+ {
85
+ body: '{"query": "query($cursor: String) { users(first: 50, after: $cursor) { edges { node { id name } } pageInfo { endCursor hasNextPage } } }", "variables": {"cursor": "<<cursor>>"}}',
86
+ pagination: {
87
+ type: "cursorBased",
88
+ pageSize: "50",
89
+ cursorPath: "data.users.pageInfo.endCursor",
90
+ stopCondition: "response.data.data.users.pageInfo.hasNextPage === false"
91
+ }
92
+ }
93
+ ```
94
+
95
+ Note the `cursorPath` and `stopCondition` must account for the GraphQL `data` wrapper.
96
+
97
+ ### Schema Discovery
98
+
99
+ Superglue automatically introspects GraphQL schemas during documentation fetching. When a system URL contains "graphql", introspection is the first strategy tried — the full schema is fetched and stored as system documentation.
100
+
101
+ Use `sg system search-docs` to query the stored schema for types, fields, and arguments before building tools. This is more reliable than manual introspection and works even when the API has introspection disabled for external callers.
102
+
103
+ If the schema is not available via `sg system search-docs` (e.g. the system was created without a GraphQL URL, or introspection was blocked), test manually with `sg system call`:
104
+
105
+ ```json
106
+ { "query": "{ __typename }" }
107
+ ```
108
+
109
+ If that succeeds, try a targeted introspection for the types you need:
110
+
111
+ ```json
112
+ { "query": "{ __type(name: \"User\") { fields { name type { name kind } } } }" }
113
+ ```
114
+
115
+ ## Common Pitfalls
116
+
117
+ - Forgetting `Content-Type: application/json` header — most GraphQL servers reject requests without it
118
+ - Accessing `sourceData.stepId.data.user` instead of `sourceData.stepId.data.data.user` — the GraphQL response has its own `data` wrapper inside the step envelope
119
+ - Building complex variable objects inline with multiple `<<>>` expressions — use a transform step to build the variables object
120
+ - Using `cursorPath` or `stopCondition` that doesn't account for the GraphQL `data` wrapper (e.g. `users.pageInfo` instead of `data.users.pageInfo`)
121
+ - Not setting `modify: true` on mutation steps
122
+
123
+ ## Error Recovery
124
+
125
+ When a GraphQL step fails:
126
+
127
+ 1. **Check GraphQL-level errors** — even 200 responses can contain `errors` in the response body. Check `sourceData.stepId.data.errors` for field-level or validation errors.
128
+ 2. **Verify the endpoint works** — use `sg system call` with `{ "query": "{ __typename }" }` to confirm connectivity and auth before debugging the query itself.
129
+ 3. **Check schema compatibility** — fields, types, and arguments change between API versions. Use `sg system search-docs` to check the auto-introspected schema first. If unavailable, use targeted `__type` queries or web search to verify the current schema.
130
+ 4. **Verify variables match the query signature** — GraphQL type mismatches (e.g. passing a string where `ID!` is expected) cause validation errors. Check that variable types in the query match what you're passing.
131
+ 5. **Check for auth scope issues** — some GraphQL APIs return partial data with `null` fields and errors when the token lacks required scopes for specific fields.
@@ -0,0 +1,246 @@
1
+ # HTTP
2
+
3
+ ## Step Configuration
4
+
5
+ ```typescript
6
+ {
7
+ type: "request",
8
+ systemId: "my_api",
9
+ url: "<<my_api_url>>/v1/users",
10
+ method: "GET",
11
+ headers: { "Authorization": "Bearer <<my_api_access_token>>" },
12
+ queryParams: { "limit": "<<limit>>", "status": "active" },
13
+ body: '{"name": "<<(sourceData) => sourceData.currentItem.name>>"}',
14
+ pagination: { ... }
15
+ }
16
+ ```
17
+
18
+ All fields support `<<variable>>` and `<<(sourceData) => ...>>` expressions.
19
+
20
+ ### Fields
21
+
22
+ | Field | Required | Notes |
23
+ | ------------- | -------- | ------------------------------------------------------- |
24
+ | `url` | yes | Prefer `<<systemId_url>>/path` over hardcoded base URLs |
25
+ | `method` | yes | `GET`, `POST`, `PUT`, `DELETE`, `PATCH` |
26
+ | `headers` | no | Auth headers, content type, custom headers |
27
+ | `queryParams` | no | Key-value pairs appended to URL |
28
+ | `body` | no | Stripped for GET/HEAD/DELETE/OPTIONS |
29
+ | `pagination` | no | See Pagination section |
30
+ | `systemId` | no | Links system credentials and URL. Omit for public APIs |
31
+
32
+ ## Authentication
33
+
34
+ Credentials are never injected automatically. You must place auth headers explicitly using `<<systemId_credentialKey>>` syntax.
35
+
36
+ ```javascript
37
+ // Bearer token (including OAuth — token refresh is automatic, header is not)
38
+ { "Authorization": "Bearer <<systemId_access_token>>" }
39
+
40
+ // API key
41
+ { "X-API-Key": "<<systemId_api_key>>" }
42
+
43
+ // Basic Auth — auto-encoded to Base64, do NOT manually encode
44
+ { "Authorization": "Basic <<systemId_username>>:<<systemId_password>>" }
45
+
46
+ // Runtime credentials from payload
47
+ { "Authorization": "Bearer <<(sourceData) => sourceData.user_access_token>>" }
48
+ ```
49
+
50
+ ### SOAP Authentication
51
+
52
+ Some APIs (e.g. Salesforce) use SOAP XML login where credentials are embedded in the request body, not in headers. Multiple `<<>>` placeholders can appear adjacent — each is resolved independently and the results are concatenated.
53
+
54
+ ```typescript
55
+ {
56
+ method: "POST",
57
+ url: "https://test.salesforce.com/services/Soap/u/59.0",
58
+ headers: {
59
+ "Content-Type": "text/xml; charset=UTF-8",
60
+ "SOAPAction": "login"
61
+ },
62
+ body: '<?xml version="1.0" encoding="utf-8"?><env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"><env:Body><n1:login xmlns:n1="urn:partner.soap.sforce.com"><n1:username><<systemId_username>></n1:username><n1:password><<systemId_password>><<systemId_security_token>></n1:password></n1:login></env:Body></env:Envelope>'
63
+ }
64
+ ```
65
+
66
+ `<<systemId_password>><<systemId_security_token>>` resolves to the password and token concatenated — no separator needed. This works in URL, headers, and body fields.
67
+
68
+ Headers starting with `x-` are treated as custom headers.
69
+ Modern APIs expect auth in headers, not query parameters, unless docs explicitly say otherwise.
70
+
71
+ ## HTTP Runtime Details
72
+
73
+ ### Request Behavior
74
+
75
+ - **GET, HEAD, DELETE, OPTIONS**: body is always stripped
76
+ - **POST, PUT, PATCH**: body with leading `{` is JSON-parsed; empty body becomes undefined
77
+ - Default headers: `Accept: */*`, Chrome-like `User-Agent`
78
+ - HTTPS: `rejectUnauthorized: false` (accepts self-signed certs)
79
+
80
+ ### Body Resolution
81
+
82
+ The runtime processes the body in this order:
83
+
84
+ 1. `<<>>` template variables are resolved — object/array returns are `JSON.stringify`'d before splicing
85
+ 2. The resulting string is JSON-parsed when possible
86
+ 3. `file::` references are resolved on the parsed object
87
+ 4. Final send format is determined:
88
+ - raw bytes when the entire body is `file::<key>.raw`
89
+ - base64 string for `file::<key>.base64`
90
+ - `multipart/form-data` when the `Content-Type` header requests it and body resolves to an object
91
+ - `JSON.stringify(...)` for objects
92
+ - plain string for everything else
93
+
94
+ File references resolve only in the body, not in headers, query params, or URLs.
95
+
96
+ **Double-encoding risk:** the runtime stringifies at both step 1 (expression into template) and step 4 (object into fetch body). When the body contains nested JSON (e.g. LLM APIs), have `<<>>` expressions return a string via `JSON.stringify(...)`, or use a preceding transform step.
97
+
98
+ ### File Uploads
99
+
100
+ **Raw body** — send a file as the entire HTTP body:
101
+
102
+ ```typescript
103
+ {
104
+ method: "PUT",
105
+ headers: { "Content-Type": "application/pdf" },
106
+ body: "file::document.raw"
107
+ }
108
+ ```
109
+
110
+ **Base64-in-JSON** — embed a base64-encoded file inside a structured payload:
111
+
112
+ ```typescript
113
+ {
114
+ method: "POST",
115
+ headers: { "Content-Type": "application/json" },
116
+ body: '{"fileBase64":"file::document.base64"}'
117
+ }
118
+ ```
119
+
120
+ **Multipart form** — set the `Content-Type` header and provide a JSON object of form fields:
121
+
122
+ ```typescript
123
+ {
124
+ method: "POST",
125
+ headers: { "Content-Type": "multipart/form-data" },
126
+ body: '{"file":"file::report.raw","description":"monthly export"}'
127
+ }
128
+ ```
129
+
130
+ The runtime builds a real `FormData` request: `file::...raw` values become file parts, scalars become text parts, non-file objects/arrays are JSON-stringified into text parts. The user-supplied `Content-Type` header is removed so `fetch` sets the boundary automatically.
131
+
132
+ Multipart restrictions:
133
+
134
+ - Body must resolve to a flat object of form fields
135
+ - Nested file refs inside objects are not supported
136
+ - Do not manually set a multipart boundary
137
+ - Use `.raw` for file parts, `.base64` for base64 text fields, `.extracted` for parsed content fields
138
+
139
+ ### Response Handling
140
+
141
+ All HTTP responses are read as raw bytes, then classified by byte-level detection (see `file-handling.md` for the full detection logic).
142
+
143
+ - **Binary types** (PDF, Excel, DOCX, ZIP, GZIP): treated as files, parsed content also populates `data`. Later steps can reference via `file::<stepId>.raw/base64/extracted`.
144
+ - **`application/octet-stream`**: always treated as a file.
145
+ - **Responses over 25 MB**: treated as files as a safety fallback.
146
+ - **RAW + `Content-Disposition: attachment`**: treated as a file.
147
+ - **Structured/text responses** (JSON, XML, CSV, YAML, HTML, text under size limit): parsed inline to `data` only.
148
+
149
+ ### Error Detection
150
+
151
+ Non-2xx responses throw with method, URL, response body preview (1000 chars), masked config, and retry count. Any response with status in the 2xx range is treated as success and returned to the step as-is — the runtime does not scan successful response bodies for error fields, so APIs that return HTTP 200 with `{ "error": "..." }` must be handled by your transform or by setting a `stopCondition`/assertion at the step level.
152
+
153
+ Bypass: set `failureBehavior: "continue"` to skip the non-2xx throw and return the response anyway.
154
+
155
+ ### Retry Behavior
156
+
157
+ **Connection/server errors:**
158
+
159
+ - Default 5 retries, configurable via `retries` on the step
160
+ - Retries on fetch-level errors (network/DNS/abort except timeout) and on 5xx responses
161
+ - Fixed delay between retries (default 1000ms, configurable via `retryDelay`)
162
+
163
+ **Rate limiting (429):**
164
+
165
+ - Counts against the same retry budget as 5xx/network errors
166
+ - Respects `Retry-After` header (seconds or HTTP-date)
167
+ - Without header: exponential backoff `10^n * 1000ms + jitter` (capped at 1hr per wait)
168
+ - Total wait across all 429 retries capped at `MAX_RATE_LIMIT_WAIT_MS` (1hr)
169
+
170
+ **Timeouts:** Request timeout aborts the fetch and throws immediately — timeouts are not retried.
171
+
172
+ ### Pagination
173
+
174
+ Makes multiple requests in a loop, merging results. Only configure after verifying the exact API pagination mechanism from docs.
175
+
176
+ **Configuration:**
177
+
178
+ ```typescript
179
+ pagination: {
180
+ type: "offsetBased" | "pageBased" | "cursorBased",
181
+ pageSize: "50",
182
+ cursorPath: "meta.next_cursor", // cursorBased only — dot-path to cursor
183
+ stopCondition: "(response, pageInfo) => !response.data.meta.next_cursor"
184
+ }
185
+ ```
186
+
187
+ **Variables** — the matching variable MUST appear in the request (URL, queryParams, headers, or body). Throws if missing.
188
+
189
+ | Type | Variable | Starts at | Increments by |
190
+ | ----------- | ---------------------------- | --------- | ------------------------ |
191
+ | pageBased | `<<page>>` | 1 | 1 |
192
+ | offsetBased | `<<offset>>` | 0 | pageSize |
193
+ | cursorBased | `<<cursor>>` | null | extracted via cursorPath |
194
+ | all | `<<limit>>` / `<<pageSize>>` | pageSize | — |
195
+
196
+ **Stop conditions** — JS expression evaluated in sandbox. Receives `(response, pageInfo)`:
197
+
198
+ - `response.data` = parsed API response body
199
+ - `response.headers` = response headers
200
+ - `pageInfo = { page, offset, cursor, totalFetched }`
201
+ - Return `true` to STOP
202
+
203
+ ```javascript
204
+ "!response.data.meta.next_cursor";
205
+ "response.data.items.length === 0";
206
+ "response.data.hasMore === false";
207
+ "pageInfo.totalFetched >= 1000";
208
+ ```
209
+
210
+ **Cursor extraction:** `cursorPath` is a simple dot-separated property path (e.g. `meta.next_cursor`, `data.0.id`) resolved against the parsed response body. It is not a JSONPath expression — wildcards, filters, and `$.` prefixes are not supported. For property names containing `@` (e.g. OData `@odata.nextLink`), use the name directly as the path. Null/undefined cursor stops pagination.
211
+
212
+ **Safety checks (when stopCondition is set):**
213
+
214
+ 1. Identical pages 1 and 2 (both with data): throws — pagination params are not being applied
215
+ 2. Both pages empty + stop did not trigger: throws — broken stop condition
216
+ 3. Duplicate consecutive response after page 2: auto-stops
217
+
218
+ **Without stopCondition (fallback):**
219
+
220
+ - Array shorter than pageSize: stop
221
+ - Duplicate response hash against any previously seen page: stop
222
+ - Non-array response: stop after first request
223
+ - Max 500 requests (vs 1000 max with a stopCondition)
224
+
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
+
227
+ ## Common Pitfalls
228
+
229
+ - Hardcoding base URLs that match the system's base URL instead of using `<<systemId_url>>`
230
+ - Forgetting auth headers entirely — nothing is injected automatically, including for OAuth systems (only token refresh is automatic)
231
+ - Putting pagination variables in the config but not setting a `pagination` block, or vice versa
232
+ - Using `<<currentItem.id>>` — must use arrow function syntax: `<<(sourceData) => sourceData.currentItem.id>>`
233
+ - Mixing multiple `<<>>` expressions in one body string when the API expects nested JSON — use a transform step instead
234
+ - Setting `method: "POST"` for GraphQL queries without `modify: false` — POST that only reads should not be marked as modifying
235
+
236
+ ## Error Recovery
237
+
238
+ When an HTTP step fails and the cause is not immediately clear:
239
+
240
+ 1. **Isolate endpoint vs system-level issues** — use `sg system call` to test a different endpoint on the same system. If multiple endpoints fail, the issue is likely global authentication or system configuration. If only one fails, the issue is endpoint-specific (wrong URL, missing params, incorrect method or authentication scopes).
241
+
242
+ 2. **Check credential scopes and system-side configuration** — a 403 or empty response often means the API key or OAuth token lacks required scopes, or the resource is restricted server-side. Use `sg system search-docs` or a web search to look up required scopes/permissions for the endpoint.
243
+
244
+ 3. **Verify request shape against API docs** — use `sg system search-docs` and web search to confirm the endpoint URL, required headers, query parameters, body format, and expected method. APIs change across versions. Search for the error message and the endpoint in docs and online.
245
+
246
+ 4. **Check for rate limiting** — 429 errors are retried automatically with backoff, but persistent 429s may mean the API key has hit a quota. Check API dashboard or docs for rate limit details.