@hasna/connectors 0.2.3 → 0.2.4

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 (320) hide show
  1. package/README.md +5 -3
  2. package/bin/index.js +1073 -47
  3. package/bin/mcp.js +161 -6
  4. package/bin/serve.js +379 -9
  5. package/connectors/connect-anthropic/AGENTS.md +1 -1
  6. package/connectors/connect-anthropic/CLAUDE.md +1 -1
  7. package/connectors/connect-anthropic/GEMINI.md +1 -1
  8. package/connectors/connect-anthropic/README.md +2 -2
  9. package/connectors/connect-anthropic/src/utils/config.ts +2 -2
  10. package/connectors/connect-aws/AGENTS.md +2 -2
  11. package/connectors/connect-aws/CLAUDE.md +2 -2
  12. package/connectors/connect-aws/GEMINI.md +2 -2
  13. package/connectors/connect-aws/README.md +2 -2
  14. package/connectors/connect-aws/src/utils/config.ts +2 -2
  15. package/connectors/connect-brandsight/AGENTS.md +2 -2
  16. package/connectors/connect-brandsight/CLAUDE.md +2 -2
  17. package/connectors/connect-brandsight/GEMINI.md +2 -2
  18. package/connectors/connect-brandsight/README.md +3 -3
  19. package/connectors/connect-brandsight/src/utils/config.ts +1 -1
  20. package/connectors/connect-brandsight/src/utils/contacts.ts +1 -1
  21. package/connectors/connect-cloudflare/AGENTS.md +1 -1
  22. package/connectors/connect-cloudflare/CLAUDE.md +1 -1
  23. package/connectors/connect-cloudflare/GEMINI.md +1 -1
  24. package/connectors/connect-cloudflare/README.md +2 -2
  25. package/connectors/connect-cloudflare/src/utils/config.ts +2 -2
  26. package/connectors/connect-discord/AGENTS.md +2 -2
  27. package/connectors/connect-discord/CLAUDE.md +2 -2
  28. package/connectors/connect-discord/GEMINI.md +2 -2
  29. package/connectors/connect-discord/README.md +2 -2
  30. package/connectors/connect-discord/src/utils/config.ts +2 -2
  31. package/connectors/connect-docker/AGENTS.md +2 -2
  32. package/connectors/connect-docker/CLAUDE.md +2 -2
  33. package/connectors/connect-docker/GEMINI.md +2 -2
  34. package/connectors/connect-docker/README.md +2 -2
  35. package/connectors/connect-docker/src/utils/config.ts +2 -2
  36. package/connectors/connect-e2b/AGENTS.md +1 -1
  37. package/connectors/connect-e2b/CLAUDE.md +1 -1
  38. package/connectors/connect-e2b/GEMINI.md +1 -1
  39. package/connectors/connect-e2b/README.md +2 -2
  40. package/connectors/connect-e2b/src/utils/config.ts +2 -2
  41. package/connectors/connect-elevenlabs/AGENTS.md +1 -1
  42. package/connectors/connect-elevenlabs/CLAUDE.md +1 -1
  43. package/connectors/connect-elevenlabs/GEMINI.md +1 -1
  44. package/connectors/connect-elevenlabs/README.md +2 -2
  45. package/connectors/connect-elevenlabs/src/utils/config.ts +2 -2
  46. package/connectors/connect-exa/AGENTS.md +1 -1
  47. package/connectors/connect-exa/CLAUDE.md +1 -1
  48. package/connectors/connect-exa/GEMINI.md +1 -1
  49. package/connectors/connect-exa/README.md +2 -2
  50. package/connectors/connect-exa/src/utils/config.ts +2 -2
  51. package/connectors/connect-figma/AGENTS.md +2 -2
  52. package/connectors/connect-figma/CLAUDE.md +2 -2
  53. package/connectors/connect-figma/GEMINI.md +2 -2
  54. package/connectors/connect-figma/README.md +2 -2
  55. package/connectors/connect-figma/src/utils/config.ts +2 -2
  56. package/connectors/connect-firecrawl/AGENTS.md +1 -1
  57. package/connectors/connect-firecrawl/CLAUDE.md +1 -1
  58. package/connectors/connect-firecrawl/GEMINI.md +1 -1
  59. package/connectors/connect-firecrawl/README.md +2 -2
  60. package/connectors/connect-firecrawl/src/utils/config.ts +2 -2
  61. package/connectors/connect-github/AGENTS.md +2 -2
  62. package/connectors/connect-github/CLAUDE.md +2 -2
  63. package/connectors/connect-github/GEMINI.md +2 -2
  64. package/connectors/connect-github/README.md +2 -2
  65. package/connectors/connect-github/src/utils/config.ts +2 -2
  66. package/connectors/connect-gmail/AGENTS.md +2 -2
  67. package/connectors/connect-gmail/CLAUDE.md +2 -2
  68. package/connectors/connect-gmail/GEMINI.md +2 -2
  69. package/connectors/connect-gmail/README.md +2 -2
  70. package/connectors/connect-gmail/src/utils/config.ts +2 -2
  71. package/connectors/connect-google/AGENTS.md +1 -1
  72. package/connectors/connect-google/CLAUDE.md +1 -1
  73. package/connectors/connect-google/GEMINI.md +1 -1
  74. package/connectors/connect-google/README.md +2 -2
  75. package/connectors/connect-google/src/utils/config.ts +2 -2
  76. package/connectors/connect-googlecalendar/AGENTS.md +2 -2
  77. package/connectors/connect-googlecalendar/CLAUDE.md +2 -2
  78. package/connectors/connect-googlecalendar/GEMINI.md +2 -2
  79. package/connectors/connect-googlecalendar/README.md +2 -2
  80. package/connectors/connect-googlecalendar/src/utils/config.ts +2 -2
  81. package/connectors/connect-googlecloud/AGENTS.md +2 -2
  82. package/connectors/connect-googlecloud/CLAUDE.md +2 -2
  83. package/connectors/connect-googlecloud/GEMINI.md +2 -2
  84. package/connectors/connect-googlecloud/README.md +2 -2
  85. package/connectors/connect-googlecloud/src/utils/config.ts +2 -2
  86. package/connectors/connect-googlecontacts/AGENTS.md +4 -4
  87. package/connectors/connect-googlecontacts/CLAUDE.md +4 -4
  88. package/connectors/connect-googlecontacts/GEMINI.md +4 -4
  89. package/connectors/connect-googlecontacts/README.md +2 -2
  90. package/connectors/connect-googlecontacts/src/utils/config.ts +2 -2
  91. package/connectors/connect-googledocs/AGENTS.md +1 -1
  92. package/connectors/connect-googledocs/CLAUDE.md +1 -1
  93. package/connectors/connect-googledocs/GEMINI.md +1 -1
  94. package/connectors/connect-googledocs/README.md +2 -2
  95. package/connectors/connect-googledocs/src/utils/config.ts +2 -2
  96. package/connectors/connect-googledrive/AGENTS.md +1 -1
  97. package/connectors/connect-googledrive/CLAUDE.md +6 -6
  98. package/connectors/connect-googledrive/GEMINI.md +1 -1
  99. package/connectors/connect-googledrive/README.md +2 -2
  100. package/connectors/connect-googledrive/src/utils/config.ts +2 -2
  101. package/connectors/connect-googlegemini/AGENTS.md +2 -2
  102. package/connectors/connect-googlegemini/CLAUDE.md +2 -2
  103. package/connectors/connect-googlegemini/GEMINI.md +2 -2
  104. package/connectors/connect-googlegemini/README.md +2 -2
  105. package/connectors/connect-googlegemini/src/utils/config.ts +1 -1
  106. package/connectors/connect-googlemaps/AGENTS.md +1 -1
  107. package/connectors/connect-googlemaps/CLAUDE.md +1 -1
  108. package/connectors/connect-googlemaps/GEMINI.md +1 -1
  109. package/connectors/connect-googlemaps/README.md +2 -2
  110. package/connectors/connect-googlemaps/src/utils/config.ts +1 -1
  111. package/connectors/connect-googlesheets/AGENTS.md +1 -1
  112. package/connectors/connect-googlesheets/CLAUDE.md +1 -1
  113. package/connectors/connect-googlesheets/GEMINI.md +1 -1
  114. package/connectors/connect-googlesheets/README.md +2 -2
  115. package/connectors/connect-googlesheets/src/utils/config.ts +2 -2
  116. package/connectors/connect-googletasks/AGENTS.md +1 -1
  117. package/connectors/connect-googletasks/CLAUDE.md +1 -1
  118. package/connectors/connect-googletasks/GEMINI.md +1 -1
  119. package/connectors/connect-googletasks/README.md +2 -2
  120. package/connectors/connect-googletasks/src/utils/config.ts +1 -1
  121. package/connectors/connect-hedra/AGENTS.md +2 -2
  122. package/connectors/connect-hedra/CLAUDE.md +2 -2
  123. package/connectors/connect-hedra/GEMINI.md +2 -2
  124. package/connectors/connect-hedra/README.md +2 -2
  125. package/connectors/connect-hedra/src/utils/config.ts +2 -2
  126. package/connectors/connect-heygen/AGENTS.md +2 -2
  127. package/connectors/connect-heygen/CLAUDE.md +2 -2
  128. package/connectors/connect-heygen/GEMINI.md +2 -2
  129. package/connectors/connect-heygen/README.md +2 -2
  130. package/connectors/connect-heygen/src/utils/config.ts +2 -2
  131. package/connectors/connect-huggingface/AGENTS.md +2 -2
  132. package/connectors/connect-huggingface/CLAUDE.md +2 -2
  133. package/connectors/connect-huggingface/GEMINI.md +2 -2
  134. package/connectors/connect-huggingface/README.md +2 -2
  135. package/connectors/connect-huggingface/src/utils/config.ts +2 -2
  136. package/connectors/connect-icons8/AGENTS.md +2 -2
  137. package/connectors/connect-icons8/CLAUDE.md +2 -2
  138. package/connectors/connect-icons8/GEMINI.md +2 -2
  139. package/connectors/connect-icons8/README.md +2 -2
  140. package/connectors/connect-icons8/src/utils/config.ts +2 -2
  141. package/connectors/connect-maropost/AGENTS.md +1 -1
  142. package/connectors/connect-maropost/CLAUDE.md +1 -1
  143. package/connectors/connect-maropost/GEMINI.md +1 -1
  144. package/connectors/connect-maropost/README.md +2 -2
  145. package/connectors/connect-maropost/src/utils/config.ts +2 -2
  146. package/connectors/connect-mercury/AGENTS.md +1 -1
  147. package/connectors/connect-mercury/CLAUDE.md +1 -1
  148. package/connectors/connect-mercury/GEMINI.md +1 -1
  149. package/connectors/connect-mercury/README.md +2 -2
  150. package/connectors/connect-mercury/src/utils/config.ts +2 -2
  151. package/connectors/connect-meta/AGENTS.md +1 -1
  152. package/connectors/connect-meta/CLAUDE.md +1 -1
  153. package/connectors/connect-meta/GEMINI.md +1 -1
  154. package/connectors/connect-meta/README.md +2 -2
  155. package/connectors/connect-meta/src/utils/config.ts +2 -2
  156. package/connectors/connect-midjourney/AGENTS.md +2 -2
  157. package/connectors/connect-midjourney/CLAUDE.md +2 -2
  158. package/connectors/connect-midjourney/GEMINI.md +2 -2
  159. package/connectors/connect-midjourney/README.md +2 -2
  160. package/connectors/connect-midjourney/src/utils/config.ts +2 -2
  161. package/connectors/connect-mistral/AGENTS.md +1 -1
  162. package/connectors/connect-mistral/CLAUDE.md +1 -1
  163. package/connectors/connect-mistral/GEMINI.md +1 -1
  164. package/connectors/connect-mistral/README.md +2 -2
  165. package/connectors/connect-mistral/src/utils/config.ts +2 -2
  166. package/connectors/connect-mixpanel/AGENTS.md +2 -2
  167. package/connectors/connect-mixpanel/CLAUDE.md +2 -2
  168. package/connectors/connect-mixpanel/GEMINI.md +2 -2
  169. package/connectors/connect-mixpanel/README.md +2 -2
  170. package/connectors/connect-mixpanel/src/utils/config.ts +2 -2
  171. package/connectors/connect-notion/CLAUDE.md +2 -2
  172. package/connectors/connect-notion/src/utils/config.ts +2 -2
  173. package/connectors/connect-openai/AGENTS.md +1 -1
  174. package/connectors/connect-openai/CLAUDE.md +1 -1
  175. package/connectors/connect-openai/GEMINI.md +1 -1
  176. package/connectors/connect-openai/README.md +2 -2
  177. package/connectors/connect-openai/src/utils/config.ts +2 -2
  178. package/connectors/connect-openweathermap/AGENTS.md +1 -1
  179. package/connectors/connect-openweathermap/CLAUDE.md +1 -1
  180. package/connectors/connect-openweathermap/GEMINI.md +1 -1
  181. package/connectors/connect-openweathermap/README.md +2 -2
  182. package/connectors/connect-openweathermap/src/utils/config.ts +1 -1
  183. package/connectors/connect-pandadoc/AGENTS.md +1 -1
  184. package/connectors/connect-pandadoc/CLAUDE.md +1 -1
  185. package/connectors/connect-pandadoc/GEMINI.md +1 -1
  186. package/connectors/connect-pandadoc/README.md +2 -2
  187. package/connectors/connect-pandadoc/src/utils/config.ts +2 -2
  188. package/connectors/connect-quo/AGENTS.md +1 -1
  189. package/connectors/connect-quo/CLAUDE.md +1 -1
  190. package/connectors/connect-quo/GEMINI.md +1 -1
  191. package/connectors/connect-quo/README.md +2 -2
  192. package/connectors/connect-quo/src/utils/config.ts +2 -2
  193. package/connectors/connect-reddit/AGENTS.md +2 -2
  194. package/connectors/connect-reddit/CLAUDE.md +2 -2
  195. package/connectors/connect-reddit/GEMINI.md +2 -2
  196. package/connectors/connect-reddit/README.md +2 -2
  197. package/connectors/connect-reddit/src/utils/config.ts +2 -2
  198. package/connectors/connect-reducto/AGENTS.md +1 -1
  199. package/connectors/connect-reducto/CLAUDE.md +1 -1
  200. package/connectors/connect-reducto/GEMINI.md +1 -1
  201. package/connectors/connect-reducto/README.md +2 -2
  202. package/connectors/connect-reducto/src/utils/config.ts +1 -1
  203. package/connectors/connect-resend/AGENTS.md +1 -1
  204. package/connectors/connect-resend/CLAUDE.md +1 -1
  205. package/connectors/connect-resend/GEMINI.md +1 -1
  206. package/connectors/connect-resend/README.md +2 -2
  207. package/connectors/connect-resend/src/utils/config.ts +2 -2
  208. package/connectors/connect-revolut/AGENTS.md +2 -2
  209. package/connectors/connect-revolut/CLAUDE.md +2 -2
  210. package/connectors/connect-revolut/GEMINI.md +2 -2
  211. package/connectors/connect-revolut/README.md +2 -2
  212. package/connectors/connect-revolut/src/utils/config.ts +2 -2
  213. package/connectors/connect-sedo/AGENTS.md +1 -1
  214. package/connectors/connect-sedo/CLAUDE.md +1 -1
  215. package/connectors/connect-sedo/GEMINI.md +1 -1
  216. package/connectors/connect-sedo/README.md +2 -2
  217. package/connectors/connect-sedo/src/utils/config.ts +1 -1
  218. package/connectors/connect-sentry/AGENTS.md +2 -2
  219. package/connectors/connect-sentry/CLAUDE.md +2 -2
  220. package/connectors/connect-sentry/GEMINI.md +2 -2
  221. package/connectors/connect-sentry/README.md +2 -2
  222. package/connectors/connect-sentry/src/utils/config.ts +2 -2
  223. package/connectors/connect-shadcn/AGENTS.md +2 -2
  224. package/connectors/connect-shadcn/CLAUDE.md +2 -2
  225. package/connectors/connect-shadcn/GEMINI.md +2 -2
  226. package/connectors/connect-shadcn/README.md +2 -2
  227. package/connectors/connect-shadcn/src/utils/config.ts +2 -2
  228. package/connectors/connect-shopify/AGENTS.md +2 -2
  229. package/connectors/connect-shopify/CLAUDE.md +2 -2
  230. package/connectors/connect-shopify/GEMINI.md +2 -2
  231. package/connectors/connect-shopify/README.md +2 -2
  232. package/connectors/connect-shopify/src/utils/config.ts +2 -2
  233. package/connectors/connect-snap/AGENTS.md +1 -1
  234. package/connectors/connect-snap/CLAUDE.md +1 -1
  235. package/connectors/connect-snap/GEMINI.md +1 -1
  236. package/connectors/connect-snap/README.md +2 -2
  237. package/connectors/connect-snap/src/utils/config.ts +2 -2
  238. package/connectors/connect-stabilityai/AGENTS.md +1 -1
  239. package/connectors/connect-stabilityai/CLAUDE.md +1 -1
  240. package/connectors/connect-stabilityai/GEMINI.md +1 -1
  241. package/connectors/connect-stabilityai/README.md +2 -2
  242. package/connectors/connect-stabilityai/src/utils/config.ts +2 -2
  243. package/connectors/connect-stripe/AGENTS.md +2 -2
  244. package/connectors/connect-stripe/CLAUDE.md +2 -2
  245. package/connectors/connect-stripe/GEMINI.md +2 -2
  246. package/connectors/connect-stripe/README.md +2 -2
  247. package/connectors/connect-stripe/src/utils/config.ts +2 -2
  248. package/connectors/connect-stripeatlas/AGENTS.md +2 -2
  249. package/connectors/connect-stripeatlas/CLAUDE.md +2 -2
  250. package/connectors/connect-stripeatlas/GEMINI.md +2 -2
  251. package/connectors/connect-stripeatlas/README.md +2 -2
  252. package/connectors/connect-stripeatlas/src/utils/config.ts +2 -2
  253. package/connectors/connect-substack/AGENTS.md +2 -2
  254. package/connectors/connect-substack/CLAUDE.md +2 -2
  255. package/connectors/connect-substack/GEMINI.md +2 -2
  256. package/connectors/connect-substack/README.md +2 -2
  257. package/connectors/connect-substack/src/utils/config.ts +2 -2
  258. package/connectors/connect-tiktok/AGENTS.md +1 -1
  259. package/connectors/connect-tiktok/CLAUDE.md +1 -1
  260. package/connectors/connect-tiktok/GEMINI.md +1 -1
  261. package/connectors/connect-tiktok/README.md +2 -2
  262. package/connectors/connect-tiktok/src/utils/config.ts +2 -2
  263. package/connectors/connect-tinker/AGENTS.md +2 -2
  264. package/connectors/connect-tinker/CLAUDE.md +2 -2
  265. package/connectors/connect-tinker/GEMINI.md +2 -2
  266. package/connectors/connect-tinker/README.md +2 -2
  267. package/connectors/connect-tinker/src/utils/config.ts +2 -2
  268. package/connectors/connect-twilio/AGENTS.md +1 -1
  269. package/connectors/connect-twilio/CLAUDE.md +1 -1
  270. package/connectors/connect-twilio/GEMINI.md +1 -1
  271. package/connectors/connect-twilio/README.md +2 -2
  272. package/connectors/connect-twilio/src/utils/config.ts +3 -3
  273. package/connectors/connect-uspto/AGENTS.md +1 -1
  274. package/connectors/connect-uspto/CLAUDE.md +1 -1
  275. package/connectors/connect-uspto/GEMINI.md +1 -1
  276. package/connectors/connect-uspto/README.md +2 -2
  277. package/connectors/connect-uspto/src/utils/config.ts +2 -2
  278. package/connectors/connect-webflow/AGENTS.md +2 -2
  279. package/connectors/connect-webflow/CLAUDE.md +2 -2
  280. package/connectors/connect-webflow/GEMINI.md +2 -2
  281. package/connectors/connect-webflow/README.md +2 -2
  282. package/connectors/connect-webflow/src/utils/config.ts +2 -2
  283. package/connectors/connect-wix/AGENTS.md +2 -2
  284. package/connectors/connect-wix/CLAUDE.md +2 -2
  285. package/connectors/connect-wix/GEMINI.md +2 -2
  286. package/connectors/connect-wix/README.md +2 -2
  287. package/connectors/connect-wix/src/utils/config.ts +2 -2
  288. package/connectors/connect-x/AGENTS.md +1 -1
  289. package/connectors/connect-x/CLAUDE.md +1 -1
  290. package/connectors/connect-x/GEMINI.md +1 -1
  291. package/connectors/connect-x/README.md +2 -2
  292. package/connectors/connect-x/src/utils/config.ts +2 -2
  293. package/connectors/connect-xads/AGENTS.md +2 -2
  294. package/connectors/connect-xads/CLAUDE.md +2 -2
  295. package/connectors/connect-xads/GEMINI.md +2 -2
  296. package/connectors/connect-xads/README.md +2 -2
  297. package/connectors/connect-xads/src/utils/config.ts +1 -1
  298. package/connectors/connect-xai/AGENTS.md +1 -1
  299. package/connectors/connect-xai/CLAUDE.md +1 -1
  300. package/connectors/connect-xai/GEMINI.md +1 -1
  301. package/connectors/connect-xai/README.md +2 -2
  302. package/connectors/connect-xai/src/utils/config.ts +2 -2
  303. package/connectors/connect-youtube/AGENTS.md +1 -1
  304. package/connectors/connect-youtube/CLAUDE.md +1 -1
  305. package/connectors/connect-youtube/GEMINI.md +1 -1
  306. package/connectors/connect-youtube/README.md +2 -2
  307. package/connectors/connect-youtube/src/utils/config.ts +2 -2
  308. package/connectors/connect-zoom/AGENTS.md +2 -2
  309. package/connectors/connect-zoom/CLAUDE.md +2 -2
  310. package/connectors/connect-zoom/GEMINI.md +2 -2
  311. package/connectors/connect-zoom/README.md +2 -2
  312. package/connectors/connect-zoom/src/utils/config.ts +2 -2
  313. package/dashboard/dist/assets/index-DmR_QNtT.css +1 -0
  314. package/dashboard/dist/assets/index-Dp-apHbC.js +284 -0
  315. package/dashboard/dist/index.html +2 -2
  316. package/dist/server/auth.d.ts +20 -0
  317. package/dist/server/serve.d.ts +1 -1
  318. package/package.json +1 -1
  319. package/dashboard/dist/assets/index-CMPMQfHL.js +0 -234
  320. package/dashboard/dist/assets/index-D0alGsGP.css +0 -1
