@squadbase/vite-server 0.1.5-dev.0 → 0.1.6

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,688 @@
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/linear/parameters.ts
46
+ var parameters = {
47
+ apiKey: new ParameterDefinition({
48
+ slug: "api-key",
49
+ name: "Linear API Key",
50
+ description: "Personal API key generated from Linear Settings > Security & access > API.",
51
+ envVarBaseKey: "LINEAR_API_KEY",
52
+ type: "text",
53
+ secret: true,
54
+ required: true
55
+ })
56
+ };
57
+
58
+ // ../connectors/src/connectors/linear/sdk/index.ts
59
+ var BASE_URL = "https://api.linear.app/graphql";
60
+ function createClient(params) {
61
+ const apiKey = params[parameters.apiKey.slug];
62
+ if (!apiKey) {
63
+ throw new Error(
64
+ `linear: missing required parameter: ${parameters.apiKey.slug}`
65
+ );
66
+ }
67
+ function authHeaders(extra) {
68
+ const headers = new Headers(extra);
69
+ headers.set("Authorization", apiKey);
70
+ headers.set("Content-Type", "application/json");
71
+ return headers;
72
+ }
73
+ async function gql(query, variables) {
74
+ const body = { query };
75
+ if (variables) body.variables = variables;
76
+ const res = await fetch(BASE_URL, {
77
+ method: "POST",
78
+ headers: authHeaders(),
79
+ body: JSON.stringify(body)
80
+ });
81
+ const json = await res.json();
82
+ if (json.errors && json.errors.length > 0) {
83
+ throw new Error(
84
+ `linear graphql: ${json.errors.map((e) => e.message).join("; ")}`
85
+ );
86
+ }
87
+ if (!json.data) {
88
+ throw new Error("linear graphql: no data in response");
89
+ }
90
+ return json.data;
91
+ }
92
+ return {
93
+ graphql: gql,
94
+ request(init) {
95
+ const headers = new Headers(init?.headers);
96
+ headers.set("Authorization", apiKey);
97
+ headers.set("Content-Type", "application/json");
98
+ return fetch(BASE_URL, { ...init, headers });
99
+ },
100
+ async listTeams(options) {
101
+ const vars = {};
102
+ if (options?.first) vars.first = options.first;
103
+ if (options?.after) vars.after = options.after;
104
+ if (options?.includeArchived) vars.includeArchived = options.includeArchived;
105
+ const args = Object.entries(vars).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ");
106
+ const argsStr = args ? `(${args})` : "";
107
+ const result = await gql(`{ teams${argsStr} { nodes { id name key description } pageInfo { hasNextPage endCursor } } }`);
108
+ return result.teams;
109
+ },
110
+ async listIssues(options) {
111
+ const vars = {};
112
+ if (options?.first) vars.first = options.first;
113
+ if (options?.after) vars.after = options.after;
114
+ if (options?.includeArchived) vars.includeArchived = options.includeArchived;
115
+ if (options?.orderBy) vars.orderBy = options.orderBy;
116
+ let filterStr = "";
117
+ if (options?.teamId) {
118
+ filterStr = `, filter: { team: { id: { eq: "${options.teamId}" } } }`;
119
+ }
120
+ const args = Object.entries(vars).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ");
121
+ const argsStr = args || filterStr ? `(${args}${filterStr})` : "";
122
+ const result = await gql(`{ issues${argsStr} { nodes { id identifier title description state { id name } assignee { id name } priority priorityLabel labels { nodes { id name } } project { id name } cycle { id name } createdAt updatedAt } pageInfo { hasNextPage endCursor } } }`);
123
+ return result.issues;
124
+ },
125
+ async getIssue(id) {
126
+ const result = await gql(
127
+ `{ issue(id: "${id}") { id identifier title description state { id name } assignee { id name } priority priorityLabel labels { nodes { id name } } project { id name } cycle { id name } team { id name } createdAt updatedAt completedAt } }`
128
+ );
129
+ return result.issue;
130
+ },
131
+ async createIssue(input) {
132
+ const result = await gql(
133
+ `mutation($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id identifier title state { id name } } } }`,
134
+ { input }
135
+ );
136
+ return result.issueCreate;
137
+ },
138
+ async listProjects(options) {
139
+ const vars = {};
140
+ if (options?.first) vars.first = options.first;
141
+ if (options?.after) vars.after = options.after;
142
+ if (options?.includeArchived) vars.includeArchived = options.includeArchived;
143
+ const args = Object.entries(vars).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ");
144
+ const argsStr = args ? `(${args})` : "";
145
+ const result = await gql(`{ projects${argsStr} { nodes { id name description state startDate targetDate } pageInfo { hasNextPage endCursor } } }`);
146
+ return result.projects;
147
+ }
148
+ };
149
+ }
150
+
151
+ // ../connectors/src/connector-onboarding.ts
152
+ var ConnectorOnboarding = class {
153
+ /** Phase 1: Connection setup instructions (optional — some connectors don't need this) */
154
+ connectionSetupInstructions;
155
+ /** Phase 2: Data overview instructions */
156
+ dataOverviewInstructions;
157
+ constructor(config) {
158
+ this.connectionSetupInstructions = config.connectionSetupInstructions;
159
+ this.dataOverviewInstructions = config.dataOverviewInstructions;
160
+ }
161
+ getConnectionSetupPrompt(language) {
162
+ return this.connectionSetupInstructions?.[language] ?? null;
163
+ }
164
+ getDataOverviewInstructions(language) {
165
+ return this.dataOverviewInstructions[language];
166
+ }
167
+ };
168
+
169
+ // ../connectors/src/connector-tool.ts
170
+ var ConnectorTool = class {
171
+ name;
172
+ description;
173
+ inputSchema;
174
+ outputSchema;
175
+ _execute;
176
+ constructor(config) {
177
+ this.name = config.name;
178
+ this.description = config.description;
179
+ this.inputSchema = config.inputSchema;
180
+ this.outputSchema = config.outputSchema;
181
+ this._execute = config.execute;
182
+ }
183
+ createTool(connections, config) {
184
+ return {
185
+ description: this.description,
186
+ inputSchema: this.inputSchema,
187
+ outputSchema: this.outputSchema,
188
+ execute: (input) => this._execute(input, connections, config)
189
+ };
190
+ }
191
+ };
192
+
193
+ // ../connectors/src/connector-plugin.ts
194
+ var ConnectorPlugin = class _ConnectorPlugin {
195
+ slug;
196
+ authType;
197
+ name;
198
+ description;
199
+ iconUrl;
200
+ parameters;
201
+ releaseFlag;
202
+ proxyPolicy;
203
+ experimentalAttributes;
204
+ onboarding;
205
+ systemPrompt;
206
+ tools;
207
+ query;
208
+ checkConnection;
209
+ constructor(config) {
210
+ this.slug = config.slug;
211
+ this.authType = config.authType;
212
+ this.name = config.name;
213
+ this.description = config.description;
214
+ this.iconUrl = config.iconUrl;
215
+ this.parameters = config.parameters;
216
+ this.releaseFlag = config.releaseFlag;
217
+ this.proxyPolicy = config.proxyPolicy;
218
+ this.experimentalAttributes = config.experimentalAttributes;
219
+ this.onboarding = config.onboarding;
220
+ this.systemPrompt = config.systemPrompt;
221
+ this.tools = config.tools;
222
+ this.query = config.query;
223
+ this.checkConnection = config.checkConnection;
224
+ }
225
+ get connectorKey() {
226
+ return _ConnectorPlugin.deriveKey(this.slug, this.authType);
227
+ }
228
+ /**
229
+ * Create tools for connections that belong to this connector.
230
+ * Filters connections by connectorKey internally.
231
+ * Returns tools keyed as `${connectorKey}_${toolName}`.
232
+ */
233
+ createTools(connections, config, opts) {
234
+ const myConnections = connections.filter(
235
+ (c) => _ConnectorPlugin.deriveKey(c.connector.slug, c.connector.authType) === this.connectorKey
236
+ );
237
+ const result = {};
238
+ for (const t of Object.values(this.tools)) {
239
+ const tool = t.createTool(myConnections, config);
240
+ const originalToModelOutput = tool.toModelOutput;
241
+ result[`${this.connectorKey}_${t.name}`] = {
242
+ ...tool,
243
+ toModelOutput: async (options) => {
244
+ if (!originalToModelOutput) {
245
+ return opts.truncateOutput(options.output);
246
+ }
247
+ const modelOutput = await originalToModelOutput(options);
248
+ if (modelOutput.type === "text" || modelOutput.type === "json") {
249
+ return opts.truncateOutput(modelOutput.value);
250
+ }
251
+ return modelOutput;
252
+ }
253
+ };
254
+ }
255
+ return result;
256
+ }
257
+ static deriveKey(slug, authType) {
258
+ if (authType) return `${slug}-${authType}`;
259
+ const LEGACY_NULL_AUTH_TYPE_MAP = {
260
+ // user-password
261
+ "postgresql": "user-password",
262
+ "mysql": "user-password",
263
+ "clickhouse": "user-password",
264
+ "kintone": "user-password",
265
+ "squadbase-db": "user-password",
266
+ // service-account
267
+ "snowflake": "service-account",
268
+ "bigquery": "service-account",
269
+ "google-analytics": "service-account",
270
+ "google-calendar": "service-account",
271
+ "aws-athena": "service-account",
272
+ "redshift": "service-account",
273
+ // api-key
274
+ "databricks": "api-key",
275
+ "dbt": "api-key",
276
+ "airtable": "api-key",
277
+ "openai": "api-key",
278
+ "gemini": "api-key",
279
+ "anthropic": "api-key",
280
+ "wix-store": "api-key"
281
+ };
282
+ const fallbackAuthType = LEGACY_NULL_AUTH_TYPE_MAP[slug];
283
+ if (fallbackAuthType) return `${slug}-${fallbackAuthType}`;
284
+ return slug;
285
+ }
286
+ };
287
+
288
+ // ../connectors/src/auth-types.ts
289
+ var AUTH_TYPES = {
290
+ OAUTH: "oauth",
291
+ API_KEY: "api-key",
292
+ JWT: "jwt",
293
+ SERVICE_ACCOUNT: "service-account",
294
+ PAT: "pat",
295
+ USER_PASSWORD: "user-password"
296
+ };
297
+
298
+ // ../connectors/src/connectors/linear/setup.ts
299
+ var linearOnboarding = new ConnectorOnboarding({
300
+ dataOverviewInstructions: {
301
+ en: `1. Call linear_request with query \`{ teams { nodes { id name } } }\` to list all teams
302
+ 2. Call linear_request with query \`{ workflowStates { nodes { id name type } } }\` to list workflow states
303
+ 3. Call linear_request with query \`{ issues(first: 5, orderBy: updatedAt) { nodes { id identifier title state { name } assignee { name } priority } } }\` to sample recent issues
304
+ 4. Call linear_request with query \`{ projects(first: 5) { nodes { id name state } } }\` to list projects
305
+ 5. Explore other resources (cycles, labels, users) as needed`,
306
+ ja: `1. linear_request \u3067\u30AF\u30A8\u30EA \`{ teams { nodes { id name } } }\` \u3092\u547C\u3073\u51FA\u3057\u3001\u30C1\u30FC\u30E0\u4E00\u89A7\u3092\u53D6\u5F97
307
+ 2. linear_request \u3067\u30AF\u30A8\u30EA \`{ workflowStates { nodes { id name type } } }\` \u3092\u547C\u3073\u51FA\u3057\u3001\u30EF\u30FC\u30AF\u30D5\u30ED\u30FC\u72B6\u614B\u4E00\u89A7\u3092\u53D6\u5F97
308
+ 3. linear_request \u3067\u30AF\u30A8\u30EA \`{ issues(first: 5, orderBy: updatedAt) { nodes { id identifier title state { name } assignee { name } priority } } }\` \u3092\u547C\u3073\u51FA\u3057\u3001\u6700\u8FD1\u306EIssue\u3092\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0
309
+ 4. linear_request \u3067\u30AF\u30A8\u30EA \`{ projects(first: 5) { nodes { id name state } } }\` \u3092\u547C\u3073\u51FA\u3057\u3001\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u4E00\u89A7\u3092\u53D6\u5F97
310
+ 5. \u5FC5\u8981\u306B\u5FDC\u3058\u3066\u4ED6\u306E\u30EA\u30BD\u30FC\u30B9\uFF08cycles\u3001labels\u3001users\uFF09\u3092\u63A2\u7D22`
311
+ }
312
+ });
313
+
314
+ // ../connectors/src/connectors/linear/tools/request.ts
315
+ import { z } from "zod";
316
+ var BASE_URL2 = "https://api.linear.app/graphql";
317
+ var REQUEST_TIMEOUT_MS = 6e4;
318
+ var inputSchema = z.object({
319
+ toolUseIntent: z.string().optional().describe(
320
+ "Brief description of what you intend to accomplish with this tool call"
321
+ ),
322
+ connectionId: z.string().describe("ID of the Linear connection to use"),
323
+ query: z.string().describe(
324
+ `GraphQL query or mutation string. Use standard GraphQL syntax. Example: '{ issues(first: 10) { nodes { id title state { name } } } }' for queries, or 'mutation { issueCreate(input: { title: "Bug fix", teamId: "TEAM_ID" }) { success issue { id title } } }' for mutations.`
325
+ ),
326
+ variables: z.record(z.string(), z.unknown()).optional().describe(
327
+ 'Optional GraphQL variables object. Use with parameterized queries, e.g. { "teamId": "abc-123", "first": 10 }'
328
+ )
329
+ });
330
+ var outputSchema = z.discriminatedUnion("success", [
331
+ z.object({
332
+ success: z.literal(true),
333
+ data: z.record(z.string(), z.unknown())
334
+ }),
335
+ z.object({
336
+ success: z.literal(false),
337
+ error: z.string()
338
+ })
339
+ ]);
340
+ var requestTool = new ConnectorTool({
341
+ name: "request",
342
+ description: `Send authenticated GraphQL queries and mutations to the Linear API (https://api.linear.app/graphql).
343
+ Use this tool for all Linear interactions: querying issues, projects, teams, cycles, users, labels, workflow states, and performing mutations like creating/updating issues and comments.
344
+ Linear's API is GraphQL-only \u2014 there is no REST API. All requests are POST with a JSON body containing "query" and optional "variables".
345
+ Archived resources are hidden by default; pass includeArchived: true in query arguments to include them. Pagination uses Relay-style cursors with first/after and last/before arguments.`,
346
+ inputSchema,
347
+ outputSchema,
348
+ async execute({ connectionId, query, variables }, connections) {
349
+ const connection2 = connections.find((c) => c.id === connectionId);
350
+ if (!connection2) {
351
+ return {
352
+ success: false,
353
+ error: `Connection ${connectionId} not found`
354
+ };
355
+ }
356
+ console.log(
357
+ `[connector-request] linear/${connection2.name}: GraphQL request`
358
+ );
359
+ try {
360
+ const apiKey = parameters.apiKey.getValue(connection2);
361
+ const controller = new AbortController();
362
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
363
+ try {
364
+ const body = { query };
365
+ if (variables) body.variables = variables;
366
+ const response = await fetch(BASE_URL2, {
367
+ method: "POST",
368
+ headers: {
369
+ Authorization: apiKey,
370
+ "Content-Type": "application/json"
371
+ },
372
+ body: JSON.stringify(body),
373
+ signal: controller.signal
374
+ });
375
+ const result = await response.json();
376
+ if (!response.ok) {
377
+ const errorMessage = typeof result?.message === "string" ? result.message : typeof result?.error === "string" ? result.error : `HTTP ${response.status} ${response.statusText}`;
378
+ return { success: false, error: errorMessage };
379
+ }
380
+ if (result.errors && Array.isArray(result.errors) && result.errors.length > 0) {
381
+ const messages = result.errors.map((e) => e.message).join("; ");
382
+ if (!result.data) {
383
+ return { success: false, error: messages };
384
+ }
385
+ return {
386
+ success: true,
387
+ data: { ...result, _warnings: messages }
388
+ };
389
+ }
390
+ return {
391
+ success: true,
392
+ data: result.data ?? result
393
+ };
394
+ } finally {
395
+ clearTimeout(timeout);
396
+ }
397
+ } catch (err) {
398
+ const msg = err instanceof Error ? err.message : String(err);
399
+ return { success: false, error: msg };
400
+ }
401
+ }
402
+ });
403
+
404
+ // ../connectors/src/connectors/linear/index.ts
405
+ var tools = { request: requestTool };
406
+ var linearConnector = new ConnectorPlugin({
407
+ slug: "linear",
408
+ authType: AUTH_TYPES.API_KEY,
409
+ name: "Linear",
410
+ description: "Connect to Linear for project management data \u2014 issues, projects, teams, cycles, and more.",
411
+ iconUrl: "https://images.ctfassets.net/9ncizv60xc5y/6oR77h6TeniXGdmnp2P2LX/a2ac8630ae52d164363adb0c695d9f0b/linear.webp",
412
+ parameters,
413
+ releaseFlag: { dev1: true, dev2: true, prod: true },
414
+ onboarding: linearOnboarding,
415
+ systemPrompt: {
416
+ en: `### Tools
417
+
418
+ - \`linear_request\`: The only way to call the Linear GraphQL API. Use it to query and mutate all Linear resources: issues, projects, teams, cycles, users, labels, workflow states, and comments. Authentication is configured automatically. Linear's API is GraphQL-only \u2014 send a query string and optional variables. Pagination uses Relay-style cursors with \`first\`/\`after\` and \`last\`/\`before\` arguments. Archived resources are hidden by default; pass \`includeArchived: true\` to include them.
419
+
420
+ ### Business Logic
421
+
422
+ The business logic type for this connector is "typescript". Use the connector SDK in your handler. Do NOT read credentials from environment variables.
423
+
424
+ SDK methods (client created via \`connection(connectionId)\`):
425
+ - \`client.graphql(query, variables?)\` \u2014 send any GraphQL query/mutation and get typed data back
426
+ - \`client.request(init?)\` \u2014 low-level authenticated fetch to the GraphQL endpoint
427
+ - \`client.listTeams(options?)\` \u2014 list teams in the workspace
428
+ - \`client.listIssues(options?)\` \u2014 list issues with optional teamId filter, pagination, ordering
429
+ - \`client.getIssue(id)\` \u2014 fetch a single issue by ID or identifier (e.g. "TEAM-123")
430
+ - \`client.createIssue(input)\` \u2014 create a new issue (requires title and teamId)
431
+ - \`client.listProjects(options?)\` \u2014 list projects with pagination
432
+
433
+ \`\`\`ts
434
+ import type { Context } from "hono";
435
+ import { connection } from "@squadbase/vite-server/connectors/linear";
436
+
437
+ const linear = connection("<connectionId>");
438
+
439
+ export default async function handler(c: Context) {
440
+ const { teamId, first = 20 } = await c.req.json<{
441
+ teamId?: string;
442
+ first?: number;
443
+ }>();
444
+
445
+ const { nodes, pageInfo } = await linear.listIssues({ teamId, first });
446
+
447
+ return c.json({ issues: nodes, pageInfo });
448
+ }
449
+ \`\`\`
450
+
451
+ ### Linear GraphQL API Reference
452
+
453
+ - Endpoint: \`https://api.linear.app/graphql\`
454
+ - Authentication: API key passed as \`Authorization: <API_KEY>\` header (handled automatically)
455
+ - All requests are POST with JSON body: \`{ "query": "...", "variables": {...} }\`
456
+ - Rate limit: 1,500 requests per hour with authentication
457
+ - Pagination: Relay-style cursor-based \u2014 use \`first\`/\`after\` (forward) or \`last\`/\`before\` (backward); responses include \`pageInfo { hasNextPage endCursor }\`
458
+ - Archived resources: hidden by default; pass \`includeArchived: true\` to include them
459
+ - Issue creation: if no \`stateId\` is given, the issue is assigned to the team's first workflow state (or Triage if enabled)
460
+ - GraphQL responses may partially succeed (HTTP 200 with both \`data\` and \`errors\`)
461
+
462
+ #### Common Queries
463
+ - \`{ viewer { id name email } }\` \u2014 current authenticated user
464
+ - \`{ teams { nodes { id name key } } }\` \u2014 list all teams
465
+ - \`{ issues(first: 10, orderBy: updatedAt) { nodes { id identifier title state { name } assignee { name } priority } pageInfo { hasNextPage endCursor } } }\` \u2014 list recent issues
466
+ - \`{ issue(id: "TEAM-123") { id identifier title description state { name } assignee { name } } }\` \u2014 get a single issue by identifier
467
+ - \`{ projects(first: 10) { nodes { id name state startDate targetDate } } }\` \u2014 list projects
468
+ - \`{ cycles(first: 10) { nodes { id name number startsAt endsAt } } }\` \u2014 list cycles
469
+ - \`{ workflowStates { nodes { id name type team { name } } } }\` \u2014 list workflow states
470
+ - \`{ users { nodes { id name email } } }\` \u2014 list users
471
+ - \`{ issueLabels { nodes { id name color } } }\` \u2014 list labels
472
+
473
+ #### Common Mutations
474
+ - \`mutation { issueCreate(input: { title: "...", teamId: "..." }) { success issue { id identifier title } } }\` \u2014 create issue
475
+ - \`mutation { issueUpdate(id: "...", input: { stateId: "..." }) { success issue { id title state { name } } } }\` \u2014 update issue
476
+ - \`mutation { commentCreate(input: { issueId: "...", body: "..." }) { success comment { id body } } }\` \u2014 add comment`,
477
+ ja: `### \u30C4\u30FC\u30EB
478
+
479
+ - \`linear_request\`: Linear GraphQL API\u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002Issue\u3001\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3001\u30C1\u30FC\u30E0\u3001\u30B5\u30A4\u30AF\u30EB\u3001\u30E6\u30FC\u30B6\u30FC\u3001\u30E9\u30D9\u30EB\u3001\u30EF\u30FC\u30AF\u30D5\u30ED\u30FC\u72B6\u614B\u3001\u30B3\u30E1\u30F3\u30C8\u306A\u3069\u3059\u3079\u3066\u306ELinear\u30EA\u30BD\u30FC\u30B9\u306E\u30AF\u30A8\u30EA\u3068\u30DF\u30E5\u30FC\u30C6\u30FC\u30B7\u30E7\u30F3\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u8A8D\u8A3C\u306F\u81EA\u52D5\u7684\u306B\u8A2D\u5B9A\u3055\u308C\u307E\u3059\u3002Linear\u306EAPI\u306FGraphQL\u306E\u307F\u3067\u3059\u3002\u30AF\u30A8\u30EA\u6587\u5B57\u5217\u3068\u30AA\u30D7\u30B7\u30E7\u30F3\u306E\u5909\u6570\u3092\u9001\u4FE1\u3057\u307E\u3059\u3002\u30DA\u30FC\u30B8\u30CD\u30FC\u30B7\u30E7\u30F3\u306FRelay\u5F62\u5F0F\u306E\u30AB\u30FC\u30BD\u30EB\u30D9\u30FC\u30B9\u3067\u3001\`first\`/\`after\`\u304A\u3088\u3073\`last\`/\`before\`\u5F15\u6570\u3092\u4F7F\u7528\u3057\u307E\u3059\u3002\u30A2\u30FC\u30AB\u30A4\u30D6\u3055\u308C\u305F\u30EA\u30BD\u30FC\u30B9\u306F\u30C7\u30D5\u30A9\u30EB\u30C8\u3067\u975E\u8868\u793A\u3067\u3059\u3002\`includeArchived: true\`\u3092\u6E21\u3059\u3068\u542B\u3081\u3089\u308C\u307E\u3059\u3002
480
+
481
+ ### Business Logic
482
+
483
+ \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
484
+
485
+ SDK\u30E1\u30BD\u30C3\u30C9 (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8):
486
+ - \`client.graphql(query, variables?)\` \u2014 \u4EFB\u610F\u306EGraphQL\u30AF\u30A8\u30EA/\u30DF\u30E5\u30FC\u30C6\u30FC\u30B7\u30E7\u30F3\u3092\u9001\u4FE1\u3057\u578B\u4ED8\u304D\u30C7\u30FC\u30BF\u3092\u53D6\u5F97
487
+ - \`client.request(init?)\` \u2014 GraphQL\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3078\u306E\u4F4E\u30EC\u30D9\u30EB\u8A8D\u8A3C\u4ED8\u304Dfetch
488
+ - \`client.listTeams(options?)\` \u2014 \u30EF\u30FC\u30AF\u30B9\u30DA\u30FC\u30B9\u306E\u30C1\u30FC\u30E0\u4E00\u89A7\u3092\u53D6\u5F97
489
+ - \`client.listIssues(options?)\` \u2014 teamId\u30D5\u30A3\u30EB\u30BF\u3001\u30DA\u30FC\u30B8\u30CD\u30FC\u30B7\u30E7\u30F3\u3001\u30BD\u30FC\u30C8\u4ED8\u304D\u3067Issue\u4E00\u89A7\u3092\u53D6\u5F97
490
+ - \`client.getIssue(id)\` \u2014 ID\u307E\u305F\u306F\u8B58\u5225\u5B50\uFF08\u4F8B: "TEAM-123"\uFF09\u3067\u5358\u4E00Issue\u3092\u53D6\u5F97
491
+ - \`client.createIssue(input)\` \u2014 \u65B0\u3057\u3044Issue\u3092\u4F5C\u6210\uFF08title\u3068teamId\u304C\u5FC5\u9808\uFF09
492
+ - \`client.listProjects(options?)\` \u2014 \u30DA\u30FC\u30B8\u30CD\u30FC\u30B7\u30E7\u30F3\u4ED8\u304D\u3067\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u4E00\u89A7\u3092\u53D6\u5F97
493
+
494
+ \`\`\`ts
495
+ import type { Context } from "hono";
496
+ import { connection } from "@squadbase/vite-server/connectors/linear";
497
+
498
+ const linear = connection("<connectionId>");
499
+
500
+ export default async function handler(c: Context) {
501
+ const { teamId, first = 20 } = await c.req.json<{
502
+ teamId?: string;
503
+ first?: number;
504
+ }>();
505
+
506
+ const { nodes, pageInfo } = await linear.listIssues({ teamId, first });
507
+
508
+ return c.json({ issues: nodes, pageInfo });
509
+ }
510
+ \`\`\`
511
+
512
+ ### Linear GraphQL API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
513
+
514
+ - \u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8: \`https://api.linear.app/graphql\`
515
+ - \u8A8D\u8A3C: API\u30AD\u30FC\u3092 \`Authorization: <API_KEY>\` \u30D8\u30C3\u30C0\u30FC\u3067\u9001\u4FE1\uFF08\u81EA\u52D5\u8A2D\u5B9A\uFF09
516
+ - \u3059\u3079\u3066\u306E\u30EA\u30AF\u30A8\u30B9\u30C8\u306FPOST\u3067JSON\u30DC\u30C7\u30A3: \`{ "query": "...", "variables": {...} }\`
517
+ - \u30EC\u30FC\u30C8\u5236\u9650: \u8A8D\u8A3C\u6E08\u307F\u30671\u6642\u9593\u3042\u305F\u308A1,500\u30EA\u30AF\u30A8\u30B9\u30C8
518
+ - \u30DA\u30FC\u30B8\u30CD\u30FC\u30B7\u30E7\u30F3: Relay\u5F62\u5F0F\u30AB\u30FC\u30BD\u30EB\u30D9\u30FC\u30B9 \u2014 \`first\`/\`after\`\uFF08\u524D\u65B9\uFF09\u307E\u305F\u306F\`last\`/\`before\`\uFF08\u5F8C\u65B9\uFF09\u3092\u4F7F\u7528\u3002\u30EC\u30B9\u30DD\u30F3\u30B9\u306B\u306F \`pageInfo { hasNextPage endCursor }\` \u304C\u542B\u307E\u308C\u308B
519
+ - \u30A2\u30FC\u30AB\u30A4\u30D6\u3055\u308C\u305F\u30EA\u30BD\u30FC\u30B9: \u30C7\u30D5\u30A9\u30EB\u30C8\u3067\u975E\u8868\u793A\u3002\`includeArchived: true\` \u3067\u542B\u3081\u308B
520
+ - Issue\u4F5C\u6210: \`stateId\` \u3092\u6307\u5B9A\u3057\u306A\u3044\u5834\u5408\u3001\u30C1\u30FC\u30E0\u306E\u6700\u521D\u306E\u30EF\u30FC\u30AF\u30D5\u30ED\u30FC\u72B6\u614B\uFF08\u307E\u305F\u306FTriage\u304C\u6709\u52B9\u306A\u5834\u5408\u306FTriage\uFF09\u306B\u5272\u308A\u5F53\u3066\u3089\u308C\u308B
521
+ - GraphQL\u30EC\u30B9\u30DD\u30F3\u30B9\u306F\u90E8\u5206\u7684\u306B\u6210\u529F\u3059\u308B\u5834\u5408\u304C\u3042\u308B\uFF08HTTP 200\u3067\`data\`\u3068\`errors\`\u306E\u4E21\u65B9\u304C\u542B\u307E\u308C\u308B\uFF09
522
+
523
+ #### \u4E3B\u8981\u30AF\u30A8\u30EA
524
+ - \`{ viewer { id name email } }\` \u2014 \u73FE\u5728\u306E\u8A8D\u8A3C\u30E6\u30FC\u30B6\u30FC
525
+ - \`{ teams { nodes { id name key } } }\` \u2014 \u30C1\u30FC\u30E0\u4E00\u89A7
526
+ - \`{ issues(first: 10, orderBy: updatedAt) { nodes { id identifier title state { name } assignee { name } priority } pageInfo { hasNextPage endCursor } } }\` \u2014 \u6700\u8FD1\u306EIssue\u4E00\u89A7
527
+ - \`{ issue(id: "TEAM-123") { id identifier title description state { name } assignee { name } } }\` \u2014 \u8B58\u5225\u5B50\u3067\u5358\u4E00Issue\u3092\u53D6\u5F97
528
+ - \`{ projects(first: 10) { nodes { id name state startDate targetDate } } }\` \u2014 \u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u4E00\u89A7
529
+ - \`{ cycles(first: 10) { nodes { id name number startsAt endsAt } } }\` \u2014 \u30B5\u30A4\u30AF\u30EB\u4E00\u89A7
530
+ - \`{ workflowStates { nodes { id name type team { name } } } }\` \u2014 \u30EF\u30FC\u30AF\u30D5\u30ED\u30FC\u72B6\u614B\u4E00\u89A7
531
+ - \`{ users { nodes { id name email } } }\` \u2014 \u30E6\u30FC\u30B6\u30FC\u4E00\u89A7
532
+ - \`{ issueLabels { nodes { id name color } } }\` \u2014 \u30E9\u30D9\u30EB\u4E00\u89A7
533
+
534
+ #### \u4E3B\u8981\u30DF\u30E5\u30FC\u30C6\u30FC\u30B7\u30E7\u30F3
535
+ - \`mutation { issueCreate(input: { title: "...", teamId: "..." }) { success issue { id identifier title } } }\` \u2014 Issue\u4F5C\u6210
536
+ - \`mutation { issueUpdate(id: "...", input: { stateId: "..." }) { success issue { id title state { name } } } }\` \u2014 Issue\u66F4\u65B0
537
+ - \`mutation { commentCreate(input: { issueId: "...", body: "..." }) { success comment { id body } } }\` \u2014 \u30B3\u30E1\u30F3\u30C8\u8FFD\u52A0`
538
+ },
539
+ tools
540
+ });
541
+
542
+ // src/connectors/create-connector-sdk.ts
543
+ import { readFileSync } from "fs";
544
+ import path from "path";
545
+
546
+ // src/connector-client/env.ts
547
+ function resolveEnvVar(entry, key, connectionId) {
548
+ const envVarName = entry.envVars[key];
549
+ if (!envVarName) {
550
+ throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
551
+ }
552
+ const value = process.env[envVarName];
553
+ if (!value) {
554
+ throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
555
+ }
556
+ return value;
557
+ }
558
+ function resolveEnvVarOptional(entry, key) {
559
+ const envVarName = entry.envVars[key];
560
+ if (!envVarName) return void 0;
561
+ return process.env[envVarName] || void 0;
562
+ }
563
+
564
+ // src/connector-client/proxy-fetch.ts
565
+ import { getContext } from "hono/context-storage";
566
+ import { getCookie } from "hono/cookie";
567
+ var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
568
+ function normalizeHeaders(input) {
569
+ const out = {};
570
+ if (!input) return out;
571
+ new Headers(input).forEach((value, key) => {
572
+ out[key] = value;
573
+ });
574
+ return out;
575
+ }
576
+ function createSandboxProxyFetch(connectionId) {
577
+ return async (input, init) => {
578
+ const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
579
+ const sandboxId = process.env.INTERNAL_SQUADBASE_SANDBOX_ID;
580
+ if (!token || !sandboxId) {
581
+ throw new Error(
582
+ "Connection proxy is not configured. Please check your deployment settings."
583
+ );
584
+ }
585
+ const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
586
+ const originalMethod = init?.method ?? "GET";
587
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
588
+ const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
589
+ const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
590
+ return fetch(proxyUrl, {
591
+ method: "POST",
592
+ headers: {
593
+ "Content-Type": "application/json",
594
+ Authorization: `Bearer ${token}`
595
+ },
596
+ body: JSON.stringify({
597
+ url: originalUrl,
598
+ method: originalMethod,
599
+ headers: normalizeHeaders(init?.headers),
600
+ body: originalBody
601
+ })
602
+ });
603
+ };
604
+ }
605
+ function createDeployedAppProxyFetch(connectionId) {
606
+ const projectId = process.env["SQUADBASE_PROJECT_ID"];
607
+ if (!projectId) {
608
+ throw new Error(
609
+ "Connection proxy is not configured. Please check your deployment settings."
610
+ );
611
+ }
612
+ const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
613
+ const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
614
+ return async (input, init) => {
615
+ const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
616
+ const originalMethod = init?.method ?? "GET";
617
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
618
+ const c = getContext();
619
+ const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
620
+ if (!appSession) {
621
+ throw new Error(
622
+ "No authentication method available for connection proxy."
623
+ );
624
+ }
625
+ return fetch(proxyUrl, {
626
+ method: "POST",
627
+ headers: {
628
+ "Content-Type": "application/json",
629
+ Authorization: `Bearer ${appSession}`
630
+ },
631
+ body: JSON.stringify({
632
+ url: originalUrl,
633
+ method: originalMethod,
634
+ headers: normalizeHeaders(init?.headers),
635
+ body: originalBody
636
+ })
637
+ });
638
+ };
639
+ }
640
+ function createProxyFetch(connectionId) {
641
+ if (process.env.INTERNAL_SQUADBASE_SANDBOX_ID) {
642
+ return createSandboxProxyFetch(connectionId);
643
+ }
644
+ return createDeployedAppProxyFetch(connectionId);
645
+ }
646
+
647
+ // src/connectors/create-connector-sdk.ts
648
+ function loadConnectionsSync() {
649
+ const filePath = process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
650
+ try {
651
+ const raw = readFileSync(filePath, "utf-8");
652
+ return JSON.parse(raw);
653
+ } catch {
654
+ return {};
655
+ }
656
+ }
657
+ function createConnectorSdk(plugin, createClient2) {
658
+ return (connectionId) => {
659
+ const connections = loadConnectionsSync();
660
+ const entry = connections[connectionId];
661
+ if (!entry) {
662
+ throw new Error(
663
+ `Connection "${connectionId}" not found in .squadbase/connections.json`
664
+ );
665
+ }
666
+ if (entry.connector.slug !== plugin.slug) {
667
+ throw new Error(
668
+ `Connection "${connectionId}" is not a ${plugin.slug} connection (got "${entry.connector.slug}")`
669
+ );
670
+ }
671
+ const params = {};
672
+ for (const param of Object.values(plugin.parameters)) {
673
+ if (param.required) {
674
+ params[param.slug] = resolveEnvVar(entry, param.slug, connectionId);
675
+ } else {
676
+ const val = resolveEnvVarOptional(entry, param.slug);
677
+ if (val !== void 0) params[param.slug] = val;
678
+ }
679
+ }
680
+ return createClient2(params, createProxyFetch(connectionId));
681
+ };
682
+ }
683
+
684
+ // src/connectors/entries/linear.ts
685
+ var connection = createConnectorSdk(linearConnector, createClient);
686
+ export {
687
+ connection
688
+ };