@hasna/connectors 1.2.1 → 1.3.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.
package/bin/index.js CHANGED
@@ -4,6 +4,7 @@ var __create = Object.create;
4
4
  var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
9
  var __toESM = (mod, isNodeMode, target) => {
9
10
  target = mod != null ? __create(__getProtoOf(mod)) : {};
@@ -16,6 +17,20 @@ var __toESM = (mod, isNodeMode, target) => {
16
17
  });
17
18
  return to;
18
19
  };
20
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
21
+ var __toCommonJS = (from) => {
22
+ var entry = __moduleCache.get(from), desc;
23
+ if (entry)
24
+ return entry;
25
+ entry = __defProp({}, "__esModule", { value: true });
26
+ if (from && typeof from === "object" || typeof from === "function")
27
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
28
+ get: () => from[key],
29
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
30
+ }));
31
+ __moduleCache.set(from, entry);
32
+ return entry;
33
+ };
19
34
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
20
35
  var __export = (target, all) => {
21
36
  for (var name in all)
@@ -2099,6 +2114,22 @@ function migrate(db) {
2099
2114
  created_at TEXT NOT NULL
2100
2115
  )
2101
2116
  `);
2117
+ db.run(`
2118
+ CREATE TABLE IF NOT EXISTS connector_usage (
2119
+ id TEXT PRIMARY KEY,
2120
+ connector TEXT NOT NULL,
2121
+ action TEXT NOT NULL,
2122
+ agent_id TEXT,
2123
+ timestamp TEXT NOT NULL
2124
+ )
2125
+ `);
2126
+ db.run(`CREATE INDEX IF NOT EXISTS idx_usage_connector ON connector_usage(connector, timestamp DESC)`);
2127
+ db.run(`
2128
+ CREATE TABLE IF NOT EXISTS connector_promotions (
2129
+ connector TEXT UNIQUE NOT NULL,
2130
+ promoted_at TEXT NOT NULL
2131
+ )
2132
+ `);
2102
2133
  }
2103
2134
  var DB_DIR, DB_PATH, _db = null;
2104
2135
  var init_database = __esm(() => {
@@ -2424,6 +2455,95 @@ var init_workflow_runner = __esm(() => {
2424
2455
  init_strip();
2425
2456
  });
2426
2457
 
2458
+ // src/lib/fuzzy.ts
2459
+ function levenshtein(a, b) {
2460
+ const m = a.length;
2461
+ const n = b.length;
2462
+ if (m === 0)
2463
+ return n;
2464
+ if (n === 0)
2465
+ return m;
2466
+ let prev = new Array(n + 1);
2467
+ let curr = new Array(n + 1);
2468
+ for (let j = 0;j <= n; j++)
2469
+ prev[j] = j;
2470
+ for (let i = 1;i <= m; i++) {
2471
+ curr[0] = i;
2472
+ for (let j = 1;j <= n; j++) {
2473
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
2474
+ curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
2475
+ }
2476
+ [prev, curr] = [curr, prev];
2477
+ }
2478
+ return prev[n];
2479
+ }
2480
+ function bestFuzzyScore(token, candidates, maxDistance = 2) {
2481
+ if (token.length < 3)
2482
+ return 0;
2483
+ let bestDist = maxDistance + 1;
2484
+ for (const c of candidates) {
2485
+ if (Math.abs(token.length - c.length) > maxDistance)
2486
+ continue;
2487
+ const d = levenshtein(token, c);
2488
+ if (d < bestDist)
2489
+ bestDist = d;
2490
+ if (d === 0)
2491
+ return maxDistance + 1;
2492
+ }
2493
+ if (bestDist > maxDistance)
2494
+ return 0;
2495
+ return maxDistance - bestDist + 1;
2496
+ }
2497
+
2498
+ // src/lib/synonyms.ts
2499
+ function expandQuery(tokens) {
2500
+ const synonyms = new Set;
2501
+ for (const token of tokens) {
2502
+ const matches = SYNONYM_MAP[token];
2503
+ if (matches) {
2504
+ for (const syn of matches) {
2505
+ if (!tokens.includes(syn))
2506
+ synonyms.add(syn);
2507
+ }
2508
+ }
2509
+ for (const [key, values] of Object.entries(SYNONYM_MAP)) {
2510
+ if (values.includes(token) && !tokens.includes(key)) {
2511
+ synonyms.add(key);
2512
+ }
2513
+ }
2514
+ }
2515
+ return { original: tokens, expanded: [...synonyms] };
2516
+ }
2517
+ var SYNONYM_MAP;
2518
+ var init_synonyms = __esm(() => {
2519
+ SYNONYM_MAP = {
2520
+ email: ["smtp", "mail", "inbox", "resend", "ses"],
2521
+ chat: ["messaging", "im", "slack", "discord", "teams"],
2522
+ sms: ["text", "twilio", "messaging"],
2523
+ payment: ["billing", "invoicing", "commerce", "checkout", "stripe"],
2524
+ payments: ["billing", "invoicing", "commerce", "checkout", "stripe"],
2525
+ ecommerce: ["shop", "store", "commerce", "shopify"],
2526
+ finance: ["banking", "accounting", "invoicing"],
2527
+ crypto: ["blockchain", "web3", "wallet"],
2528
+ ai: ["llm", "ml", "model", "gpt", "claude", "anthropic", "openai"],
2529
+ llm: ["ai", "model", "gpt", "claude"],
2530
+ auth: ["oauth", "sso", "login", "identity", "authentication"],
2531
+ database: ["db", "sql", "nosql", "postgres", "mongo", "supabase"],
2532
+ deploy: ["hosting", "infrastructure", "ci", "cd", "vercel"],
2533
+ storage: ["files", "drive", "s3", "bucket", "upload"],
2534
+ cloud: ["aws", "gcp", "azure", "infrastructure"],
2535
+ api: ["rest", "graphql", "endpoint", "webhook"],
2536
+ monitoring: ["logs", "observability", "alerting", "datadog", "sentry"],
2537
+ ci: ["cd", "deploy", "pipeline", "github", "actions"],
2538
+ crm: ["sales", "leads", "contacts", "hubspot", "salesforce"],
2539
+ analytics: ["data", "metrics", "tracking", "mixpanel", "amplitude"],
2540
+ project: ["task", "issue", "board", "jira", "linear", "asana"],
2541
+ docs: ["documentation", "wiki", "notion", "confluence"],
2542
+ design: ["figma", "sketch", "ui", "ux"],
2543
+ security: ["auth", "encryption", "compliance", "vault"]
2544
+ };
2545
+ });
2546
+
2427
2547
  // src/lib/registry.ts
2428
2548
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
2429
2549
  import { join as join3, dirname } from "path";
@@ -2431,9 +2551,152 @@ import { fileURLToPath } from "url";
2431
2551
  function getConnectorsByCategory(category) {
2432
2552
  return CONNECTORS.filter((c) => c.category === category);
2433
2553
  }
2434
- function searchConnectors(query) {
2435
- const q = query.toLowerCase();
2436
- return CONNECTORS.filter((c) => c.name.toLowerCase().includes(q) || c.displayName.toLowerCase().includes(q) || c.description.toLowerCase().includes(q) || c.tags.some((t) => t.includes(q)));
2554
+ function searchConnectors(query, context) {
2555
+ const tokens = query.toLowerCase().trim().split(/\s+/).filter(Boolean);
2556
+ if (tokens.length === 0)
2557
+ return [];
2558
+ const limit = context?.limit ?? 20;
2559
+ const installed = new Set(context?.installed ?? []);
2560
+ const promoted = new Set(context?.promoted ?? []);
2561
+ const usage = context?.usage ?? new Map;
2562
+ const results = [];
2563
+ for (const c of CONNECTORS) {
2564
+ const nameLow = c.name.toLowerCase();
2565
+ const displayLow = c.displayName.toLowerCase();
2566
+ const descLow = c.description.toLowerCase();
2567
+ const tagsLow = c.tags.map((t) => t.toLowerCase());
2568
+ let score = 0;
2569
+ const matchReasons = [];
2570
+ let allTokensMatch = true;
2571
+ for (const token of tokens) {
2572
+ let tokenMatched = false;
2573
+ if (nameLow === token) {
2574
+ score += 100;
2575
+ matchReasons.push(`name="${token}"`);
2576
+ tokenMatched = true;
2577
+ } else if (nameLow.includes(token)) {
2578
+ score += 10;
2579
+ matchReasons.push(`name~${token}`);
2580
+ tokenMatched = true;
2581
+ }
2582
+ if (tagsLow.includes(token)) {
2583
+ score += 8;
2584
+ if (!tokenMatched)
2585
+ matchReasons.push(`tag="${token}"`);
2586
+ tokenMatched = true;
2587
+ } else if (tagsLow.some((t) => t.includes(token))) {
2588
+ score += 5;
2589
+ if (!tokenMatched)
2590
+ matchReasons.push(`tag~${token}`);
2591
+ tokenMatched = true;
2592
+ }
2593
+ if (displayLow.includes(token)) {
2594
+ score += 3;
2595
+ if (!tokenMatched)
2596
+ matchReasons.push(`display~${token}`);
2597
+ tokenMatched = true;
2598
+ }
2599
+ if (descLow.includes(token)) {
2600
+ score += 1;
2601
+ if (!tokenMatched)
2602
+ matchReasons.push(`desc~${token}`);
2603
+ tokenMatched = true;
2604
+ }
2605
+ if (!tokenMatched && token.length >= 3) {
2606
+ const nameFuzzy = bestFuzzyScore(token, [nameLow], 1);
2607
+ if (nameFuzzy > 0) {
2608
+ score += nameFuzzy * 6;
2609
+ matchReasons.push(`fuzzy:name\u2248${token}`);
2610
+ tokenMatched = true;
2611
+ }
2612
+ if (!tokenMatched) {
2613
+ const tagFuzzy = bestFuzzyScore(token, tagsLow, 2);
2614
+ if (tagFuzzy > 0) {
2615
+ score += tagFuzzy * 3;
2616
+ matchReasons.push(`fuzzy:tag\u2248${token}`);
2617
+ tokenMatched = true;
2618
+ }
2619
+ }
2620
+ if (!tokenMatched) {
2621
+ const displayFuzzy = bestFuzzyScore(token, [displayLow], 2);
2622
+ if (displayFuzzy > 0) {
2623
+ score += displayFuzzy * 2;
2624
+ matchReasons.push(`fuzzy:display\u2248${token}`);
2625
+ tokenMatched = true;
2626
+ }
2627
+ }
2628
+ }
2629
+ if (!tokenMatched) {
2630
+ allTokensMatch = false;
2631
+ break;
2632
+ }
2633
+ }
2634
+ if (!allTokensMatch)
2635
+ continue;
2636
+ const badges = [];
2637
+ if (installed.has(c.name)) {
2638
+ score += 50;
2639
+ badges.push("installed");
2640
+ }
2641
+ if (promoted.has(c.name)) {
2642
+ score += 30;
2643
+ badges.push("promoted");
2644
+ }
2645
+ const usageCount = usage.get(c.name) ?? 0;
2646
+ if (usageCount > 0) {
2647
+ score += Math.min(usageCount * 2, 40);
2648
+ if (usageCount >= 5)
2649
+ badges.push("hot");
2650
+ }
2651
+ results.push({ ...c, score, matchReasons, badges });
2652
+ }
2653
+ const matchedNames = new Set(results.map((r) => r.name));
2654
+ if (results.length < limit) {
2655
+ const { expanded } = expandQuery(tokens);
2656
+ if (expanded.length > 0) {
2657
+ for (const c of CONNECTORS) {
2658
+ if (matchedNames.has(c.name))
2659
+ continue;
2660
+ const nameLow2 = c.name.toLowerCase();
2661
+ const tagsLow2 = c.tags.map((t) => t.toLowerCase());
2662
+ const descLow2 = c.description.toLowerCase();
2663
+ let synScore = 0;
2664
+ const synReasons = [];
2665
+ for (const syn of expanded) {
2666
+ if (nameLow2.includes(syn)) {
2667
+ synScore += 2;
2668
+ synReasons.push(`syn:name~${syn}`);
2669
+ } else if (tagsLow2.some((t) => t.includes(syn))) {
2670
+ synScore += 1;
2671
+ synReasons.push(`syn:tag~${syn}`);
2672
+ } else if (descLow2.includes(syn)) {
2673
+ synScore += 1;
2674
+ synReasons.push(`syn:desc~${syn}`);
2675
+ }
2676
+ }
2677
+ if (synScore > 0) {
2678
+ const badges = [];
2679
+ if (installed.has(c.name)) {
2680
+ synScore += 50;
2681
+ badges.push("installed");
2682
+ }
2683
+ if (promoted.has(c.name)) {
2684
+ synScore += 30;
2685
+ badges.push("promoted");
2686
+ }
2687
+ const usageCount = usage.get(c.name) ?? 0;
2688
+ if (usageCount > 0) {
2689
+ synScore += Math.min(usageCount * 2, 40);
2690
+ if (usageCount >= 5)
2691
+ badges.push("hot");
2692
+ }
2693
+ results.push({ ...c, score: synScore, matchReasons: synReasons, badges });
2694
+ }
2695
+ }
2696
+ }
2697
+ }
2698
+ results.sort((a, b) => b.score - a.score);
2699
+ return results.slice(0, limit);
2437
2700
  }
2438
2701
  function getConnector(name) {
2439
2702
  return CONNECTORS.find((c) => c.name === name);
@@ -2462,6 +2725,7 @@ function loadConnectorVersions() {
2462
2725
  }
2463
2726
  var CATEGORIES, CONNECTORS, versionsLoaded = false;
2464
2727
  var init_registry = __esm(() => {
2728
+ init_synonyms();
2465
2729
  CATEGORIES = [
2466
2730
  "AI & ML",
2467
2731
  "Developer Tools",
@@ -10318,6 +10582,16 @@ function loadTokens(name) {
10318
10582
  return null;
10319
10583
  }
10320
10584
  }
10585
+ const profileConfig = loadProfileConfig(name);
10586
+ if (profileConfig.refreshToken || profileConfig.accessToken) {
10587
+ return {
10588
+ accessToken: profileConfig.accessToken,
10589
+ refreshToken: profileConfig.refreshToken,
10590
+ expiresAt: profileConfig.expiresAt,
10591
+ tokenType: profileConfig.tokenType,
10592
+ scope: profileConfig.scope
10593
+ };
10594
+ }
10321
10595
  return null;
10322
10596
  }
10323
10597
  function getAuthStatus(name) {
@@ -10643,6 +10917,75 @@ var init_auth = __esm(() => {
10643
10917
  };
10644
10918
  });
10645
10919
 
10920
+ // src/db/promotions.ts
10921
+ var exports_promotions = {};
10922
+ __export(exports_promotions, {
10923
+ promoteConnector: () => promoteConnector,
10924
+ isPromoted: () => isPromoted,
10925
+ getPromotedConnectors: () => getPromotedConnectors,
10926
+ demoteConnector: () => demoteConnector
10927
+ });
10928
+ function promoteConnector(name, db) {
10929
+ const d = db ?? getDatabase();
10930
+ d.run("INSERT OR REPLACE INTO connector_promotions (connector, promoted_at) VALUES (?, ?)", [name, now()]);
10931
+ }
10932
+ function demoteConnector(name, db) {
10933
+ const d = db ?? getDatabase();
10934
+ return d.run("DELETE FROM connector_promotions WHERE connector = ?", [name]).changes > 0;
10935
+ }
10936
+ function getPromotedConnectors(db) {
10937
+ const d = db ?? getDatabase();
10938
+ return d.query("SELECT connector FROM connector_promotions ORDER BY promoted_at DESC").all().map((r) => r.connector);
10939
+ }
10940
+ function isPromoted(name, db) {
10941
+ const d = db ?? getDatabase();
10942
+ const row = d.query("SELECT 1 FROM connector_promotions WHERE connector = ?").get(name);
10943
+ return !!row;
10944
+ }
10945
+ var init_promotions = __esm(() => {
10946
+ init_database();
10947
+ });
10948
+
10949
+ // src/db/usage.ts
10950
+ var exports_usage = {};
10951
+ __export(exports_usage, {
10952
+ logUsage: () => logUsage,
10953
+ getUsageStats: () => getUsageStats,
10954
+ getUsageMap: () => getUsageMap,
10955
+ getTopConnectors: () => getTopConnectors,
10956
+ cleanOldUsage: () => cleanOldUsage
10957
+ });
10958
+ function logUsage(connector, action, agentId, db) {
10959
+ const d = db ?? getDatabase();
10960
+ d.run("INSERT INTO connector_usage (id, connector, action, agent_id, timestamp) VALUES (?, ?, ?, ?, ?)", [shortUuid(), connector, action, agentId ?? null, now()]);
10961
+ }
10962
+ function getUsageStats(connector, db) {
10963
+ const d = db ?? getDatabase();
10964
+ const total = d.query("SELECT COUNT(*) as c FROM connector_usage WHERE connector = ?").get(connector).c;
10965
+ const d7 = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
10966
+ const last7d = d.query("SELECT COUNT(*) as c FROM connector_usage WHERE connector = ? AND timestamp > ?").get(connector, d7).c;
10967
+ const d1 = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
10968
+ const last24h = d.query("SELECT COUNT(*) as c FROM connector_usage WHERE connector = ? AND timestamp > ?").get(connector, d1).c;
10969
+ return { connector, total, last7d, last24h };
10970
+ }
10971
+ function getTopConnectors(limit = 10, days = 7, db) {
10972
+ const d = db ?? getDatabase();
10973
+ const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
10974
+ return d.query("SELECT connector, COUNT(*) as count FROM connector_usage WHERE timestamp > ? GROUP BY connector ORDER BY count DESC LIMIT ?").all(since, limit);
10975
+ }
10976
+ function getUsageMap(days = 7, db) {
10977
+ const top = getTopConnectors(100, days, db);
10978
+ return new Map(top.map((t) => [t.connector, t.count]));
10979
+ }
10980
+ function cleanOldUsage(days = 30, db) {
10981
+ const d = db ?? getDatabase();
10982
+ const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
10983
+ return d.run("DELETE FROM connector_usage WHERE timestamp < ?", [cutoff]).changes;
10984
+ }
10985
+ var init_usage = __esm(() => {
10986
+ init_database();
10987
+ });
10988
+
10646
10989
  // src/db/agents.ts
10647
10990
  function shortUuid2() {
10648
10991
  return crypto.randomUUID().slice(0, 8);
@@ -11071,6 +11414,30 @@ Dashboard not found at: ${dashboardDir}`);
11071
11414
  if (path === "/api/activity" && method === "GET") {
11072
11415
  return json(activityLog, 200, port);
11073
11416
  }
