@squadbase/vite-server 0.1.17-dev.a107052 → 0.1.17-dev.d4fff69

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 (80) hide show
  1. package/dist/cli/index.js +4284 -820
  2. package/dist/connectors/airtable-oauth.js +48 -8
  3. package/dist/connectors/airtable.js +44 -8
  4. package/dist/connectors/amplitude.js +8 -8
  5. package/dist/connectors/anthropic.js +2 -2
  6. package/dist/connectors/asana.js +37 -10
  7. package/dist/connectors/attio.js +30 -13
  8. package/dist/connectors/aws-billing.js +8 -8
  9. package/dist/connectors/azure-sql.js +47 -10
  10. package/dist/connectors/backlog-api-key.js +40 -15
  11. package/dist/connectors/clickup.js +50 -10
  12. package/dist/connectors/cosmosdb.js +12 -12
  13. package/dist/connectors/customerio.js +8 -8
  14. package/dist/connectors/dbt.js +686 -25
  15. package/dist/connectors/freshdesk.js +82 -8
  16. package/dist/connectors/freshsales.js +8 -8
  17. package/dist/connectors/freshservice.js +8 -8
  18. package/dist/connectors/gamma.js +15 -15
  19. package/dist/connectors/gemini.js +2 -2
  20. package/dist/connectors/github.js +12 -12
  21. package/dist/connectors/gmail-oauth.js +10 -10
  22. package/dist/connectors/gmail.js +4 -4
  23. package/dist/connectors/google-ads.js +8 -8
  24. package/dist/connectors/google-analytics-oauth.js +152 -25
  25. package/dist/connectors/google-analytics.js +475 -95
  26. package/dist/connectors/google-audit-log.js +4 -4
  27. package/dist/connectors/google-calendar-oauth.js +61 -15
  28. package/dist/connectors/google-calendar.js +61 -11
  29. package/dist/connectors/google-docs.js +10 -10
  30. package/dist/connectors/google-drive.js +32 -10
  31. package/dist/connectors/google-search-console-oauth.js +126 -17
  32. package/dist/connectors/google-sheets.js +6 -6
  33. package/dist/connectors/google-slides.js +10 -10
  34. package/dist/connectors/grafana.js +45 -10
  35. package/dist/connectors/hackernews.d.ts +5 -0
  36. package/dist/connectors/hackernews.js +890 -0
  37. package/dist/connectors/hubspot-oauth.js +41 -9
  38. package/dist/connectors/hubspot.js +25 -9
  39. package/dist/connectors/influxdb.js +8 -8
  40. package/dist/connectors/intercom-oauth.js +72 -12
  41. package/dist/connectors/intercom.js +12 -12
  42. package/dist/connectors/jdbc.js +37 -10
  43. package/dist/connectors/jira-api-key.js +68 -11
  44. package/dist/connectors/kintone-api-token.js +66 -18
  45. package/dist/connectors/kintone.js +54 -11
  46. package/dist/connectors/linear.js +54 -12
  47. package/dist/connectors/linkedin-ads.js +41 -14
  48. package/dist/connectors/mailchimp-oauth.js +6 -6
  49. package/dist/connectors/mailchimp.js +6 -6
  50. package/dist/connectors/meta-ads-oauth.js +33 -14
  51. package/dist/connectors/meta-ads.js +35 -14
  52. package/dist/connectors/mixpanel.js +8 -8
  53. package/dist/connectors/monday.js +9 -9
  54. package/dist/connectors/mongodb.js +8 -8
  55. package/dist/connectors/notion-oauth.js +60 -11
  56. package/dist/connectors/notion.js +60 -11
  57. package/dist/connectors/openai.js +2 -2
  58. package/dist/connectors/oracle.js +39 -11
  59. package/dist/connectors/outlook-oauth.js +21 -21
  60. package/dist/connectors/powerbi-oauth.js +13 -13
  61. package/dist/connectors/salesforce.js +42 -9
  62. package/dist/connectors/semrush.js +6 -6
  63. package/dist/connectors/sentry.js +36 -10
  64. package/dist/connectors/shopify-oauth.js +43 -10
  65. package/dist/connectors/shopify.js +8 -8
  66. package/dist/connectors/sqlserver.js +47 -10
  67. package/dist/connectors/stripe-api-key.js +66 -15
  68. package/dist/connectors/stripe-oauth.js +70 -19
  69. package/dist/connectors/supabase.js +31 -6
  70. package/dist/connectors/tableau.js +15 -15
  71. package/dist/connectors/tiktok-ads.js +37 -16
  72. package/dist/connectors/wix-store.js +8 -8
  73. package/dist/connectors/x.d.ts +5 -0
  74. package/dist/connectors/x.js +927 -0
  75. package/dist/connectors/zendesk-oauth.js +55 -12
  76. package/dist/connectors/zendesk.js +12 -12
  77. package/dist/index.js +4302 -818
  78. package/dist/main.js +4302 -818
  79. package/dist/vite-plugin.js +4282 -818
  80. package/package.json +9 -1
