@hasna/connectors 0.2.5 → 0.2.6

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 CHANGED
@@ -34,6 +34,8 @@ npx @hasna/connectors
34
34
 
35
35
  ```bash
36
36
  connectors install figma stripe github # Install connectors
37
+ connectors install --preset ai # Install a preset bundle
38
+ connectors install --category "AI & ML" # Install entire category
37
39
  connectors install figma --overwrite # Overwrite existing
38
40
  connectors remove figma # Remove a connector
39
41
  ```
@@ -49,6 +51,10 @@ connectors info stripe # Connector details
49
51
  connectors docs gmail # Auth, env vars, CLI docs
50
52
  connectors status # Auth status of installed connectors
51
53
  connectors doctor # Health check and troubleshooting
54
+ connectors test # Verify API keys with real requests
55
+ connectors whoami # Show setup summary
56
+ connectors env # Generate .env.example
57
+ connectors presets # List preset bundles
52
58
  ```
53
59
 
54
60
  ### Dashboard
package/bin/index.js CHANGED
@@ -6499,12 +6499,91 @@ init_auth();
6499
6499
  import { readdirSync as readdirSync4, existsSync as existsSync5, statSync as statSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
6500
6500
  import { homedir as homedir3 } from "os";
6501
6501
  import { join as join5, relative } from "path";
6502
+
6503
+ // src/lib/test-endpoints.ts
6504
+ var TEST_ENDPOINTS = {
6505
+ anthropic: {
6506
+ url: "https://api.anthropic.com/v1/models",
6507
+ headers: (key) => ({ "x-api-key": key, "anthropic-version": "2023-06-01" })
6508
+ },
6509
+ openai: {
6510
+ url: "https://api.openai.com/v1/models",
6511
+ headers: (key) => ({ Authorization: `Bearer ${key}` })
6512
+ },
6513
+ xai: {
6514
+ url: "https://api.x.ai/v1/models",
6515
+ headers: (key) => ({ Authorization: `Bearer ${key}` })
6516
+ },
6517
+ mistral: {
6518
+ url: "https://api.mistral.ai/v1/models",
6519
+ headers: (key) => ({ Authorization: `Bearer ${key}` })
6520
+ },
6521
+ github: {
6522
+ url: "https://api.github.com/user",
6523
+ headers: (key) => ({ Authorization: `Bearer ${key}`, "User-Agent": "connectors-cli" })
6524
+ },
6525
+ stripe: {
6526
+ url: "https://api.stripe.com/v1/balance",
6527
+ headers: (key) => ({ Authorization: `Bearer ${key}` })
6528
+ },
6529
+ figma: {
6530
+ url: "https://api.figma.com/v1/me",
6531
+ headers: (key) => ({ "X-Figma-Token": key })
6532
+ },
6533
+ discord: {
6534
+ url: "https://discord.com/api/v10/users/@me",
6535
+ headers: (key) => ({ Authorization: `Bot ${key}` })
6536
+ },
6537
+ resend: {
6538
+ url: "https://api.resend.com/domains",
6539
+ headers: (key) => ({ Authorization: `Bearer ${key}` })
6540
+ },
6541
+ notion: {
6542
+ url: "https://api.notion.com/v1/users/me",
6543
+ headers: (key) => ({ Authorization: `Bearer ${key}`, "Notion-Version": "2022-06-28" })
6544
+ },
6545
+ exa: {
6546
+ url: "https://api.exa.ai/search",
6547
+ method: "POST",
6548
+ headers: (key) => ({ "x-api-key": key, "Content-Type": "application/json" })
6549
+ },
6550
+ sentry: {
6551
+ url: "https://sentry.io/api/0/",
6552
+ headers: (key) => ({ Authorization: `Bearer ${key}` })
6553
+ },
6554
+ huggingface: {
6555
+ url: "https://huggingface.co/api/whoami-v2",
6556
+ headers: (key) => ({ Authorization: `Bearer ${key}` })
6557
+ },
6558
+ elevenlabs: {
6559
+ url: "https://api.elevenlabs.io/v1/user",
6560
+ headers: (key) => ({ "xi-api-key": key })
6561
+ },
6562
+ cloudflare: {
6563
+ url: "https://api.cloudflare.com/client/v4/user/tokens/verify",
6564
+ headers: (key) => ({ Authorization: `Bearer ${key}` })
6565
+ },
6566
+ mixpanel: {
6567
+ url: "https://mixpanel.com/api/app/me",
6568
+ headers: (key) => ({ Authorization: `Bearer ${key}` })
6569
+ }
6570
+ };
6571
+
6572
+ // src/cli/index.tsx
6502
6573
  import { createInterface } from "readline";
6503
6574
  import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
6504
6575
  loadConnectorVersions();
6505
6576
  var isTTY = process.stdout.isTTY ?? false;
6577
+ var PRESETS = {
6578
+ fullstack: { description: "Full-stack web app essentials", connectors: ["stripe", "github", "resend", "anthropic", "figma"] },
6579
+ ai: { description: "AI and ML models", connectors: ["anthropic", "openai", "xai", "mistral", "googlegemini", "elevenlabs"] },
6580
+ google: { description: "Google Workspace suite", connectors: ["gmail", "googledrive", "googledocs", "googlesheets", "googlecalendar", "googletasks", "googlecontacts"] },
6581
+ social: { description: "Social media platforms", connectors: ["x", "reddit", "youtube", "tiktok", "meta", "discord", "substack"] },
6582
+ devtools: { description: "Developer tooling", connectors: ["github", "docker", "sentry", "cloudflare", "e2b", "firecrawl"] },
6583
+ commerce: { description: "Commerce and finance", connectors: ["stripe", "shopify", "revolut", "mercury", "pandadoc"] }
6584
+ };
6506
6585
  var program2 = new Command;
6507
- program2.name("connectors").description("Install API connectors for your project").version("0.2.5");
6586
+ program2.name("connectors").description("Install API connectors for your project").version("0.2.6");
6508
6587
  program2.command("interactive", { isDefault: true }).alias("i").description("Interactive connector browser").action(() => {
6509
6588
  if (!isTTY) {
6510
6589
  console.log(`Non-interactive environment detected. Use a subcommand:
@@ -6534,7 +6613,7 @@ function listFilesRecursive(dir, base = dir) {
6534
6613
  }
6535
6614
  return files;
6536
6615
  }
6537
- program2.command("install").alias("add").argument("[connectors...]", "Connectors to install").option("-o, --overwrite", "Overwrite existing connectors", false).option("-d, --dry-run", "Preview what would be installed without making changes", false).option("-c, --category <category>", "Install all connectors in a category").option("--json", "Output results as JSON", false).description("Install one or more connectors").action((connectors, options) => {
6616
+ program2.command("install").alias("add").argument("[connectors...]", "Connectors to install").option("-o, --overwrite", "Overwrite existing connectors", false).option("-d, --dry-run", "Preview what would be installed without making changes", false).option("-c, --category <category>", "Install all connectors in a category").option("--preset <preset>", "Install a preset bundle (e.g. ai, fullstack, google)").option("--json", "Output results as JSON", false).description("Install one or more connectors").action((connectors, options) => {
6538
6617
  if (options.category) {
6539
6618
  const category = CATEGORIES.find((c) => c.toLowerCase() === options.category.toLowerCase());
6540
6619
  if (!category) {
@@ -6550,6 +6629,20 @@ program2.command("install").alias("add").argument("[connectors...]", "Connectors
6550
6629
  const categoryConnectors = getConnectorsByCategory(category).map((c) => c.name);
6551
6630
  connectors.push(...categoryConnectors);
6552
6631
  }
6632
+ if (options.preset) {
6633
+ const preset = PRESETS[options.preset.toLowerCase()];
6634
+ if (!preset) {
6635
+ if (options.json) {
6636
+ console.log(JSON.stringify({ error: `Unknown preset: ${options.preset}. Available: ${Object.keys(PRESETS).join(", ")}` }));
6637
+ } else {
6638
+ console.log(chalk2.red(`Unknown preset: ${options.preset}`));
6639
+ console.log(chalk2.dim(`Available: ${Object.keys(PRESETS).join(", ")}`));
6640
+ }
6641
+ process.exit(1);
6642
+ return;
6643
+ }
6644
+ connectors.push(...preset.connectors);
6645
+ }
6553
6646
  if (connectors.length === 0) {
6554
6647
  if (!isTTY) {
6555
6648
  console.error("Error: specify connectors to install. Example: connectors install figma stripe");
@@ -7653,7 +7746,7 @@ program2.command("import").argument("<file>", "JSON backup file to import (use -
7653
7746
  }
7654
7747
  });
7655
7748
  program2.command("upgrade").alias("self-update").option("--check", "Only check for updates, don't install", false).option("--json", "Output as JSON", false).description("Check for updates and upgrade to the latest version").action(async (options) => {
7656
- const currentVersion = "0.2.5";
7749
+ const currentVersion = "0.2.6";
7657
7750
  try {
7658
7751
  const res = await fetch("https://registry.npmjs.org/@hasna/connectors/latest");
7659
7752
  if (!res.ok)
@@ -7792,4 +7885,261 @@ complete -F _connectors connectors`);
7792
7885
  process.exit(1);
7793
7886
  }
7794
7887
  });
7888
+ program2.command("env").option("-o, --output <file>", "Write to file instead of stdout").option("--json", "Output as JSON", false).description("Generate .env.example from installed connectors' required env vars").action((options) => {
7889
+ const installed = getInstalledConnectors();
7890
+ if (installed.length === 0) {
7891
+ if (options.json) {
7892
+ console.log(JSON.stringify({ vars: [], connectors: [] }));
7893
+ } else {
7894
+ console.log(chalk2.dim("No connectors installed. Run: connectors install <name>"));
7895
+ }
7896
+ return;
7897
+ }
7898
+ const vars = [];
7899
+ const seen = new Set;
7900
+ for (const name of installed) {
7901
+ const docs = getConnectorDocs(name);
7902
+ if (!docs?.envVars)
7903
+ continue;
7904
+ for (const v of docs.envVars) {
7905
+ if (!seen.has(v.variable)) {
7906
+ seen.add(v.variable);
7907
+ vars.push({ variable: v.variable, description: v.description, connector: name });
7908
+ }
7909
+ }
7910
+ }
7911
+ if (options.json) {
7912
+ console.log(JSON.stringify({ vars, connectors: installed }, null, 2));
7913
+ return;
7914
+ }
7915
+ const lines = [
7916
+ "# Environment Variables",
7917
+ `# Generated by connectors env (${installed.length} installed connectors)`,
7918
+ "#"
7919
+ ];
7920
+ let lastConnector = "";
7921
+ for (const v of vars) {
7922
+ if (v.connector !== lastConnector) {
7923
+ lines.push("");
7924
+ lines.push(`# ${v.connector}`);
7925
+ lastConnector = v.connector;
7926
+ }
7927
+ if (v.description)
7928
+ lines.push(`# ${v.description}`);
7929
+ lines.push(`${v.variable}=`);
7930
+ }
7931
+ const output = lines.join(`
7932
+ `) + `
7933
+ `;
7934
+ if (options.output) {
7935
+ writeFileSync4(options.output, output);
7936
+ console.log(chalk2.green(`\u2713 Written to ${options.output} (${vars.length} variables)`));
7937
+ } else {
7938
+ console.log(output);
7939
+ }
7940
+ });
7941
+ program2.command("presets").option("--json", "Output as JSON", false).description("List available connector preset bundles").action((options) => {
7942
+ if (options.json) {
7943
+ console.log(JSON.stringify(Object.entries(PRESETS).map(([name, p]) => ({
7944
+ name,
7945
+ description: p.description,
7946
+ connectors: p.connectors,
7947
+ count: p.connectors.length
7948
+ })), null, 2));
7949
+ return;
7950
+ }
7951
+ console.log(chalk2.bold(`
7952
+ Available presets:
7953
+ `));
7954
+ for (const [name, preset] of Object.entries(PRESETS)) {
7955
+ console.log(` ${chalk2.cyan(name.padEnd(12))} ${preset.description}`);
7956
+ console.log(chalk2.dim(` ${"".padEnd(12)} ${preset.connectors.join(", ")}`));
7957
+ console.log();
7958
+ }
7959
+ console.log(chalk2.dim(` Install with: connectors install --preset <name>
7960
+ `));
7961
+ });
7962
+ program2.command("whoami").option("--json", "Output as JSON", false).description("Show current setup: config dir, installed connectors, auth status").action((options) => {
7963
+ const configDir = join5(homedir3(), ".connectors");
7964
+ const installed = getInstalledConnectors();
7965
+ const version = "0.2.6";
7966
+ let configured = 0;
7967
+ let unconfigured = 0;
7968
+ const connectorDetails = [];
7969
+ for (const name of installed) {
7970
+ const auth = getAuthStatus(name);
7971
+ if (auth.configured)
7972
+ configured++;
7973
+ else
7974
+ unconfigured++;
7975
+ const connectorConfigDir = join5(configDir, name.startsWith("connect-") ? name : `connect-${name}`);
7976
+ const currentProfileFile = join5(connectorConfigDir, "current_profile");
7977
+ let profile = "default";
7978
+ if (existsSync5(currentProfileFile)) {
7979
+ try {
7980
+ profile = readFileSync5(currentProfileFile, "utf-8").trim() || "default";
7981
+ } catch {}
7982
+ }
7983
+ connectorDetails.push({ name, configured: auth.configured, authType: auth.type, profile });
7984
+ }
7985
+ if (options.json) {
7986
+ console.log(JSON.stringify({
7987
+ version,
7988
+ configDir,
7989
+ configDirExists: existsSync5(configDir),
7990
+ installed: installed.length,
7991
+ configured,
7992
+ unconfigured,
7993
+ connectors: connectorDetails
7994
+ }, null, 2));
7995
+ return;
7996
+ }
7997
+ console.log(chalk2.bold(`
7998
+ Connectors Setup
7999
+ `));
8000
+ console.log(` Version: ${chalk2.cyan(version)}`);
8001
+ console.log(` Config: ${configDir}${existsSync5(configDir) ? "" : chalk2.dim(" (not created yet)")}`);
8002
+ console.log(` Installed: ${installed.length} connector${installed.length !== 1 ? "s" : ""}`);
8003
+ console.log(` Configured: ${chalk2.green(String(configured))} ready, ${unconfigured > 0 ? chalk2.red(String(unconfigured)) : chalk2.dim("0")} need auth`);
8004
+ if (connectorDetails.length > 0) {
8005
+ console.log(chalk2.bold(`
8006
+ Connectors:
8007
+ `));
8008
+ const nameWidth = Math.max(10, ...connectorDetails.map((c) => c.name.length)) + 2;
8009
+ for (const c of connectorDetails) {
8010
+ const status = c.configured ? chalk2.green("\u2713") : chalk2.red("\u2717");
8011
+ const profileLabel = c.profile !== "default" ? chalk2.dim(` [${c.profile}]`) : "";
8012
+ console.log(` ${status} ${chalk2.cyan(c.name.padEnd(nameWidth))}${c.authType.padEnd(8)}${profileLabel}`);
8013
+ }
8014
+ }
8015
+ console.log();
8016
+ });
8017
+ program2.command("test").argument("[connector]", "Connector to test (default: all installed)").option("--json", "Output as JSON", false).option("--timeout <ms>", "Request timeout in milliseconds", "10000").description("Verify API credentials by making a real request to the connector's API").action(async (connector, options) => {
8018
+ const timeout = parseInt(options.timeout, 10) || 1e4;
8019
+ const installed = getInstalledConnectors();
8020
+ let toTest;
8021
+ if (connector) {
8022
+ if (!getConnector(connector)) {
8023
+ if (options.json) {
8024
+ console.log(JSON.stringify({ error: `Connector '${connector}' not found` }));
8025
+ } else {
8026
+ console.log(chalk2.red(`Connector '${connector}' not found`));
8027
+ }
8028
+ process.exit(1);
8029
+ return;
8030
+ }
8031
+ toTest = [connector];
8032
+ } else {
8033
+ if (installed.length === 0) {
8034
+ if (options.json) {
8035
+ console.log(JSON.stringify({ results: [], tested: 0 }));
8036
+ } else {
8037
+ console.log(chalk2.dim("No connectors installed. Run: connectors install <name>"));
8038
+ }
8039
+ return;
8040
+ }
8041
+ toTest = installed;
8042
+ }
8043
+ if (!options.json)
8044
+ console.log(chalk2.bold(`
8045
+ Testing connector credentials...
8046
+ `));
8047
+ const results = [];
8048
+ for (const name of toTest) {
8049
+ const auth = getAuthStatus(name);
8050
+ const endpoint = TEST_ENDPOINTS[name];
8051
+ if (!auth.configured) {
8052
+ results.push({ name, status: "no-key", message: "No credentials configured" });
8053
+ if (!options.json)
8054
+ console.log(` ${chalk2.dim("\u25CB")} ${chalk2.dim(name)} \u2014 ${chalk2.dim("no credentials configured")}`);
8055
+ continue;
8056
+ }
8057
+ if (!endpoint) {
8058
+ results.push({ name, status: "skip", message: "No test endpoint defined" });
8059
+ if (!options.json)
8060
+ console.log(` ${chalk2.dim("\u25CB")} ${chalk2.dim(name)} \u2014 ${chalk2.dim("no test endpoint (key exists)")}`);
8061
+ continue;
8062
+ }
8063
+ const docs = getConnectorDocs(name);
8064
+ const envVars = docs?.envVars || [];
8065
+ let apiKey;
8066
+ for (const v of envVars) {
8067
+ if (process.env[v.variable]) {
8068
+ apiKey = process.env[v.variable];
8069
+ break;
8070
+ }
8071
+ }
8072
+ if (!apiKey) {
8073
+ const connectorConfigDir = join5(homedir3(), ".connectors", name.startsWith("connect-") ? name : `connect-${name}`);
8074
+ const profileFile = join5(connectorConfigDir, "profiles", "default.json");
8075
+ if (existsSync5(profileFile)) {
8076
+ try {
8077
+ const config = JSON.parse(readFileSync5(profileFile, "utf-8"));
8078
+ apiKey = Object.values(config).find((v) => typeof v === "string" && v.length > 0);
8079
+ } catch {}
8080
+ }
8081
+ if (!apiKey) {
8082
+ const profileDirConfig = join5(connectorConfigDir, "profiles", "default", "config.json");
8083
+ if (existsSync5(profileDirConfig)) {
8084
+ try {
8085
+ const config = JSON.parse(readFileSync5(profileDirConfig, "utf-8"));
8086
+ apiKey = Object.values(config).find((v) => typeof v === "string" && v.length > 0);
8087
+ } catch {}
8088
+ }
8089
+ }
8090
+ }
8091
+ if (!apiKey) {
8092
+ results.push({ name, status: "no-key", message: "Credentials configured but could not extract key" });
8093
+ if (!options.json)
8094
+ console.log(` ${chalk2.yellow("\u26A0")} ${chalk2.yellow(name)} \u2014 ${chalk2.dim("could not extract key")}`);
8095
+ continue;
8096
+ }
8097
+ const start = Date.now();
8098
+ try {
8099
+ const res = await fetch(endpoint.url, {
8100
+ method: endpoint.method || "GET",
8101
+ headers: endpoint.headers(apiKey),
8102
+ body: endpoint.method === "POST" ? JSON.stringify({ query: "test", num_results: 1 }) : undefined,
8103
+ signal: AbortSignal.timeout(timeout)
8104
+ });
8105
+ const ms = Date.now() - start;
8106
+ const successCodes = endpoint.successCodes || [200];
8107
+ if (successCodes.includes(res.status) || res.status >= 200 && res.status < 300) {
8108
+ results.push({ name, status: "pass", message: `OK (${res.status})`, ms });
8109
+ if (!options.json)
8110
+ console.log(` ${chalk2.green("\u2713")} ${chalk2.green(name)} \u2014 ${chalk2.dim(`${res.status} OK`)} ${chalk2.dim(`(${ms}ms)`)}`);
8111
+ } else {
8112
+ const body = await res.text().catch(() => "");
8113
+ const msg = res.status === 401 ? "Invalid or expired credentials" : `HTTP ${res.status}`;
8114
+ results.push({ name, status: "fail", message: msg, ms });
8115
+ if (!options.json)
8116
+ console.log(` ${chalk2.red("\u2717")} ${chalk2.red(name)} \u2014 ${chalk2.red(msg)} ${chalk2.dim(`(${ms}ms)`)}`);
8117
+ }
8118
+ } catch (e) {
8119
+ const ms = Date.now() - start;
8120
+ const msg = e instanceof Error ? e.message : String(e);
8121
+ results.push({ name, status: "fail", message: msg, ms });
8122
+ if (!options.json)
8123
+ console.log(` ${chalk2.red("\u2717")} ${chalk2.red(name)} \u2014 ${chalk2.red(msg)}`);
8124
+ }
8125
+ }
8126
+ if (options.json) {
8127
+ console.log(JSON.stringify({ results, tested: results.length, passed: results.filter((r) => r.status === "pass").length }, null, 2));
8128
+ } else {
8129
+ const passed = results.filter((r) => r.status === "pass").length;
8130
+ const failed = results.filter((r) => r.status === "fail").length;
8131
+ const skipped = results.filter((r) => r.status === "skip" || r.status === "no-key").length;
8132
+ console.log();
8133
+ const parts = [];
8134
+ if (passed > 0)
8135
+ parts.push(chalk2.green(`${passed} passed`));
8136
+ if (failed > 0)
8137
+ parts.push(chalk2.red(`${failed} failed`));
8138
+ if (skipped > 0)
8139
+ parts.push(chalk2.dim(`${skipped} skipped`));
8140
+ console.log(` ${parts.join(", ")}
8141
+ `);
8142
+ }
8143
+ process.exit(results.some((r) => r.status === "fail") ? 1 : 0);
8144
+ });
7795
8145
  program2.parse();
package/bin/mcp.js CHANGED
@@ -20275,7 +20275,7 @@ function guessKeyField(name) {
20275
20275
  loadConnectorVersions();
20276
20276
  var server = new McpServer({
20277
20277
  name: "connectors",
20278
- version: "0.2.5"
20278
+ version: "0.2.6"
20279
20279
  });
20280
20280
  server.registerTool("search_connectors", {
20281
20281
  title: "Search Connectors",
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Test endpoint definitions for verifying API credentials.
3
+ * Each entry maps a connector name to its health-check endpoint.
4
+ */
5
+ export interface TestEndpoint {
6
+ url: string;
7
+ method?: string;
8
+ headers: (key: string) => Record<string, string>;
9
+ /** Expected successful status codes */
10
+ successCodes?: number[];
11
+ }
12
+ export declare const TEST_ENDPOINTS: Record<string, TestEndpoint>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/connectors",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Open source connector library - Install API connectors with a single command",
5
5
  "type": "module",
6
6
  "bin": {