11417
+ if (path === "/api/hot" && method === "GET") {
11418
+ const { getTopConnectors: getTopConnectors2 } = await Promise.resolve().then(() => (init_usage(), exports_usage));
11419
+ const { getPromotedConnectors: getPromotedConnectors2 } = await Promise.resolve().then(() => (init_promotions(), exports_promotions));
11420
+ const limit = parseInt(url2.searchParams.get("limit") || "10", 10);
11421
+ const days = parseInt(url2.searchParams.get("days") || "7", 10);
11422
+ const db = getDatabase2();
11423
+ const top = getTopConnectors2(limit, days, db);
11424
+ const promoted = new Set(getPromotedConnectors2(db));
11425
+ return json(top.map((t) => ({ ...t, promoted: promoted.has(t.connector) })), 200, port);
11426
+ }
11427
+ const promoteMatch = path.match(/^\/api\/connectors\/([^/]+)\/promote$/);
11428
+ if (promoteMatch && method === "POST") {
11429
+ const name = promoteMatch[1];
11430
+ if (!getConnector(name))
11431
+ return json({ error: "Connector not found" }, 404, port);
11432
+ const { promoteConnector: promoteConnector2 } = await Promise.resolve().then(() => (init_promotions(), exports_promotions));
11433
+ promoteConnector2(name, getDatabase2());
11434
+ return json({ success: true, connector: name }, 200, port);
11435
+ }
11436
+ if (promoteMatch && method === "DELETE") {
11437
+ const { demoteConnector: demoteConnector2 } = await Promise.resolve().then(() => (init_promotions(), exports_promotions));
11438
+ const removed = demoteConnector2(promoteMatch[1], getDatabase2());
11439
+ return json({ success: removed, connector: promoteMatch[1] }, 200, port);
11440
+ }
11074
11441
  if (path === "/api/llm" && method === "GET") {
11075
11442
  const config = getLlmConfig();
11076
11443
  if (!config)
@@ -11484,7 +11851,7 @@ import chalk2 from "chalk";
11484
11851
  // package.json
11485
11852
  var package_default = {
11486
11853
  name: "@hasna/connectors",
11487
- version: "1.2.0",
11854
+ version: "1.3.1",
11488
11855
  description: "Open source connector library - Install API connectors with a single command",
11489
11856
  type: "module",
11490
11857
  bin: {
@@ -13738,10 +14105,18 @@ Available connectors (${CONNECTORS.length}):
13738
14105
  console.log();
13739
14106
  }
13740
14107
  });
