@hasna/connectors 0.0.6 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -33
- package/bin/index.js +1266 -723
- package/bin/mcp.js +160 -1
- package/bin/serve.js +1056 -0
- package/connectors/connect-anthropic/package.json +2 -2
- package/connectors/connect-aws/package.json +2 -2
- package/connectors/connect-brandsight/package.json +2 -2
- package/connectors/connect-browseruse/package.json +2 -2
- package/connectors/connect-cloudflare/package.json +2 -2
- package/connectors/connect-discord/package.json +2 -2
- package/connectors/connect-docker/package.json +2 -2
- package/connectors/connect-e2b/package.json +2 -2
- package/connectors/connect-elevenlabs/package.json +2 -2
- package/connectors/connect-exa/package.json +2 -2
- package/connectors/connect-figma/package.json +2 -2
- package/connectors/connect-firecrawl/package.json +2 -2
- package/connectors/connect-github/package.json +2 -2
- package/connectors/connect-gmail/package.json +2 -2
- package/connectors/connect-google/package.json +2 -2
- package/connectors/connect-googlecalendar/package.json +2 -2
- package/connectors/connect-googlecloud/package.json +2 -2
- package/connectors/connect-googlecontacts/package.json +2 -2
- package/connectors/connect-googledocs/package.json +2 -2
- package/connectors/connect-googledrive/package.json +2 -2
- package/connectors/connect-googlegemini/package.json +2 -2
- package/connectors/connect-googlemaps/package.json +2 -2
- package/connectors/connect-googlesheets/package.json +2 -2
- package/connectors/connect-hedra/package.json +2 -2
- package/connectors/connect-heygen/package.json +2 -2
- package/connectors/connect-huggingface/package.json +2 -2
- package/connectors/connect-icons8/package.json +2 -2
- package/connectors/connect-maropost/package.json +2 -2
- package/connectors/connect-mercury/package.json +2 -2
- package/connectors/connect-meta/package.json +2 -2
- package/connectors/connect-midjourney/package.json +2 -2
- package/connectors/connect-mistral/package.json +2 -2
- package/connectors/connect-mixpanel/package.json +2 -2
- package/connectors/connect-openai/package.json +2 -2
- package/connectors/connect-openweathermap/package.json +2 -2
- package/connectors/connect-pandadoc/package.json +2 -2
- package/connectors/connect-quo/package.json +2 -2
- package/connectors/connect-reddit/package.json +2 -2
- package/connectors/connect-resend/package.json +2 -2
- package/connectors/connect-revolut/package.json +2 -2
- package/connectors/connect-sedo/package.json +2 -2
- package/connectors/connect-sentry/package.json +2 -2
- package/connectors/connect-shadcn/package.json +2 -2
- package/connectors/connect-shopify/package.json +2 -2
- package/connectors/connect-snap/package.json +2 -2
- package/connectors/connect-stabilityai/package.json +2 -2
- package/connectors/connect-stripe/package.json +2 -2
- package/connectors/connect-stripeatlas/package.json +2 -2
- package/connectors/connect-substack/package.json +2 -2
- package/connectors/connect-tiktok/package.json +2 -2
- package/connectors/connect-tinker/package.json +2 -2
- package/connectors/connect-twilio/package.json +2 -2
- package/connectors/connect-uspto/package.json +2 -2
- package/connectors/connect-webflow/package.json +2 -2
- package/connectors/connect-wix/package.json +2 -2
- package/connectors/connect-x/package.json +2 -2
- package/connectors/connect-xads/package.json +2 -2
- package/connectors/connect-xai/package.json +2 -2
- package/connectors/connect-youtube/package.json +2 -2
- package/connectors/connect-zoom/package.json +2 -2
- package/dashboard/dist/assets/index-BZZ_709y.css +1 -0
- package/dashboard/dist/assets/index-CBroKWCD.js +234 -0
- package/dashboard/dist/index.html +13 -0
- package/dashboard/dist/logo.jpg +0 -0
- package/package.json +8 -4
package/bin/serve.js
ADDED
|
@@ -0,0 +1,1056 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
var __require = import.meta.require;
|
|
4
|
+
|
|
5
|
+
// src/server/serve.ts
|
|
6
|
+
import { existsSync as existsSync4 } from "fs";
|
|
7
|
+
import { join as join4, dirname as dirname3, extname } from "path";
|
|
8
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
9
|
+
|
|
10
|
+
// src/lib/registry.ts
|
|
11
|
+
import { existsSync, readFileSync } from "fs";
|
|
12
|
+
import { join, dirname } from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
var CONNECTORS = [
|
|
15
|
+
{
|
|
16
|
+
name: "anthropic",
|
|
17
|
+
displayName: "Anthropic",
|
|
18
|
+
description: "Claude AI models and API",
|
|
19
|
+
category: "AI & ML",
|
|
20
|
+
tags: ["ai", "llm", "claude"]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "openai",
|
|
24
|
+
displayName: "OpenAI",
|
|
25
|
+
description: "GPT models, DALL-E, and Whisper",
|
|
26
|
+
category: "AI & ML",
|
|
27
|
+
tags: ["ai", "llm", "gpt", "dalle"]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "xai",
|
|
31
|
+
displayName: "xAI",
|
|
32
|
+
description: "Grok AI models",
|
|
33
|
+
category: "AI & ML",
|
|
34
|
+
tags: ["ai", "llm", "grok"]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "mistral",
|
|
38
|
+
displayName: "Mistral",
|
|
39
|
+
description: "Mistral AI models",
|
|
40
|
+
category: "AI & ML",
|
|
41
|
+
tags: ["ai", "llm"]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "googlegemini",
|
|
45
|
+
displayName: "Google Gemini",
|
|
46
|
+
description: "Gemini AI models",
|
|
47
|
+
category: "AI & ML",
|
|
48
|
+
tags: ["ai", "llm", "google"]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "huggingface",
|
|
52
|
+
displayName: "Hugging Face",
|
|
53
|
+
description: "ML models and datasets hub",
|
|
54
|
+
category: "AI & ML",
|
|
55
|
+
tags: ["ai", "ml", "models"]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "stabilityai",
|
|
59
|
+
displayName: "Stability AI",
|
|
60
|
+
description: "Stable Diffusion image generation",
|
|
61
|
+
category: "AI & ML",
|
|
62
|
+
tags: ["ai", "image", "generation"]
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "midjourney",
|
|
66
|
+
displayName: "Midjourney",
|
|
67
|
+
description: "AI image generation",
|
|
68
|
+
category: "AI & ML",
|
|
69
|
+
tags: ["ai", "image", "generation"]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "heygen",
|
|
73
|
+
displayName: "HeyGen",
|
|
74
|
+
description: "AI video generation",
|
|
75
|
+
category: "AI & ML",
|
|
76
|
+
tags: ["ai", "video", "avatar"]
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "hedra",
|
|
80
|
+
displayName: "Hedra",
|
|
81
|
+
description: "AI video generation",
|
|
82
|
+
category: "AI & ML",
|
|
83
|
+
tags: ["ai", "video"]
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: "elevenlabs",
|
|
87
|
+
displayName: "ElevenLabs",
|
|
88
|
+
description: "AI voice synthesis and cloning",
|
|
89
|
+
category: "AI & ML",
|
|
90
|
+
tags: ["ai", "voice", "tts"]
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "reducto",
|
|
94
|
+
displayName: "Reducto",
|
|
95
|
+
description: "Document processing and extraction",
|
|
96
|
+
category: "AI & ML",
|
|
97
|
+
tags: ["ai", "document", "ocr"]
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "github",
|
|
101
|
+
displayName: "GitHub",
|
|
102
|
+
description: "Repositories, issues, PRs, and actions",
|
|
103
|
+
category: "Developer Tools",
|
|
104
|
+
tags: ["git", "code", "vcs"]
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: "docker",
|
|
108
|
+
displayName: "Docker",
|
|
109
|
+
description: "Container management and registry",
|
|
110
|
+
category: "Developer Tools",
|
|
111
|
+
tags: ["containers", "devops"]
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "sentry",
|
|
115
|
+
displayName: "Sentry",
|
|
116
|
+
description: "Error tracking and monitoring",
|
|
117
|
+
category: "Developer Tools",
|
|
118
|
+
tags: ["monitoring", "errors"]
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "cloudflare",
|
|
122
|
+
displayName: "Cloudflare",
|
|
123
|
+
description: "DNS, CDN, and edge computing",
|
|
124
|
+
category: "Developer Tools",
|
|
125
|
+
tags: ["cdn", "dns", "edge"]
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "googlecloud",
|
|
129
|
+
displayName: "Google Cloud",
|
|
130
|
+
description: "GCP services and APIs",
|
|
131
|
+
category: "Developer Tools",
|
|
132
|
+
tags: ["cloud", "gcp"]
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: "aws",
|
|
136
|
+
displayName: "AWS",
|
|
137
|
+
description: "Amazon Web Services",
|
|
138
|
+
category: "Developer Tools",
|
|
139
|
+
tags: ["cloud", "aws"]
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "e2b",
|
|
143
|
+
displayName: "E2B",
|
|
144
|
+
description: "Code interpreter sandboxes",
|
|
145
|
+
category: "Developer Tools",
|
|
146
|
+
tags: ["sandbox", "code"]
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "firecrawl",
|
|
150
|
+
displayName: "Firecrawl",
|
|
151
|
+
description: "Web scraping and crawling",
|
|
152
|
+
category: "Developer Tools",
|
|
153
|
+
tags: ["scraping", "web"]
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "browseruse",
|
|
157
|
+
displayName: "Browser Use",
|
|
158
|
+
description: "Browser automation for AI",
|
|
159
|
+
category: "Developer Tools",
|
|
160
|
+
tags: ["browser", "automation"]
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: "shadcn",
|
|
164
|
+
displayName: "shadcn/ui",
|
|
165
|
+
description: "UI component registry",
|
|
166
|
+
category: "Developer Tools",
|
|
167
|
+
tags: ["ui", "components", "react"]
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "figma",
|
|
171
|
+
displayName: "Figma",
|
|
172
|
+
description: "Design files, components, and comments",
|
|
173
|
+
category: "Design & Content",
|
|
174
|
+
tags: ["design", "ui"]
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "webflow",
|
|
178
|
+
displayName: "Webflow",
|
|
179
|
+
description: "Website builder and CMS",
|
|
180
|
+
category: "Design & Content",
|
|
181
|
+
tags: ["website", "cms"]
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: "wix",
|
|
185
|
+
displayName: "Wix",
|
|
186
|
+
description: "Website builder",
|
|
187
|
+
category: "Design & Content",
|
|
188
|
+
tags: ["website"]
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: "icons8",
|
|
192
|
+
displayName: "Icons8",
|
|
193
|
+
description: "Icons and illustrations",
|
|
194
|
+
category: "Design & Content",
|
|
195
|
+
tags: ["icons", "assets"]
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: "gmail",
|
|
199
|
+
displayName: "Gmail",
|
|
200
|
+
description: "Email sending and management",
|
|
201
|
+
category: "Communication",
|
|
202
|
+
tags: ["email", "google"]
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: "discord",
|
|
206
|
+
displayName: "Discord",
|
|
207
|
+
description: "Messaging and communities",
|
|
208
|
+
category: "Communication",
|
|
209
|
+
tags: ["chat", "community"]
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: "twilio",
|
|
213
|
+
displayName: "Twilio",
|
|
214
|
+
description: "SMS, voice, and messaging",
|
|
215
|
+
category: "Communication",
|
|
216
|
+
tags: ["sms", "voice"]
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: "resend",
|
|
220
|
+
displayName: "Resend",
|
|
221
|
+
description: "Email API for developers",
|
|
222
|
+
category: "Communication",
|
|
223
|
+
tags: ["email", "api"]
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: "zoom",
|
|
227
|
+
displayName: "Zoom",
|
|
228
|
+
description: "Video meetings and webinars",
|
|
229
|
+
category: "Communication",
|
|
230
|
+
tags: ["video", "meetings"]
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: "maropost",
|
|
234
|
+
displayName: "Maropost",
|
|
235
|
+
description: "Email marketing automation",
|
|
236
|
+
category: "Communication",
|
|
237
|
+
tags: ["email", "marketing"]
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: "x",
|
|
241
|
+
displayName: "X (Twitter)",
|
|
242
|
+
description: "Posts, threads, and engagement",
|
|
243
|
+
category: "Social Media",
|
|
244
|
+
tags: ["social", "twitter"]
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
name: "reddit",
|
|
248
|
+
displayName: "Reddit",
|
|
249
|
+
description: "Posts, comments, and subreddits",
|
|
250
|
+
category: "Social Media",
|
|
251
|
+
tags: ["social", "community"]
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: "substack",
|
|
255
|
+
displayName: "Substack",
|
|
256
|
+
description: "Newsletter publishing",
|
|
257
|
+
category: "Social Media",
|
|
258
|
+
tags: ["newsletter", "writing"]
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: "meta",
|
|
262
|
+
displayName: "Meta",
|
|
263
|
+
description: "Facebook and Instagram APIs",
|
|
264
|
+
category: "Social Media",
|
|
265
|
+
tags: ["social", "facebook", "instagram"]
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
name: "snap",
|
|
269
|
+
displayName: "Snapchat",
|
|
270
|
+
description: "Snapchat marketing API",
|
|
271
|
+
category: "Social Media",
|
|
272
|
+
tags: ["social", "ads"]
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: "tiktok",
|
|
276
|
+
displayName: "TikTok",
|
|
277
|
+
description: "TikTok content and ads",
|
|
278
|
+
category: "Social Media",
|
|
279
|
+
tags: ["social", "video"]
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
name: "youtube",
|
|
283
|
+
displayName: "YouTube",
|
|
284
|
+
description: "Videos, channels, and analytics",
|
|
285
|
+
category: "Social Media",
|
|
286
|
+
tags: ["video", "google"]
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: "stripe",
|
|
290
|
+
displayName: "Stripe",
|
|
291
|
+
description: "Payments, subscriptions, and billing",
|
|
292
|
+
category: "Commerce & Finance",
|
|
293
|
+
tags: ["payments", "billing"]
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
name: "stripeatlas",
|
|
297
|
+
displayName: "Stripe Atlas",
|
|
298
|
+
description: "Company incorporation",
|
|
299
|
+
category: "Commerce & Finance",
|
|
300
|
+
tags: ["incorporation", "business"]
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: "shopify",
|
|
304
|
+
displayName: "Shopify",
|
|
305
|
+
description: "E-commerce platform",
|
|
306
|
+
category: "Commerce & Finance",
|
|
307
|
+
tags: ["ecommerce", "store"]
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
name: "revolut",
|
|
311
|
+
displayName: "Revolut",
|
|
312
|
+
description: "Banking and payments",
|
|
313
|
+
category: "Commerce & Finance",
|
|
314
|
+
tags: ["banking", "fintech"]
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: "mercury",
|
|
318
|
+
displayName: "Mercury",
|
|
319
|
+
description: "Startup banking",
|
|
320
|
+
category: "Commerce & Finance",
|
|
321
|
+
tags: ["banking", "startup"]
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name: "pandadoc",
|
|
325
|
+
displayName: "PandaDoc",
|
|
326
|
+
description: "Document signing and proposals",
|
|
327
|
+
category: "Commerce & Finance",
|
|
328
|
+
tags: ["documents", "esign"]
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
name: "google",
|
|
332
|
+
displayName: "Google",
|
|
333
|
+
description: "Google OAuth and APIs",
|
|
334
|
+
category: "Google Workspace",
|
|
335
|
+
tags: ["google", "auth"]
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name: "googledrive",
|
|
339
|
+
displayName: "Google Drive",
|
|
340
|
+
description: "File storage and sharing",
|
|
341
|
+
category: "Google Workspace",
|
|
342
|
+
tags: ["storage", "google"]
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: "googledocs",
|
|
346
|
+
displayName: "Google Docs",
|
|
347
|
+
description: "Document creation and editing",
|
|
348
|
+
category: "Google Workspace",
|
|
349
|
+
tags: ["documents", "google"]
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
name: "googlesheets",
|
|
353
|
+
displayName: "Google Sheets",
|
|
354
|
+
description: "Spreadsheets and data",
|
|
355
|
+
category: "Google Workspace",
|
|
356
|
+
tags: ["spreadsheets", "google"]
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: "googlecalendar",
|
|
360
|
+
displayName: "Google Calendar",
|
|
361
|
+
description: "Calendar and events",
|
|
362
|
+
category: "Google Workspace",
|
|
363
|
+
tags: ["calendar", "google"]
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
name: "googletasks",
|
|
367
|
+
displayName: "Google Tasks",
|
|
368
|
+
description: "Task management",
|
|
369
|
+
category: "Google Workspace",
|
|
370
|
+
tags: ["tasks", "google"]
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: "googlecontacts",
|
|
374
|
+
displayName: "Google Contacts",
|
|
375
|
+
description: "Contact management",
|
|
376
|
+
category: "Google Workspace",
|
|
377
|
+
tags: ["contacts", "google"]
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
name: "googlemaps",
|
|
381
|
+
displayName: "Google Maps",
|
|
382
|
+
description: "Maps, places, and directions",
|
|
383
|
+
category: "Google Workspace",
|
|
384
|
+
tags: ["maps", "google"]
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
name: "exa",
|
|
388
|
+
displayName: "Exa",
|
|
389
|
+
description: "AI-powered web search",
|
|
390
|
+
category: "Data & Analytics",
|
|
391
|
+
tags: ["search", "ai"]
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
name: "mixpanel",
|
|
395
|
+
displayName: "Mixpanel",
|
|
396
|
+
description: "Product analytics",
|
|
397
|
+
category: "Data & Analytics",
|
|
398
|
+
tags: ["analytics", "product"]
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
name: "openweathermap",
|
|
402
|
+
displayName: "OpenWeatherMap",
|
|
403
|
+
description: "Weather data and forecasts",
|
|
404
|
+
category: "Data & Analytics",
|
|
405
|
+
tags: ["weather", "data"]
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
name: "brandsight",
|
|
409
|
+
displayName: "Brandsight",
|
|
410
|
+
description: "Brand monitoring",
|
|
411
|
+
category: "Data & Analytics",
|
|
412
|
+
tags: ["brand", "monitoring"]
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
name: "notion",
|
|
416
|
+
displayName: "Notion",
|
|
417
|
+
description: "Pages, databases, blocks, and property management",
|
|
418
|
+
category: "Business Tools",
|
|
419
|
+
tags: ["productivity", "databases", "wiki", "notes"]
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
name: "quo",
|
|
423
|
+
displayName: "Quo",
|
|
424
|
+
description: "Business quotes and invoices",
|
|
425
|
+
category: "Business Tools",
|
|
426
|
+
tags: ["invoices", "quotes"]
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
name: "tinker",
|
|
430
|
+
displayName: "Tinker",
|
|
431
|
+
description: "Internal tooling",
|
|
432
|
+
category: "Business Tools",
|
|
433
|
+
tags: ["internal", "tools"]
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
name: "sedo",
|
|
437
|
+
displayName: "Sedo",
|
|
438
|
+
description: "Domain marketplace",
|
|
439
|
+
category: "Business Tools",
|
|
440
|
+
tags: ["domains", "marketplace"]
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
name: "uspto",
|
|
444
|
+
displayName: "USPTO",
|
|
445
|
+
description: "US Patent and Trademark Office",
|
|
446
|
+
category: "Patents & IP",
|
|
447
|
+
tags: ["patents", "trademarks", "ip"]
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
name: "xads",
|
|
451
|
+
displayName: "X Ads",
|
|
452
|
+
description: "Twitter/X advertising",
|
|
453
|
+
category: "Advertising",
|
|
454
|
+
tags: ["ads", "twitter"]
|
|
455
|
+
}
|
|
456
|
+
];
|
|
457
|
+
function getConnector(name) {
|
|
458
|
+
return CONNECTORS.find((c) => c.name === name);
|
|
459
|
+
}
|
|
460
|
+
var versionsLoaded = false;
|
|
461
|
+
function loadConnectorVersions() {
|
|
462
|
+
if (versionsLoaded)
|
|
463
|
+
return;
|
|
464
|
+
versionsLoaded = true;
|
|
465
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
466
|
+
const candidates = [
|
|
467
|
+
join(thisDir, "..", "connectors"),
|
|
468
|
+
join(thisDir, "..", "..", "connectors")
|
|
469
|
+
];
|
|
470
|
+
const connectorsDir = candidates.find((d) => existsSync(d));
|
|
471
|
+
if (!connectorsDir)
|
|
472
|
+
return;
|
|
473
|
+
for (const connector of CONNECTORS) {
|
|
474
|
+
try {
|
|
475
|
+
const pkgPath = join(connectorsDir, `connect-${connector.name}`, "package.json");
|
|
476
|
+
if (existsSync(pkgPath)) {
|
|
477
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
478
|
+
connector.version = pkg.version || "0.0.0";
|
|
479
|
+
}
|
|
480
|
+
} catch {}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// src/lib/installer.ts
|
|
485
|
+
import { existsSync as existsSync2, cpSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
486
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
487
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
488
|
+
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
489
|
+
function resolveConnectorsDir() {
|
|
490
|
+
const fromBin = join2(__dirname2, "..", "connectors");
|
|
491
|
+
if (existsSync2(fromBin))
|
|
492
|
+
return fromBin;
|
|
493
|
+
const fromSrc = join2(__dirname2, "..", "..", "connectors");
|
|
494
|
+
if (existsSync2(fromSrc))
|
|
495
|
+
return fromSrc;
|
|
496
|
+
return fromBin;
|
|
497
|
+
}
|
|
498
|
+
var CONNECTORS_DIR = resolveConnectorsDir();
|
|
499
|
+
function getConnectorPath(name) {
|
|
500
|
+
const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
|
|
501
|
+
return join2(CONNECTORS_DIR, connectorName);
|
|
502
|
+
}
|
|
503
|
+
function getInstalledConnectors(targetDir = process.cwd()) {
|
|
504
|
+
const connectorsDir = join2(targetDir, ".connectors");
|
|
505
|
+
if (!existsSync2(connectorsDir)) {
|
|
506
|
+
return [];
|
|
507
|
+
}
|
|
508
|
+
const { readdirSync, statSync } = __require("fs");
|
|
509
|
+
return readdirSync(connectorsDir).filter((f) => {
|
|
510
|
+
const fullPath = join2(connectorsDir, f);
|
|
511
|
+
return f.startsWith("connect-") && statSync(fullPath).isDirectory();
|
|
512
|
+
}).map((f) => f.replace("connect-", ""));
|
|
513
|
+
}
|
|
514
|
+
function getConnectorDocs(name) {
|
|
515
|
+
const connectorPath = getConnectorPath(name);
|
|
516
|
+
const claudeMdPath = join2(connectorPath, "CLAUDE.md");
|
|
517
|
+
if (!existsSync2(claudeMdPath))
|
|
518
|
+
return null;
|
|
519
|
+
const raw = readFileSync2(claudeMdPath, "utf-8");
|
|
520
|
+
return {
|
|
521
|
+
overview: extractSection(raw, "Project Overview"),
|
|
522
|
+
auth: extractSection(raw, "Authentication"),
|
|
523
|
+
envVars: parseEnvVarsTable(extractSection(raw, "Environment Variables")),
|
|
524
|
+
cliCommands: extractSection(raw, "CLI Commands"),
|
|
525
|
+
dataStorage: extractSection(raw, "Data Storage"),
|
|
526
|
+
raw
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
function extractSection(markdown, heading) {
|
|
530
|
+
const regex = new RegExp(`^##\\s+${escapeRegex(heading)}\\s*$`, "m");
|
|
531
|
+
const match = regex.exec(markdown);
|
|
532
|
+
if (!match)
|
|
533
|
+
return "";
|
|
534
|
+
const start = match.index + match[0].length;
|
|
535
|
+
const nextHeading = markdown.slice(start).search(/^##\s/m);
|
|
536
|
+
const content = nextHeading === -1 ? markdown.slice(start) : markdown.slice(start, start + nextHeading);
|
|
537
|
+
return content.trim();
|
|
538
|
+
}
|
|
539
|
+
function escapeRegex(str) {
|
|
540
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
541
|
+
}
|
|
542
|
+
function parseEnvVarsTable(section) {
|
|
543
|
+
if (!section)
|
|
544
|
+
return [];
|
|
545
|
+
const vars = [];
|
|
546
|
+
const lines = section.split(`
|
|
547
|
+
`);
|
|
548
|
+
for (const line of lines) {
|
|
549
|
+
const match = line.match(/\|\s*`([^`]+)`\s*\|\s*(.+?)\s*\|/);
|
|
550
|
+
if (match && match[1] !== "Variable") {
|
|
551
|
+
vars.push({ variable: match[1], description: match[2].trim() });
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return vars;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/server/auth.ts
|
|
558
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
559
|
+
import { homedir } from "os";
|
|
560
|
+
import { join as join3 } from "path";
|
|
561
|
+
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
562
|
+
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
563
|
+
var GOOGLE_SCOPES = {
|
|
564
|
+
gmail: [
|
|
565
|
+
"https://www.googleapis.com/auth/gmail.readonly",
|
|
566
|
+
"https://www.googleapis.com/auth/gmail.send",
|
|
567
|
+
"https://www.googleapis.com/auth/gmail.compose",
|
|
568
|
+
"https://www.googleapis.com/auth/gmail.modify",
|
|
569
|
+
"https://www.googleapis.com/auth/gmail.labels",
|
|
570
|
+
"https://mail.google.com/"
|
|
571
|
+
].join(" "),
|
|
572
|
+
googlecalendar: [
|
|
573
|
+
"https://www.googleapis.com/auth/calendar",
|
|
574
|
+
"https://www.googleapis.com/auth/calendar.events"
|
|
575
|
+
].join(" "),
|
|
576
|
+
googledrive: [
|
|
577
|
+
"https://www.googleapis.com/auth/drive",
|
|
578
|
+
"https://www.googleapis.com/auth/drive.file"
|
|
579
|
+
].join(" "),
|
|
580
|
+
googledocs: [
|
|
581
|
+
"https://www.googleapis.com/auth/documents",
|
|
582
|
+
"https://www.googleapis.com/auth/drive.file"
|
|
583
|
+
].join(" "),
|
|
584
|
+
googlesheets: [
|
|
585
|
+
"https://www.googleapis.com/auth/spreadsheets",
|
|
586
|
+
"https://www.googleapis.com/auth/drive.file"
|
|
587
|
+
].join(" "),
|
|
588
|
+
googletasks: [
|
|
589
|
+
"https://www.googleapis.com/auth/tasks"
|
|
590
|
+
].join(" "),
|
|
591
|
+
googlecontacts: [
|
|
592
|
+
"https://www.googleapis.com/auth/contacts",
|
|
593
|
+
"https://www.googleapis.com/auth/contacts.readonly"
|
|
594
|
+
].join(" "),
|
|
595
|
+
google: [
|
|
596
|
+
"openid",
|
|
597
|
+
"https://www.googleapis.com/auth/userinfo.email",
|
|
598
|
+
"https://www.googleapis.com/auth/userinfo.profile"
|
|
599
|
+
].join(" ")
|
|
600
|
+
};
|
|
601
|
+
function getAuthType(name) {
|
|
602
|
+
const docs = getConnectorDocs(name);
|
|
603
|
+
if (!docs?.auth)
|
|
604
|
+
return "apikey";
|
|
605
|
+
const authLower = docs.auth.toLowerCase();
|
|
606
|
+
if (authLower.includes("oauth"))
|
|
607
|
+
return "oauth";
|
|
608
|
+
if (authLower.includes("bearer token"))
|
|
609
|
+
return "bearer";
|
|
610
|
+
return "apikey";
|
|
611
|
+
}
|
|
612
|
+
function getConnectorConfigDir(name) {
|
|
613
|
+
const connectorName = name.startsWith("connect-") ? name : `connect-${name}`;
|
|
614
|
+
return join3(homedir(), ".connect", connectorName);
|
|
615
|
+
}
|
|
616
|
+
function getCurrentProfile(name) {
|
|
617
|
+
const configDir = getConnectorConfigDir(name);
|
|
618
|
+
const currentProfileFile = join3(configDir, "current_profile");
|
|
619
|
+
if (existsSync3(currentProfileFile)) {
|
|
620
|
+
try {
|
|
621
|
+
return readFileSync3(currentProfileFile, "utf-8").trim() || "default";
|
|
622
|
+
} catch {
|
|
623
|
+
return "default";
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return "default";
|
|
627
|
+
}
|
|
628
|
+
function loadProfileConfig(name) {
|
|
629
|
+
const configDir = getConnectorConfigDir(name);
|
|
630
|
+
const profile = getCurrentProfile(name);
|
|
631
|
+
const profileFile = join3(configDir, "profiles", `${profile}.json`);
|
|
632
|
+
if (existsSync3(profileFile)) {
|
|
633
|
+
try {
|
|
634
|
+
return JSON.parse(readFileSync3(profileFile, "utf-8"));
|
|
635
|
+
} catch {}
|
|
636
|
+
}
|
|
637
|
+
const profileDirConfig = join3(configDir, "profiles", profile, "config.json");
|
|
638
|
+
if (existsSync3(profileDirConfig)) {
|
|
639
|
+
try {
|
|
640
|
+
return JSON.parse(readFileSync3(profileDirConfig, "utf-8"));
|
|
641
|
+
} catch {}
|
|
642
|
+
}
|
|
643
|
+
return {};
|
|
644
|
+
}
|
|
645
|
+
function loadTokens(name) {
|
|
646
|
+
const configDir = getConnectorConfigDir(name);
|
|
647
|
+
const profile = getCurrentProfile(name);
|
|
648
|
+
const tokensFile = join3(configDir, "profiles", profile, "tokens.json");
|
|
649
|
+
if (existsSync3(tokensFile)) {
|
|
650
|
+
try {
|
|
651
|
+
return JSON.parse(readFileSync3(tokensFile, "utf-8"));
|
|
652
|
+
} catch {
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
function getAuthStatus(name) {
|
|
659
|
+
const authType = getAuthType(name);
|
|
660
|
+
const docs = getConnectorDocs(name);
|
|
661
|
+
const envVars = (docs?.envVars || []).map((v) => ({
|
|
662
|
+
variable: v.variable,
|
|
663
|
+
description: v.description,
|
|
664
|
+
set: !!process.env[v.variable]
|
|
665
|
+
}));
|
|
666
|
+
if (authType === "oauth") {
|
|
667
|
+
const tokens = loadTokens(name);
|
|
668
|
+
const config2 = loadProfileConfig(name);
|
|
669
|
+
const hasTokens = !!tokens?.accessToken;
|
|
670
|
+
const hasRefreshToken = !!tokens?.refreshToken;
|
|
671
|
+
const tokenExpiry = tokens?.expiresAt;
|
|
672
|
+
return {
|
|
673
|
+
type: "oauth",
|
|
674
|
+
configured: hasTokens || hasRefreshToken,
|
|
675
|
+
tokenExpiry,
|
|
676
|
+
hasRefreshToken,
|
|
677
|
+
envVars
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
const config = loadProfileConfig(name);
|
|
681
|
+
const hasKey = Object.values(config).some((v) => typeof v === "string" && v.length > 0);
|
|
682
|
+
const hasEnvVar = envVars.some((v) => v.set);
|
|
683
|
+
return {
|
|
684
|
+
type: authType,
|
|
685
|
+
configured: hasKey || hasEnvVar,
|
|
686
|
+
envVars
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
function saveApiKey(name, key, field) {
|
|
690
|
+
const configDir = getConnectorConfigDir(name);
|
|
691
|
+
const profile = getCurrentProfile(name);
|
|
692
|
+
const keyField = field || guessKeyField(name);
|
|
693
|
+
const profileFile = join3(configDir, "profiles", `${profile}.json`);
|
|
694
|
+
const profileDir = join3(configDir, "profiles", profile);
|
|
695
|
+
if (existsSync3(profileFile)) {
|
|
696
|
+
const config = JSON.parse(readFileSync3(profileFile, "utf-8"));
|
|
697
|
+
config[keyField] = key;
|
|
698
|
+
writeFileSync2(profileFile, JSON.stringify(config, null, 2));
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
if (existsSync3(profileDir)) {
|
|
702
|
+
const configFile = join3(profileDir, "config.json");
|
|
703
|
+
const config = existsSync3(configFile) ? JSON.parse(readFileSync3(configFile, "utf-8")) : {};
|
|
704
|
+
config[keyField] = key;
|
|
705
|
+
writeFileSync2(configFile, JSON.stringify(config, null, 2));
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
mkdirSync2(join3(configDir, "profiles"), { recursive: true });
|
|
709
|
+
writeFileSync2(profileFile, JSON.stringify({ [keyField]: key }, null, 2));
|
|
710
|
+
}
|
|
711
|
+
function guessKeyField(name) {
|
|
712
|
+
const docs = getConnectorDocs(name);
|
|
713
|
+
if (!docs?.envVars.length)
|
|
714
|
+
return "apiKey";
|
|
715
|
+
const keyVar = docs.envVars.find((v) => v.variable.includes("API_KEY") || v.variable.includes("API_SECRET") || v.variable.includes("TOKEN") || v.variable.includes("SECRET"));
|
|
716
|
+
if (keyVar) {
|
|
717
|
+
const parts = keyVar.variable.toLowerCase().split("_");
|
|
718
|
+
const withoutPrefix = parts.slice(1);
|
|
719
|
+
if (withoutPrefix.length > 0) {
|
|
720
|
+
return withoutPrefix.map((p, i) => i === 0 ? p : p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return "apiKey";
|
|
724
|
+
}
|
|
725
|
+
function getOAuthConfig(name) {
|
|
726
|
+
const configDir = getConnectorConfigDir(name);
|
|
727
|
+
const credentialsFile = join3(configDir, "credentials.json");
|
|
728
|
+
if (existsSync3(credentialsFile)) {
|
|
729
|
+
try {
|
|
730
|
+
const creds = JSON.parse(readFileSync3(credentialsFile, "utf-8"));
|
|
731
|
+
return { clientId: creds.clientId, clientSecret: creds.clientSecret };
|
|
732
|
+
} catch {}
|
|
733
|
+
}
|
|
734
|
+
const config = loadProfileConfig(name);
|
|
735
|
+
return {
|
|
736
|
+
clientId: config.clientId,
|
|
737
|
+
clientSecret: config.clientSecret
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
function getOAuthStartUrl(name, redirectUri) {
|
|
741
|
+
const oauthConfig = getOAuthConfig(name);
|
|
742
|
+
if (!oauthConfig.clientId)
|
|
743
|
+
return null;
|
|
744
|
+
const scopes = GOOGLE_SCOPES[name];
|
|
745
|
+
if (!scopes)
|
|
746
|
+
return null;
|
|
747
|
+
const params = new URLSearchParams({
|
|
748
|
+
client_id: oauthConfig.clientId,
|
|
749
|
+
redirect_uri: redirectUri,
|
|
750
|
+
response_type: "code",
|
|
751
|
+
scope: scopes,
|
|
752
|
+
access_type: "offline",
|
|
753
|
+
prompt: "consent"
|
|
754
|
+
});
|
|
755
|
+
return `${GOOGLE_AUTH_URL}?${params.toString()}`;
|
|
756
|
+
}
|
|
757
|
+
async function exchangeOAuthCode(name, code, redirectUri) {
|
|
758
|
+
const oauthConfig = getOAuthConfig(name);
|
|
759
|
+
if (!oauthConfig.clientId || !oauthConfig.clientSecret) {
|
|
760
|
+
throw new Error("OAuth credentials not configured for " + name);
|
|
761
|
+
}
|
|
762
|
+
const response = await fetch(GOOGLE_TOKEN_URL, {
|
|
763
|
+
method: "POST",
|
|
764
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
765
|
+
body: new URLSearchParams({
|
|
766
|
+
code,
|
|
767
|
+
client_id: oauthConfig.clientId,
|
|
768
|
+
client_secret: oauthConfig.clientSecret,
|
|
769
|
+
redirect_uri: redirectUri,
|
|
770
|
+
grant_type: "authorization_code"
|
|
771
|
+
})
|
|
772
|
+
});
|
|
773
|
+
if (!response.ok) {
|
|
774
|
+
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
775
|
+
throw new Error(`Token exchange failed: ${error.error_description || error.error || response.statusText}`);
|
|
776
|
+
}
|
|
777
|
+
const data = await response.json();
|
|
778
|
+
const tokens = {
|
|
779
|
+
accessToken: data.access_token,
|
|
780
|
+
refreshToken: data.refresh_token,
|
|
781
|
+
expiresAt: Date.now() + data.expires_in * 1000,
|
|
782
|
+
tokenType: data.token_type,
|
|
783
|
+
scope: data.scope
|
|
784
|
+
};
|
|
785
|
+
saveOAuthTokens(name, tokens);
|
|
786
|
+
return tokens;
|
|
787
|
+
}
|
|
788
|
+
function saveOAuthTokens(name, tokens) {
|
|
789
|
+
const configDir = getConnectorConfigDir(name);
|
|
790
|
+
const profile = getCurrentProfile(name);
|
|
791
|
+
const profileDir = join3(configDir, "profiles", profile);
|
|
792
|
+
mkdirSync2(profileDir, { recursive: true });
|
|
793
|
+
const tokensFile = join3(profileDir, "tokens.json");
|
|
794
|
+
writeFileSync2(tokensFile, JSON.stringify(tokens, null, 2), { mode: 384 });
|
|
795
|
+
}
|
|
796
|
+
async function refreshOAuthToken(name) {
|
|
797
|
+
const oauthConfig = getOAuthConfig(name);
|
|
798
|
+
const currentTokens = loadTokens(name);
|
|
799
|
+
if (!oauthConfig.clientId || !oauthConfig.clientSecret) {
|
|
800
|
+
throw new Error("OAuth credentials not configured for " + name);
|
|
801
|
+
}
|
|
802
|
+
if (!currentTokens?.refreshToken) {
|
|
803
|
+
throw new Error("No refresh token available. Please re-authenticate.");
|
|
804
|
+
}
|
|
805
|
+
const response = await fetch(GOOGLE_TOKEN_URL, {
|
|
806
|
+
method: "POST",
|
|
807
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
808
|
+
body: new URLSearchParams({
|
|
809
|
+
client_id: oauthConfig.clientId,
|
|
810
|
+
client_secret: oauthConfig.clientSecret,
|
|
811
|
+
refresh_token: currentTokens.refreshToken,
|
|
812
|
+
grant_type: "refresh_token"
|
|
813
|
+
})
|
|
814
|
+
});
|
|
815
|
+
if (!response.ok) {
|
|
816
|
+
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
817
|
+
throw new Error(`Token refresh failed: ${error.error_description || error.error}`);
|
|
818
|
+
}
|
|
819
|
+
const data = await response.json();
|
|
820
|
+
const tokens = {
|
|
821
|
+
accessToken: data.access_token,
|
|
822
|
+
refreshToken: currentTokens.refreshToken,
|
|
823
|
+
expiresAt: Date.now() + data.expires_in * 1000,
|
|
824
|
+
tokenType: data.token_type,
|
|
825
|
+
scope: data.scope || currentTokens.scope
|
|
826
|
+
};
|
|
827
|
+
saveOAuthTokens(name, tokens);
|
|
828
|
+
return tokens;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// src/server/serve.ts
|
|
832
|
+
function resolveDashboardDir() {
|
|
833
|
+
const candidates = [];
|
|
834
|
+
try {
|
|
835
|
+
const scriptDir = dirname3(fileURLToPath3(import.meta.url));
|
|
836
|
+
candidates.push(join4(scriptDir, "..", "dashboard", "dist"));
|
|
837
|
+
candidates.push(join4(scriptDir, "..", "..", "dashboard", "dist"));
|
|
838
|
+
} catch {}
|
|
839
|
+
if (process.argv[1]) {
|
|
840
|
+
const mainDir = dirname3(process.argv[1]);
|
|
841
|
+
candidates.push(join4(mainDir, "..", "dashboard", "dist"));
|
|
842
|
+
candidates.push(join4(mainDir, "..", "..", "dashboard", "dist"));
|
|
843
|
+
}
|
|
844
|
+
candidates.push(join4(process.cwd(), "dashboard", "dist"));
|
|
845
|
+
for (const candidate of candidates) {
|
|
846
|
+
if (existsSync4(candidate))
|
|
847
|
+
return candidate;
|
|
848
|
+
}
|
|
849
|
+
return join4(process.cwd(), "dashboard", "dist");
|
|
850
|
+
}
|
|
851
|
+
var MIME_TYPES = {
|
|
852
|
+
".html": "text/html; charset=utf-8",
|
|
853
|
+
".js": "application/javascript",
|
|
854
|
+
".css": "text/css",
|
|
855
|
+
".json": "application/json",
|
|
856
|
+
".png": "image/png",
|
|
857
|
+
".jpg": "image/jpeg",
|
|
858
|
+
".svg": "image/svg+xml",
|
|
859
|
+
".ico": "image/x-icon",
|
|
860
|
+
".woff": "font/woff",
|
|
861
|
+
".woff2": "font/woff2"
|
|
862
|
+
};
|
|
863
|
+
function json(data, status = 200) {
|
|
864
|
+
return new Response(JSON.stringify(data), {
|
|
865
|
+
status,
|
|
866
|
+
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" }
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
function htmlResponse(content, status = 200) {
|
|
870
|
+
return new Response(content, {
|
|
871
|
+
status,
|
|
872
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
function getAllConnectorsWithAuth() {
|
|
876
|
+
const installed = new Set(getInstalledConnectors());
|
|
877
|
+
return CONNECTORS.map((meta) => {
|
|
878
|
+
const isInstalled = installed.has(meta.name);
|
|
879
|
+
const auth = isInstalled ? getAuthStatus(meta.name) : null;
|
|
880
|
+
return {
|
|
881
|
+
name: meta.name,
|
|
882
|
+
displayName: meta.displayName,
|
|
883
|
+
description: meta.description,
|
|
884
|
+
category: meta.category,
|
|
885
|
+
version: meta.version,
|
|
886
|
+
installed: isInstalled,
|
|
887
|
+
auth
|
|
888
|
+
};
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
function errorPage(title, message, hint) {
|
|
892
|
+
return `<!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:var(--bg,#0a0a0a);color:var(--fg,#e5e5e5);">
|
|
893
|
+
<style>@media(prefers-color-scheme:light){:root{--bg:#fff;--fg:#111;--sub:#666;--hint:#888}}:root{--bg:#0a0a0a;--fg:#e5e5e5;--sub:#888;--hint:#666}</style>
|
|
894
|
+
<div style="text-align:center;">
|
|
895
|
+
<h2 style="color:#ef4444;">${title}</h2>
|
|
896
|
+
<p style="color:var(--sub);">${message}</p>
|
|
897
|
+
${hint ? `<p style="color:var(--hint);font-size:14px;">${hint}</p>` : ""}
|
|
898
|
+
</div>
|
|
899
|
+
</body></html>`;
|
|
900
|
+
}
|
|
901
|
+
function serveStaticFile(filePath) {
|
|
902
|
+
if (!existsSync4(filePath))
|
|
903
|
+
return null;
|
|
904
|
+
const ext = extname(filePath);
|
|
905
|
+
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
906
|
+
return new Response(Bun.file(filePath), {
|
|
907
|
+
headers: { "Content-Type": contentType }
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
async function startServer(port, options) {
|
|
911
|
+
const shouldOpen = options?.open ?? true;
|
|
912
|
+
loadConnectorVersions();
|
|
913
|
+
const dashboardDir = resolveDashboardDir();
|
|
914
|
+
const dashboardExists = existsSync4(dashboardDir);
|
|
915
|
+
if (!dashboardExists) {
|
|
916
|
+
console.error(`Dashboard not built at ${dashboardDir}. Run: cd dashboard && bun run build`);
|
|
917
|
+
}
|
|
918
|
+
const server = Bun.serve({
|
|
919
|
+
port,
|
|
920
|
+
async fetch(req) {
|
|
921
|
+
const url2 = new URL(req.url);
|
|
922
|
+
const path = url2.pathname;
|
|
923
|
+
const method = req.method;
|
|
924
|
+
if (path === "/api/connectors" && method === "GET") {
|
|
925
|
+
return json(getAllConnectorsWithAuth());
|
|
926
|
+
}
|
|
927
|
+
const singleMatch = path.match(/^\/api\/connectors\/([^/]+)$/);
|
|
928
|
+
if (singleMatch && method === "GET") {
|
|
929
|
+
const name = singleMatch[1];
|
|
930
|
+
const meta = getConnector(name);
|
|
931
|
+
if (!meta)
|
|
932
|
+
return json({ error: `Connector '${name}' not found` }, 404);
|
|
933
|
+
const auth = getAuthStatus(name);
|
|
934
|
+
const docs = getConnectorDocs(name);
|
|
935
|
+
return json({
|
|
936
|
+
name: meta.name,
|
|
937
|
+
displayName: meta.displayName,
|
|
938
|
+
description: meta.description,
|
|
939
|
+
category: meta.category,
|
|
940
|
+
version: meta.version,
|
|
941
|
+
auth,
|
|
942
|
+
overview: docs?.overview || null
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
const keyMatch = path.match(/^\/api\/connectors\/([^/]+)\/key$/);
|
|
946
|
+
if (keyMatch && method === "POST") {
|
|
947
|
+
const name = keyMatch[1];
|
|
948
|
+
try {
|
|
949
|
+
const body = await req.json();
|
|
950
|
+
if (!body.key)
|
|
951
|
+
return json({ error: "Missing 'key' in request body" }, 400);
|
|
952
|
+
saveApiKey(name, body.key, body.field);
|
|
953
|
+
return json({ success: true });
|
|
954
|
+
} catch (e) {
|
|
955
|
+
return json({ error: e instanceof Error ? e.message : "Failed to save key" }, 500);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
const refreshMatch = path.match(/^\/api\/connectors\/([^/]+)\/refresh$/);
|
|
959
|
+
if (refreshMatch && method === "POST") {
|
|
960
|
+
const name = refreshMatch[1];
|
|
961
|
+
try {
|
|
962
|
+
const tokens = await refreshOAuthToken(name);
|
|
963
|
+
return json({ success: true, expiresAt: tokens.expiresAt });
|
|
964
|
+
} catch (e) {
|
|
965
|
+
return json({ success: false, error: e instanceof Error ? e.message : "Failed to refresh" }, 500);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
const oauthStartMatch = path.match(/^\/oauth\/([^/]+)\/start$/);
|
|
969
|
+
if (oauthStartMatch && method === "GET") {
|
|
970
|
+
const name = oauthStartMatch[1];
|
|
971
|
+
const redirectUri = `http://localhost:${port}/oauth/${name}/callback`;
|
|
972
|
+
const authUrl = getOAuthStartUrl(name, redirectUri);
|
|
973
|
+
if (!authUrl) {
|
|
974
|
+
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>`));
|
|
975
|
+
}
|
|
976
|
+
return Response.redirect(authUrl, 302);
|
|
977
|
+
}
|
|
978
|
+
const oauthCallbackMatch = path.match(/^\/oauth\/([^/]+)\/callback$/);
|
|
979
|
+
if (oauthCallbackMatch && method === "GET") {
|
|
980
|
+
const name = oauthCallbackMatch[1];
|
|
981
|
+
const code = url2.searchParams.get("code");
|
|
982
|
+
const error = url2.searchParams.get("error");
|
|
983
|
+
if (error) {
|
|
984
|
+
return htmlResponse(errorPage("Authentication Failed", error, "You can close this window."));
|
|
985
|
+
}
|
|
986
|
+
if (!code) {
|
|
987
|
+
return htmlResponse(errorPage("Missing Authorization Code", "No code received from the OAuth provider.", "You can close this window and try again."));
|
|
988
|
+
}
|
|
989
|
+
try {
|
|
990
|
+
const redirectUri = `http://localhost:${port}/oauth/${name}/callback`;
|
|
991
|
+
await exchangeOAuthCode(name, code, redirectUri);
|
|
992
|
+
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;">
|
|
993
|
+
<div style="text-align:center;">
|
|
994
|
+
<h2 style="color:#22c55e;">Connected!</h2>
|
|
995
|
+
<p style="color:#888;"><strong>${name}</strong> is now authenticated.</p>
|
|
996
|
+
<p style="color:#666;font-size:14px;">You can close this window and return to the dashboard.</p>
|
|
997
|
+
<script>
|
|
998
|
+
if (window.opener) {
|
|
999
|
+
window.opener.postMessage({ type: 'oauth-complete', connector: '${name}' }, '*');
|
|
1000
|
+
}
|
|
1001
|
+
</script>
|
|
1002
|
+
</div>
|
|
1003
|
+
</body></html>`);
|
|
1004
|
+
} catch (e) {
|
|
1005
|
+
return htmlResponse(errorPage("Authentication Failed", e instanceof Error ? e.message : "Unknown error", "You can close this window."));
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (method === "OPTIONS") {
|
|
1009
|
+
return new Response(null, {
|
|
1010
|
+
headers: {
|
|
1011
|
+
"Access-Control-Allow-Origin": "*",
|
|
1012
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
1013
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
if (dashboardExists && (method === "GET" || method === "HEAD")) {
|
|
1018
|
+
if (path !== "/") {
|
|
1019
|
+
const filePath = join4(dashboardDir, path);
|
|
1020
|
+
const res2 = serveStaticFile(filePath);
|
|
1021
|
+
if (res2)
|
|
1022
|
+
return res2;
|
|
1023
|
+
}
|
|
1024
|
+
const indexPath = join4(dashboardDir, "index.html");
|
|
1025
|
+
const res = serveStaticFile(indexPath);
|
|
1026
|
+
if (res)
|
|
1027
|
+
return res;
|
|
1028
|
+
}
|
|
1029
|
+
return json({ error: "Not found" }, 404);
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
const url = `http://localhost:${port}`;
|
|
1033
|
+
console.log(`Connectors Dashboard running at ${url}`);
|
|
1034
|
+
if (shouldOpen) {
|
|
1035
|
+
try {
|
|
1036
|
+
const { exec } = await import("child_process");
|
|
1037
|
+
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1038
|
+
exec(`${openCmd} ${url}`);
|
|
1039
|
+
} catch {}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// src/server/index.ts
|
|
1044
|
+
var DEFAULT_PORT = 19426;
|
|
1045
|
+
function parsePort() {
|
|
1046
|
+
const portArg = process.argv.find((a) => a === "--port" || a.startsWith("--port="));
|
|
1047
|
+
if (portArg) {
|
|
1048
|
+
if (portArg.includes("=")) {
|
|
1049
|
+
return parseInt(portArg.split("=")[1], 10) || DEFAULT_PORT;
|
|
1050
|
+
}
|
|
1051
|
+
const idx = process.argv.indexOf(portArg);
|
|
1052
|
+
return parseInt(process.argv[idx + 1], 10) || DEFAULT_PORT;
|
|
1053
|
+
}
|
|
1054
|
+
return DEFAULT_PORT;
|
|
1055
|
+
}
|
|
1056
|
+
startServer(parsePort());
|