@squadbase/vite-server 0.1.9-dev.d3c856d → 0.1.9-dev.f236b23

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.
@@ -0,0 +1,825 @@
1
+ // ../connectors/src/parameter-definition.ts
2
+ var ParameterDefinition = class {
3
+ slug;
4
+ name;
5
+ description;
6
+ envVarBaseKey;
7
+ type;
8
+ secret;
9
+ required;
10
+ constructor(config) {
11
+ this.slug = config.slug;
12
+ this.name = config.name;
13
+ this.description = config.description;
14
+ this.envVarBaseKey = config.envVarBaseKey;
15
+ this.type = config.type;
16
+ this.secret = config.secret;
17
+ this.required = config.required;
18
+ }
19
+ /**
20
+ * Get the parameter value from a ConnectorConnectionObject.
21
+ */
22
+ getValue(connection2) {
23
+ const param = connection2.parameters.find(
24
+ (p) => p.parameterSlug === this.slug
25
+ );
26
+ if (!param || param.value == null) {
27
+ throw new Error(
28
+ `Parameter "${this.slug}" not found or has no value in connection "${connection2.id}"`
29
+ );
30
+ }
31
+ return param.value;
32
+ }
33
+ /**
34
+ * Try to get the parameter value. Returns undefined if not found (for optional params).
35
+ */
36
+ tryGetValue(connection2) {
37
+ const param = connection2.parameters.find(
38
+ (p) => p.parameterSlug === this.slug
39
+ );
40
+ if (!param || param.value == null) return void 0;
41
+ return param.value;
42
+ }
43
+ };
44
+
45
+ // ../connectors/src/connectors/semrush/parameters.ts
46
+ var parameters = {
47
+ apiKey: new ParameterDefinition({
48
+ slug: "api-key",
49
+ name: "Semrush API Key",
50
+ description: "Your Semrush API key from Profile > Subscription info > API units > API key. A subscription with API units is required.",
51
+ envVarBaseKey: "SEMRUSH_API_KEY",
52
+ type: "text",
53
+ secret: true,
54
+ required: true
55
+ })
56
+ };
57
+
58
+ // ../connectors/src/connectors/semrush/sdk/index.ts
59
+ var BASE_URL = "https://api.semrush.com";
60
+ function createClient(params) {
61
+ const apiKey = params[parameters.apiKey.slug];
62
+ if (!apiKey) {
63
+ throw new Error(
64
+ `semrush: missing required parameter: ${parameters.apiKey.slug}`
65
+ );
66
+ }
67
+ function buildUrl(path2, query) {
68
+ const url = new URL(`${BASE_URL}${path2.startsWith("/") ? "" : "/"}${path2}`);
69
+ if (query) {
70
+ for (const [k, v] of Object.entries(query)) {
71
+ if (k === "key") continue;
72
+ url.searchParams.set(k, v);
73
+ }
74
+ }
75
+ url.searchParams.set("key", apiKey);
76
+ return url.toString();
77
+ }
78
+ function parseCsv(text) {
79
+ const trimmed = text.trim();
80
+ if (trimmed.length === 0) {
81
+ return { columns: [], rows: [], raw: text };
82
+ }
83
+ const lines = trimmed.split(/\r?\n/);
84
+ const columns = lines[0].split(";");
85
+ const rows = [];
86
+ for (let i = 1; i < lines.length; i++) {
87
+ const cells = lines[i].split(";");
88
+ const row = {};
89
+ for (let c = 0; c < columns.length; c++) {
90
+ row[columns[c]] = cells[c] ?? "";
91
+ }
92
+ rows.push(row);
93
+ }
94
+ return { columns, rows, raw: text };
95
+ }
96
+ async function request(path2, init) {
97
+ const { query, ...rest } = init ?? {};
98
+ return fetch(buildUrl(path2, query), rest);
99
+ }
100
+ return {
101
+ request,
102
+ async report(type, query) {
103
+ const res = await request("/", { query: { type, ...query ?? {} } });
104
+ const text = await res.text();
105
+ if (text.startsWith("ERROR ")) {
106
+ throw new Error(`semrush: ${text.trim()}`);
107
+ }
108
+ if (!res.ok) {
109
+ throw new Error(
110
+ `semrush: ${res.status} ${res.statusText}: ${text}`
111
+ );
112
+ }
113
+ return parseCsv(text);
114
+ },
115
+ async trends(path2, query) {
116
+ const trendsPath = path2.startsWith("/analytics/") ? path2 : `/analytics/v1/${path2.replace(/^\//, "")}`;
117
+ const res = await request(trendsPath, { query });
118
+ const text = await res.text();
119
+ if (!res.ok) {
120
+ throw new Error(
121
+ `semrush trends: ${res.status} ${res.statusText}: ${text}`
122
+ );
123
+ }
124
+ try {
125
+ return JSON.parse(text);
126
+ } catch {
127
+ throw new Error(
128
+ `semrush trends: response is not JSON: ${text.slice(0, 200)}`
129
+ );
130
+ }
131
+ },
132
+ async projects(path2, init) {
133
+ const projectsPath = path2.startsWith("/management/") ? path2 : `/management/v1/${path2.replace(/^\//, "")}`;
134
+ const res = await request(projectsPath, init);
135
+ const text = await res.text();
136
+ if (!res.ok) {
137
+ throw new Error(
138
+ `semrush projects: ${res.status} ${res.statusText}: ${text}`
139
+ );
140
+ }
141
+ try {
142
+ return JSON.parse(text);
143
+ } catch {
144
+ throw new Error(
145
+ `semrush projects: response is not JSON: ${text.slice(0, 200)}`
146
+ );
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
+ constructor(config) {
212
+ this.slug = config.slug;
213
+ this.authType = config.authType;
214
+ this.name = config.name;
215
+ this.description = config.description;
216
+ this.iconUrl = config.iconUrl;
217
+ this.parameters = config.parameters;
218
+ this.releaseFlag = config.releaseFlag;
219
+ this.proxyPolicy = config.proxyPolicy;
220
+ this.experimentalAttributes = config.experimentalAttributes;
221
+ this.categories = config.categories ?? [];
222
+ this.onboarding = config.onboarding;
223
+ this.systemPrompt = config.systemPrompt;
224
+ this.tools = config.tools;
225
+ this.query = config.query;
226
+ this.checkConnection = config.checkConnection;
227
+ }
228
+ get connectorKey() {
229
+ return _ConnectorPlugin.deriveKey(this.slug, this.authType);
230
+ }
231
+ /**
232
+ * Create tools for connections that belong to this connector.
233
+ * Filters connections by connectorKey internally.
234
+ * Returns tools keyed as `${connectorKey}_${toolName}`.
235
+ */
236
+ createTools(connections, config, opts) {
237
+ const myConnections = connections.filter(
238
+ (c) => _ConnectorPlugin.deriveKey(c.connector.slug, c.connector.authType) === this.connectorKey
239
+ );
240
+ const result = {};
241
+ for (const t of Object.values(this.tools)) {
242
+ const tool = t.createTool(myConnections, config);
243
+ const originalToModelOutput = tool.toModelOutput;
244
+ result[`${this.connectorKey}_${t.name}`] = {
245
+ ...tool,
246
+ toModelOutput: async (options) => {
247
+ if (!originalToModelOutput) {
248
+ return opts.truncateOutput(options.output);
249
+ }
250
+ const modelOutput = await originalToModelOutput(options);
251
+ if (modelOutput.type === "text" || modelOutput.type === "json") {
252
+ return opts.truncateOutput(modelOutput.value);
253
+ }
254
+ return modelOutput;
255
+ }
256
+ };
257
+ }
258
+ return result;
259
+ }
260
+ static deriveKey(slug, authType) {
261
+ if (authType) return `${slug}-${authType}`;
262
+ const LEGACY_NULL_AUTH_TYPE_MAP = {
263
+ // user-password
264
+ "postgresql": "user-password",
265
+ "mysql": "user-password",
266
+ "clickhouse": "user-password",
267
+ "kintone": "user-password",
268
+ "squadbase-db": "user-password",
269
+ // service-account
270
+ "snowflake": "service-account",
271
+ "bigquery": "service-account",
272
+ "google-analytics": "service-account",
273
+ "google-calendar": "service-account",
274
+ "aws-athena": "service-account",
275
+ "redshift": "service-account",
276
+ // api-key
277
+ "databricks": "api-key",
278
+ "dbt": "api-key",
279
+ "airtable": "api-key",
280
+ "openai": "api-key",
281
+ "gemini": "api-key",
282
+ "anthropic": "api-key",
283
+ "wix-store": "api-key"
284
+ };
285
+ const fallbackAuthType = LEGACY_NULL_AUTH_TYPE_MAP[slug];
286
+ if (fallbackAuthType) return `${slug}-${fallbackAuthType}`;
287
+ return slug;
288
+ }
289
+ };
290
+
291
+ // ../connectors/src/auth-types.ts
292
+ var AUTH_TYPES = {
293
+ OAUTH: "oauth",
294
+ API_KEY: "api-key",
295
+ JWT: "jwt",
296
+ SERVICE_ACCOUNT: "service-account",
297
+ PAT: "pat",
298
+ USER_PASSWORD: "user-password"
299
+ };
300
+
301
+ // ../connectors/src/connectors/semrush/tools/request.ts
302
+ import { z } from "zod";
303
+ var BASE_URL2 = "https://api.semrush.com";
304
+ var REQUEST_TIMEOUT_MS = 6e4;
305
+ var inputSchema = z.object({
306
+ toolUseIntent: z.string().optional().describe(
307
+ "Brief description of what you intend to accomplish with this tool call"
308
+ ),
309
+ connectionId: z.string().describe("ID of the Semrush connection to use"),
310
+ method: z.enum(["GET", "POST"]).describe(
311
+ "HTTP method. GET for almost all Semrush endpoints. POST is used for a few Projects API mutations."
312
+ ),
313
+ path: z.string().describe(
314
+ "API path appended to https://api.semrush.com (e.g., '/' for the Standard Analytics API, '/analytics/v1/' for the Trends API, '/management/v1/projects/' for the Projects API). You may also pass an absolute https:// URL on a Semrush host (e.g., 'https://www.semrush.com/users/countapiunits.html' to check API unit balance)."
315
+ ),
316
+ queryParams: z.record(z.string(), z.string()).optional().describe(
317
+ "Query parameters to append to the URL. The 'key' parameter is injected automatically \u2014 do not include it. Common params: 'type' (report type, e.g. 'domain_overview'), 'domain', 'phrase', 'database' (e.g. 'us', 'uk', 'jp'), 'export_columns' (comma-separated), 'display_limit', 'display_offset', 'display_date' (YYYYMM15)."
318
+ ),
319
+ body: z.string().optional().describe(
320
+ "Request body for POST requests. Pass a raw string (JSON for the Projects API, form-encoded otherwise). Set 'contentType' to control the Content-Type header."
321
+ ),
322
+ contentType: z.enum(["application/json", "application/x-www-form-urlencoded"]).optional().describe(
323
+ "Content-Type header for POST requests. Defaults to application/json."
324
+ ),
325
+ responseFormat: z.enum(["text", "json"]).optional().describe(
326
+ "How to parse the response body. Defaults to 'text' because the Standard Analytics API returns semicolon-separated CSV. Use 'json' for the Trends API and Projects API which return JSON."
327
+ )
328
+ });
329
+ var outputSchema = z.discriminatedUnion("success", [
330
+ z.object({
331
+ success: z.literal(true),
332
+ status: z.number(),
333
+ contentType: z.string().optional(),
334
+ data: z.unknown()
335
+ }),
336
+ z.object({
337
+ success: z.literal(false),
338
+ error: z.string()
339
+ })
340
+ ]);
341
+ var requestTool = new ConnectorTool({
342
+ name: "request",
343
+ description: `Send authenticated requests to the Semrush API (https://api.semrush.com).
344
+ Use this tool for all Semrush interactions across the Standard Analytics API (domain/keyword/backlink reports), the Trends API (\`/analytics/v1/\`), and the Projects API (\`/management/v1/projects/\`).
345
+ Authentication is handled automatically \u2014 the API key is appended as the \`key\` query parameter on every request. Do NOT include \`key\` in queryParams.
346
+
347
+ The Standard Analytics API returns semicolon-separated CSV with the first row being the header (use responseFormat="text"). The Trends and Projects APIs return JSON (use responseFormat="json").
348
+ Errors from the Standard API are returned as a plain text body starting with "ERROR" and HTTP 200, so always inspect the response body even on success.`,
349
+ inputSchema,
350
+ outputSchema,
351
+ async execute({
352
+ connectionId,
353
+ method,
354
+ path: path2,
355
+ queryParams,
356
+ body,
357
+ contentType,
358
+ responseFormat
359
+ }, connections) {
360
+ const connection2 = connections.find((c) => c.id === connectionId);
361
+ if (!connection2) {
362
+ return {
363
+ success: false,
364
+ error: `Connection ${connectionId} not found`
365
+ };
366
+ }
367
+ console.log(
368
+ `[connector-request] semrush/${connection2.name}: ${method} ${path2}`
369
+ );
370
+ try {
371
+ const apiKey = parameters.apiKey.getValue(connection2);
372
+ const isAbsolute = /^https?:\/\//i.test(path2);
373
+ if (isAbsolute) {
374
+ const host = new URL(path2).hostname.toLowerCase();
375
+ if (host !== "api.semrush.com" && host !== "www.semrush.com") {
376
+ return {
377
+ success: false,
378
+ error: `Absolute URLs are only allowed on api.semrush.com or www.semrush.com (got ${host})`
379
+ };
380
+ }
381
+ }
382
+ const url = new URL(
383
+ isAbsolute ? path2 : `${BASE_URL2}${path2.startsWith("/") ? "" : "/"}${path2}`
384
+ );
385
+ if (queryParams) {
386
+ for (const [k, v] of Object.entries(queryParams)) {
387
+ if (k === "key") continue;
388
+ url.searchParams.set(k, v);
389
+ }
390
+ }
391
+ url.searchParams.set("key", apiKey);
392
+ const controller = new AbortController();
393
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
394
+ try {
395
+ const headers = {};
396
+ if (body) {
397
+ headers["Content-Type"] = contentType ?? "application/json";
398
+ }
399
+ const response = await fetch(url.toString(), {
400
+ method,
401
+ headers,
402
+ body: body ?? void 0,
403
+ signal: controller.signal
404
+ });
405
+ const responseContentType = response.headers.get("content-type") ?? void 0;
406
+ const text = await response.text();
407
+ if (text.startsWith("ERROR ")) {
408
+ return { success: false, error: text.trim() };
409
+ }
410
+ if (!response.ok) {
411
+ return {
412
+ success: false,
413
+ error: text || `HTTP ${response.status} ${response.statusText}`
414
+ };
415
+ }
416
+ const wantJson = responseFormat === "json" || responseFormat === void 0 && (responseContentType?.includes("application/json") ?? false);
417
+ let data = text;
418
+ if (wantJson && text.length > 0) {
419
+ try {
420
+ data = JSON.parse(text);
421
+ } catch {
422
+ data = text;
423
+ }
424
+ }
425
+ return {
426
+ success: true,
427
+ status: response.status,
428
+ contentType: responseContentType,
429
+ data
430
+ };
431
+ } finally {
432
+ clearTimeout(timeout);
433
+ }
434
+ } catch (err) {
435
+ const msg = err instanceof Error ? err.message : String(err);
436
+ return { success: false, error: msg };
437
+ }
438
+ }
439
+ });
440
+
441
+ // ../connectors/src/connectors/semrush/setup.ts
442
+ var requestToolName = `semrush-api-key_${requestTool.name}`;
443
+ var semrushOnboarding = new ConnectorOnboarding({
444
+ connectionSetupInstructions: {
445
+ ja: `\u4EE5\u4E0B\u306E\u624B\u9806\u3067Semrush\u30B3\u30CD\u30AF\u30B7\u30E7\u30F3\u306E\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3092\u884C\u3063\u3066\u304F\u3060\u3055\u3044\u3002
446
+
447
+ 1. \`${requestToolName}\` \u3092\u547C\u3073\u51FA\u3057\u3066\u3001API\u6B8B\u91CF\u3092\u78BA\u8A8D\u3059\u308B\uFF08\u3053\u306E\u30EA\u30AF\u30A8\u30B9\u30C8\u306FAPI\u30E6\u30CB\u30C3\u30C8\u3092\u6D88\u8CBB\u3057\u307E\u305B\u3093\uFF09:
448
+ - \`method\`: \`"GET"\`
449
+ - \`path\`: \`"https://www.semrush.com/users/countapiunits.html"\`
450
+ - \`queryParams\`: \u306A\u3057
451
+ - \`responseFormat\`: \`"text"\`
452
+ 2. \u30EC\u30B9\u30DD\u30F3\u30B9\u672C\u6587\u304C \`ERROR\` \u3067\u59CB\u307E\u308B\u3001\u307E\u305F\u306FAPI\u30E6\u30CB\u30C3\u30C8\u6B8B\u91CF\u304C0\u306E\u5834\u5408\u3001\u30E6\u30FC\u30B6\u30FC\u306BAPI\u30AD\u30FC\u3068\u30B5\u30D6\u30B9\u30AF\u30EA\u30D7\u30B7\u30E7\u30F3\u72B6\u614B\uFF08API units \u304C\u4ED8\u4E0E\u3055\u308C\u3066\u3044\u308B\u304B\uFF09\u306E\u78BA\u8A8D\u3092\u4F9D\u983C\u3059\u308B
453
+
454
+ #### \u5236\u7D04
455
+ - **\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u4E2D\u306BAPI\u30E6\u30CB\u30C3\u30C8\u3092\u6D88\u8CBB\u3059\u308B\u30EC\u30DD\u30FC\u30C8\u3092\u5B9F\u884C\u3057\u306A\u3044\u3053\u3068**\u3002\u5B9F\u884C\u3057\u3066\u3088\u3044\u306E\u306F\u4E0A\u8A18\u306E\u6B8B\u91CF\u78BA\u8A8D\u30EA\u30AF\u30A8\u30B9\u30C8\u306E\u307F\uFF08\`countapiunits.html\` \u306F\u7121\u6599\uFF09
456
+ - \u30C4\u30FC\u30EB\u9593\u306F1\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`,
457
+ en: `Follow these steps to set up the Semrush connection.
458
+
459
+ 1. Call \`${requestToolName}\` to check the remaining API units (this request does NOT consume API units):
460
+ - \`method\`: \`"GET"\`
461
+ - \`path\`: \`"https://www.semrush.com/users/countapiunits.html"\`
462
+ - \`queryParams\`: none
463
+ - \`responseFormat\`: \`"text"\`
464
+ 2. If the response body starts with \`ERROR\` or API units are 0, ask the user to verify the API key and subscription status (API units must be granted to the account)
465
+
466
+ #### Constraints
467
+ - **Do NOT run reports that consume API units during setup**. Only the unit balance check above is allowed (the \`countapiunits.html\` endpoint is free)
468
+ - Write only 1 sentence between tool calls, then immediately call the next tool. Skip unnecessary explanations and proceed efficiently`
469
+ },
470
+ dataOverviewInstructions: {
471
+ en: `1. Call ${requestToolName} with path "/" and queryParams \`{ "type": "domain_overview", "domain": "<example.com>", "database": "us" }\` to inspect the domain overview report (CSV)
472
+ 2. Call ${requestToolName} with path "/" and queryParams \`{ "type": "domain_organic", "domain": "<example.com>", "database": "us", "display_limit": "5" }\` to sample organic keywords
473
+ 3. Call ${requestToolName} with path "/" and queryParams \`{ "type": "phrase_this", "phrase": "<keyword>", "database": "us" }\` to inspect a keyword overview
474
+ 4. Explore other report types (backlinks_overview, domain_adwords, phrase_related) and the Trends API ("/analytics/v1/...") with responseFormat="json" as needed
475
+ 5. Remember: the Standard Analytics API returns semicolon-separated CSV with the first row as the header`,
476
+ ja: `1. ${requestToolName} \u3067 path "/" \u3068 queryParams \`{ "type": "domain_overview", "domain": "<example.com>", "database": "us" }\` \u3092\u547C\u3073\u51FA\u3057\u3001\u30C9\u30E1\u30A4\u30F3\u30AA\u30FC\u30D0\u30FC\u30D3\u30E5\u30FC\uFF08CSV\uFF09\u3092\u78BA\u8A8D
477
+ 2. ${requestToolName} \u3067 path "/" \u3068 queryParams \`{ "type": "domain_organic", "domain": "<example.com>", "database": "us", "display_limit": "5" }\` \u3092\u547C\u3073\u51FA\u3057\u3001\u30AA\u30FC\u30AC\u30CB\u30C3\u30AF\u30AD\u30FC\u30EF\u30FC\u30C9\u3092\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0
478
+ 3. ${requestToolName} \u3067 path "/" \u3068 queryParams \`{ "type": "phrase_this", "phrase": "<keyword>", "database": "us" }\` \u3092\u547C\u3073\u51FA\u3057\u3001\u30AD\u30FC\u30EF\u30FC\u30C9\u6982\u8981\u3092\u78BA\u8A8D
479
+ 4. \u5FC5\u8981\u306B\u5FDC\u3058\u3066\u4ED6\u306E\u30EC\u30DD\u30FC\u30C8\u30BF\u30A4\u30D7\uFF08backlinks_overview\u3001domain_adwords\u3001phrase_related\uFF09\u3084 Trends API ("/analytics/v1/...", responseFormat="json") \u3092\u63A2\u7D22
480
+ 5. \u6CE8\u610F: Standard Analytics API \u306F\u30BB\u30DF\u30B3\u30ED\u30F3\u533A\u5207\u308ACSV\u3092\u8FD4\u3057\u30011\u884C\u76EE\u304C\u30D8\u30C3\u30C0\u30FC`
481
+ }
482
+ });
483
+
484
+ // ../connectors/src/connectors/semrush/index.ts
485
+ var tools = { request: requestTool };
486
+ var semrushConnector = new ConnectorPlugin({
487
+ slug: "semrush",
488
+ authType: AUTH_TYPES.API_KEY,
489
+ name: "Semrush",
490
+ description: "Connect to Semrush for SEO, paid search, content, and competitive intelligence data via the Semrush API (Standard Analytics, Trends, and Projects).",
491
+ iconUrl: "https://images.ctfassets.net/9ncizv60xc5y/4mrVYrg72PeaEv5F6hEF6g/b6cd050f9d97b9998ab3371cfe656988/semrush-icon.png",
492
+ parameters,
493
+ releaseFlag: { dev1: true, dev2: true, prod: true },
494
+ categories: ["marketing"],
495
+ onboarding: semrushOnboarding,
496
+ systemPrompt: {
497
+ en: `### Tools
498
+
499
+ - \`semrush-api-key_request\`: The only way to call the Semrush API. Use it for the Standard Analytics API (\`/?type=...\` reports), the Trends API (\`/analytics/v1/...\`), and the Projects API (\`/management/v1/...\`). Authentication is handled automatically \u2014 the API key is appended as the \`key\` query parameter; never include it yourself. The Standard Analytics API returns semicolon-separated CSV with the first row as the header (use \`responseFormat="text"\`); the Trends and Projects APIs return JSON (use \`responseFormat="json"\`). Errors from the Standard API are returned as a body that starts with \`ERROR\`, often with HTTP 200 \u2014 the tool surfaces these as \`success: false\` automatically.
500
+
501
+ ### Business Logic
502
+
503
+ The business logic type for this connector is "typescript". Write handler code using the connector SDK shown below. Do NOT access credentials directly from environment variables.
504
+
505
+ SDK methods (client created via \`connection(connectionId)\`):
506
+ - \`client.request(path, init?)\` \u2014 low-level authenticated fetch. Pass \`init.query\` to set query parameters; \`key\` is injected automatically
507
+ - \`client.report(type, query?)\` \u2014 call a Standard Analytics report and parse the CSV into \`{ columns, rows, raw }\`
508
+ - \`client.trends(path, query?)\` \u2014 call a Trends API endpoint (\`/analytics/v1/...\`) and return parsed JSON
509
+ - \`client.projects(path, init?)\` \u2014 call a Projects API endpoint (\`/management/v1/...\`) and return parsed JSON
510
+
511
+ \`\`\`ts
512
+ import type { Context } from "hono";
513
+ import { connection } from "@squadbase/vite-server/connectors/semrush";
514
+
515
+ const semrush = connection("<connectionId>");
516
+
517
+ export default async function handler(c: Context) {
518
+ const { domain = "example.com", database = "us" } = await c.req.json<{
519
+ domain?: string;
520
+ database?: string;
521
+ }>();
522
+
523
+ const overview = await semrush.report("domain_overview", { domain, database });
524
+
525
+ return c.json({ columns: overview.columns, rows: overview.rows });
526
+ }
527
+ \`\`\`
528
+
529
+ ### Semrush API Reference
530
+
531
+ - Standard Analytics API: \`https://api.semrush.com/?type={report}&key={apiKey}&...\` \u2014 semicolon-separated CSV
532
+ - Trends API: \`https://api.semrush.com/analytics/v1/...\` \u2014 JSON
533
+ - Projects (Management) API: \`https://api.semrush.com/management/v1/...\` \u2014 JSON
534
+
535
+ Authentication: API key passed as the \`key\` query parameter on every request (handled automatically).
536
+
537
+ #### Common Standard Analytics report types
538
+ - \`domain_overview\` \u2014 domain summary (organic/paid traffic, keywords, backlinks)
539
+ - \`domain_organic\` \u2014 organic keywords for a domain
540
+ - \`domain_adwords\` \u2014 paid keywords for a domain
541
+ - \`domain_organic_organic\` / \`domain_adwords_adwords\` \u2014 organic / paid competitors
542
+ - \`phrase_this\` \u2014 keyword overview (search volume, CPC, competition, trend)
543
+ - \`phrase_related\` \u2014 related keywords
544
+ - \`phrase_fullsearch\` \u2014 full-text keyword research
545
+ - \`phrase_questions\` \u2014 question keywords
546
+ - \`phrase_kdi\` \u2014 keyword difficulty index
547
+ - \`backlinks_overview\` \u2014 backlinks summary
548
+ - \`backlinks\` \u2014 list of backlinks
549
+ - \`backlinks_refdomains\` \u2014 referring domains
550
+ - \`url_organic\` / \`url_adwords\` \u2014 keywords ranking for a specific URL
551
+
552
+ To check remaining API units (free, does NOT consume units), call the request tool with an absolute URL: \`path: "https://www.semrush.com/users/countapiunits.html"\`, no query params, \`responseFormat: "text"\`. The response body is just a number.
553
+
554
+ #### Common query parameters
555
+ - \`type\` \u2014 report type (required for the Standard API)
556
+ - \`domain\` / \`phrase\` / \`url\` \u2014 entity to query
557
+ - \`database\` \u2014 regional database (e.g. \`us\`, \`uk\`, \`de\`, \`fr\`, \`jp\`, \`br\`); required for most reports
558
+ - \`display_limit\` \u2014 page size (default 10000, max 100000 depending on report)
559
+ - \`display_offset\` \u2014 pagination offset
560
+ - \`display_date\` \u2014 historical date in \`YYYYMM15\` format (always day 15)
561
+ - \`export_columns\` \u2014 comma-separated columns to return (e.g. \`Ph,Po,Nq,Cp\`)
562
+ - \`display_sort\` \u2014 sort directive (e.g. \`tr_desc\`)
563
+ - \`display_filter\` \u2014 filter directive (e.g. \`+|Ph|Co|keyword\`)
564
+
565
+ #### Tips
566
+ - Each Standard Analytics report consumes API units; check the unit balance via \`https://www.semrush.com/users/countapiunits.html?key=...\` (free) first if you suspect a quota issue
567
+ - The CSV separator is \`;\` (semicolon), NOT \`,\`. Some cells may contain commas inside them.
568
+ - An HTTP 200 response with a body starting with \`ERROR\` indicates an API error (auth, parameters, or quota)
569
+ - The Trends API requires a separate Trends subscription; calls without it will fail with an authorization error
570
+ - Date strings in historical endpoints must be the 15th of the month (\`YYYYMM15\`)`,
571
+ ja: `### \u30C4\u30FC\u30EB
572
+
573
+ - \`semrush-api-key_request\`: Semrush API\u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002Standard Analytics API\uFF08\`/?type=...\` \u5F62\u5F0F\u306E\u30EC\u30DD\u30FC\u30C8\uFF09\u3001Trends API\uFF08\`/analytics/v1/...\`\uFF09\u3001Projects API\uFF08\`/management/v1/...\`\uFF09\u3059\u3079\u3066\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u8A8D\u8A3C\u306F\u81EA\u52D5\u7684\u306B\u884C\u308F\u308C\u3001API\u30AD\u30FC\u306F \`key\` \u30AF\u30A8\u30EA\u30D1\u30E9\u30E1\u30FC\u30BF\u3068\u3057\u3066\u4ED8\u4E0E\u3055\u308C\u308B\u305F\u3081\u3001\u81EA\u5206\u3067\u542B\u3081\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002Standard Analytics API \u306F1\u884C\u76EE\u304C\u30D8\u30C3\u30C0\u30FC\u306E\u30BB\u30DF\u30B3\u30ED\u30F3\u533A\u5207\u308ACSV\u3092\u8FD4\u3059\u305F\u3081 \`responseFormat="text"\` \u3092\u4F7F\u7528\u3057\u3001Trends/Projects API \u306F JSON \u3092\u8FD4\u3059\u305F\u3081 \`responseFormat="json"\` \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002Standard API \u306E\u30A8\u30E9\u30FC\u306F HTTP 200 \u3067\u3082\u672C\u6587\u304C \`ERROR\` \u3067\u59CB\u307E\u308B\u5F62\u5F0F\u3067\u8FD4\u308B\u3053\u3068\u304C\u3042\u308A\u307E\u3059\u304C\u3001\u30C4\u30FC\u30EB\u306F\u81EA\u52D5\u7684\u306B \`success: false\` \u3068\u3057\u3066\u8FD4\u3057\u307E\u3059\u3002
574
+
575
+ ### Business Logic
576
+
577
+ \u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306E\u30D3\u30B8\u30CD\u30B9\u30ED\u30B8\u30C3\u30AF\u30BF\u30A4\u30D7\u306F "typescript" \u3067\u3059\u3002\u4EE5\u4E0B\u306B\u793A\u3059\u30B3\u30CD\u30AF\u30BFSDK\u3092\u4F7F\u7528\u3057\u3066\u30CF\u30F3\u30C9\u30E9\u30B3\u30FC\u30C9\u3092\u8A18\u8FF0\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u74B0\u5883\u5909\u6570\u304B\u3089\u76F4\u63A5\u8A8D\u8A3C\u60C5\u5831\u306B\u30A2\u30AF\u30BB\u30B9\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
578
+
579
+ SDK\u30E1\u30BD\u30C3\u30C9 (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8):
580
+ - \`client.request(path, init?)\` \u2014 \u8A8D\u8A3C\u4ED8\u304D\u306E\u4F4E\u30EC\u30D9\u30EBfetch\u3002\`init.query\` \u3067\u30AF\u30A8\u30EA\u30D1\u30E9\u30E1\u30FC\u30BF\u3092\u6307\u5B9A\u3002\`key\` \u306F\u81EA\u52D5\u4ED8\u4E0E
581
+ - \`client.report(type, query?)\` \u2014 Standard Analytics \u306E\u30EC\u30DD\u30FC\u30C8\u3092\u547C\u3073\u51FA\u3057\u3001CSV\u3092 \`{ columns, rows, raw }\` \u306B\u30D1\u30FC\u30B9
582
+ - \`client.trends(path, query?)\` \u2014 Trends API \u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\uFF08\`/analytics/v1/...\`\uFF09\u3092\u547C\u3073\u51FA\u3057 JSON \u3092\u8FD4\u3059
583
+ - \`client.projects(path, init?)\` \u2014 Projects API \u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\uFF08\`/management/v1/...\`\uFF09\u3092\u547C\u3073\u51FA\u3057 JSON \u3092\u8FD4\u3059
584
+
585
+ \`\`\`ts
586
+ import type { Context } from "hono";
587
+ import { connection } from "@squadbase/vite-server/connectors/semrush";
588
+
589
+ const semrush = connection("<connectionId>");
590
+
591
+ export default async function handler(c: Context) {
592
+ const { domain = "example.com", database = "us" } = await c.req.json<{
593
+ domain?: string;
594
+ database?: string;
595
+ }>();
596
+
597
+ const overview = await semrush.report("domain_overview", { domain, database });
598
+
599
+ return c.json({ columns: overview.columns, rows: overview.rows });
600
+ }
601
+ \`\`\`
602
+
603
+ ### Semrush API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
604
+
605
+ - Standard Analytics API: \`https://api.semrush.com/?type={report}&key={apiKey}&...\` \u2014 \u30BB\u30DF\u30B3\u30ED\u30F3\u533A\u5207\u308ACSV
606
+ - Trends API: \`https://api.semrush.com/analytics/v1/...\` \u2014 JSON
607
+ - Projects (Management) API: \`https://api.semrush.com/management/v1/...\` \u2014 JSON
608
+
609
+ \u8A8D\u8A3C: API\u30AD\u30FC\u3092\u3059\u3079\u3066\u306E\u30EA\u30AF\u30A8\u30B9\u30C8\u306B \`key\` \u30AF\u30A8\u30EA\u30D1\u30E9\u30E1\u30FC\u30BF\u3068\u3057\u3066\u4ED8\u4E0E\uFF08\u81EA\u52D5\uFF09\u3002
610
+
611
+ #### \u4E3B\u8981\u306A Standard Analytics \u30EC\u30DD\u30FC\u30C8\u30BF\u30A4\u30D7
612
+ - \`domain_overview\` \u2014 \u30C9\u30E1\u30A4\u30F3\u306E\u30B5\u30DE\u30EA\u30FC\uFF08\u30AA\u30FC\u30AC\u30CB\u30C3\u30AF/\u6709\u6599\u30C8\u30E9\u30D5\u30A3\u30C3\u30AF\u3001\u30AD\u30FC\u30EF\u30FC\u30C9\u3001\u30D0\u30C3\u30AF\u30EA\u30F3\u30AF\uFF09
613
+ - \`domain_organic\` \u2014 \u30C9\u30E1\u30A4\u30F3\u306E\u30AA\u30FC\u30AC\u30CB\u30C3\u30AF\u30AD\u30FC\u30EF\u30FC\u30C9
614
+ - \`domain_adwords\` \u2014 \u30C9\u30E1\u30A4\u30F3\u306E\u6709\u6599\u30AD\u30FC\u30EF\u30FC\u30C9
615
+ - \`domain_organic_organic\` / \`domain_adwords_adwords\` \u2014 \u30AA\u30FC\u30AC\u30CB\u30C3\u30AF\uFF0F\u6709\u6599\u306E\u7AF6\u5408
616
+ - \`phrase_this\` \u2014 \u30AD\u30FC\u30EF\u30FC\u30C9\u6982\u8981\uFF08\u691C\u7D22\u30DC\u30EA\u30E5\u30FC\u30E0\u3001CPC\u3001\u7AF6\u5408\u5EA6\u3001\u30C8\u30EC\u30F3\u30C9\uFF09
617
+ - \`phrase_related\` \u2014 \u95A2\u9023\u30AD\u30FC\u30EF\u30FC\u30C9
618
+ - \`phrase_fullsearch\` \u2014 \u30D5\u30EB\u30C6\u30AD\u30B9\u30C8\u30AD\u30FC\u30EF\u30FC\u30C9\u30EA\u30B5\u30FC\u30C1
619
+ - \`phrase_questions\` \u2014 \u8CEA\u554F\u5F62\u5F0F\u30AD\u30FC\u30EF\u30FC\u30C9
620
+ - \`phrase_kdi\` \u2014 \u30AD\u30FC\u30EF\u30FC\u30C9\u96E3\u6613\u5EA6\uFF08KDI\uFF09
621
+ - \`backlinks_overview\` \u2014 \u30D0\u30C3\u30AF\u30EA\u30F3\u30AF\u6982\u8981
622
+ - \`backlinks\` \u2014 \u30D0\u30C3\u30AF\u30EA\u30F3\u30AF\u4E00\u89A7
623
+ - \`backlinks_refdomains\` \u2014 \u53C2\u7167\u30C9\u30E1\u30A4\u30F3
624
+ - \`url_organic\` / \`url_adwords\` \u2014 \u7279\u5B9AURL\u3067\u30E9\u30F3\u30AF\u30A4\u30F3\u3057\u3066\u3044\u308B\u30AD\u30FC\u30EF\u30FC\u30C9
625
+
626
+ API \u30E6\u30CB\u30C3\u30C8\u6B8B\u91CF\u306E\u78BA\u8A8D\uFF08\u7121\u6599\u3001\u30E6\u30CB\u30C3\u30C8\u3092\u6D88\u8CBB\u3057\u306A\u3044\uFF09\u306F request \u30C4\u30FC\u30EB\u306B\u7D76\u5BFEURL\u3092\u6E21\u3059: \`path: "https://www.semrush.com/users/countapiunits.html"\`\u3001queryParams \u306A\u3057\u3001\`responseFormat: "text"\`\u3002\u30EC\u30B9\u30DD\u30F3\u30B9\u672C\u6587\u306F\u6570\u5024\u306E\u307F\u3002
627
+
628
+ #### \u4E3B\u8981\u306A\u30AF\u30A8\u30EA\u30D1\u30E9\u30E1\u30FC\u30BF
629
+ - \`type\` \u2014 \u30EC\u30DD\u30FC\u30C8\u7A2E\u5225\uFF08Standard API \u3067\u306F\u5FC5\u9808\uFF09
630
+ - \`domain\` / \`phrase\` / \`url\` \u2014 \u30AF\u30A8\u30EA\u5BFE\u8C61\u306E\u30A8\u30F3\u30C6\u30A3\u30C6\u30A3
631
+ - \`database\` \u2014 \u5730\u57DF\u5225\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\uFF08\`us\`, \`uk\`, \`de\`, \`fr\`, \`jp\`, \`br\` \u306A\u3069\uFF09\u3002\u591A\u304F\u306E\u30EC\u30DD\u30FC\u30C8\u3067\u5FC5\u9808
632
+ - \`display_limit\` \u2014 \u30DA\u30FC\u30B8\u30B5\u30A4\u30BA\uFF08\u30C7\u30D5\u30A9\u30EB\u30C810000\u3001\u30EC\u30DD\u30FC\u30C8\u306B\u3088\u3063\u3066\u306F\u6700\u5927100000\uFF09
633
+ - \`display_offset\` \u2014 \u30DA\u30FC\u30B8\u30CD\u30FC\u30B7\u30E7\u30F3\u30AA\u30D5\u30BB\u30C3\u30C8
634
+ - \`display_date\` \u2014 \u5C65\u6B74\u306E\u65E5\u4ED8\u3002\`YYYYMM15\` \u5F62\u5F0F\uFF08\u5FC5\u305A\u6708\u306E15\u65E5\uFF09
635
+ - \`export_columns\` \u2014 \u8FD4\u5374\u30AB\u30E9\u30E0\u3092\u30AB\u30F3\u30DE\u533A\u5207\u308A\u3067\u6307\u5B9A\uFF08\u4F8B: \`Ph,Po,Nq,Cp\`\uFF09
636
+ - \`display_sort\` \u2014 \u30BD\u30FC\u30C8\u6307\u5B9A\uFF08\u4F8B: \`tr_desc\`\uFF09
637
+ - \`display_filter\` \u2014 \u30D5\u30A3\u30EB\u30BF\u6307\u5B9A\uFF08\u4F8B: \`+|Ph|Co|keyword\`\uFF09
638
+
639
+ #### \u30D2\u30F3\u30C8
640
+ - Standard Analytics \u306E\u5404\u30EC\u30DD\u30FC\u30C8\u306F API \u30E6\u30CB\u30C3\u30C8\u3092\u6D88\u8CBB\u3059\u308B\u3002\u30AF\u30A9\u30FC\u30BF\u304C\u7591\u308F\u3057\u3044\u5834\u5408\u306F \`https://www.semrush.com/users/countapiunits.html?key=...\` \u3067\u6B8B\u91CF\u3092\u5148\u306B\u78BA\u8A8D\u3059\u308B\uFF08\u7121\u6599\uFF09
641
+ - CSV \u306E\u30BB\u30D1\u30EC\u30FC\u30BF\u306F \`;\`\uFF08\u30BB\u30DF\u30B3\u30ED\u30F3\uFF09\u3067\u3042\u308A \`,\` \u3067\u306F\u306A\u3044\u3002\u30BB\u30EB\u5185\u306B\u30AB\u30F3\u30DE\u304C\u542B\u307E\u308C\u308B\u3053\u3068\u304C\u3042\u308B
642
+ - HTTP 200 \u3067\u3082\u672C\u6587\u304C \`ERROR\` \u3067\u59CB\u307E\u308B\u5834\u5408\u306F API\u30A8\u30E9\u30FC\uFF08\u8A8D\u8A3C\u3001\u30D1\u30E9\u30E1\u30FC\u30BF\u3001\u30AF\u30A9\u30FC\u30BF\uFF09
643
+ - Trends API \u306F\u5225\u9014 Trends \u30B5\u30D6\u30B9\u30AF\u30EA\u30D7\u30B7\u30E7\u30F3\u304C\u5FC5\u8981\u3002\u672A\u5951\u7D04\u3060\u3068\u8A8D\u53EF\u30A8\u30E9\u30FC\u306B\u306A\u308B
644
+ - \u5C65\u6B74\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u306E\u65E5\u4ED8\u306F\u5FC5\u305A\u6708\u306E15\u65E5\uFF08\`YYYYMM15\`\uFF09`
645
+ },
646
+ tools,
647
+ async checkConnection(params) {
648
+ try {
649
+ const apiKey = params["api-key"];
650
+ if (!apiKey) {
651
+ return {
652
+ success: false,
653
+ error: "API Key is not configured"
654
+ };
655
+ }
656
+ const url = new URL("https://www.semrush.com/users/countapiunits.html");
657
+ url.searchParams.set("key", apiKey);
658
+ const res = await fetch(url.toString(), { method: "GET" });
659
+ const text = await res.text();
660
+ if (text.startsWith("ERROR ")) {
661
+ return { success: false, error: text.trim() };
662
+ }
663
+ if (!res.ok) {
664
+ return {
665
+ success: false,
666
+ error: `Semrush API failed: HTTP ${res.status} ${text || res.statusText}`
667
+ };
668
+ }
669
+ return { success: true };
670
+ } catch (error) {
671
+ return {
672
+ success: false,
673
+ error: error instanceof Error ? error.message : String(error)
674
+ };
675
+ }
676
+ }
677
+ });
678
+
679
+ // src/connectors/create-connector-sdk.ts
680
+ import { readFileSync } from "fs";
681
+ import path from "path";
682
+
683
+ // src/connector-client/env.ts
684
+ function resolveEnvVar(entry, key, connectionId) {
685
+ const envVarName = entry.envVars[key];
686
+ if (!envVarName) {
687
+ throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
688
+ }
689
+ const value = process.env[envVarName];
690
+ if (!value) {
691
+ throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
692
+ }
693
+ return value;
694
+ }
695
+ function resolveEnvVarOptional(entry, key) {
696
+ const envVarName = entry.envVars[key];
697
+ if (!envVarName) return void 0;
698
+ return process.env[envVarName] || void 0;
699
+ }
700
+
701
+ // src/connector-client/proxy-fetch.ts
702
+ import { getContext } from "hono/context-storage";
703
+ import { getCookie } from "hono/cookie";
704
+ var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
705
+ function normalizeHeaders(input) {
706
+ const out = {};
707
+ if (!input) return out;
708
+ new Headers(input).forEach((value, key) => {
709
+ out[key] = value;
710
+ });
711
+ return out;
712
+ }
713
+ function createSandboxProxyFetch(connectionId) {
714
+ return async (input, init) => {
715
+ const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
716
+ const sandboxId = process.env.INTERNAL_SQUADBASE_SANDBOX_ID;
717
+ if (!token || !sandboxId) {
718
+ throw new Error(
719
+ "Connection proxy is not configured. Please check your deployment settings."
720
+ );
721
+ }
722
+ const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
723
+ const originalMethod = init?.method ?? "GET";
724
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
725
+ const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
726
+ const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
727
+ return fetch(proxyUrl, {
728
+ method: "POST",
729
+ headers: {
730
+ "Content-Type": "application/json",
731
+ Authorization: `Bearer ${token}`
732
+ },
733
+ body: JSON.stringify({
734
+ url: originalUrl,
735
+ method: originalMethod,
736
+ headers: normalizeHeaders(init?.headers),
737
+ body: originalBody
738
+ })
739
+ });
740
+ };
741
+ }
742
+ function createDeployedAppProxyFetch(connectionId) {
743
+ const projectId = process.env["SQUADBASE_PROJECT_ID"];
744
+ if (!projectId) {
745
+ throw new Error(
746
+ "Connection proxy is not configured. Please check your deployment settings."
747
+ );
748
+ }
749
+ const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
750
+ const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
751
+ return async (input, init) => {
752
+ const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
753
+ const originalMethod = init?.method ?? "GET";
754
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
755
+ const c = getContext();
756
+ const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
757
+ if (!appSession) {
758
+ throw new Error(
759
+ "No authentication method available for connection proxy."
760
+ );
761
+ }
762
+ return fetch(proxyUrl, {
763
+ method: "POST",
764
+ headers: {
765
+ "Content-Type": "application/json",
766
+ Authorization: `Bearer ${appSession}`
767
+ },
768
+ body: JSON.stringify({
769
+ url: originalUrl,
770
+ method: originalMethod,
771
+ headers: normalizeHeaders(init?.headers),
772
+ body: originalBody
773
+ })
774
+ });
775
+ };
776
+ }
777
+ function createProxyFetch(connectionId) {
778
+ if (process.env.INTERNAL_SQUADBASE_SANDBOX_ID) {
779
+ return createSandboxProxyFetch(connectionId);
780
+ }
781
+ return createDeployedAppProxyFetch(connectionId);
782
+ }
783
+
784
+ // src/connectors/create-connector-sdk.ts
785
+ function loadConnectionsSync() {
786
+ const filePath = process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
787
+ try {
788
+ const raw = readFileSync(filePath, "utf-8");
789
+ return JSON.parse(raw);
790
+ } catch {
791
+ return {};
792
+ }
793
+ }
794
+ function createConnectorSdk(plugin, createClient2) {
795
+ return (connectionId) => {
796
+ const connections = loadConnectionsSync();
797
+ const entry = connections[connectionId];
798
+ if (!entry) {
799
+ throw new Error(
800
+ `Connection "${connectionId}" not found in .squadbase/connections.json`
801
+ );
802
+ }
803
+ if (entry.connector.slug !== plugin.slug) {
804
+ throw new Error(
805
+ `Connection "${connectionId}" is not a ${plugin.slug} connection (got "${entry.connector.slug}")`
806
+ );
807
+ }
808
+ const params = {};
809
+ for (const param of Object.values(plugin.parameters)) {
810
+ if (param.required) {
811
+ params[param.slug] = resolveEnvVar(entry, param.slug, connectionId);
812
+ } else {
813
+ const val = resolveEnvVarOptional(entry, param.slug);
814
+ if (val !== void 0) params[param.slug] = val;
815
+ }
816
+ }
817
+ return createClient2(params, createProxyFetch(connectionId));
818
+ };
819
+ }
820
+
821
+ // src/connectors/entries/semrush.ts
822
+ var connection = createConnectorSdk(semrushConnector, createClient);
823
+ export {
824
+ connection
825
+ };