@squadbase/vite-server 0.1.17 → 0.1.19-dev.a00d9c3

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 (82) hide show
  1. package/dist/cli/index.js +5961 -2986
  2. package/dist/connectors/airtable-oauth.js +39 -15
  3. package/dist/connectors/airtable.js +86 -15
  4. package/dist/connectors/amplitude.js +39 -15
  5. package/dist/connectors/anthropic.js +39 -15
  6. package/dist/connectors/asana.js +39 -15
  7. package/dist/connectors/attio.js +39 -15
  8. package/dist/connectors/aws-billing.js +39 -15
  9. package/dist/connectors/azure-sql.js +291 -18
  10. package/dist/connectors/backlog-api-key.js +39 -15
  11. package/dist/connectors/clickup.js +39 -15
  12. package/dist/connectors/cosmosdb.js +39 -15
  13. package/dist/connectors/customerio.js +39 -15
  14. package/dist/connectors/dbt.js +39 -15
  15. package/dist/connectors/freshdesk.js +39 -15
  16. package/dist/connectors/freshsales.js +39 -15
  17. package/dist/connectors/freshservice.js +39 -15
  18. package/dist/connectors/gamma.js +39 -15
  19. package/dist/connectors/gemini.js +39 -15
  20. package/dist/connectors/github.js +39 -15
  21. package/dist/connectors/gmail-oauth.js +32 -13
  22. package/dist/connectors/gmail.js +39 -15
  23. package/dist/connectors/google-ads.js +39 -15
  24. package/dist/connectors/google-analytics-oauth.js +40 -16
  25. package/dist/connectors/google-analytics.js +39 -15
  26. package/dist/connectors/google-audit-log.js +39 -15
  27. package/dist/connectors/google-calendar-oauth.js +40 -16
  28. package/dist/connectors/google-calendar.js +39 -15
  29. package/dist/connectors/google-docs.js +32 -13
  30. package/dist/connectors/google-drive.js +32 -13
  31. package/dist/connectors/google-search-console-oauth.js +39 -15
  32. package/dist/connectors/google-sheets.js +138 -195
  33. package/dist/connectors/google-slides.js +32 -13
  34. package/dist/connectors/grafana.js +39 -15
  35. package/dist/connectors/hackernews.d.ts +5 -0
  36. package/dist/connectors/hackernews.js +909 -0
  37. package/dist/connectors/hubspot-oauth.js +32 -13
  38. package/dist/connectors/hubspot.js +39 -15
  39. package/dist/connectors/influxdb.js +39 -15
  40. package/dist/connectors/intercom-oauth.js +32 -13
  41. package/dist/connectors/intercom.js +39 -15
  42. package/dist/connectors/jdbc.js +70 -19
  43. package/dist/connectors/jira-api-key.js +39 -15
  44. package/dist/connectors/kintone-api-token.js +39 -15
  45. package/dist/connectors/kintone.js +128 -15
  46. package/dist/connectors/linear.js +39 -15
  47. package/dist/connectors/linkedin-ads.js +39 -15
  48. package/dist/connectors/mailchimp-oauth.js +39 -15
  49. package/dist/connectors/mailchimp.js +39 -15
  50. package/dist/connectors/meta-ads-oauth.js +39 -15
  51. package/dist/connectors/meta-ads.js +39 -15
  52. package/dist/connectors/mixpanel.js +39 -15
  53. package/dist/connectors/monday.js +39 -15
  54. package/dist/connectors/mongodb.js +39 -15
  55. package/dist/connectors/notion-oauth.js +32 -13
  56. package/dist/connectors/notion.js +84 -15
  57. package/dist/connectors/openai.js +39 -15
  58. package/dist/connectors/oracle.js +293 -19
  59. package/dist/connectors/outlook-oauth.js +33 -14
  60. package/dist/connectors/powerbi-oauth.js +33 -14
  61. package/dist/connectors/salesforce.js +108 -15
  62. package/dist/connectors/semrush.js +39 -15
  63. package/dist/connectors/sentry.js +39 -15
  64. package/dist/connectors/shopify-oauth.js +32 -13
  65. package/dist/connectors/shopify.js +39 -15
  66. package/dist/connectors/slack.d.ts +5 -0
  67. package/dist/connectors/slack.js +656 -0
  68. package/dist/connectors/sqlserver.js +291 -18
  69. package/dist/connectors/stripe-api-key.js +39 -15
  70. package/dist/connectors/stripe-oauth.js +32 -13
  71. package/dist/connectors/supabase.js +293 -16
  72. package/dist/connectors/tableau.js +40 -16
  73. package/dist/connectors/tiktok-ads.js +39 -15
  74. package/dist/connectors/wix-store.js +39 -15
  75. package/dist/connectors/x.d.ts +5 -0
  76. package/dist/connectors/x.js +951 -0
  77. package/dist/connectors/zendesk-oauth.js +32 -13
  78. package/dist/connectors/zendesk.js +39 -15
  79. package/dist/index.js +6425 -3397
  80. package/dist/main.js +6425 -3397
  81. package/dist/vite-plugin.js +5965 -2987
  82. package/package.json +13 -4
