@superglue/cli 1.1.7 → 1.1.8

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "superglue",
3
3
  "description": "Build, test, and manage superglue tools via the sg CLI. Use when working with superglue tools, systems, APIs, SDKs, or the sg command.",
4
- "version": "1.1.7",
4
+ "version": "0.1.0",
5
5
  "author": {
6
6
  "name": "superglue",
7
7
  "email": "hello@superglue.cloud"
@@ -9,12 +9,5 @@
9
9
  "homepage": "https://superglue.cloud",
10
10
  "repository": "https://github.com/superglue-ai/cli",
11
11
  "license": "MIT",
12
- "keywords": [
13
- "systems",
14
- "integration",
15
- "cli",
16
- "tools",
17
- "automation",
18
- "api"
19
- ]
12
+ "keywords": ["systems", "integration", "cli", "tools", "automation", "api"]
20
13
  }
package/dist/cli.js CHANGED
@@ -2390,7 +2390,7 @@ var require_templates = __commonJS({
2390
2390
  "use strict";
2391
2391
  Object.defineProperty(exports2, "__esModule", { value: true });
2392
2392
  exports2.systemOptions = exports2.systems = void 0;
2393
- exports2.findTemplateForSystem = findTemplateForSystem3;
2393
+ exports2.findTemplateForSystem = findTemplateForSystem4;
2394
2394
  exports2.uniqueKeywords = uniqueKeywords;
2395
2395
  exports2.enrichWithTemplate = enrichWithTemplate;
2396
2396
  exports2.getOAuthConfig = getOAuthConfig;
@@ -2407,6 +2407,15 @@ var require_templates = __commonJS({
2407
2407
  preferredAuthType: "apikey",
2408
2408
  keywords: ["database", "sql", "postgres", "postgresql", "api key", "tables"]
2409
2409
  },
2410
+ redis_direct: {
2411
+ name: "redis_direct",
2412
+ apiUrl: "redis://<<username>>:<<password>>@<<host>>:<<port>>/<<database>>",
2413
+ regex: "^.*(rediss?://).*$",
2414
+ icon: "redis",
2415
+ docsUrl: "https://redis.io/docs/latest/commands/",
2416
+ preferredAuthType: "apikey",
2417
+ keywords: ["database", "cache", "redis", "key-value", "nosql", "api key"]
2418
+ },
2410
2419
  stripe: {
2411
2420
  name: "stripe",
2412
2421
  apiUrl: "https://api.stripe.com",
@@ -4784,8 +4793,146 @@ HEADERS: { "Authorization": "Bearer <<token>>", "Content-Type": "application/jso
4784
4793
  Tenant-Specific Endpoints: Multi-tenant apps need tenant ID in OAuth URLs (/04a63d67.../oauth2/v2.0/authorize) instead of /common endpoint
4785
4794
  Credentials Needed: Application (client) ID + Client Secret (generated under Certificates & secrets - copy the Value immediately, not the Secret ID)
4786
4795
  API Permissions: Add Microsoft Graph permissions (e.g., Sites.ReadWrite.All) under API permissions, then grant admin consent if you have admin rights
4787
- Scopes Must Include: Always add offline_access scope to get refresh tokens for long-term access without re-authentication
4796
+ Scopes Must Include: Always add offline_access scope to get refresh tokens for long-term access without re-authentication
4788
4797
  `
4798
+ },
4799
+ dynamics365_sales: {
4800
+ name: "dynamics365_sales",
4801
+ apiUrl: "https://<<org>>.crm<<region_number>>.dynamics.com/api/data/v9.2",
4802
+ regex: "^.*(crm\\d*\\.dynamics\\.com|dynamicscrm).*$",
4803
+ icon: "lucide:square-percent",
4804
+ docsUrl: "https://learn.microsoft.com/en-us/power-apps/developer/data-platform/webapi/query-data-web-api",
4805
+ preferredAuthType: "oauth",
4806
+ oauth: {
4807
+ authUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
4808
+ tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
4809
+ scopes: "https://<<org>>.crm<<region_number>>.dynamics.com/user_impersonation offline_access",
4810
+ grant_type: "authorization_code"
4811
+ },
4812
+ keywords: [
4813
+ "accounts",
4814
+ "contacts",
4815
+ "leads",
4816
+ "opportunities",
4817
+ "quotes",
4818
+ "salesorders",
4819
+ "salesorderdetails",
4820
+ "products",
4821
+ "incidents",
4822
+ "campaigns",
4823
+ "invoices",
4824
+ "competitors",
4825
+ "teams",
4826
+ "systemusers",
4827
+ "businessunits",
4828
+ "odata",
4829
+ "dynamics",
4830
+ "crm",
4831
+ "dynamics 365 sales",
4832
+ "dataverse",
4833
+ "oauth"
4834
+ ],
4835
+ systemSpecificInstructions: `Dynamics 365 Sales \u2014 Dataverse Web API v9.2
4836
+
4837
+ REQUIRED HEADERS (in addition to Authorization):
4838
+ - OData-MaxVersion: 4.0
4839
+ - OData-Version: 4.0
4840
+ - If-None-Match: null (recommended \u2014 prevents stale cached data, especially on $expand queries)
4841
+
4842
+ QUERY BEHAVIOR:
4843
+ - $skip is NOT supported. Use @odata.nextLink for pagination (default page size 5000, control with Prefer: odata.maxpagesize=<n>).
4844
+ - PAGINATION IN SUPERGLUE: Use cursorBased pagination with cursorPath "@odata.nextLink":
4845
+ pagination: { type: "cursorBased", pageSize: "5000", cursorPath: "@odata.nextLink" }
4846
+ The cursor is a full URL \u2014 use <<cursor>> as the step URL (set the initial URL via a "cursor" input variable).
4847
+ - $apply is supported for aggregations.
4848
+ - Lookup fields (foreign keys) are named _<field>_value \u2014 e.g. _parentaccountid_value, _customerid_value.
4849
+ - To get formatted/display values (e.g. option set labels, currency formatting), add header: Prefer: odata.include-annotations="OData.Community.Display.V1.FormattedValue"
4850
+ - FetchXML: GET /api/data/v9.2/<entity>?fetchXml=<url_encoded_xml> for complex aggregations and outer joins that OData $expand cannot express.
4851
+ - Max URL length is 32KB. For long queries, wrap in a $batch POST request (raises limit to 64KB).
4852
+ - Max individual OData URL segment is 260 chars \u2014 use parameter aliases (@p1=value) to shorten segments.
4853
+
4854
+ WRITE SPECIFICS:
4855
+ - PATCH doubles as an upsert when the target record doesn't exist (by ID or alternate key). Use If-Match: * to prevent accidental creates. Use If-None-Match: * to prevent accidental updates.
4856
+ - Setting lookups on write: use @odata.bind \u2014 e.g. "parentaccountid@odata.bind": "/accounts(<guid>)"
4857
+ - Disassociating a lookup: set the navigation property to null (without @odata.bind), e.g. "parentcustomerid_account": null
4858
+
4859
+ RATE LIMITS (Service Protection):
4860
+ - 429 Too Many Requests returned when limits exceeded. Always implement Retry-After handling.
4861
+ - Per-user limits per 5-min window: 6,000 requests, 20 min combined execution time, 52+ concurrent requests.
4862
+ - These are per web server \u2014 actual capacity varies by environment.
4863
+
4864
+ DISCOVERING CUSTOM ENTITIES:
4865
+ GET /api/data/v9.2/EntityDefinitions?$filter=IsCustomEntity eq true&$select=LogicalName,DisplayName
4866
+ `
4867
+ },
4868
+ dynamics365_business_central: {
4869
+ name: "dynamics365_business_central",
4870
+ apiUrl: "https://api.businesscentral.dynamics.com/v2.0/<<environment>>/api/v2.0",
4871
+ regex: "^.*(businesscentral\\.dynamics\\.com|business[\\s\\-]?central).*$",
4872
+ icon: "lucide:landmark",
4873
+ docsUrl: "https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/api-reference/v2.0/",
4874
+ preferredAuthType: "oauth",
4875
+ oauth: {
4876
+ authUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
4877
+ tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
4878
+ scopes: "https://api.businesscentral.dynamics.com/.default offline_access",
4879
+ grant_type: "authorization_code"
4880
+ },
4881
+ keywords: [
4882
+ "customers",
4883
+ "vendors",
4884
+ "items",
4885
+ "salesOrders",
4886
+ "salesInvoices",
4887
+ "salesQuotes",
4888
+ "purchaseOrders",
4889
+ "purchaseInvoices",
4890
+ "generalLedgerEntries",
4891
+ "journals",
4892
+ "accounts",
4893
+ "employees",
4894
+ "dimensions",
4895
+ "warehouses",
4896
+ "inventory",
4897
+ "odata",
4898
+ "dynamics",
4899
+ "erp",
4900
+ "business central",
4901
+ "finance",
4902
+ "oauth"
4903
+ ],
4904
+ systemSpecificInstructions: `Dynamics 365 Business Central \u2014 API v2.0
4905
+
4906
+ COMPANY SCOPING: All entities are scoped under a company. List companies first: GET /api/v2.0/companies, then access entities at /companies(<id>)/<entity>.
4907
+
4908
+ WRITE SPECIFICS:
4909
+ - Updates REQUIRE an If-Match header with the entity's @odata.etag value \u2014 omitting it will fail.
4910
+ - Do NOT insert child records belonging to the same parent in parallel \u2014 causes locks. Use $batch to serialize them.
4911
+ - Transactional $batch: add Isolation: snapshot header for all-or-nothing batch operations. Max 100 operations per $batch.
4912
+
4913
+ PAGINATION: Uses @odata.nextLink (a full URL for the next page). In superglue, use offsetBased pagination with $top and $skip since BC supports $skip:
4914
+ pagination: { type: "offsetBased", pageSize: "1000" }
4915
+ queryParams: { "$top": "<<limit>>", "$skip": "<<offset>>" }
4916
+
4917
+ QUERY LIMITS:
4918
+ - Max page size: 20,000 entities per response (returns 413 if exceeded).
4919
+ - Max request body size: 350 MB.
4920
+ - Request timeout: 8 minutes \u2014 long-running requests get 408 or 504.
4921
+ - Use Data-Access-Intent: ReadOnly header on GET requests that don't need latest data (routes to read replica, reduces load).
4922
+
4923
+ RATE LIMITS:
4924
+ - 429 Too Many Requests when limits exceeded. Always implement Retry-After handling.
4925
+ - Per-user: 6,000 requests per 5-min sliding window, 5 concurrent requests, 100 max connections.
4926
+
4927
+ PERFORMANCE GOTCHAS:
4928
+ - Avoid temp-table-based API pages with >100 records \u2014 no caching, poor pagination.
4929
+ - Calculated/complex fields on API pages are expensive. Prefer stored values.
4930
+ - API pages/queries are up to 10x faster than SOAP endpoints \u2014 always prefer API v2.0.
4931
+
4932
+ WEBHOOKS: Supports up to 200 webhook subscriptions for entity change notifications via /subscriptions.
4933
+
4934
+ CUSTOM APIs: Publishers expose custom API pages at /api/{publisher}/{group}/{version}/companies(<id>)/<endpoint>, NOT under /api/v2.0/.
4935
+ `
4789
4936
  },
4790
4937
  redis: {
4791
4938
  name: "redis",
@@ -5409,7 +5556,7 @@ EXAMPLE WORKFLOWS:
5409
5556
  icon: system.icon || "default"
5410
5557
  }))
5411
5558
  ];
5412
- function findTemplateForSystem3(system) {
5559
+ function findTemplateForSystem4(system) {
5413
5560
  if (system.templateName && exports2.systems[system.templateName]) {
5414
5561
  return { key: system.templateName, template: exports2.systems[system.templateName] };
5415
5562
  }
@@ -5451,7 +5598,7 @@ EXAMPLE WORKFLOWS:
5451
5598
  return [...new Set(keywords)];
5452
5599
  }
5453
5600
  function enrichWithTemplate(input) {
5454
- const match = findTemplateForSystem3(input);
5601
+ const match = findTemplateForSystem4(input);
5455
5602
  if (!match) {
5456
5603
  return input;
5457
5604
  }
@@ -5488,7 +5635,7 @@ EXAMPLE WORKFLOWS:
5488
5635
  tokenContentType: creds.tokenContentType,
5489
5636
  extraHeaders
5490
5637
  };
5491
- const match = findTemplateForSystem3(system);
5638
+ const match = findTemplateForSystem4(system);
5492
5639
  const templateOAuth = match?.template.oauth;
5493
5640
  return {
5494
5641
  tokenAuthMethod: storedConfig.tokenAuthMethod ?? templateOAuth?.tokenAuthMethod,
@@ -5500,7 +5647,7 @@ EXAMPLE WORKFLOWS:
5500
5647
  if (system.credentials?.token_url) {
5501
5648
  return system.credentials.token_url;
5502
5649
  }
5503
- const match = findTemplateForSystem3(system);
5650
+ const match = findTemplateForSystem4(system);
5504
5651
  if (match?.template.oauth?.tokenUrl) {
5505
5652
  return match.template.oauth.tokenUrl;
5506
5653
  }
@@ -19977,6 +20124,8 @@ var require_utils = __commonJS({
19977
20124
  var getConnectionProtocol2 = (url) => {
19978
20125
  if (url.startsWith("postgres://") || url.startsWith("postgresql://"))
19979
20126
  return "postgres";
20127
+ if (url.startsWith("redis://") || url.startsWith("rediss://"))
20128
+ return "redis";
19980
20129
  if (url.startsWith("ftp://") || url.startsWith("ftps://") || url.startsWith("sftp://"))
19981
20130
  return "sftp";
19982
20131
  if (url.startsWith("smb://"))
@@ -21785,7 +21934,7 @@ var import_node_util = require("util");
21785
21934
  // package.json
21786
21935
  var package_default = {
21787
21936
  name: "@superglue/cli",
21788
- version: "1.1.7",
21937
+ version: "1.1.8",
21789
21938
  bin: {
21790
21939
  sg: "./dist/cli.js"
21791
21940
  },
@@ -22935,12 +23084,21 @@ async function collectSensitiveCredentials(fields) {
22935
23084
  return creds;
22936
23085
  }
22937
23086
  function registerCreateCommand(parent, getContext2) {
22938
- parent.command("create").description("Create a new system").option("--config <file>", "JSON config file").option("--id <id>", "System ID (derived from name if omitted)").option("--name <name>", "Human-readable name (required)").option("--url <url>", "API URL").option("--template <id>", "Template ID").option("--instructions <text>", "Specific instructions").option("--credentials <json>", "Non-sensitive credentials JSON").option("--sensitive-credentials <fields>", "Comma-separated sensitive credential field names").option("--docs-url <url>", "Documentation URL to scrape").option("--openapi-url <url>", "OpenAPI spec URL").addOption(
23087
+ parent.command("create").description("Create a new system").option("--config <file>", "JSON config file").option("--id <id>", "System ID (derived from name if omitted)").option("--name <name>", "Human-readable name (required)").option("--url <url>", "API URL").option(
23088
+ "--template <id>",
23089
+ "Template ID \u2014 auto-fills URL, OAuth config, and credentials. Auto-detected from URL if omitted."
23090
+ ).option("--instructions <text>", "Specific instructions").option("--credentials <json>", "Non-sensitive credentials JSON").option("--sensitive-credentials <fields>", "Comma-separated sensitive credential field names").option("--docs-url <url>", "Documentation URL to scrape").option("--openapi-url <url>", "OpenAPI spec URL").addOption(
22939
23091
  new import_commander.Option("--env <environment>", "Environment: dev or prod (default: prod)").choices([
22940
23092
  "dev",
22941
23093
  "prod"
22942
23094
  ])
22943
- ).action(async (opts) => {
23095
+ ).addHelpText("after", () => {
23096
+ const oauthTemplates = Object.entries(import_shared6.systems).filter(([, t]) => t.oauth).map(([key, t]) => ` ${key.padEnd(24)} ${t.name}`).join("\n");
23097
+ return `
23098
+ OAuth templates (use with --template or auto-detected from URL):
23099
+ ${oauthTemplates}
23100
+ `;
23101
+ }).action(async (opts) => {
22944
23102
  const { client } = getContext2();
22945
23103
  let systemInput;
22946
23104
  if (opts.config) {
@@ -22973,14 +23131,34 @@ function registerCreateCommand(parent, getContext2) {
22973
23131
  environment: opts.env === "dev" || opts.env === "prod" ? opts.env : void 0
22974
23132
  };
22975
23133
  }
22976
- if (opts.template) {
22977
- const template = import_shared6.systems[opts.template];
23134
+ let templateKey = opts.template;
23135
+ let template;
23136
+ if (templateKey) {
23137
+ template = import_shared6.systems[templateKey];
22978
23138
  if (!template) {
22979
23139
  error(
22980
- `Template not found: ${opts.template}. Available: ${Object.keys(import_shared6.systems).join(", ")}`
23140
+ `Template not found: ${templateKey}. Available: ${Object.keys(import_shared6.systems).join(", ")}`
22981
23141
  );
22982
23142
  process.exit(1);
22983
23143
  }
23144
+ } else if (systemInput.url || systemInput.name || systemInput.id) {
23145
+ const match = (0, import_shared6.findTemplateForSystem)({
23146
+ url: systemInput.url,
23147
+ name: systemInput.name,
23148
+ id: systemInput.id
23149
+ });
23150
+ if (match) {
23151
+ templateKey = match.key;
23152
+ template = match.template;
23153
+ if (process.stderr.isTTY) {
23154
+ process.stderr.write(
23155
+ `${c.dim}Auto-detected template: ${c.bold}${templateKey}${c.reset}${c.dim} (use --template to override)${c.reset}
23156
+ `
23157
+ );
23158
+ }
23159
+ }
23160
+ }
23161
+ if (template && templateKey) {
22984
23162
  const oauthCreds = {};
22985
23163
  if (template.oauth) {
22986
23164
  if (template.oauth.authUrl) oauthCreds.auth_url = template.oauth.authUrl;
@@ -22992,7 +23170,7 @@ function registerCreateCommand(parent, getContext2) {
22992
23170
  ...systemInput,
22993
23171
  name: systemInput.name || template.name,
22994
23172
  url: systemInput.url || template.apiUrl,
22995
- templateName: opts.template,
23173
+ templateName: templateKey,
22996
23174
  credentials: { ...oauthCreds, ...systemInput.credentials }
22997
23175
  };
22998
23176
  }
@@ -23031,12 +23209,14 @@ function registerCreateCommand(parent, getContext2) {
23031
23209
  if (process.argv.includes("--json") || !process.stdout.isTTY) {
23032
23210
  output({
23033
23211
  success: true,
23034
- system: { id: result.id, name: result.name, url: result.url }
23212
+ system: { id: result.id, name: result.name, url: result.url },
23213
+ ...templateKey ? { template: templateKey } : {}
23035
23214
  });
23036
23215
  } else {
23037
23216
  success(`System created: ${c.bold}${result.id}${c.reset}`, {
23038
23217
  name: result.name,
23039
- url: result.url
23218
+ url: result.url,
23219
+ ...templateKey ? { template: templateKey } : {}
23040
23220
  });
23041
23221
  }
23042
23222
  } catch (err) {
@@ -235,21 +235,26 @@ sg system call --url "postgres://<<my_db_host>>:5432/mydb" \
235
235
  --system-id my_db \
236
236
  --body '{"query":"SELECT * FROM users LIMIT 5"}'
237
237
 
238
+ # Redis command
239
+ sg system call --url "redis://<<my_redis_host>>:6379/0" \
240
+ --system-id my_redis \
241
+ --body '{"command":"HGETALL","args":["user:123"]}'
242
+
238
243
  # File server (SFTP)
239
244
  sg system call --url "sftp://<<my_sftp_host>>:22/data" \
240
245
  --system-id my_sftp \
241
246
  --body '{"operation":"list","path":"/"}'
242
247
  ```
243
248
 
244
- | Flag | Description |
245
- | ------------------- | --------------------------------------------------------- |
246
- | `--url <url>` | Full URL with protocol (http/https/postgres/sftp/ftp/smb) |
247
- | `--system-id <id>` | System ID for credential injection |
248
- | `--method <method>` | HTTP method (GET, POST, PUT, DELETE, PATCH) |
249
- | `--headers <json>` | HTTP headers JSON with credential placeholders |
250
- | `--body <string>` | Request body (JSON string) |
251
- | `--env <env>` | Environment: `dev` or `prod` |
252
- | `--file <key=path>` | File references (repeatable) |
249
+ | Flag | Description |
250
+ | ------------------- | ---------------------------------------------------------------------- |
251
+ | `--url <url>` | Full URL with protocol (http/https/postgres/redis/rediss/sftp/ftp/smb) |
252
+ | `--system-id <id>` | System ID for credential injection |
253
+ | `--method <method>` | HTTP method (GET, POST, PUT, DELETE, PATCH) |
254
+ | `--headers <json>` | HTTP headers JSON with credential placeholders |
255
+ | `--body <string>` | Request body (JSON string) |
256
+ | `--env <env>` | Environment: `dev` or `prod` |
257
+ | `--file <key=path>` | File references (repeatable) |
253
258
 
254
259
  **`sg system search-docs`** — Search system documentation.
255
260
 
@@ -366,15 +371,26 @@ All step config fields support `<<variable>>` placeholders:
366
371
  | -------------------------- | --------------------------- |
367
372
  | `<<fieldName>>` | Payload field or credential |
368
373
  | `<<systemId_credKey>>` | System credential |
374
+ | `<<sg_auth_email>>` | Email of authenticated user |
369
375
  | `<<(sourceData) => expr>>` | JavaScript expression |
370
376
 
371
377
  **CRITICAL**: Simple `<<varName>>` only works for **top-level keys**. No dots, no nesting.
372
378
 
373
- - VALID: `<<userId>>`, `<<currentItem>>`, `<<stripe_api_key>>`
379
+ - VALID: `<<userId>>`, `<<currentItem>>`, `<<stripe_api_key>>`, `<<sg_auth_email>>`
374
380
  - INVALID: `<<currentItem.id>>`, `<<sourceData.userId>>` — these FAIL at runtime
375
381
 
376
382
  For nested access, use arrow functions: `<<(sourceData) => sourceData.currentItem.id>>`
377
383
 
384
+ ### Context Variables
385
+
386
+ Context variables provide information about the execution environment:
387
+
388
+ - `<<sg_auth_email>>` — Email of the authenticated user who triggered the tool
389
+
390
+ **Use case**: Personalize tool outputs based on who runs the tool (e.g., user-specific permissions, account lookups, audit logging).
391
+
392
+ **Important**: `sg_auth_email` is NOT available in scheduled executions. Tools using `<<sg_auth_email>>` will fail if run via scheduler.
393
+
378
394
  ### sourceData Object
379
395
 
380
396
  `sourceData` is the cumulative state available to all JS expressions (data selectors, transforms, variable expressions):
@@ -442,7 +458,7 @@ Empty arrays are valid — the step simply skips execution.
442
458
 
443
459
  ### Two Step Types
444
460
 
445
- **Request step** (HTTP, Postgres, FTP/SFTP/SMB) — makes an API call:
461
+ **Request step** (HTTP, Postgres, Redis, FTP/SFTP/SMB) — makes an API call:
446
462
 
447
463
  ```json
448
464
  {
@@ -534,9 +550,31 @@ Templates auto-populate endpoints and OAuth config. Use `sg system find` to disc
534
550
 
535
551
  ### OAuth Flow
536
552
 
537
- 1. `sg system create --name "Gmail" --url https://gmail.googleapis.com --credentials '{"auth_url":"...","token_url":"..."}' --sensitive-credentials client_id,client_secret`
538
- 2. `sg system oauth --system-id gmail --scopes "..."`
539
- 3. User authenticates in browser → tokens saved automatically
553
+ **IMPORTANT:** For OAuth systems, always use `--template` when creating the system. Templates auto-configure OAuth URLs, token exchange settings, and scopes. Without a template, OAuth will likely fail or require manual credential setup.
554
+
555
+ If you don't know the template ID, the CLI auto-detects it from the URL — but using `--template` explicitly is more reliable.
556
+
557
+ **Step 1: Create the system with template and credentials**
558
+
559
+ ```bash
560
+ # Best: explicit template (auto-fills URL and OAuth config)
561
+ sg system create --name "Gmail" --template gmail \
562
+ --credentials '{"client_id":"...","client_secret":"..."}'
563
+
564
+ # Also works: URL-based auto-detection (template inferred from googleapis.com)
565
+ sg system create --name "Gmail" --url https://gmail.googleapis.com \
566
+ --credentials '{"client_id":"...","client_secret":"..."}'
567
+ ```
568
+
569
+ **Step 2: Authenticate via OAuth**
570
+
571
+ ```bash
572
+ sg system oauth --system-id gmail --scopes "https://www.googleapis.com/auth/gmail.readonly"
573
+ ```
574
+
575
+ **Step 3:** User authenticates in browser → tokens saved automatically
576
+
577
+ **Common OAuth templates:** gmail, google_drive, google_calendar, google_sheets, slack, salesforce, github, jira, confluence, notion, airtable, hubspot, shopify, dropbox, zoom, microsoft_365
540
578
 
541
579
  ### System IDs
542
580
 
@@ -608,6 +646,7 @@ For detailed documentation on specific topics, read these files in the `referenc
608
646
  | ------------------------------------- | ------------------------------------------------------------------------------------------------ |
609
647
  | `references/integration.md` | **READ THIS** when deploying tools to production - SDK usage, REST API, webhooks, error handling |
610
648
  | `references/databases.md` | Building tools that query PostgreSQL/MySQL databases |
649
+ | `references/redis.md` | Building tools that interact with Redis (commands, pipelines, data types) |
611
650
  | `references/file-servers.md` | Building tools that interact with FTP/SFTP/SMB file servers |
612
651
  | `references/transforms-and-output.md` | Complex data transformations, output shaping, JS sandbox constraints |
613
652
 
@@ -0,0 +1,190 @@
1
+ # Redis Steps
2
+
3
+ ## Step Configuration
4
+
5
+ ```typescript
6
+ {
7
+ type: "request",
8
+ systemId: "my_redis",
9
+ url: "redis://<<my_redis_username>>:<<my_redis_password>>@<<my_redis_host>>:<<my_redis_port>>/<<my_redis_database>>",
10
+ method: "POST", // ignored for Redis but required by schema
11
+ body: '{"command": "GET", "args": ["<<keyName>>"]}'
12
+ }
13
+ ```
14
+
15
+ Configure the connection URL depending on which credentials are saved in the stored Redis system - reference them accordingly.
16
+
17
+ ## URL Format
18
+
19
+ ```
20
+ redis://user:password@host:port/database
21
+ rediss://user:password@host:port/database (TLS)
22
+ ```
23
+
24
+ - `redis://` for standard connections
25
+ - `rediss://` for TLS-encrypted connections (e.g. Redis Cloud, AWS ElastiCache with encryption)
26
+ - `/database` is the database number (0-15), defaults to 0 if omitted
27
+
28
+ All credential variables are resolved before connecting. Trailing slashes are cleaned.
29
+
30
+ ## Body Format
31
+
32
+ JSON string with `command` (required) and optional `args` array. Body can be a single command object or an array of commands (pipelined in a single round-trip):
33
+
34
+ ```json
35
+ { "command": "GET", "args": ["mykey"] }
36
+ ```
37
+
38
+ Multiple commands:
39
+
40
+ ```json
41
+ [
42
+ { "command": "GET", "args": ["user:1:name"] },
43
+ { "command": "HGETALL", "args": ["user:1"] }
44
+ ]
45
+ ```
46
+
47
+ ### Common Commands
48
+
49
+ **String operations:**
50
+
51
+ ```json
52
+ { "command": "GET", "args": ["user:123:name"] }
53
+ { "command": "SET", "args": ["user:123:name", "Alice"] }
54
+ { "command": "MGET", "args": ["key1", "key2", "key3"] }
55
+ { "command": "INCR", "args": ["counter"] }
56
+ ```
57
+
58
+ **Hash operations:**
59
+
60
+ ```json
61
+ { "command": "HGETALL", "args": ["user:123"] }
62
+ { "command": "HGET", "args": ["user:123", "email"] }
63
+ { "command": "HSET", "args": ["user:123", "email", "alice@example.com"] }
64
+ { "command": "HMGET", "args": ["user:123", "name", "email"] }
65
+ ```
66
+
67
+ **List operations:**
68
+
69
+ ```json
70
+ { "command": "LRANGE", "args": ["queue:tasks", "0", "-1"] }
71
+ { "command": "LPUSH", "args": ["queue:tasks", "task1"] }
72
+ { "command": "RPOP", "args": ["queue:tasks"] }
73
+ { "command": "LLEN", "args": ["queue:tasks"] }
74
+ ```
75
+
76
+ **Set operations:**
77
+
78
+ ```json
79
+ { "command": "SMEMBERS", "args": ["tags:post:1"] }
80
+ { "command": "SADD", "args": ["tags:post:1", "redis", "database"] }
81
+ { "command": "SISMEMBER", "args": ["tags:post:1", "redis"] }
82
+ ```
83
+
84
+ **Sorted set operations:**
85
+
86
+ ```json
87
+ { "command": "ZRANGE", "args": ["leaderboard", "0", "-1", "WITHSCORES"] }
88
+ { "command": "ZADD", "args": ["leaderboard", "100", "player1"] }
89
+ { "command": "ZRANK", "args": ["leaderboard", "player1"] }
90
+ ```
91
+
92
+ **Key operations:**
93
+
94
+ ```json
95
+ { "command": "KEYS", "args": ["user:*"] }
96
+ { "command": "EXISTS", "args": ["mykey"] }
97
+ { "command": "TTL", "args": ["session:abc"] }
98
+ { "command": "EXPIRE", "args": ["session:abc", "3600"] }
99
+ { "command": "DEL", "args": ["mykey"] }
100
+ { "command": "TYPE", "args": ["mykey"] }
101
+ ```
102
+
103
+ **Scan (safe iteration):**
104
+
105
+ ```json
106
+ { "command": "SCAN", "args": ["0", "MATCH", "user:*", "COUNT", "100"] }
107
+ { "command": "HSCAN", "args": ["myhash", "0", "MATCH", "field*"] }
108
+ ```
109
+
110
+ ### Dynamic Arguments with Variables
111
+
112
+ ```json
113
+ { "command": "GET", "args": ["<<(sourceData) => `user:${sourceData.userId}:profile`>>"] }
114
+ ```
115
+
116
+ ```json
117
+ { "command": "HSET", "args": ["<<userKey>>", "<<fieldName>>", "<<fieldValue>>"] }
118
+ ```
119
+
120
+ ## Connection Management
121
+
122
+ - A fresh connection is created per execution and closed after completion
123
+ - TLS auto-configured for `rediss://` URLs (with `rejectUnauthorized: false`)
124
+ - 5-second connection timeout, 30-second command timeout
125
+ - Array commands (pipeline) run on a single connection for efficiency
126
+
127
+ ## Retry Logic
128
+
129
+ Default retries from server config. On final failure, error includes command and args.
130
+
131
+ ## Return Value
132
+
133
+ Returns the raw Redis response:
134
+
135
+ - `GET` / `HGET`: string or null
136
+ - `HGETALL`: object with field-value pairs (flattened array from Redis, parsed by ioredis)
137
+ - `LRANGE` / `SMEMBERS` / `KEYS`: array of strings
138
+ - `SET` / `DEL` / `EXPIRE`: "OK" or integer
139
+ - `INCR` / `LLEN` / `SADD`: integer
140
+ - `MGET` / `HMGET`: array (nulls for missing keys)
141
+ - `SCAN` / `HSCAN`: array of [cursor, results]
142
+
143
+ ## Multiple Commands (Pipeline)
144
+
145
+ Pass an array of commands to execute them in a single round-trip (like FTP/SFTP batch operations):
146
+
147
+ ```json
148
+ [
149
+ { "command": "GET", "args": ["user:1:name"] },
150
+ { "command": "HGETALL", "args": ["user:1"] },
151
+ { "command": "LRANGE", "args": ["tasks", "0", "-1"] }
152
+ ]
153
+ ```
154
+
155
+ Returns an array of results, one per command:
156
+
157
+ ```json
158
+ [
159
+ { "command": "GET", "result": "Alice" },
160
+ { "command": "HGETALL", "result": ["name", "Alice", "email", "alice@example.com"] },
161
+ { "command": "LRANGE", "result": ["task3", "task2", "task1"] }
162
+ ]
163
+ ```
164
+
165
+ If an individual command fails, that entry has `error` instead of `result`:
166
+
167
+ ```json
168
+ [
169
+ { "command": "GET", "result": "Alice" },
170
+ { "command": "WRONGCMD", "error": "ERR unknown command 'WRONGCMD'" }
171
+ ]
172
+ ```
173
+
174
+ A single command returns a single result. Multiple commands return an array — same pattern as FTP/SFTP operations.
175
+
176
+ ## Batch Operations via Loop
177
+
178
+ Use a data selector returning an array to execute a command per item:
179
+
180
+ ```javascript
181
+ // dataSelector: (sourceData) => sourceData.userIds
182
+ // body: {"command": "HGETALL", "args": ["<<(sourceData) => `user:${sourceData.currentItem}`>>"]}
183
+ ```
184
+
185
+ ## Important Notes
186
+
187
+ - All args must be strings (Redis protocol is text-based) - numbers are auto-coerced
188
+ - Use `SCAN` instead of `KEYS` in production for large keyspaces
189
+ - `HGETALL` returns an empty object `{}` for non-existent keys
190
+ - Use array body for multi-command pipelines instead of separate steps
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superglue/cli",
3
- "version": "1.1.7",
3
+ "version": "1.1.8",
4
4
  "bin": {
5
5
  "sg": "./dist/cli.js"
6
6
  },
@@ -15,6 +15,7 @@
15
15
  "fast-json-patch": "^3.1.1"
16
16
  },
17
17
  "devDependencies": {
18
+ "@superglue/shared": "file:../shared",
18
19
  "@types/node": "^20.0.0",
19
20
  "tsup": "^8.0.0",
20
21
  "typescript": "^5.0.0"