@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.
- package/.claude-plugin/plugin.json +2 -9
- package/dist/cli.js +195 -15
- package/dist/skills/superglue/SKILL.md +53 -14
- package/dist/skills/superglue/references/redis.md +190 -0
- package/package.json +2 -1
- package/skills/superglue/SKILL.md +53 -14
- package/skills/superglue/references/redis.md +190 -0
- package/dist/dist/cli.js +0 -23727
- package/dist/dist/skills/superglue/SKILL.md +0 -628
- package/dist/dist/skills/superglue/agents/openai.yaml +0 -7
- package/dist/dist/skills/superglue/assets/superglue.png +0 -0
- package/dist/dist/skills/superglue/references/databases.md +0 -88
- package/dist/dist/skills/superglue/references/file-servers.md +0 -136
- package/dist/dist/skills/superglue/references/integration.md +0 -279
- package/dist/dist/skills/superglue/references/transforms-and-output.md +0 -92
|
@@ -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": "
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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.
|
|
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(
|
|
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
|
-
).
|
|
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
|
-
|
|
22977
|
-
|
|
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: ${
|
|
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:
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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.
|
|
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"
|