package/bin/index.js CHANGED
@@ -4014,6 +4014,9 @@ function getConnectorPath(name) {
4014
4014
  const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
4015
4015
  return join2(CONNECTORS_DIR, connectorName);
4016
4016
  }
4017
+ function connectorExists(name) {
4018
+ return existsSync2(getConnectorPath(name));
4019
+ }
4017
4020
  function installConnector(name, options = {}) {
4018
4021
  const { targetDir = process.cwd(), overwrite = false } = options;
4019
4022
  if (!/^[a-z0-9-]+$/.test(name)) {
@@ -4148,7 +4151,7 @@ var init_installer = __esm(() => {
4148
4151
  });
4149
4152
 
4150
4153
  // src/server/auth.ts
4151
- import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
4154
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, readdirSync as readdirSync2, rmSync as rmSync2, statSync as statSync2 } from "fs";
4152
4155
  import { randomBytes } from "crypto";
4153
4156
  import { homedir } from "os";
4154
4157
  import { join as join3 } from "path";
@@ -4165,7 +4168,7 @@ function getAuthType(name) {
4165
4168
  }
4166
4169
  function getConnectorConfigDir(name) {
4167
4170
  const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
4168
- return join3(homedir(), ".connect", connectorName);
4171
+ return join3(homedir(), ".connectors", connectorName);
4169
4172
  }
4170
4173
  function getCurrentProfile(name) {
4171
4174
  const configDir = getConnectorConfigDir(name);
@@ -4217,6 +4220,8 @@ function getAuthStatus(name) {
4217
4220
  description: v.description,
4218
4221
  set: !!process.env[v.variable]
4219
4222
  }));
