@superglue/cli 0.1.0 → 1.1.0

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/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # superglue CLI
2
+
3
+ [![npm](https://img.shields.io/npm/v/@superglue/cli?style=flat-square&logo=npm)](https://www.npmjs.com/package/@superglue/cli)
4
+ [![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](LICENSE)
5
+
6
+ Build, test, and manage superglue API integration tools from the command line.
7
+
8
+ ## What is superglue?
9
+
10
+ - AI-powered tool builder that works with any API, database or file storage server
11
+ - Abstracts away authentication, documentation handling and data mapping between systems
12
+ - Self-heals tools: when steps fail due to upstream API changes, superglue can auto-repair failures
13
+
14
+ ## Installation
15
+
16
+ ### As npm package
17
+
18
+ ```bash
19
+ npm install -g @superglue/cli
20
+ sg --help
21
+ ```
22
+
23
+ ### As Claude Code Plugin
24
+
25
+ ```
26
+ /plugin install github:superglue-ai/cli
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ```bash
32
+ sg init # guided setup
33
+ sg system list # verify connection
34
+ sg tool build --config '...' # build a tool
35
+ sg tool run --draft <id> # test it
36
+ sg tool save --draft <id> # save it
37
+ ```
38
+
39
+ ## Commands
40
+
41
+ ### Tools
42
+
43
+ - `sg tool build` — Build a tool from config
44
+ - `sg tool run` — Run a draft or saved tool
45
+ - `sg tool edit` — Edit via JSON Patch
46
+ - `sg tool save` — Save a draft
47
+ - `sg tool list` — List saved tools
48
+ - `sg tool find` — Search tools
49
+
50
+ ### Systems
51
+
52
+ - `sg system create` — Create a system
53
+ - `sg system edit` — Edit system config
54
+ - `sg system list` — List systems
55
+ - `sg system find` — Search systems
56
+ - `sg system call` — Make authenticated API calls
57
+ - `sg system oauth` — Authenticate via OAuth
58
+ - `sg system docs` — Search system documentation
59
+
60
+ ### Runs
61
+
62
+ - `sg run list` — List execution runs
63
+ - `sg run get` — Get run details
64
+
65
+ ## Documentation
66
+
67
+ - [Full CLI Reference](./skills/superglue/SKILL.md) — Complete command documentation
68
+ - [superglue Docs](https://docs.superglue.cloud) — Platform documentation
69
+ - [API Reference](https://docs.superglue.cloud/api-reference/) — REST API docs
70
+
71
+ ## Related
72
+
73
+ - [superglue Web App](https://app.superglue.cloud) — Visual tool builder
74
+ - [@superglue/client](https://www.npmjs.com/package/@superglue/client) — TypeScript SDK
75
+ - [superglue-client](https://pypi.org/project/superglue-client/) — Python SDK
76
+
77
+ ## License
78
+
79
+ MIT
package/dist/cli.js CHANGED
@@ -20962,23 +20962,32 @@ var require_cli_oauth = __commonJS({
20962
20962
  Object.defineProperty(exports2, "__esModule", { value: true });
20963
20963
  exports2.encryptCliApiKey = encryptCliApiKey2;
20964
20964
  exports2.decryptCliApiKey = decryptCliApiKey;
20965
- var crypto_1 = __importDefault(require("crypto"));
20966
- function encryptCliApiKey2(apiKey, systemId) {
20967
- const key = crypto_1.default.createHash("sha256").update(`cli-oauth-${systemId}`).digest();
20968
- const iv = crypto_1.default.randomBytes(16);
20969
- const cipher = crypto_1.default.createCipheriv("aes-256-cbc", key, iv);
20965
+ var node_crypto_1 = __importDefault(require("crypto"));
20966
+ function encryptCliApiKey2(apiKey, systemId, secret) {
20967
+ if (!secret) {
20968
+ throw new Error("Server secret required for CLI OAuth encryption");
20969
+ }
20970
+ const key = node_crypto_1.default.createHash("sha256").update(`${secret}:cli-oauth:${systemId}`).digest();
20971
+ const iv = node_crypto_1.default.randomBytes(12);
20972
+ const cipher = node_crypto_1.default.createCipheriv("aes-256-gcm", key, iv);
20970
20973
  let encrypted = cipher.update(apiKey, "utf8", "hex");
20971
20974
  encrypted += cipher.final("hex");
20972
- return `${iv.toString("hex")}:${encrypted}`;
20975
+ const authTag = cipher.getAuthTag().toString("hex");
20976
+ return `${iv.toString("hex")}:${encrypted}:${authTag}`;
20973
20977
  }
20974
- function decryptCliApiKey(encrypted, systemId) {
20978
+ function decryptCliApiKey(encrypted, systemId, secret) {
20979
+ if (!secret) {
20980
+ return null;
20981
+ }
20975
20982
  try {
20976
- const [ivHex, encryptedHex] = encrypted.split(":");
20977
- if (!ivHex || !encryptedHex)
20983
+ const [ivHex, encryptedHex, authTagHex] = encrypted.split(":");
20984
+ if (!ivHex || !encryptedHex || !authTagHex)
20978
20985
  return null;
20979
- const key = crypto_1.default.createHash("sha256").update(`cli-oauth-${systemId}`).digest();
20986
+ const key = node_crypto_1.default.createHash("sha256").update(`${secret}:cli-oauth:${systemId}`).digest();
20980
20987
  const iv = Buffer.from(ivHex, "hex");
20981
- const decipher = crypto_1.default.createDecipheriv("aes-256-cbc", key, iv);
20988
+ const authTag = Buffer.from(authTagHex, "hex");
20989
+ const decipher = node_crypto_1.default.createDecipheriv("aes-256-gcm", key, iv);
20990
+ decipher.setAuthTag(authTag);
20982
20991
  let decrypted = decipher.update(encryptedHex, "hex", "utf8");
20983
20992
  decrypted += decipher.final("utf8");
20984
20993
  return decrypted;
@@ -21454,6 +21463,14 @@ var require_superglue_client = __commonJS({
21454
21463
  const response = await this.restRequest("GET", `/v1/oauth/templates/${encodeURIComponent(params.templateId)}/credentials`);
21455
21464
  return response.data;
21456
21465
  }
21466
+ /**
21467
+ * Get the CLI OAuth encryption secret and orgId.
21468
+ * Used by CLI to encrypt API keys in OAuth state parameters.
21469
+ */
21470
+ async getCliOAuthSecret() {
21471
+ const response = await this.restRequest("GET", "/v1/oauth/cli-secret");
21472
+ return response.data;
21473
+ }
21457
21474
  async triggerSystemDocumentationScrapeJob(systemId, options) {
21458
21475
  const response = await this.restRequest("POST", `/v1/systems/${encodeURIComponent(systemId)}/documentation/scrape`, options);
21459
21476
  return response.data;
@@ -21571,6 +21588,7 @@ var import_commander = require("commander");
21571
21588
 
21572
21589
  // src/config.ts
21573
21590
  var fs = __toESM(require("fs"));
21591
+ var os = __toESM(require("os"));
21574
21592
  var path = __toESM(require("path"));
21575
21593
  var DEFAULT_CONFIG = {
21576
21594
  output: { mode: "stdout", directory: ".superglue/output" },
@@ -21579,16 +21597,26 @@ var DEFAULT_CONFIG = {
21579
21597
  editTool: "confirm"
21580
21598
  }
21581
21599
  };
21582
- function getConfigDir() {
21600
+ function getConfigDir(preferLocal) {
21583
21601
  const localDir = path.join(process.cwd(), ".superglue");
21602
+ const homeDir = os.homedir();
21603
+ if (!homeDir) {
21604
+ return localDir;
21605
+ }
21606
+ const globalDir = path.join(homeDir, ".superglue");
21607
+ if (preferLocal === true) {
21608
+ return localDir;
21609
+ }
21610
+ if (preferLocal === false) {
21611
+ return globalDir;
21612
+ }
21584
21613
  if (fs.existsSync(path.join(localDir, "config.json"))) {
21585
21614
  return localDir;
21586
21615
  }
21587
- const homeDir = process.env.HOME || process.env.USERPROFILE || "";
21588
- return path.join(homeDir, ".superglue");
21616
+ return globalDir;
21589
21617
  }
21590
- function getConfigPath() {
21591
- return path.join(getConfigDir(), "config.json");
21618
+ function getConfigPath(preferLocal) {
21619
+ return path.join(getConfigDir(preferLocal), "config.json");
21592
21620
  }
21593
21621
  function loadConfigFile() {
21594
21622
  const configPath = getConfigPath();
@@ -21614,13 +21642,15 @@ function resolveConfig(flags) {
21614
21642
  policies: { ...DEFAULT_CONFIG.policies, ...file.policies }
21615
21643
  };
21616
21644
  }
21617
- function writeConfig(config) {
21618
- const dir = getConfigDir();
21645
+ function writeConfig(config, preferLocal) {
21646
+ const dir = getConfigDir(preferLocal);
21619
21647
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
21620
- fs.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + "\n");
21648
+ fs.writeFileSync(path.join(dir, "config.json"), JSON.stringify(config, null, 2) + "\n", {
21649
+ mode: 384
21650
+ });
21621
21651
  }
21622
- function ensureConfigDirs(config) {
21623
- const draftsDir = path.join(getConfigDir(), "drafts");
21652
+ function ensureConfigDirs(config, preferLocal) {
21653
+ const draftsDir = path.join(getConfigDir(preferLocal), "drafts");
21624
21654
  if (!fs.existsSync(draftsDir)) fs.mkdirSync(draftsDir, { recursive: true });
21625
21655
  if (config.output.mode === "stdout+file") {
21626
21656
  const outDir = path.resolve(config.output.directory);
@@ -21644,10 +21674,130 @@ function createClient(config) {
21644
21674
  // src/commands/init.ts
21645
21675
  var fs2 = __toESM(require("fs"));
21646
21676
  var path2 = __toESM(require("path"));
21677
+ var os2 = __toESM(require("os"));
21647
21678
  var import_shared2 = __toESM(require_dist2());
21648
21679
 
21649
21680
  // src/output.ts
21650
21681
  var readline = __toESM(require("readline"));
21682
+
21683
+ // src/version.ts
21684
+ var import_node_child_process = require("child_process");
21685
+ var import_node_util = require("util");
21686
+
21687
+ // package.json
21688
+ var package_default = {
21689
+ name: "@superglue/cli",
21690
+ version: "1.1.0",
21691
+ bin: {
21692
+ sg: "./dist/cli.js"
21693
+ },
21694
+ files: [
21695
+ "dist/",
21696
+ "skills/",
21697
+ "skills.md"
21698
+ ],
21699
+ dependencies: {
21700
+ commander: "^13.0.0",
21701
+ "fast-json-patch": "^3.1.1"
21702
+ },
21703
+ devDependencies: {
21704
+ "@superglue/shared": "file:../shared",
21705
+ "@types/node": "^20.0.0",
21706
+ tsup: "^8.0.0",
21707
+ typescript: "^5.0.0"
21708
+ },
21709
+ scripts: {
21710
+ build: "tsup && (cp -r skills dist/ 2>/dev/null || true)",
21711
+ dev: "tsup --watch",
21712
+ "type-check": "tsc --noEmit",
21713
+ prepublishOnly: "npm run build"
21714
+ },
21715
+ publishConfig: {
21716
+ access: "public"
21717
+ }
21718
+ };
21719
+
21720
+ // src/version.ts
21721
+ var execAsync = (0, import_node_util.promisify)(import_node_child_process.exec);
21722
+ var CLI_VERSION = package_default.version;
21723
+ function parseVersionPart(part) {
21724
+ const match = part.match(/^(\d+)(?:-(.+))?$/);
21725
+ if (match) {
21726
+ return { numeric: parseInt(match[1], 10), prerelease: match[2] || null };
21727
+ }
21728
+ return { numeric: 0, prerelease: part };
21729
+ }
21730
+ function compareVersions(a, b) {
21731
+ const partsA = a.split(".");
21732
+ const partsB = b.split(".");
21733
+ const maxLen = Math.max(partsA.length, partsB.length);
21734
+ for (let i = 0; i < maxLen; i++) {
21735
+ const parsedA = parseVersionPart(partsA[i] || "0");
21736
+ const parsedB = parseVersionPart(partsB[i] || "0");
21737
+ if (parsedA.numeric < parsedB.numeric) return -1;
21738
+ if (parsedA.numeric > parsedB.numeric) return 1;
21739
+ if (parsedA.prerelease === null && parsedB.prerelease !== null) return 1;
21740
+ if (parsedA.prerelease !== null && parsedB.prerelease === null) return -1;
21741
+ if (parsedA.prerelease !== null && parsedB.prerelease !== null) {
21742
+ if (parsedA.prerelease < parsedB.prerelease) return -1;
21743
+ if (parsedA.prerelease > parsedB.prerelease) return 1;
21744
+ }
21745
+ }
21746
+ return 0;
21747
+ }
21748
+ async function checkVersionCompatibility(endpoint) {
21749
+ try {
21750
+ const healthUrl = `${endpoint.replace(/\/$/, "")}/v1/health`;
21751
+ const response = await fetch(healthUrl, { signal: AbortSignal.timeout(3e3) });
21752
+ if (!response.ok) return;
21753
+ const health = await response.json();
21754
+ if (!health.minCliVersion) return;
21755
+ if (compareVersions(CLI_VERSION, health.minCliVersion) < 0) {
21756
+ console.error("");
21757
+ console.error(`${c.yellow}${c.bold}\u26A0 CLI version ${CLI_VERSION} is outdated.${c.reset}`);
21758
+ console.error(
21759
+ `${c.yellow} Server requires at least v${health.minCliVersion}. Run: ${c.bold}sg update${c.reset}`
21760
+ );
21761
+ console.error("");
21762
+ }
21763
+ } catch {
21764
+ }
21765
+ }
21766
+ async function getLatestNpmVersion() {
21767
+ try {
21768
+ const response = await fetch("https://registry.npmjs.org/@superglue/cli/latest", {
21769
+ signal: AbortSignal.timeout(5e3)
21770
+ });
21771
+ if (!response.ok) return null;
21772
+ const data = await response.json();
21773
+ return data.version || null;
21774
+ } catch {
21775
+ return null;
21776
+ }
21777
+ }
21778
+ async function updateCli() {
21779
+ const latestVersion = await getLatestNpmVersion();
21780
+ if (!latestVersion) {
21781
+ return { success: false, message: "Could not fetch latest version from npm" };
21782
+ }
21783
+ if (compareVersions(CLI_VERSION, latestVersion) >= 0) {
21784
+ return { success: true, message: `Already on latest version (${CLI_VERSION})` };
21785
+ }
21786
+ try {
21787
+ await execAsync("npm install -g @superglue/cli@latest");
21788
+ return {
21789
+ success: true,
21790
+ message: `Updated from ${CLI_VERSION} to ${latestVersion}`
21791
+ };
21792
+ } catch (err) {
21793
+ return {
21794
+ success: false,
21795
+ message: `Update failed: ${err.message}. Try manually: npm install -g @superglue/cli@latest`
21796
+ };
21797
+ }
21798
+ }
21799
+
21800
+ // src/output.ts
21651
21801
  var isJsonMode = () => process.argv.includes("--json") || !process.stdout.isTTY;
21652
21802
  var c = {
21653
21803
  reset: "\x1B[0m",
@@ -21700,7 +21850,7 @@ function banner() {
21700
21850
  const after = line.slice(last + 1);
21701
21851
  console.log(` ${before}${inv}${inside}${rst}${after}`);
21702
21852
  });
21703
- console.log(` ${d} CLI v0.1.0${rst}`);
21853
+ console.log(` ${d} CLI v${CLI_VERSION}${rst}`);
21704
21854
  console.log("");
21705
21855
  }
21706
21856
  function spinner(message) {
@@ -21753,6 +21903,11 @@ function error(message, details) {
21753
21903
  if (details) prettyPrint(details, 4);
21754
21904
  }
21755
21905
  }
21906
+ function warn(message) {
21907
+ if (!isJsonMode()) {
21908
+ console.log(` ${c.yellow}\u26A0${c.reset} ${message}`);
21909
+ }
21910
+ }
21756
21911
  function info(message) {
21757
21912
  if (!isJsonMode()) {
21758
21913
  console.log(` ${c.dim}${message}${c.reset}`);
@@ -21945,22 +22100,25 @@ async function promptHidden(message) {
21945
22100
  let input = "";
21946
22101
  const onData = (ch) => {
21947
22102
  const s = ch.toString();
21948
- if (s === "\n" || s === "\r") {
21949
- if (process.stdin.isTTY) process.stdin.setRawMode(false);
21950
- process.stdin.removeListener("data", onData);
21951
- process.stdout.write("\n");
21952
- rl.close();
21953
- resolve4(input);
21954
- } else if (s === "") {
21955
- process.exit(0);
21956
- } else if (s === "\x7F" || s === "\b") {
21957
- if (input.length > 0) {
21958
- input = input.slice(0, -1);
21959
- process.stdout.write("\b \b");
22103
+ for (const char of s) {
22104
+ if (char === "\n" || char === "\r") {
22105
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
22106
+ process.stdin.removeListener("data", onData);
22107
+ process.stdout.write("\n");
22108
+ rl.close();
22109
+ resolve4(input);
22110
+ return;
22111
+ } else if (char === "") {
22112
+ process.exit(130);
22113
+ } else if (char === "\x7F" || char === "\b") {
22114
+ if (input.length > 0) {
22115
+ input = input.slice(0, -1);
22116
+ process.stdout.write("\b \b");
22117
+ }
22118
+ } else {
22119
+ input += char;
22120
+ process.stdout.write(`${c.dim}\u2022${c.reset}`);
21960
22121
  }
21961
- } else {
21962
- input += s;
21963
- process.stdout.write(`${c.dim}\u2022${c.reset}`);
21964
22122
  }
21965
22123
  };
21966
22124
  process.stdin.on("data", onData);
@@ -22039,6 +22197,19 @@ function registerInitCommand(program2) {
22039
22197
  0
22040
22198
  );
22041
22199
  const editPolicies = ["confirm", "auto_accept"];
22200
+ heading("Config Location");
22201
+ const homeDir = os2.homedir();
22202
+ const localPath = path2.join(process.cwd(), ".superglue");
22203
+ const globalPath = path2.join(homeDir, ".superglue");
22204
+ const configLocationIdx = await choose(
22205
+ "Where to save config?",
22206
+ [
22207
+ `Project (${localPath}) \u2014 config stays with this project`,
22208
+ `Global (${globalPath}) \u2014 shared across all projects`
22209
+ ],
22210
+ 0
22211
+ );
22212
+ const preferLocal = configLocationIdx === 0;
22042
22213
  const config = {
22043
22214
  apiKey,
22044
22215
  endpoint,
@@ -22049,35 +22220,39 @@ function registerInitCommand(program2) {
22049
22220
  editTool: editPolicies[editPolicyIdx]
22050
22221
  }
22051
22222
  };
22052
- writeConfig(config);
22053
- ensureConfigDirs(config);
22223
+ writeConfig(config, preferLocal);
22224
+ ensureConfigDirs(config, preferLocal);
22225
+ const configDir = getConfigDir(preferLocal);
22226
+ const configRelPath = preferLocal ? ".superglue/config.json" : path2.join(configDir, "config.json");
22054
22227
  const gitignorePath = path2.join(process.cwd(), ".gitignore");
22055
- const ignoreEntries = [".superglue/drafts/", `${outputDir}/`, ".superglue/config.json"];
22056
- if (fs2.existsSync(gitignorePath)) {
22057
- const existing = fs2.readFileSync(gitignorePath, "utf-8");
22058
- const toAdd = ignoreEntries.filter((e) => !existing.includes(e));
22059
- if (toAdd.length > 0) {
22060
- fs2.appendFileSync(gitignorePath, "\n# superglue CLI\n" + toAdd.join("\n") + "\n");
22061
- info("Updated .gitignore");
22228
+ const ignoreEntries = [];
22229
+ if (outputMode === "stdout+file" && !path2.isAbsolute(outputDir)) {
22230
+ ignoreEntries.push(`${outputDir}/`);
22231
+ }
22232
+ if (preferLocal) {
22233
+ ignoreEntries.push(".superglue/drafts/", ".superglue/config.json");
22234
+ }
22235
+ if (ignoreEntries.length > 0) {
22236
+ if (fs2.existsSync(gitignorePath)) {
22237
+ const existing = fs2.readFileSync(gitignorePath, "utf-8");
22238
+ const toAdd = ignoreEntries.filter((e) => !existing.includes(e));
22239
+ if (toAdd.length > 0) {
22240
+ fs2.appendFileSync(gitignorePath, "\n# superglue CLI\n" + toAdd.join("\n") + "\n");
22241
+ info("Updated .gitignore");
22242
+ }
22243
+ } else if (preferLocal) {
22244
+ fs2.writeFileSync(gitignorePath, "# superglue CLI\n" + ignoreEntries.join("\n") + "\n");
22245
+ info("Created .gitignore");
22062
22246
  }
22063
- } else {
22064
- fs2.writeFileSync(gitignorePath, "# superglue CLI\n" + ignoreEntries.join("\n") + "\n");
22065
- info("Created .gitignore");
22066
22247
  }
22067
22248
  console.log("");
22068
- console.log(` ${c.dim}\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510${c.reset}`);
22069
- console.log(
22070
- ` ${c.dim}\u2502${c.reset} ${c.green}\u2713${c.reset} Config saved to ${c.bold}.superglue/config.json${c.reset} ${c.dim}\u2502${c.reset}`
22071
- );
22072
- console.log(
22073
- ` ${c.dim}\u2502${c.reset} ${c.green}\u2713${c.reset} Created ${c.bold}.superglue/drafts/${c.reset} ${c.dim}\u2502${c.reset}`
22074
- );
22249
+ success(`Config saved to ${c.bold}${configRelPath}${c.reset}`);
22250
+ if (preferLocal) {
22251
+ success(`Created ${c.bold}.superglue/drafts/${c.reset}`);
22252
+ }
22075
22253
  if (outputMode === "stdout+file") {
22076
- console.log(
22077
- ` ${c.dim}\u2502${c.reset} ${c.green}\u2713${c.reset} Created ${c.bold}${outputDir}/${c.reset}${"".padEnd(Math.max(0, 27 - outputDir.length))}${c.dim}\u2502${c.reset}`
22078
- );
22254
+ success(`Created ${c.bold}${outputDir}/${c.reset}`);
22079
22255
  }
22080
- console.log(` ${c.dim}\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518${c.reset}`);
22081
22256
  console.log("");
22082
22257
  console.log(` ${c.bold}You're all set!${c.reset} Try these commands:
22083
22258
  `);
@@ -22105,9 +22280,11 @@ var path3 = __toESM(require("path"));
22105
22280
  function getDraftsDir() {
22106
22281
  return path3.join(getConfigDir(), "drafts");
22107
22282
  }
22283
+ function encodeDraftId(draftId) {
22284
+ return Buffer.from(draftId, "utf-8").toString("base64url");
22285
+ }
22108
22286
  function getDraftPath(draftId) {
22109
- const safeName = draftId.replace(/[^a-zA-Z0-9_-]/g, "_");
22110
- return path3.join(getDraftsDir(), `${safeName}.json`);
22287
+ return path3.join(getDraftsDir(), `${encodeDraftId(draftId)}.json`);
22111
22288
  }
22112
22289
  function writeDraft(draft) {
22113
22290
  const dir = getDraftsDir();
@@ -22425,11 +22602,11 @@ function registerEditCommand(parent, getContext2) {
22425
22602
  draftId: workingDraftId,
22426
22603
  config: {
22427
22604
  ...patchedConfig,
22428
- instruction: originalConfig.instruction || patchedConfig.instruction,
22605
+ instruction: patchedConfig.instruction ?? originalConfig.instruction,
22429
22606
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
22430
22607
  },
22431
22608
  systemIds: patchedConfig.systemIds || [],
22432
- instruction: patchedConfig.instruction || "",
22609
+ instruction: patchedConfig.instruction ?? "",
22433
22610
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
22434
22611
  };
22435
22612
  writeDraft(draft);
@@ -22495,8 +22672,8 @@ function registerSaveCommand(parent, getContext2) {
22495
22672
  process.exit(1);
22496
22673
  }
22497
22674
  const toolId = opts.id || draft.config.id;
22675
+ const spin = spinner(`Saving tool ${c.bold}${toolId}${c.reset}...`);
22498
22676
  try {
22499
- const spin = spinner(`Saving tool ${c.bold}${toolId}${c.reset}...`);
22500
22677
  const saved = await client.upsertWorkflow(toolId, {
22501
22678
  ...draft.config,
22502
22679
  id: toolId,
@@ -22518,6 +22695,7 @@ function registerSaveCommand(parent, getContext2) {
22518
22695
  console.log("");
22519
22696
  }
22520
22697
  } catch (err) {
22698
+ spin.stop();
22521
22699
  error(err.message);
22522
22700
  process.exit(1);
22523
22701
  }
@@ -22556,7 +22734,7 @@ function registerFindCommand(parent, getContext2) {
22556
22734
  output({ success: true, tool });
22557
22735
  return;
22558
22736
  }
22559
- const searchQuery = query || "*";
22737
+ const searchQuery = (query || "").trim() || "*";
22560
22738
  if (searchQuery === "*") {
22561
22739
  const { items } = await client.listWorkflows(100);
22562
22740
  const tools = items.filter((t) => !t.archived).map((t) => ({
@@ -22657,10 +22835,10 @@ function registerCreateCommand(parent, getContext2) {
22657
22835
  if (template.oauth.client_id) oauthCreds.client_id = template.oauth.client_id;
22658
22836
  }
22659
22837
  systemInput = {
22838
+ ...systemInput,
22660
22839
  name: systemInput.name || template.name,
22661
- url: template.apiUrl,
22840
+ url: systemInput.url || template.apiUrl,
22662
22841
  templateName: opts.template,
22663
- ...systemInput,
22664
22842
  credentials: { ...oauthCreds, ...systemInput.credentials }
22665
22843
  };
22666
22844
  }
@@ -22759,7 +22937,9 @@ function registerEditCommand2(parent, getContext2) {
22759
22937
  url: opts.scrapeUrl,
22760
22938
  keywords
22761
22939
  });
22762
- } catch {
22940
+ } catch (scrapeErr) {
22941
+ const errMsg = scrapeErr instanceof Error ? scrapeErr.message : String(scrapeErr || "Unknown error");
22942
+ warn(`Documentation scrape failed for ${opts.scrapeUrl}: ${errMsg}`);
22763
22943
  }
22764
22944
  }
22765
22945
  spin.stop();
@@ -22875,14 +23055,29 @@ function registerCallCommand(parent, getContext2) {
22875
23055
  if (body && Object.keys(filePayloads).length > 0) {
22876
23056
  try {
22877
23057
  let parsed = JSON.parse(body);
22878
- for (const [k, v] of Object.entries(parsed)) {
22879
- if (typeof v === "string" && v.startsWith("file::")) {
22880
- const key = v.slice(6);
22881
- if (key in filePayloads) parsed[k] = filePayloads[key];
23058
+ const resolveFileRefs = (obj) => {
23059
+ if (typeof obj === "string" && obj.startsWith("file::")) {
23060
+ const key = obj.slice(6);
23061
+ if (key in filePayloads) return filePayloads[key];
23062
+ error(
23063
+ `Unresolved file reference: file::${key}. Available: ${Object.keys(filePayloads).join(", ") || "(none)"}`
23064
+ );
23065
+ process.exit(1);
22882
23066
  }
22883
- }
23067
+ if (Array.isArray(obj)) return obj.map(resolveFileRefs);
23068
+ if (obj && typeof obj === "object") {
23069
+ const result = {};
23070
+ for (const [k, v] of Object.entries(obj)) {
23071
+ result[k] = resolveFileRefs(v);
23072
+ }
23073
+ return result;
23074
+ }
23075
+ return obj;
23076
+ };
23077
+ parsed = resolveFileRefs(parsed);
22884
23078
  body = JSON.stringify(parsed);
22885
- } catch {
23079
+ } catch (err) {
23080
+ if (err.message?.startsWith("Unresolved file reference")) throw err;
22886
23081
  }
22887
23082
  }
22888
23083
  const autoExec = process.argv.includes("--yes") || process.argv.includes("-y") || shouldAutoExecute(config.policies.callSystem, method, opts.url);
@@ -22995,13 +23190,13 @@ function registerDocsCommand(parent, getContext2) {
22995
23190
  }
22996
23191
 
22997
23192
  // src/commands/system/oauth.ts
22998
- var import_node_child_process = require("child_process");
23193
+ var import_node_child_process2 = require("child_process");
22999
23194
  var import_shared8 = __toESM(require_dist2());
23000
23195
  function openBrowser(url) {
23001
23196
  const platform = process.platform;
23002
23197
  const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
23003
23198
  const args = platform === "win32" ? ["/c", "start", "", url] : [url];
23004
- (0, import_node_child_process.spawn)(cmd, args, { detached: true, stdio: "ignore" }).unref();
23199
+ (0, import_node_child_process2.spawn)(cmd, args, { detached: true, stdio: "ignore" }).unref();
23005
23200
  }
23006
23201
  async function pollForTokens(client, systemId, originalToken, timeoutMs = 3e5, intervalMs = 2e3) {
23007
23202
  const start = Date.now();
@@ -23100,10 +23295,21 @@ function registerOAuthCommand(parent, getContext2) {
23100
23295
  process.exit(1);
23101
23296
  }
23102
23297
  const originalToken = system.credentials?.access_token;
23103
- const encryptedApiKey = (0, import_shared8.encryptCliApiKey)(config.apiKey, opts.systemId);
23298
+ let encryptionSecret;
23299
+ let orgId;
23300
+ try {
23301
+ const secretData = await client.getCliOAuthSecret();
23302
+ encryptionSecret = secretData.secret;
23303
+ orgId = secretData.orgId;
23304
+ } catch (err) {
23305
+ error(`Failed to get OAuth encryption secret: ${err.message}`);
23306
+ process.exit(1);
23307
+ }
23308
+ const encryptedApiKey = (0, import_shared8.encryptCliApiKey)(config.apiKey, opts.systemId, encryptionSecret);
23104
23309
  const redirectUri = `${config.webEndpoint.replace(/\/$/, "")}/api/auth/callback`;
23105
23310
  const state = {
23106
23311
  systemId: opts.systemId,
23312
+ orgId,
23107
23313
  timestamp: Date.now(),
23108
23314
  redirectUri,
23109
23315
  token_url: tokenUrl,
@@ -23150,12 +23356,12 @@ function registerSystemCommands(program2, getContext2) {
23150
23356
 
23151
23357
  // src/commands/run/list.ts
23152
23358
  function registerListCommand(parent, getContext2) {
23153
- parent.command("list").description("List recent runs").option("--tool <id>", "Filter by tool ID").option("--status <status>", "Filter by status (running, success, failed, aborted)").option("--source <sources>", "Comma-separated request sources").option("--user <userId>", "Filter by user ID").option("--system-id <id>", "Filter by system ID").option("--limit <n>", "Max results", "10").option("--offset <n>", "Skip first N results", "0").option("--search <text>", "Full-text search").action(async (opts) => {
23359
+ parent.command("list").description("List recent runs").option("--tool <id>", "Filter by tool ID").option("--status <status>", "Filter by status (running, success, failed, aborted)").option("--source <sources>", "Comma-separated request sources").option("--user <userId>", "Filter by user ID").option("--system-id <id>", "Filter by system ID").option("--limit <n>", "Max results", "10").option("--offset <n>", "Skip first N results", "0").action(async (opts) => {
23154
23360
  const { client } = getContext2();
23155
23361
  try {
23156
23362
  const limit = Math.min(parseInt(opts.limit, 10) || 10, 50);
23157
23363
  const offset = parseInt(opts.offset, 10) || 0;
23158
- const page = Math.floor(offset / limit) + 1;
23364
+ const page = Math.ceil((offset + 1) / limit);
23159
23365
  const requestSources = opts.source?.split(",").filter(Boolean);
23160
23366
  const result = await client.listRuns({
23161
23367
  toolId: opts.tool,
@@ -23238,74 +23444,6 @@ function registerRunCommands(program2, getContext2) {
23238
23444
  registerGetCommand(run, getContext2);
23239
23445
  }
23240
23446
 
23241
- // src/version.ts
23242
- var import_node_child_process2 = require("child_process");
23243
- var import_node_util = require("util");
23244
- var execAsync = (0, import_node_util.promisify)(import_node_child_process2.exec);
23245
- var CLI_VERSION = "0.1.0";
23246
- function compareVersions(a, b) {
23247
- const partsA = a.split(".").map(Number);
23248
- const partsB = b.split(".").map(Number);
23249
- for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
23250
- const numA = partsA[i] || 0;
23251
- const numB = partsB[i] || 0;
23252
- if (numA < numB) return -1;
23253
- if (numA > numB) return 1;
23254
- }
23255
- return 0;
23256
- }
23257
- async function checkVersionCompatibility(endpoint) {
23258
- try {
23259
- const healthUrl = `${endpoint.replace(/\/$/, "")}/v1/health`;
23260
- const response = await fetch(healthUrl, { signal: AbortSignal.timeout(3e3) });
23261
- if (!response.ok) return;
23262
- const health = await response.json();
23263
- if (!health.minCliVersion) return;
23264
- if (compareVersions(CLI_VERSION, health.minCliVersion) < 0) {
23265
- console.error("");
23266
- console.error(`${c.yellow}${c.bold}\u26A0 CLI version ${CLI_VERSION} is outdated.${c.reset}`);
23267
- console.error(
23268
- `${c.yellow} Server requires at least v${health.minCliVersion}. Run: ${c.bold}sg update${c.reset}`
23269
- );
23270
- console.error("");
23271
- }
23272
- } catch {
23273
- }
23274
- }
23275
- async function getLatestNpmVersion() {
23276
- try {
23277
- const response = await fetch("https://registry.npmjs.org/@superglue/cli/latest", {
23278
- signal: AbortSignal.timeout(5e3)
23279
- });
23280
- if (!response.ok) return null;
23281
- const data = await response.json();
23282
- return data.version || null;
23283
- } catch {
23284
- return null;
23285
- }
23286
- }
23287
- async function updateCli() {
23288
- const latestVersion = await getLatestNpmVersion();
23289
- if (!latestVersion) {
23290
- return { success: false, message: "Could not fetch latest version from npm" };
23291
- }
23292
- if (compareVersions(CLI_VERSION, latestVersion) >= 0) {
23293
- return { success: true, message: `Already on latest version (${CLI_VERSION})` };
23294
- }
23295
- try {
23296
- await execAsync("npm install -g @superglue/cli@latest");
23297
- return {
23298
- success: true,
23299
- message: `Updated from ${CLI_VERSION} to ${latestVersion}`
23300
- };
23301
- } catch (err) {
23302
- return {
23303
- success: false,
23304
- message: `Update failed: ${err.message}. Try manually: npm install -g @superglue/cli@latest`
23305
- };
23306
- }
23307
- }
23308
-
23309
23447
  // src/commands/update.ts
23310
23448
  function registerUpdateCommand(program2) {
23311
23449
  program2.command("update").description("Update the superglue CLI to the latest version").option("--check", "Only check for updates, don't install").action(async (opts) => {
@@ -23317,7 +23455,8 @@ function registerUpdateCommand(program2) {
23317
23455
  error("Could not fetch latest version from npm");
23318
23456
  process.exit(1);
23319
23457
  }
23320
- if (latestVersion === CLI_VERSION) {
23458
+ const comparison = compareVersions(CLI_VERSION, latestVersion);
23459
+ if (comparison >= 0) {
23321
23460
  success(`You're on the latest version (${CLI_VERSION})`);
23322
23461
  } else {
23323
23462
  console.log("");
@@ -23395,10 +23534,41 @@ registerToolCommands(program, getContext);
23395
23534
  registerSystemCommands(program, getContext);
23396
23535
  registerRunCommands(program, getContext);
23397
23536
  var commandsRequiringServer = ["tool", "system", "run"];
23398
- var firstArg = process.argv[2];
23399
- if (firstArg && commandsRequiringServer.includes(firstArg)) {
23400
- const opts = program.opts();
23401
- const config = resolveConfig({ apiKey: opts.apiKey, endpoint: opts.endpoint });
23537
+ var globalFlagsWithValues = ["--api-key", "--endpoint"];
23538
+ var globalFlagsNoValue = ["--json", "-y", "--yes", "-h", "--help", "-V", "--version"];
23539
+ function findSubcommand(argv) {
23540
+ let i = 2;
23541
+ while (i < argv.length) {
23542
+ const arg = argv[i];
23543
+ if (globalFlagsWithValues.includes(arg)) {
23544
+ i += 2;
23545
+ } else if (globalFlagsNoValue.includes(arg)) {
23546
+ i += 1;
23547
+ } else if (arg.startsWith("-")) {
23548
+ i += 1;
23549
+ } else {
23550
+ return arg;
23551
+ }
23552
+ }
23553
+ return void 0;
23554
+ }
23555
+ function extractFlagValue(argv, flag) {
23556
+ for (let i = 0; i < argv.length; i++) {
23557
+ const arg = argv[i];
23558
+ if (arg.startsWith(`${flag}=`)) {
23559
+ return arg.slice(flag.length + 1);
23560
+ }
23561
+ if (arg === flag && i + 1 < argv.length) {
23562
+ return argv[i + 1];
23563
+ }
23564
+ }
23565
+ return void 0;
23566
+ }
23567
+ var subcommand = findSubcommand(process.argv);
23568
+ if (subcommand && commandsRequiringServer.includes(subcommand)) {
23569
+ const cliApiKey = extractFlagValue(process.argv, "--api-key");
23570
+ const cliEndpoint = extractFlagValue(process.argv, "--endpoint");
23571
+ const config = resolveConfig({ apiKey: cliApiKey, endpoint: cliEndpoint });
23402
23572
  if (config.apiKey) {
23403
23573
  checkVersionCompatibility(config.endpoint).then(() => program.parse());
23404
23574
  } else {
@@ -227,14 +227,14 @@ sg system oauth --system-id my_api --grant-type client_credentials --scopes "rea
227
227
  {
228
228
  "id": "unique-step-id",
229
229
  "config": {
230
- "systemId": "system-id",
231
- "url": "https://api.example.com/endpoint?param=<<payload.field>>",
230
+ "systemId": "example_system",
231
+ "url": "https://api.example.com/endpoint?param=<<payload_field>>",
232
232
  "method": "GET",
233
233
  "headers": {
234
- "Authorization": "Bearer <<system-id_access_token>>"
234
+ "Authorization": "Bearer <<example_system_access_token>>"
235
235
  },
236
236
  "queryParams": { "limit": "10" },
237
- "body": "{ \"key\": \"<<payload.field>>\" }"
237
+ "body": "{ \"key\": \"<<payload_field>>\" }"
238
238
  },
239
239
  "dataSelector": "(sourceData) => sourceData.previousStep.data.items",
240
240
  "transformCode": "(items) => items.map(i => ({ id: i.id, name: i.name }))",
@@ -620,6 +620,7 @@ const { data } = await runTool("my-tool-id", { inputs: { userId: "123" } });
620
620
  ### Python SDK
621
621
 
622
622
  ```python
623
+ import os
623
624
  from superglue_client import SuperglueClient
624
625
  from superglue_client.api.tools import run_tool
625
626
  client = SuperglueClient(base_url="https://api.superglue.cloud/v1", token=os.environ["SUPERGLUE_API_KEY"])
@@ -75,7 +75,7 @@ Downloaded files are all parsed automatically:
75
75
 
76
76
  - String → written as-is
77
77
  - Buffer → written directly
78
- - Object/Array → JSON.stringified with 2-space indent
78
+ - Object/Array → `JSON.stringify` with 2-space indent
79
79
 
80
80
  ## Connection Details
81
81
 
@@ -7,36 +7,36 @@ After building and saving tools with the CLI, integrate them into your applicati
7
7
  For any language, use the REST API directly:
8
8
 
9
9
  ```bash
10
- # Set your API key
10
+ # Set your API key and endpoint
11
11
  export SUPERGLUE_API_KEY="your-api-key"
12
- export SUPERGLUE_ENDPOINT="https://api.superglue.cloud"
12
+ export SUPERGLUE_API_ENDPOINT="https://api.superglue.cloud"
13
13
 
14
14
  # Run a tool (sync - waits for completion)
15
- curl -X POST "$SUPERGLUE_ENDPOINT/v1/tools/my-tool-id/run" \
15
+ curl -X POST "$SUPERGLUE_API_ENDPOINT/v1/tools/my-tool-id/run" \
16
16
  -H "Authorization: Bearer $SUPERGLUE_API_KEY" \
17
17
  -H "Content-Type: application/json" \
18
18
  -d '{"inputs": {"userId": "123"}, "options": {"async": false}}'
19
19
 
20
20
  # Run a tool (async - returns immediately)
21
- curl -X POST "$SUPERGLUE_ENDPOINT/v1/tools/my-tool-id/run" \
21
+ curl -X POST "$SUPERGLUE_API_ENDPOINT/v1/tools/my-tool-id/run" \
22
22
  -H "Authorization: Bearer $SUPERGLUE_API_KEY" \
23
23
  -H "Content-Type: application/json" \
24
24
  -d '{"inputs": {"userId": "123"}, "options": {"async": true}}'
25
25
 
26
26
  # Get run status/result
27
- curl "$SUPERGLUE_ENDPOINT/v1/runs/{runId}" \
27
+ curl "$SUPERGLUE_API_ENDPOINT/v1/runs/{runId}" \
28
28
  -H "Authorization: Bearer $SUPERGLUE_API_KEY"
29
29
 
30
30
  # List tools
31
- curl "$SUPERGLUE_ENDPOINT/v1/tools?page=1&limit=50" \
31
+ curl "$SUPERGLUE_API_ENDPOINT/v1/tools?page=1&limit=50" \
32
32
  -H "Authorization: Bearer $SUPERGLUE_API_KEY"
33
33
 
34
34
  # Get tool details
35
- curl "$SUPERGLUE_ENDPOINT/v1/tools/my-tool-id" \
35
+ curl "$SUPERGLUE_API_ENDPOINT/v1/tools/my-tool-id" \
36
36
  -H "Authorization: Bearer $SUPERGLUE_API_KEY"
37
37
 
38
38
  # Cancel a running tool
39
- curl -X POST "$SUPERGLUE_ENDPOINT/v1/runs/{runId}/cancel" \
39
+ curl -X POST "$SUPERGLUE_API_ENDPOINT/v1/runs/{runId}/cancel" \
40
40
  -H "Authorization: Bearer $SUPERGLUE_API_KEY"
41
41
  ```
42
42
 
@@ -144,6 +144,7 @@ const { data: systems } = await listSystems();
144
144
  ## Python Usage
145
145
 
146
146
  ```python
147
+ import os
147
148
  from superglue_client import SuperglueClient
148
149
  from superglue_client.api.tools import run_tool, list_tools, get_tool
149
150
  from superglue_client.api.runs import get_run
@@ -261,7 +262,7 @@ async function runWithFallback(inputs: Record<string, any>) {
261
262
  SUPERGLUE_API_KEY=your-api-key
262
263
 
263
264
  # Optional (defaults to hosted)
264
- SUPERGLUE_API_ENDPOINT=https://api.superglue.cloud/v1
265
+ SUPERGLUE_API_ENDPOINT=https://api.superglue.cloud
265
266
  ```
266
267
 
267
268
  ## Best Practices
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superglue/cli",
3
- "version": "0.1.0",
3
+ "version": "1.1.0",
4
4
  "bin": {
5
5
  "sg": "./dist/cli.js"
6
6
  },
@@ -20,7 +20,7 @@
20
20
  "typescript": "^5.0.0"
21
21
  },
22
22
  "scripts": {
23
- "build": "tsup && cp -r skills dist/ 2>/dev/null || true",
23
+ "build": "tsup && (cp -r skills dist/ 2>/dev/null || true)",
24
24
  "dev": "tsup --watch",
25
25
  "type-check": "tsc --noEmit",
26
26
  "prepublishOnly": "npm run build"
@@ -227,14 +227,14 @@ sg system oauth --system-id my_api --grant-type client_credentials --scopes "rea
227
227
  {
228
228
  "id": "unique-step-id",
229
229
  "config": {
230
- "systemId": "system-id",
231
- "url": "https://api.example.com/endpoint?param=<<payload.field>>",
230
+ "systemId": "example_system",
231
+ "url": "https://api.example.com/endpoint?param=<<payload_field>>",
232
232
  "method": "GET",
233
233
  "headers": {
234
- "Authorization": "Bearer <<system-id_access_token>>"
234
+ "Authorization": "Bearer <<example_system_access_token>>"
235
235
  },
236
236
  "queryParams": { "limit": "10" },
237
- "body": "{ \"key\": \"<<payload.field>>\" }"
237
+ "body": "{ \"key\": \"<<payload_field>>\" }"
238
238
  },
239
239
  "dataSelector": "(sourceData) => sourceData.previousStep.data.items",
240
240
  "transformCode": "(items) => items.map(i => ({ id: i.id, name: i.name }))",
@@ -620,6 +620,7 @@ const { data } = await runTool("my-tool-id", { inputs: { userId: "123" } });
620
620
  ### Python SDK
621
621
 
622
622
  ```python
623
+ import os
623
624
  from superglue_client import SuperglueClient
624
625
  from superglue_client.api.tools import run_tool
625
626
  client = SuperglueClient(base_url="https://api.superglue.cloud/v1", token=os.environ["SUPERGLUE_API_KEY"])
@@ -75,7 +75,7 @@ Downloaded files are all parsed automatically:
75
75
 
76
76
  - String → written as-is
77
77
  - Buffer → written directly
78
- - Object/Array → JSON.stringified with 2-space indent
78
+ - Object/Array → `JSON.stringify` with 2-space indent
79
79
 
80
80
  ## Connection Details
81
81
 
@@ -7,36 +7,36 @@ After building and saving tools with the CLI, integrate them into your applicati
7
7
  For any language, use the REST API directly:
8
8
 
9
9
  ```bash
10
- # Set your API key
10
+ # Set your API key and endpoint
11
11
  export SUPERGLUE_API_KEY="your-api-key"
12
- export SUPERGLUE_ENDPOINT="https://api.superglue.cloud"
12
+ export SUPERGLUE_API_ENDPOINT="https://api.superglue.cloud"
13
13
 
14
14
  # Run a tool (sync - waits for completion)
15
- curl -X POST "$SUPERGLUE_ENDPOINT/v1/tools/my-tool-id/run" \
15
+ curl -X POST "$SUPERGLUE_API_ENDPOINT/v1/tools/my-tool-id/run" \
16
16
  -H "Authorization: Bearer $SUPERGLUE_API_KEY" \
17
17
  -H "Content-Type: application/json" \
18
18
  -d '{"inputs": {"userId": "123"}, "options": {"async": false}}'
19
19
 
20
20
  # Run a tool (async - returns immediately)
21
- curl -X POST "$SUPERGLUE_ENDPOINT/v1/tools/my-tool-id/run" \
21
+ curl -X POST "$SUPERGLUE_API_ENDPOINT/v1/tools/my-tool-id/run" \
22
22
  -H "Authorization: Bearer $SUPERGLUE_API_KEY" \
23
23
  -H "Content-Type: application/json" \
24
24
  -d '{"inputs": {"userId": "123"}, "options": {"async": true}}'
25
25
 
26
26
  # Get run status/result
27
- curl "$SUPERGLUE_ENDPOINT/v1/runs/{runId}" \
27
+ curl "$SUPERGLUE_API_ENDPOINT/v1/runs/{runId}" \
28
28
  -H "Authorization: Bearer $SUPERGLUE_API_KEY"
29
29
 
30
30
  # List tools
31
- curl "$SUPERGLUE_ENDPOINT/v1/tools?page=1&limit=50" \
31
+ curl "$SUPERGLUE_API_ENDPOINT/v1/tools?page=1&limit=50" \
32
32
  -H "Authorization: Bearer $SUPERGLUE_API_KEY"
33
33
 
34
34
  # Get tool details
35
- curl "$SUPERGLUE_ENDPOINT/v1/tools/my-tool-id" \
35
+ curl "$SUPERGLUE_API_ENDPOINT/v1/tools/my-tool-id" \
36
36
  -H "Authorization: Bearer $SUPERGLUE_API_KEY"
37
37
 
38
38
  # Cancel a running tool
39
- curl -X POST "$SUPERGLUE_ENDPOINT/v1/runs/{runId}/cancel" \
39
+ curl -X POST "$SUPERGLUE_API_ENDPOINT/v1/runs/{runId}/cancel" \
40
40
  -H "Authorization: Bearer $SUPERGLUE_API_KEY"
41
41
  ```
42
42
 
@@ -144,6 +144,7 @@ const { data: systems } = await listSystems();
144
144
  ## Python Usage
145
145
 
146
146
  ```python
147
+ import os
147
148
  from superglue_client import SuperglueClient
148
149
  from superglue_client.api.tools import run_tool, list_tools, get_tool
149
150
  from superglue_client.api.runs import get_run
@@ -261,7 +262,7 @@ async function runWithFallback(inputs: Record<string, any>) {
261
262
  SUPERGLUE_API_KEY=your-api-key
262
263
 
263
264
  # Optional (defaults to hosted)
264
- SUPERGLUE_API_ENDPOINT=https://api.superglue.cloud/v1
265
+ SUPERGLUE_API_ENDPOINT=https://api.superglue.cloud
265
266
  ```
266
267
 
267
268
  ## Best Practices