@@ -350,7 +350,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
350
350
  /**
351
351
  * Create tools for connections that belong to this connector.
352
352
  * Filters connections by connectorKey internally.
353
- * Returns tools keyed as `${connectorKey}_${toolName}`.
353
+ * Returns tools keyed as `connector_${connectorKey}_${toolName}`.
354
354
  */
355
355
  createTools(connections, config, opts) {
356
356
  const myConnections = connections.filter(
@@ -360,7 +360,7 @@ var ConnectorPlugin = class _ConnectorPlugin {
360
360
  for (const t of Object.values(this.tools)) {
361
361
  const tool = t.createTool(myConnections, config);
362
362
  const originalToModelOutput = tool.toModelOutput;
363
- result[`${this.connectorKey}_${t.name}`] = {
363
+ result[`connector_${this.connectorKey}_${t.name}`] = {
364
364
  ...tool,
365
365
  toModelOutput: async (options) => {
366
366
  if (!originalToModelOutput) {
@@ -490,10 +490,10 @@ var AUTH_TYPES = {
490
490
  // ../connectors/src/connectors/google-analytics/setup.ts
491
491
  var googleAnalyticsOnboarding = new ConnectorOnboarding({
492
492
  dataOverviewInstructions: {
493
- en: `1. Call google-analytics-service-account_request with GET properties/{propertyId}/metadata to list available dimensions and metrics
494
- 2. Call 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. 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. 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`
497
497
  }
498
498
  });
499
499
 
@@ -601,7 +601,17 @@ async function dataFetch(params, path2, init) {
601
601
  // ../connectors/src/connectors/google-analytics/setup-flow.ts
602
602
  var ALL_PROPERTIES = "__ALL_PROPERTIES__";
603
603
  var GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES = 20;
604
- var METADATA_DISPLAY_LIMIT = 30;
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;
605
615
  async function listAccountSummaries(params) {
606
616
  const summaries = [];
607
617
  let pageToken;
@@ -624,9 +634,13 @@ function propertyIdFromResource(resource) {
624
634
  return (resource ?? "").replace(/^properties\//, "");
625
635
  }
626
636
  async function getProperty(params, propertyId) {
627
- const res = await adminFetch(params, `/properties/${propertyId}`);
628
- if (!res.ok) return null;
629
- 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
+ }
630
644
  }
631
645
  async function getMetadata(params, propertyId) {
632
646
  try {
@@ -634,41 +648,387 @@ async function getMetadata(params, propertyId) {
634
648
  params,
635
649
  `/properties/${propertyId}/metadata`
636
650
  );
637
- if (!res.ok) return { dimensions: [], metrics: [] };
638
- const data = await res.json();
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) {
639
676
  return {
640
- dimensions: data.dimensions ?? [],
641
- metrics: data.metrics ?? []
677
+ ok: false,
678
+ status: "network",
679
+ body: err instanceof Error ? err.message : String(err)
642
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] ?? {});
643
698
  } catch {
644
- return { dimensions: [], metrics: [] };
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
+ );
645
886
  }
887
+ sections.push("");
646
888
  }
647
- function appendMetadataSection(sections, dimensions, metrics) {
648
- if (dimensions.length > 0) {
649
- sections.push(`#### Dimensions (${dimensions.length})`, "");
650
- for (const d of dimensions.slice(0, METADATA_DISPLAY_LIMIT)) {
651
- sections.push(`- ${d.apiName ?? d.uiName ?? "(unknown)"}`);
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}`);
652
907
  }
653
- if (dimensions.length > METADATA_DISPLAY_LIMIT) {
908
+ if (customDims.length > CUSTOM_METADATA_LIMIT) {
654
909
  sections.push(
655
- `- \u2026and ${dimensions.length - METADATA_DISPLAY_LIMIT} more`
910
+ `- _\u2026and ${customDims.length - CUSTOM_METADATA_LIMIT} more custom dimensions._`
656
911
  );
657
912
  }
658
- sections.push("");
659
- }
660
- if (metrics.length > 0) {
661
- sections.push(`#### Metrics (${metrics.length})`, "");
662
- for (const m of metrics.slice(0, METADATA_DISPLAY_LIMIT)) {
663
- sections.push(`- ${m.apiName ?? m.uiName ?? "(unknown)"}`);
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}`);
664
916
  }
665
- if (metrics.length > METADATA_DISPLAY_LIMIT) {
917
+ if (customMets.length > CUSTOM_METADATA_LIMIT) {
666
918
  sections.push(
667
- `- \u2026and ${metrics.length - METADATA_DISPLAY_LIMIT} more`
919
+ `- _\u2026and ${customMets.length - CUSTOM_METADATA_LIMIT} more custom metrics._`
668
920
  );
669
921
  }
670
922
  sections.push("");
671
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);
672
1032
  }
673
1033
  var googleAnalyticsSetupFlow = {
674
1034
  initialState: () => ({}),
@@ -737,8 +1097,8 @@ var googleAnalyticsSetupFlow = {
737
1097
  slug: "manualPropertyId",
738
1098
  type: "text",
739
1099
  question: {
740
- 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",
741
- en: "Enter your GA4 Property ID (e.g., 123456789). Found in GA4 Admin > Property Settings."
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."
742
1102
  },
743
1103
  async fetchOptions(state) {
744
1104
  if (state.properties?.length) return [];
@@ -752,85 +1112,107 @@ var googleAnalyticsSetupFlow = {
752
1112
  ],
753
1113
  async finalize(state, rt) {
754
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;
755
1126
  if (state.account && state.properties) {
756
- let summaries = [];
757
1127
  try {
758
- summaries = await listAccountSummaries(rt.params);
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\//, "");
759
1133
  } catch {
1134
+ accountLabel = state.account.replace(/^accounts\//, "");
760
1135
  }
761
- const account = summaries.find(
762
- (s) => (s.account ?? s.name) === state.account
763
- );
764
- const accountLabel = account?.displayName ?? state.account.replace(/^accounts\//, "");
765
- const targetPropertyIds = await resolveSetupSelection({
1136
+ targetPropertyIds = await resolveSetupSelection({
766
1137
  selected: state.properties,
767
1138
  allSentinel: ALL_PROPERTIES,
768
- fetchAll: async () => (account?.propertySummaries ?? []).map((p) => propertyIdFromResource(p.property)).filter((v) => v),
1139
+ fetchAll: async () => (accountSummary?.propertySummaries ?? []).map((p) => propertyIdFromResource(p.property)).filter((v) => v),
769
1140
  limit: GOOGLE_ANALYTICS_SETUP_MAX_PROPERTIES
770
1141
  });
771
- sections.push(`### Account: ${accountLabel}`, "");
772
- if (targetPropertyIds.length === 0) {
773
- sections.push("_No properties selected._", "");
774
- return sections.join("\n");
775
- }
776
- sections.push(
777
- "| Property ID | Display Name | Time Zone | Currency | Industry |"
778
- );
1142
+ } else if (state.manualPropertyId) {
1143
+ targetPropertyIds = [state.manualPropertyId];
1144
+ }
1145
+ if (targetPropertyIds.length === 0) {
779
1146
  sections.push(
780
- "|-------------|--------------|-----------|----------|----------|"
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
+ ""
781
1149
  );
782
- for (const pid of targetPropertyIds) {
783
- const prop = await getProperty(rt.params, pid).catch(() => null);
784
- const displayName = (prop?.displayName ?? "-").replace(/\|/g, "\\|");
785
- const timeZone = prop?.timeZone ?? "-";
786
- const currency = prop?.currencyCode ?? "-";
787
- const industry = (prop?.industryCategory ?? "-").replace(
788
- /\|/g,
789
- "\\|"
790
- );
1150
+ if (serviceAccountEmail) {
791
1151
  sections.push(
792
- `| ${pid} | ${displayName} | ${timeZone} | ${currency} | ${industry} |`
793
- );
794
- }
795
- sections.push("");
796
- const firstPropertyId = targetPropertyIds[0];
797
- if (firstPropertyId) {
798
- const { dimensions, metrics } = await getMetadata(
799
- rt.params,
800
- firstPropertyId
1152
+ `_Service account email (share your GA4 property with this address)_: \`${serviceAccountEmail}\``,
1153
+ ""
801
1154
  );
802
- appendMetadataSection(sections, dimensions, metrics);
803
1155
  }
804
1156
  return sections.join("\n");
805
1157
  }
806
- const propertyId = state.manualPropertyId;
807
- if (propertyId) {
808
- sections.push(`### Property: ${propertyId}`, "");
809
- const { dimensions, metrics } = await getMetadata(
810
- rt.params,
811
- propertyId
812
- );
813
- appendMetadataSection(sections, dimensions, metrics);
814
- return sections.join("\n");
815
- }
816
1158
  sections.push(
817
- "_Could not list GA4 accounts. Please enable the Google Analytics Admin API in your GCP project. Property ID can be specified per request at runtime._",
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._",
818
1160
  ""
819
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 ?? "-");
1177
+ const timeZone = prop?.timeZone ?? "-";
1178
+ const currency = prop?.currencyCode ?? "-";
1179
+ const industry = escapePipe(prop?.industryCategory ?? "-");
1180
+ sections.push(
1181
+ `| ${pid} | ${displayName} | ${timeZone} | ${currency} | ${industry} |`
1182
+ );
1183
+ }
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
+ }
820
1203
  return sections.join("\n");
821
1204
  }
822
1205
  };
823
1206
 
824
1207
  // ../connectors/src/connectors/google-analytics/tools/request.ts
825
1208
  import { z } from "zod";
826
- var BASE_URL2 = "https://analyticsdata.googleapis.com/v1beta/";
1209
+ var BASE_URL2 = "https://analyticsdata.googleapis.com";
827
1210
  var REQUEST_TIMEOUT_MS = 6e4;
828
1211
  var inputSchema = z.object({
829
1212
  toolUseIntent: z.string().optional().describe("Brief description of what you intend to accomplish with this tool call"),
830
1213
  connectionId: z.string().describe("ID of the Google Analytics connection to use"),
831
- propertyId: z.string().describe("GA4 property ID (e.g., '123456789')"),
832
1214
  method: z.enum(["GET", "POST"]).describe("HTTP method"),
833
- path: z.string().describe("API path (e.g., 'properties/{propertyId}:runReport'). {propertyId} is replaced with the propertyId parameter."),
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'."),
834
1216
  body: z.record(z.string(), z.unknown()).optional().describe("POST request body (JSON)")
835
1217
  });
836
1218
  var outputSchema = z.discriminatedUnion("success", [
@@ -847,11 +1229,10 @@ var outputSchema = z.discriminatedUnion("success", [
847
1229
  var requestTool = new ConnectorTool({
848
1230
  name: "request",
849
1231
  description: `Send authenticated requests to the Google Analytics Data API.
850
- Authentication is handled automatically using a service account.
851
- {propertyId} in the path is automatically replaced with the propertyId parameter.`,
1232
+ Authentication is handled automatically using a service account.`,
852
1233
  inputSchema,
853
1234
  outputSchema,
854
- async execute({ connectionId, propertyId, method, path: path2, body }, connections) {
1235
+ async execute({ connectionId, method, path: path2, body }, connections) {
855
1236
  const connection2 = connections.find((c) => c.id === connectionId);
856
1237
  if (!connection2) {
857
1238
  return { success: false, error: `Connection ${connectionId} not found` };
@@ -871,8 +1252,7 @@ Authentication is handled automatically using a service account.
871
1252
  if (!token) {
872
1253
  return { success: false, error: "Failed to obtain access token" };
873
1254
  }
874
- const resolvedPath = path2.replace(/\{propertyId\}/g, propertyId);
875
- const url = `${BASE_URL2}${resolvedPath}`;
1255
+ const url = `${BASE_URL2}${path2}`;
876
1256
  const controller = new AbortController();
877
1257
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
878
1258
  try {
@@ -919,7 +1299,7 @@ var googleAnalyticsConnector = new ConnectorPlugin({
919
1299
  systemPrompt: {
920
1300
  en: `### Tools
921
1301
 
922
- - \`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. Requires a \`propertyId\` parameter. 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.
923
1303
 
924
1304
  ### Business Logic
925
1305
 
@@ -968,8 +1348,8 @@ export default async function handler(c: Context) {
968
1348
 
969
1349
  Common operations:
970
1350
 
971
- - **Get metadata** (list available dimensions/metrics for a property): \`GET properties/{propertyId}/metadata\`
972
- - **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:
973
1353
  \`\`\`json
974
1354
  {
975
1355
  "dateRanges": [{ "startDate": "7daysAgo", "endDate": "today" }],
@@ -978,7 +1358,7 @@ Common operations:
978
1358
  "limit": 100
979
1359
  }
980
1360
  \`\`\`
981
- - **Run realtime report**: \`POST properties/{propertyId}:runRealtimeReport\`
1361
+ - **Run realtime report**: \`POST /v1beta/properties/{propertyId}:runRealtimeReport\`
982
1362
 
983
1363
  #### Common dimensions
984
1364
  date, country, city, deviceCategory, browser, pagePath, pageTitle, sessionSource, sessionMedium, eventName
@@ -991,7 +1371,7 @@ activeUsers, sessions, screenPageViews, bounceRate, averageSessionDuration, conv
991
1371
  - Relative: \`"today"\`, \`"yesterday"\`, \`"7daysAgo"\`, \`"30daysAgo"\``,
992
1372
  ja: `### \u30C4\u30FC\u30EB
993
1373
 
994
- - \`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\`propertyId\` \u30D1\u30E9\u30E1\u30FC\u30BF\u30FC\u304C\u5FC5\u8981\u3067\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
995
1375
 
996
1376
  ### Business Logic
997
1377
 
@@ -1040,8 +1420,8 @@ export default async function handler(c: Context) {
1040
1420
 
1041
1421
  \u4E3B\u306A\u64CD\u4F5C:
1042
1422
 
1043
- - **\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\`
1044
- - **\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:
1045
1425
  \`\`\`json
1046
1426
  {
1047
1427
  "dateRanges": [{ "startDate": "7daysAgo", "endDate": "today" }],
@@ -1050,7 +1430,7 @@ export default async function handler(c: Context) {
1050
1430
  "limit": 100
1051
1431
  }
1052
1432
  \`\`\`
1053
- - **\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\`
1054
1434
 
1055
1435
  #### \u4E3B\u8981\u306A\u30C7\u30A3\u30E1\u30F3\u30B7\u30E7\u30F3
1056
1436
  date, country, city, deviceCategory, browser, pagePath, pageTitle, sessionSource, sessionMedium, eventName