@squadbase/vite-server 0.1.12-dev.a9ac647 → 0.1.17-dev.3b633bb

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/cli/index.js +14375 -1652
  2. package/dist/connectors/airtable-oauth.js +282 -46
  3. package/dist/connectors/airtable.js +319 -51
  4. package/dist/connectors/amplitude.js +322 -47
  5. package/dist/connectors/anthropic.js +135 -47
  6. package/dist/connectors/asana.js +327 -49
  7. package/dist/connectors/attio.js +302 -49
  8. package/dist/connectors/aws-billing.js +287 -46
  9. package/dist/connectors/azure-sql.js +421 -102
  10. package/dist/connectors/backlog-api-key.js +317 -47
  11. package/dist/connectors/clickup.js +338 -49
  12. package/dist/connectors/cosmosdb.js +305 -50
  13. package/dist/connectors/customerio.js +319 -47
  14. package/dist/connectors/dbt.js +340 -47
  15. package/dist/connectors/freshdesk.js +342 -53
  16. package/dist/connectors/freshsales.js +333 -52
  17. package/dist/connectors/freshservice.js +361 -53
  18. package/dist/connectors/gamma.js +327 -52
  19. package/dist/connectors/gemini.js +134 -47
  20. package/dist/connectors/github.js +386 -49
  21. package/dist/connectors/gmail-oauth.js +204 -7
  22. package/dist/connectors/gmail.js +350 -47
  23. package/dist/connectors/google-ads.js +288 -46
  24. package/dist/connectors/google-analytics-oauth.js +310 -46
  25. package/dist/connectors/google-analytics.js +547 -87
  26. package/dist/connectors/google-audit-log.js +438 -47
  27. package/dist/connectors/google-calendar-oauth.js +259 -46
  28. package/dist/connectors/google-calendar.js +359 -47
  29. package/dist/connectors/google-docs.js +220 -6
  30. package/dist/connectors/google-drive.js +262 -5
  31. package/dist/connectors/google-search-console-oauth.js +256 -46
  32. package/dist/connectors/google-sheets.js +272 -47
  33. package/dist/connectors/google-slides.js +205 -6
  34. package/dist/connectors/grafana.js +332 -49
  35. package/dist/connectors/hubspot-oauth.js +208 -5
  36. package/dist/connectors/hubspot.js +306 -49
  37. package/dist/connectors/influxdb.js +416 -51
  38. package/dist/connectors/intercom-oauth.js +210 -5
  39. package/dist/connectors/intercom.js +302 -49
  40. package/dist/connectors/jdbc.js +762 -110
  41. package/dist/connectors/jira-api-key.js +326 -47
  42. package/dist/connectors/kintone-api-token.js +281 -47
  43. package/dist/connectors/kintone.js +328 -47
  44. package/dist/connectors/linear.js +330 -49
  45. package/dist/connectors/linkedin-ads.js +268 -50
  46. package/dist/connectors/mailchimp-oauth.js +268 -46
  47. package/dist/connectors/mailchimp.js +320 -49
  48. package/dist/connectors/meta-ads-oauth.js +273 -48
  49. package/dist/connectors/meta-ads.js +285 -50
  50. package/dist/connectors/mixpanel.js +338 -47
  51. package/dist/connectors/monday.js +360 -49
  52. package/dist/connectors/mongodb.js +319 -57
  53. package/dist/connectors/notion-oauth.js +231 -5
  54. package/dist/connectors/notion.js +323 -51
  55. package/dist/connectors/openai.js +134 -47
  56. package/dist/connectors/oracle.js +454 -103
  57. package/dist/connectors/outlook-oauth.js +204 -5
  58. package/dist/connectors/powerbi-oauth.js +498 -5
  59. package/dist/connectors/salesforce.js +384 -49
  60. package/dist/connectors/semrush.js +609 -49
  61. package/dist/connectors/sentry.js +289 -50
  62. package/dist/connectors/shopify-oauth.js +187 -5
  63. package/dist/connectors/shopify.js +357 -47
  64. package/dist/connectors/sqlserver.js +415 -102
  65. package/dist/connectors/stripe-api-key.js +269 -46
  66. package/dist/connectors/stripe-oauth.js +202 -5
  67. package/dist/connectors/supabase.js +303 -48
  68. package/dist/connectors/tableau.js +536 -163
  69. package/dist/connectors/tiktok-ads.js +279 -48
  70. package/dist/connectors/wix-store.js +320 -49
  71. package/dist/connectors/zendesk-oauth.js +239 -5
  72. package/dist/connectors/zendesk.js +358 -47
  73. package/dist/index.d.ts +149 -1
  74. package/dist/index.js +15057 -2117
  75. package/dist/main.js +15005 -2073
  76. package/dist/vite-plugin.js +14752 -2019
  77. package/package.json +1 -1
@@ -1,48 +1,60 @@
1
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
+ var __esm = (fn, res) => function __init() {
3
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
4
+ };
5
+
1
6
  // ../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;
7
+ var ParameterDefinition;
8
+ var init_parameter_definition = __esm({
9
+ "../connectors/src/parameter-definition.ts"() {
10
+ "use strict";
11
+ ParameterDefinition = class {
12
+ slug;
13
+ name;
14
+ description;
15
+ envVarBaseKey;
16
+ type;
17
+ secret;
18
+ required;
19
+ constructor(config) {
20
+ this.slug = config.slug;
21
+ this.name = config.name;
22
+ this.description = config.description;
23
+ this.envVarBaseKey = config.envVarBaseKey;
24
+ this.type = config.type;
25
+ this.secret = config.secret;
26
+ this.required = config.required;
27
+ }
28
+ /**
29
+ * Get the parameter value from a ConnectorConnectionObject.
30
+ */
31
+ getValue(connection2) {
32
+ const param = connection2.parameters.find(
33
+ (p) => p.parameterSlug === this.slug
34
+ );
35
+ if (!param || param.value == null) {
36
+ throw new Error(
37
+ `Parameter "${this.slug}" not found or has no value in connection "${connection2.id}"`
38
+ );
39
+ }
40
+ return param.value;
41
+ }
42
+ /**
43
+ * Try to get the parameter value. Returns undefined if not found (for optional params).
44
+ */
45
+ tryGetValue(connection2) {
46
+ const param = connection2.parameters.find(
47
+ (p) => p.parameterSlug === this.slug
48
+ );
49
+ if (!param || param.value == null) return void 0;
50
+ return param.value;
51
+ }
52
+ };
42
53
  }
43
- };
54
+ });
44
55
 