4223
+ const envVarTotalCount = envVars.length;
4224
+ const envVarSetCount = envVars.filter((v) => v.set).length;
4220
4225
  if (authType === "oauth") {
4221
4226
  const tokens = loadTokens(name);
4222
4227
  const config2 = loadProfileConfig(name);
@@ -4228,7 +4233,9 @@ function getAuthStatus(name) {
4228
4233
  configured: hasTokens || hasRefreshToken,
4229
4234
  tokenExpiry,
4230
4235
  hasRefreshToken,
4231
- envVars
4236
+ envVars,
4237
+ envVarSetCount,
4238
+ envVarTotalCount
4232
4239
  };
4233
4240
  }
4234
4241
  const config = loadProfileConfig(name);
@@ -4237,9 +4244,15 @@ function getAuthStatus(name) {
4237
4244
  return {
4238
4245
  type: authType,
4239
4246
  configured: hasKey || hasEnvVar,
4240
- envVars
4247
+ envVars,
4248
+ envVarSetCount,
4249
+ envVarTotalCount
4241
4250
  };
4242
4251
  }
4252
+ function getEnvVars(name) {
4253
+ const docs = getConnectorDocs(name);
4254
+ return docs?.envVars || [];
4255
+ }
4243
4256
  function saveApiKey(name, key, field) {
4244
4257
  const configDir = getConnectorConfigDir(name);
4245
4258
  const profile = getCurrentProfile(name);
@@ -4408,6 +4421,55 @@ async function refreshOAuthToken(name) {
4408
4421
  saveOAuthTokens(name, tokens);
4409
4422
  return tokens;
4410
4423
  }
4424
+ function listProfiles(name) {
4425
+ const configDir = getConnectorConfigDir(name);
4426
+ const profilesDir = join3(configDir, "profiles");
4427
+ if (!existsSync3(profilesDir))
4428
+ return ["default"];
4429
+ const seen = new Set;
4430
+ try {
4431
+ const entries = readdirSync2(profilesDir);
4432
+ for (const entry of entries) {
4433
+ const fullPath = join3(profilesDir, entry);
4434
+ const stat = statSync2(fullPath);
4435
+ if (stat.isDirectory()) {
4436
+ seen.add(entry);
4437
+ } else if (entry.endsWith(".json")) {
4438
+ seen.add(entry.replace(/\.json$/, ""));
4439
+ }
4440
+ }
4441
+ } catch {}
4442
+ seen.add("default");
4443
+ return Array.from(seen).sort();
4444
+ }
4445
+ function switchProfile(name, profile) {
4446
+ const configDir = getConnectorConfigDir(name);
4447
+ mkdirSync2(configDir, { recursive: true });
4448
+ writeFileSync2(join3(configDir, "current_profile"), profile);
4449
+ }
4450
+ function deleteProfile(name, profile) {
4451
+ if (profile === "default")
4452
+ return false;
4453
+ const configDir = getConnectorConfigDir(name);
4454
+ const profilesDir = join3(configDir, "profiles");
4455
+ const profileFile = join3(profilesDir, `${profile}.json`);
4456
+ if (existsSync3(profileFile)) {
4457
+ rmSync2(profileFile);
4458
+ if (getCurrentProfile(name) === profile) {
4459
+ switchProfile(name, "default");
4460
+ }
4461
+ return true;
4462
+ }
4463
+ const profileDir = join3(profilesDir, profile);
4464
+ if (existsSync3(profileDir)) {
4465
+ rmSync2(profileDir, { recursive: true });
4466
+ if (getCurrentProfile(name) === profile) {
4467
+ switchProfile(name, "default");
4468
+ }
4469
+ return true;
4470
+ }
4471
+ return false;
4472
+ }
4411
4473
  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;
4412
4474
  var init_auth = __esm(() => {
4413
4475
  init_installer();
@@ -4457,9 +4519,16 @@ var exports_serve = {};
4457
4519
  __export(exports_serve, {
4458
4520
  startServer: () => startServer
4459
4521
  });
4460
- import { existsSync as existsSync4 } from "fs";
4461
- import { join as join4, dirname as dirname3, extname } from "path";
4522
+ import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
4523
+ import { join as join4, dirname as dirname3, extname, basename } from "path";
4462
4524
  import { fileURLToPath as fileURLToPath3 } from "url";
4525
+ import { homedir as homedir2 } from "os";
4526
+ function logActivity(action, connector, detail) {
4527
+ activityLog.unshift({ action, connector, timestamp: Date.now(), detail });
4528
+ if (activityLog.length > MAX_ACTIVITY_LOG) {
4529
+ activityLog.length = MAX_ACTIVITY_LOG;
4530
+ }
4531
+ }
4463
4532
  function resolveDashboardDir() {
4464
4533
  const candidates = [];
4465
4534
  try {
@@ -4533,7 +4602,19 @@ function serveStaticFile(filePath) {
4533
4602
  headers: { "Content-Type": contentType }
4534
4603
  });
4535
4604
  }
4536
- async function startServer(port, options) {
4605
+ async function findAvailablePort(preferred) {
4606
+ for (let port = preferred;port < preferred + 100; port++) {
4607
+ try {
4608
+ const server = Bun.serve({ port, fetch() {
4609
+ return new Response;
4610
+ } });
4611
+ server.stop(true);
4612
+ return port;
4613
+ } catch {}
4614
+ }
4615
+ throw new Error(`No available port found in range ${preferred}-${preferred + 99}`);
4616
+ }
4617
+ async function startServer(requestedPort, options) {
4537
4618
  const shouldOpen = options?.open ?? true;
4538
4619
  loadConnectorVersions();
4539
4620
  const dashboardDir = resolveDashboardDir();
@@ -4550,6 +4631,10 @@ Dashboard not found at: ${dashboardDir}`);
4550
4631
  console.error(` bun run build:dashboard
4551
4632
  `);
4552
4633
  }
4634
+ const port = await findAvailablePort(requestedPort);
4635
+ if (port !== requestedPort) {
4636
+ console.log(`Port ${requestedPort} is in use, using port ${port} instead`);
4637
+ }
4553
4638
  const server = Bun.serve({
4554
4639
  port,
4555
4640
  async fetch(req) {
@@ -4592,6 +4677,7 @@ Dashboard not found at: ${dashboardDir}`);
4592
4677
  if (!body.key)
4593
4678
  return json({ error: "Missing 'key' in request body" }, 400, port);
4594
4679
  saveApiKey(name, body.key, body.field);
4680
+ logActivity("key_saved", name, body.field ? `Field: ${body.field}` : undefined);
4595
4681
  return json({ success: true }, 200, port);
4596
4682
  } catch (e) {
4597
4683
  return json({ error: e instanceof Error ? e.message : "Failed to save key" }, 500, port);
@@ -4604,18 +4690,215 @@ Dashboard not found at: ${dashboardDir}`);
4604
4690
  return json({ error: "Invalid connector name" }, 400, port);
4605
4691
  try {
4606
4692
  const tokens = await refreshOAuthToken(name);
4693
+ logActivity("token_refreshed", name, tokens.expiresAt ? `Expires: ${new Date(tokens.expiresAt).toISOString()}` : undefined);
4607
4694
  return json({ success: true, expiresAt: tokens.expiresAt }, 200, port);
4608
4695
  } catch (e) {
4609
4696
  return json({ success: false, error: e instanceof Error ? e.message : "Failed to refresh" }, 500, port);
4610
4697
  }
4611
4698
  }
4699
+ const installMatch = path.match(/^\/api\/connectors\/([^/]+)\/install$/);
4700
+ if (installMatch && method === "POST") {
4701
+ const name = installMatch[1];
4702
+ if (!isValidConnectorName(name))
4703
+ return json({ error: "Invalid connector name" }, 400, port);
4704
+ const meta = getConnector(name);
4705
+ if (!meta)
4706
+ return json({ error: `Connector '${name}' not found` }, 404, port);
4707
+ try {
4708
+ const result = installConnector(name);
4709
+ if (!result.success) {
4710
+ return json({ error: result.error || "Failed to install connector" }, 500, port);
4711
+ }
4712
+ logActivity("installed", name);
4713
+ return json({ success: true, name }, 200, port);
4714
+ } catch (e) {
4715
+ return json({ error: e instanceof Error ? e.message : "Failed to install connector" }, 500, port);
4716
+ }
4717
+ }
4718
+ const uninstallMatch = path.match(/^\/api\/connectors\/([^/]+)\/uninstall$/);
4719
+ if (uninstallMatch && method === "POST") {
4720
+ const name = uninstallMatch[1];
4721
+ if (!isValidConnectorName(name))
4722
+ return json({ error: "Invalid connector name" }, 400, port);
4723
+ try {
4724
+ const removed = removeConnector(name);
4725
+ if (!removed) {
4726
+ return json({ error: `Connector '${name}' is not installed` }, 404, port);
4727
+ }
4728
+ logActivity("uninstalled", name);
4729
+ return json({ success: true, name }, 200, port);
4730
+ } catch (e) {
4731
+ return json({ error: e instanceof Error ? e.message : "Failed to uninstall connector" }, 500, port);
4732
+ }
4733
+ }
4734
+ if (path === "/api/update" && method === "POST") {
4735
+ try {
4736
+ const installed = getInstalledConnectors();
4737
+ if (installed.length === 0) {
4738
+ return json({ updated: [], count: 0 }, 200, port);
4739
+ }
4740
+ const results = installed.map((name) => installConnector(name, { overwrite: true }));
4741
+ return json({
4742
+ results,
4743
+ count: results.filter((r) => r.success).length,
4744
+ total: installed.length
4745
+ }, 200, port);
4746
+ } catch (e) {
4747
+ return json({ error: e instanceof Error ? e.message : "Failed to update" }, 500, port);
4748
+ }
4749
+ }
4750
+ if (path === "/api/activity" && method === "GET") {
4751
+ return json(activityLog, 200, port);
4752
+ }
4753
+ const profilesMatch = path.match(/^\/api\/connectors\/([^/]+)\/profiles$/);
4754
+ if (profilesMatch && method === "GET") {
4755
+ const name = profilesMatch[1];
4756
+ if (!isValidConnectorName(name))
4757
+ return json({ error: "Invalid connector name" }, 400, port);
4758
+ try {
4759
+ const profiles = listProfiles(name);
4760
+ const configDir = join4(homedir2(), ".connectors", name.startsWith("connect-") ? name : `connect-${name}`);
4761
+ const currentProfileFile = join4(configDir, "current_profile");
4762
+ let current = "default";
4763
+ if (existsSync4(currentProfileFile)) {
4764
+ try {
4765
+ current = readFileSync4(currentProfileFile, "utf-8").trim() || "default";
4766
+ } catch {}
4767
+ }
4768
+ return json({ current, profiles }, 200, port);
4769
+ } catch (e) {
4770
+ return json({ error: e instanceof Error ? e.message : "Failed to list profiles" }, 500, port);
4771
+ }
4772
+ }
4773
+ const profileSwitchMatch = path.match(/^\/api\/connectors\/([^/]+)\/profiles\/switch$/);
4774
+ if (profileSwitchMatch && method === "POST") {
4775
+ const name = profileSwitchMatch[1];
4776
+ if (!isValidConnectorName(name))
4777
+ return json({ error: "Invalid connector name" }, 400, port);
4778
+ try {
4779
+ const contentLength = parseInt(req.headers.get("content-length") || "0", 10);
4780
+ if (contentLength > MAX_BODY_SIZE)
4781
+ return json({ error: "Request body too large" }, 413, port);
4782
+ const body = await req.json();
4783
+ if (!body.profile)
4784
+ return json({ error: "Missing 'profile' in request body" }, 400, port);
4785
+ switchProfile(name, body.profile);
4786
+ logActivity("profile_switch", name, `Switched to profile: ${body.profile}`);
4787
+ return json({ success: true, profile: body.profile }, 200, port);
4788
+ } catch (e) {
4789
+ return json({ error: e instanceof Error ? e.message : "Failed to switch profile" }, 500, port);
4790
+ }
4791
+ }
4792
+ const profileDeleteMatch = path.match(/^\/api\/connectors\/([^/]+)\/profiles\/([^/]+)$/);
4793
+ if (profileDeleteMatch && method === "DELETE") {
4794
+ const name = profileDeleteMatch[1];
4795
+ const profile = profileDeleteMatch[2];
4796
+ if (!isValidConnectorName(name))
4797
+ return json({ error: "Invalid connector name" }, 400, port);
4798
+ if (profile === "default")
4799
+ return json({ error: "Cannot delete the default profile" }, 400, port);
4800
+ try {
4801
+ const deleted = deleteProfile(name, profile);
4802
+ if (!deleted)
4803
+ return json({ error: `Profile '${profile}' not found` }, 404, port);
4804
+ logActivity("profile_delete", name, `Deleted profile: ${profile}`);
4805
+ return json({ success: true }, 200, port);
4806
+ } catch (e) {
4807
+ return json({ error: e instanceof Error ? e.message : "Failed to delete profile" }, 500, port);
4808
+ }
4809
+ }
4810
+ if (path === "/api/export" && method === "GET") {
4811
+ try {
4812
+ const connectDir = join4(homedir2(), ".connectors");
4813
+ const result = {};
4814
+ if (existsSync4(connectDir)) {
4815
+ const entries = readdirSync3(connectDir, { withFileTypes: true });
4816
+ for (const entry of entries) {
4817
+ if (!entry.isDirectory() || !entry.name.startsWith("connect-"))
4818
+ continue;
4819
+ const connectorName = entry.name.replace(/^connect-/, "");
4820
+ const profilesDir = join4(connectDir, entry.name, "profiles");
4821
+ if (!existsSync4(profilesDir))
4822
+ continue;
4823
+ const profiles = {};
4824
+ const profileEntries = readdirSync3(profilesDir, { withFileTypes: true });
4825
+ for (const pEntry of profileEntries) {
4826
+ if (pEntry.isFile() && pEntry.name.endsWith(".json")) {
4827
+ const profileName = basename(pEntry.name, ".json");
4828
+ try {
4829
+ const config = JSON.parse(readFileSync4(join4(profilesDir, pEntry.name), "utf-8"));
4830
+ profiles[profileName] = config;
4831
+ } catch {}
4832
+ }
4833
+ if (pEntry.isDirectory()) {
4834
+ const configPath = join4(profilesDir, pEntry.name, "config.json");
4835
+ if (existsSync4(configPath)) {
4836
+ try {
4837
+ const config = JSON.parse(readFileSync4(configPath, "utf-8"));
4838
+ profiles[pEntry.name] = config;
4839
+ } catch {}
4840
+ }
4841
+ }
4842
+ }
4843
+ if (Object.keys(profiles).length > 0) {
4844
+ result[connectorName] = { profiles };
4845
+ }
4846
+ }
4847
+ }
4848
+ const exportData = { connectors: result, exportedAt: new Date().toISOString() };
4849
+ return new Response(JSON.stringify(exportData, null, 2), {
4850
+ status: 200,
4851
+ headers: {
4852
+ "Content-Type": "application/json",
4853
+ "Content-Disposition": `attachment; filename="connectors-backup-${new Date().toISOString().slice(0, 10)}.json"`,
4854
+ "Access-Control-Allow-Origin": port ? `http://localhost:${port}` : "*",
4855
+ ...SECURITY_HEADERS
4856
+ }
4857
+ });
4858
+ } catch (e) {
4859
+ return json({ error: e instanceof Error ? e.message : "Failed to export credentials" }, 500, port);
4860
+ }
4861
+ }
4862
+ if (path === "/api/import" && method === "POST") {
4863
+ try {
4864
+ const contentLength = parseInt(req.headers.get("content-length") || "0", 10);
4865
+ if (contentLength > MAX_BODY_SIZE)
4866
+ return json({ error: "Request body too large" }, 413, port);
4867
+ const body = await req.json();
4868
+ if (!body.connectors || typeof body.connectors !== "object") {
4869
+ return json({ error: "Invalid import format: missing 'connectors' object" }, 400, port);
4870
+ }
4871
+ let imported = 0;
4872
+ const connectDir = join4(homedir2(), ".connectors");
4873
+ for (const [connectorName, data] of Object.entries(body.connectors)) {
4874
+ if (!isValidConnectorName(connectorName))
4875
+ continue;
4876
+ if (!data.profiles || typeof data.profiles !== "object")
4877
+ continue;
4878
+ const connectorDir = join4(connectDir, `connect-${connectorName}`);
4879
+ const profilesDir = join4(connectorDir, "profiles");
4880
+ for (const [profileName, config] of Object.entries(data.profiles)) {
4881
+ if (!config || typeof config !== "object")
4882
+ continue;
4883
+ mkdirSync3(profilesDir, { recursive: true });
4884
+ const profileFile = join4(profilesDir, `${profileName}.json`);
4885
+ writeFileSync3(profileFile, JSON.stringify(config, null, 2));
4886
+ imported++;
4887
+ }
4888
+ }
4889
+ logActivity("credentials_imported", "all", `Imported ${imported} profiles`);
4890
+ return json({ success: true, imported }, 200, port);
4891
+ } catch (e) {
4892
+ return json({ error: e instanceof Error ? e.message : "Failed to import credentials" }, 500, port);
4893
+ }
4894
+ }
4612
4895
  const oauthStartMatch = path.match(/^\/oauth\/([^/]+)\/start$/);
