@hasna/connectors 0.2.4 → 0.2.5

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.
Files changed (3) hide show
  1. package/bin/index.js +298 -9
  2. package/bin/mcp.js +1 -1
  3. package/package.json +2 -2
package/bin/index.js CHANGED
@@ -6496,14 +6496,15 @@ function App({ initialConnectors, overwrite = false }) {
6496
6496
  init_registry();
6497
6497
  init_installer();
6498
6498
  init_auth();
6499
- import { readdirSync as readdirSync4, statSync as statSync3 } from "fs";
6499
+ import { readdirSync as readdirSync4, existsSync as existsSync5, statSync as statSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
6500
+ import { homedir as homedir3 } from "os";
6500
6501
  import { join as join5, relative } from "path";
6501
6502
  import { createInterface } from "readline";
6502
6503
  import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
6503
6504
  loadConnectorVersions();
6504
6505
  var isTTY = process.stdout.isTTY ?? false;
6505
6506
  var program2 = new Command;
6506
- program2.name("connectors").description("Install API connectors for your project").version("0.2.4");
6507
+ program2.name("connectors").description("Install API connectors for your project").version("0.2.5");
6507
6508
  program2.command("interactive", { isDefault: true }).alias("i").description("Interactive connector browser").action(() => {
6508
6509
  if (!isTTY) {
6509
6510
  console.log(`Non-interactive environment detected. Use a subcommand:
@@ -6533,7 +6534,22 @@ function listFilesRecursive(dir, base = dir) {
6533
6534
  }
6534
6535
  return files;
6535
6536
  }
6536
- 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("--json", "Output results as JSON", false).description("Install one or more connectors").action((connectors, options) => {
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) => {
6538
+ if (options.category) {
6539
+ const category = CATEGORIES.find((c) => c.toLowerCase() === options.category.toLowerCase());
6540
+ if (!category) {
6541
+ if (options.json) {
6542
+ console.log(JSON.stringify({ error: `Unknown category: ${options.category}. Available: ${CATEGORIES.join(", ")}` }));
6543
+ } else {
6544
+ console.log(chalk2.red(`Unknown category: ${options.category}`));
6545
+ console.log(chalk2.dim(`Available: ${CATEGORIES.join(", ")}`));
6546
+ }
6547
+ process.exit(1);
6548
+ return;
6549
+ }
6550
+ const categoryConnectors = getConnectorsByCategory(category).map((c) => c.name);
6551
+ connectors.push(...categoryConnectors);
6552
+ }
6537
6553
  if (connectors.length === 0) {
6538
6554
  if (!isTTY) {
6539
6555
  console.error("Error: specify connectors to install. Example: connectors install figma stripe");
@@ -6665,7 +6681,41 @@ Next steps:`));
6665
6681
  }
6666
6682
  process.exit(results.every((r) => r.success) ? 0 : 1);
6667
6683
  });
6668
- program2.command("list").alias("ls").option("-c, --category <category>", "Filter by category").option("-a, --all", "Show all available connectors", false).option("-i, --installed", "Show only installed connectors", false).option("--json", "Output as JSON", false).description("List available or installed connectors").action((options) => {
6684
+ program2.command("list").alias("ls").option("-c, --category <category>", "Filter by category").option("-a, --all", "Show all available connectors", false).option("-i, --installed", "Show only installed connectors", false).option("-b, --brief", "Output only connector names", false).option("--json", "Output as JSON", false).description("List available or installed connectors").action((options) => {
6685
+ if (options.brief) {
6686
+ if (options.installed) {
6687
+ const installed = getInstalledConnectors();
6688
+ if (options.json) {
6689
+ console.log(JSON.stringify(installed));
6690
+ } else {
6691
+ for (const name of installed)
6692
+ console.log(name);
6693
+ }
6694
+ } else if (options.category) {
6695
+ const category = CATEGORIES.find((c) => c.toLowerCase() === options.category.toLowerCase());
6696
+ if (!category) {
6697
+ console.error(`Unknown category: ${options.category}`);
6698
+ process.exit(1);
6699
+ return;
6700
+ }
6701
+ const names = getConnectorsByCategory(category).map((c) => c.name);
6702
+ if (options.json) {
6703
+ console.log(JSON.stringify(names));
6704
+ } else {
6705
+ for (const n of names)
6706
+ console.log(n);
6707
+ }
6708
+ } else {
6709
+ const names = CONNECTORS.map((c) => c.name);
6710
+ if (options.json) {
6711
+ console.log(JSON.stringify(names));
6712
+ } else {
6713
+ for (const n of names)
6714
+ console.log(n);
6715
+ }
6716
+ }
6717
+ return;
6718
+ }
6669
6719
  if (options.installed) {
6670
6720
  const installed = getInstalledConnectors();
6671
6721
  if (installed.length === 0) {
@@ -7284,13 +7334,10 @@ Open this URL to authenticate:
7284
7334
  connector,
7285
7335
  authType,
7286
7336
  configured: statusAfter2.configured,
7287
- field: options.field || null
7337
+ field: options.field || "apiKey"
7288
7338
  }));
7289
7339
  } else {
7290
- console.log(chalk2.green(`\u2713 API key saved for ${meta.displayName}`));
7291
- if (options.field) {
7292
- console.log(chalk2.dim(` Field: ${options.field}`));
7293
- }
7340
+ console.log(chalk2.green(`\u2713 Saved ${options.field || "apiKey"} for ${meta.displayName}`));
7294
7341
  }
7295
7342
  process.exit(0);
7296
7343
  return;
@@ -7503,4 +7550,246 @@ Next steps:
7503
7550
  console.log();
7504
7551
  process.exit(results.every((r) => r.success) ? 0 : 1);
7505
7552
  });
7553
+ program2.command("export").option("-o, --output <file>", "Write to file instead of stdout").description("Export all connector credentials as JSON backup").action((options) => {
7554
+ const connectDir = join5(homedir3(), ".connectors");
7555
+ const result = {};
7556
+ if (existsSync5(connectDir)) {
7557
+ for (const entry of readdirSync4(connectDir)) {
7558
+ const entryPath = join5(connectDir, entry);
7559
+ if (!statSync3(entryPath).isDirectory() || !entry.startsWith("connect-"))
7560
+ continue;
7561
+ const connectorName = entry.replace(/^connect-/, "");
7562
+ const profilesDir = join5(entryPath, "profiles");
7563
+ if (!existsSync5(profilesDir))
7564
+ continue;
7565
+ const profiles = {};
7566
+ for (const pEntry of readdirSync4(profilesDir)) {
7567
+ const pPath = join5(profilesDir, pEntry);
7568
+ if (statSync3(pPath).isFile() && pEntry.endsWith(".json")) {
7569
+ try {
7570
+ profiles[pEntry.replace(/\.json$/, "")] = JSON.parse(readFileSync5(pPath, "utf-8"));
7571
+ } catch {}
7572
+ } else if (statSync3(pPath).isDirectory()) {
7573
+ const configPath = join5(pPath, "config.json");
7574
+ if (existsSync5(configPath)) {
7575
+ try {
7576
+ profiles[pEntry] = JSON.parse(readFileSync5(configPath, "utf-8"));
7577
+ } catch {}
7578
+ }
7579
+ }
7580
+ }
7581
+ if (Object.keys(profiles).length > 0)
7582
+ result[connectorName] = { profiles };
7583
+ }
7584
+ }
7585
+ const exportData = JSON.stringify({ connectors: result, exportedAt: new Date().toISOString() }, null, 2);
7586
+ if (options.output) {
7587
+ writeFileSync4(options.output, exportData);
7588
+ console.log(chalk2.green(`\u2713 Exported to ${options.output}`));
7589
+ } else {
7590
+ console.log(exportData);
7591
+ }
7592
+ });
7593
+ program2.command("import").argument("<file>", "JSON backup file to import (use - for stdin)").option("--json", "Output as JSON", false).description("Import connector credentials from a JSON backup").action(async (file, options) => {
7594
+ let raw;
7595
+ if (file === "-") {
7596
+ const chunks = [];
7597
+ for await (const chunk of process.stdin)
7598
+ chunks.push(chunk.toString());
7599
+ raw = chunks.join("");
7600
+ } else {
7601
+ if (!existsSync5(file)) {
7602
+ if (options.json) {
7603
+ console.log(JSON.stringify({ error: `File not found: ${file}` }));
7604
+ } else {
7605
+ console.log(chalk2.red(`File not found: ${file}`));
7606
+ }
7607
+ process.exit(1);
7608
+ return;
7609
+ }
7610
+ raw = readFileSync5(file, "utf-8");
7611
+ }
7612
+ let data;
7613
+ try {
7614
+ data = JSON.parse(raw);
7615
+ } catch {
7616
+ if (options.json) {
7617
+ console.log(JSON.stringify({ error: "Invalid JSON" }));
7618
+ } else {
7619
+ console.log(chalk2.red("Invalid JSON in import file"));
7620
+ }
7621
+ process.exit(1);
7622
+ return;
7623
+ }
7624
+ if (!data.connectors || typeof data.connectors !== "object") {
7625
+ if (options.json) {
7626
+ console.log(JSON.stringify({ error: "Invalid format: missing 'connectors' object" }));
7627
+ } else {
7628
+ console.log(chalk2.red("Invalid format: missing 'connectors' object"));
7629
+ }
7630
+ process.exit(1);
7631
+ return;
7632
+ }
7633
+ const connectDir = join5(homedir3(), ".connectors");
7634
+ let imported = 0;
7635
+ for (const [connectorName, connData] of Object.entries(data.connectors)) {
7636
+ if (!/^[a-z0-9-]+$/.test(connectorName))
7637
+ continue;
7638
+ if (!connData.profiles || typeof connData.profiles !== "object")
7639
+ continue;
7640
+ const profilesDir = join5(connectDir, `connect-${connectorName}`, "profiles");
7641
+ for (const [profileName, config] of Object.entries(connData.profiles)) {
7642
+ if (!config || typeof config !== "object")
7643
+ continue;
7644
+ mkdirSync4(profilesDir, { recursive: true });
7645
+ writeFileSync4(join5(profilesDir, `${profileName}.json`), JSON.stringify(config, null, 2));
7646
+ imported++;
7647
+ }
7648
+ }
7649
+ if (options.json) {
7650
+ console.log(JSON.stringify({ success: true, imported }));
7651
+ } else {
7652
+ console.log(chalk2.green(`\u2713 Imported ${imported} profile(s)`));
7653
+ }
7654
+ });
7655
+ 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";
7657
+ try {
7658
+ const res = await fetch("https://registry.npmjs.org/@hasna/connectors/latest");
7659
+ if (!res.ok)
7660
+ throw new Error(`npm registry returned ${res.status}`);
7661
+ const data = await res.json();
7662
+ const latestVersion = data.version;
7663
+ const isUpToDate = currentVersion === latestVersion;
7664
+ if (options.json) {
7665
+ console.log(JSON.stringify({ current: currentVersion, latest: latestVersion, upToDate: isUpToDate }));
7666
+ if (options.check) {
7667
+ process.exit(isUpToDate ? 0 : 1);
7668
+ return;
7669
+ }
7670
+ } else {
7671
+ console.log(`
7672
+ Current: ${chalk2.cyan(currentVersion)}`);
7673
+ console.log(` Latest: ${chalk2.cyan(latestVersion)}`);
7674
+ if (isUpToDate) {
7675
+ console.log(chalk2.green(`
7676
+ Already up to date!
7677
+ `));
7678
+ process.exit(0);
7679
+ return;
7680
+ }
7681
+ console.log(chalk2.yellow(`
7682
+ Update available: ${currentVersion} \u2192 ${latestVersion}`));
7683
+ }
7684
+ if (options.check) {
7685
+ if (!options.json)
7686
+ console.log(chalk2.dim(`
7687
+ Run 'connectors upgrade' to install.
7688
+ `));
7689
+ process.exit(isUpToDate ? 0 : 1);
7690
+ return;
7691
+ }
7692
+ if (!options.json)
7693
+ console.log(chalk2.dim(`
7694
+ Upgrading...`));
7695
+ const { execSync } = await import("child_process");
7696
+ try {
7697
+ execSync(`bun install -g @hasna/connectors@${latestVersion}`, { stdio: options.json ? "pipe" : "inherit" });
7698
+ } catch {
7699
+ try {
7700
+ execSync(`npm install -g @hasna/connectors@${latestVersion}`, { stdio: options.json ? "pipe" : "inherit" });
7701
+ } catch (e) {
7702
+ if (options.json) {
7703
+ console.log(JSON.stringify({ error: "Failed to upgrade. Try manually: bun install -g @hasna/connectors@latest" }));
7704
+ } else {
7705
+ console.log(chalk2.red(`
7706
+ Failed to upgrade. Try manually:`));
7707
+ console.log(chalk2.dim(` bun install -g @hasna/connectors@latest
7708
+ `));
7709
+ }
7710
+ process.exit(1);
7711
+ return;
7712
+ }
7713
+ }
7714
+ if (options.json) {
7715
+ console.log(JSON.stringify({ upgraded: true, from: currentVersion, to: latestVersion }));
7716
+ } else {
7717
+ console.log(chalk2.green(`
7718
+ Upgraded to ${latestVersion}!
7719
+ `));
7720
+ }
7721
+ } catch (e) {
7722
+ if (options.json) {
7723
+ console.log(JSON.stringify({ error: e instanceof Error ? e.message : "Failed to check for updates" }));
7724
+ } else {
7725
+ console.log(chalk2.red(`
7726
+ Failed to check for updates: ${e instanceof Error ? e.message : e}
7727
+ `));
7728
+ }
7729
+ process.exit(1);
7730
+ }
7731
+ });
7732
+ program2.command("completions").argument("<shell>", "Shell type: bash, zsh, or fish").description("Output shell completion script").action((shell) => {
7733
+ const commands = ["interactive", "install", "list", "search", "info", "docs", "remove", "categories", "serve", "update", "status", "doctor", "auth", "init", "export", "import", "upgrade", "completions"];
7734
+ const connectorNames = CONNECTORS.map((c) => c.name);
7735
+ const categoryNames = CATEGORIES.map((c) => `"${c}"`);
7736
+ if (shell === "zsh") {
7737
+ console.log(`#compdef connectors
7738
+ _connectors() {
7739
+ local -a commands connectors categories
7740
+ commands=(${commands.join(" ")})
7741
+ connectors=(${connectorNames.join(" ")})
7742
+ categories=(${categoryNames.map((c) => c.replace(/"/g, "\\\"")).join(" ")})
7743
+
7744
+ if (( CURRENT == 2 )); then
7745
+ _describe 'command' commands
7746
+ elif (( CURRENT == 3 )); then
7747
+ case "\${words[2]}" in
7748
+ install|add|info|docs|remove|rm|auth)
7749
+ _describe 'connector' connectors ;;
7750
+ search) _message 'search query' ;;
7751
+ list|ls) _arguments '--category[Filter by category]:category:(${CATEGORIES.join(" ").replace(/&/g, "\\&")})' '--installed' '--json' '--brief' ;;
7752
+ *) ;;
7753
+ esac
7754
+ fi
7755
+ }
7756
+ compdef _connectors connectors`);
7757
+ } else if (shell === "bash") {
7758
+ console.log(`_connectors() {
7759
+ local cur prev commands connectors
7760
+ COMPREPLY=()
7761
+ cur="\${COMP_WORDS[COMP_CWORD]}"
7762
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
7763
+ commands="${commands.join(" ")}"
7764
+ connectors="${connectorNames.join(" ")}"
7765
+
7766
+ if [[ \${COMP_CWORD} -eq 1 ]]; then
7767
+ COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
7768
+ elif [[ \${COMP_CWORD} -eq 2 ]]; then
7769
+ case "\${prev}" in
7770
+ install|add|info|docs|remove|rm|auth)
7771
+ COMPREPLY=( $(compgen -W "\${connectors}" -- "\${cur}") ) ;;
7772
+ esac
7773
+ fi
7774
+ }
7775
+ complete -F _connectors connectors`);
7776
+ } else if (shell === "fish") {
7777
+ let script = `# Fish completions for connectors
7778
+ `;
7779
+ for (const cmd of commands) {
7780
+ script += `complete -c connectors -n "__fish_use_subcommand" -a "${cmd}"
7781
+ `;
7782
+ }
7783
+ script += `# Connector names for install/info/docs/remove/auth
7784
+ `;
7785
+ for (const name of connectorNames) {
7786
+ script += `complete -c connectors -n "__fish_seen_subcommand_from install add info docs remove rm auth" -a "${name}"
7787
+ `;
7788
+ }
7789
+ console.log(script);
7790
+ } else {
7791
+ console.error(`Unknown shell: ${shell}. Supported: bash, zsh, fish`);
7792
+ process.exit(1);
7793
+ }
7794
+ });
7506
7795
  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.4"
20278
+ version: "0.2.5"
20279
20279
  });
20280
20280
  server.registerTool("search_connectors", {
20281
20281
  title: "Search Connectors",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/connectors",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Open source connector library - Install API connectors with a single command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -26,7 +26,7 @@
26
26
  "scripts": {
27
27
  "build": "cd dashboard && bun run build && cd .. && bun build ./src/cli/index.tsx --outdir ./bin --target bun --external ink --external react --external chalk --external conf && bun build ./src/mcp/index.ts --outfile ./bin/mcp.js --target bun && bun build ./src/server/index.ts --outfile ./bin/serve.js --target bun && bun build ./src/index.ts --outdir ./dist --target bun && tsc --emitDeclarationOnly --outDir ./dist",
28
28
  "build:dashboard": "cd dashboard && bun run build",
29
- "postinstall": "cd dashboard && bun install",
29
+ "postinstall": "[ \"$SKIP_DASHBOARD\" = \"1\" ] || [ -d dashboard/node_modules ] || (cd dashboard && bun install)",
30
30
  "dev": "bun run ./src/cli/index.tsx",
31
31
  "typecheck": "tsc --noEmit",
32
32
  "test": "bun test",