45
56
  // ../connectors/src/connectors/tableau/parameters.ts
57
+ init_parameter_definition();
46
58
  var parameters = {
47
59
  serverUrl: new ParameterDefinition({
48
60
  slug: "server-url",
@@ -93,64 +105,26 @@ var parameters = {
93
105
 
94
106
  // ../connectors/src/connectors/tableau/sdk/index.ts
95
107
  var DEFAULT_API_VERSION = "3.28";
96
- function createClient(params) {
108
+ var TABLEAU_SESSION_SENTINEL_URL = "squadbase://tableau-session/";
109
+ function createClient(params, fetchFn = fetch) {
97
110
  const serverUrl = params[parameters.serverUrl.slug];
98
- const siteContentUrl = params[parameters.siteContentUrl.slug];
99
- const patName = params[parameters.patName.slug];
100
- const patSecret = params[parameters.patSecret.slug];
101
111
  const apiVersion = params[parameters.apiVersion.slug] || DEFAULT_API_VERSION;
102
- if (!serverUrl || siteContentUrl == null || !patName || !patSecret) {
103
- throw new Error(
104
- `tableau: missing required parameters: ${parameters.serverUrl.slug}, ${parameters.siteContentUrl.slug}, ${parameters.patName.slug}, ${parameters.patSecret.slug}`
105
- );
112
+ if (!serverUrl) {
113
+ throw new Error(`tableau: missing required parameter: ${parameters.serverUrl.slug}`);
106
114
  }
107
115
  const baseUrl = `${serverUrl.replace(/\/$/, "")}/api/${apiVersion}`;
108
- let session = null;
109
- async function signIn2() {
110
- const res = await fetch(`${baseUrl}/auth/signin`, {
111
- method: "POST",
112
- headers: {
113
- "Content-Type": "application/json",
114
- Accept: "application/json"
115
- },
116
- body: JSON.stringify({
117
- credentials: {
118
- personalAccessTokenName: patName,
119
- personalAccessTokenSecret: patSecret,
120
- site: { contentUrl: siteContentUrl }
121
- }
122
- })
123
- });
124
- if (!res.ok) {
125
- const body = await res.text();
126
- throw new Error(`tableau: sign-in failed (${res.status}): ${body}`);
127
- }
128
- const data = await res.json();
129
- return {
130
- authToken: data.credentials.token,
131
- siteId: data.credentials.site.id,
132
- userId: data.credentials.user.id,
133
- expiresAt: Date.now() + 30 * 60 * 1e3
134
- };
135
- }
136
- async function ensureSession() {
137
- if (session && session.expiresAt > Date.now() + 6e4) {
138
- return session;
139
- }
140
- session = await signIn2();
141
- return session;
142
- }
143
- async function request(path2, init) {
144
- const s = await ensureSession();
145
- const resolvedPath = path2.replace(/\{siteId\}/g, s.siteId).replace(/^([^/])/, "/$1");
146
- const url = `${baseUrl}${resolvedPath}`;
116
+ function buildRequestInit(init) {
147
117
  const headers = new Headers(init?.headers);
148
- headers.set("X-Tableau-Auth", s.authToken);
149
118
  if (!headers.has("Accept")) headers.set("Accept", "application/json");
150
119
  if (!headers.has("Content-Type") && init?.body) {
151
120
  headers.set("Content-Type", "application/json");
152
121
  }
153
- return fetch(url, { ...init, headers });
122
+ return { ...init, headers };
123
+ }
124
+ async function request(path2, init) {
125
+ const trimmedPath = path2.replace(/^([^/])/, "/$1");
126
+ const url = `${baseUrl}${trimmedPath}`;
127
+ return fetchFn(url, buildRequestInit(init));
154
128
  }
155
129
  async function getJson(path2) {
156
130
  const res = await request(path2);
@@ -173,8 +147,19 @@ function createClient(params) {
173
147
  return {
174
148
  request,
175
149
  async getSession() {
176
- const s = await ensureSession();
177
- return { authToken: s.authToken, siteId: s.siteId, userId: s.userId };
150
+ const res = await fetchFn(TABLEAU_SESSION_SENTINEL_URL, { method: "POST" });
151
+ if (!res.ok) {
152
+ const body = await res.text().catch(() => "(unreadable)");
153
+ throw new Error(
154
+ `tableau: failed to fetch session (${res.status}): ${body}`
155
+ );
156
+ }
157
+ const data = await res.json();
158
+ return {
159
+ authToken: data.authToken,
160
+ siteId: data.siteId,
161
+ userId: data.userId
162
+ };
178
163
  },
179
164
  async listProjects(options) {
180
165
  return getJson(
@@ -271,6 +256,28 @@ var ConnectorPlugin = class _ConnectorPlugin {
271
256
  tools;
272
257
  query;
273
258
  checkConnection;
259
+ /**
260
+ * SQPD-1212: Logic-based, rule-driven connection setup. Connectors that
261
+ * implement this expose a step-by-step exploration flow (database/schema/
262
+ * table/etc. discovery) that the dashboard backend drives via the
263
+ * `/connections/:connectionId/setup` endpoint. Implement by delegating to
264
+ * `runSetupFlow` from `setup-flow.ts`.
265
+ */
266
+ setup;
267
+ /**
268
+ * Opt-out of the default "verify before save" behavior on connection
269
+ * creation. The backend invokes `checkConnection` synchronously while
270
+ * creating the connection and aborts (no row inserted) if it fails — this
271
+ * flag disables that for connectors where the check cannot succeed pre-save:
272
+ *
273
+ * - `squadbase-db` populates `connection-url` only after Neon provisioning
274
+ * - OAuth connectors require an OAuth-aware proxyFetch keyed by the
275
+ * connectionId, which doesn't exist until the row is saved
276
+ *
277
+ * Exceptions are the explicit position; new credential-input connectors get
278
+ * the default verify-on-create behavior without opt-in.
279
+ */
280
+ skipConnectionCheckOnCreate;
274
281
  constructor(config) {
275
282
  this.slug = config.slug;
276
283
  this.authType = config.authType;
@@ -287,6 +294,8 @@ var ConnectorPlugin = class _ConnectorPlugin {
287
294
  this.tools = config.tools;
288
295
  this.query = config.query;
289
296
  this.checkConnection = config.checkConnection;
297
+ this.setup = config.setup;
298
+ this.skipConnectionCheckOnCreate = config.skipConnectionCheckOnCreate;
290
299
  }
291
300
  get connectorKey() {
292
301
  return _ConnectorPlugin.deriveKey(this.slug, this.authType);
@@ -351,6 +360,76 @@ var ConnectorPlugin = class _ConnectorPlugin {
351
360
  }
352
361
  };
353
362
 
363
+ // ../connectors/src/setup-flow.ts
364
+ async function runSetupFlow(flow, params, ctx, config) {
365
+ const runtime = {
366
+ params,
367
+ language: ctx.language,
368
+ config
369
+ };
370
+ let state = flow.initialState();
371
+ let answerIdx = 0;
372
+ const pendingParameterUpdates = [];
373
+ for (const step of flow.steps) {
374
+ const ans = ctx.answers[answerIdx];
375
+ if (ans && ans.questionSlug === step.slug) {
376
+ state = step.applyAnswer(state, ans.answer);
377
+ if (step.toParameterUpdates) {
378
+ pendingParameterUpdates.push(...step.toParameterUpdates(state));
379
+ }
380
+ answerIdx += 1;
381
+ continue;
382
+ }
383
+ const resolvedAllowFreeText = step.allowFreeText !== void 0 ? step.allowFreeText : true;
384
+ if (step.type === "text") {
385
+ if (step.fetchOptions) {
386
+ const options2 = await step.fetchOptions(state, runtime);
387
+ if (options2.length === 0) {
388
+ continue;
389
+ }
390
+ }
391
+ return {
392
+ type: "nextQuestion",
393
+ questionSlug: step.slug,
394
+ question: step.question[ctx.language],
395
+ questionType: "text",
396
+ allowFreeText: resolvedAllowFreeText,
397
+ ...pendingParameterUpdates.length > 0 && {
398
+ parameterUpdates: pendingParameterUpdates
399
+ }
400
+ };
401
+ }
402
+ const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
403
+ if (options.length === 0) {
404
+ continue;
405
+ }
406
+ return {
407
+ type: "nextQuestion",
408
+ questionSlug: step.slug,
409
+ question: step.question[ctx.language],
410
+ questionType: step.type,
411
+ options,
412
+ allowFreeText: resolvedAllowFreeText,
413
+ ...pendingParameterUpdates.length > 0 && {
414
+ parameterUpdates: pendingParameterUpdates
415
+ }
416
+ };
417
+ }
418
+ const dataInvestigationResult = await flow.finalize(state, runtime);
419
+ return {
420
+ type: "fulfilled",
421
+ dataInvestigationResult,
422
+ ...pendingParameterUpdates.length > 0 && {
423
+ parameterUpdates: pendingParameterUpdates
424
+ }
425
+ };
426
+ }
427
+ async function resolveSetupSelection(params) {
428
+ const { selected, allSentinel, fetchAll, limit } = params;
429
+ const resolved = selected.includes(allSentinel) ? await fetchAll() : selected.filter((v) => v !== allSentinel);
430
+ return resolved.slice(0, limit);
431
+ }
432
+
354
433
  // ../connectors/src/auth-types.ts
355
434
  var AUTH_TYPES = {
356
435
  OAUTH: "oauth",
@@ -393,60 +472,341 @@ var tableauOnboarding = new ConnectorOnboarding({
393
472
  }
394
473
  });
395
474
 
396
- // ../connectors/src/connectors/tableau/tools/request.ts
397
- import { z } from "zod";
475
+ // ../connectors/src/connectors/tableau/utils.ts
398
476
  var DEFAULT_API_VERSION2 = "3.28";
399
- var REQUEST_TIMEOUT_MS = 6e4;
400
- var sessionCache = /* @__PURE__ */ new Map();
401
- function buildBaseUrl(serverUrl, apiVersion) {
477
+ function buildTableauBaseUrl(params) {
478
+ const serverUrl = params[parameters.serverUrl.slug];
479
+ const apiVersion = params[parameters.apiVersion.slug] || DEFAULT_API_VERSION2;
480
+ if (!serverUrl) {
481
+ throw new Error(
482
+ `tableau: missing required parameter: ${parameters.serverUrl.slug}`
483
+ );
484
+ }
402
485
  return `${serverUrl.replace(/\/$/, "")}/api/${apiVersion}`;
403
486
  }
404
- async function signIn(serverUrl, apiVersion, siteContentUrl, patName, patSecret) {
405
- const url = `${buildBaseUrl(serverUrl, apiVersion)}/auth/signin`;
406
- const body = {
407
- credentials: {
408
- personalAccessTokenName: patName,
409
- personalAccessTokenSecret: patSecret,
410
- site: { contentUrl: siteContentUrl }
487
+ async function tableauProxyApiFetch(proxyFetch, params, path2, init) {
488
+ const baseUrl = buildTableauBaseUrl(params);
489
+ const trimmedPath = path2.replace(/^([^/])/, "/$1");
490
+ return proxyFetch(`${baseUrl}${trimmedPath}`, init);
491
+ }
492
+
493
+ // ../connectors/src/connectors/tableau/setup-flow.ts
494
+ var ALL_PROJECTS = "__ALL_PROJECTS__";
495
+ var ALL_WORKBOOKS = "__ALL_WORKBOOKS__";
496
+ var TABLEAU_SETUP_MAX_PROJECTS = 10;
497
+ var MAX_WORKBOOKS_DETAIL = 10;
498
+ var MAX_VIEWS_PER_WORKBOOK = 5;
499
+ var MAX_SAMPLE_ROWS = 5;
500
+ var PAGE_SIZE = 100;
501
+ async function listAllProjects(rt) {
502
+ const all = [];
503
+ let pageNumber = 1;
504
+ while (all.length < 500) {
505
+ const res = await tableauProxyApiFetch(
506
+ rt.config.proxyFetch,
507
+ rt.params,
508
+ `/sites/{siteId}/projects?pageSize=${PAGE_SIZE}&pageNumber=${pageNumber}`
509
+ );
510
+ if (!res.ok) {
511
+ const body = await res.text().catch(() => res.statusText);
512
+ throw new Error(`tableau: listProjects failed (${res.status}): ${body}`);
411
513
  }
412
- };
514
+ const data = await res.json();
515
+ const batch = data.projects?.project ?? [];
516
+ all.push(...batch);
517
+ const total = Number(data.pagination?.totalAvailable ?? "0");
518
+ if (!batch.length || all.length >= total) break;
519
+ pageNumber += 1;
520
+ }
521
+ return all;
522
+ }
523
+ async function listProjectResources(rt, resource) {
524
+ const all = [];
525
+ let pageNumber = 1;
526
+ while (all.length < 1e3) {
527
+ const res = await tableauProxyApiFetch(
528
+ rt.config.proxyFetch,
529
+ rt.params,
530
+ `/sites/{siteId}/${resource}?pageSize=${PAGE_SIZE}&pageNumber=${pageNumber}`
531
+ );
532
+ if (!res.ok) {
533
+ const body = await res.text().catch(() => res.statusText);
534
+ throw new Error(
535
+ `tableau: list ${resource} failed (${res.status}): ${body}`
536
+ );
537
+ }
538
+ const data = await res.json();
539
+ const container = data[resource];
540
+ const batch = (resource === "workbooks" ? container?.workbook : container?.datasource) ?? [];
541
+ all.push(...batch);
542
+ const total = Number(data.pagination?.totalAvailable ?? "0");
543
+ if (!batch.length || all.length >= total) break;
544
+ pageNumber += 1;
545
+ }
546
+ return all;
547
+ }
548
+ async function listViewsForWorkbook(rt, workbookId) {
549
+ const res = await tableauProxyApiFetch(
550
+ rt.config.proxyFetch,
551
+ rt.params,
552
+ `/sites/{siteId}/workbooks/${encodeURIComponent(workbookId)}/views`
553
+ );
554
+ if (!res.ok) return [];
555
+ const data = await res.json();
556
+ return data.views?.view ?? [];
557
+ }
558
+ async function fetchViewDataSample(rt, viewId) {
559
+ try {
560
+ const res = await tableauProxyApiFetch(
561
+ rt.config.proxyFetch,
562
+ rt.params,
563
+ `/sites/{siteId}/views/${encodeURIComponent(viewId)}/data`
564
+ );
565
+ if (!res.ok) return null;
566
+ const csv = await res.text();
567
+ const lines = csv.trim().split("\n").filter(Boolean);
568
+ if (lines.length === 0) return null;
569
+ const columns = parseCsvLine(lines[0]);
570
+ const rows = lines.slice(1, 1 + MAX_SAMPLE_ROWS).map(parseCsvLine);
571
+ return { columns, rows };
572
+ } catch {
573
+ return null;
574
+ }
575
+ }
576
+ function parseCsvLine(line) {
577
+ const result = [];
578
+ let current = "";
579
+ let inQuotes = false;
580
+ for (let i = 0; i < line.length; i++) {
581
+ const ch = line[i];
582
+ if (ch === '"') {
583
+ if (inQuotes && line[i + 1] === '"') {
584
+ current += '"';
585
+ i++;
586
+ } else {
587
+ inQuotes = !inQuotes;
588
+ }
589
+ } else if (ch === "," && !inQuotes) {
590
+ result.push(current.trim());
591
+ current = "";
592
+ } else {
593
+ current += ch;
594
+ }
595
+ }
596
+ result.push(current.trim());
597
+ return result;
598
+ }
599
+ var tableauSetupFlow = {
600
+ initialState: () => ({}),
601
+ steps: [
602
+ {
603
+ slug: "projects",
604
+ type: "multiSelect",
605
+ question: {
606
+ ja: "\u5BFE\u8C61\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\uFF08\u8907\u6570\u9078\u629E\u53EF\uFF09",
607
+ en: "Select target projects (multi-select allowed)"
608
+ },
609
+ async fetchOptions(_state, rt) {
610
+ const projects = await listAllProjects(rt);
611
+ const projectOptions = projects.filter((p) => p.id && p.name).map((p) => ({ value: p.id, label: p.name }));
612
+ return [
613
+ {
614
+ value: ALL_PROJECTS,
615
+ label: rt.language === "ja" ? "\u3059\u3079\u3066\u306E\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8" : "All projects"
616
+ },
617
+ ...projectOptions
618
+ ];
619
+ },
620
+ applyAnswer: (state, answer) => ({ ...state, projects: answer })
621
+ },
622
+ {
623
+ slug: "workbooks",
624
+ type: "multiSelect",
625
+ question: {
626
+ ja: "\u5206\u6790\u5BFE\u8C61\u306E\u30EF\u30FC\u30AF\u30D6\u30C3\u30AF\u3092\u9078\u3093\u3067\u304F\u3060\u3055\u3044\uFF08\u8907\u6570\u9078\u629E\u53EF\uFF09",
627
+ en: "Select the workbooks to analyze (multi-select allowed)"
628
+ },
629
+ async fetchOptions(state, rt) {
630
+ if (!state.projects?.length) return [];
631
+ const allProjects = await listAllProjects(rt);
632
+ const targetIds = await resolveSetupSelection({
633
+ selected: state.projects,
634
+ allSentinel: ALL_PROJECTS,
635
+ fetchAll: async () => allProjects.map((p) => p.id).filter((id) => id),
636
+ limit: TABLEAU_SETUP_MAX_PROJECTS
637
+ });
638
+ const targetSet = new Set(targetIds);
639
+ const workbooks = await listProjectResources(rt, "workbooks");
640
+ const options = workbooks.filter((wb) => wb.id && wb.project?.id && targetSet.has(wb.project.id)).map((wb) => ({
641
+ value: wb.id,
642
+ label: wb.name ?? wb.id ?? "(unknown)"
643
+ }));
644
+ if (options.length === 0) return [];
645
+ return [
646
+ {
647
+ value: ALL_WORKBOOKS,
648
+ label: rt.language === "ja" ? "\u3059\u3079\u3066\u306E\u30EF\u30FC\u30AF\u30D6\u30C3\u30AF" : "All workbooks"
649
+ },
650
+ ...options
651
+ ];
652
+ },
653
+ applyAnswer: (state, answer) => ({ ...state, workbooks: answer })
654
+ }
655
+ ],
656
+ async finalize(state, rt) {
657
+ if (!state.projects) {
658
+ throw new Error("Tableau setup: incomplete state on finalize");
659
+ }
660
+ const allProjects = await listAllProjects(rt);
661
+ const projectById = new Map(allProjects.map((p) => [p.id, p]));
662
+ const targetProjectIds = await resolveSetupSelection({
663
+ selected: state.projects,
664
+ allSentinel: ALL_PROJECTS,
665
+ fetchAll: async () => allProjects.map((p) => p.id).filter((id) => id),
666
+ limit: TABLEAU_SETUP_MAX_PROJECTS
667
+ });
668
+ const sections = ["## Tableau", ""];
669
+ if (!targetProjectIds.length) {
670
+ sections.push("_No projects selected._", "");
671
+ return sections.join("\n");
672
+ }
673
+ const [allWorkbooks, datasources] = await Promise.all([
674
+ listProjectResources(rt, "workbooks"),
675
+ listProjectResources(rt, "datasources")
676
+ ]);
677
+ const projectIdSet = new Set(targetProjectIds);
678
+ const targetWorkbooks = await resolveSetupSelection({
679
+ selected: state.workbooks ?? [],
680
+ allSentinel: ALL_WORKBOOKS,
681
+ fetchAll: async () => allWorkbooks.filter((wb) => wb.id && wb.project?.id && projectIdSet.has(wb.project.id)).map((wb) => wb.id).filter(Boolean),
682
+ limit: MAX_WORKBOOKS_DETAIL
683
+ });
684
+ const targetWorkbookSet = new Set(targetWorkbooks);
685
+ const workbooksByProject = /* @__PURE__ */ new Map();
686
+ for (const wb of allWorkbooks) {
687
+ const pid = wb.project?.id;
688
+ if (!pid || !projectIdSet.has(pid)) continue;
689
+ const bucket = workbooksByProject.get(pid) ?? [];
690
+ bucket.push(wb);
691
+ workbooksByProject.set(pid, bucket);
692
+ }
693
+ const datasourcesByProject = /* @__PURE__ */ new Map();
694
+ for (const ds of datasources) {
695
+ const pid = ds.project?.id;
696
+ if (!pid || !projectIdSet.has(pid)) continue;
697
+ const bucket = datasourcesByProject.get(pid) ?? [];
698
+ bucket.push(ds);
699
+ datasourcesByProject.set(pid, bucket);
700
+ }
701
+ const allDiscoveredColumns = [];
702
+ for (const pid of targetProjectIds) {
703
+ const project = projectById.get(pid);
704
+ sections.push(`### Project: ${project?.name ?? pid}`, "");
705
+ const projectWorkbooks = workbooksByProject.get(pid) ?? [];
706
+ for (const wb of projectWorkbooks) {
707
+ if (!wb.id) continue;
708
+ const isDetailed = targetWorkbookSet.has(wb.id);
709
+ sections.push(`#### Workbook: ${wb.name ?? wb.id}`, "");
710
+ if (!isDetailed) continue;
711
+ const views = await listViewsForWorkbook(rt, wb.id);
712
+ if (views.length === 0) {
713
+ sections.push("_No views found._", "");
714
+ continue;
715
+ }
716
+ for (const view of views.slice(0, MAX_VIEWS_PER_WORKBOOK)) {
717
+ if (!view.id) continue;
718
+ sections.push(`##### View: ${view.name ?? view.id}`, "");
719
+ const sample = await fetchViewDataSample(rt, view.id);
720
+ if (!sample || sample.columns.length === 0) {
721
+ sections.push("_Data not available._", "");
722
+ continue;
723
+ }
724
+ allDiscoveredColumns.push(...sample.columns);
725
+ sections.push(`| ${sample.columns.join(" | ")} |`);
726
+ sections.push(`|${sample.columns.map(() => "---").join("|")}|`);
727
+ for (const row of sample.rows) {
728
+ const cells = row.map((c) => c.replace(/\|/g, "\\|"));
729
+ sections.push(`| ${cells.join(" | ")} |`);
730
+ }
731
+ sections.push("");
732
+ }
733
+ }
734
+ const projectDatasources = datasourcesByProject.get(pid) ?? [];
735
+ if (projectDatasources.length > 0) {
736
+ sections.push(`#### Datasources (${projectDatasources.length})`, "");
737
+ for (const ds of projectDatasources.slice(0, 25)) {
738
+ sections.push(`- ${ds.name ?? ds.id ?? "(unknown)"}`);
739
+ }
740
+ sections.push("");
741
+ }
742
+ }
743
+ if (allDiscoveredColumns.length > 0) {
744
+ const unique = [...new Set(allDiscoveredColumns.map((c) => c.toLowerCase()))];
745
+ const themes = [];
746
+ const hasPattern = (keywords) => unique.some((c) => keywords.some((k) => c.includes(k)));
747
+ if (hasPattern(["revenue", "sales", "amount", "price", "cost", "profit", "\u58F2\u4E0A", "\u91D1\u984D", "\u5229\u76CA"]))
748
+ themes.push("Revenue & profitability analysis (trends, breakdown by segment)");
749
+ if (hasPattern(["date", "month", "year", "quarter", "day", "\u65E5\u4ED8", "\u6708", "\u5E74", "\u671F"]))
750
+ themes.push("Time-series trend analysis (YoY, MoM comparisons)");
751
+ if (hasPattern(["region", "country", "city", "state", "\u5730\u57DF", "\u56FD", "\u90FD\u5E02", "\u5E02"]))
752
+ themes.push("Geographic/regional performance comparison");
753
+ if (hasPattern(["product", "category", "item", "sku", "\u5546\u54C1", "\u30AB\u30C6\u30B4\u30EA"]))
754
+ themes.push("Product/category mix analysis");
755
+ if (hasPattern(["customer", "user", "account", "\u9867\u5BA2", "\u30E6\u30FC\u30B6\u30FC"]))
756
+ themes.push("Customer segmentation and behavior analysis");
757
+ if (hasPattern(["quantity", "count", "volume", "\u6570\u91CF", "\u4EF6\u6570"]))
758
+ themes.push("Volume and demand pattern analysis");
759
+ if (hasPattern(["rate", "ratio", "percentage", "%", "\u7387"]))
760
+ themes.push("KPI ratio tracking and benchmarking");
761
+ if (themes.length > 0) {
762
+ sections.push("### Suggested Analysis Themes", "");
763
+ for (const theme of themes) {
764
+ sections.push(`- ${theme}`);
765
+ }
766
+ sections.push("");
767
+ }
768
+ }
769
+ return sections.join("\n");
770
+ }
771
+ };
772
+
773
+ // ../connectors/src/connectors/tableau/tools/request.ts
774
+ import { z } from "zod";
775
+ var DEFAULT_API_VERSION3 = "3.28";
776
+ var REQUEST_TIMEOUT_MS = 6e4;
777
+ var cachedToken = null;
778
+ async function getProxyToken(config) {
779
+ if (cachedToken && cachedToken.expiresAt > Date.now() + 6e4) {
780
+ return cachedToken.token;
781
+ }
782
+ const url = `${config.appApiBaseUrl}/v0/database/${config.projectId}/environment/${config.environmentId}/oauth-request-proxy-token`;
413
783
  const res = await fetch(url, {
414
784
  method: "POST",
415
785
  headers: {
416
786
  "Content-Type": "application/json",
417
- Accept: "application/json"
787
+ "x-api-key": config.appApiKey,
788
+ "project-id": config.projectId
418
789
  },
419
- body: JSON.stringify(body)
790
+ body: JSON.stringify({
791
+ sandboxId: config.sandboxId,
792
+ issuedBy: "coding-agent"
793
+ })
420
794
  });
421
795
  if (!res.ok) {
422
796
  const errorText = await res.text().catch(() => res.statusText);
423
797
  throw new Error(
424
- `Tableau sign-in failed: HTTP ${res.status} ${errorText}`
798
+ `Failed to get proxy token: HTTP ${res.status} ${errorText}`
425
799
  );
426
800
  }
427
801
  const data = await res.json();
428
- return {
429
- authToken: data.credentials.token,
430
- siteId: data.credentials.site.id,
431
- userId: data.credentials.user.id,
432
- expiresAt: Date.now() + 30 * 60 * 1e3
802
+ cachedToken = {
803
+ token: data.token,
804
+ expiresAt: new Date(data.expiresAt).getTime()
433
805
  };
806
+ return data.token;
434
807
  }
435
- async function getSession(serverUrl, apiVersion, siteContentUrl, patName, patSecret) {
436
- const cacheKey = `${serverUrl}|${siteContentUrl}|${patName}`;
437
- const cached = sessionCache.get(cacheKey);
438
- if (cached && cached.expiresAt > Date.now() + 6e4) {
439
- return cached;
440
- }
441
- const session = await signIn(
442
- serverUrl,
443
- apiVersion,
444
- siteContentUrl,
445
- patName,
446
- patSecret
447
- );
448
- sessionCache.set(cacheKey, session);
449
- return session;
808
+ function buildBaseUrl(serverUrl, apiVersion) {
809
+ return `${serverUrl.replace(/\/$/, "")}/api/${apiVersion}`;
450
810
  }
451
811
  var inputSchema = z.object({
452
812
  toolUseIntent: z.string().optional().describe(
@@ -480,12 +840,12 @@ var outputSchema = z.discriminatedUnion("success", [
480
840
  var requestTool = new ConnectorTool({
481
841
  name: "request",
482
842
  description: `Send authenticated requests to the Tableau REST API.
483
- The tool signs in once with the configured Personal Access Token (PAT) to obtain an X-Tableau-Auth token (cached per connection), then forwards the request with the token attached.
484
- All paths are relative to {serverUrl}/api/{apiVersion}. Use the literal placeholder \`{siteId}\` in the path \u2014 it is automatically substituted with the site ID returned from sign-in.
843
+ The X-Tableau-Auth token is issued and managed centrally by the Squadbase backend so concurrent processes (chat & deployed app) share a single PAT sign-in.
844
+ All paths are relative to {serverUrl}/api/{apiVersion}. Use the literal placeholder \`{siteId}\` in the path \u2014 it is automatically substituted with the signed-in site ID.
485
845
  Accept and Content-Type headers default to application/json so list responses come back as JSON instead of Tableau's default XML.`,
486
846
  inputSchema,
487
847
  outputSchema,
488
- async execute({ connectionId, method, path: path2, queryParams, body }, connections) {
848
+ async execute({ connectionId, method, path: path2, queryParams, body }, connections, config) {
489
849
  const connection2 = connections.find((c) => c.id === connectionId);
490
850
  if (!connection2) {
491
851
  return {
@@ -498,39 +858,31 @@ Accept and Content-Type headers default to application/json so list responses co
498
858
  );
499
859
  try {
500
860
  const serverUrl = parameters.serverUrl.getValue(connection2);
501
- const siteContentUrl = parameters.siteContentUrl.getValue(connection2);
502
- const patName = parameters.patName.getValue(connection2);
503
- const patSecret = parameters.patSecret.getValue(connection2);
504
- const apiVersion = parameters.apiVersion.tryGetValue(connection2) || DEFAULT_API_VERSION2;
505
- const session = await getSession(
506
- serverUrl,
507
- apiVersion,
508
- siteContentUrl,
509
- patName,
510
- patSecret
511
- );
512
- const resolvedPath = path2.trim().replace(/\{siteId\}/g, session.siteId).replace(/^([^/])/, "/$1");
513
- let url = `${buildBaseUrl(serverUrl, apiVersion)}${resolvedPath}`;
861
+ const apiVersion = parameters.apiVersion.tryGetValue(connection2) || DEFAULT_API_VERSION3;
862
+ const trimmedPath = path2.trim().replace(/^([^/])/, "/$1");
863
+ const baseUrl = buildBaseUrl(serverUrl, apiVersion);
864
+ let url = `${baseUrl}${trimmedPath}`;
514
865
  if (queryParams) {
515
- const searchParams = new URLSearchParams(queryParams);
516
- url += `?${searchParams.toString()}`;
866
+ url += `?${new URLSearchParams(queryParams).toString()}`;
517
867
  }
868
+ const token = await getProxyToken(config.oauthProxy);
869
+ const proxyUrl = `https://${config.oauthProxy.sandboxId}.${config.oauthProxy.previewBaseDomain}/_sqcore/connections/${connectionId}/request`;
518
870
  const controller = new AbortController();
519
871
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
520
872
  try {
521
- const init = {
522
- method,
873
+ const response = await fetch(proxyUrl, {
874
+ method: "POST",
523
875
  headers: {
524
- "X-Tableau-Auth": session.authToken,
525
- Accept: "application/json",
526
- "Content-Type": "application/json"
876
+ "Content-Type": "application/json",
877
+ Authorization: `Bearer ${token}`
527
878
  },
879
+ body: JSON.stringify({
880
+ url,
881
+ method,
882
+ ...body !== void 0 ? { body } : {}
883
+ }),
528
884
  signal: controller.signal
529
- };
530
- if (body !== void 0) {
531
- init.body = JSON.stringify(body);
532
- }
533
- const response = await fetch(url, init);
885
+ });
534
886
  const text = await response.text();
535
887
  const data = text ? (() => {
536
888
  try {
@@ -569,7 +921,7 @@ var tableauConnector = new ConnectorPlugin({
569
921
  systemPrompt: {
570
922
  en: `### Tools
571
923
 
572
- - \`tableau_request\`: The only way to call the Tableau REST API. Use it for projects, workbooks, views, data sources, users, and permissions. The tool signs in once with the configured PAT to obtain an \`X-Tableau-Auth\` token (cached), then forwards each request with the token attached. Use the literal \`{siteId}\` placeholder in the path \u2014 it is replaced with the session's site ID automatically.
924
+ - \`tableau_request\`: The only way to call the Tableau REST API. Use it for projects, workbooks, views, data sources, users, and permissions. The \`X-Tableau-Auth\` token is issued and managed centrally by the Squadbase backend so concurrent processes (chat & deployed app) share a single PAT sign-in. Use the literal \`{siteId}\` placeholder in the path \u2014 it is replaced with the session's site ID automatically.
573
925
 
574
926
  ### Business Logic
575
927
 
@@ -626,7 +978,7 @@ export default async function handler(c: Context) {
626
978
  - Combine: \`filter=ownerName:eq:alice,tags:in:[finance]\``,
627
979
  ja: `### \u30C4\u30FC\u30EB
628
980
 
629
- - \`tableau_request\`: Tableau REST API \u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3001\u30EF\u30FC\u30AF\u30D6\u30C3\u30AF\u3001\u30D3\u30E5\u30FC\u3001\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u3001\u30E6\u30FC\u30B6\u30FC\u3001\u6A29\u9650\u306A\u3069\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u8A2D\u5B9A\u3055\u308C\u305F PAT \u3067\u4E00\u5EA6\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u3066 \`X-Tableau-Auth\` \u30C8\u30FC\u30AF\u30F3\u3092\u53D6\u5F97 (\u30AD\u30E3\u30C3\u30B7\u30E5) \u3057\u3001\u5404\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u305D\u306E\u30C8\u30FC\u30AF\u30F3\u3092\u4ED8\u3051\u3066\u9001\u4FE1\u3057\u307E\u3059\u3002\u30D1\u30B9\u5185\u306B\u306F \`{siteId}\` \u30D7\u30EC\u30FC\u30B9\u30DB\u30EB\u30C0\u30FC\u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044 \u2014 \u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u30B5\u30A4\u30C8 ID \u3067\u81EA\u52D5\u7F6E\u63DB\u3055\u308C\u307E\u3059\u3002
981
+ - \`tableau_request\`: Tableau REST API \u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3001\u30EF\u30FC\u30AF\u30D6\u30C3\u30AF\u3001\u30D3\u30E5\u30FC\u3001\u30C7\u30FC\u30BF\u30BD\u30FC\u30B9\u3001\u30E6\u30FC\u30B6\u30FC\u3001\u6A29\u9650\u306A\u3069\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\`X-Tableau-Auth\` \u30C8\u30FC\u30AF\u30F3\u306F Squadbase \u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u3067\u4E00\u5143\u7BA1\u7406\u3055\u308C\u3001\u8907\u6570\u30D7\u30ED\u30BB\u30B9 (\u30C1\u30E3\u30C3\u30C8\u30FB\u30C7\u30D7\u30ED\u30A4\u6E08\u307F\u30A2\u30D7\u30EA) \u3067\u540C\u3058 PAT \u30B5\u30A4\u30F3\u30A4\u30F3\u3092\u5171\u6709\u3057\u307E\u3059\u3002\u30D1\u30B9\u5185\u306B\u306F \`{siteId}\` \u30D7\u30EC\u30FC\u30B9\u30DB\u30EB\u30C0\u30FC\u3092\u4F7F\u3063\u3066\u304F\u3060\u3055\u3044 \u2014 \u30BB\u30C3\u30B7\u30E7\u30F3\u306E\u30B5\u30A4\u30C8 ID \u3067\u81EA\u52D5\u7F6E\u63DB\u3055\u308C\u307E\u3059\u3002
630
982
 
631
983
  ### Business Logic
632
984
 
@@ -683,6 +1035,7 @@ export default async function handler(c: Context) {
683
1035
  - \u7D44\u307F\u5408\u308F\u305B: \`filter=ownerName:eq:alice,tags:in:[finance]\``
684
1036
  },
685
1037
  tools,
1038
+ setup: (params, ctx, config) => runSetupFlow(tableauSetupFlow, params, ctx, config),
686
1039
  async checkConnection(params) {
687
1040
  const serverUrl = params[parameters.serverUrl.slug];
688
1041
  const siteContentUrl = params[parameters.siteContentUrl.slug];
@@ -756,6 +1109,7 @@ function resolveEnvVarOptional(entry, key) {
756
1109
  import { getContext } from "hono/context-storage";
757
1110
  import { getCookie } from "hono/cookie";
758
1111
  var APP_SESSION_COOKIE_NAME = "__Host-squadbase-session";
1112
+ var TABLEAU_SESSION_SENTINEL_URL2 = "squadbase://tableau-session/";
759
1113
  function normalizeHeaders(input) {
760
1114
  const out = {};
761
1115
  if (!input) return out;
@@ -764,6 +1118,11 @@ function normalizeHeaders(input) {
764
1118
  });
765
1119
  return out;
766
1120
  }
1121
+ function extractInputUrl(input) {
1122
+ if (typeof input === "string") return input;
1123
+ if (input instanceof URL) return input.href;
1124
+ return input.url;
1125
+ }
767
1126
  function createSandboxProxyFetch(connectionId) {
768
1127
  return async (input, init) => {
769
1128
  const token = process.env.INTERNAL_SQUADBASE_OAUTH_MACHINE_CREDENTIAL;
@@ -773,10 +1132,17 @@ function createSandboxProxyFetch(connectionId) {
773
1132
  "Connection proxy is not configured. Please check your deployment settings."
774
1133
  );
775
1134
  }
776
- const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
1135
+ const originalUrl = extractInputUrl(input);
1136
+ const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
1137
+ if (originalUrl === TABLEAU_SESSION_SENTINEL_URL2) {
1138
+ const sessionUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
1139
+ return fetch(sessionUrl, {
1140
+ method: "POST",
1141
+ headers: { Authorization: `Bearer ${token}` }
1142
+ });
1143
+ }
777
1144
  const originalMethod = init?.method ?? "GET";
778
1145
  const originalBody = init?.body ? JSON.parse(init.body) : void 0;
779
- const baseDomain = process.env["SQUADBASE_PREVIEW_BASE_DOMAIN"] ?? "preview.app.squadbase.dev";
780
1146
  const proxyUrl = `https://${sandboxId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
781
1147
  return fetch(proxyUrl, {
782
1148
  method: "POST",
@@ -802,10 +1168,9 @@ function createDeployedAppProxyFetch(connectionId) {
802
1168
  }
803
1169
  const baseDomain = process.env["SQUADBASE_APP_BASE_DOMAIN"] ?? "squadbase.app";
804
1170
  const proxyUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/request`;
1171
+ const sessionUrl = `https://${projectId}.${baseDomain}/_sqcore/connections/${connectionId}/tableau-session`;
805
1172
  return async (input, init) => {
806
- const originalUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
807
- const originalMethod = init?.method ?? "GET";
808
- const originalBody = init?.body ? JSON.parse(init.body) : void 0;
1173
+ const originalUrl = extractInputUrl(input);
809
1174
  const c = getContext();
810
1175
  const appSession = getCookie(c, APP_SESSION_COOKIE_NAME);
811
1176
  if (!appSession) {
@@ -813,6 +1178,14 @@ function createDeployedAppProxyFetch(connectionId) {
813
1178
  "No authentication method available for connection proxy."
814
1179
  );
815
1180
  }
1181
+ if (originalUrl === TABLEAU_SESSION_SENTINEL_URL2) {
1182
+ return fetch(sessionUrl, {
1183
+ method: "POST",
1184
+ headers: { Authorization: `Bearer ${appSession}` }
1185
+ });
1186
+ }
1187
+ const originalMethod = init?.method ?? "GET";
1188
+ const originalBody = init?.body ? JSON.parse(init.body) : void 0;
816
1189
  return fetch(proxyUrl, {
817
1190
  method: "POST",
818
1191
  headers: {