4613
4896
  if (oauthStartMatch && method === "GET") {
4614
4897
  const name = oauthStartMatch[1];
4615
4898
  const redirectUri = `http://localhost:${port}/oauth/${name}/callback`;
4616
4899
  const authUrl = getOAuthStartUrl(name, redirectUri);
4617
4900
  if (!authUrl) {
4618
- return htmlResponse(errorPage("OAuth Not Available", `No OAuth client credentials found for <strong>${name}</strong>.`, `Set up credentials at <code>~/.connect/connect-${name}/credentials.json</code>`));
4901
+ return htmlResponse(errorPage("OAuth Not Available", `No OAuth client credentials found for <strong>${name}</strong>.`, `Set up credentials at <code>~/.connectors/connect-${name}/credentials.json</code>`));
4619
4902
  }
4620
4903
  return Response.redirect(authUrl, 302);
4621
4904
  }
@@ -4637,6 +4920,7 @@ Dashboard not found at: ${dashboardDir}`);
4637
4920
  try {
4638
4921
  const redirectUri = `http://localhost:${port}/oauth/${name}/callback`;
4639
4922
  await exchangeOAuthCode(name, code, redirectUri);
4923
+ logActivity("oauth_connected", name);
4640
4924
  return htmlResponse(`<!DOCTYPE html><html><head><meta charset="utf-8"></head><body style="font-family:system-ui,sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#0a0a0a;color:#e5e5e5;">
4641
4925
  <div style="text-align:center;">
4642
4926
  <h2 style="color:#22c55e;">Connected!</h2>
@@ -4657,7 +4941,7 @@ Dashboard not found at: ${dashboardDir}`);
4657
4941
  return new Response(null, {
4658
4942
  headers: {
4659
4943
  "Access-Control-Allow-Origin": `http://localhost:${port}`,
4660
- "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
4944
+ "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
4661
4945
  "Access-Control-Allow-Headers": "Content-Type"
4662
4946
  }
4663
4947
  });