13741
- program2.command("search").argument("<query>", "Search term").option("--json", "Output as JSON", false).description("Search for connectors").action((query, options) => {
13742
- const results = searchConnectors(query);
14108
+ program2.command("search").argument("<query>", "Search term").option("--json", "Output as JSON", false).option("--limit <n>", "Max results", "20").description("Search for connectors (ranked with fuzzy matching)").action((query, options) => {
14109
+ const installed = getInstalledConnectors();
14110
+ const { getPromotedConnectors: getPromotedConnectors2 } = (init_promotions(), __toCommonJS(exports_promotions));
14111
+ const { getUsageMap: getUsageMap2 } = (init_usage(), __toCommonJS(exports_usage));
14112
+ const results = searchConnectors(query, {
14113
+ installed,
14114
+ promoted: getPromotedConnectors2(),
14115
+ usage: getUsageMap2(),
14116
+ limit: parseInt(options.limit)
14117
+ });
13743
14118
  if (options.json) {
13744
- console.log(JSON.stringify(results));
14119
+ console.log(JSON.stringify(results.map((c) => ({ name: c.name, displayName: c.displayName, version: c.version, category: c.category, description: c.description, score: c.score, badges: c.badges, matchReasons: c.matchReasons }))));
13745
14120
  return;
13746
14121
  }
13747
14122
  if (results.length === 0) {
@@ -13751,10 +14126,12 @@ program2.command("search").argument("<query>", "Search term").option("--json", "
13751
14126
  console.log(chalk2.bold(`
13752
14127
  Found ${results.length} connector(s):
13753
14128
  `));
13754
- console.log(` ${chalk2.dim("Name".padEnd(20))}${chalk2.dim("Version".padEnd(10))}${chalk2.dim("Category".padEnd(20))}${chalk2.dim("Description")}`);
13755
- console.log(chalk2.dim(` ${"\u2500".repeat(70)}`));
14129
+ console.log(` ${chalk2.dim("Name".padEnd(22))}${chalk2.dim("Score".padEnd(7))}${chalk2.dim("Category".padEnd(20))}${chalk2.dim("Description")}`);
14130
+ console.log(chalk2.dim(` ${"\u2500".repeat(75)}`));
13756
14131
  for (const c of results) {
13757
- console.log(` ${chalk2.cyan(c.name.padEnd(20))}${chalk2.dim((c.version || "-").padEnd(10))}${chalk2.dim(c.category.padEnd(20))}${c.description}`);
14132
+ const badges = c.badges.map((b) => b === "installed" ? chalk2.green("[INS]") : b === "hot" ? chalk2.red("[HOT]") : b === "promoted" ? chalk2.yellow("[PRO]") : "").join(" ");
14133
+ const badgeStr = badges ? " " + badges : "";
14134
+ console.log(` ${chalk2.cyan(c.name.padEnd(22))}${String(c.score).padEnd(7)}${chalk2.dim(c.category.padEnd(20))}${c.description}${badgeStr}`);
13758
14135
  }
13759
14136
  });
13760
14137
  program2.command("info").argument("<connector>", "Connector name").option("--json", "Output as JSON", false).description("Show detailed info about a connector").action((connector, options) => {
@@ -15464,6 +15841,51 @@ Setting up ${meta.displayName}...
15464
15841
  }
15465
15842
  process.exit(0);
15466
15843
  });
15844
+ program2.command("hot").description("Show top connectors by usage").option("--limit <n>", "Max results", "10").option("--days <n>", "Time window in days", "7").option("--json", "Output as JSON").action((options) => {
15845
+ const { getTopConnectors: getTopConnectors2 } = (init_usage(), __toCommonJS(exports_usage));
15846
+ const { getPromotedConnectors: getPromotedConnectors2 } = (init_promotions(), __toCommonJS(exports_promotions));
15847
+ const top = getTopConnectors2(parseInt(options.limit), parseInt(options.days), getDatabase());
15848
+ const promoted = new Set(getPromotedConnectors2(getDatabase()));
15849
+ if (options.json) {
15850
+ console.log(JSON.stringify(top.map((t) => ({ ...t, promoted: promoted.has(t.connector) }))));
15851
+ return;
15852
+ }
15853
+ if (top.length === 0) {
15854
+ console.log(chalk2.dim("No usage data yet. Use connectors to build up stats."));
15855
+ return;
15856
+ }
15857
+ console.log(chalk2.bold(`
15858
+ Top connectors (last ${options.days} days):
15859
+ `));
15860
+ console.log(` ${chalk2.dim("#".padEnd(4))}${chalk2.dim("Connector".padEnd(22))}${chalk2.dim("Usage".padEnd(8))}${chalk2.dim("Badges")}`);
15861
+ console.log(chalk2.dim(` ${"\u2500".repeat(45)}`));
15862
+ for (let i = 0;i < top.length; i++) {
15863
+ const t = top[i];
15864
+ const badges = [
15865
+ t.count >= 5 ? chalk2.red("[HOT]") : "",
15866
+ promoted.has(t.connector) ? chalk2.yellow("[PRO]") : ""
15867
+ ].filter(Boolean).join(" ");
15868
+ console.log(` ${String(i + 1).padEnd(4)}${chalk2.cyan(t.connector.padEnd(22))}${String(t.count).padEnd(8)}${badges}`);
15869
+ }
15870
+ });
15871
+ program2.command("promote").argument("<connector>", "Connector to promote").description("Mark a connector as promoted (boosted in search)").action((connector) => {
15872
+ const meta = getConnector(connector);
15873
+ if (!meta) {
15874
+ console.error(chalk2.red(`Connector '${connector}' not found`));
15875
+ process.exit(1);
15876
+ }
15877
+ const { promoteConnector: promoteConnector2 } = (init_promotions(), __toCommonJS(exports_promotions));
15878
+ promoteConnector2(connector, getDatabase());
15879
+ console.log(chalk2.green("\u2713") + ` ${meta.displayName} promoted \u2014 will rank higher in search`);
15880
+ });
15881
+ program2.command("demote").argument("<connector>", "Connector to demote").description("Remove promotion from a connector").action((connector) => {
15882
+ const { demoteConnector: demoteConnector2 } = (init_promotions(), __toCommonJS(exports_promotions));
15883
+ const removed = demoteConnector2(connector, getDatabase());
15884
+ if (removed)
15885
+ console.log(chalk2.green("\u2713") + ` ${connector} demoted`);
15886
+ else
15887
+ console.log(chalk2.dim(`${connector} was not promoted`));
15888
+ });
15467
15889
  var jobsCmd = program2.command("jobs").description("Manage scheduled connector jobs");
15468
15890
  jobsCmd.command("add").description("Add a scheduled job").requiredOption("--name <name>", "Job name").requiredOption("--connector <connector>", "Connector name").requiredOption("--command <command>", "Command to run").requiredOption("--cron <cron>", "Cron expression (5-field)").option("--args <args>", "Command args (space-separated)").option("--strip", "Apply LLM stripping to output").option("--json", "Output as JSON").action((options) => {
15469
15891
  const db = getDatabase();