@hasna/connectors 0.1.0 → 0.2.1

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 (110) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +19 -13
  3. package/bin/index.js +208 -73
  4. package/bin/mcp.js +24 -18
  5. package/bin/serve.js +94 -32
  6. package/connectors/connect-anthropic/package.json +2 -2
  7. package/connectors/connect-aws/package.json +2 -2
  8. package/connectors/connect-brandsight/package.json +2 -2
  9. package/connectors/connect-cloudflare/.env.example +0 -5
  10. package/connectors/connect-cloudflare/package.json +2 -2
  11. package/connectors/connect-discord/package.json +2 -2
  12. package/connectors/connect-docker/package.json +2 -2
  13. package/connectors/connect-e2b/package.json +2 -2
  14. package/connectors/connect-elevenlabs/package.json +2 -2
  15. package/connectors/connect-exa/package.json +2 -2
  16. package/connectors/connect-figma/package.json +2 -2
  17. package/connectors/connect-firecrawl/package.json +2 -2
  18. package/connectors/connect-github/package.json +2 -2
  19. package/connectors/connect-gmail/package.json +2 -2
  20. package/connectors/connect-google/package.json +2 -2
  21. package/connectors/connect-googlecalendar/package.json +2 -2
  22. package/connectors/connect-googlecloud/package.json +2 -2
  23. package/connectors/connect-googlecontacts/package.json +2 -2
  24. package/connectors/connect-googledocs/package.json +2 -2
  25. package/connectors/connect-googledrive/package.json +2 -2
  26. package/connectors/connect-googlegemini/package.json +2 -2
  27. package/connectors/connect-googlesheets/package.json +2 -2
  28. package/connectors/connect-googletasks/package.json +2 -2
  29. package/connectors/connect-hedra/package.json +2 -2
  30. package/connectors/connect-heygen/package.json +2 -2
  31. package/connectors/connect-huggingface/package.json +2 -2
  32. package/connectors/connect-icons8/package.json +2 -2
  33. package/connectors/connect-maropost/package.json +2 -2
  34. package/connectors/connect-mercury/package.json +2 -2
  35. package/connectors/connect-meta/package.json +2 -2
  36. package/connectors/connect-midjourney/package.json +2 -2
  37. package/connectors/connect-mistral/package.json +2 -2
  38. package/connectors/connect-mixpanel/package.json +2 -2
  39. package/connectors/connect-notion/.env.example +0 -5
  40. package/connectors/connect-notion/package.json +2 -2
  41. package/connectors/connect-openai/package.json +2 -2
  42. package/connectors/connect-openweathermap/package.json +2 -2
  43. package/connectors/connect-pandadoc/package.json +2 -2
  44. package/connectors/connect-quo/package.json +2 -2
  45. package/connectors/connect-reddit/package.json +2 -2
  46. package/connectors/connect-reducto/package.json +1 -1
  47. package/connectors/connect-resend/package.json +2 -2
  48. package/connectors/connect-revolut/package.json +2 -2
  49. package/connectors/connect-sedo/package.json +2 -2
  50. package/connectors/connect-sentry/package.json +2 -2
  51. package/connectors/connect-shadcn/package.json +2 -2
  52. package/connectors/connect-snap/package.json +2 -2
  53. package/connectors/connect-stabilityai/package.json +2 -2
  54. package/connectors/connect-stripe/package.json +2 -2
  55. package/connectors/connect-stripeatlas/package.json +2 -2
  56. package/connectors/connect-substack/package.json +2 -2
  57. package/connectors/connect-tiktok/package.json +2 -2
  58. package/connectors/connect-tinker/package.json +2 -2
  59. package/connectors/connect-twilio/package.json +4 -4
  60. package/connectors/connect-uspto/package.json +2 -2
  61. package/connectors/connect-x/package.json +2 -2
  62. package/connectors/connect-xads/package.json +2 -2
  63. package/connectors/connect-xai/package.json +2 -2
  64. package/connectors/connect-youtube/package.json +2 -2
  65. package/connectors/connect-zoom/package.json +2 -2
  66. package/dist/cli/cli.test.d.ts +1 -0
  67. package/dist/cli/components/App.d.ts +6 -0
  68. package/dist/cli/components/CategorySelect.d.ts +6 -0
  69. package/dist/cli/components/ConnectorSelect.d.ts +10 -0
  70. package/dist/cli/components/Header.d.ts +6 -0
  71. package/dist/cli/components/InstallProgress.d.ts +8 -0
  72. package/dist/cli/components/SearchView.d.ts +8 -0
  73. package/dist/cli/components/components.test.d.ts +1 -0
  74. package/dist/cli/index.d.ts +2 -0
  75. package/dist/index.d.ts +11 -0
  76. package/dist/index.js +11 -16
  77. package/dist/lib/installer.d.ts +55 -0
  78. package/dist/lib/installer.test.d.ts +1 -0
  79. package/dist/lib/registry.d.ts +18 -0
  80. package/dist/lib/registry.test.d.ts +1 -0
  81. package/dist/mcp/index.d.ts +2 -0
  82. package/dist/mcp/mcp.test.d.ts +1 -0
  83. package/dist/server/auth.d.ts +70 -0
  84. package/dist/server/dashboard.d.ts +5 -0
  85. package/dist/server/index.d.ts +6 -0
  86. package/dist/server/serve.d.ts +12 -0
  87. package/dist/server/server.test.d.ts +1 -0
  88. package/package.json +5 -4
  89. package/connectors/connect-browseruse/.env.example +0 -7
  90. package/connectors/connect-browseruse/.npmrc.example +0 -2
  91. package/connectors/connect-browseruse/AGENTS.md +0 -172
  92. package/connectors/connect-browseruse/CLAUDE.md +0 -172
  93. package/connectors/connect-browseruse/GEMINI.md +0 -172
  94. package/connectors/connect-browseruse/README.md +0 -153
  95. package/connectors/connect-browseruse/package.json +0 -52
  96. package/connectors/connect-browseruse/src/api/billing.ts +0 -32
  97. package/connectors/connect-browseruse/src/api/browsers.ts +0 -50
  98. package/connectors/connect-browseruse/src/api/client.ts +0 -168
  99. package/connectors/connect-browseruse/src/api/files.ts +0 -70
  100. package/connectors/connect-browseruse/src/api/index.ts +0 -95
  101. package/connectors/connect-browseruse/src/api/profiles.ts +0 -59
  102. package/connectors/connect-browseruse/src/api/sessions.ts +0 -88
  103. package/connectors/connect-browseruse/src/api/skills.ts +0 -194
  104. package/connectors/connect-browseruse/src/api/tasks.ts +0 -127
  105. package/connectors/connect-browseruse/src/cli/index.ts +0 -888
  106. package/connectors/connect-browseruse/src/index.ts +0 -35
  107. package/connectors/connect-browseruse/src/types/index.ts +0 -312
  108. package/connectors/connect-browseruse/src/utils/config.ts +0 -212
  109. package/connectors/connect-browseruse/src/utils/output.ts +0 -119
  110. package/connectors/connect-browseruse/tsconfig.json +0 -16