@@ -4693,11 +4977,12 @@ Dashboard not found at: ${dashboardDir}`);
4693
4977
  } catch {}
4694
4978
  }
4695
4979
  }
4696
- var MIME_TYPES, SECURITY_HEADERS, MAX_BODY_SIZE;
4980
+ var activityLog, MAX_ACTIVITY_LOG = 100, MIME_TYPES, SECURITY_HEADERS, MAX_BODY_SIZE;
4697
4981
  var init_serve = __esm(() => {
4698
4982
  init_registry();
4699
4983
  init_installer();
4700
4984
  init_auth();
4985
+ activityLog = [];
4701
4986
  MIME_TYPES = {
4702
4987
  ".html": "text/html; charset=utf-8",
4703
4988
  ".js": "application/javascript",
@@ -5218,42 +5503,67 @@ function ConnectorSelect({
5218
5503
  onConfirm,
5219
5504
  onBack
5220
5505
  }) {
5221
- const totalItems = connectors.length + 2;
5222
5506
  const [cursor, setCursor] = useState2(0);
5507
+ const [filter, setFilter] = useState2("");
5508
+ const filteredConnectors = useMemo(() => {
5509
+ if (!filter)
5510
+ return connectors;
5511
+ const lower = filter.toLowerCase();
5512
+ return connectors.filter((c) => c.name.toLowerCase().includes(lower) || c.description && c.description.toLowerCase().includes(lower));
5513
+ }, [connectors, filter]);
5514
+ const totalItems = filteredConnectors.length + 2;
5515
+ const clampedCursor = useMemo(() => {
5516
+ if (cursor >= totalItems)
5517
+ return totalItems - 1;
5518
+ return cursor;
5519
+ }, [cursor, totalItems]);
5223
5520
  const maxVisible = 16;
5224
5521
  const scrollOffset = useMemo(() => {
5225
5522
  if (totalItems <= maxVisible)
5226
5523
  return 0;
5227
5524
  const half = Math.floor(maxVisible / 2);
5228
- if (cursor < half)
5525
+ if (clampedCursor < half)
5229
5526
  return 0;
5230
- if (cursor > totalItems - maxVisible + half)
5527
+ if (clampedCursor > totalItems - maxVisible + half)
5231
5528
  return totalItems - maxVisible;
5232
- return cursor - half;
5233
- }, [cursor, totalItems]);
5529
+ return clampedCursor - half;
5530
+ }, [clampedCursor, totalItems]);
5234
5531
  useInput2((input, key) => {
5235
5532
  if (key.escape) {
5236
- onBack();
5533
+ if (filter) {
5534
+ setFilter("");
5535
+ setCursor(0);
5536
+ } else {
5537
+ onBack();
5538
+ }
5237
5539
  } else if (key.upArrow) {
5238
- setCursor((c) => c > 0 ? c - 1 : totalItems - 1);
5540
+ setCursor((c) => {
5541
+ const total = filteredConnectors.length + 2;
5542
+ return c > 0 ? c - 1 : total - 1;
5543
+ });
5239
5544
  } else if (key.downArrow) {
5240
- setCursor((c) => c < totalItems - 1 ? c + 1 : 0);
5545
+ setCursor((c) => {
5546
+ const total = filteredConnectors.length + 2;
5547
+ return c < total - 1 ? c + 1 : 0;
5548
+ });
5241
5549
  } else if (key.return) {
5242
- if (cursor === 0) {
5550
+ const cur = clampedCursor;
5551
+ const total = filteredConnectors.length + 2;
5552
+ if (cur === 0) {
5243
5553
  onBack();
5244
- } else if (cursor === totalItems - 1) {
5554
+ } else if (cur === total - 1) {
5245
5555
  if (selected.size > 0)
5246
5556
  onConfirm();
5247
5557
  } else {
5248
- onToggle(connectors[cursor - 1].name);
5558
+ onToggle(filteredConnectors[cur - 1].name);
5249
5559
  }
5250
- } else if (input === " " && cursor > 0 && cursor < totalItems - 1) {
5251
- onToggle(connectors[cursor - 1].name);
5560
+ } else if (input === " " && clampedCursor > 0 && clampedCursor < filteredConnectors.length + 1) {
5561
+ onToggle(filteredConnectors[clampedCursor - 1].name);
5252
5562
  } else if (input === "i" && selected.size > 0) {
5253
5563
  onConfirm();
5254
5564
  } else if (input === "a") {
5255
- const allSelected = connectors.every((c) => selected.has(c.name));
5256
- for (const c of connectors) {
5565
+ const allSelected = filteredConnectors.every((c) => selected.has(c.name));
5566
+ for (const c of filteredConnectors) {
5257
5567
  if (allSelected) {
5258
5568
  if (selected.has(c.name))
5259
5569
  onToggle(c.name);
@@ -5262,6 +5572,12 @@ function ConnectorSelect({
5262
5572
  onToggle(c.name);
5263
5573
  }
5264
5574
  }
5575
+ } else if (key.backspace || key.delete) {
5576
+ setFilter((f) => f.slice(0, -1));
5577
+ setCursor(0);
5578
+ } else if (input && /^[a-zA-Z0-9\-_.]$/.test(input) && input !== "a" && input !== "i") {
5579
+ setFilter((f) => f + input);
5580
+ setCursor(0);
5265
5581
  }
5266
5582
  });
5267
5583
  const visibleStart = scrollOffset;
@@ -5271,11 +5587,33 @@ function ConnectorSelect({
5271
5587
  children: [
5272
5588
  /* @__PURE__ */ jsxDEV3(Box5, {
5273
5589
  marginBottom: 1,
5274
- children: /* @__PURE__ */ jsxDEV3(Text5, {
5275
- bold: true,
5276
- children: "Select connectors to install:"
5277
- }, undefined, false, undefined, this)
5278
- }, undefined, false, undefined, this),
5590
+ children: [
5591
+ /* @__PURE__ */ jsxDEV3(Text5, {
5592
+ bold: true,
5593
+ children: "Select connectors to install:"
5594
+ }, undefined, false, undefined, this),
5595
+ filter ? /* @__PURE__ */ jsxDEV3(Text5, {
5596
+ color: "yellow",
5597
+ children: [
5598
+ " Filter: ",
5599
+ filter
5600
+ ]
5601
+ }, undefined, true, undefined, this) : null,
5602
+ filter && filteredConnectors.length === 0 ? /* @__PURE__ */ jsxDEV3(Text5, {
5603
+ dimColor: true,
5604
+ children: " (no matches)"
5605
+ }, undefined, false, undefined, this) : filter ? /* @__PURE__ */ jsxDEV3(Text5, {
5606
+ dimColor: true,
5607
+ children: [
5608
+ " (",
5609
+ filteredConnectors.length,
5610
+ " match",
5611
+ filteredConnectors.length !== 1 ? "es" : "",
5612
+ ")"
5613
+ ]
5614
+ }, undefined, true, undefined, this) : null
5615
+ ]
5616
+ }, undefined, true, undefined, this),
5279
5617
  /* @__PURE__ */ jsxDEV3(Box5, {
5280
5618
  children: [
5281
5619
  /* @__PURE__ */ jsxDEV3(Box5, {
@@ -5326,7 +5664,7 @@ function ConnectorSelect({
5326
5664
  Array.from({ length: visibleEnd - visibleStart }, (_, i) => {
5327
5665
  const idx = visibleStart + i;
5328
5666
  if (idx === 0) {
5329
- const isActive2 = cursor === 0;
5667
+ const isActive2 = clampedCursor === 0;
5330
5668
  return /* @__PURE__ */ jsxDEV3(Box5, {
5331
5669
  children: /* @__PURE__ */ jsxDEV3(Text5, {
5332
5670
  color: isActive2 ? "cyan" : undefined,
@@ -5339,7 +5677,7 @@ function ConnectorSelect({
5339
5677
  }, "__back__", false, undefined, this);
5340
5678
  }
5341
5679
  if (idx === totalItems - 1) {
5342
- const isActive2 = cursor === totalItems - 1;
5680
+ const isActive2 = clampedCursor === totalItems - 1;
5343
5681
  const hasSelection = selected.size > 0;
5344
5682
  return /* @__PURE__ */ jsxDEV3(Box5, {
5345
5683
  children: /* @__PURE__ */ jsxDEV3(Text5, {
@@ -5355,8 +5693,8 @@ function ConnectorSelect({
5355
5693
  }, undefined, true, undefined, this)
5356
5694
  }, "__confirm__", false, undefined, this);
5357
5695
  }
5358
- const c = connectors[idx - 1];
5359
- const isActive = cursor === idx;
5696
+ const c = filteredConnectors[idx - 1];
5697
+ const isActive = clampedCursor === idx;
5360
5698
  const isChecked = selected.has(c.name);
5361
5699
  return /* @__PURE__ */ jsxDEV3(Box5, {
5362
5700
  children: [
@@ -5418,8 +5756,11 @@ function ConnectorSelect({
5418
5756
  marginTop: 1,
5419
5757
  children: /* @__PURE__ */ jsxDEV3(Text5, {
5420
5758
  dimColor: true,
5421
- children: "\u2191\u2193 navigate space/enter toggle a select all i install esc back"
5422
- }, undefined, false, undefined, this)
5759
+ children: [
5760
+ "\u2191\u2193 navigate space/enter toggle a select all i install type to filter esc ",
5761
+ filter ? "clear filter" : "back"
5762
+ ]
5763
+ }, undefined, true, undefined, this)
5423
5764
  }, undefined, false, undefined, this)
5424
5765
  ]
5425
5766
  }, undefined, true, undefined, this);
@@ -6154,11 +6495,15 @@ function App({ initialConnectors, overwrite = false }) {
6154
6495
  // src/cli/index.tsx
6155
6496
  init_registry();
6156
6497
  init_installer();
6498
+ init_auth();
6499
+ import { readdirSync as readdirSync4, statSync as statSync3 } from "fs";
6500
+ import { join as join5, relative } from "path";
6501
+ import { createInterface } from "readline";
6157
6502
  import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
6158
6503
  loadConnectorVersions();
6159
6504
  var isTTY = process.stdout.isTTY ?? false;
6160
6505
  var program2 = new Command;
6161
- program2.name("connectors").description("Install API connectors for your project").version("0.2.3");
6506
+ program2.name("connectors").description("Install API connectors for your project").version("0.2.4");
6162
6507
  program2.command("interactive", { isDefault: true }).alias("i").description("Interactive connector browser").action(() => {
6163
6508
  if (!isTTY) {
6164
6509
  console.log(`Non-interactive environment detected. Use a subcommand:
@@ -6176,7 +6521,19 @@ Run 'connectors --help' for full usage.`);
6176
6521
  }
6177
6522
  render(/* @__PURE__ */ jsxDEV7(App, {}, undefined, false, undefined, this));
6178
6523
  });
6179
- program2.command("install").alias("add").argument("[connectors...]", "Connectors to install").option("-o, --overwrite", "Overwrite existing connectors", false).option("--json", "Output results as JSON", false).description("Install one or more connectors").action((connectors, options) => {
6524
+ function listFilesRecursive(dir, base = dir) {
6525
+ const files = [];
6526
+ for (const entry of readdirSync4(dir)) {
6527
+ const fullPath = join5(dir, entry);
6528
+ if (statSync3(fullPath).isDirectory()) {
6529
+ files.push(...listFilesRecursive(fullPath, base));
6530
+ } else {
6531
+ files.push(relative(base, fullPath));
6532
+ }
6533
+ }
6534
+ return files;
6535
+ }
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) => {
6180
6537
  if (connectors.length === 0) {
6181
6538
  if (!isTTY) {
6182
6539
  console.error("Error: specify connectors to install. Example: connectors install figma stripe");
@@ -6185,6 +6542,101 @@ program2.command("install").alias("add").argument("[connectors...]", "Connectors
6185
6542
  render(/* @__PURE__ */ jsxDEV7(App, {}, undefined, false, undefined, this));
6186
6543
  return;
6187
6544
  }
6545
+ if (options.dryRun) {
6546
+ const installed = getInstalledConnectors();
6547
+ const destDir = join5(process.cwd(), ".connectors");
6548
+ const actions = [];
6549
+ for (const name of connectors) {
6550
+ if (!/^[a-z0-9-]+$/.test(name)) {
6551
+ actions.push({ connector: name, action: "error", reason: `Invalid connector name '${name}'` });
6552
+ continue;
6553
+ }
6554
+ const meta = getConnector(name);
6555
+ if (!meta) {
6556
+ actions.push({ connector: name, action: "error", reason: `Connector '${name}' not found in registry` });
6557
+ continue;
6558
+ }
6559
+ if (!connectorExists(name)) {
6560
+ actions.push({ connector: name, action: "error", reason: `Connector '${name}' source files not found` });
6561
+ continue;
6562
+ }
6563
+ const connectorDirName = name.startsWith("connect-") ? name : `connect-${name}`;
6564
+ const sourcePath = getConnectorPath(name);
6565
+ const destPath = join5(destDir, connectorDirName);
6566
+ const alreadyInstalled = installed.includes(name);
6567
+ const files = listFilesRecursive(sourcePath);
6568
+ const importLine = `export * as ${name} from './${connectorDirName}/src/index.js';`;
6569
+ if (alreadyInstalled && !options.overwrite) {
6570
+ actions.push({
6571
+ connector: name,
6572
+ action: "skip",
6573
+ reason: "Already installed. Use --overwrite to replace.",
6574
+ sourcePath,
6575
+ destPath
6576
+ });
6577
+ } else {
6578
+ actions.push({
6579
+ connector: name,
6580
+ action: alreadyInstalled ? "overwrite" : "install",
6581
+ sourcePath,
6582
+ destPath,
6583
+ files,
6584
+ importLine
6585
+ });
6586
+ }
6587
+ }
6588
+ if (options.json) {
6589
+ console.log(JSON.stringify({ dryRun: true, actions }, null, 2));
6590
+ process.exit(actions.every((a) => a.action !== "error") ? 0 : 1);
6591
+ return;
6592
+ }
6593
+ console.log(chalk2.bold(`
6594
+ Dry run \u2014 no changes will be made
6595
+ `));
6596
+ for (const a of actions) {
6597
+ if (a.action === "error") {
6598
+ console.log(chalk2.red(` \u2717 ${a.connector}: ${a.reason}`));
6599
+ continue;
6600
+ }
6601
+ if (a.action === "skip") {
6602
+ console.log(chalk2.yellow(` \u2298 ${a.connector}: ${a.reason}`));
6603
+ continue;
6604
+ }
6605
+ const actionLabel = a.action === "overwrite" ? chalk2.yellow("overwrite") : chalk2.green("install");
6606
+ console.log(` ${actionLabel} ${chalk2.cyan(a.connector)}`);
6607
+ console.log(chalk2.dim(` source: ${a.sourcePath}`));
6608
+ console.log(chalk2.dim(` dest: ${a.destPath}`));
6609
+ if (a.files && a.files.length > 0) {
6610
+ console.log(chalk2.dim(` files (${a.files.length}):`));
6611
+ for (const f of a.files) {
6612
+ console.log(chalk2.dim(` ${f}`));
6613
+ }
6614
+ }
6615
+ if (a.importLine) {
6616
+ console.log(` ${chalk2.dim("index.ts:")} ${a.importLine}`);
6617
+ }
6618
+ console.log();
6619
+ }
6620
+ const installCount = actions.filter((a) => a.action === "install").length;
6621
+ const overwriteCount = actions.filter((a) => a.action === "overwrite").length;
6622
+ const skipCount = actions.filter((a) => a.action === "skip").length;
6623
+ const errorCount = actions.filter((a) => a.action === "error").length;
6624
+ const parts = [];
6625
+ if (installCount)
6626
+ parts.push(chalk2.green(`${installCount} to install`));
6627
+ if (overwriteCount)
6628
+ parts.push(chalk2.yellow(`${overwriteCount} to overwrite`));
6629
+ if (skipCount)
6630
+ parts.push(chalk2.yellow(`${skipCount} skipped`));
6631
+ if (errorCount)
6632
+ parts.push(chalk2.red(`${errorCount} failed`));
6633
+ console.log(` ${chalk2.bold("Summary:")} ${parts.join(", ")}`);
6634
+ console.log(chalk2.dim(`
6635
+ Run without --dry-run to apply.
6636
+ `));
6637
+ process.exit(errorCount > 0 ? 1 : 0);
6638
+ return;
6639
+ }
6188
6640
  const results = connectors.map((name) => installConnector(name, { overwrite: options.overwrite }));
6189
6641
  if (options.json) {
6190
6642
  console.log(JSON.stringify(results, null, 2));
@@ -6216,20 +6668,74 @@ Next steps:`));
6216
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) => {
6217
6669
  if (options.installed) {
6218
6670
  const installed = getInstalledConnectors();
6219
- if (options.json) {
6220
- console.log(JSON.stringify(installed));
6671
+ if (installed.length === 0) {
6672
+ if (options.json) {
6673
+ console.log(JSON.stringify([]));
6674
+ } else {
6675
+ console.log(chalk2.dim("No connectors installed"));
6676
+ }
6221
6677
  return;
6222
6678
  }
6223
- if (installed.length === 0) {
6224
- console.log(chalk2.dim("No connectors installed"));
6679
+ const statuses = installed.map((name) => {
6680
+ const meta = getConnector(name);
6681
+ const auth = getAuthStatus(name);
6682
+ let expiryLabel = null;
6683
+ let expired = false;
6684
+ if (auth.type === "oauth" && auth.tokenExpiry) {
6685
+ const remaining = auth.tokenExpiry - Date.now();
6686
+ if (remaining <= 0) {
6687
+ expiryLabel = "Expired";
6688
+ expired = true;
6689
+ } else {
6690
+ const minutes = Math.floor(remaining / 60000);
6691
+ if (minutes < 60) {
6692
+ expiryLabel = `Expires ${minutes}m`;
6693
+ } else {
6694
+ const hours = Math.floor(minutes / 60);
6695
+ expiryLabel = `Expires ${hours}h`;
6696
+ }
6697
+ }
6698
+ }
6699
+ return {
6700
+ name,
6701
+ category: meta?.category || "Unknown",
6702
+ authType: auth.type,
6703
+ configured: auth.configured,
6704
+ expired,
6705
+ expiryLabel,
6706
+ tokenExpiry: auth.tokenExpiry || null,
6707
+ hasRefreshToken: auth.hasRefreshToken || false
6708
+ };
6709
+ });
6710
+ if (options.json) {
6711
+ console.log(JSON.stringify(statuses, null, 2));
6225
6712
  return;
6226
6713
  }
6714
+ const nameWidth = Math.max(6, ...statuses.map((s) => s.name.length)) + 2;
6715
+ const catWidth = Math.max(10, ...statuses.map((s) => s.category.length)) + 2;
6716
+ const authWidth = 10;
6227
6717
  console.log(chalk2.bold(`
6228
6718
  Installed connectors (${installed.length}):
6229
6719
  `));
6230
- for (const name of installed) {
6231
- console.log(` ${name}`);
6720
+ console.log(` ${chalk2.dim("Name".padEnd(nameWidth))}` + `${chalk2.dim("Category".padEnd(catWidth))}` + `${chalk2.dim("Auth Type".padEnd(authWidth))}` + `${chalk2.dim("Status")}`);
6721
+ console.log(chalk2.dim(` ${"\u2500".repeat(nameWidth + catWidth + authWidth + 24)}`));
6722
+ for (const s of statuses) {
6723
+ const authTypeLabel = s.authType === "oauth" ? "OAuth" : s.authType === "apikey" ? "API Key" : "Bearer";
6724
+ let statusLabel;
6725
+ if (s.configured && s.expired) {
6726
+ statusLabel = chalk2.yellow("\u26A0 Token expired");
6727
+ } else if (s.configured) {
6728
+ statusLabel = chalk2.green("\u2713 Configured");
6729
+ } else {
6730
+ statusLabel = chalk2.red("\u2717 Needs auth");
6731
+ }
6732
+ let expiryStr = "";
6733
+ if (s.expiryLabel && s.configured && !s.expired) {
6734
+ expiryStr = ` ${chalk2.dim(s.expiryLabel)}`;
6735
+ }
6736
+ console.log(` ${chalk2.cyan(s.name.padEnd(nameWidth))}` + `${s.category.padEnd(catWidth)}` + `${authTypeLabel.padEnd(authWidth)}` + `${statusLabel}${expiryStr}`);
6232
6737
  }
6738
+ console.log();
6233
6739
  return;
6234
6740
  }
6235
6741
  if (options.category) {
@@ -6449,7 +6955,7 @@ Starting Connectors Dashboard...
6449
6955
  const { startServer: startServer2 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
6450
6956
  await startServer2(port, { open: options.open });
6451
6957
  });
6452
- program2.command("update").description("Update all installed connectors to the latest version from the package").option("--json", "Output as JSON", false).action((options) => {
6958
+ program2.command("update").argument("[connectors...]", "Specific connectors to update (default: all installed)").description("Update installed connectors to the latest version from the package").option("-a, --all", "Update all without prompting", false).option("--json", "Output as JSON", false).action(async (connectors, options) => {
6453
6959
  const installed = getInstalledConnectors();
6454
6960
  if (installed.length === 0) {
6455
6961
  if (options.json) {
@@ -6459,14 +6965,57 @@ program2.command("update").description("Update all installed connectors to the l
6459
6965
  }
6460
6966
  return;
6461
6967
  }
6462
- const results = installed.map((name) => installConnector(name, { overwrite: true }));
6968
+ let toUpdate;
6969
+ if (connectors.length > 0) {
6970
+ const notInstalled = connectors.filter((name) => !installed.includes(name));
6971
+ if (notInstalled.length > 0) {
6972
+ if (options.json) {
6973
+ console.log(JSON.stringify({ error: `Not installed: ${notInstalled.join(", ")}` }));
6974
+ } else {
6975
+ console.log(chalk2.red(`Not installed: ${notInstalled.join(", ")}`));
6976
+ console.log(chalk2.dim("Installed connectors: " + installed.join(", ")));
6977
+ }
6978
+ process.exit(1);
6979
+ return;
6980
+ }
6981
+ toUpdate = connectors;
6982
+ } else if (options.all || !isTTY) {
6983
+ toUpdate = installed;
6984
+ } else {
6985
+ console.log(chalk2.bold(`
6986
+ Installed connectors (${installed.length}):
6987
+ `));
6988
+ for (const name of installed) {
6989
+ console.log(` ${chalk2.cyan(name)}`);
6990
+ }
6991
+ console.log();
6992
+ const confirmed = await new Promise((resolve) => {
6993
+ const rl = createInterface({
6994
+ input: process.stdin,
6995
+ output: process.stdout
6996
+ });
6997
+ rl.question(` Update all ${installed.length} connector(s)? (y/N) `, (answer) => {
6998
+ rl.close();
6999
+ resolve(answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes");
7000
+ });
7001
+ });
7002
+ if (!confirmed) {
7003
+ console.log(chalk2.dim(`
7004
+ Aborted. Use 'connectors update <name1> <name2>' to update specific connectors.
7005
+ `));
7006
+ process.exit(0);
7007
+ return;
7008
+ }
7009
+ toUpdate = installed;
7010
+ }
7011
+ const results = toUpdate.map((name) => installConnector(name, { overwrite: true }));
6463
7012
  if (options.json) {
6464
7013
  console.log(JSON.stringify(results, null, 2));
6465
7014
  process.exit(results.every((r) => r.success) ? 0 : 1);
6466
7015
  return;
6467
7016
  }
6468
7017
  console.log(chalk2.bold(`
6469
- Updating ${installed.length} connector(s)...
7018
+ Updating ${toUpdate.length} connector(s)...
6470
7019
  `));
6471
7020
  for (const result of results) {
6472
7021
  if (result.success) {
@@ -6477,4 +7026,481 @@ Updating ${installed.length} connector(s)...
6477
7026
  }
6478
7027
  process.exit(results.every((r) => r.success) ? 0 : 1);
6479
7028
  });
7029
+ program2.command("status").option("--json", "Output as JSON", false).description("Show auth status of installed connectors").action((options) => {
7030
+ const installed = getInstalledConnectors();
7031
+ if (installed.length === 0) {
7032
+ if (options.json) {
7033
+ console.log(JSON.stringify([]));
7034
+ } else {
7035
+ console.log(chalk2.dim("No connectors installed. Run: connectors install <name>"));
7036
+ }
7037
+ return;
7038
+ }
7039
+ const statuses = installed.map((name) => {
7040
+ const meta = getConnector(name);
7041
+ const auth = getAuthStatus(name);
7042
+ let expiryLabel = null;
7043
+ let expired = false;
7044
+ if (auth.type === "oauth" && auth.tokenExpiry) {
7045
+ const remaining = auth.tokenExpiry - Date.now();
7046
+ if (remaining <= 0) {
7047
+ expiryLabel = "Expired";
7048
+ expired = true;
7049
+ } else {
7050
+ const minutes = Math.floor(remaining / 60000);
7051
+ if (minutes < 60) {
7052
+ expiryLabel = `Expires ${minutes}m`;
7053
+ } else {
7054
+ const hours = Math.floor(minutes / 60);
7055
+ expiryLabel = `Expires ${hours}h`;
7056
+ }
7057
+ }
7058
+ }
7059
+ return {
7060
+ name,
7061
+ category: meta?.category || "Unknown",
7062
+ authType: auth.type,
7063
+ configured: auth.configured,
7064
+ expired,
7065
+ expiryLabel,
7066
+ tokenExpiry: auth.tokenExpiry || null,
7067
+ hasRefreshToken: auth.hasRefreshToken || false
7068
+ };
7069
+ });
7070
+ if (options.json) {
7071
+ console.log(JSON.stringify(statuses, null, 2));
7072
+ return;
7073
+ }
7074
+ const nameWidth = Math.max(6, ...statuses.map((s) => s.name.length)) + 2;
7075
+ const catWidth = Math.max(10, ...statuses.map((s) => s.category.length)) + 2;
7076
+ const authWidth = 10;
7077
+ console.log(chalk2.bold(`
7078
+ Connector Status
7079
+ `));
7080
+ console.log(` ${chalk2.dim("Name".padEnd(nameWidth))}` + `${chalk2.dim("Category".padEnd(catWidth))}` + `${chalk2.dim("Auth Type".padEnd(authWidth))}` + `${chalk2.dim("Status")}`);
7081
+ console.log(chalk2.dim(` ${"\u2500".repeat(nameWidth + catWidth + authWidth + 24)}`));
7082
+ for (const s of statuses) {
7083
+ const authTypeLabel = s.authType === "oauth" ? "OAuth" : s.authType === "apikey" ? "API Key" : "Bearer";
7084
+ let statusLabel;
7085
+ if (s.configured && s.expired) {
7086
+ statusLabel = chalk2.yellow("\u26A0 Token expired");
7087
+ } else if (s.configured) {
7088
+ statusLabel = chalk2.green("\u2713 Configured");
7089
+ } else {
7090
+ statusLabel = chalk2.red("\u2717 Not configured");
7091
+ }
7092
+ let expiryStr = "";
7093
+ if (s.expiryLabel && s.configured && !s.expired) {
7094
+ expiryStr = ` ${chalk2.dim(s.expiryLabel)}`;
7095
+ }
7096
+ console.log(` ${chalk2.cyan(s.name.padEnd(nameWidth))}` + `${s.category.padEnd(catWidth)}` + `${authTypeLabel.padEnd(authWidth)}` + `${statusLabel}${expiryStr}`);
7097
+ }
7098
+ console.log();
7099
+ });
7100
+ program2.command("doctor").option("--json", "Output as JSON", false).description("Check all installed connectors for issues and output a health report").action((options) => {
7101
+ const installed = getInstalledConnectors();
7102
+ if (installed.length === 0) {
7103
+ if (options.json) {
7104
+ console.log(JSON.stringify({ connectors: [], summary: { healthy: 0, warnings: 0, errors: 0 } }));
7105
+ } else {
7106
+ console.log(chalk2.dim("No connectors installed. Run: connectors install <name>"));
7107
+ }
7108
+ return;
7109
+ }
7110
+ const ONE_HOUR = 60 * 60 * 1000;
7111
+ const results = installed.map((name) => {
7112
+ const meta = getConnector(name);
7113
+ const auth = getAuthStatus(name);
7114
+ const issues = [];
7115
+ const suggestions = [];
7116
+ let level = "healthy";
7117
+ if (!auth.configured) {
7118
+ level = "error";
7119
+ issues.push("Not configured");
7120
+ if (auth.type === "oauth") {
7121
+ suggestions.push(`Run 'connectors serve' and authenticate ${name} via OAuth`);
7122
+ } else {
7123
+ suggestions.push(`Run 'connectors auth ${name}' to configure`);
7124
+ }
7125
+ } else if (auth.type === "oauth" && auth.tokenExpiry) {
7126
+ const remaining = auth.tokenExpiry - Date.now();
7127
+ if (remaining <= 0) {
7128
+ level = "error";
7129
+ issues.push("Token expired");
7130
+ if (auth.hasRefreshToken) {
7131
+ suggestions.push(`Run 'connectors serve' and refresh the token for ${name}`);
7132
+ } else {
7133
+ suggestions.push(`Run 'connectors serve' and re-authenticate ${name} via OAuth`);
7134
+ }
7135
+ } else if (remaining < ONE_HOUR) {
7136
+ level = "warning";
7137
+ const minutes = Math.floor(remaining / 60000);
7138
+ issues.push(`Token expiring soon (${minutes}m remaining)`);
7139
+ suggestions.push(`Refresh the token for ${name} before it expires`);
7140
+ }
7141
+ }
7142
+ if (auth.configured && auth.envVarTotalCount > 1 && auth.envVarSetCount < auth.envVarTotalCount) {
7143
+ if (level === "healthy")
7144
+ level = "warning";
7145
+ issues.push(`Partially configured (${auth.envVarSetCount}/${auth.envVarTotalCount} env vars set)`);
7146
+ const missingVars = auth.envVars.filter((v) => !v.set).map((v) => v.variable);
7147
+ suggestions.push(`Set missing env vars: ${missingVars.join(", ")}`);
7148
+ }
7149
+ return {
7150
+ name,
7151
+ displayName: meta?.displayName || name,
7152
+ category: meta?.category || "Unknown",
7153
+ authType: auth.type,
7154
+ level,
7155
+ issues,
7156
+ suggestions
7157
+ };
7158
+ });
7159
+ const summary = {
7160
+ healthy: results.filter((r) => r.level === "healthy").length,
7161
+ warnings: results.filter((r) => r.level === "warning").length,
7162
+ errors: results.filter((r) => r.level === "error").length
7163
+ };
7164
+ if (options.json) {
7165
+ console.log(JSON.stringify({ connectors: results, summary }, null, 2));
7166
+ process.exit(summary.errors > 0 ? 1 : 0);
7167
+ return;
7168
+ }
7169
+ console.log(chalk2.bold(`
7170
+ Connector Health Report
7171
+ `));
7172
+ for (const r of results) {
7173
+ let icon;
7174
+ if (r.level === "healthy") {
7175
+ icon = chalk2.green("\u2713");
7176
+ } else if (r.level === "warning") {
7177
+ icon = chalk2.yellow("\u26A0");
7178
+ } else {
7179
+ icon = chalk2.red("\u2717");
7180
+ }
7181
+ const nameStr = r.level === "healthy" ? chalk2.green(r.name) : r.level === "warning" ? chalk2.yellow(r.name) : chalk2.red(r.name);
7182
+ if (r.issues.length === 0) {
7183
+ console.log(` ${icon} ${nameStr} \u2014 ${chalk2.green("healthy")}`);
7184
+ } else {
7185
+ console.log(` ${icon} ${nameStr} \u2014 ${r.issues.join(", ")}`);
7186
+ for (const suggestion of r.suggestions) {
7187
+ console.log(chalk2.dim(` \u2192 ${suggestion}`));
7188
+ }
7189
+ }
7190
+ }
7191
+ const parts = [];
7192
+ if (summary.healthy > 0)
7193
+ parts.push(chalk2.green(`${summary.healthy} healthy`));
7194
+ if (summary.warnings > 0)
7195
+ parts.push(chalk2.yellow(`${summary.warnings} warning${summary.warnings !== 1 ? "s" : ""}`));
7196
+ if (summary.errors > 0)
7197
+ parts.push(chalk2.red(`${summary.errors} error${summary.errors !== 1 ? "s" : ""}`));
7198
+ console.log(`
7199
+ ${chalk2.bold("Summary:")} ${parts.join(", ")}`);
7200
+ if (summary.errors > 0 || summary.warnings > 0) {
7201
+ console.log(chalk2.dim(`
7202
+ Run 'connectors auth <name>' to configure individual connectors.`));
7203
+ console.log(chalk2.dim(` Run 'connectors serve' to manage auth via the dashboard.
7204
+ `));
7205
+ } else {
7206
+ console.log(chalk2.green(`
7207
+ All connectors are healthy!
7208
+ `));
7209
+ }
7210
+ process.exit(summary.errors > 0 ? 1 : 0);
7211
+ });
7212
+ program2.command("auth").argument("<connector>", "Connector name to configure auth for").option("-k, --key <value>", "API key or bearer token value (non-interactive)").option("-f, --field <field>", "Which field to set (for multi-field connectors)").option("--json", "Output as JSON", false).description("Configure authentication for a connector").action(async (connector, options) => {
7213
+ const meta = getConnector(connector);
7214
+ if (!meta) {
7215
+ if (options.json) {
7216
+ console.log(JSON.stringify({ error: `Connector '${connector}' not found` }));
7217
+ } else {
7218
+ console.log(chalk2.red(`Connector '${connector}' not found`));
7219
+ }
7220
+ process.exit(1);
7221
+ return;
7222
+ }
7223
+ const authType = getAuthType(connector);
7224
+ const statusBefore = getAuthStatus(connector);
7225
+ if (!options.json) {
7226
+ const statusLabel = statusBefore.configured ? chalk2.green("configured") : chalk2.red("not configured");
7227
+ console.log(chalk2.bold(`
7228
+ ${meta.displayName} \u2014 Auth Configuration
7229
+ `));
7230
+ console.log(` Auth type: ${authType === "oauth" ? "OAuth" : authType === "apikey" ? "API Key" : "Bearer Token"}`);
7231
+ console.log(` Status: ${statusLabel}`);
7232
+ const envVars2 = getEnvVars(connector);
7233
+ if (envVars2.length > 0) {
7234
+ console.log(` Fields: ${envVars2.map((v) => v.variable).join(", ")}`);
7235
+ }
7236
+ console.log();
7237
+ }
7238
+ if (authType === "oauth") {
7239
+ if (options.json) {
7240
+ console.log(JSON.stringify({
7241
+ connector,
7242
+ authType: "oauth",
7243
+ message: "OAuth connectors require browser-based authentication. Use 'connectors serve' or pass --key to set tokens manually."
7244
+ }));
7245
+ process.exit(0);
7246
+ return;
7247
+ }
7248
+ console.log(chalk2.yellow("OAuth connectors require browser-based authentication."));
7249
+ console.log();
7250
+ try {
7251
+ const port = 19426 + Math.floor(Math.random() * 1000);
7252
+ const { startServer: startServer2 } = await Promise.resolve().then(() => (init_serve(), exports_serve));
7253
+ console.log(chalk2.dim(`Starting temporary server on port ${port}...`));
7254
+ await startServer2(port, { open: false });
7255
+ const oauthUrl = `http://localhost:${port}/oauth/${connector}/start`;
7256
+ console.log(chalk2.bold(`
7257
+ Open this URL to authenticate:
7258
+ `));
7259
+ console.log(` ${chalk2.cyan(oauthUrl)}
7260
+ `);
7261
+ try {
7262
+ const { exec } = await import("child_process");
7263
+ const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
7264
+ exec(`${openCmd} "${oauthUrl}"`);
7265
+ console.log(chalk2.dim("Browser opened. Complete the OAuth flow, then press Ctrl+C to stop the server."));
7266
+ } catch {
7267
+ console.log(chalk2.dim("Open the URL above in your browser to complete authentication."));
7268
+ }
7269
+ console.log(chalk2.dim(`Press Ctrl+C when done.
7270
+ `));
7271
+ await new Promise(() => {});
7272
+ } catch (err) {
7273
+ console.log(chalk2.red(`Failed to start OAuth flow: ${err}`));
7274
+ console.log(chalk2.dim("Try 'connectors serve' to use the full dashboard instead."));
7275
+ process.exit(1);
7276
+ }
7277
+ return;
7278
+ }
7279
+ if (options.key) {
7280
+ saveApiKey(connector, options.key, options.field || undefined);
7281
+ const statusAfter2 = getAuthStatus(connector);
7282
+ if (options.json) {
7283
+ console.log(JSON.stringify({
7284
+ connector,
7285
+ authType,
7286
+ configured: statusAfter2.configured,
7287
+ field: options.field || null
7288
+ }));
7289
+ } else {
7290
+ console.log(chalk2.green(`\u2713 API key saved for ${meta.displayName}`));
7291
+ if (options.field) {
7292
+ console.log(chalk2.dim(` Field: ${options.field}`));
7293
+ }
7294
+ }
7295
+ process.exit(0);
7296
+ return;
7297
+ }
7298
+ if (!isTTY) {
7299
+ if (options.json) {
7300
+ console.log(JSON.stringify({ error: "Interactive mode requires a TTY. Use --key flag." }));
7301
+ } else {
7302
+ console.log(chalk2.red("Interactive mode requires a TTY. Use --key <value> to set non-interactively."));
7303
+ }
7304
+ process.exit(1);
7305
+ return;
7306
+ }
7307
+ const envVars = getEnvVars(connector);
7308
+ const fieldLabel = options.field ? options.field : envVars.length > 0 ? envVars[0].variable : "API Key";
7309
+ const rl = createInterface({
7310
+ input: process.stdin,
7311
+ output: process.stdout
7312
+ });
7313
+ const key = await new Promise((resolve) => {
7314
+ let input = "";
7315
+ process.stdout.write(` Enter ${fieldLabel}: `);
7316
+ if (process.stdin.isTTY) {
7317
+ process.stdin.setRawMode(true);
7318
+ }
7319
+ process.stdin.resume();
7320
+ process.stdin.setEncoding("utf-8");
7321
+ const onData = (ch) => {
7322
+ const c = ch.toString();
7323
+ if (c === `
7324
+ ` || c === "\r" || c === "\x04") {
7325
+ process.stdout.write(`
7326
+ `);
7327
+ process.stdin.removeListener("data", onData);
7328
+ if (process.stdin.isTTY) {
7329
+ process.stdin.setRawMode(false);
7330
+ }
7331
+ process.stdin.pause();
7332
+ rl.close();
7333
+ resolve(input);
7334
+ } else if (c === "\x03") {
7335
+ process.stdout.write(`
7336
+ `);
7337
+ rl.close();
7338
+ process.exit(0);
7339
+ } else if (c === "\x7F" || c === "\b") {
7340
+ if (input.length > 0) {
7341
+ input = input.slice(0, -1);
7342
+ process.stdout.write("\b \b");
7343
+ }
7344
+ } else {
7345
+ input += c;
7346
+ process.stdout.write("*");
7347
+ }
7348
+ };
7349
+ process.stdin.on("data", onData);
7350
+ });
7351
+ if (!key.trim()) {
7352
+ console.log(chalk2.red(`
7353
+ No key provided. Aborting.`));
7354
+ process.exit(1);
7355
+ return;
7356
+ }
7357
+ saveApiKey(connector, key.trim(), options.field || undefined);
7358
+ const statusAfter = getAuthStatus(connector);
7359
+ console.log(chalk2.green(`
7360
+ \u2713 API key saved for ${meta.displayName}`));
7361
+ if (statusAfter.configured) {
7362
+ console.log(chalk2.green(` Status: configured`));
7363
+ }
7364
+ process.exit(0);
7365
+ });
7366
+ program2.command("init").option("--json", "Output categories and connectors as JSON (non-interactive)", false).description("Guided onboarding: pick categories, choose connectors, install them").action(async (options) => {
7367
+ if (options.json) {
7368
+ const data = CATEGORIES.map((category) => ({
7369
+ name: category,
7370
+ connectors: getConnectorsByCategory(category).map((c) => ({
7371
+ name: c.name,
7372
+ displayName: c.displayName,
7373
+ description: c.description,
7374
+ version: c.version || null
7375
+ }))
7376
+ }));
7377
+ console.log(JSON.stringify(data, null, 2));
7378
+ process.exit(0);
7379
+ return;
7380
+ }
7381
+ if (!isTTY) {
7382
+ console.error("Interactive mode requires a TTY. Use --json for non-interactive output.");
7383
+ process.exit(1);
7384
+ return;
7385
+ }
7386
+ function ask(question) {
7387
+ return new Promise((resolve) => {
7388
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
7389
+ rl.question(question, (answer) => {
7390
+ rl.close();
7391
+ resolve(answer.trim());
7392
+ });
7393
+ });
7394
+ }
7395
+ console.log();
7396
+ console.log(chalk2.bold("Welcome to Connectors!"));
7397
+ console.log(chalk2.dim(`Let's get you set up with the API connectors you need.
7398
+ `));
7399
+ console.log(chalk2.bold(`Available categories:
7400
+ `));
7401
+ const categoryList = CATEGORIES.map((cat) => ({
7402
+ name: cat,
7403
+ connectors: getConnectorsByCategory(cat)
7404
+ }));
7405
+ for (let i = 0;i < categoryList.length; i++) {
7406
+ const c = categoryList[i];
7407
+ console.log(` ${chalk2.cyan(String(i + 1).padStart(2))}. ${c.name} ${chalk2.dim(`(${c.connectors.length} connectors)`)}`);
7408
+ }
7409
+ console.log();
7410
+ const catAnswer = await ask(chalk2.bold("Pick categories") + chalk2.dim(" (comma-separated numbers, e.g. 1,3,5): "));
7411
+ if (!catAnswer) {
7412
+ console.log(chalk2.dim(`
7413
+ No categories selected. Exiting.
7414
+ `));
7415
+ process.exit(0);
7416
+ return;
7417
+ }
7418
+ const catIndices = catAnswer.split(",").map((s) => parseInt(s.trim(), 10) - 1).filter((i) => i >= 0 && i < categoryList.length);
7419
+ if (catIndices.length === 0) {
7420
+ console.log(chalk2.red(`
7421
+ No valid categories selected. Exiting.
7422
+ `));
7423
+ process.exit(1);
7424
+ return;
7425
+ }
7426
+ const selectedCategories = catIndices.map((i) => categoryList[i]);
7427
+ const seen = new Set;
7428
+ const connectorPool = [];
7429
+ for (const cat of selectedCategories) {
7430
+ for (const c of cat.connectors) {
7431
+ if (!seen.has(c.name)) {
7432
+ seen.add(c.name);
7433
+ connectorPool.push({
7434
+ name: c.name,
7435
+ displayName: c.displayName,
7436
+ description: c.description,
7437
+ category: cat.name
7438
+ });
7439
+ }
7440
+ }
7441
+ }
7442
+ console.log();
7443
+ console.log(chalk2.bold(`Connectors in ${selectedCategories.map((c) => c.name).join(", ")}:
7444
+ `));
7445
+ const nameWidth = Math.max(12, ...connectorPool.map((c) => c.name.length)) + 2;
7446
+ for (let i = 0;i < connectorPool.length; i++) {
7447
+ const c = connectorPool[i];
7448
+ console.log(` ${chalk2.cyan(String(i + 1).padStart(3))}. ${c.name.padEnd(nameWidth)}${chalk2.dim(c.description)}`);
7449
+ }
7450
+ console.log();
7451
+ const connAnswer = await ask(chalk2.bold("Install which connectors?") + chalk2.dim(" (comma-separated numbers, or 'all'): "));
7452
+ if (!connAnswer) {
7453
+ console.log(chalk2.dim(`
7454
+ No connectors selected. Exiting.
7455
+ `));
7456
+ process.exit(0);
7457
+ return;
7458
+ }
7459
+ let toInstall;
7460
+ if (connAnswer.toLowerCase() === "all") {
7461
+ toInstall = connectorPool.map((c) => c.name);
7462
+ } else {
7463
+ const connIndices = connAnswer.split(",").map((s) => parseInt(s.trim(), 10) - 1).filter((i) => i >= 0 && i < connectorPool.length);
7464
+ if (connIndices.length === 0) {
7465
+ console.log(chalk2.red(`
7466
+ No valid connectors selected. Exiting.
7467
+ `));
7468
+ process.exit(1);
7469
+ return;
7470
+ }
7471
+ toInstall = connIndices.map((i) => connectorPool[i].name);
7472
+ }
7473
+ console.log(chalk2.bold(`
7474
+ Installing ${toInstall.length} connector(s)...
7475
+ `));
7476
+ const results = toInstall.map((name) => installConnector(name, { overwrite: false }));
7477
+ const succeeded = [];
7478
+ for (const result of results) {
7479
+ if (result.success) {
7480
+ console.log(chalk2.green(` \u2713 ${result.connector}`));
7481
+ succeeded.push(result.connector);
7482
+ } else {
7483
+ console.log(chalk2.red(` \u2717 ${result.connector}: ${result.error}`));
7484
+ }
7485
+ }
7486
+ if (succeeded.length > 0) {
7487
+ console.log(chalk2.bold(`
7488
+ Next steps:
7489
+ `));
7490
+ console.log(` ${chalk2.dim("1.")} Import in your code:`);
7491
+ console.log(` ${chalk2.cyan(`import { ${succeeded.slice(0, 3).join(", ")}${succeeded.length > 3 ? ", ..." : ""} } from './.connectors'`)}`);
7492
+ console.log();
7493
+ console.log(` ${chalk2.dim("2.")} Configure authentication:`);
7494
+ console.log(` ${chalk2.cyan("connectors auth <name>")} ${chalk2.dim("\u2014 set API keys interactively")}`);
7495
+ console.log(` ${chalk2.cyan("connectors serve")} ${chalk2.dim("\u2014 open dashboard for OAuth setup")}`);
7496
+ console.log();
7497
+ console.log(` ${chalk2.dim("3.")} Check connector docs:`);
7498
+ console.log(` ${chalk2.cyan(`connectors docs ${succeeded[0]}`)} ${chalk2.dim("\u2014 see auth & env var details")}`);
7499
+ console.log();
7500
+ console.log(` ${chalk2.dim("4.")} Verify everything works:`);
7501
+ console.log(` ${chalk2.cyan("connectors doctor")} ${chalk2.dim("\u2014 health check all connectors")}`);
7502
+ }
7503
+ console.log();
7504
+ process.exit(results.every((r) => r.success) ? 0 : 1);
7505
+ });
6480
7506
  program2.parse();