@squadbase/vite-server 0.1.3-dev.11 → 0.1.3-dev.13

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,724 @@
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/sentry/parameters.ts
46
+ var parameters = {
47
+ organizationSlug: new ParameterDefinition({
48
+ slug: "organization-slug",
49
+ name: "Sentry Organization Slug",
50
+ description: "The slug of your Sentry organization (e.g., 'my-org'). Found in your Sentry URL: https://sentry.io/organizations/{slug}/",
51
+ envVarBaseKey: "SENTRY_ORGANIZATION_SLUG",
52
+ type: "text",
53
+ secret: false,
54
+ required: true
55
+ }),
56
+ authToken: new ParameterDefinition({
57
+ slug: "auth-token",
58
+ name: "Sentry Auth Token",
59
+ description: "Sentry API authentication token. Generate one at https://sentry.io/settings/account/api/auth-tokens/ with the required scopes (project:read, org:read, event:read, issue:read, issue:write).",
60
+ envVarBaseKey: "SENTRY_AUTH_TOKEN",
61
+ type: "text",
62
+ secret: true,
63
+ required: true
64
+ })
65
+ };
66
+
67
+ // ../connectors/src/connectors/sentry/sdk/index.ts
68
+ var BASE_URL = "https://sentry.io/api/0";
69
+ function createClient(params) {
70
+ const authToken = params[parameters.authToken.slug];
71
+ const organizationSlug = params[parameters.organizationSlug.slug];
72
+ if (!authToken) {
73
+ throw new Error(
74
+ `sentry: missing required parameter: ${parameters.authToken.slug}`
75
+ );
76
+ }
77
+ if (!organizationSlug) {
78
+ throw new Error(
79
+ `sentry: missing required parameter: ${parameters.organizationSlug.slug}`
80
+ );
81
+ }
82
+ function resolvePath(path2) {
83
+ return path2.replace(/\{organizationSlug\}/g, organizationSlug);
84
+ }
85
+ async function authenticatedFetch(url, init) {
86
+ const headers = new Headers(init?.headers);
87
+ headers.set("Authorization", `Bearer ${authToken}`);
88
+ headers.set("Content-Type", "application/json");
89
+ headers.set("Accept", "application/json");
90
+ return fetch(url, { ...init, headers });
91
+ }
92
+ async function request(path2, init) {
93
+ const resolved = resolvePath(path2);
94
+ const url = `${BASE_URL}${resolved.startsWith("/") ? "" : "/"}${resolved}`;
95
+ return authenticatedFetch(url, init);
96
+ }
97
+ async function listProjects() {
98
+ const url = `${BASE_URL}/organizations/${organizationSlug}/projects/`;
99
+ const response = await authenticatedFetch(url);
100
+ if (!response.ok) {
101
+ const body = await response.text();
102
+ throw new Error(
103
+ `sentry: listProjects failed (${response.status}): ${body}`
104
+ );
105
+ }
106
+ return await response.json();
107
+ }
108
+ async function listIssues(options) {
109
+ const searchParams = new URLSearchParams();
110
+ if (options?.project) searchParams.set("project", options.project);
111
+ if (options?.query) searchParams.set("query", options.query);
112
+ if (options?.sort) searchParams.set("sort", options.sort);
113
+ if (options?.cursor) searchParams.set("cursor", options.cursor);
114
+ const qs = searchParams.toString();
115
+ const url = `${BASE_URL}/organizations/${organizationSlug}/issues/${qs ? `?${qs}` : ""}`;
116
+ const response = await authenticatedFetch(url);
117
+ if (!response.ok) {
118
+ const body = await response.text();
119
+ throw new Error(
120
+ `sentry: listIssues failed (${response.status}): ${body}`
121
+ );
122
+ }
123
+ return await response.json();
124
+ }
125
+ async function getIssue(issueId) {
126
+ const url = `${BASE_URL}/organizations/${organizationSlug}/issues/${issueId}/`;
127
+ const response = await authenticatedFetch(url);
128
+ if (!response.ok) {
129
+ const body = await response.text();
130
+ throw new Error(
131
+ `sentry: getIssue failed (${response.status}): ${body}`
132
+ );
133
+ }
134
+ return await response.json();
135
+ }
136
+ async function listIssueEvents(issueId) {
137
+ const url = `${BASE_URL}/organizations/${organizationSlug}/issues/${issueId}/events/`;
138
+ const response = await authenticatedFetch(url);
139
+ if (!response.ok) {
140
+ const body = await response.text();
141
+ throw new Error(
142
+ `sentry: listIssueEvents failed (${response.status}): ${body}`
143
+ );
144
+ }
145
+ return await response.json();
146
+ }
147
+ async function updateIssue(issueId, updates) {
148
+ const url = `${BASE_URL}/organizations/${organizationSlug}/issues/${issueId}/`;
149
+ const response = await authenticatedFetch(url, {
150
+ method: "PUT",
151
+ body: JSON.stringify(updates)
152
+ });
153
+ if (!response.ok) {
154
+ const body = await response.text();
155
+ throw new Error(
156
+ `sentry: updateIssue failed (${response.status}): ${body}`
157
+ );
158
+ }
159
+ return await response.json();
160
+ }
161
+ return {
162
+ request,
163
+ listProjects,
164
+ listIssues,
165
+ getIssue,
166
+ listIssueEvents,
167
+ updateIssue
168
+ };
169
+ }
170
+
171
+ // ../connectors/src/connector-onboarding.ts
172
+ var ConnectorOnboarding = class {
173
+ /** Phase 1: Connection setup instructions (optional — some connectors don't need this) */
174
+ connectionSetupInstructions;
175
+ /** Phase 2: Data overview instructions */
176
+ dataOverviewInstructions;
177
+ constructor(config) {
178
+ this.connectionSetupInstructions = config.connectionSetupInstructions;
179
+ this.dataOverviewInstructions = config.dataOverviewInstructions;
180
+ }
181
+ getConnectionSetupPrompt(language) {
182
+ return this.connectionSetupInstructions?.[language] ?? null;
183
+ }
184
+ getDataOverviewInstructions(language) {
185
+ return this.dataOverviewInstructions[language];
186
+ }
187
+ };
188
+
189
+ // ../connectors/src/connector-tool.ts
190
+ var ConnectorTool = class {
191
+ name;
192
+ description;
193
+ inputSchema;
194
+ outputSchema;
195
+ _execute;
196
+ constructor(config) {
197
+ this.name = config.name;
198
+ this.description = config.description;
199
+ this.inputSchema = config.inputSchema;
200
+ this.outputSchema = config.outputSchema;
201
+ this._execute = config.execute;
202
+ }
203
+ createTool(connections, config) {
204
+ return {
205
+ description: this.description,
206
+ inputSchema: this.inputSchema,
207
+ outputSchema: this.outputSchema,
208
+ execute: (input) => this._execute(input, connections, config)
209
+ };
210
+ }
211
+ };
212
+
213
+ // ../connectors/src/connector-plugin.ts
214
+ var ConnectorPlugin = class _ConnectorPlugin {
215
+ slug;
216
+ authType;
217
+ name;
218
+ description;
219
+ iconUrl;
220
+ parameters;
221
+ releaseFlag;
222
+ proxyPolicy;
223
+ experimentalAttributes;
224
+ onboarding;
225
+ systemPrompt;
226
+ tools;
227
+ query;
228
+ checkConnection;
229
+ constructor(config) {
230
+ this.slug = config.slug;
231
+ this.authType = config.authType;
232
+ this.name = config.name;
233
+ this.description = config.description;
234
+ this.iconUrl = config.iconUrl;
235
+ this.parameters = config.parameters;
236
+ this.releaseFlag = config.releaseFlag;
237
+ this.proxyPolicy = config.proxyPolicy;
238
+ this.experimentalAttributes = config.experimentalAttributes;
239
+ this.onboarding = config.onboarding;
240
+ this.systemPrompt = config.systemPrompt;
241
+ this.tools = config.tools;
242
+ this.query = config.query;
243
+ this.checkConnection = config.checkConnection;
244
+ }
245
+ get connectorKey() {
246
+ return _ConnectorPlugin.deriveKey(this.slug, this.authType);
247
+ }
248
+ /**
249
+ * Create tools for connections that belong to this connector.
250
+ * Filters connections by connectorKey internally.
251
+ * Returns tools keyed as `${connectorKey}_${toolName}`.
252
+ */
253
+ createTools(connections, config) {
254
+ const myConnections = connections.filter(
255
+ (c) => _ConnectorPlugin.deriveKey(c.connector.slug, c.connector.authType) === this.connectorKey
256
+ );
257
+ const result = {};
258
+ for (const t of Object.values(this.tools)) {
259
+ result[`${this.connectorKey}_${t.name}`] = t.createTool(
260
+ myConnections,
261
+ config
262
+ );
263
+ }
264
+ return result;
265
+ }
266
+ static deriveKey(slug, authType) {
267
+ return authType ? `${slug}-${authType}` : slug;
268
+ }
269
+ };
270
+
271
+ // ../connectors/src/auth-types.ts
272
+ var AUTH_TYPES = {
273
+ OAUTH: "oauth",
274
+ API_KEY: "api-key",
275
+ JWT: "jwt",
276
+ SERVICE_ACCOUNT: "service-account",
277
+ PAT: "pat",
278
+ USER_PASSWORD: "user-password"
279
+ };
280
+
281
+ // ../connectors/src/connectors/sentry/setup.ts
282
+ var sentryOnboarding = new ConnectorOnboarding({
283
+ dataOverviewInstructions: {
284
+ en: `1. Call sentry_request with GET /organizations/{organizationSlug}/projects/ to list all projects
285
+ 2. Call sentry_request with GET /organizations/{organizationSlug}/issues/?sort=date&query=is:unresolved to get recent unresolved issues
286
+ 3. For a specific issue, call sentry_request with GET /organizations/{organizationSlug}/issues/{issueId}/ to get details`,
287
+ ja: `1. sentry_request \u3067 GET /organizations/{organizationSlug}/projects/ \u3092\u547C\u3073\u51FA\u3057\u3001\u5168\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u4E00\u89A7\u8868\u793A
288
+ 2. sentry_request \u3067 GET /organizations/{organizationSlug}/issues/?sort=date&query=is:unresolved \u3092\u547C\u3073\u51FA\u3057\u3001\u6700\u8FD1\u306E\u672A\u89E3\u6C7A\u30A4\u30B7\u30E5\u30FC\u3092\u53D6\u5F97
289
+ 3. \u7279\u5B9A\u306E\u30A4\u30B7\u30E5\u30FC\u306B\u3064\u3044\u3066 sentry_request \u3067 GET /organizations/{organizationSlug}/issues/{issueId}/ \u3092\u547C\u3073\u51FA\u3057\u3001\u8A73\u7D30\u3092\u53D6\u5F97`
290
+ }
291
+ });
292
+
293
+ // ../connectors/src/connectors/sentry/tools/request.ts
294
+ import { z } from "zod";
295
+ var BASE_URL2 = "https://sentry.io/api/0";
296
+ var REQUEST_TIMEOUT_MS = 6e4;
297
+ var inputSchema = z.object({
298
+ toolUseIntent: z.string().optional().describe(
299
+ "Brief description of what you intend to accomplish with this tool call"
300
+ ),
301
+ connectionId: z.string().describe("ID of the Sentry connection to use"),
302
+ method: z.enum(["GET", "POST", "PUT", "DELETE"]).describe("HTTP method"),
303
+ path: z.string().describe(
304
+ "API path appended to https://sentry.io/api/0 (e.g., '/organizations/{organizationSlug}/projects/', '/projects/{organizationSlug}/{project}/issues/'). {organizationSlug} is automatically replaced with the configured organization slug."
305
+ ),
306
+ body: z.record(z.string(), z.unknown()).optional().describe("JSON request body for POST/PUT requests")
307
+ });
308
+ var outputSchema = z.discriminatedUnion("success", [
309
+ z.object({
310
+ success: z.literal(true),
311
+ status: z.number(),
312
+ data: z.unknown()
313
+ }),
314
+ z.object({
315
+ success: z.literal(false),
316
+ error: z.string()
317
+ })
318
+ ]);
319
+ var requestTool = new ConnectorTool({
320
+ name: "request",
321
+ description: `Send authenticated requests to the Sentry API.
322
+ Supports GET, POST, PUT, and DELETE methods.
323
+ Authentication is handled automatically via Bearer token.
324
+ {organizationSlug} in the path is automatically replaced with the configured organization slug.`,
325
+ inputSchema,
326
+ outputSchema,
327
+ async execute({ connectionId, method, path: path2, body }, connections) {
328
+ const connection2 = connections.find((c) => c.id === connectionId);
329
+ if (!connection2) {
330
+ return {
331
+ success: false,
332
+ error: `Connection ${connectionId} not found`
333
+ };
334
+ }
335
+ console.log(
336
+ `[connector-request] sentry/${connection2.name}: ${method} ${path2}`
337
+ );
338
+ try {
339
+ const authToken = parameters.authToken.getValue(connection2);
340
+ const organizationSlug = parameters.organizationSlug.getValue(connection2);
341
+ const resolvedPath = path2.replace(
342
+ /\{organizationSlug\}/g,
343
+ organizationSlug
344
+ );
345
+ const url = `${BASE_URL2}${resolvedPath.startsWith("/") ? "" : "/"}${resolvedPath}`;
346
+ const controller = new AbortController();
347
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
348
+ try {
349
+ const response = await fetch(url, {
350
+ method,
351
+ headers: {
352
+ Authorization: `Bearer ${authToken}`,
353
+ "Content-Type": "application/json",
354
+ Accept: "application/json"
355
+ },
356
+ ...body != null ? { body: JSON.stringify(body) } : {},
357
+ signal: controller.signal
358
+ });
359
+ const data = await response.json();
360
+ if (!response.ok) {
361
+ const errorMessage = typeof data === "object" && data !== null && "detail" in data && typeof data.detail === "string" ? data.detail : `HTTP ${response.status} ${response.statusText}`;
362
+ return { success: false, error: errorMessage };
363
+ }
364
+ return { success: true, status: response.status, data };
365
+ } finally {
366
+ clearTimeout(timeout);
367
+ }
368
+ } catch (err) {
369
+ const msg = err instanceof Error ? err.message : String(err);
370
+ return { success: false, error: msg };
371
+ }
372
+ }
373
+ });
374
+
375
+ // ../connectors/src/connectors/sentry/index.ts
376
+ var tools = { request: requestTool };
377
+ var sentryConnector = new ConnectorPlugin({
378
+ slug: "sentry",
379
+ authType: AUTH_TYPES.API_KEY,
380
+ name: "Sentry",
381
+ description: "Connect to Sentry for error tracking and performance monitoring data.",
382
+ iconUrl: "https://images.ctfassets.net/9ncizv60xc5y/4B8ZEGFGjTeMWNnXQb1dAL/ac10f813f02353f5b0cbe64fb5c06d8f/sentry.svg",
383
+ parameters,
384
+ releaseFlag: { dev1: true, dev2: false, prod: false },
385
+ onboarding: sentryOnboarding,
386
+ systemPrompt: {
387
+ en: `### Tools
388
+
389
+ - \`sentry_request\`: Send authenticated requests to the Sentry API. Supports GET, POST, PUT, and DELETE methods. Authentication is handled automatically via Bearer token. The {organizationSlug} placeholder in paths is automatically replaced with the configured organization slug.
390
+
391
+ ### Sentry API Reference
392
+
393
+ #### Organization & Projects
394
+ - GET \`/organizations/{organizationSlug}/\` \u2014 Get organization details
395
+ - GET \`/organizations/{organizationSlug}/projects/\` \u2014 List all projects
396
+
397
+ #### Issues
398
+ - GET \`/organizations/{organizationSlug}/issues/\` \u2014 List issues (supports query params: \`query\`, \`sort\`, \`project\`, \`cursor\`)
399
+ - GET \`/organizations/{organizationSlug}/issues/{issueId}/\` \u2014 Get issue details
400
+ - PUT \`/organizations/{organizationSlug}/issues/{issueId}/\` \u2014 Update issue (resolve, ignore, assign). Body: \`{ "status": "resolved" }\` or \`{ "assignedTo": "user:id" }\`
401
+ - DELETE \`/organizations/{organizationSlug}/issues/{issueId}/\` \u2014 Delete an issue
402
+
403
+ #### Events
404
+ - GET \`/organizations/{organizationSlug}/issues/{issueId}/events/\` \u2014 List events for an issue
405
+ - GET \`/organizations/{organizationSlug}/issues/{issueId}/events/latest/\` \u2014 Get latest event for an issue
406
+ - GET \`/organizations/{organizationSlug}/events/{eventId}/\` \u2014 Get event details
407
+
408
+ #### Tags & Stats
409
+ - GET \`/organizations/{organizationSlug}/issues/{issueId}/tags/\` \u2014 List tags for an issue
410
+ - GET \`/organizations/{organizationSlug}/issues/{issueId}/tags/{tagKey}/values/\` \u2014 List tag values
411
+
412
+ #### Issue Search Query Syntax
413
+ - \`is:unresolved\` \u2014 Unresolved issues
414
+ - \`is:resolved\` \u2014 Resolved issues
415
+ - \`is:ignored\` \u2014 Ignored issues
416
+ - \`assigned:me\` \u2014 Assigned to current user
417
+ - \`assigned:none\` \u2014 Unassigned issues
418
+ - \`level:error\` \u2014 Filter by level (fatal, error, warning, info, debug)
419
+ - \`project:my-project\` \u2014 Filter by project slug
420
+ - \`browser:Chrome\` \u2014 Filter by browser tag
421
+ - \`os:Windows\` \u2014 Filter by OS tag
422
+ - \`first-seen:>2024-01-01\` \u2014 First seen after date
423
+ - \`last-seen:<24h\` \u2014 Last seen within 24 hours
424
+ - \`times-seen:>100\` \u2014 Seen more than 100 times
425
+ - Combine with spaces: \`is:unresolved level:error project:backend\`
426
+
427
+ #### Sort Options
428
+ - \`date\` \u2014 Last seen (default)
429
+ - \`new\` \u2014 First seen
430
+ - \`freq\` \u2014 Frequency (most frequent first)
431
+ - \`priority\` \u2014 Priority
432
+
433
+ ### Tips
434
+ - Use \`{organizationSlug}\` placeholder in paths \u2014 it is automatically replaced
435
+ - Sentry API responses are paginated; check the \`Link\` header for pagination cursors
436
+ - Issue IDs are numeric; short IDs (e.g., \`PROJECT-123\`) can be used in the \`query\` param: \`query=PROJECT-123\`
437
+ - To resolve an issue: PUT with \`{ "status": "resolved" }\`
438
+ - To ignore an issue: PUT with \`{ "status": "ignored" }\`
439
+
440
+ ### Business Logic
441
+
442
+ 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.
443
+
444
+ #### Example
445
+
446
+ \`\`\`ts
447
+ import { connection } from "@squadbase/vite-server/connectors/sentry";
448
+
449
+ const sentry = connection("<connectionId>");
450
+
451
+ // List all projects
452
+ const projects = await sentry.listProjects();
453
+ console.log(projects.map(p => p.slug));
454
+
455
+ // List unresolved issues sorted by frequency
456
+ const issues = await sentry.listIssues({ query: "is:unresolved", sort: "freq" });
457
+ issues.forEach(issue => console.log(issue.shortId, issue.title, issue.count));
458
+
459
+ // Get issue details
460
+ const issue = await sentry.getIssue("12345");
461
+ console.log(issue.title, issue.status, issue.lastSeen);
462
+
463
+ // List events for an issue
464
+ const events = await sentry.listIssueEvents("12345");
465
+ events.forEach(e => console.log(e.dateCreated, e.message));
466
+
467
+ // Resolve an issue
468
+ await sentry.updateIssue("12345", { status: "resolved" });
469
+ \`\`\``,
470
+ ja: `### \u30C4\u30FC\u30EB
471
+
472
+ - \`sentry_request\`: Sentry API\u3078\u306E\u8A8D\u8A3C\u6E08\u307F\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u9001\u4FE1\u3057\u307E\u3059\u3002GET, POST, PUT, DELETE\u30E1\u30BD\u30C3\u30C9\u3092\u30B5\u30DD\u30FC\u30C8\u3057\u307E\u3059\u3002Bearer token\u306B\u3088\u308B\u8A8D\u8A3C\u306F\u81EA\u52D5\u3067\u884C\u308F\u308C\u307E\u3059\u3002\u30D1\u30B9\u5185\u306E{organizationSlug}\u30D7\u30EC\u30FC\u30B9\u30DB\u30EB\u30C0\u30FC\u306F\u8A2D\u5B9A\u6E08\u307F\u306E\u7D44\u7E54\u30B9\u30E9\u30C3\u30B0\u3067\u81EA\u52D5\u7684\u306B\u7F6E\u63DB\u3055\u308C\u307E\u3059\u3002
473
+
474
+ ### Sentry API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
475
+
476
+ #### \u7D44\u7E54 & \u30D7\u30ED\u30B8\u30A7\u30AF\u30C8
477
+ - GET \`/organizations/{organizationSlug}/\` \u2014 \u7D44\u7E54\u306E\u8A73\u7D30\u3092\u53D6\u5F97
478
+ - GET \`/organizations/{organizationSlug}/projects/\` \u2014 \u5168\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u4E00\u89A7\u8868\u793A
479
+
480
+ #### \u30A4\u30B7\u30E5\u30FC
481
+ - GET \`/organizations/{organizationSlug}/issues/\` \u2014 \u30A4\u30B7\u30E5\u30FC\u4E00\u89A7\uFF08\u30AF\u30A8\u30EA\u30D1\u30E9\u30E1\u30FC\u30BF: \`query\`, \`sort\`, \`project\`, \`cursor\`\uFF09
482
+ - GET \`/organizations/{organizationSlug}/issues/{issueId}/\` \u2014 \u30A4\u30B7\u30E5\u30FC\u306E\u8A73\u7D30\u3092\u53D6\u5F97
483
+ - PUT \`/organizations/{organizationSlug}/issues/{issueId}/\` \u2014 \u30A4\u30B7\u30E5\u30FC\u306E\u66F4\u65B0\uFF08\u89E3\u6C7A\u3001\u7121\u8996\u3001\u62C5\u5F53\u8005\u5272\u308A\u5F53\u3066\uFF09\u3002Body: \`{ "status": "resolved" }\` \u307E\u305F\u306F \`{ "assignedTo": "user:id" }\`
484
+ - DELETE \`/organizations/{organizationSlug}/issues/{issueId}/\` \u2014 \u30A4\u30B7\u30E5\u30FC\u306E\u524A\u9664
485
+
486
+ #### \u30A4\u30D9\u30F3\u30C8
487
+ - GET \`/organizations/{organizationSlug}/issues/{issueId}/events/\` \u2014 \u30A4\u30B7\u30E5\u30FC\u306E\u30A4\u30D9\u30F3\u30C8\u4E00\u89A7
488
+ - GET \`/organizations/{organizationSlug}/issues/{issueId}/events/latest/\` \u2014 \u30A4\u30B7\u30E5\u30FC\u306E\u6700\u65B0\u30A4\u30D9\u30F3\u30C8
489
+ - GET \`/organizations/{organizationSlug}/events/{eventId}/\` \u2014 \u30A4\u30D9\u30F3\u30C8\u306E\u8A73\u7D30
490
+
491
+ #### \u30BF\u30B0 & \u7D71\u8A08
492
+ - GET \`/organizations/{organizationSlug}/issues/{issueId}/tags/\` \u2014 \u30A4\u30B7\u30E5\u30FC\u306E\u30BF\u30B0\u4E00\u89A7
493
+ - GET \`/organizations/{organizationSlug}/issues/{issueId}/tags/{tagKey}/values/\` \u2014 \u30BF\u30B0\u5024\u306E\u4E00\u89A7
494
+
495
+ #### \u30A4\u30B7\u30E5\u30FC\u691C\u7D22\u30AF\u30A8\u30EA\u69CB\u6587
496
+ - \`is:unresolved\` \u2014 \u672A\u89E3\u6C7A\u306E\u30A4\u30B7\u30E5\u30FC
497
+ - \`is:resolved\` \u2014 \u89E3\u6C7A\u6E08\u307F\u306E\u30A4\u30B7\u30E5\u30FC
498
+ - \`is:ignored\` \u2014 \u7121\u8996\u3055\u308C\u305F\u30A4\u30B7\u30E5\u30FC
499
+ - \`assigned:me\` \u2014 \u81EA\u5206\u306B\u5272\u308A\u5F53\u3066\u3089\u308C\u305F\u30A4\u30B7\u30E5\u30FC
500
+ - \`assigned:none\` \u2014 \u672A\u5272\u308A\u5F53\u3066\u306E\u30A4\u30B7\u30E5\u30FC
501
+ - \`level:error\` \u2014 \u30EC\u30D9\u30EB\u3067\u30D5\u30A3\u30EB\u30BF\uFF08fatal, error, warning, info, debug\uFF09
502
+ - \`project:my-project\` \u2014 \u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u30B9\u30E9\u30C3\u30B0\u3067\u30D5\u30A3\u30EB\u30BF
503
+ - \`first-seen:>2024-01-01\` \u2014 \u6307\u5B9A\u65E5\u4EE5\u964D\u306B\u521D\u3081\u3066\u767A\u751F
504
+ - \`last-seen:<24h\` \u2014 24\u6642\u9593\u4EE5\u5185\u306B\u6700\u5F8C\u306B\u767A\u751F
505
+ - \`times-seen:>100\` \u2014 100\u56DE\u4EE5\u4E0A\u767A\u751F
506
+ - \u30B9\u30DA\u30FC\u30B9\u3067\u7D44\u307F\u5408\u308F\u305B: \`is:unresolved level:error project:backend\`
507
+
508
+ #### \u30BD\u30FC\u30C8\u30AA\u30D7\u30B7\u30E7\u30F3
509
+ - \`date\` \u2014 \u6700\u7D42\u78BA\u8A8D\u65E5\uFF08\u30C7\u30D5\u30A9\u30EB\u30C8\uFF09
510
+ - \`new\` \u2014 \u521D\u56DE\u78BA\u8A8D\u65E5
511
+ - \`freq\` \u2014 \u983B\u5EA6\uFF08\u9AD8\u3044\u9806\uFF09
512
+ - \`priority\` \u2014 \u512A\u5148\u5EA6
513
+
514
+ ### \u30D2\u30F3\u30C8
515
+ - \u30D1\u30B9\u306B \`{organizationSlug}\` \u30D7\u30EC\u30FC\u30B9\u30DB\u30EB\u30C0\u30FC\u3092\u4F7F\u7528 \u2014 \u8A2D\u5B9A\u6E08\u307F\u306E\u7D44\u7E54\u30B9\u30E9\u30C3\u30B0\u3067\u81EA\u52D5\u7684\u306B\u7F6E\u63DB\u3055\u308C\u307E\u3059
516
+ - Sentry API\u306E\u30EC\u30B9\u30DD\u30F3\u30B9\u306F\u30DA\u30FC\u30B8\u30CD\u30FC\u30B7\u30E7\u30F3\u5BFE\u5FDC\u3067\u3059\u3002\`Link\`\u30D8\u30C3\u30C0\u30FC\u3067\u30AB\u30FC\u30BD\u30EB\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044
517
+ - \u30A4\u30B7\u30E5\u30FCID\u306F\u6570\u5024\u3067\u3059\u3002\u30B7\u30E7\u30FC\u30C8ID\uFF08\u4F8B: \`PROJECT-123\`\uFF09\u306F\`query\`\u30D1\u30E9\u30E1\u30FC\u30BF\u3067\u4F7F\u7528\u3067\u304D\u307E\u3059: \`query=PROJECT-123\`
518
+ - \u30A4\u30B7\u30E5\u30FC\u3092\u89E3\u6C7A\u3059\u308B\u306B\u306F: PUT\u3067 \`{ "status": "resolved" }\`
519
+ - \u30A4\u30B7\u30E5\u30FC\u3092\u7121\u8996\u3059\u308B\u306B\u306F: PUT\u3067 \`{ "status": "ignored" }\`
520
+
521
+ ### Business Logic
522
+
523
+ \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
524
+
525
+ #### Example
526
+
527
+ \`\`\`ts
528
+ import { connection } from "@squadbase/vite-server/connectors/sentry";
529
+
530
+ const sentry = connection("<connectionId>");
531
+
532
+ // \u5168\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u4E00\u89A7\u8868\u793A
533
+ const projects = await sentry.listProjects();
534
+ console.log(projects.map(p => p.slug));
535
+
536
+ // \u672A\u89E3\u6C7A\u30A4\u30B7\u30E5\u30FC\u3092\u983B\u5EA6\u9806\u3067\u4E00\u89A7\u8868\u793A
537
+ const issues = await sentry.listIssues({ query: "is:unresolved", sort: "freq" });
538
+ issues.forEach(issue => console.log(issue.shortId, issue.title, issue.count));
539
+
540
+ // \u30A4\u30B7\u30E5\u30FC\u306E\u8A73\u7D30\u3092\u53D6\u5F97
541
+ const issue = await sentry.getIssue("12345");
542
+ console.log(issue.title, issue.status, issue.lastSeen);
543
+
544
+ // \u30A4\u30B7\u30E5\u30FC\u306E\u30A4\u30D9\u30F3\u30C8\u4E00\u89A7\u3092\u53D6\u5F97
545
+ const events = await sentry.listIssueEvents("12345");
546
+ events.forEach(e => console.log(e.dateCreated, e.message));
547
+
548
+ // \u30A4\u30B7\u30E5\u30FC\u3092\u89E3\u6C7A\u3059\u308B
549
+ await sentry.updateIssue("12345", { status: "resolved" });
550
+ \`\`\``
551
+ },
552
+ tools,
553
+ async checkConnection(params) {
554
+ const authToken = params[parameters.authToken.slug];
555
+ const organizationSlug = params[parameters.organizationSlug.slug];
556
+ if (!authToken || !organizationSlug) {
557
+ return {
558
+ success: false,
559
+ error: "Missing required parameters: auth-token and organization-slug"
560
+ };
561
+ }
562
+ const url = `https://sentry.io/api/0/organizations/${organizationSlug}/`;
563
+ try {
564
+ const res = await fetch(url, {
565
+ method: "GET",
566
+ headers: {
567
+ Authorization: `Bearer ${authToken}`,
568
+ Accept: "application/json"
569
+ }
570
+ });
571
+ if (!res.ok) {
572
+ const errorText = await res.text().catch(() => res.statusText);
573
+ return {
574
+ success: false,
575
+ error: `Sentry API failed: HTTP ${res.status} ${errorText}`
576
+ };
577
+ }
578
+ return { success: true };
579
+ } catch (error) {
580
+ return {
581
+ success: false,
582
+ error: error instanceof Error ? error.message : String(error)
583
+ };
584
+ }
585
+ }
586
+ });
587
+
588
+ // src/connectors/create-connector-sdk.ts
589
+ import { readFileSync } from "fs";
590
+ import path from "path";
591
+
592
+ // src/connector-client/env.ts
593
+ function resolveEnvVar(entry, key, connectionId) {
594
+ const envVarName = entry.envVars[key];
595
+ if (!envVarName) {
596
+ throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
597
+ }
598
+ const value = process.env[envVarName];
599
+ if (!value) {
600
+ throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
601
+ }
602
+ return value;
603
+ }
604
+ function resolveEnvVarOptional(entry, key) {
605
+ const envVarName = entry.envVars[key];
606
+ if (!envVarName) return void 0;
607
+ return process.env[envVarName] || void 0;
608
+ }
609
+
610
+ // src/connector-client/proxy-fetch.ts
611
+ import { getContext } from "hono/context-storage";
612
+ import { getCookie } from "hono/cookie";
613
+ var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
614
+ function createSandboxProxyFetch(connectionId) {
615
+ return async (input, init) => {
616
+ const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
617
+ const sandboxId = process.env.INTERNAL_SQUADBASE_SANDBOX_ID;
618
+ if (!token || !sandboxId) {
619
+ throw new Error(
620
+ "Connection proxy is not configured. Please check your deployment settings."
621
+ );
622
+ }
623
+ const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
624
+ const originalMethod = init?.method ?? "GET";
625
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
626
+ const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
627
+ const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
628
+ return fetch(proxyUrl, {
629
+ method: "POST",
630
+ headers: {
631
+ "Content-Type": "application/json",
632
+ Authorization: `Bearer ${token}`
633
+ },
634
+ body: JSON.stringify({
635
+ url: originalUrl,
636
+ method: originalMethod,
637
+ body: originalBody
638
+ })
639
+ });
640
+ };
641
+ }
642
+ function createDeployedAppProxyFetch(connectionId) {
643
+ const projectId = process.env["SQUADBASE_PROJECT_ID"];
644
+ if (!projectId) {
645
+ throw new Error(
646
+ "Connection proxy is not configured. Please check your deployment settings."
647
+ );
648
+ }
649
+ const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
650
+ const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
651
+ return async (input, init) => {
652
+ const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
653
+ const originalMethod = init?.method ?? "GET";
654
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
655
+ const c = getContext();
656
+ const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
657
+ if (!appSession) {
658
+ throw new Error(
659
+ "No authentication method available for connection proxy."
660
+ );
661
+ }
662
+ return fetch(proxyUrl, {
663
+ method: "POST",
664
+ headers: {
665
+ "Content-Type": "application/json",
666
+ Authorization: `Bearer ${appSession}`
667
+ },
668
+ body: JSON.stringify({
669
+ url: originalUrl,
670
+ method: originalMethod,
671
+ body: originalBody
672
+ })
673
+ });
674
+ };
675
+ }
676
+ function createProxyFetch(connectionId) {
677
+ if (process.env.INTERNAL_SQUADBASE_SANDBOX_ID) {
678
+ return createSandboxProxyFetch(connectionId);
679
+ }
680
+ return createDeployedAppProxyFetch(connectionId);
681
+ }
682
+
683
+ // src/connectors/create-connector-sdk.ts
684
+ function loadConnectionsSync() {
685
+ const filePath = process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
686
+ try {
687
+ const raw = readFileSync(filePath, "utf-8");
688
+ return JSON.parse(raw);
689
+ } catch {
690
+ return {};
691
+ }
692
+ }
693
+ function createConnectorSdk(plugin, createClient2) {
694
+ return (connectionId) => {
695
+ const connections = loadConnectionsSync();
696
+ const entry = connections[connectionId];
697
+ if (!entry) {
698
+ throw new Error(
699
+ `Connection "${connectionId}" not found in .squadbase/connections.json`
700
+ );
701
+ }
702
+ if (entry.connector.slug !== plugin.slug) {
703
+ throw new Error(
704
+ `Connection "${connectionId}" is not a ${plugin.slug} connection (got "${entry.connector.slug}")`
705
+ );
706
+ }
707
+ const params = {};
708
+ for (const param of Object.values(plugin.parameters)) {
709
+ if (param.required) {
710
+ params[param.slug] = resolveEnvVar(entry, param.slug, connectionId);
711
+ } else {
712
+ const val = resolveEnvVarOptional(entry, param.slug);
713
+ if (val !== void 0) params[param.slug] = val;
714
+ }
715
+ }
716
+ return createClient2(params, createProxyFetch(connectionId));
717
+ };
718
+ }
719
+
720
+ // src/connectors/entries/sentry.ts
721
+ var connection = createConnectorSdk(sentryConnector, createClient);
722
+ export {
723
+ connection
724
+ };