package/bin/index.js CHANGED
@@ -2060,13 +2060,6 @@ var init_registry = __esm(() => {
2060
2060
  category: "Developer Tools",
2061
2061
  tags: ["scraping", "web"]
2062
2062
  },
2063
- {
2064
- name: "browseruse",
2065
- displayName: "Browser Use",
2066
- description: "Browser automation for AI",
2067
- category: "Developer Tools",
2068
- tags: ["browser", "automation"]
2069
- },
2070
2063
  {
2071
2064
  name: "shadcn",
2072
2065
  displayName: "shadcn/ui",
@@ -2336,9 +2329,9 @@ var init_registry = __esm(() => {
2336
2329
  {
2337
2330
  name: "tinker",
2338
2331
  displayName: "Tinker",
2339
- description: "Internal tooling",
2340
- category: "Business Tools",
2341
- tags: ["internal", "tools"]
2332
+ description: "LLM fine-tuning and training API",
2333
+ category: "AI & ML",
2334
+ tags: ["ai", "llm", "fine-tuning"]
2342
2335
  },
2343
2336
  {
2344
2337
  name: "sedo",
@@ -4005,7 +3998,7 @@ var require_cli_spinners = __commonJS((exports, module) => {
4005
3998
  });
4006
3999
 
4007
4000
  // src/lib/installer.ts
4008
- import { existsSync as existsSync2, cpSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
4001
+ import { existsSync as existsSync2, cpSync, mkdirSync, readFileSync as readFileSync2, writeFileSync, readdirSync, statSync, rmSync } from "fs";
4009
4002
  import { join as join2, dirname as dirname2 } from "path";
4010
4003
  import { fileURLToPath as fileURLToPath2 } from "url";
4011
4004
  function resolveConnectorsDir() {
@@ -4023,6 +4016,13 @@ function getConnectorPath(name) {
4023
4016
  }
4024
4017
  function installConnector(name, options = {}) {
4025
4018
  const { targetDir = process.cwd(), overwrite = false } = options;
4019
+ if (!/^[a-z0-9-]+$/.test(name)) {
4020
+ return {
4021
+ connector: name,
4022
+ success: false,
4023
+ error: `Invalid connector name '${name}'`
4024
+ };
4025
+ }
4026
4026
  const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
4027
4027
  const sourcePath = getConnectorPath(name);
4028
4028
  const destDir = join2(targetDir, ".connectors");
@@ -4063,7 +4063,6 @@ function installConnector(name, options = {}) {
4063
4063
  }
4064
4064
  function updateConnectorsIndex(connectorsDir) {
4065
4065
  const indexPath = join2(connectorsDir, "index.ts");
4066
- const { readdirSync } = __require("fs");
4067
4066
  const connectors = readdirSync(connectorsDir).filter((f) => f.startsWith("connect-") && !f.includes("."));
4068
4067
  const exports = connectors.map((c) => {
4069
4068
  const name = c.replace("connect-", "");
@@ -4084,7 +4083,6 @@ function getInstalledConnectors(targetDir = process.cwd()) {
4084
4083
  if (!existsSync2(connectorsDir)) {
4085
4084
  return [];
4086
4085
  }
4087
- const { readdirSync, statSync } = __require("fs");
4088
4086
  return readdirSync(connectorsDir).filter((f) => {
4089
4087
  const fullPath = join2(connectorsDir, f);
4090
4088
  return f.startsWith("connect-") && statSync(fullPath).isDirectory();
@@ -4133,7 +4131,6 @@ function parseEnvVarsTable(section) {
4133
4131
  return vars;
4134
4132
  }
4135
4133
  function removeConnector(name, targetDir = process.cwd()) {
4136
- const { rmSync } = __require("fs");
4137
4134
  const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
4138
4135
  const connectorsDir = join2(targetDir, ".connectors");
4139
4136
  const connectorPath = join2(connectorsDir, connectorName);
@@ -4152,6 +4149,7 @@ var init_installer = __esm(() => {
4152
4149
 
4153
4150
  // src/server/auth.ts
4154
4151
  import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
4152
+ import { randomBytes } from "crypto";
4155
4153
  import { homedir } from "os";
4156
4154
  import { join as join3 } from "path";
4157
4155
  function getAuthType(name) {
@@ -4249,14 +4247,22 @@ function saveApiKey(name, key, field) {
4249
4247
  const profileFile = join3(configDir, "profiles", `${profile}.json`);
4250
4248
  const profileDir = join3(configDir, "profiles", profile);
4251
4249
  if (existsSync3(profileFile)) {
4252
- const config = JSON.parse(readFileSync3(profileFile, "utf-8"));
4250
+ let config = {};
4251
+ try {
4252
+ config = JSON.parse(readFileSync3(profileFile, "utf-8"));
4253
+ } catch {}
4253
4254
  config[keyField] = key;
4254
4255
  writeFileSync2(profileFile, JSON.stringify(config, null, 2));
4255
4256
  return;
4256
4257
  }
4257
4258
  if (existsSync3(profileDir)) {
4258
4259
  const configFile = join3(profileDir, "config.json");
4259
- const config = existsSync3(configFile) ? JSON.parse(readFileSync3(configFile, "utf-8")) : {};
4260
+ let config = {};
4261
+ if (existsSync3(configFile)) {
4262
+ try {
4263
+ config = JSON.parse(readFileSync3(configFile, "utf-8"));
4264
+ } catch {}
4265
+ }
4260
4266
  config[keyField] = key;
4261
4267
  writeFileSync2(configFile, JSON.stringify(config, null, 2));
4262
4268
  return;
@@ -4300,16 +4306,33 @@ function getOAuthStartUrl(name, redirectUri) {
4300
4306
  const scopes = GOOGLE_SCOPES[name];
4301
4307
  if (!scopes)
4302
4308
  return null;
4309
+ const state = randomBytes(32).toString("hex");
4310
+ oauthStateStore.set(state, { connector: name, createdAt: Date.now() });
4311
+ const tenMinutesAgo = Date.now() - 10 * 60 * 1000;
4312
+ for (const [key, val] of oauthStateStore) {
4313
+ if (val.createdAt < tenMinutesAgo)
4314
+ oauthStateStore.delete(key);
4315
+ }
4303
4316
  const params = new URLSearchParams({
4304
4317
  client_id: oauthConfig.clientId,
4305
4318
  redirect_uri: redirectUri,
4306
4319
  response_type: "code",
4307
4320
  scope: scopes,
4308
4321
  access_type: "offline",
4309
- prompt: "consent"
4322
+ prompt: "consent",
4323
+ state
4310
4324
  });
4311
4325
  return `${GOOGLE_AUTH_URL}?${params.toString()}`;
4312
4326
  }
4327
+ function validateOAuthState(state, expectedConnector) {
4328
+ if (!state)
4329
+ return false;
4330
+ const entry = oauthStateStore.get(state);
4331
+ if (!entry || entry.connector !== expectedConnector)
4332
+ return false;
4333
+ oauthStateStore.delete(state);
4334
+ return Date.now() - entry.createdAt < 10 * 60 * 1000;
4335
+ }
4313
4336
  async function exchangeOAuthCode(name, code, redirectUri) {
4314
4337
  const oauthConfig = getOAuthConfig(name);
4315
4338
  if (!oauthConfig.clientId || !oauthConfig.clientSecret) {
@@ -4324,7 +4347,8 @@ async function exchangeOAuthCode(name, code, redirectUri) {
4324
4347
  client_secret: oauthConfig.clientSecret,
4325
4348
  redirect_uri: redirectUri,
4326
4349
  grant_type: "authorization_code"
4327
- })
4350
+ }),
4351
+ signal: AbortSignal.timeout(FETCH_TIMEOUT)
4328
4352
  });
4329
4353
  if (!response.ok) {
4330
4354
  const error = await response.json().catch(() => ({ error: response.statusText }));
@@ -4366,7 +4390,8 @@ async function refreshOAuthToken(name) {
4366
4390
  client_secret: oauthConfig.clientSecret,
4367
4391
  refresh_token: currentTokens.refreshToken,
4368
4392
  grant_type: "refresh_token"
4369
- })
4393
+ }),
4394
+ signal: AbortSignal.timeout(FETCH_TIMEOUT)
4370
4395
  });
4371
4396
  if (!response.ok) {
4372
4397
  const error = await response.json().catch(() => ({ error: response.statusText }));
@@ -4383,9 +4408,10 @@ async function refreshOAuthToken(name) {
4383
4408
  saveOAuthTokens(name, tokens);
4384
4409
  return tokens;
4385
4410
  }
4386
- var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth", GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token", GOOGLE_SCOPES;
4411
+ var FETCH_TIMEOUT = 1e4, oauthStateStore, GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth", GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token", GOOGLE_SCOPES;
4387
4412
  var init_auth = __esm(() => {
4388
4413
  init_installer();
4414
+ oauthStateStore = new Map;
4389
4415
  GOOGLE_SCOPES = {
4390
4416
  gmail: [
4391
4417
  "https://www.googleapis.com/auth/gmail.readonly",
@@ -4453,18 +4479,25 @@ function resolveDashboardDir() {
4453
4479
  }
4454
4480
  return join4(process.cwd(), "dashboard", "dist");
4455
4481
  }
4456
- function json(data, status = 200) {
4482
+ function json(data, status = 200, port) {
4457
4483
  return new Response(JSON.stringify(data), {
4458
4484
  status,
4459
- headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }
4485
+ headers: {
4486
+ "Content-Type": "application/json",
4487
+ "Access-Control-Allow-Origin": port ? `http://localhost:${port}` : "*",
4488
+ ...SECURITY_HEADERS
4489
+ }
4460
4490
  });
4461
4491
  }
4462
4492
  function htmlResponse(content, status = 200) {
4463
4493
  return new Response(content, {
4464
4494
  status,
4465
- headers: { "Content-Type": "text/html; charset=utf-8" }
4495
+ headers: { "Content-Type": "text/html; charset=utf-8", ...SECURITY_HEADERS }
4466
4496
  });
4467
4497
  }
4498
+ function isValidConnectorName(name) {
4499
+ return /^[a-z0-9-]+$/.test(name);
4500
+ }
4468
4501
  function getAllConnectorsWithAuth() {
4469
4502
  const installed = new Set(getInstalledConnectors());
4470
4503
  return CONNECTORS.map((meta) => {
@@ -4506,7 +4539,16 @@ async function startServer(port, options) {
4506
4539
  const dashboardDir = resolveDashboardDir();
4507
4540
  const dashboardExists = existsSync4(dashboardDir);
4508
4541
  if (!dashboardExists) {
4509
- console.error(`Dashboard not built at ${dashboardDir}. Run: cd dashboard && bun run build`);
4542
+ console.error(`
4543
+ Dashboard not found at: ${dashboardDir}`);
4544
+ console.error(`Run this to build it:
4545
+ `);
4546
+ console.error(` cd dashboard && bun install && bun run build
4547
+ `);
4548
+ console.error(`Or from the project root:
4549
+ `);
4550
+ console.error(` bun run build:dashboard
4551
+ `);
4510
4552
  }
4511
4553
  const server = Bun.serve({
4512
4554
  port,
@@ -4515,14 +4557,16 @@ async function startServer(port, options) {
4515
4557
  const path = url2.pathname;
4516
4558
  const method = req.method;
4517
4559
  if (path === "/api/connectors" && method === "GET") {
4518
- return json(getAllConnectorsWithAuth());
4560
+ return json(getAllConnectorsWithAuth(), 200, port);
4519
4561
  }
4520
4562
  const singleMatch = path.match(/^\/api\/connectors\/([^/]+)$/);
4521
4563
  if (singleMatch && method === "GET") {
4522
4564
  const name = singleMatch[1];
4565
+ if (!isValidConnectorName(name))
4566
+ return json({ error: "Invalid connector name" }, 400, port);
4523
4567
  const meta = getConnector(name);
4524
4568
  if (!meta)
4525
- return json({ error: `Connector '${name}' not found` }, 404);
4569
+ return json({ error: `Connector '${name}' not found` }, 404, port);
4526
4570
  const auth = getAuthStatus(name);
4527
4571
  const docs = getConnectorDocs(name);
4528
4572
  return json({
@@ -4533,29 +4577,36 @@ async function startServer(port, options) {
4533
4577
  version: meta.version,
4534
4578
  auth,
4535
4579
  overview: docs?.overview || null
4536
- });
4580
+ }, 200, port);
4537
4581
  }
4538
4582
  const keyMatch = path.match(/^\/api\/connectors\/([^/]+)\/key$/);
4539
4583
  if (keyMatch && method === "POST") {
4540
4584
  const name = keyMatch[1];
4585
+ if (!isValidConnectorName(name))
4586
+ return json({ error: "Invalid connector name" }, 400, port);
4541
4587
  try {
4588
+ const contentLength = parseInt(req.headers.get("content-length") || "0", 10);
4589
+ if (contentLength > MAX_BODY_SIZE)
4590
+ return json({ error: "Request body too large" }, 413, port);
4542
4591
  const body = await req.json();
4543
4592
  if (!body.key)
4544
- return json({ error: "Missing 'key' in request body" }, 400);
4593
+ return json({ error: "Missing 'key' in request body" }, 400, port);
4545
4594
  saveApiKey(name, body.key, body.field);
4546
- return json({ success: true });
4595
+ return json({ success: true }, 200, port);
4547
4596
  } catch (e) {
4548
- return json({ error: e instanceof Error ? e.message : "Failed to save key" }, 500);
4597
+ return json({ error: e instanceof Error ? e.message : "Failed to save key" }, 500, port);
4549
4598
  }
4550
4599
  }
4551
4600
  const refreshMatch = path.match(/^\/api\/connectors\/([^/]+)\/refresh$/);
4552
4601
  if (refreshMatch && method === "POST") {
4553
4602
  const name = refreshMatch[1];
4603
+ if (!isValidConnectorName(name))
4604
+ return json({ error: "Invalid connector name" }, 400, port);
4554
4605
  try {
4555
4606
  const tokens = await refreshOAuthToken(name);
4556
- return json({ success: true, expiresAt: tokens.expiresAt });
4607
+ return json({ success: true, expiresAt: tokens.expiresAt }, 200, port);
4557
4608
  } catch (e) {
4558
- return json({ success: false, error: e instanceof Error ? e.message : "Failed to refresh" }, 500);
4609
+ return json({ success: false, error: e instanceof Error ? e.message : "Failed to refresh" }, 500, port);
4559
4610
  }
4560
4611
  }
4561
4612
  const oauthStartMatch = path.match(/^\/oauth\/([^/]+)\/start$/);
@@ -4573,9 +4624,13 @@ async function startServer(port, options) {
4573
4624
  const name = oauthCallbackMatch[1];
4574
4625
  const code = url2.searchParams.get("code");
4575
4626
  const error = url2.searchParams.get("error");
4627
+ const state = url2.searchParams.get("state");
4576
4628
  if (error) {
4577
4629
  return htmlResponse(errorPage("Authentication Failed", error, "You can close this window."));
4578
4630
  }
4631
+ if (!validateOAuthState(state, name)) {
4632
+ return htmlResponse(errorPage("Invalid State", "CSRF validation failed. The OAuth state parameter is missing or invalid.", "Please try again from the dashboard."));
4633
+ }
4579
4634
  if (!code) {
4580
4635
  return htmlResponse(errorPage("Missing Authorization Code", "No code received from the OAuth provider.", "You can close this window and try again."));
4581
4636
  }
@@ -4589,7 +4644,7 @@ async function startServer(port, options) {
4589
4644
  <p style="color:#666;font-size:14px;">You can close this window and return to the dashboard.</p>
4590
4645
  <script>
4591
4646
  if (window.opener) {
4592
- window.opener.postMessage({ type: 'oauth-complete', connector: '${name}' }, '*');
4647
+ window.opener.postMessage({ type: 'oauth-complete', connector: '${name}' }, 'http://localhost:${port}');
4593
4648
  }
4594
4649
  </script>
4595
4650
  </div>
@@ -4601,7 +4656,7 @@ async function startServer(port, options) {
4601
4656
  if (method === "OPTIONS") {
4602
4657
  return new Response(null, {
4603
4658
  headers: {
4604
- "Access-Control-Allow-Origin": "*",
4659
+ "Access-Control-Allow-Origin": `http://localhost:${port}`,
4605
4660
  "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
4606
4661
  "Access-Control-Allow-Headers": "Content-Type"
4607
4662
  }
@@ -4619,9 +4674,15 @@ async function startServer(port, options) {
4619
4674
  if (res)
4620
4675
  return res;
4621
4676
  }
4622
- return json({ error: "Not found" }, 404);
4677
+ return json({ error: "Not found" }, 404, port);
4623
4678
  }
4624
4679
  });
4680
+ const shutdown = () => {
4681
+ server.stop();
4682
+ process.exit(0);
4683
+ };
4684
+ process.on("SIGINT", shutdown);
4685
+ process.on("SIGTERM", shutdown);
4625
4686
  const url = `http://localhost:${port}`;
4626
4687
  console.log(`Connectors Dashboard running at ${url}`);
4627
4688
  if (shouldOpen) {
@@ -4632,7 +4693,7 @@ async function startServer(port, options) {
4632
4693
  } catch {}
4633
4694
  }
4634
4695
  }
4635
- var MIME_TYPES;
4696
+ var MIME_TYPES, SECURITY_HEADERS, MAX_BODY_SIZE;
4636
4697
  var init_serve = __esm(() => {
4637
4698
  init_registry();
4638
4699
  init_installer();
@@ -4649,6 +4710,11 @@ var init_serve = __esm(() => {
4649
4710
  ".woff": "font/woff",
4650
4711
  ".woff2": "font/woff2"
4651
4712
  };
4713
+ SECURITY_HEADERS = {
4714
+ "X-Content-Type-Options": "nosniff",
4715
+ "X-Frame-Options": "DENY"
4716
+ };
4717
+ MAX_BODY_SIZE = 1024 * 1024;
4652
4718
  });
4653
4719
 
4654
4720
  // src/cli/index.tsx
@@ -5123,10 +5189,12 @@ function CategorySelect({ onSelect, onBack }) {
5123
5189
  return /* @__PURE__ */ jsxDEV2(Box4, {
5124
5190
  flexDirection: "column",
5125
5191
  children: [
5126
- /* @__PURE__ */ jsxDEV2(Text4, {
5127
- bold: true,
5192
+ /* @__PURE__ */ jsxDEV2(Box4, {
5128
5193
  marginBottom: 1,
5129
- children: "Select a category:"
5194
+ children: /* @__PURE__ */ jsxDEV2(Text4, {
5195
+ bold: true,
5196
+ children: "Select a category:"
5197
+ }, undefined, false, undefined, this)
5130
5198
  }, undefined, false, undefined, this),
5131
5199
  /* @__PURE__ */ jsxDEV2(SelectInput_default, {
5132
5200
  items,
@@ -5164,7 +5232,9 @@ function ConnectorSelect({
5164
5232
  return cursor - half;
5165
5233
  }, [cursor, totalItems]);
5166
5234
  useInput2((input, key) => {
5167
- if (key.upArrow) {
5235
+ if (key.escape) {
5236
+ onBack();
5237
+ } else if (key.upArrow) {
5168
5238
  setCursor((c) => c > 0 ? c - 1 : totalItems - 1);
5169
5239
  } else if (key.downArrow) {
5170
5240
  setCursor((c) => c < totalItems - 1 ? c + 1 : 0);
@@ -5179,6 +5249,19 @@ function ConnectorSelect({
5179
5249
  }
5180
5250
  } else if (input === " " && cursor > 0 && cursor < totalItems - 1) {
5181
5251
  onToggle(connectors[cursor - 1].name);
5252
+ } else if (input === "i" && selected.size > 0) {
5253
+ onConfirm();
5254
+ } else if (input === "a") {
5255
+ const allSelected = connectors.every((c) => selected.has(c.name));
5256
+ for (const c of connectors) {
5257
+ if (allSelected) {
5258
+ if (selected.has(c.name))
5259
+ onToggle(c.name);
5260
+ } else {
5261
+ if (!selected.has(c.name))
5262
+ onToggle(c.name);
5263
+ }
5264
+ }
5182
5265
  }
5183
5266
  });
5184
5267
  const visibleStart = scrollOffset;
@@ -5186,10 +5269,12 @@ function ConnectorSelect({
5186
5269
  return /* @__PURE__ */ jsxDEV3(Box5, {
5187
5270
  flexDirection: "column",
5188
5271
  children: [
5189
- /* @__PURE__ */ jsxDEV3(Text5, {
5190
- bold: true,
5272
+ /* @__PURE__ */ jsxDEV3(Box5, {
5191
5273
  marginBottom: 1,
5192
- children: "Select connectors to install:"
5274
+ children: /* @__PURE__ */ jsxDEV3(Text5, {
5275
+ bold: true,
5276
+ children: "Select connectors to install:"
5277
+ }, undefined, false, undefined, this)
5193
5278
  }, undefined, false, undefined, this),
5194
5279
  /* @__PURE__ */ jsxDEV3(Box5, {
5195
5280
  children: [
@@ -5333,7 +5418,7 @@ function ConnectorSelect({
5333
5418
  marginTop: 1,
5334
5419
  children: /* @__PURE__ */ jsxDEV3(Text5, {
5335
5420
  dimColor: true,
5336
- children: "\u2191\u2193 navigate space toggle enter confirm esc back"
5421
+ children: "\u2191\u2193 navigate space/enter toggle a select all i install esc back"
5337
5422
  }, undefined, false, undefined, this)
5338
5423
  }, undefined, false, undefined, this)
5339
5424
  ]
@@ -5513,6 +5598,19 @@ function SearchView({
5513
5598
  if (connectorIdx < results.length) {
5514
5599
  onToggle(results[connectorIdx].name);
5515
5600
  }
5601
+ } else if (input === "i" && selected.size > 0) {
5602
+ onConfirm();
5603
+ } else if (input === "a" && mode === "select") {
5604
+ const allSelected = results.every((c) => selected.has(c.name));
5605
+ for (const c of results) {
5606
+ if (allSelected) {
5607
+ if (selected.has(c.name))
5608
+ onToggle(c.name);
5609
+ } else {
5610
+ if (!selected.has(c.name))
5611
+ onToggle(c.name);
5612
+ }
5613
+ }
5516
5614
  }
5517
5615
  });
5518
5616
  const visibleStart = scrollOffset;
@@ -5550,17 +5648,19 @@ function SearchView({
5550
5648
  results.length > 0 && /* @__PURE__ */ jsxDEV4(Box6, {
5551
5649
  flexDirection: "column",
5552
5650
  children: [
5553
- /* @__PURE__ */ jsxDEV4(Text7, {
5554
- dimColor: true,
5651
+ /* @__PURE__ */ jsxDEV4(Box6, {
5555
5652
  marginBottom: 1,
5556
- children: [
5557
- "Found ",
5558
- results.length,
5559
- " connector(s)",
5560
- mode === "search" ? " \u2014 press \u2193 to select" : "",
5561
- ":"
5562
- ]
5563
- }, undefined, true, undefined, this),
5653
+ children: /* @__PURE__ */ jsxDEV4(Text7, {
5654
+ dimColor: true,
5655
+ children: [
5656
+ "Found ",
5657
+ results.length,
5658
+ " connector(s)",
5659
+ mode === "search" ? " \u2014 press \u2193 to select" : "",
5660
+ ":"
5661
+ ]
5662
+ }, undefined, true, undefined, this)
5663
+ }, undefined, false, undefined, this),
5564
5664
  /* @__PURE__ */ jsxDEV4(Box6, {
5565
5665
  children: [
5566
5666
  /* @__PURE__ */ jsxDEV4(Box6, {
@@ -5706,7 +5806,7 @@ function SearchView({
5706
5806
  marginTop: 1,
5707
5807
  children: /* @__PURE__ */ jsxDEV4(Text7, {
5708
5808
  dimColor: true,
5709
- children: mode === "search" ? "type to search \u2193 select results esc back" : "\u2191\u2193 navigate space toggle enter confirm esc search"
5809
+ children: mode === "search" ? "type to search \u2193 select results esc back" : "\u2191\u2193 navigate space/enter toggle a select all i install esc search"
5710
5810
  }, undefined, false, undefined, this)
5711
5811
  }, undefined, false, undefined, this)
5712
5812
  ]
@@ -5862,9 +5962,14 @@ function App({ initialConnectors, overwrite = false }) {
5862
5962
  if (key.escape) {
5863
5963
  if (view === "main") {
5864
5964
  exit();
5965
+ } else if (view === "browse" || view === "search") {
5966
+ setView("main");
5967
+ } else if (view === "connectors") {
5968
+ setCategory(null);
5969
+ setView("browse");
5865
5970
  }
5866
5971
  }
5867
- if (input === "q") {
5972
+ if (input === "q" && view !== "search") {
5868
5973
  exit();
5869
5974
  }
5870
5975
  });
@@ -5909,9 +6014,11 @@ function App({ initialConnectors, overwrite = false }) {
5909
6014
  view === "main" && /* @__PURE__ */ jsxDEV6(Box8, {
5910
6015
  flexDirection: "column",
5911
6016
  children: [
5912
- /* @__PURE__ */ jsxDEV6(Text10, {
6017
+ /* @__PURE__ */ jsxDEV6(Box8, {
5913
6018
  marginBottom: 1,
5914
- children: "What would you like to do?"
6019
+ children: /* @__PURE__ */ jsxDEV6(Text10, {
6020
+ children: "What would you like to do?"
6021
+ }, undefined, false, undefined, this)
5915
6022
  }, undefined, false, undefined, this),
5916
6023
  /* @__PURE__ */ jsxDEV6(SelectInput_default, {
5917
6024
  items: mainMenuItems,
@@ -5957,11 +6064,13 @@ function App({ initialConnectors, overwrite = false }) {
5957
6064
  view === "done" && /* @__PURE__ */ jsxDEV6(Box8, {
5958
6065
  flexDirection: "column",
5959
6066
  children: [
5960
- /* @__PURE__ */ jsxDEV6(Text10, {
5961
- bold: true,
5962
- color: "green",
6067
+ /* @__PURE__ */ jsxDEV6(Box8, {
5963
6068
  marginBottom: 1,
5964
- children: "Installation complete!"
6069
+ children: /* @__PURE__ */ jsxDEV6(Text10, {
6070
+ bold: true,
6071
+ color: "green",
6072
+ children: "Installation complete!"
6073
+ }, undefined, false, undefined, this)
5965
6074
  }, undefined, false, undefined, this),
5966
6075
  results.filter((r) => r.success).length > 0 && /* @__PURE__ */ jsxDEV6(Box8, {
5967
6076
  flexDirection: "column",
@@ -6049,7 +6158,7 @@ import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
6049
6158
  loadConnectorVersions();
6050
6159
  var isTTY = process.stdout.isTTY ?? false;
6051
6160
  var program2 = new Command;
6052
- program2.name("connectors").description("Install API connectors for your project").version("0.1.0");
6161
+ program2.name("connectors").description("Install API connectors for your project").version("0.2.1");
6053
6162
  program2.command("interactive", { isDefault: true }).alias("i").description("Interactive connector browser").action(() => {
6054
6163
  if (!isTTY) {
6055
6164
  console.log(`Non-interactive environment detected. Use a subcommand:
@@ -6085,15 +6194,23 @@ program2.command("install").alias("add").argument("[connectors...]", "Connectors
6085
6194
  console.log(chalk2.bold(`
6086
6195
  Installing connectors...
6087
6196
  `));
6197
+ const succeeded = [];
6088
6198
  for (const result of results) {
6089
6199
  if (result.success) {
6090
6200
  console.log(chalk2.green(`\u2713 ${result.connector}`));
6201
+ succeeded.push(result.connector);
6091
6202
  } else {
6092
6203
  console.log(chalk2.red(`\u2717 ${result.connector}: ${result.error}`));
6093
6204
  }
6094
6205
  }
6095
- console.log(chalk2.dim(`
6096
- Connectors installed to .connectors/`));
6206
+ if (succeeded.length > 0) {
6207
+ console.log(chalk2.bold(`
6208
+ Next steps:`));
6209
+ const importNames = succeeded.join(", ");
6210
+ console.log(chalk2.dim(` 1. Import: `) + `import { ${importNames} } from './.connectors'`);
6211
+ console.log(chalk2.dim(` 2. Set key: `) + `connectors docs ${succeeded[0]}` + chalk2.dim(` (see env vars)`));
6212
+ console.log(chalk2.dim(` 3. Explore: `) + `connectors serve` + chalk2.dim(` (dashboard for auth management)`));
6213
+ }
6097
6214
  process.exit(results.every((r) => r.success) ? 0 : 1);
6098
6215
  });
6099
6216
  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) => {
@@ -6319,7 +6436,7 @@ Categories:
6319
6436
  console.log(` ${category} (${count})`);
6320
6437
  }
6321
6438
  });
6322
- program2.command("serve").alias("dashboard").option("-p, --port <port>", "Port to run the dashboard on", "19426").option("--open", "Open dashboard in browser (default)", true).option("--no-open", "Don't open browser automatically").description("Start local dashboard for connector auth management").action(async (options) => {
6439
+ program2.command("serve").alias("dashboard").alias("open").option("-p, --port <port>", "Port to run the dashboard on", "19426").option("--open", "Open dashboard in browser (default)", true).option("--no-open", "Don't open browser automatically").description("Start local dashboard for connector auth management").action(async (options) => {
6323
6440
  const port = parseInt(options.port, 10);
6324
6441
  if (isNaN(port) || port < 1 || port > 65535) {
6325
6442
  console.log(chalk2.red("Invalid port number"));
@@ -6332,14 +6449,32 @@ Starting Connectors Dashboard...
6332
6449
  const { startServer: startServer2 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
6333
6450
  await startServer2(port, { open: options.open });
6334
6451
  });
6335
- program2.command("open").option("-p, --port <port>", "Port to run the dashboard on", "19426").description("Open the connectors dashboard in your browser").action(async (options) => {
6336
- const port = parseInt(options.port, 10);
6337
- if (isNaN(port) || port < 1 || port > 65535) {
6338
- console.log(chalk2.red("Invalid port number"));
6339
- process.exit(1);
6452
+ program2.command("update").description("Update all installed connectors to the latest version from the package").option("--json", "Output as JSON", false).action((options) => {
6453
+ const installed = getInstalledConnectors();
6454
+ if (installed.length === 0) {
6455
+ if (options.json) {
6456
+ console.log(JSON.stringify({ updated: [] }));
6457
+ } else {
6458
+ console.log(chalk2.dim("No connectors installed. Run: connectors install <name>"));
6459
+ }
6340
6460
  return;
6341
6461
  }
6342
- const { startServer: startServer2 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
6343
- await startServer2(port, { open: true });
6462
+ const results = installed.map((name) => installConnector(name, { overwrite: true }));
6463
+ if (options.json) {
6464
+ console.log(JSON.stringify(results, null, 2));
6465
+ process.exit(results.every((r) => r.success) ? 0 : 1);
6466
+ return;
6467
+ }
6468
+ console.log(chalk2.bold(`
6469
+ Updating ${installed.length} connector(s)...
6470
+ `));
6471
+ for (const result of results) {
6472
+ if (result.success) {
6473
+ console.log(chalk2.green(`\u2713 ${result.connector}`));
6474
+ } else {
6475
+ console.log(chalk2.red(`\u2717 ${result.connector}: ${result.error}`));
6476
+ }
6477
+ }
6478
+ process.exit(results.every((r) => r.success) ? 0 : 1);
6344
6479
  });
6345
6480
  program2.parse();