@squadbase/vite-server 0.1.17-dev.24af54e → 0.1.17-dev.423ee34

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 +4873 -1073
  2. package/dist/connectors/airtable-oauth.js +78 -11
  3. package/dist/connectors/airtable.js +74 -11
  4. package/dist/connectors/amplitude.js +38 -11
  5. package/dist/connectors/anthropic.js +4 -2
  6. package/dist/connectors/asana.js +67 -13
  7. package/dist/connectors/attio.js +60 -16
  8. package/dist/connectors/aws-billing.js +38 -11
  9. package/dist/connectors/azure-sql.js +64 -13
  10. package/dist/connectors/backlog-api-key.js +70 -18
  11. package/dist/connectors/clickup.js +80 -13
  12. package/dist/connectors/cosmosdb.js +42 -15
  13. package/dist/connectors/customerio.js +39 -12
  14. package/dist/connectors/dbt.js +716 -28
  15. package/dist/connectors/freshdesk.js +112 -11
  16. package/dist/connectors/freshsales.js +38 -11
  17. package/dist/connectors/freshservice.js +38 -11
  18. package/dist/connectors/gamma.js +47 -20
  19. package/dist/connectors/gemini.js +4 -2
  20. package/dist/connectors/github.js +42 -15
  21. package/dist/connectors/gmail-oauth.js +38 -13
  22. package/dist/connectors/gmail.js +34 -7
  23. package/dist/connectors/google-ads.js +38 -11
  24. package/dist/connectors/google-analytics-oauth.js +182 -28
  25. package/dist/connectors/google-analytics.js +653 -104
  26. package/dist/connectors/google-audit-log.js +34 -7
  27. package/dist/connectors/google-calendar-oauth.js +91 -18
  28. package/dist/connectors/google-calendar.js +91 -14
  29. package/dist/connectors/google-docs.js +38 -13
  30. package/dist/connectors/google-drive.js +60 -13
  31. package/dist/connectors/google-search-console-oauth.js +156 -20
  32. package/dist/connectors/google-sheets.js +36 -9
  33. package/dist/connectors/google-slides.js +38 -13
  34. package/dist/connectors/grafana.js +75 -13
  35. package/dist/connectors/hubspot-oauth.js +69 -12
  36. package/dist/connectors/hubspot.js +55 -12
  37. package/dist/connectors/influxdb.js +38 -11
  38. package/dist/connectors/intercom-oauth.js +100 -15
  39. package/dist/connectors/intercom.js +42 -15
  40. package/dist/connectors/jdbc.js +36 -9
  41. package/dist/connectors/jira-api-key.js +98 -14
  42. package/dist/connectors/kintone-api-token.js +96 -21
  43. package/dist/connectors/kintone.js +84 -14
  44. package/dist/connectors/linear.js +84 -15
  45. package/dist/connectors/linkedin-ads.js +71 -17
  46. package/dist/connectors/mailchimp-oauth.js +36 -9
  47. package/dist/connectors/mailchimp.js +36 -9
  48. package/dist/connectors/meta-ads-oauth.js +63 -17
  49. package/dist/connectors/meta-ads.js +65 -17
  50. package/dist/connectors/mixpanel.js +38 -11
  51. package/dist/connectors/monday.js +39 -12
  52. package/dist/connectors/mongodb.js +38 -11
  53. package/dist/connectors/notion-oauth.js +88 -14
  54. package/dist/connectors/notion.js +90 -14
  55. package/dist/connectors/openai.js +4 -2
  56. package/dist/connectors/oracle.js +78 -20
  57. package/dist/connectors/outlook-oauth.js +48 -23
  58. package/dist/connectors/powerbi-oauth.js +321 -49
  59. package/dist/connectors/salesforce.js +72 -12
  60. package/dist/connectors/semrush.js +374 -52
  61. package/dist/connectors/sentry.js +66 -13
  62. package/dist/connectors/shopify-oauth.js +71 -13
  63. package/dist/connectors/shopify.js +38 -11
  64. package/dist/connectors/sqlserver.js +64 -13
  65. package/dist/connectors/stripe-api-key.js +96 -18
  66. package/dist/connectors/stripe-oauth.js +98 -22
  67. package/dist/connectors/supabase.js +55 -11
  68. package/dist/connectors/tableau.js +262 -92
  69. package/dist/connectors/tiktok-ads.js +67 -19
  70. package/dist/connectors/wix-store.js +38 -11
  71. package/dist/connectors/zendesk-oauth.js +83 -15
  72. package/dist/connectors/zendesk.js +42 -15
  73. package/dist/index.d.ts +1 -0
  74. package/dist/index.js +4902 -1077
  75. package/dist/main.js +4891 -1071
  76. package/dist/vite-plugin.js +4871 -1071
  77. package/package.json +1 -1
