@superglue/cli 1.1.7 → 1.1.9
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 +296 -68
- 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.9",
|
|
21789
21938
|
bin: {
|
|
21790
21939
|
sg: "./dist/cli.js"
|
|
21791
21940
|
},
|
|
@@ -22310,61 +22459,92 @@ async function choose(message, options, defaultIndex = 0) {
|
|
|
22310
22459
|
|
|
22311
22460
|
// src/commands/init.ts
|
|
22312
22461
|
function registerInitCommand(program2) {
|
|
22313
|
-
program2.command("init").description("Set up superglue CLI configuration").action(async () => {
|
|
22314
|
-
|
|
22315
|
-
|
|
22316
|
-
|
|
22462
|
+
program2.command("init").description("Set up superglue CLI configuration").option("--api-key <key>", "API key (skips interactive prompt)").option("--endpoint <url>", "API endpoint", "https://api.superglue.cloud").option("--web-endpoint <url>", "Web endpoint for OAuth callbacks").option("--output-mode <mode>", "Output mode: stdout or stdout+file", "stdout").option("--output-dir <dir>", "Output directory for stdout+file mode", ".superglue/output").option("--global", "Save config globally (~/.superglue/) instead of locally").action(async (opts) => {
|
|
22463
|
+
const isNonInteractive = opts.apiKey || !process.stdin.isTTY;
|
|
22464
|
+
let apiKey;
|
|
22465
|
+
let endpoint;
|
|
22466
|
+
let webEndpoint;
|
|
22467
|
+
let outputMode;
|
|
22468
|
+
let outputDir;
|
|
22469
|
+
let preferLocal;
|
|
22470
|
+
if (isNonInteractive) {
|
|
22471
|
+
apiKey = opts.apiKey || process.env.SUPERGLUE_API_KEY;
|
|
22472
|
+
if (!apiKey) {
|
|
22473
|
+
error("API key is required (--api-key or SUPERGLUE_API_KEY)");
|
|
22474
|
+
process.exit(1);
|
|
22475
|
+
}
|
|
22476
|
+
endpoint = opts.endpoint;
|
|
22477
|
+
const defaultWeb = endpoint.replace(/:3002\b/, ":3001").replace(/api\.superglue/, "app.superglue");
|
|
22478
|
+
webEndpoint = opts.webEndpoint || defaultWeb;
|
|
22479
|
+
outputMode = opts.outputMode === "stdout+file" ? "stdout+file" : "stdout";
|
|
22480
|
+
outputDir = opts.outputDir;
|
|
22481
|
+
preferLocal = !opts.global;
|
|
22482
|
+
const spin = spinner("Verifying connection...");
|
|
22483
|
+
try {
|
|
22484
|
+
const client = new import_shared2.SuperglueClient({ apiKey, apiEndpoint: endpoint });
|
|
22485
|
+
await client.listSystems(1);
|
|
22486
|
+
spin.stop(`${c.green}\u2713${c.reset} Connected to ${c.bold}${endpoint}${c.reset}`);
|
|
22487
|
+
} catch (err) {
|
|
22488
|
+
spin.stop(`${c.red}\u2717${c.reset} Connection failed`);
|
|
22489
|
+
error(err.message);
|
|
22490
|
+
process.exit(1);
|
|
22491
|
+
}
|
|
22492
|
+
} else {
|
|
22493
|
+
banner();
|
|
22494
|
+
console.log(` ${c.bold}Welcome to the superglue CLI setup!${c.reset}`);
|
|
22495
|
+
console.log(` ${c.dim}Let's get you connected in a few steps.${c.reset}
|
|
22317
22496
|
`);
|
|
22318
|
-
|
|
22319
|
-
|
|
22320
|
-
|
|
22321
|
-
|
|
22322
|
-
|
|
22323
|
-
|
|
22324
|
-
|
|
22325
|
-
|
|
22326
|
-
|
|
22327
|
-
|
|
22328
|
-
|
|
22329
|
-
|
|
22330
|
-
|
|
22331
|
-
|
|
22332
|
-
|
|
22333
|
-
|
|
22334
|
-
|
|
22335
|
-
|
|
22336
|
-
|
|
22337
|
-
|
|
22338
|
-
|
|
22339
|
-
|
|
22497
|
+
heading("Authentication");
|
|
22498
|
+
console.log(
|
|
22499
|
+
` ${c.dim}Get your API key at${c.reset} ${link("https://app.superglue.cloud/admin?view=api-keys")}`
|
|
22500
|
+
);
|
|
22501
|
+
console.log("");
|
|
22502
|
+
apiKey = await promptHidden(" API Key");
|
|
22503
|
+
if (!apiKey) {
|
|
22504
|
+
error("API key is required");
|
|
22505
|
+
process.exit(1);
|
|
22506
|
+
}
|
|
22507
|
+
endpoint = await prompt(" API Endpoint", opts.endpoint);
|
|
22508
|
+
const defaultWeb = endpoint.replace(/:3002\b/, ":3001").replace(/api\.superglue/, "app.superglue");
|
|
22509
|
+
webEndpoint = await prompt(" Web Endpoint (for OAuth callbacks)", defaultWeb);
|
|
22510
|
+
const spin = spinner("Verifying connection...");
|
|
22511
|
+
try {
|
|
22512
|
+
const client = new import_shared2.SuperglueClient({ apiKey, apiEndpoint: endpoint });
|
|
22513
|
+
await client.listSystems(1);
|
|
22514
|
+
spin.stop(`${c.green}\u2713${c.reset} Connected to ${c.bold}${endpoint}${c.reset}`);
|
|
22515
|
+
} catch (err) {
|
|
22516
|
+
spin.stop(`${c.red}\u2717${c.reset} Connection failed`);
|
|
22517
|
+
error(err.message);
|
|
22518
|
+
process.exit(1);
|
|
22519
|
+
}
|
|
22520
|
+
heading("Output Preferences");
|
|
22521
|
+
const outputModeIdx = await choose(
|
|
22522
|
+
"Output mode",
|
|
22523
|
+
[
|
|
22524
|
+
"stdout only \u2014 print results to terminal",
|
|
22525
|
+
"stdout + file \u2014 also save results as JSON files"
|
|
22526
|
+
],
|
|
22527
|
+
0
|
|
22528
|
+
);
|
|
22529
|
+
outputMode = outputModeIdx === 1 ? "stdout+file" : "stdout";
|
|
22530
|
+
outputDir = ".superglue/output";
|
|
22531
|
+
if (outputMode === "stdout+file") {
|
|
22532
|
+
outputDir = await prompt(" Output directory", ".superglue/output");
|
|
22533
|
+
}
|
|
22534
|
+
heading("Config Location");
|
|
22535
|
+
const homeDir = os2.homedir();
|
|
22536
|
+
const localPath = path2.join(process.cwd(), ".superglue");
|
|
22537
|
+
const globalPath = path2.join(homeDir, ".superglue");
|
|
22538
|
+
const configLocationIdx = await choose(
|
|
22539
|
+
"Where to save config?",
|
|
22540
|
+
[
|
|
22541
|
+
`Project (${localPath}) \u2014 config stays with this project`,
|
|
22542
|
+
`Global (${globalPath}) \u2014 shared across all projects`
|
|
22543
|
+
],
|
|
22544
|
+
0
|
|
22545
|
+
);
|
|
22546
|
+
preferLocal = configLocationIdx === 0;
|
|
22340
22547
|
}
|
|
22341
|
-
heading("Output Preferences");
|
|
22342
|
-
const outputModeIdx = await choose(
|
|
22343
|
-
"Output mode",
|
|
22344
|
-
[
|
|
22345
|
-
"stdout only \u2014 print results to terminal",
|
|
22346
|
-
"stdout + file \u2014 also save results as JSON files"
|
|
22347
|
-
],
|
|
22348
|
-
0
|
|
22349
|
-
);
|
|
22350
|
-
const outputMode = outputModeIdx === 1 ? "stdout+file" : "stdout";
|
|
22351
|
-
let outputDir = ".superglue/output";
|
|
22352
|
-
if (outputMode === "stdout+file") {
|
|
22353
|
-
outputDir = await prompt(" Output directory", ".superglue/output");
|
|
22354
|
-
}
|
|
22355
|
-
heading("Config Location");
|
|
22356
|
-
const homeDir = os2.homedir();
|
|
22357
|
-
const localPath = path2.join(process.cwd(), ".superglue");
|
|
22358
|
-
const globalPath = path2.join(homeDir, ".superglue");
|
|
22359
|
-
const configLocationIdx = await choose(
|
|
22360
|
-
"Where to save config?",
|
|
22361
|
-
[
|
|
22362
|
-
`Project (${localPath}) \u2014 config stays with this project`,
|
|
22363
|
-
`Global (${globalPath}) \u2014 shared across all projects`
|
|
22364
|
-
],
|
|
22365
|
-
0
|
|
22366
|
-
);
|
|
22367
|
-
const preferLocal = configLocationIdx === 0;
|
|
22368
22548
|
const config = {
|
|
22369
22549
|
apiKey,
|
|
22370
22550
|
endpoint,
|
|
@@ -22935,12 +23115,21 @@ async function collectSensitiveCredentials(fields) {
|
|
|
22935
23115
|
return creds;
|
|
22936
23116
|
}
|
|
22937
23117
|
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(
|
|
23118
|
+
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(
|
|
23119
|
+
"--template <id>",
|
|
23120
|
+
"Template ID \u2014 auto-fills URL, OAuth config, and credentials. Auto-detected from URL if omitted."
|
|
23121
|
+
).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
23122
|
new import_commander.Option("--env <environment>", "Environment: dev or prod (default: prod)").choices([
|
|
22940
23123
|
"dev",
|
|
22941
23124
|
"prod"
|
|
22942
23125
|
])
|
|
22943
|
-
).
|
|
23126
|
+
).addHelpText("after", () => {
|
|
23127
|
+
const oauthTemplates = Object.entries(import_shared6.systems).filter(([, t]) => t.oauth).map(([key, t]) => ` ${key.padEnd(24)} ${t.name}`).join("\n");
|
|
23128
|
+
return `
|
|
23129
|
+
OAuth templates (use with --template or auto-detected from URL):
|
|
23130
|
+
${oauthTemplates}
|
|
23131
|
+
`;
|
|
23132
|
+
}).action(async (opts) => {
|
|
22944
23133
|
const { client } = getContext2();
|
|
22945
23134
|
let systemInput;
|
|
22946
23135
|
if (opts.config) {
|
|
@@ -22973,14 +23162,34 @@ function registerCreateCommand(parent, getContext2) {
|
|
|
22973
23162
|
environment: opts.env === "dev" || opts.env === "prod" ? opts.env : void 0
|
|
22974
23163
|
};
|
|
22975
23164
|
}
|
|
22976
|
-
|
|
22977
|
-
|
|
23165
|
+
let templateKey = opts.template;
|
|
23166
|
+
let template;
|
|
23167
|
+
if (templateKey) {
|
|
23168
|
+
template = import_shared6.systems[templateKey];
|
|
22978
23169
|
if (!template) {
|
|
22979
23170
|
error(
|
|
22980
|
-
`Template not found: ${
|
|
23171
|
+
`Template not found: ${templateKey}. Available: ${Object.keys(import_shared6.systems).join(", ")}`
|
|
22981
23172
|
);
|
|
22982
23173
|
process.exit(1);
|
|
22983
23174
|
}
|
|
23175
|
+
} else if (systemInput.url || systemInput.name || systemInput.id) {
|
|
23176
|
+
const match = (0, import_shared6.findTemplateForSystem)({
|
|
23177
|
+
url: systemInput.url,
|
|
23178
|
+
name: systemInput.name,
|
|
23179
|
+
id: systemInput.id
|
|
23180
|
+
});
|
|
23181
|
+
if (match) {
|
|
23182
|
+
templateKey = match.key;
|
|
23183
|
+
template = match.template;
|
|
23184
|
+
if (process.stderr.isTTY) {
|
|
23185
|
+
process.stderr.write(
|
|
23186
|
+
`${c.dim}Auto-detected template: ${c.bold}${templateKey}${c.reset}${c.dim} (use --template to override)${c.reset}
|
|
23187
|
+
`
|
|
23188
|
+
);
|
|
23189
|
+
}
|
|
23190
|
+
}
|
|
23191
|
+
}
|
|
23192
|
+
if (template && templateKey) {
|
|
22984
23193
|
const oauthCreds = {};
|
|
22985
23194
|
if (template.oauth) {
|
|
22986
23195
|
if (template.oauth.authUrl) oauthCreds.auth_url = template.oauth.authUrl;
|
|
@@ -22992,7 +23201,7 @@ function registerCreateCommand(parent, getContext2) {
|
|
|
22992
23201
|
...systemInput,
|
|
22993
23202
|
name: systemInput.name || template.name,
|
|
22994
23203
|
url: systemInput.url || template.apiUrl,
|
|
22995
|
-
templateName:
|
|
23204
|
+
templateName: templateKey,
|
|
22996
23205
|
credentials: { ...oauthCreds, ...systemInput.credentials }
|
|
22997
23206
|
};
|
|
22998
23207
|
}
|
|
@@ -23031,12 +23240,14 @@ function registerCreateCommand(parent, getContext2) {
|
|
|
23031
23240
|
if (process.argv.includes("--json") || !process.stdout.isTTY) {
|
|
23032
23241
|
output({
|
|
23033
23242
|
success: true,
|
|
23034
|
-
system: { id: result.id, name: result.name, url: result.url }
|
|
23243
|
+
system: { id: result.id, name: result.name, url: result.url },
|
|
23244
|
+
...templateKey ? { template: templateKey } : {}
|
|
23035
23245
|
});
|
|
23036
23246
|
} else {
|
|
23037
23247
|
success(`System created: ${c.bold}${result.id}${c.reset}`, {
|
|
23038
23248
|
name: result.name,
|
|
23039
|
-
url: result.url
|
|
23249
|
+
url: result.url,
|
|
23250
|
+
...templateKey ? { template: templateKey } : {}
|
|
23040
23251
|
});
|
|
23041
23252
|
}
|
|
23042
23253
|
} catch (err) {
|
|
@@ -23334,6 +23545,7 @@ function registerDocsCommand(parent, getContext2) {
|
|
|
23334
23545
|
|
|
23335
23546
|
// src/commands/system/oauth.ts
|
|
23336
23547
|
var import_node_child_process2 = require("child_process");
|
|
23548
|
+
var import_node_crypto = __toESM(require("crypto"));
|
|
23337
23549
|
var import_shared9 = __toESM(require_dist2());
|
|
23338
23550
|
function openBrowser(url) {
|
|
23339
23551
|
const platform = process.platform;
|
|
@@ -23449,6 +23661,21 @@ function registerOAuthCommand(parent, getContext2) {
|
|
|
23449
23661
|
process.exit(1);
|
|
23450
23662
|
}
|
|
23451
23663
|
const encryptedApiKey = (0, import_shared9.encryptCliApiKey)(config.apiKey, opts.systemId, encryptionSecret);
|
|
23664
|
+
const usingTemplate = Boolean(templateMatch?.key);
|
|
23665
|
+
let clientCredentialsUid;
|
|
23666
|
+
if (!usingTemplate && system.credentials?.client_secret && clientId) {
|
|
23667
|
+
clientCredentialsUid = import_node_crypto.default.randomUUID();
|
|
23668
|
+
try {
|
|
23669
|
+
await client.cacheOauthClientCredentials({
|
|
23670
|
+
clientCredentialsUid,
|
|
23671
|
+
clientId,
|
|
23672
|
+
clientSecret: system.credentials.client_secret
|
|
23673
|
+
});
|
|
23674
|
+
} catch (err) {
|
|
23675
|
+
error(`Failed to cache OAuth credentials: ${err.message}`);
|
|
23676
|
+
process.exit(1);
|
|
23677
|
+
}
|
|
23678
|
+
}
|
|
23452
23679
|
const redirectUri = `${config.webEndpoint.replace(/\/$/, "")}/api/auth/callback`;
|
|
23453
23680
|
const state = {
|
|
23454
23681
|
systemId: opts.systemId,
|
|
@@ -23458,6 +23685,7 @@ function registerOAuthCommand(parent, getContext2) {
|
|
|
23458
23685
|
token_url: tokenUrl,
|
|
23459
23686
|
clientId,
|
|
23460
23687
|
...templateMatch ? { templateId: templateMatch.key } : {},
|
|
23688
|
+
...clientCredentialsUid ? { client_credentials_uid: clientCredentialsUid } : {},
|
|
23461
23689
|
scopes: opts.scopes,
|
|
23462
23690
|
cliApiKey: encryptedApiKey
|
|
23463
23691
|
};
|
|
@@ -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
|
|