@@ -0,0 +1,951 @@
1
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
+ var __esm = (fn, res, err) => function __init() {
3
+ if (err) throw err[0];
4
+ try {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ } catch (e) {
7
+ throw err = [e], e;
8
+ }
9
+ };
10
+
11
+ // ../connectors/src/parameter-definition.ts
12
+ var ParameterDefinition;
13
+ var init_parameter_definition = __esm({
14
+ "../connectors/src/parameter-definition.ts"() {
15
+ "use strict";
16
+ ParameterDefinition = class {
17
+ slug;
18
+ name;
19
+ description;
20
+ envVarBaseKey;
21
+ type;
22
+ secret;
23
+ required;
24
+ isDeprecated;
25
+ constructor(config) {
26
+ this.slug = config.slug;
27
+ this.name = config.name;
28
+ this.description = config.description;
29
+ this.envVarBaseKey = config.envVarBaseKey;
30
+ this.type = config.type;
31
+ this.secret = config.secret;
32
+ this.required = config.required;
33
+ this.isDeprecated = config.isDeprecated ?? false;
34
+ }
35
+ /**
36
+ * Get the parameter value from a ConnectorConnectionObject.
37
+ */
38
+ getValue(connection2) {
39
+ const param = connection2.parameters.find(
40
+ (p) => p.parameterSlug === this.slug
41
+ );
42
+ if (!param || param.value == null) {
43
+ throw new Error(
44
+ `Parameter "${this.slug}" not found or has no value in connection "${connection2.id}"`
45
+ );
46
+ }
47
+ return param.value;
48
+ }
49
+ /**
50
+ * Try to get the parameter value. Returns undefined if not found (for optional params).
51
+ */
52
+ tryGetValue(connection2) {
53
+ const param = connection2.parameters.find(
54
+ (p) => p.parameterSlug === this.slug
55
+ );
56
+ if (!param || param.value == null) return void 0;
57
+ return param.value;
58
+ }
59
+ };
60
+ }
61
+ });
62
+
63
+ // ../connectors/src/connectors/x/parameters.ts
64
+ init_parameter_definition();
65
+ var parameters = {
66
+ bearerToken: new ParameterDefinition({
67
+ slug: "bearer-token",
68
+ name: "X API Bearer Token",
69
+ description: "The X API Bearer Token for app-only authentication. Create it in the X Developer Console for your app.",
70
+ envVarBaseKey: "X_BEARER_TOKEN",
71
+ type: "text",
72
+ secret: true,
73
+ required: true
74
+ })
75
+ };
76
+
77
+ // ../connectors/src/connectors/x/sdk/index.ts
78
+ var BASE_URL = "https://api.x.com";
79
+ function createClient(params) {
80
+ const bearerToken = params[parameters.bearerToken.slug];
81
+ if (!bearerToken) {
82
+ throw new Error(
83
+ `x: missing required parameter: ${parameters.bearerToken.slug}`
84
+ );
85
+ }
86
+ function buildUrl(path2, query) {
87
+ if (/^https?:\/\//i.test(path2)) {
88
+ throw new Error("x: absolute URLs are not allowed");
89
+ }
90
+ const url = new URL(`${BASE_URL}${path2.startsWith("/") ? "" : "/"}${path2}`);
91
+ if (query) {
92
+ for (const [k, v] of Object.entries(query)) {
93
+ if (v !== void 0) url.searchParams.set(k, String(v));
94
+ }
95
+ }
96
+ return url.toString();
97
+ }
98
+ async function request(path2, init) {
99
+ const { query, headers: initHeaders, ...rest } = init ?? {};
100
+ const headers = new Headers(initHeaders);
101
+ headers.set("Authorization", `Bearer ${bearerToken}`);
102
+ headers.set("Accept", "application/json");
103
+ if (rest.body && !headers.has("Content-Type")) {
104
+ headers.set("Content-Type", "application/json");
105
+ }
106
+ return fetch(buildUrl(path2, query), { ...rest, headers });
107
+ }
108
+ async function json(path2, query) {
109
+ const res = await request(path2, { method: "GET", query });
110
+ const text = await res.text();
111
+ const data = text ? JSON.parse(text) : {};
112
+ if (!res.ok) {
113
+ throw new Error(`x: ${res.status} ${res.statusText}: ${text}`);
114
+ }
115
+ return data;
116
+ }
117
+ return {
118
+ request,
119
+ getUserByUsername(username, options) {
120
+ return json(
121
+ `/2/users/by/username/${encodeURIComponent(username)}`,
122
+ options?.query
123
+ );
124
+ },
125
+ getUser(id, options) {
126
+ return json(`/2/users/${encodeURIComponent(id)}`, options?.query);
127
+ },
128
+ getTweet(id, options) {
129
+ return json(`/2/tweets/${encodeURIComponent(id)}`, options?.query);
130
+ },
131
+ getUserTweets(userId, options) {
132
+ return json(
133
+ `/2/users/${encodeURIComponent(userId)}/tweets`,
134
+ options?.query
135
+ );
136
+ },
137
+ searchRecent(query, options) {
138
+ return json("/2/tweets/search/recent", {
139
+ query,
140
+ ...options?.query ?? {}
141
+ });
142
+ },
143
+ searchAll(query, options) {
144
+ return json("/2/tweets/search/all", {
145
+ query,
146
+ ...options?.query ?? {}
147
+ });
148
+ }
149
+ };
150
+ }
151
+
152
+ // ../connectors/src/connector-onboarding.ts
153
+ var ConnectorOnboarding = class {
154
+ /** Phase 1: Connection setup instructions (optional — some connectors don't need this) */
155
+ connectionSetupInstructions;
156
+ /** Phase 2: Data overview instructions */
157
+ dataOverviewInstructions;
158
+ constructor(config) {
159
+ this.connectionSetupInstructions = config.connectionSetupInstructions;
160
+ this.dataOverviewInstructions = config.dataOverviewInstructions;
161
+ }
162
+ getConnectionSetupPrompt(language) {
163
+ return this.connectionSetupInstructions?.[language] ?? null;
164
+ }
165
+ getDataOverviewInstructions(language) {
166
+ return this.dataOverviewInstructions[language];
167
+ }
168
+ };
169
+
170
+ // ../connectors/src/connector-tool.ts
171
+ var ConnectorTool = class {
172
+ name;
173
+ description;
174
+ inputSchema;
175
+ outputSchema;
176
+ _execute;
177
+ constructor(config) {
178
+ this.name = config.name;
179
+ this.description = config.description;
180
+ this.inputSchema = config.inputSchema;
181
+ this.outputSchema = config.outputSchema;
182
+ this._execute = config.execute;
183
+ }
184
+ createTool(connections, config) {
185
+ return {
186
+ description: this.description,
187
+ inputSchema: this.inputSchema,
188
+ outputSchema: this.outputSchema,
189
+ execute: (input) => this._execute(input, connections, config)
190
+ };
191
+ }
192
+ };
193
+
194
+ // ../connectors/src/connector-plugin.ts
195
+ var ConnectorPlugin = class _ConnectorPlugin {
196
+ slug;
197
+ authType;
198
+ name;
199
+ description;
200
+ iconUrl;
201
+ parameters;
202
+ releaseFlag;
203
+ proxyPolicy;
204
+ experimentalAttributes;
205
+ categories;
206
+ onboarding;
207
+ systemPrompt;
208
+ tools;
209
+ query;
210
+ checkConnection;
211
+ /**
212
+ * SQPD-1212: Logic-based, rule-driven connection setup. Connectors that
213
+ * implement this expose a step-by-step exploration flow (database/schema/
214
+ * table/etc. discovery) that the dashboard backend drives via the
215
+ * `/connections/:connectionId/setup` endpoint. Implement by delegating to
216
+ * `runSetupFlow` from `setup-flow.ts`.
217
+ */
218
+ setup;
219
+ /**
220
+ * 統一 introspection。SQL DB の `information_schema` / `SHOW` / `DESCRIBE` や SaaS の
221
+ * メタデータ API といった取得経路の差を吸収し、tables / columns / PK / FK を共通型
222
+ * {@link IntrospectedSchema} へ正規化して返す。取得経路は {@link IntrospectContext} で
223
+ * 抽象化され、SQL DB は `ctx.query`、SaaS は `ctx.proxyFetch` + `ctx.params` を使う。
224
+ * `ctx.profile === true` のときは L2 プロファイル(行数・NULL 率等)も best-effort で埋める。
225
+ * coding-agent の `DataContextIndex` がデータ地図を構築する際に使う。
226
+ */
227
+ introspect;
228
+ /**
229
+ * Opt-out of the default "verify before save" behavior on connection
230
+ * creation. The backend invokes `checkConnection` synchronously while
231
+ * creating the connection and aborts (no row inserted) if it fails — this
232
+ * flag disables that for connectors where the check cannot succeed pre-save:
233
+ *
234
+ * - `squadbase-db` populates `connection-url` only after Neon provisioning
235
+ * - OAuth connectors require an OAuth-aware proxyFetch keyed by the
236
+ * connectionId, which doesn't exist until the row is saved
237
+ *
238
+ * Exceptions are the explicit position; new credential-input connectors get
239
+ * the default verify-on-create behavior without opt-in.
240
+ */
241
+ skipConnectionCheckOnCreate;
242
+ /**
243
+ * 決定論的な「立ち上げ survey」レシピ(ルールベース prewarm 基盤)。実装した connector は、LLM を使わずに
244
+ * read-only で自分を survey する probe 列(テーブル/列メタデータ → 小サンプル)を宣言する。coding-agent の
245
+ * recon driver がこれを読み、connector ツールの `execute` を直接呼んで結果を seed にする。未実装の connector は
246
+ * driver 側で safely skip される(その connection は listConnections のみ seed)。標準 `information_schema` を
247
+ * 持つ SQL connector は `makeStandardSqlSurveyProbes` を使える。**probe は必ず read-only**。
248
+ */
249
+ surveyProbes;
250
+ constructor(config) {
251
+ this.slug = config.slug;
252
+ this.authType = config.authType;
253
+ this.name = config.name;
254
+ this.description = config.description;
255
+ this.iconUrl = config.iconUrl;
256
+ this.parameters = config.parameters;
257
+ this.releaseFlag = config.releaseFlag;
258
+ this.proxyPolicy = config.proxyPolicy;
259
+ this.experimentalAttributes = config.experimentalAttributes;
260
+ this.categories = config.categories ?? [];
261
+ this.onboarding = config.onboarding;
262
+ this.systemPrompt = config.systemPrompt;
263
+ this.tools = config.tools;
264
+ this.query = config.query;
265
+ this.checkConnection = config.checkConnection;
266
+ this.setup = config.setup;
267
+ this.introspect = config.introspect;
268
+ this.skipConnectionCheckOnCreate = config.skipConnectionCheckOnCreate;
269
+ this.surveyProbes = config.surveyProbes;
270
+ }
271
+ get connectorKey() {
272
+ return _ConnectorPlugin.deriveKey(this.slug, this.authType);
273
+ }
274
+ /**
275
+ * Create tools for connections that belong to this connector.
276
+ * Filters connections by connectorKey internally.
277
+ * Returns tools keyed as `connector_${connectorKey}_${toolName}`.
278
+ */
279
+ createTools(connections, config, opts) {
280
+ const myConnections = connections.filter(
281
+ (c) => _ConnectorPlugin.deriveKey(c.connector.slug, c.connector.authType) === this.connectorKey
282
+ );
283
+ const result = {};
284
+ for (const t of Object.values(this.tools)) {
285
+ const tool = t.createTool(myConnections, config);
286
+ const originalToModelOutput = tool.toModelOutput;
287
+ result[`connector_${this.connectorKey}_${t.name}`] = {
288
+ ...tool,
289
+ toModelOutput: async (options) => {
290
+ if (!originalToModelOutput) {
291
+ return opts.truncateOutput(options.output);
292
+ }
293
+ const modelOutput = await originalToModelOutput(options);
294
+ if (modelOutput.type === "text" || modelOutput.type === "json") {
295
+ return opts.truncateOutput(modelOutput.value);
296
+ }
297
+ return modelOutput;
298
+ }
299
+ };
300
+ }
301
+ return result;
302
+ }
303
+ static deriveKey(slug, authType) {
304
+ if (authType) return `${slug}-${authType}`;
305
+ const LEGACY_NULL_AUTH_TYPE_MAP = {
306
+ // user-password
307
+ postgresql: "user-password",
308
+ mysql: "user-password",
309
+ clickhouse: "user-password",
310
+ kintone: "user-password",
311
+ "squadbase-db": "user-password",
312
+ // service-account
313
+ snowflake: "service-account",
314
+ bigquery: "service-account",
315
+ "google-analytics": "service-account",
316
+ "google-calendar": "service-account",
317
+ "aws-athena": "service-account",
318
+ redshift: "service-account",
319
+ // api-key
320
+ databricks: "api-key",
321
+ dbt: "api-key",
322
+ airtable: "api-key",
323
+ openai: "api-key",
324
+ gemini: "api-key",
325
+ anthropic: "api-key",
326
+ "wix-store": "api-key"
327
+ };
328
+ const fallbackAuthType = LEGACY_NULL_AUTH_TYPE_MAP[slug];
329
+ if (fallbackAuthType) return `${slug}-${fallbackAuthType}`;
330
+ return slug;
331
+ }
332
+ };
333
+
334
+ // ../connectors/src/setup-flow.ts
335
+ async function runSetupFlow(flow, params, ctx, config) {
336
+ const runtime = {
337
+ params,
338
+ language: ctx.language,
339
+ config
340
+ };
341
+ let state = flow.initialState();
342
+ let answerIdx = 0;
343
+ const pendingParameterUpdates = [];
344
+ for (const step of flow.steps) {
345
+ const ans = ctx.answers[answerIdx];
346
+ if (ans && ans.questionSlug === step.slug) {
347
+ state = step.applyAnswer(state, ans.answer);
348
+ if (step.toParameterUpdates) {
349
+ pendingParameterUpdates.push(...step.toParameterUpdates(state));
350
+ }
351
+ answerIdx += 1;
352
+ continue;
353
+ }
354
+ const resolvedAllowFreeText = step.allowFreeText !== void 0 ? step.allowFreeText : true;
355
+ if (step.type === "text") {
356
+ if (step.fetchOptions) {
357
+ const options2 = await step.fetchOptions(state, runtime);
358
+ if (options2.length === 0) {
359
+ continue;
360
+ }
361
+ }
362
+ return {
363
+ type: "nextQuestion",
364
+ questionSlug: step.slug,
365
+ question: step.question[ctx.language],
366
+ questionType: "text",
367
+ allowFreeText: resolvedAllowFreeText,
368
+ ...pendingParameterUpdates.length > 0 && {
369
+ parameterUpdates: pendingParameterUpdates
370
+ }
371
+ };
372
+ }
373
+ const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
374
+ if (options.length === 0) {
375
+ continue;
376
+ }
377
+ return {
378
+ type: "nextQuestion",
379
+ questionSlug: step.slug,
380
+ question: step.question[ctx.language],
381
+ questionType: step.type,
382
+ options,
383
+ allowFreeText: resolvedAllowFreeText,
384
+ ...pendingParameterUpdates.length > 0 && {
385
+ parameterUpdates: pendingParameterUpdates
386
+ }
387
+ };
388
+ }
389
+ const dataInvestigationResult = await flow.finalize(state, runtime);
390
+ return {
391
+ type: "fulfilled",
392
+ dataInvestigationResult,
393
+ ...pendingParameterUpdates.length > 0 && {
394
+ parameterUpdates: pendingParameterUpdates
395
+ }
396
+ };
397
+ }
398
+
399
+ // ../connectors/src/auth-types.ts
400
+ var AUTH_TYPES = {
401
+ OAUTH: "oauth",
402
+ API_KEY: "api-key",
403
+ JWT: "jwt",
404
+ SERVICE_ACCOUNT: "service-account",
405
+ PAT: "pat",
406
+ USER_PASSWORD: "user-password"
407
+ };
408
+
409
+ // ../connectors/src/connectors/x/tools/request.ts
410
+ import { z } from "zod";
411
+ var BASE_URL2 = "https://api.x.com";
412
+ var REQUEST_TIMEOUT_MS = 6e4;
413
+ function readRateLimit(headers) {
414
+ const limit = headers.get("x-rate-limit-limit");
415
+ const remaining = headers.get("x-rate-limit-remaining");
416
+ const reset = headers.get("x-rate-limit-reset");
417
+ return {
418
+ limit: limit ? Number(limit) : void 0,
419
+ remaining: remaining ? Number(remaining) : void 0,
420
+ reset: reset ? Number(reset) : void 0
421
+ };
422
+ }
423
+ var inputSchema = z.object({
424
+ toolUseIntent: z.string().optional().describe(
425
+ "Brief description of what you intend to accomplish with this tool call"
426
+ ),
427
+ connectionId: z.string().describe("ID of the X connection to use"),
428
+ method: z.enum(["GET", "POST", "PUT", "DELETE"]).describe(
429
+ "HTTP method. Use GET for dashboard data reads. Use POST/PUT/DELETE only when the user explicitly asks for mutations supported by their X API access level."
430
+ ),
431
+ path: z.string().describe(
432
+ "API path appended to https://api.x.com. Use '/2/...' paths such as '/2/users/by/username/xdevelopers', '/2/users/{id}/tweets', or '/2/tweets/search/recent'. Absolute URLs are not allowed."
433
+ ),
434
+ queryParams: z.record(z.string(), z.string()).optional().describe(
435
+ "Query parameters to append to the request URL. Examples: tweet.fields=created_at,public_metrics, user.fields=created_at,public_metrics, max_results=10, query=from:xdevelopers. Keep max_results small for dashboard exploration."
436
+ ),
437
+ body: z.record(z.string(), z.unknown()).optional().describe("JSON request body for POST/PUT requests.")
438
+ });
439
+ var outputSchema = z.discriminatedUnion("success", [
440
+ z.object({
441
+ success: z.literal(true),
442
+ status: z.number(),
443
+ rateLimit: z.object({
444
+ limit: z.number().optional(),
445
+ remaining: z.number().optional(),
446
+ reset: z.number().optional()
447
+ }),
448
+ data: z.unknown()
449
+ }),
450
+ z.object({
451
+ success: z.literal(false),
452
+ status: z.number().optional(),
453
+ rateLimit: z.object({
454
+ limit: z.number().optional(),
455
+ remaining: z.number().optional(),
456
+ reset: z.number().optional()
457
+ }).optional(),
458
+ error: z.string()
459
+ })
460
+ ]);
461
+ var requestTool = new ConnectorTool({
462
+ name: "request",
463
+ description: `Send authenticated requests to the X API (https://api.x.com).
464
+ Use this tool for X API v2 reads such as looking up users, fetching posts, retrieving timelines, and performing recent search. Authentication is handled automatically with the connection's Bearer Token; never include Authorization headers yourself.
465
+
466
+ X rate limits are endpoint-specific and commonly reset on fixed windows. This tool returns x-rate-limit-limit, x-rate-limit-remaining, and x-rate-limit-reset when X sends them. Before making repeated calls or adding pagination, inspect these values and keep dashboard requests small. Do not use this tool for frequent polling; if a request returns 429, wait until the reset timestamp instead of retrying immediately.`,
467
+ inputSchema,
468
+ outputSchema,
469
+ async execute({ connectionId, method, path: path2, queryParams, body }, connections) {
470
+ const connection2 = connections.find((c) => c.id === connectionId);
471
+ if (!connection2) {
472
+ return {
473
+ success: false,
474
+ error: `Connection ${connectionId} not found`
475
+ };
476
+ }
477
+ console.log(`[connector-request] x/${connection2.name}: ${method} ${path2}`);
478
+ try {
479
+ if (/^https?:\/\//i.test(path2)) {
480
+ return {
481
+ success: false,
482
+ error: "Absolute URLs are not allowed. Pass a path such as /2/users/me."
483
+ };
484
+ }
485
+ const bearerToken = parameters.bearerToken.getValue(connection2);
486
+ const url = new URL(
487
+ `${BASE_URL2}${path2.startsWith("/") ? "" : "/"}${path2}`
488
+ );
489
+ if (queryParams) {
490
+ for (const [k, v] of Object.entries(queryParams)) {
491
+ url.searchParams.set(k, v);
492
+ }
493
+ }
494
+ const controller = new AbortController();
495
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
496
+ try {
497
+ const response = await fetch(url.toString(), {
498
+ method,
499
+ headers: {
500
+ Authorization: `Bearer ${bearerToken}`,
501
+ Accept: "application/json",
502
+ ...body ? { "Content-Type": "application/json" } : {}
503
+ },
504
+ body: body ? JSON.stringify(body) : void 0,
505
+ signal: controller.signal
506
+ });
507
+ const rateLimit = readRateLimit(response.headers);
508
+ const text = await response.text();
509
+ let data = text;
510
+ if (text.length > 0) {
511
+ try {
512
+ data = JSON.parse(text);
513
+ } catch {
514
+ data = text;
515
+ }
516
+ }
517
+ if (!response.ok) {
518
+ const error = typeof data === "object" && data != null ? JSON.stringify(data) : String(data || response.statusText);
519
+ return {
520
+ success: false,
521
+ status: response.status,
522
+ rateLimit,
523
+ error
524
+ };
525
+ }
526
+ return {
527
+ success: true,
528
+ status: response.status,
529
+ rateLimit,
530
+ data
531
+ };
532
+ } finally {
533
+ clearTimeout(timeout);
534
+ }
535
+ } catch (err) {
536
+ const msg = err instanceof Error ? err.message : String(err);
537
+ return { success: false, error: msg };
538
+ }
539
+ }
540
+ });
541
+
542
+ // ../connectors/src/connectors/x/setup.ts
543
+ var requestToolName = `x-api-key_${requestTool.name}`;
544
+ var xOnboarding = new ConnectorOnboarding({
545
+ connectionSetupInstructions: {
546
+ en: `Follow these steps to set up the X connection.
547
+
548
+ 1. Call \`${requestToolName}\` to verify the Bearer Token:
549
+ - \`method\`: \`"GET"\`
550
+ - \`path\`: \`"/2/users/by/username/xdevelopers"\`
551
+ 2. If an error is returned, ask the user to verify their Bearer Token (it must be a valid App-only Bearer Token with at least Basic access)
552
+
553
+ #### Constraints
554
+ - **Do NOT read post or user data beyond the single verification call above**
555
+ - Write only 1 sentence between tool calls, then immediately call the next tool. Skip unnecessary explanations and proceed efficiently`,
556
+ ja: `\u4EE5\u4E0B\u306E\u624B\u9806\u3067X\u30B3\u30CD\u30AF\u30B7\u30E7\u30F3\u306E\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3092\u884C\u3063\u3066\u304F\u3060\u3055\u3044\u3002
557
+
558
+ 1. \`${requestToolName}\` \u3092\u547C\u3073\u51FA\u3057\u3066 Bearer Token \u3092\u691C\u8A3C\u3059\u308B:
559
+ - \`method\`: \`"GET"\`
560
+ - \`path\`: \`"/2/users/by/username/xdevelopers"\`
561
+ 2. \u30A8\u30E9\u30FC\u304C\u8FD4\u3055\u308C\u305F\u5834\u5408\u3001\u30E6\u30FC\u30B6\u30FC\u306B Bearer Token \u306E\u78BA\u8A8D\u3092\u4F9D\u983C\u3059\u308B\uFF08Basic \u30A2\u30AF\u30BB\u30B9\u4EE5\u4E0A\u306E\u6A29\u9650\u3092\u6301\u3064\u6709\u52B9\u306A App-only Bearer Token \u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u308B\u304B\u78BA\u8A8D\uFF09
562
+
563
+ #### \u5236\u7D04
564
+ - **\u4E0A\u8A18\u306E\u691C\u8A3C\u547C\u3073\u51FA\u3057 1 \u56DE\u3092\u8D85\u3048\u3066\u6295\u7A3F\u3084\u30E6\u30FC\u30B6\u30FC\u30C7\u30FC\u30BF\u3092\u8AAD\u307F\u53D6\u3089\u306A\u3044\u3053\u3068**
565
+ - \u30C4\u30FC\u30EB\u9593\u306F 1 \u6587\u3060\u3051\u66F8\u3044\u3066\u5373\u6B21\u306E\u30C4\u30FC\u30EB\u547C\u3073\u51FA\u3057\u3002\u4E0D\u8981\u306A\u8AAC\u660E\u306F\u7701\u7565\u3057\u3001\u52B9\u7387\u7684\u306B\u9032\u3081\u308B`
566
+ },
567
+ dataOverviewInstructions: {
568
+ en: `1. Use connector_x-api-key_request with GET /2/users/by/username/{username} to inspect target accounts
569
+ 2. Use GET /2/users/{id}/tweets with a small max_results value to sample recent posts
570
+ 3. Use GET /2/tweets/search/recent only when keyword search is required
571
+ 4. Always check rateLimit.remaining and rateLimit.reset in tool results before issuing additional calls`,
572
+ ja: `1. connector_x-api-key_request \u3067 GET /2/users/by/username/{username} \u3092\u547C\u3073\u51FA\u3057\u3001\u5BFE\u8C61\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u78BA\u8A8D\u3057\u307E\u3059
573
+ 2. GET /2/users/{id}/tweets \u3092\u5C0F\u3055\u306A max_results \u3067\u547C\u3073\u51FA\u3057\u3001\u6700\u8FD1\u306E\u6295\u7A3F\u3092\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0\u3057\u307E\u3059
574
+ 3. \u30AD\u30FC\u30EF\u30FC\u30C9\u691C\u7D22\u304C\u5FC5\u8981\u306A\u5834\u5408\u306E\u307F GET /2/tweets/search/recent \u3092\u4F7F\u7528\u3057\u307E\u3059
575
+ 4. \u8FFD\u52A0\u306E\u547C\u3073\u51FA\u3057\u524D\u306B\u3001\u30C4\u30FC\u30EB\u7D50\u679C\u306E rateLimit.remaining \u3068 rateLimit.reset \u3092\u5FC5\u305A\u78BA\u8A8D\u3057\u307E\u3059`
576
+ }
577
+ });
578
+
579
+ // ../connectors/src/connectors/x/setup-flow.ts
580
+ var xSetupFlow = {
581
+ initialState: () => ({}),
582
+ steps: [],
583
+ async finalize(_state, rt) {
584
+ const sections = rt.language === "ja" ? [
585
+ "## X",
586
+ "",
587
+ "- \u63A5\u7D9A\u306F Bearer Token \u3067\u8A8D\u8A3C\u3055\u308C\u307E\u3059\u3002",
588
+ "- \u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u5B9F\u88C5\u3067\u306F\u3001\u540C\u3058\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3078\u306E\u77ED\u5468\u671F\u30DD\u30FC\u30EA\u30F3\u30B0\u3084\u5927\u91CF\u30DA\u30FC\u30B8\u30CD\u30FC\u30B7\u30E7\u30F3\u3092\u907F\u3051\u3066\u304F\u3060\u3055\u3044\u3002",
589
+ "- X API \u306E rate limit \u306F\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u5358\u4F4D\u3067\u7BA1\u7406\u3055\u308C\u307E\u3059\u3002\u30EC\u30B9\u30DD\u30F3\u30B9\u30D8\u30C3\u30C0\u30FC\u306E `x-rate-limit-remaining` \u3068 `x-rate-limit-reset` \u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
590
+ "- 429 \u304C\u8FD4\u3063\u305F\u5834\u5408\u306F\u3001reset \u6642\u523B\u307E\u3067\u518D\u8A66\u884C\u3092\u6B62\u3081\u3066\u304F\u3060\u3055\u3044\u3002"
591
+ ] : [
592
+ "## X",
593
+ "",
594
+ "- The connection authenticates with a Bearer Token.",
595
+ "- When building dashboards, avoid frequent polling and large pagination against the same endpoint.",
596
+ "- X API rate limits are endpoint-specific. Check the `x-rate-limit-remaining` and `x-rate-limit-reset` response headers.",
597
+ "- If a request returns 429, stop retrying until the reset time."
598
+ ];
599
+ return sections.join("\n");
600
+ }
601
+ };
602
+
603
+ // ../connectors/src/connectors/x/index.ts
604
+ var tools = { request: requestTool };
605
+ var xConnector = new ConnectorPlugin({
606
+ slug: "x",
607
+ authType: AUTH_TYPES.API_KEY,
608
+ name: "X",
609
+ description: "Connect to X API v2 for posts, users, search, timelines, trends, and related social media data using a user-provided Bearer Token.",
610
+ iconUrl: "https://cdn.simpleicons.org/x/000000",
611
+ parameters,
612
+ releaseFlag: { dev1: true, dev2: true, prod: true },
613
+ categories: ["social_media"],
614
+ onboarding: xOnboarding,
615
+ systemPrompt: {
616
+ en: `### Tools
617
+
618
+ - \`connector_x-api-key_request\`: Send authenticated requests to X API v2. Use it for reading users, posts, timelines, and recent search. Authentication is handled automatically with the connection's Bearer Token. The tool returns rate limit headers when available; inspect \`rateLimit.remaining\` and \`rateLimit.reset\` before making repeated calls.
619
+
620
+ ### Business Logic
621
+
622
+ The business logic type for this connector is "typescript". Use the connector SDK in your handler. Do NOT read credentials from environment variables.
623
+
624
+ SDK methods (client created via \`connection(connectionId)\`):
625
+ - \`client.request(path, init?)\` \u2014 low-level authenticated fetch against \`https://api.x.com\`
626
+ - \`client.getUserByUsername(username, options?)\` \u2014 get an X user by username
627
+ - \`client.getUser(id, options?)\` \u2014 get an X user by ID
628
+ - \`client.getTweet(id, options?)\` \u2014 get a post by ID
629
+ - \`client.getUserTweets(userId, options?)\` \u2014 get recent posts for a user
630
+ - \`client.searchRecent(query, options?)\` \u2014 search recent posts
631
+ - \`client.searchAll(query, options?)\` \u2014 search the full archive when the connected X API access tier supports it
632
+
633
+ When building dashboards, keep API calls small and cache or reuse responses in your own handler where possible. Avoid short-interval polling, avoid fetching many pages during rendering, and avoid repeated recent-search calls for the same query. If X returns HTTP 429, do not retry immediately; wait until the \`x-rate-limit-reset\` timestamp.
634
+
635
+ \`\`\`ts
636
+ import type { Context } from "hono";
637
+ import { connection } from "@squadbase/vite-server/connectors/x";
638
+
639
+ const x = connection("<connectionId>");
640
+
641
+ export default async function handler(c: Context) {
642
+ const { username = "xdevelopers", maxResults = 10 } = await c.req.json<{
643
+ username?: string;
644
+ maxResults?: number;
645
+ }>();
646
+
647
+ const user = await x.getUserByUsername(username, {
648
+ query: { "user.fields": "created_at,description,public_metrics" },
649
+ });
650
+ const userId = (user.data as { id: string }).id;
651
+ const tweets = await x.getUserTweets(userId, {
652
+ query: {
653
+ max_results: Math.min(maxResults, 25),
654
+ "tweet.fields": "created_at,public_metrics",
655
+ },
656
+ });
657
+
658
+ return c.json({ user: user.data, tweets: tweets.data ?? [] });
659
+ }
660
+ \`\`\`
661
+
662
+ ### X API Reference
663
+
664
+ - Base URL: \`https://api.x.com\`
665
+ - Authentication: Bearer Token (handled automatically)
666
+ - Rate limits: endpoint-specific; response headers may include \`x-rate-limit-limit\`, \`x-rate-limit-remaining\`, and \`x-rate-limit-reset\`
667
+
668
+ #### Common Endpoints
669
+ - GET \`/2/users/by/username/{username}\` \u2014 Look up a user by username
670
+ - GET \`/2/users/{id}\` \u2014 Look up a user by ID
671
+ - GET \`/2/users/{id}/tweets\` \u2014 Get a user's recent posts
672
+ - GET \`/2/tweets/{id}\` \u2014 Look up a post by ID
673
+ - GET \`/2/tweets/search/recent\` \u2014 Search recent posts
674
+ - GET \`/2/tweets/search/all\` \u2014 Search the full archive (requires X API access that includes full-archive search)
675
+ - GET \`/2/tweets/counts/recent\` \u2014 Count recent posts matching a query
676
+
677
+ #### Tips
678
+ - Request only fields you need with \`tweet.fields\`, \`user.fields\`, and \`expansions\`
679
+ - Use small \`max_results\` values while exploring; dashboards rarely need hundreds of posts per render
680
+ - Prefer counts endpoints for trend charts when the user needs aggregate volume instead of individual posts`,
681
+ ja: `### \u30C4\u30FC\u30EB
682
+
683
+ - \`connector_x-api-key_request\`: X API v2 \u306B\u8A8D\u8A3C\u6E08\u307F\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u9001\u4FE1\u3057\u307E\u3059\u3002\u30E6\u30FC\u30B6\u30FC\u3001\u6295\u7A3F\u3001\u30BF\u30A4\u30E0\u30E9\u30A4\u30F3\u3001recent search \u306E\u8AAD\u307F\u53D6\u308A\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u8A8D\u8A3C\u306F\u63A5\u7D9A\u306E Bearer Token \u3067\u81EA\u52D5\u7684\u306B\u884C\u308F\u308C\u307E\u3059\u3002\u5229\u7528\u53EF\u80FD\u306A\u5834\u5408\u306F rate limit \u30D8\u30C3\u30C0\u30FC\u3082\u8FD4\u3059\u305F\u3081\u3001\u7E70\u308A\u8FD4\u3057\u547C\u3073\u51FA\u3059\u524D\u306B \`rateLimit.remaining\` \u3068 \`rateLimit.reset\` \u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002
684
+
685
+ ### Business Logic
686
+
687
+ \u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306E\u30D3\u30B8\u30CD\u30B9\u30ED\u30B8\u30C3\u30AF\u30BF\u30A4\u30D7\u306F "typescript" \u3067\u3059\u3002\u30CF\u30F3\u30C9\u30E9\u5185\u3067\u306F\u30B3\u30CD\u30AF\u30BFSDK\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u74B0\u5883\u5909\u6570\u304B\u3089\u8A8D\u8A3C\u60C5\u5831\u3092\u8AAD\u307F\u53D6\u3089\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
688
+
689
+ SDK\u30E1\u30BD\u30C3\u30C9 (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8):
690
+ - \`client.request(path, init?)\` \u2014 \`https://api.x.com\` \u306B\u5BFE\u3059\u308B\u4F4E\u30EC\u30D9\u30EB\u306E\u8A8D\u8A3C\u4ED8\u304Dfetch
691
+ - \`client.getUserByUsername(username, options?)\` \u2014 username \u3067 X \u30E6\u30FC\u30B6\u30FC\u3092\u53D6\u5F97
692
+ - \`client.getUser(id, options?)\` \u2014 ID \u3067 X \u30E6\u30FC\u30B6\u30FC\u3092\u53D6\u5F97
693
+ - \`client.getTweet(id, options?)\` \u2014 ID \u3067\u6295\u7A3F\u3092\u53D6\u5F97
694
+ - \`client.getUserTweets(userId, options?)\` \u2014 \u30E6\u30FC\u30B6\u30FC\u306E\u6700\u8FD1\u306E\u6295\u7A3F\u3092\u53D6\u5F97
695
+ - \`client.searchRecent(query, options?)\` \u2014 \u6700\u8FD1\u306E\u6295\u7A3F\u3092\u691C\u7D22
696
+ - \`client.searchAll(query, options?)\` \u2014 \u63A5\u7D9A\u4E2D\u306E X API access tier \u304C\u5BFE\u5FDC\u3057\u3066\u3044\u308B\u5834\u5408\u306B full archive \u3092\u691C\u7D22
697
+
698
+ \u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u3092\u4F5C\u308B\u6642\u306F API \u547C\u3073\u51FA\u3057\u3092\u5C0F\u3055\u304F\u3057\u3001\u53EF\u80FD\u3067\u3042\u308C\u3070\u30CF\u30F3\u30C9\u30E9\u5185\u3067\u30EC\u30B9\u30DD\u30F3\u30B9\u3092\u518D\u5229\u7528\u30FB\u30AD\u30E3\u30C3\u30B7\u30E5\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u77ED\u5468\u671F\u30DD\u30FC\u30EA\u30F3\u30B0\u3001\u30EC\u30F3\u30C0\u30EA\u30F3\u30B0\u3054\u3068\u306E\u5927\u91CF\u30DA\u30FC\u30B8\u30CD\u30FC\u30B7\u30E7\u30F3\u3001\u540C\u3058\u30AF\u30A8\u30EA\u3067\u306E recent search \u9023\u6253\u306F\u907F\u3051\u3066\u304F\u3060\u3055\u3044\u3002X \u304C HTTP 429 \u3092\u8FD4\u3057\u305F\u5834\u5408\u306F\u5373\u6642\u30EA\u30C8\u30E9\u30A4\u305B\u305A\u3001\`x-rate-limit-reset\` \u306E\u6642\u523B\u307E\u3067\u5F85\u3063\u3066\u304F\u3060\u3055\u3044\u3002
699
+
700
+ \`\`\`ts
701
+ import type { Context } from "hono";
702
+ import { connection } from "@squadbase/vite-server/connectors/x";
703
+
704
+ const x = connection("<connectionId>");
705
+
706
+ export default async function handler(c: Context) {
707
+ const { username = "xdevelopers", maxResults = 10 } = await c.req.json<{
708
+ username?: string;
709
+ maxResults?: number;
710
+ }>();
711
+
712
+ const user = await x.getUserByUsername(username, {
713
+ query: { "user.fields": "created_at,description,public_metrics" },
714
+ });
715
+ const userId = (user.data as { id: string }).id;
716
+ const tweets = await x.getUserTweets(userId, {
717
+ query: {
718
+ max_results: Math.min(maxResults, 25),
719
+ "tweet.fields": "created_at,public_metrics",
720
+ },
721
+ });
722
+
723
+ return c.json({ user: user.data, tweets: tweets.data ?? [] });
724
+ }
725
+ \`\`\`
726
+
727
+ ### X API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
728
+
729
+ - \u30D9\u30FC\u30B9URL: \`https://api.x.com\`
730
+ - \u8A8D\u8A3C: Bearer Token\uFF08\u81EA\u52D5\u8A2D\u5B9A\uFF09
731
+ - Rate limit: \u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u5358\u4F4D\u3002\u30EC\u30B9\u30DD\u30F3\u30B9\u30D8\u30C3\u30C0\u30FC\u306B \`x-rate-limit-limit\`, \`x-rate-limit-remaining\`, \`x-rate-limit-reset\` \u304C\u542B\u307E\u308C\u308B\u3053\u3068\u304C\u3042\u308A\u307E\u3059
732
+
733
+ #### \u4E3B\u8981\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8
734
+ - GET \`/2/users/by/username/{username}\` \u2014 username \u3067\u30E6\u30FC\u30B6\u30FC\u3092\u53D6\u5F97
735
+ - GET \`/2/users/{id}\` \u2014 ID \u3067\u30E6\u30FC\u30B6\u30FC\u3092\u53D6\u5F97
736
+ - GET \`/2/users/{id}/tweets\` \u2014 \u30E6\u30FC\u30B6\u30FC\u306E\u6700\u8FD1\u306E\u6295\u7A3F\u3092\u53D6\u5F97
737
+ - GET \`/2/tweets/{id}\` \u2014 ID \u3067\u6295\u7A3F\u3092\u53D6\u5F97
738
+ - GET \`/2/tweets/search/recent\` \u2014 \u6700\u8FD1\u306E\u6295\u7A3F\u3092\u691C\u7D22
739
+ - GET \`/2/tweets/search/all\` \u2014 full archive \u3092\u691C\u7D22\uFF08full-archive search \u3092\u542B\u3080 X API access \u304C\u5FC5\u8981\uFF09
740
+ - GET \`/2/tweets/counts/recent\` \u2014 \u30AF\u30A8\u30EA\u306B\u4E00\u81F4\u3059\u308B\u6700\u8FD1\u306E\u6295\u7A3F\u6570\u3092\u96C6\u8A08
741
+
742
+ #### \u30D2\u30F3\u30C8
743
+ - \`tweet.fields\`, \`user.fields\`, \`expansions\` \u3067\u5FC5\u8981\u306A\u30D5\u30A3\u30FC\u30EB\u30C9\u3060\u3051\u3092\u53D6\u5F97\u3057\u3066\u304F\u3060\u3055\u3044
744
+ - \u63A2\u7D22\u6642\u306E \`max_results\` \u306F\u5C0F\u3055\u304F\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u591A\u304F\u306E\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u3067\u306F1\u56DE\u306E\u63CF\u753B\u306B\u6570\u767E\u4EF6\u306E\u6295\u7A3F\u306F\u4E0D\u8981\u3067\u3059
745
+ - \u500B\u5225\u6295\u7A3F\u3067\u306F\u306A\u304F\u4EF6\u6570\u30C8\u30EC\u30F3\u30C9\u304C\u5FC5\u8981\u306A\u5834\u5408\u306F counts endpoint \u3092\u512A\u5148\u3057\u3066\u304F\u3060\u3055\u3044`
746
+ },
747
+ tools,
748
+ setup: (params, ctx, config) => runSetupFlow(xSetupFlow, params, ctx, config),
749
+ async checkConnection(params) {
750
+ const bearerToken = params[parameters.bearerToken.slug];
751
+ if (!bearerToken) {
752
+ return {
753
+ success: false,
754
+ error: `Missing required parameter: ${parameters.bearerToken.slug}`
755
+ };
756
+ }
757
+ try {
758
+ const res = await fetch(
759
+ "https://api.x.com/2/users/by/username/xdevelopers",
760
+ {
761
+ method: "GET",
762
+ headers: {
763
+ Authorization: `Bearer ${bearerToken}`,
764
+ Accept: "application/json"
765
+ }
766
+ }
767
+ );
768
+ if (!res.ok) {
769
+ const errorText = await res.text().catch(() => res.statusText);
770
+ return {
771
+ success: false,
772
+ error: `X API failed: HTTP ${res.status} ${errorText}`
773
+ };
774
+ }
775
+ return { success: true };
776
+ } catch (error) {
777
+ return {
778
+ success: false,
779
+ error: error instanceof Error ? error.message : String(error)
780
+ };
781
+ }
782
+ }
783
+ });
784
+
785
+ // src/connectors/create-connector-sdk.ts
786
+ import { readFileSync } from "fs";
787
+ import path from "path";
788
+
789
+ // src/connector-client/env.ts
790
+ function resolveEnvVar(entry, key, connectionId) {
791
+ const envVarName = entry.envVars[key];
792
+ if (!envVarName) {
793
+ throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
794
+ }
795
+ const value = process.env[envVarName];
796
+ if (!value) {
797
+ throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
798
+ }
799
+ return value;
800
+ }
801
+ function resolveEnvVarOptional(entry, key) {
802
+ const envVarName = entry.envVars[key];
803
+ if (!envVarName) return void 0;
804
+ return process.env[envVarName] || void 0;
805
+ }
806
+
807
+ // src/connector-client/proxy-fetch.ts
808
+ import { getContext } from "hono/context-storage";
809
+ import { getCookie } from "hono/cookie";
810
+ var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
811
+ var TABLEAU_SESSION_SENTINEL_URL = "squadbase://tableau-session/";
812
+ function normalizeHeaders(input) {
813
+ const out = {};
814
+ if (!input) return out;
815
+ new Headers(input).forEach((value, key) => {
816
+ out[key] = value;
817
+ });
818
+ return out;
819
+ }
820
+ function extractInputUrl(input) {
821
+ if (typeof input === "string") return input;
822
+ if (input instanceof URL) return input.href;
823
+ return input.url;
824
+ }
825
+ function createSandboxProxyFetch(connectionId) {
826
+ return async (input, init) => {
827
+ const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
828
+ const sandboxId = process.env.INTERNAL_SQUADBASE_SANDBOX_ID;
829
+ if (!token || !sandboxId) {
830
+ throw new Error(
831
+ "Connection proxy is not configured. Please check your deployment settings."
832
+ );
833
+ }
834
+ const originalUrl = extractInputUrl(input);
835
+ const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
836
+ if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
837
+ const sessionUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
838
+ return fetch(sessionUrl, {
839
+ method: "POST",
840
+ headers: { Authorization: `Bearer ${token}` }
841
+ });
842
+ }
843
+ const originalMethod = init?.method ?? "GET";
844
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
845
+ const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
846
+ return fetch(proxyUrl, {
847
+ method: "POST",
848
+ headers: {
849
+ "Content-Type": "application/json",
850
+ Authorization: `Bearer ${token}`
851
+ },
852
+ body: JSON.stringify({
853
+ url: originalUrl,
854
+ method: originalMethod,
855
+ headers: normalizeHeaders(init?.headers),
856
+ body: originalBody
857
+ })
858
+ });
859
+ };
860
+ }
861
+ function createDeployedAppProxyFetch(connectionId) {
862
+ const projectId = process.env["SQUADBASE_PROJECT_ID"];
863
+ if (!projectId) {
864
+ throw new Error(
865
+ "Connection proxy is not configured. Please check your deployment settings."
866
+ );
867
+ }
868
+ const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
869
+ const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
870
+ const sessionUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
871
+ return async (input, init) => {
872
+ const originalUrl = extractInputUrl(input);
873
+ const c = getContext();
874
+ const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
875
+ if (!appSession) {
876
+ throw new Error(
877
+ "No authentication method available for connection proxy."
878
+ );
879
+ }
880
+ if (originalUrl === TABLEAU_SESSION_SENTINEL_URL) {
881
+ return fetch(sessionUrl, {
882
+ method: "POST",
883
+ headers: { Authorization: `Bearer ${appSession}` }
884
+ });
885
+ }
886
+ const originalMethod = init?.method ?? "GET";
887
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
888
+ return fetch(proxyUrl, {
889
+ method: "POST",
890
+ headers: {
891
+ "Content-Type": "application/json",
892
+ Authorization: `Bearer ${appSession}`
893
+ },
894
+ body: JSON.stringify({
895
+ url: originalUrl,
896
+ method: originalMethod,
897
+ headers: normalizeHeaders(init?.headers),
898
+ body: originalBody
899
+ })
900
+ });
901
+ };
902
+ }
903
+ function createProxyFetch(connectionId) {
904
+ if (process.env.INTERNAL_SQUADBASE_SANDBOX_ID) {
905
+ return createSandboxProxyFetch(connectionId);
906
+ }
907
+ return createDeployedAppProxyFetch(connectionId);
908
+ }
909
+
910
+ // src/connectors/create-connector-sdk.ts
911
+ function loadConnectionsSync() {
912
+ const filePath = process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
913
+ try {
914
+ const raw = readFileSync(filePath, "utf-8");
915
+ return JSON.parse(raw);
916
+ } catch {
917
+ return {};
918
+ }
919
+ }
920
+ function createConnectorSdk(plugin, createClient2) {
921
+ return (connectionId) => {
922
+ const connections = loadConnectionsSync();
923
+ const entry = connections[connectionId];
924
+ if (!entry) {
925
+ throw new Error(
926
+ `Connection "${connectionId}" not found in .squadbase/connections.json`
927
+ );
928
+ }
929
+ if (entry.connector.slug !== plugin.slug) {
930
+ throw new Error(
931
+ `Connection "${connectionId}" is not a ${plugin.slug} connection (got "${entry.connector.slug}")`
932
+ );
933
+ }
934
+ const params = {};
935
+ for (const param of Object.values(plugin.parameters)) {
936
+ if (param.required) {
937
+ params[param.slug] = resolveEnvVar(entry, param.slug, connectionId);
938
+ } else {
939
+ const val = resolveEnvVarOptional(entry, param.slug);
940
+ if (val !== void 0) params[param.slug] = val;
941
+ }
942
+ }
943
+ return createClient2(params, createProxyFetch(connectionId));
944
+ };
945
+ }
946
+
947
+ // src/connectors/entries/x.ts
948
+ var connection = createConnectorSdk(xConnector, createClient);
949
+ export {
950
+ connection
951
+ };