@@ -16,6 +16,7 @@ var init_parameter_definition = __esm({
16
16
  type;
17
17
  secret;
18
18
  required;
19
+ isDeprecated;
19
20
  constructor(config) {
20
21
  this.slug = config.slug;
21
22
  this.name = config.name;
@@ -24,6 +25,7 @@ var init_parameter_definition = __esm({
24
25
  this.type = config.type;
25
26
  this.secret = config.secret;
26
27
  this.required = config.required;
28
+ this.isDeprecated = config.isDeprecated ?? false;
27
29
  }
28
30
  /**
29
31
  * Get the parameter value from a ConnectorConnectionObject.
@@ -71,11 +73,12 @@ var parameters = {
71
73
  propertyId: new ParameterDefinition({
72
74
  slug: "property-id",
73
75
  name: "Google Analytics Property ID",
74
- description: "The Google Analytics 4 property ID (e.g., 123456789).",
76
+ description: "The Google Analytics 4 property ID (e.g., 123456789). Automatically set during the setup flow.",
75
77
  envVarBaseKey: "GA_PROPERTY_ID",
76
78
  type: "text",
77
79
  secret: false,
78
- required: true
80
+ required: false,
81
+ isDeprecated: true
79
82
  })
80
83
  };
81
84
 
@@ -107,15 +110,10 @@ function buildJwt(clientEmail, privateKey, nowSec) {
107
110
  }
108
111
  function createClient(params) {
109
112
  const serviceAccountKeyJsonBase64 = params[parameters.serviceAccountKeyJsonBase64.slug];
110
- const propertyId = params[parameters.propertyId.slug];
111
- if (!serviceAccountKeyJsonBase64 || !propertyId) {
112
- const required = [
113
- parameters.serviceAccountKeyJsonBase64.slug,
114
- parameters.propertyId.slug
115
- ];
116
- const missing = required.filter((s) => !params[s]);
113
+ const defaultPropertyId = params["property-id"] ?? "";
114
+ if (!serviceAccountKeyJsonBase64) {
117
115
  throw new Error(
118
- `google-analytics: missing required parameters: ${missing.join(", ")}`
116
+ `google-analytics: missing required parameters: ${parameters.serviceAccountKeyJsonBase64.slug}`
119
117
  );
120
118
  }
121
119
  let serviceAccountKey;
@@ -169,19 +167,23 @@ function createClient(params) {
169
167
  return {
170
168
  async request(path2, init) {
171
169
  const accessToken = await getAccessToken2();
172
- const resolvedPath = path2.replace(/\{propertyId\}/g, propertyId);
173
- const url = `${BASE_URL.replace(/\/+$/, "")}/${resolvedPath.replace(/^\/+/, "")}`;
170
+ const url = `${BASE_URL.replace(/\/+$/, "")}/${path2.replace(/^\/+/, "")}`;
174
171
  const headers = new Headers(init?.headers);
175
172
  headers.set("Authorization", `Bearer ${accessToken}`);
176
173
  return fetch(url, { ...init, headers });
177
174
  },
178
175
  async runReport(request) {
176
+ const { propertyId: pid, ...body } = request;
177
+ const propertyId = pid || defaultPropertyId;
178
+ if (!propertyId) {
179
+ throw new Error("google-analytics: propertyId is required in runReport request");
180
+ }
179
181
  const response = await this.request(
180
182
  `properties/${propertyId}:runReport`,
181
183
  {
182
184
  method: "POST",
183
185
  headers: { "Content-Type": "application/json" },
184
- body: JSON.stringify(request)
186
+ body: JSON.stringify(body)
185
187
  }
186
188
  );
187
189
  if (!response.ok) {
@@ -196,7 +198,11 @@ function createClient(params) {
196
198
  rowCount: data.rowCount ?? 0
197
199
  };
198
200
  },
199
- async getMetadata() {
201
+ async getMetadata(request) {
202
+ const propertyId = request.propertyId || defaultPropertyId;
203
+ if (!propertyId) {
204
+ throw new Error("google-analytics: propertyId is required in getMetadata request");
205
+ }
200
206
  const response = await this.request(
201
207
  `properties/${propertyId}/metadata`,
202
208
  { method: "GET" }
@@ -210,12 +216,17 @@ function createClient(params) {
210
216
  return await response.json();
211
217
  },
212
218
  async runRealtimeReport(request) {
219
+ const { propertyId: pid, ...body } = request;
220
+ const propertyId = pid || defaultPropertyId;
221
+ if (!propertyId) {
222
+ throw new Error("google-analytics: propertyId is required in runRealtimeReport request");
223
+ }
213
224
  const response = await this.request(
214
225
  `properties/${propertyId}:runRealtimeReport`,
215
226
  {
216
227
  method: "POST",
217
228
  headers: { "Content-Type": "application/json" },
218
- body: JSON.stringify(request)
229
+ body: JSON.stringify(body)
219
230
  }
220
231
  );
221
232
  if (!response.ok) {
@@ -339,7 +350,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
339
350
  /**
340
351
  * Create tools for connections that belong to this connector.
341
352
  * Filters connections by connectorKey internally.
342
- * Returns tools keyed as `${connectorKey}_${toolName}`.
353
+ * Returns tools keyed as `connector_${connectorKey}_${toolName}`.
343
354
  */
344
355
  createTools(connections, config, opts) {
345
356
  const myConnections = connections.filter(
@@ -349,7 +360,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
349
360
  for (const t of Object.values(this.tools)) {
350
361
  const tool = t.createTool(myConnections, config);
351
362
  const originalToModelOutput = tool.toModelOutput;
352
- result[`${this.connectorKey}_${t.name}`] = {
363
+ result[`connector_${this.connectorKey}_${t.name}`] = {
353
364
  ...tool,
354
365
  toModelOutput: async (options) => {
355
366
  if (!originalToModelOutput) {
@@ -405,19 +416,34 @@ async function runSetupFlow(flow, params, ctx, config) {
405
416
  };
406
417
  let state = flow.initialState();
407
418
  let answerIdx = 0;
419
+ const pendingParameterUpdates = [];
408
420
  for (const step of flow.steps) {
409
421
  const ans = ctx.answers[answerIdx];
410
422
  if (ans && ans.questionSlug === step.slug) {
411
423
  state = step.applyAnswer(state, ans.answer);
424
+ if (step.toParameterUpdates) {
425
+ pendingParameterUpdates.push(...step.toParameterUpdates(state));
426
+ }
412
427
  answerIdx += 1;
413
428
  continue;
414
429
  }
430
+ const resolvedAllowFreeText = step.allowFreeText !== void 0 ? step.allowFreeText : true;
415
431
  if (step.type === "text") {
432
+ if (step.fetchOptions) {
433
+ const options2 = await step.fetchOptions(state, runtime);
434
+ if (options2.length === 0) {
435
+ continue;
436
+ }
437
+ }
416
438
  return {
417
439
  type: "nextQuestion",
418
440
  questionSlug: step.slug,
419
441
  question: step.question[ctx.language],
420
- questionType: "text"
442
+ questionType: "text",
443
+ allowFreeText: resolvedAllowFreeText,
444
+ ...pendingParameterUpdates.length > 0 && {
445
+ parameterUpdates: pendingParameterUpdates
446
+ }
421
447
  };
422
448
  }
423
449
  const options = step.fetchOptions ? await step.fetchOptions(state, runtime) : [];
@@ -429,11 +455,21 @@ async function runSetupFlow(flow, params, ctx, config) {
429
455
  questionSlug: step.slug,
430
456
  question: step.question[ctx.language],
431
457
  questionType: step.type,
432
- options
458
+ options,
459
+ allowFreeText: resolvedAllowFreeText,
460
+ ...pendingParameterUpdates.length > 0 && {
461
+ parameterUpdates: pendingParameterUpdates
462
+ }
433
463
  };
434
464
  }
435
465
  const dataInvestigationResult = await flow.finalize(state, runtime);
436
- return { type: "fulfilled", dataInvestigationResult };
466
+ return {
467
+ type: "fulfilled",
468
+ dataInvestigationResult,
469
+ ...pendingParameterUpdates.length > 0 && {
470
+ parameterUpdates: pendingParameterUpdates
471
+ }
472
+ };
437
473
  }
438
474
  async function resolveSetupSelection(params) {
439
475
  const { selected, allSentinel, fetchAll, limit } = params;
@@ -454,10 +490,10 @@ var AUTH_TYPES = {
454
490
  // ../connectors/src/connectors/google-analytics/setup.ts
455
491
  var googleAnalyticsOnboarding = new ConnectorOnboarding({
456
492
  dataOverviewInstructions: {
457
- en: `1. Call google-analytics-service-account_request with GET properties/{propertyId}/metadata to list available dimensions and metrics
458
- 2. Call google-analytics-service-account_request with POST properties/{propertyId}:runReport using a small date range and basic metrics to verify data availability`,
459
- ja: `1. google-analytics-service-account_request \u3067 GET properties/{propertyId}/metadata \u3092\u547C\u3073\u51FA\u3057\u3001\u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3\u3068\u30E1\u30C8\u30EA\u30AF\u30B9\u306E\u4E00\u89A7\u3092\u53D6\u5F97
460
- 2. google-analytics-service-account_request \u3067 POST properties/{propertyId}:runReport \u3092\u77ED\u3044\u671F\u9593\u3068\u57FA\u672C\u30E1\u30C8\u30EA\u30AF\u30B9\u3067\u547C\u3073\u51FA\u3057\u3001\u30C7\u30FC\u30BF\u306E\u53EF\u7528\u6027\u3092\u78BA\u8A8D`
493
+ en: `1. Call connector_google-analytics-service-account_request with GET properties/{propertyId}/metadata to list available dimensions and metrics
494
+ 2. Call connector_google-analytics-service-account_request with POST properties/{propertyId}:runReport using a small date range and basic metrics to verify data availability`,
495
+ ja: `1. connector_google-analytics-service-account_request \u3067 GET properties/{propertyId}/metadata \u3092\u547C\u3073\u51FA\u3057\u3001\u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3\u3068\u30E1\u30C8\u30EA\u30AF\u30B9\u306E\u4E00\u89A7\u3092\u53D6\u5F97
496
+ 2. connector_google-analytics-service-account_request \u3067 POST properties/{propertyId}:runReport \u3092\u77ED\u3044\u671F\u9593\u3068\u57FA\u672C\u30E1\u30C8\u30EA\u30AF\u30B9\u3067\u547C\u3073\u51FA\u3057\u3001\u30C7\u30FC\u30BF\u306E\u53EF\u7528\u6027\u3092\u78BA\u8A8D`
461
497
  }
462
498
  });
463
499
 
@@ -465,6 +501,7 @@ var googleAnalyticsOnboarding = new ConnectorOnboarding({
465
501
  import * as crypto2 from "crypto";
466
502
  var TOKEN_URL2 = "https://oauth2.googleapis.com/token";
467
503
  var ADMIN_BASE_URL = "https://analyticsadmin.googleapis.com/v1beta";
504
+ var DATA_BASE_URL = "https://analyticsdata.googleapis.com/v1beta";
468
505
  var SCOPE2 = "https://www.googleapis.com/auth/analytics.readonly";
469
506
  function base64url2(input) {
470
507
  const buf = typeof input === "string" ? Buffer.from(input) : input;
@@ -546,10 +583,35 @@ async function adminFetch(params, path2, init) {
546
583
  headers.set("Authorization", `Bearer ${accessToken}`);
547
584
  return fetch(url, { ...init, headers });
548
585
  }
586
+ async function dataFetch(params, path2, init) {
587
+ const keyJsonBase64 = params[parameters.serviceAccountKeyJsonBase64.slug];
588
+ if (!keyJsonBase64) {
589
+ throw new Error(
590
+ "google-analytics: missing required parameter: service-account-key-json-base64"
591
+ );
592
+ }
593
+ const serviceAccountKey = decodeServiceAccount(keyJsonBase64);
594
+ const accessToken = await getAccessToken(serviceAccountKey);
595
+ const url = `${DATA_BASE_URL}${path2.startsWith("/") ? "" : "/"}${path2}`;
596
+ const headers = new Headers(init?.headers);
597
+ headers.set("Authorization", `Bearer ${accessToken}`);
598
+ return fetch(url, { ...init, headers });
599
+ }
549
600
 
550
601
  // ../connectors/src/connectors/google-analytics/setup-flow.ts
551
602
  var ALL_PROPERTIES = "__ALL_PROPERTIES__";
552
603
  var GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES = 20;
604
+ var MAX_PROPERTIES_TO_EXPLORE = 2;
605
+ var TOP_N_SOURCES = 8;
606
+ var TOP_N_COUNTRIES = 8;
607
+ var TOP_N_BROWSERS = 5;
608
+ var TOP_N_PAGES = 10;
609
+ var TOP_N_EVENTS = 10;
610
+ var STANDARD_METADATA_LIMIT = 25;
611
+ var CUSTOM_METADATA_LIMIT = 15;
612
+ var DESCRIPTION_MAX = 140;
613
+ var PAGE_PATH_MAX = 80;
614
+ var EVENT_NAME_MAX = 60;
553
615
  async function listAccountSummaries(params) {
554
616
  const summaries = [];
555
617
  let pageToken;
@@ -572,9 +634,401 @@ function propertyIdFromResource(resource) {
572
634
  return (resource ?? "").replace(/^properties\//, "");
573
635
  }
574
636
  async function getProperty(params, propertyId) {
575
- const res = await adminFetch(params, `/properties/${propertyId}`);
576
- if (!res.ok) return null;
577
- return await res.json();
637
+ try {
638
+ const res = await adminFetch(params, `/properties/${propertyId}`);
639
+ if (!res.ok) return null;
640
+ return await res.json();
641
+ } catch {
642
+ return null;
643
+ }
644
+ }
645
+ async function getMetadata(params, propertyId) {
646
+ try {
647
+ const res = await dataFetch(
648
+ params,
649
+ `/properties/${propertyId}/metadata`
650
+ );
651
+ if (!res.ok) return null;
652
+ return await res.json();
653
+ } catch {
654
+ return null;
655
+ }
656
+ }
657
+ async function probePropertyAccess(params, propertyId) {
658
+ try {
659
+ const res = await dataFetch(
660
+ params,
661
+ `/properties/${propertyId}:runReport`,
662
+ {
663
+ method: "POST",
664
+ headers: { "Content-Type": "application/json" },
665
+ body: JSON.stringify({
666
+ dateRanges: [{ startDate: "7daysAgo", endDate: "today" }],
667
+ metrics: [{ name: "activeUsers" }],
668
+ limit: 1
669
+ })
670
+ }
671
+ );
672
+ if (res.ok) return { ok: true };
673
+ const body = await res.text().catch(() => res.statusText);
674
+ return { ok: false, status: res.status, body };
675
+ } catch (err) {
676
+ return {
677
+ ok: false,
678
+ status: "network",
679
+ body: err instanceof Error ? err.message : String(err)
680
+ };
681
+ }
682
+ }
683
+ async function batchRunReports(params, propertyId, requests) {
684
+ try {
685
+ const res = await dataFetch(
686
+ params,
687
+ `/properties/${propertyId}:batchRunReports`,
688
+ {
689
+ method: "POST",
690
+ headers: { "Content-Type": "application/json" },
691
+ body: JSON.stringify({ requests })
692
+ }
693
+ );
694
+ if (!res.ok) return requests.map(() => ({}));
695
+ const data = await res.json();
696
+ const reports = data.reports ?? [];
697
+ return requests.map((_, i) => reports[i] ?? {});
698
+ } catch {
699
+ return requests.map(() => ({}));
700
+ }
701
+ }
702
+ var LAST_30D = { startDate: "30daysAgo", endDate: "today" };
703
+ var PREV_30D = { startDate: "60daysAgo", endDate: "31daysAgo" };
704
+ var HEADLINE_METRICS = [
705
+ "activeUsers",
706
+ "newUsers",
707
+ "sessions",
708
+ "screenPageViews",
709
+ "engagementRate",
710
+ "averageSessionDuration",
711
+ "keyEvents",
712
+ "totalRevenue"
713
+ ];
714
+ function buildHeadlineRequest() {
715
+ return {
716
+ dateRanges: [
717
+ { ...LAST_30D, name: "current" },
718
+ { ...PREV_30D, name: "previous" }
719
+ ],
720
+ metrics: HEADLINE_METRICS.map((name) => ({ name }))
721
+ };
722
+ }
723
+ function buildTopDimensionRequest(dimension, metric, limit) {
724
+ return {
725
+ dateRanges: [LAST_30D],
726
+ dimensions: [{ name: dimension }],
727
+ metrics: [{ name: metric }],
728
+ limit,
729
+ orderBys: [{ desc: true, metric: { metricName: metric } }]
730
+ };
731
+ }
732
+ function buildTopPagesRequest() {
733
+ return {
734
+ dateRanges: [LAST_30D],
735
+ dimensions: [{ name: "pagePath" }],
736
+ metrics: [
737
+ { name: "screenPageViews" },
738
+ { name: "activeUsers" },
739
+ { name: "averageSessionDuration" }
740
+ ],
741
+ limit: TOP_N_PAGES,
742
+ orderBys: [{ desc: true, metric: { metricName: "screenPageViews" } }]
743
+ };
744
+ }
745
+ function buildTopEventsRequest() {
746
+ return {
747
+ dateRanges: [LAST_30D],
748
+ dimensions: [{ name: "eventName" }],
749
+ metrics: [{ name: "eventCount" }, { name: "totalUsers" }],
750
+ limit: TOP_N_EVENTS,
751
+ orderBys: [{ desc: true, metric: { metricName: "eventCount" } }]
752
+ };
753
+ }
754
+ function escapePipe(s) {
755
+ return s.replace(/\|/g, "\\|");
756
+ }
757
+ function truncate(s, max) {
758
+ if (!s) return "";
759
+ if (s.length <= max) return s;
760
+ return `${s.slice(0, Math.max(0, max - 1))}\u2026`;
761
+ }
762
+ function formatNumber(raw) {
763
+ if (raw == null || raw === "") return "-";
764
+ const n = Number(raw);
765
+ if (!Number.isFinite(n)) return raw;
766
+ if (Number.isInteger(n)) return n.toLocaleString("en-US");
767
+ return n.toLocaleString("en-US", { maximumFractionDigits: 2 });
768
+ }
769
+ function formatPercent(raw) {
770
+ if (raw == null || raw === "") return "-";
771
+ const n = Number(raw);
772
+ if (!Number.isFinite(n)) return raw;
773
+ return `${(n * 100).toFixed(1)}%`;
774
+ }
775
+ function formatDuration(rawSeconds) {
776
+ if (rawSeconds == null || rawSeconds === "") return "-";
777
+ const s = Math.round(Number(rawSeconds));
778
+ if (!Number.isFinite(s)) return rawSeconds;
779
+ if (s < 60) return `${s}s`;
780
+ const mins = Math.floor(s / 60);
781
+ const secs = s % 60;
782
+ return `${mins}m ${secs}s`;
783
+ }
784
+ function deltaArrow(curr, prev) {
785
+ if (!Number.isFinite(prev) || prev === 0) return "";
786
+ const pct = (curr - prev) / prev * 100;
787
+ if (Math.abs(pct) < 0.5) return " (\u2248 flat)";
788
+ const sign = pct > 0 ? "\u2191" : "\u2193";
789
+ return ` (${sign} ${Math.abs(pct).toFixed(1)}% vs prior 30d)`;
790
+ }
791
+ function pivotComparisonReport(report) {
792
+ const out = /* @__PURE__ */ new Map();
793
+ const headers = report.metricHeaders ?? [];
794
+ const rows = report.rows ?? [];
795
+ for (let i = 0; i < headers.length; i++) {
796
+ const name = headers[i]?.name;
797
+ if (!name) continue;
798
+ out.set(name, {
799
+ current: Number(rows[0]?.metricValues?.[i]?.value ?? "0"),
800
+ previous: Number(rows[1]?.metricValues?.[i]?.value ?? "0")
801
+ });
802
+ }
803
+ return out;
804
+ }
805
+ function renderHeadline(sections, report) {
806
+ const m = pivotComparisonReport(report);
807
+ if (m.size === 0) return;
808
+ sections.push("#### Headline metrics (last 30 days)", "");
809
+ const lines = [];
810
+ const push = (label, key, formatter) => {
811
+ const cell = m.get(key);
812
+ if (!cell) return;
813
+ const formatted = formatter(String(cell.current));
814
+ const delta = deltaArrow(cell.current, cell.previous);
815
+ lines.push([label, `${formatted}${delta}`]);
816
+ };
817
+ push("Active users", "activeUsers", formatNumber);
818
+ push("New users", "newUsers", formatNumber);
819
+ push("Sessions", "sessions", formatNumber);
820
+ push("Page / screen views", "screenPageViews", formatNumber);
821
+ push("Engagement rate", "engagementRate", formatPercent);
822
+ push("Avg session duration", "averageSessionDuration", formatDuration);
823
+ push("Key events (conversions)", "keyEvents", formatNumber);
824
+ const rev = m.get("totalRevenue");
825
+ if (rev && rev.current > 0) {
826
+ lines.push([
827
+ "Total revenue",
828
+ `${formatNumber(String(rev.current))}${deltaArrow(rev.current, rev.previous)}`
829
+ ]);
830
+ }
831
+ for (const [k, v] of lines) sections.push(`- **${k}**: ${v}`);
832
+ sections.push("");
833
+ }
834
+ function renderBreakdown(sections, title, blurb, report, topN, metricFormatter = formatNumber) {
835
+ const rows = report.rows ?? [];
836
+ if (rows.length === 0) return;
837
+ const dim = report.dimensionHeaders?.[0]?.name ?? "dimension";
838
+ const metricName = report.metricHeaders?.[0]?.name ?? "metric";
839
+ sections.push(`#### ${title}`, "", blurb, "");
840
+ for (const row of rows.slice(0, topN)) {
841
+ const label = row.dimensionValues?.[0]?.value || `(empty ${dim})`;
842
+ const value = metricFormatter(row.metricValues?.[0]?.value);
843
+ sections.push(`- ${label}: ${value} ${metricName}`);
844
+ }
845
+ sections.push("");
846
+ }
847
+ function renderTopPages(sections, report) {
848
+ const rows = report.rows ?? [];
849
+ if (rows.length === 0) return;
850
+ sections.push(
851
+ `#### Top pages by views (last 30 days)`,
852
+ "",
853
+ "_The most-visited pages \u2014 strong candidates for content-performance dashboards._",
854
+ ""
855
+ );
856
+ sections.push("| Path | Page views | Active users | Avg session duration |");
857
+ sections.push("|------|------------|--------------|----------------------|");
858
+ for (const row of rows.slice(0, TOP_N_PAGES)) {
859
+ const path2 = truncate(row.dimensionValues?.[0]?.value, PAGE_PATH_MAX) || "-";
860
+ sections.push(
861
+ `| ${escapePipe(path2)} | ${formatNumber(row.metricValues?.[0]?.value)} | ${formatNumber(
862
+ row.metricValues?.[1]?.value
863
+ )} | ${formatDuration(row.metricValues?.[2]?.value)} |`
864
+ );
865
+ }
866
+ sections.push("");
867
+ }
868
+ function renderTopEvents(sections, report) {
869
+ const rows = report.rows ?? [];
870
+ if (rows.length === 0) return;
871
+ sections.push(
872
+ `#### Top events (last 30 days)`,
873
+ "",
874
+ "_Standard auto-collected events (`page_view`, `session_start`, \u2026) plus any custom events the team has set up. The custom ones are usually the most interesting for funnel / conversion dashboards._",
875
+ ""
876
+ );
877
+ sections.push("| Event | Event count | Unique users |");
878
+ sections.push("|-------|-------------|--------------|");
879
+ for (const row of rows.slice(0, TOP_N_EVENTS)) {
880
+ const name = truncate(row.dimensionValues?.[0]?.value, EVENT_NAME_MAX) || "-";
881
+ sections.push(
882
+ `| \`${escapePipe(name)}\` | ${formatNumber(row.metricValues?.[0]?.value)} | ${formatNumber(
883
+ row.metricValues?.[1]?.value
884
+ )} |`
885
+ );
886
+ }
887
+ sections.push("");
888
+ }
889
+ function renderMetadata(sections, metadata) {
890
+ const dims = metadata.dimensions ?? [];
891
+ const mets = metadata.metrics ?? [];
892
+ if (dims.length === 0 && mets.length === 0) return;
893
+ const customDims = dims.filter((d) => d.customDefinition);
894
+ const customMets = mets.filter((m) => m.customDefinition);
895
+ const standardDims = dims.filter((d) => !d.customDefinition);
896
+ const standardMets = mets.filter((m) => !m.customDefinition);
897
+ sections.push("#### Available query surface", "");
898
+ sections.push(
899
+ "_Use these in custom `runReport` calls. Custom definitions reveal what the team specifically tracks beyond GA4's defaults._",
900
+ ""
901
+ );
902
+ if (customDims.length > 0 || customMets.length > 0) {
903
+ sections.push(`##### Custom definitions`, "");
904
+ for (const d of customDims.slice(0, CUSTOM_METADATA_LIMIT)) {
905
+ const desc = d.description ? ` \u2014 ${truncate(d.description, DESCRIPTION_MAX)}` : "";
906
+ sections.push(`- dimension \`${d.apiName ?? d.uiName}\`${desc}`);
907
+ }
908
+ if (customDims.length > CUSTOM_METADATA_LIMIT) {
909
+ sections.push(
910
+ `- _\u2026and ${customDims.length - CUSTOM_METADATA_LIMIT} more custom dimensions._`
911
+ );
912
+ }
913
+ for (const m of customMets.slice(0, CUSTOM_METADATA_LIMIT)) {
914
+ const desc = m.description ? ` \u2014 ${truncate(m.description, DESCRIPTION_MAX)}` : "";
915
+ sections.push(`- metric \`${m.apiName ?? m.uiName}\`${desc}`);
916
+ }
917
+ if (customMets.length > CUSTOM_METADATA_LIMIT) {
918
+ sections.push(
919
+ `- _\u2026and ${customMets.length - CUSTOM_METADATA_LIMIT} more custom metrics._`
920
+ );
921
+ }
922
+ sections.push("");
923
+ }
924
+ if (standardDims.length > 0) {
925
+ sections.push(`##### Standard dimensions (${standardDims.length})`);
926
+ const names = standardDims.slice(0, STANDARD_METADATA_LIMIT).map((d) => `\`${d.apiName ?? d.uiName}\``).join(", ");
927
+ const overflow = standardDims.length > STANDARD_METADATA_LIMIT ? ` _(+${standardDims.length - STANDARD_METADATA_LIMIT} more)_` : "";
928
+ sections.push(names + overflow, "");
929
+ }
930
+ if (standardMets.length > 0) {
931
+ sections.push(`##### Standard metrics (${standardMets.length})`);
932
+ const names = standardMets.slice(0, STANDARD_METADATA_LIMIT).map((m) => `\`${m.apiName ?? m.uiName}\``).join(", ");
933
+ const overflow = standardMets.length > STANDARD_METADATA_LIMIT ? ` _(+${standardMets.length - STANDARD_METADATA_LIMIT} more)_` : "";
934
+ sections.push(names + overflow, "");
935
+ }
936
+ }
937
+ function renderProbeFailure(sections, propertyId, probe, serviceAccountEmail) {
938
+ sections.push(`#### Connection probe failed`, "");
939
+ if (probe.status === 403) {
940
+ sections.push(
941
+ `_The service account could not access GA4 property \`${propertyId}\`._`,
942
+ "",
943
+ `**Fix**: In GA4 (Admin > Property access management), grant **Viewer** role to the service account below, then re-run setup.`,
944
+ ""
945
+ );
946
+ if (serviceAccountEmail) {
947
+ sections.push(`\`${serviceAccountEmail}\``, "");
948
+ }
949
+ } else if (probe.status === 404) {
950
+ sections.push(
951
+ `_GA4 property \`${propertyId}\` was not found. Double-check the Property ID in **Admin > Property Settings**._`,
952
+ ""
953
+ );
954
+ } else if (probe.status === "network") {
955
+ sections.push(
956
+ `_Could not reach the GA4 Data API: ${probe.body}_`,
957
+ ""
958
+ );
959
+ } else {
960
+ sections.push(
961
+ `_GA4 Data API returned ${probe.status}: ${probe.body.slice(0, 400)}_`,
962
+ ""
963
+ );
964
+ }
965
+ }
966
+ async function exploreProperty(sections, params, propertyId, serviceAccountEmail) {
967
+ const probe = await probePropertyAccess(params, propertyId);
968
+ if (!probe.ok) {
969
+ renderProbeFailure(sections, propertyId, probe, serviceAccountEmail);
970
+ return;
971
+ }
972
+ const batch1Requests = [
973
+ buildHeadlineRequest(),
974
+ buildTopDimensionRequest("sessionSourceMedium", "sessions", TOP_N_SOURCES),
975
+ buildTopDimensionRequest("country", "activeUsers", TOP_N_COUNTRIES),
976
+ buildTopDimensionRequest("deviceCategory", "sessions", 5),
977
+ buildTopDimensionRequest("browser", "activeUsers", TOP_N_BROWSERS)
978
+ ];
979
+ const batch2Requests = [buildTopPagesRequest(), buildTopEventsRequest()];
980
+ const [batch1, batch2, metadata] = await Promise.all([
981
+ batchRunReports(params, propertyId, batch1Requests),
982
+ batchRunReports(params, propertyId, batch2Requests),
983
+ getMetadata(params, propertyId)
984
+ ]);
985
+ const [headline, sourceMedium, country, device, browser] = batch1;
986
+ const [topPages, topEvents] = batch2;
987
+ if (headline) renderHeadline(sections, headline);
988
+ const hasTrafficSignal = (sourceMedium?.rows?.length ?? 0) + (country?.rows?.length ?? 0) + (device?.rows?.length ?? 0) + (browser?.rows?.length ?? 0) > 0;
989
+ if (hasTrafficSignal) {
990
+ sections.push(
991
+ "#### Traffic mix (last 30 days)",
992
+ "",
993
+ "_Where users are coming from \u2014 useful for dashboards that segment by acquisition channel, geography, or device._",
994
+ ""
995
+ );
996
+ if (sourceMedium)
997
+ renderBreakdown(
998
+ sections,
999
+ "Top source / medium",
1000
+ "_Acquisition channels driving sessions._",
1001
+ sourceMedium,
1002
+ TOP_N_SOURCES
1003
+ );
1004
+ if (country)
1005
+ renderBreakdown(
1006
+ sections,
1007
+ "Top countries by active users",
1008
+ "_Geography \u2014 informs which date/time filters and locale segments to expose._",
1009
+ country,
1010
+ TOP_N_COUNTRIES
1011
+ );
1012
+ if (device)
1013
+ renderBreakdown(
1014
+ sections,
1015
+ "Device categories",
1016
+ "_Desktop / mobile / tablet split \u2014 informs responsive cuts._",
1017
+ device,
1018
+ 5
1019
+ );
1020
+ if (browser)
1021
+ renderBreakdown(
1022
+ sections,
1023
+ "Top browsers",
1024
+ "_Browser distribution \u2014 informs compatibility dashboards or QA filters._",
1025
+ browser,
1026
+ TOP_N_BROWSERS
1027
+ );
1028
+ }
1029
+ if (topPages) renderTopPages(sections, topPages);
1030
+ if (topEvents) renderTopEvents(sections, topEvents);
1031
+ if (metadata) renderMetadata(sections, metadata);
578
1032
  }
579
1033
  var googleAnalyticsSetupFlow = {
580
1034
  initialState: () => ({}),
@@ -587,15 +1041,19 @@ var googleAnalyticsSetupFlow = {
587
1041
  en: "Select a Google Analytics account"
588
1042
  },
589
1043
  async fetchOptions(_state, rt) {
590
- const summaries = await listAccountSummaries(rt.params);
591
- return summaries.map((s) => {
592
- const accountResource = s.account ?? s.name ?? "";
593
- if (!accountResource) return null;
594
- return {
595
- value: accountResource,
596
- label: s.displayName ?? accountResource
597
- };
598
- }).filter((v) => v != null);
1044
+ try {
1045
+ const summaries = await listAccountSummaries(rt.params);
1046
+ return summaries.map((s) => {
1047
+ const accountResource = s.account ?? s.name ?? "";
1048
+ if (!accountResource) return null;
1049
+ return {
1050
+ value: accountResource,
1051
+ label: s.displayName ?? accountResource
1052
+ };
1053
+ }).filter((v) => v != null);
1054
+ } catch {
1055
+ return [];
1056
+ }
599
1057
  },
600
1058
  applyAnswer: (state, answer) => ({ ...state, account: answer[0] })
601
1059
  },
@@ -608,81 +1066,153 @@ var googleAnalyticsSetupFlow = {
608
1066
  },
609
1067
  async fetchOptions(state, rt) {
610
1068
  if (!state.account) return [];
611
- const summaries = await listAccountSummaries(rt.params);
612
- const account = summaries.find(
613
- (s) => (s.account ?? s.name) === state.account
614
- );
615
- const props = (account?.propertySummaries ?? []).map((p) => {
616
- const id = propertyIdFromResource(p.property);
617
- if (!id) return null;
618
- return {
619
- value: id,
620
- label: p.displayName ? `${p.displayName} (${id})` : id
621
- };
622
- }).filter((v) => v != null);
623
- if (props.length === 0) return [];
624
- return [
625
- {
626
- value: ALL_PROPERTIES,
627
- label: rt.language === "ja" ? "\u3059\u3079\u3066\u306E\u30D7\u30ED\u30D1\u30C6\u30A3" : "All properties"
628
- },
629
- ...props
630
- ];
1069
+ try {
1070
+ const summaries = await listAccountSummaries(rt.params);
1071
+ const account = summaries.find(
1072
+ (s) => (s.account ?? s.name) === state.account
1073
+ );
1074
+ const props = (account?.propertySummaries ?? []).map((p) => {
1075
+ const id = propertyIdFromResource(p.property);
1076
+ if (!id) return null;
1077
+ return {
1078
+ value: id,
1079
+ label: p.displayName ? `${p.displayName} (${id})` : id
1080
+ };
1081
+ }).filter((v) => v != null);
1082
+ if (props.length === 0) return [];
1083
+ return [
1084
+ {
1085
+ value: ALL_PROPERTIES,
1086
+ label: rt.language === "ja" ? "\u3059\u3079\u3066\u306E\u30D7\u30ED\u30D1\u30C6\u30A3" : "All properties"
1087
+ },
1088
+ ...props
1089
+ ];
1090
+ } catch {
1091
+ return [];
1092
+ }
631
1093
  },
632
1094
  applyAnswer: (state, answer) => ({ ...state, properties: answer })
1095
+ },
1096
+ {
1097
+ slug: "manualPropertyId",
1098
+ type: "text",
1099
+ question: {
1100
+ ja: "GA4 \u30D7\u30ED\u30D1\u30C6\u30A3 ID \u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4F8B: 123456789\uFF09\u3002GA4 \u7BA1\u7406\u753B\u9762 > \u30D7\u30ED\u30D1\u30C6\u30A3\u8A2D\u5B9A\u3067\u78BA\u8A8D\u3067\u304D\u307E\u3059\u3002\u5165\u529B\u5F8C\u3001\u30C7\u30FC\u30BF\u53D6\u5F97\u3092\u8A66\u3057\u3066\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u63D0\u6848\u7528\u306E\u30B5\u30DE\u30EA\u3092\u751F\u6210\u3057\u307E\u3059\u3002",
1101
+ en: "Enter your GA4 Property ID (e.g., 123456789). Found in GA4 Admin > Property Settings. After entry we'll probe the property via the Data API and assemble a dashboard-ready summary."
1102
+ },
1103
+ async fetchOptions(state) {
1104
+ if (state.properties?.length) return [];
1105
+ return [{ value: "_show", label: "" }];
1106
+ },
1107
+ applyAnswer: (state, answer) => ({
1108
+ ...state,
1109
+ manualPropertyId: answer[0]
1110
+ })
633
1111
  }
634
1112
  ],
635
1113
  async finalize(state, rt) {
636
- if (!state.account || !state.properties) {
637
- throw new Error("Google Analytics setup: incomplete state on finalize");
1114
+ const sections = ["## Google Analytics", ""];
1115
+ let serviceAccountEmail = null;
1116
+ try {
1117
+ const keyJsonBase64 = rt.params[parameters.serviceAccountKeyJsonBase64.slug];
1118
+ if (keyJsonBase64) {
1119
+ serviceAccountEmail = decodeServiceAccount(keyJsonBase64).client_email ?? null;
1120
+ }
1121
+ } catch {
1122
+ }
1123
+ let targetPropertyIds = [];
1124
+ let accountLabel = null;
1125
+ let accountSummary;
1126
+ if (state.account && state.properties) {
1127
+ try {
1128
+ const summaries = await listAccountSummaries(rt.params);
1129
+ accountSummary = summaries.find(
1130
+ (s) => (s.account ?? s.name) === state.account
1131
+ );
1132
+ accountLabel = accountSummary?.displayName ?? state.account.replace(/^accounts\//, "");
1133
+ } catch {
1134
+ accountLabel = state.account.replace(/^accounts\//, "");
1135
+ }
1136
+ targetPropertyIds = await resolveSetupSelection({
1137
+ selected: state.properties,
1138
+ allSentinel: ALL_PROPERTIES,
1139
+ fetchAll: async () => (accountSummary?.propertySummaries ?? []).map((p) => propertyIdFromResource(p.property)).filter((v) => v),
1140
+ limit: GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES
1141
+ });
1142
+ } else if (state.manualPropertyId) {
1143
+ targetPropertyIds = [state.manualPropertyId];
638
1144
  }
639
- const summaries = await listAccountSummaries(rt.params);
640
- const account = summaries.find(
641
- (s) => (s.account ?? s.name) === state.account
642
- );
643
- const accountLabel = account?.displayName ?? state.account.replace(/^accounts\//, "");
644
- const targetPropertyIds = await resolveSetupSelection({
645
- selected: state.properties,
646
- allSentinel: ALL_PROPERTIES,
647
- fetchAll: async () => (account?.propertySummaries ?? []).map((p) => propertyIdFromResource(p.property)).filter((v) => v),
648
- limit: GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES
649
- });
650
- const sections = [
651
- "## Google Analytics",
652
- "",
653
- `### Account: ${accountLabel}`,
654
- ""
655
- ];
656
1145
  if (targetPropertyIds.length === 0) {
657
- sections.push("_No properties selected._", "");
1146
+ sections.push(
1147
+ "_Could not identify a target GA4 property. Either enable the Google Analytics Admin API on the service account's GCP project, or re-run setup and provide the Property ID manually._",
1148
+ ""
1149
+ );
1150
+ if (serviceAccountEmail) {
1151
+ sections.push(
1152
+ `_Service account email (share your GA4 property with this address)_: \`${serviceAccountEmail}\``,
1153
+ ""
1154
+ );
1155
+ }
658
1156
  return sections.join("\n");
659
1157
  }
660
- sections.push("| Property ID | Display Name | Time Zone | Currency | Industry |");
661
- sections.push("|-------------|--------------|-----------|----------|----------|");
662
- for (const propertyId of targetPropertyIds) {
663
- const prop = await getProperty(rt.params, propertyId);
664
- const displayName = (prop?.displayName ?? "-").replace(/\|/g, "\\|");
1158
+ sections.push(
1159
+ "_The downstream coding agent should use this summary as the canonical baseline of the property's traffic shape for dashboard / app proposals. Headline metrics, traffic mix, top pages, top events, and custom dimensions/metrics together cover the most common dashboarding patterns; reach for the standard query surface for anything custom._",
1160
+ ""
1161
+ );
1162
+ if (accountLabel) sections.push(`### Account: ${accountLabel}`, "");
1163
+ sections.push("### Properties", "");
1164
+ sections.push(
1165
+ "| Property ID | Display Name | Time Zone | Currency | Industry |"
1166
+ );
1167
+ sections.push(
1168
+ "|-------------|--------------|-----------|----------|----------|"
1169
+ );
1170
+ const properties = await Promise.all(
1171
+ targetPropertyIds.map((id) => getProperty(rt.params, id))
1172
+ );
1173
+ for (let i = 0; i < targetPropertyIds.length; i++) {
1174
+ const pid = targetPropertyIds[i];
1175
+ const prop = properties[i];
1176
+ const displayName = escapePipe(prop?.displayName ?? "-");
665
1177
  const timeZone = prop?.timeZone ?? "-";
666
1178
  const currency = prop?.currencyCode ?? "-";
667
- const industry = (prop?.industryCategory ?? "-").replace(/\|/g, "\\|");
1179
+ const industry = escapePipe(prop?.industryCategory ?? "-");
668
1180
  sections.push(
669
- `| ${propertyId} | ${displayName} | ${timeZone} | ${currency} | ${industry} |`
1181
+ `| ${pid} | ${displayName} | ${timeZone} | ${currency} | ${industry} |`
670
1182
  );
671
1183
  }
672
1184
  sections.push("");
1185
+ const propertiesToExplore = targetPropertyIds.slice(
1186
+ 0,
1187
+ MAX_PROPERTIES_TO_EXPLORE
1188
+ );
1189
+ for (let i = 0; i < propertiesToExplore.length; i++) {
1190
+ const pid = propertiesToExplore[i];
1191
+ const prop = properties[i];
1192
+ const heading = prop?.displayName && prop.displayName !== "-" ? `### Data exploration: ${prop.displayName} (${pid})` : `### Data exploration: property ${pid}`;
1193
+ sections.push(heading, "");
1194
+ await exploreProperty(sections, rt.params, pid, serviceAccountEmail);
1195
+ }
1196
+ if (targetPropertyIds.length > propertiesToExplore.length) {
1197
+ const skipped = targetPropertyIds.length - propertiesToExplore.length;
1198
+ sections.push(
1199
+ `_Deep exploration limited to the first ${propertiesToExplore.length} of ${targetPropertyIds.length} properties to keep this summary bounded. The remaining ${skipped} can be explored on demand via \`connector_google-analytics-service-account_request\`._`,
1200
+ ""
1201
+ );
1202
+ }
673
1203
  return sections.join("\n");
674
1204
  }
675
1205
  };
676
1206
 
677
1207
  // ../connectors/src/connectors/google-analytics/tools/request.ts
678
1208
  import { z } from "zod";
679
- var BASE_URL2 = "https://analyticsdata.googleapis.com/v1beta/";
1209
+ var BASE_URL2 = "https://analyticsdata.googleapis.com";
680
1210
  var REQUEST_TIMEOUT_MS = 6e4;
681
1211
  var inputSchema = z.object({
682
1212
  toolUseIntent: z.string().optional().describe("Brief description of what you intend to accomplish with this tool call"),
683
1213
  connectionId: z.string().describe("ID of the Google Analytics connection to use"),
684
1214
  method: z.enum(["GET", "POST"]).describe("HTTP method"),
685
- path: z.string().describe("API path (e.g., 'properties/{propertyId}:runReport'). {propertyId} is automatically replaced."),
1215
+ path: z.string().describe("Full request path including the API version prefix, appended to https://analyticsdata.googleapis.com. Use the same form as the GA4 Data API reference, e.g., '/v1beta/properties/123456789:runReport'."),
686
1216
  body: z.record(z.string(), z.unknown()).optional().describe("POST request body (JSON)")
687
1217
  });
688
1218
  var outputSchema = z.discriminatedUnion("success", [
@@ -699,8 +1229,7 @@ var outputSchema = z.discriminatedUnion("success", [
699
1229
  var requestTool = new ConnectorTool({
700
1230
  name: "request",
701
1231
  description: `Send authenticated requests to the Google Analytics Data API.
702
- Authentication is handled automatically using a service account.
703
- {propertyId} in the path is automatically replaced with the connection's property-id.`,
1232
+ Authentication is handled automatically using a service account.`,
704
1233
  inputSchema,
705
1234
  outputSchema,
706
1235
  async execute({ connectionId, method, path: path2, body }, connections) {
@@ -712,7 +1241,6 @@ Authentication is handled automatically using a service account.
712
1241
  try {
713
1242
  const { GoogleAuth } = await import("google-auth-library");
714
1243
  const keyJsonBase64 = parameters.serviceAccountKeyJsonBase64.getValue(connection2);
715
- const propertyId = parameters.propertyId.getValue(connection2);
716
1244
  const credentials = JSON.parse(
717
1245
  Buffer.from(keyJsonBase64, "base64").toString("utf-8")
718
1246
  );
@@ -724,8 +1252,7 @@ Authentication is handled automatically using a service account.
724
1252
  if (!token) {
725
1253
  return { success: false, error: "Failed to obtain access token" };
726
1254
  }
727
- const resolvedPath = path2.replace(/\{propertyId\}/g, propertyId);
728
- const url = `${BASE_URL2}${resolvedPath}`;
1255
+ const url = `${BASE_URL2}${path2}`;
729
1256
  const controller = new AbortController();
730
1257
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
731
1258
  try {
@@ -772,7 +1299,7 @@ var googleAnalyticsConnector = new ConnectorPlugin({
772
1299
  systemPrompt: {
773
1300
  en: `### Tools
774
1301
 
775
- - \`google-analytics-service-account_request\`: The only way to call the GA4 Data API. Use it to fetch metadata, run reports, or run realtime reports. See the GA4 Data API Reference below for available endpoints and request bodies.
1302
+ - \`connector_google-analytics-service-account_request\`: The only way to call the GA4 Data API. Use it to fetch metadata, run reports, or run realtime reports. See the GA4 Data API Reference below for available endpoints and request bodies.
776
1303
 
777
1304
  ### Business Logic
778
1305
 
@@ -781,9 +1308,11 @@ The business logic type for this connector is "typescript". Use the connector SD
781
1308
  SDK methods (client created via \`connection(connectionId)\`):
782
1309
  - \`client.runReport(request)\` \u2014 run a GA4 report
783
1310
  - \`client.runRealtimeReport(request)\` \u2014 run a realtime report
784
- - \`client.getMetadata()\` \u2014 fetch available dimensions/metrics
1311
+ - \`client.getMetadata(request)\` \u2014 fetch available dimensions/metrics
785
1312
  - \`client.request(path, init?)\` \u2014 low-level authenticated fetch
786
1313
 
1314
+ **IMPORTANT**: You MUST always include \`propertyId\` in every SDK method call. The property ID is obtained during the setup flow. Without it, the request will fail.
1315
+
787
1316
  \`\`\`ts
788
1317
  import type { Context } from "hono";
789
1318
  import { connection } from "@squadbase/vite-server/connectors/google-analytics";
@@ -791,12 +1320,14 @@ import { connection } from "@squadbase/vite-server/connectors/google-analytics";
791
1320
  const ga = connection("<connectionId>");
792
1321
 
793
1322
  export default async function handler(c: Context) {
794
- const { startDate = "7daysAgo", endDate = "today" } = await c.req.json<{
1323
+ const { propertyId, startDate = "7daysAgo", endDate = "today" } = await c.req.json<{
1324
+ propertyId: string;
795
1325
  startDate?: string;
796
1326
  endDate?: string;
797
1327
  }>();
798
1328
 
799
1329
  const { rows } = await ga.runReport({
1330
+ propertyId,
800
1331
  dateRanges: [{ startDate, endDate }],
801
1332
  dimensions: [{ name: "date" }],
802
1333
  metrics: [{ name: "activeUsers" }, { name: "sessions" }],
@@ -817,8 +1348,8 @@ export default async function handler(c: Context) {
817
1348
 
818
1349
  Common operations:
819
1350
 
820
- - **Get metadata** (list available dimensions/metrics for a property): \`GET properties/{propertyId}/metadata\`
821
- - **Run report** (query analytics data): \`POST properties/{propertyId}:runReport\` with a body like:
1351
+ - **Get metadata** (list available dimensions/metrics for a property): \`GET /v1beta/properties/{propertyId}/metadata\`
1352
+ - **Run report** (query analytics data): \`POST /v1beta/properties/{propertyId}:runReport\` with a body like:
822
1353
  \`\`\`json
823
1354
  {
824
1355
  "dateRanges": [{ "startDate": "7daysAgo", "endDate": "today" }],
@@ -827,7 +1358,7 @@ Common operations:
827
1358
  "limit": 100
828
1359
  }
829
1360
  \`\`\`
830
- - **Run realtime report**: \`POST properties/{propertyId}:runRealtimeReport\`
1361
+ - **Run realtime report**: \`POST /v1beta/properties/{propertyId}:runRealtimeReport\`
831
1362
 
832
1363
  #### Common dimensions
833
1364
  date, country, city, deviceCategory, browser, pagePath, pageTitle, sessionSource, sessionMedium, eventName
@@ -840,7 +1371,7 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
840
1371
  - Relative: \`"today"\`, \`"yesterday"\`, \`"7daysAgo"\`, \`"30daysAgo"\``,
841
1372
  ja: `### \u30C4\u30FC\u30EB
842
1373
 
843
- - \`google-analytics-service-account_request\`: GA4 Data API\u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30E1\u30BF\u30C7\u30FC\u30BF\u306E\u53D6\u5F97\u3001\u30EC\u30DD\u30FC\u30C8\u306E\u5B9F\u884C\u3001\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8\u306E\u5B9F\u884C\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u5229\u7528\u53EF\u80FD\u306A\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3068\u30EA\u30AF\u30A8\u30B9\u30C8\u30DC\u30C7\u30A3\u306F\u4E0B\u90E8\u306E\u300CGA4 Data API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9\u300D\u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\u3002
1374
+ - \`connector_google-analytics-service-account_request\`: GA4 Data API\u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30E1\u30BF\u30C7\u30FC\u30BF\u306E\u53D6\u5F97\u3001\u30EC\u30DD\u30FC\u30C8\u306E\u5B9F\u884C\u3001\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8\u306E\u5B9F\u884C\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u5229\u7528\u53EF\u80FD\u306A\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u3068\u30EA\u30AF\u30A8\u30B9\u30C8\u30DC\u30C7\u30A3\u306F\u4E0B\u90E8\u306E\u300CGA4 Data API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9\u300D\u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\u3002
844
1375
 
845
1376
  ### Business Logic
846
1377
 
@@ -849,9 +1380,11 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
849
1380
  SDK\u30E1\u30BD\u30C3\u30C9 (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8):
850
1381
  - \`client.runReport(request)\` \u2014 GA4\u30EC\u30DD\u30FC\u30C8\u3092\u5B9F\u884C
851
1382
  - \`client.runRealtimeReport(request)\` \u2014 \u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8\u3092\u5B9F\u884C
852
- - \`client.getMetadata()\` \u2014 \u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3/\u30E1\u30C8\u30EA\u30AF\u30B9\u3092\u53D6\u5F97
1383
+ - \`client.getMetadata(request)\` \u2014 \u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3/\u30E1\u30C8\u30EA\u30AF\u30B9\u3092\u53D6\u5F97
853
1384
  - \`client.request(path, init?)\` \u2014 \u4F4E\u30EC\u30D9\u30EB\u306E\u8A8D\u8A3C\u4ED8\u304Dfetch
854
1385
 
1386
+ **\u91CD\u8981**: \u3059\u3079\u3066\u306ESDK\u30E1\u30BD\u30C3\u30C9\u547C\u3073\u51FA\u3057\u306B\u306F\u5FC5\u305A \`propertyId\` \u3092\u542B\u3081\u3066\u304F\u3060\u3055\u3044\u3002\u30D7\u30ED\u30D1\u30C6\u30A3ID\u306F\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u30D5\u30ED\u30FC\u3067\u53D6\u5F97\u3055\u308C\u307E\u3059\u3002\u6307\u5B9A\u3057\u306A\u3044\u3068\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u5931\u6557\u3057\u307E\u3059\u3002
1387
+
855
1388
  \`\`\`ts
856
1389
  import type { Context } from "hono";
857
1390
  import { connection } from "@squadbase/vite-server/connectors/google-analytics";
@@ -859,12 +1392,14 @@ import { connection } from "@squadbase/vite-server/connectors/google-analytics";
859
1392
  const ga = connection("<connectionId>");
860
1393
 
861
1394
  export default async function handler(c: Context) {
862
- const { startDate = "7daysAgo", endDate = "today" } = await c.req.json<{
1395
+ const { propertyId, startDate = "7daysAgo", endDate = "today" } = await c.req.json<{
1396
+ propertyId: string;
863
1397
  startDate?: string;
864
1398
  endDate?: string;
865
1399
  }>();
866
1400
 
867
1401
  const { rows } = await ga.runReport({
1402
+ propertyId,
868
1403
  dateRanges: [{ startDate, endDate }],
869
1404
  dimensions: [{ name: "date" }],
870
1405
  metrics: [{ name: "activeUsers" }, { name: "sessions" }],
@@ -885,8 +1420,8 @@ export default async function handler(c: Context) {
885
1420
 
886
1421
  \u4E3B\u306A\u64CD\u4F5C:
887
1422
 
888
- - **\u30E1\u30BF\u30C7\u30FC\u30BF\u306E\u53D6\u5F97** (\u30D7\u30ED\u30D1\u30C6\u30A3\u3067\u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3/\u30E1\u30C8\u30EA\u30AF\u30B9\u306E\u4E00\u89A7): \`GET properties/{propertyId}/metadata\`
889
- - **\u30EC\u30DD\u30FC\u30C8\u306E\u53D6\u5F97** (\u5206\u6790\u30C7\u30FC\u30BF\u306E\u30AF\u30A8\u30EA): \`POST properties/{propertyId}:runReport\`\u3001\u30DC\u30C7\u30A3\u4F8B:
1423
+ - **\u30E1\u30BF\u30C7\u30FC\u30BF\u306E\u53D6\u5F97** (\u30D7\u30ED\u30D1\u30C6\u30A3\u3067\u5229\u7528\u53EF\u80FD\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3/\u30E1\u30C8\u30EA\u30AF\u30B9\u306E\u4E00\u89A7): \`GET /v1beta/properties/{propertyId}/metadata\`
1424
+ - **\u30EC\u30DD\u30FC\u30C8\u306E\u53D6\u5F97** (\u5206\u6790\u30C7\u30FC\u30BF\u306E\u30AF\u30A8\u30EA): \`POST /v1beta/properties/{propertyId}:runReport\`\u3001\u30DC\u30C7\u30A3\u4F8B:
890
1425
  \`\`\`json
891
1426
  {
892
1427
  "dateRanges": [{ "startDate": "7daysAgo", "endDate": "today" }],
@@ -895,7 +1430,7 @@ export default async function handler(c: Context) {
895
1430
  "limit": 100
896
1431
  }
897
1432
  \`\`\`
898
- - **\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8**: \`POST properties/{propertyId}:runRealtimeReport\`
1433
+ - **\u30EA\u30A2\u30EB\u30BF\u30A4\u30E0\u30EC\u30DD\u30FC\u30C8**: \`POST /v1beta/properties/{propertyId}:runRealtimeReport\`
899
1434
 
900
1435
  #### \u4E3B\u8981\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3
901
1436
  date, country, city, deviceCategory, browser, pagePath, pageTitle, sessionSource, sessionMedium, eventName
@@ -917,13 +1452,27 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
917
1452
  error: "google-analytics: missing service account key"
918
1453
  };
919
1454
  }
1455
+ const propertyId = params[parameters.propertyId.slug];
1456
+ if (!propertyId) {
1457
+ try {
1458
+ const sa = decodeServiceAccount(keyJsonBase64);
1459
+ await getAccessToken(sa);
1460
+ return { success: true };
1461
+ } catch (err) {
1462
+ const msg = err instanceof Error ? err.message : String(err);
1463
+ return { success: false, error: msg };
1464
+ }
1465
+ }
920
1466
  try {
921
- const res = await adminFetch(params, "/accountSummaries?pageSize=1");
1467
+ const res = await dataFetch(
1468
+ params,
1469
+ `/properties/${propertyId}/metadata`
1470
+ );
922
1471
  if (!res.ok) {
923
1472
  const body = await res.text().catch(() => res.statusText);
924
1473
  return {
925
1474
  success: false,
926
- error: `google-analytics: accountSummaries failed (${res.status}): ${body}`
1475
+ error: `Google Analytics API failed: HTTP ${res.status} ${body}`
927
1476
  };
928
1477
  }
929
1478
  return { success: true };