@hasna/connectors 0.2.4 → 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 +6 -0
- package/bin/index.js +648 -9
- package/bin/mcp.js +1 -1
- package/dist/lib/test-endpoints.d.ts +12 -0
- package/package.json +2 -2
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
|
@@ -6496,14 +6496,94 @@ 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";
|
|
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
|
|
6501
6573
|
import { createInterface } from "readline";
|
|
6502
6574
|
import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
|
|
6503
6575
|
loadConnectorVersions();
|
|
6504
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
|
+
};
|
|
6505
6585
|
var program2 = new Command;
|
|
6506
|
-
program2.name("connectors").description("Install API connectors for your project").version("0.2.
|
|
6586
|
+
program2.name("connectors").description("Install API connectors for your project").version("0.2.6");
|
|
6507
6587
|
program2.command("interactive", { isDefault: true }).alias("i").description("Interactive connector browser").action(() => {
|
|
6508
6588
|
if (!isTTY) {
|
|
6509
6589
|
console.log(`Non-interactive environment detected. Use a subcommand:
|
|
@@ -6533,7 +6613,36 @@ function listFilesRecursive(dir, base = dir) {
|
|
|
6533
6613
|
}
|
|
6534
6614
|
return files;
|
|
6535
6615
|
}
|
|
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) => {
|
|
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) => {
|
|
6617
|
+
if (options.category) {
|
|
6618
|
+
const category = CATEGORIES.find((c) => c.toLowerCase() === options.category.toLowerCase());
|
|
6619
|
+
if (!category) {
|
|
6620
|
+
if (options.json) {
|
|
6621
|
+
console.log(JSON.stringify({ error: `Unknown category: ${options.category}. Available: ${CATEGORIES.join(", ")}` }));
|
|
6622
|
+
} else {
|
|
6623
|
+
console.log(chalk2.red(`Unknown category: ${options.category}`));
|
|
6624
|
+
console.log(chalk2.dim(`Available: ${CATEGORIES.join(", ")}`));
|
|
6625
|
+
}
|
|
6626
|
+
process.exit(1);
|
|
6627
|
+
return;
|
|
6628
|
+
}
|
|
6629
|
+
const categoryConnectors = getConnectorsByCategory(category).map((c) => c.name);
|
|
6630
|
+
connectors.push(...categoryConnectors);
|
|
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
|
+
}
|
|
6537
6646
|
if (connectors.length === 0) {
|
|
6538
6647
|
if (!isTTY) {
|
|
6539
6648
|
console.error("Error: specify connectors to install. Example: connectors install figma stripe");
|
|
@@ -6665,7 +6774,41 @@ Next steps:`));
|
|
|
6665
6774
|
}
|
|
6666
6775
|
process.exit(results.every((r) => r.success) ? 0 : 1);
|
|
6667
6776
|
});
|
|
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) => {
|
|
6777
|
+
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) => {
|
|
6778
|
+
if (options.brief) {
|
|
6779
|
+
if (options.installed) {
|
|
6780
|
+
const installed = getInstalledConnectors();
|
|
6781
|
+
if (options.json) {
|
|
6782
|
+
console.log(JSON.stringify(installed));
|
|
6783
|
+
} else {
|
|
6784
|
+
for (const name of installed)
|
|
6785
|
+
console.log(name);
|
|
6786
|
+
}
|
|
6787
|
+
} else if (options.category) {
|
|
6788
|
+
const category = CATEGORIES.find((c) => c.toLowerCase() === options.category.toLowerCase());
|
|
6789
|
+
if (!category) {
|
|
6790
|
+
console.error(`Unknown category: ${options.category}`);
|
|
6791
|
+
process.exit(1);
|
|
6792
|
+
return;
|
|
6793
|
+
}
|
|
6794
|
+
const names = getConnectorsByCategory(category).map((c) => c.name);
|
|
6795
|
+
if (options.json) {
|
|
6796
|
+
console.log(JSON.stringify(names));
|
|
6797
|
+
} else {
|
|
6798
|
+
for (const n of names)
|
|
6799
|
+
console.log(n);
|
|
6800
|
+
}
|
|
6801
|
+
} else {
|
|
6802
|
+
const names = CONNECTORS.map((c) => c.name);
|
|
6803
|
+
if (options.json) {
|
|
6804
|
+
console.log(JSON.stringify(names));
|
|
6805
|
+
} else {
|
|
6806
|
+
for (const n of names)
|
|
6807
|
+
console.log(n);
|
|
6808
|
+
}
|
|
6809
|
+
}
|
|
6810
|
+
return;
|
|
6811
|
+
}
|
|
6669
6812
|
if (options.installed) {
|
|
6670
6813
|
const installed = getInstalledConnectors();
|
|
6671
6814
|
if (installed.length === 0) {
|
|
@@ -7284,13 +7427,10 @@ Open this URL to authenticate:
|
|
|
7284
7427
|
connector,
|
|
7285
7428
|
authType,
|
|
7286
7429
|
configured: statusAfter2.configured,
|
|
7287
|
-
field: options.field ||
|
|
7430
|
+
field: options.field || "apiKey"
|
|
7288
7431
|
}));
|
|
7289
7432
|
} else {
|
|
7290
|
-
console.log(chalk2.green(`\u2713
|
|
7291
|
-
if (options.field) {
|
|
7292
|
-
console.log(chalk2.dim(` Field: ${options.field}`));
|
|
7293
|
-
}
|
|
7433
|
+
console.log(chalk2.green(`\u2713 Saved ${options.field || "apiKey"} for ${meta.displayName}`));
|
|
7294
7434
|
}
|
|
7295
7435
|
process.exit(0);
|
|
7296
7436
|
return;
|
|
@@ -7503,4 +7643,503 @@ Next steps:
|
|
|
7503
7643
|
console.log();
|
|
7504
7644
|
process.exit(results.every((r) => r.success) ? 0 : 1);
|
|
7505
7645
|
});
|
|
7646
|
+
program2.command("export").option("-o, --output <file>", "Write to file instead of stdout").description("Export all connector credentials as JSON backup").action((options) => {
|
|
7647
|
+
const connectDir = join5(homedir3(), ".connectors");
|
|
7648
|
+
const result = {};
|
|
7649
|
+
if (existsSync5(connectDir)) {
|
|
7650
|
+
for (const entry of readdirSync4(connectDir)) {
|
|
7651
|
+
const entryPath = join5(connectDir, entry);
|
|
7652
|
+
if (!statSync3(entryPath).isDirectory() || !entry.startsWith("connect-"))
|
|
7653
|
+
continue;
|
|
7654
|
+
const connectorName = entry.replace(/^connect-/, "");
|
|
7655
|
+
const profilesDir = join5(entryPath, "profiles");
|
|
7656
|
+
if (!existsSync5(profilesDir))
|
|
7657
|
+
continue;
|
|
7658
|
+
const profiles = {};
|
|
7659
|
+
for (const pEntry of readdirSync4(profilesDir)) {
|
|
7660
|
+
const pPath = join5(profilesDir, pEntry);
|
|
7661
|
+
if (statSync3(pPath).isFile() && pEntry.endsWith(".json")) {
|
|
7662
|
+
try {
|
|
7663
|
+
profiles[pEntry.replace(/\.json$/, "")] = JSON.parse(readFileSync5(pPath, "utf-8"));
|
|
7664
|
+
} catch {}
|
|
7665
|
+
} else if (statSync3(pPath).isDirectory()) {
|
|
7666
|
+
const configPath = join5(pPath, "config.json");
|
|
7667
|
+
if (existsSync5(configPath)) {
|
|
7668
|
+
try {
|
|
7669
|
+
profiles[pEntry] = JSON.parse(readFileSync5(configPath, "utf-8"));
|
|
7670
|
+
} catch {}
|
|
7671
|
+
}
|
|
7672
|
+
}
|
|
7673
|
+
}
|
|
7674
|
+
if (Object.keys(profiles).length > 0)
|
|
7675
|
+
result[connectorName] = { profiles };
|
|
7676
|
+
}
|
|
7677
|
+
}
|
|
7678
|
+
const exportData = JSON.stringify({ connectors: result, exportedAt: new Date().toISOString() }, null, 2);
|
|
7679
|
+
if (options.output) {
|
|
7680
|
+
writeFileSync4(options.output, exportData);
|
|
7681
|
+
console.log(chalk2.green(`\u2713 Exported to ${options.output}`));
|
|
7682
|
+
} else {
|
|
7683
|
+
console.log(exportData);
|
|
7684
|
+
}
|
|
7685
|
+
});
|
|
7686
|
+
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) => {
|
|
7687
|
+
let raw;
|
|
7688
|
+
if (file === "-") {
|
|
7689
|
+
const chunks = [];
|
|
7690
|
+
for await (const chunk of process.stdin)
|
|
7691
|
+
chunks.push(chunk.toString());
|
|
7692
|
+
raw = chunks.join("");
|
|
7693
|
+
} else {
|
|
7694
|
+
if (!existsSync5(file)) {
|
|
7695
|
+
if (options.json) {
|
|
7696
|
+
console.log(JSON.stringify({ error: `File not found: ${file}` }));
|
|
7697
|
+
} else {
|
|
7698
|
+
console.log(chalk2.red(`File not found: ${file}`));
|
|
7699
|
+
}
|
|
7700
|
+
process.exit(1);
|
|
7701
|
+
return;
|
|
7702
|
+
}
|
|
7703
|
+
raw = readFileSync5(file, "utf-8");
|
|
7704
|
+
}
|
|
7705
|
+
let data;
|
|
7706
|
+
try {
|
|
7707
|
+
data = JSON.parse(raw);
|
|
7708
|
+
} catch {
|
|
7709
|
+
if (options.json) {
|
|
7710
|
+
console.log(JSON.stringify({ error: "Invalid JSON" }));
|
|
7711
|
+
} else {
|
|
7712
|
+
console.log(chalk2.red("Invalid JSON in import file"));
|
|
7713
|
+
}
|
|
7714
|
+
process.exit(1);
|
|
7715
|
+
return;
|
|
7716
|
+
}
|
|
7717
|
+
if (!data.connectors || typeof data.connectors !== "object") {
|
|
7718
|
+
if (options.json) {
|
|
7719
|
+
console.log(JSON.stringify({ error: "Invalid format: missing 'connectors' object" }));
|
|
7720
|
+
} else {
|
|
7721
|
+
console.log(chalk2.red("Invalid format: missing 'connectors' object"));
|
|
7722
|
+
}
|
|
7723
|
+
process.exit(1);
|
|
7724
|
+
return;
|
|
7725
|
+
}
|
|
7726
|
+
const connectDir = join5(homedir3(), ".connectors");
|
|
7727
|
+
let imported = 0;
|
|
7728
|
+
for (const [connectorName, connData] of Object.entries(data.connectors)) {
|
|
7729
|
+
if (!/^[a-z0-9-]+$/.test(connectorName))
|
|
7730
|
+
continue;
|
|
7731
|
+
if (!connData.profiles || typeof connData.profiles !== "object")
|
|
7732
|
+
continue;
|
|
7733
|
+
const profilesDir = join5(connectDir, `connect-${connectorName}`, "profiles");
|
|
7734
|
+
for (const [profileName, config] of Object.entries(connData.profiles)) {
|
|
7735
|
+
if (!config || typeof config !== "object")
|
|
7736
|
+
continue;
|
|
7737
|
+
mkdirSync4(profilesDir, { recursive: true });
|
|
7738
|
+
writeFileSync4(join5(profilesDir, `${profileName}.json`), JSON.stringify(config, null, 2));
|
|
7739
|
+
imported++;
|
|
7740
|
+
}
|
|
7741
|
+
}
|
|
7742
|
+
if (options.json) {
|
|
7743
|
+
console.log(JSON.stringify({ success: true, imported }));
|
|
7744
|
+
} else {
|
|
7745
|
+
console.log(chalk2.green(`\u2713 Imported ${imported} profile(s)`));
|
|
7746
|
+
}
|
|
7747
|
+
});
|
|
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) => {
|
|
7749
|
+
const currentVersion = "0.2.6";
|
|
7750
|
+
try {
|
|
7751
|
+
const res = await fetch("https://registry.npmjs.org/@hasna/connectors/latest");
|
|
7752
|
+
if (!res.ok)
|
|
7753
|
+
throw new Error(`npm registry returned ${res.status}`);
|
|
7754
|
+
const data = await res.json();
|
|
7755
|
+
const latestVersion = data.version;
|
|
7756
|
+
const isUpToDate = currentVersion === latestVersion;
|
|
7757
|
+
if (options.json) {
|
|
7758
|
+
console.log(JSON.stringify({ current: currentVersion, latest: latestVersion, upToDate: isUpToDate }));
|
|
7759
|
+
if (options.check) {
|
|
7760
|
+
process.exit(isUpToDate ? 0 : 1);
|
|
7761
|
+
return;
|
|
7762
|
+
}
|
|
7763
|
+
} else {
|
|
7764
|
+
console.log(`
|
|
7765
|
+
Current: ${chalk2.cyan(currentVersion)}`);
|
|
7766
|
+
console.log(` Latest: ${chalk2.cyan(latestVersion)}`);
|
|
7767
|
+
if (isUpToDate) {
|
|
7768
|
+
console.log(chalk2.green(`
|
|
7769
|
+
Already up to date!
|
|
7770
|
+
`));
|
|
7771
|
+
process.exit(0);
|
|
7772
|
+
return;
|
|
7773
|
+
}
|
|
7774
|
+
console.log(chalk2.yellow(`
|
|
7775
|
+
Update available: ${currentVersion} \u2192 ${latestVersion}`));
|
|
7776
|
+
}
|
|
7777
|
+
if (options.check) {
|
|
7778
|
+
if (!options.json)
|
|
7779
|
+
console.log(chalk2.dim(`
|
|
7780
|
+
Run 'connectors upgrade' to install.
|
|
7781
|
+
`));
|
|
7782
|
+
process.exit(isUpToDate ? 0 : 1);
|
|
7783
|
+
return;
|
|
7784
|
+
}
|
|
7785
|
+
if (!options.json)
|
|
7786
|
+
console.log(chalk2.dim(`
|
|
7787
|
+
Upgrading...`));
|
|
7788
|
+
const { execSync } = await import("child_process");
|
|
7789
|
+
try {
|
|
7790
|
+
execSync(`bun install -g @hasna/connectors@${latestVersion}`, { stdio: options.json ? "pipe" : "inherit" });
|
|
7791
|
+
} catch {
|
|
7792
|
+
try {
|
|
7793
|
+
execSync(`npm install -g @hasna/connectors@${latestVersion}`, { stdio: options.json ? "pipe" : "inherit" });
|
|
7794
|
+
} catch (e) {
|
|
7795
|
+
if (options.json) {
|
|
7796
|
+
console.log(JSON.stringify({ error: "Failed to upgrade. Try manually: bun install -g @hasna/connectors@latest" }));
|
|
7797
|
+
} else {
|
|
7798
|
+
console.log(chalk2.red(`
|
|
7799
|
+
Failed to upgrade. Try manually:`));
|
|
7800
|
+
console.log(chalk2.dim(` bun install -g @hasna/connectors@latest
|
|
7801
|
+
`));
|
|
7802
|
+
}
|
|
7803
|
+
process.exit(1);
|
|
7804
|
+
return;
|
|
7805
|
+
}
|
|
7806
|
+
}
|
|
7807
|
+
if (options.json) {
|
|
7808
|
+
console.log(JSON.stringify({ upgraded: true, from: currentVersion, to: latestVersion }));
|
|
7809
|
+
} else {
|
|
7810
|
+
console.log(chalk2.green(`
|
|
7811
|
+
Upgraded to ${latestVersion}!
|
|
7812
|
+
`));
|
|
7813
|
+
}
|
|
7814
|
+
} catch (e) {
|
|
7815
|
+
if (options.json) {
|
|
7816
|
+
console.log(JSON.stringify({ error: e instanceof Error ? e.message : "Failed to check for updates" }));
|
|
7817
|
+
} else {
|
|
7818
|
+
console.log(chalk2.red(`
|
|
7819
|
+
Failed to check for updates: ${e instanceof Error ? e.message : e}
|
|
7820
|
+
`));
|
|
7821
|
+
}
|
|
7822
|
+
process.exit(1);
|
|
7823
|
+
}
|
|
7824
|
+
});
|
|
7825
|
+
program2.command("completions").argument("<shell>", "Shell type: bash, zsh, or fish").description("Output shell completion script").action((shell) => {
|
|
7826
|
+
const commands = ["interactive", "install", "list", "search", "info", "docs", "remove", "categories", "serve", "update", "status", "doctor", "auth", "init", "export", "import", "upgrade", "completions"];
|
|
7827
|
+
const connectorNames = CONNECTORS.map((c) => c.name);
|
|
7828
|
+
const categoryNames = CATEGORIES.map((c) => `"${c}"`);
|
|
7829
|
+
if (shell === "zsh") {
|
|
7830
|
+
console.log(`#compdef connectors
|
|
7831
|
+
_connectors() {
|
|
7832
|
+
local -a commands connectors categories
|
|
7833
|
+
commands=(${commands.join(" ")})
|
|
7834
|
+
connectors=(${connectorNames.join(" ")})
|
|
7835
|
+
categories=(${categoryNames.map((c) => c.replace(/"/g, "\\\"")).join(" ")})
|
|
7836
|
+
|
|
7837
|
+
if (( CURRENT == 2 )); then
|
|
7838
|
+
_describe 'command' commands
|
|
7839
|
+
elif (( CURRENT == 3 )); then
|
|
7840
|
+
case "\${words[2]}" in
|
|
7841
|
+
install|add|info|docs|remove|rm|auth)
|
|
7842
|
+
_describe 'connector' connectors ;;
|
|
7843
|
+
search) _message 'search query' ;;
|
|
7844
|
+
list|ls) _arguments '--category[Filter by category]:category:(${CATEGORIES.join(" ").replace(/&/g, "\\&")})' '--installed' '--json' '--brief' ;;
|
|
7845
|
+
*) ;;
|
|
7846
|
+
esac
|
|
7847
|
+
fi
|
|
7848
|
+
}
|
|
7849
|
+
compdef _connectors connectors`);
|
|
7850
|
+
} else if (shell === "bash") {
|
|
7851
|
+
console.log(`_connectors() {
|
|
7852
|
+
local cur prev commands connectors
|
|
7853
|
+
COMPREPLY=()
|
|
7854
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
7855
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
7856
|
+
commands="${commands.join(" ")}"
|
|
7857
|
+
connectors="${connectorNames.join(" ")}"
|
|
7858
|
+
|
|
7859
|
+
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
7860
|
+
COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
|
|
7861
|
+
elif [[ \${COMP_CWORD} -eq 2 ]]; then
|
|
7862
|
+
case "\${prev}" in
|
|
7863
|
+
install|add|info|docs|remove|rm|auth)
|
|
7864
|
+
COMPREPLY=( $(compgen -W "\${connectors}" -- "\${cur}") ) ;;
|
|
7865
|
+
esac
|
|
7866
|
+
fi
|
|
7867
|
+
}
|
|
7868
|
+
complete -F _connectors connectors`);
|
|
7869
|
+
} else if (shell === "fish") {
|
|
7870
|
+
let script = `# Fish completions for connectors
|
|
7871
|
+
`;
|
|
7872
|
+
for (const cmd of commands) {
|
|
7873
|
+
script += `complete -c connectors -n "__fish_use_subcommand" -a "${cmd}"
|
|
7874
|
+
`;
|
|
7875
|
+
}
|
|
7876
|
+
script += `# Connector names for install/info/docs/remove/auth
|
|
7877
|
+
`;
|
|
7878
|
+
for (const name of connectorNames) {
|
|
7879
|
+
script += `complete -c connectors -n "__fish_seen_subcommand_from install add info docs remove rm auth" -a "${name}"
|
|
7880
|
+
`;
|
|
7881
|
+
}
|
|
7882
|
+
console.log(script);
|
|
7883
|
+
} else {
|
|
7884
|
+
console.error(`Unknown shell: ${shell}. Supported: bash, zsh, fish`);
|
|
7885
|
+
process.exit(1);
|
|
7886
|
+
}
|
|
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
|
+
});
|
|
7506
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.
|
|
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.
|
|
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": {
|
|
@@ -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",
|