ga4-analytics
Google Analytics 4, Search Console, and Indexing API toolkit. Analyze website traffic, page performance, user demographics, real-time visitors, search queries, and SEO metrics. Use when the user asks to: check site traffic, analyze page views, see traffic sources, view user demographics, get real-time visitor data, check search console queries, analyze SEO performance, request URL re-indexing, inspect index status, compare date ranges, check bounce rates, view conversion data, or get e-commerce revenue. Requires a Google Cloud service account with GA4 and Search Console access.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install openclaw-skills-ga4-analytics
Repository
Skill path: skills/adamkristopher/ga4-analytics
Google Analytics 4, Search Console, and Indexing API toolkit. Analyze website traffic, page performance, user demographics, real-time visitors, search queries, and SEO metrics. Use when the user asks to: check site traffic, analyze page views, see traffic sources, view user demographics, get real-time visitor data, check search console queries, analyze SEO performance, request URL re-indexing, inspect index status, compare date ranges, check bounce rates, view conversion data, or get e-commerce revenue. Requires a Google Cloud service account with GA4 and Search Console access.
Open repositoryBest for
Primary workflow: Grow & Distribute.
Technical facets: Full Stack, Backend, Data / AI, Tech Writer.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: openclaw.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install ga4-analytics into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/openclaw/skills before adding ga4-analytics to shared team environments
- Use ga4-analytics for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
--- name: ga4-analytics description: "Google Analytics 4, Search Console, and Indexing API toolkit. Analyze website traffic, page performance, user demographics, real-time visitors, search queries, and SEO metrics. Use when the user asks to: check site traffic, analyze page views, see traffic sources, view user demographics, get real-time visitor data, check search console queries, analyze SEO performance, request URL re-indexing, inspect index status, compare date ranges, check bounce rates, view conversion data, or get e-commerce revenue. Requires a Google Cloud service account with GA4 and Search Console access." --- # GA4 Analytics Toolkit ## Setup Install dependencies: ```bash cd scripts && npm install ``` Configure credentials by creating a `.env` file in the project root: ``` GA4_PROPERTY_ID=123456789 [email protected] GA4_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n" SEARCH_CONSOLE_SITE_URL=https://your-domain.com GA4_DEFAULT_DATE_RANGE=30d ``` **Prerequisites**: A Google Cloud project with the Analytics Data API, Search Console API, and Indexing API enabled. A service account with access to your GA4 property and Search Console. ## Quick Start | User says | Function to call | |-----------|-----------------| | "Show me site traffic for the last 30 days" | `siteOverview("30d")` | | "What are my top search queries?" | `searchConsoleOverview("30d")` | | "Who's on the site right now?" | `liveSnapshot()` | | "Reindex these URLs" | `reindexUrls(["https://example.com/page1", ...])` | | "Compare this month vs last month" | `compareDateRanges({startDate: "30daysAgo", endDate: "today"}, {startDate: "60daysAgo", endDate: "31daysAgo"})` | | "What pages get the most traffic?" | `contentPerformance("30d")` | Execute functions by importing from `scripts/src/index.ts`: ```typescript import { siteOverview, searchConsoleOverview } from './scripts/src/index.js'; const overview = await siteOverview('30d'); ``` Or run directly with tsx: ```bash npx tsx scripts/src/index.ts ``` ## Workflow Pattern Every analysis follows three phases: ### 1. Analyze Run API functions. Each call hits the Google APIs and returns structured data. ### 2. Auto-Save All results automatically save as timestamped JSON files to `results/{category}/`. File naming pattern: `YYYYMMDD_HHMMSS__operation__extra_info.json` ### 3. Summarize After analysis, read the saved JSON files and create a markdown summary in `results/summaries/` with data tables, trends, and recommendations. ## High-Level Functions ### GA4 Analytics | Function | Purpose | What it gathers | |----------|---------|----------------| | `siteOverview(dateRange?)` | Comprehensive site snapshot | Page views, traffic sources, demographics, events | | `trafficAnalysis(dateRange?)` | Traffic deep-dive | Sources, sessions by source/medium, new vs returning | | `contentPerformance(dateRange?)` | Top pages analysis | Page views, landing pages, exit pages | | `userBehavior(dateRange?)` | Engagement patterns | Demographics, events, daily engagement metrics | | `compareDateRanges(range1, range2)` | Period comparison | Side-by-side metrics for two date ranges | | `liveSnapshot()` | Real-time data | Active users, current pages, current events | ### Search Console | Function | Purpose | What it gathers | |----------|---------|----------------| | `searchConsoleOverview(dateRange?)` | SEO snapshot | Top queries, pages, device, country breakdown | | `keywordAnalysis(dateRange?)` | Keyword deep-dive | Queries with device breakdown | | `seoPagePerformance(dateRange?)` | Page SEO metrics | Top pages by clicks, country breakdown | ### Indexing | Function | Purpose | |----------|---------| | `reindexUrls(urls)` | Request re-indexing for multiple URLs | | `checkIndexStatus(urls)` | Check if URLs are indexed | ### Utility | Function | Purpose | |----------|---------| | `getAvailableFields()` | List all available GA4 dimensions and metrics | ### Individual API Functions For granular control, import specific functions from the API modules. See [references/api-reference.md](references/api-reference.md) for the complete list of 30+ API functions with parameters, types, and examples. ## Date Ranges All functions accept flexible date range formats: | Format | Example | Description | |--------|---------|-------------| | Shorthand | `"7d"`, `"30d"`, `"90d"` | Days ago to today | | Explicit | `{startDate: "2024-01-01", endDate: "2024-01-31"}` | Specific dates | | GA4 relative | `{startDate: "30daysAgo", endDate: "today"}` | GA4 relative format | Default is `"30d"` (configurable via `GA4_DEFAULT_DATE_RANGE` in `.env`). ## Results Storage Results auto-save to `results/` with this structure: ``` results/ ├── reports/ # GA4 standard reports ├── realtime/ # Real-time snapshots ├── searchconsole/ # Search Console data ├── indexing/ # Indexing API results └── summaries/ # Human-readable markdown summaries ``` ### Managing Results ```typescript import { listResults, loadResult, getLatestResult } from './scripts/src/index.js'; // List recent results const files = listResults('reports', 10); // Load a specific result const data = loadResult(files[0]); // Get most recent result for an operation const latest = getLatestResult('reports', 'site_overview'); ``` ## Common Dimensions and Metrics ### Dimensions `pagePath`, `pageTitle`, `sessionSource`, `sessionMedium`, `country`, `deviceCategory`, `browser`, `date`, `eventName`, `landingPage`, `newVsReturning` ### Metrics `screenPageViews`, `activeUsers`, `sessions`, `newUsers`, `bounceRate`, `averageSessionDuration`, `engagementRate`, `conversions`, `totalRevenue`, `eventCount` ## Tips 1. **Specify date ranges** — "last 7 days" or "last 90 days" gives different insights than the default 30 days 2. **Request summaries** — After pulling data, ask for a markdown summary with tables and insights 3. **Compare periods** — Use `compareDateRanges()` to spot trends (this month vs last month) 4. **Check real-time data** — `liveSnapshot()` shows who's on the site right now 5. **Combine GA4 + Search Console** — Traffic data plus search query data gives the full picture --- ## Referenced Files > The following files are referenced in this skill and included for context. ### references/api-reference.md ```markdown # API Reference Complete function reference for all GA4 Analytics Toolkit modules. ## Table of Contents - [Reports API](#reports-api) (7 functions) - [Realtime API](#realtime-api) (4 functions) - [Metadata API](#metadata-api) (3 functions) - [Search Console API](#search-console-api) (6 functions) - [Indexing API](#indexing-api) (4 functions) - [Bulk Lookup API](#bulk-lookup-api) (3 functions) - [Storage](#storage) (4 functions) --- ## Reports API Import: `from './api/reports.js'` ### `parseDateRange(range?)` Parse shorthand date range (e.g., "7d", "30d") to GA4 date range format. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `range` | `string \| DateRange \| undefined` | Settings default | Date range to parse | **Returns:** `DateRange` — `{startDate: string, endDate: string}` ### `runReport(options)` Run a custom GA4 report with arbitrary dimensions and metrics. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `options.dimensions` | `string[]` | required | GA4 dimension names | | `options.metrics` | `string[]` | required | GA4 metric names | | `options.dateRange` | `string \| DateRange` | `"30d"` | Date range | | `options.filters` | `Record<string, string>` | `undefined` | Dimension filters | | `options.orderBy` | `string[]` | `undefined` | Sort order | | `options.limit` | `number` | `undefined` | Row limit | | `options.save` | `boolean` | `true` | Save results to JSON | **Returns:** `Promise<ReportResponse>` — Rows with dimension and metric values. ### `getPageViews(dateRange?)` Get page view data with paths, titles, users, and session duration. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `dateRange` | `string \| DateRange` | `"30d"` | Date range | **Dimensions:** `pagePath`, `pageTitle` **Metrics:** `screenPageViews`, `activeUsers`, `averageSessionDuration` ### `getTrafficSources(dateRange?)` Get traffic source data by source, medium, and campaign. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `dateRange` | `string \| DateRange` | `"30d"` | Date range | **Dimensions:** `sessionSource`, `sessionMedium`, `sessionCampaignName` **Metrics:** `sessions`, `activeUsers`, `newUsers`, `bounceRate` ### `getUserDemographics(dateRange?)` Get user demographic data by country, device, and browser. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `dateRange` | `string \| DateRange` | `"30d"` | Date range | **Dimensions:** `country`, `deviceCategory`, `browser` **Metrics:** `activeUsers`, `sessions`, `newUsers` ### `getEventCounts(dateRange?)` Get event count data by event name. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `dateRange` | `string \| DateRange` | `"30d"` | Date range | **Dimensions:** `eventName` **Metrics:** `eventCount`, `eventCountPerUser`, `activeUsers` ### `getConversions(dateRange?)` Get conversion data by event name and source. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `dateRange` | `string \| DateRange` | `"30d"` | Date range | **Dimensions:** `eventName`, `sessionSource` **Metrics:** `conversions`, `totalRevenue` ### `getEcommerceRevenue(dateRange?)` Get e-commerce revenue data by date and transaction. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `dateRange` | `string \| DateRange` | `"30d"` | Date range | **Dimensions:** `date`, `transactionId` **Metrics:** `totalRevenue`, `ecommercePurchases`, `averagePurchaseRevenue` --- ## Realtime API Import: `from './api/realtime.js'` ### `getActiveUsers(save?)` Get current active users by screen/page name. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `save` | `boolean` | `true` | Save results to JSON | **Returns:** `Promise<RealtimeResponse>` — Active users by `unifiedScreenName`. ### `getRealtimeEvents(save?)` Get currently firing events. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `save` | `boolean` | `true` | Save results to JSON | **Returns:** `Promise<RealtimeResponse>` — Event counts by `eventName`. ### `getRealtimePages(save?)` Get currently viewed pages. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `save` | `boolean` | `true` | Save results to JSON | **Returns:** `Promise<RealtimeResponse>` — Page views by `unifiedScreenName`. ### `getRealtimeSources(save?)` Get current traffic sources. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `save` | `boolean` | `true` | Save results to JSON | **Returns:** `Promise<RealtimeResponse>` — Active users by `firstUserSource` and `firstUserMedium`. --- ## Metadata API Import: `from './api/metadata.js'` ### `getAvailableDimensions(save?)` Get all available dimensions for the GA4 property. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `save` | `boolean` | `true` | Save results to JSON | **Returns:** `Promise<MetadataResponse>` — Array of `{apiName, uiName, description}`. ### `getAvailableMetrics(save?)` Get all available metrics for the GA4 property. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `save` | `boolean` | `true` | Save results to JSON | **Returns:** `Promise<MetadataResponse>` — Array of `{apiName, uiName, description}`. ### `getPropertyMetadata(save?)` Get full property metadata (dimensions + metrics combined). | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `save` | `boolean` | `true` | Save results to JSON | **Returns:** `Promise<MetadataResponse>` — Full metadata response. --- ## Search Console API Import: `from './api/searchConsole.js'` ### `querySearchAnalytics(options)` Run a custom Search Console analytics query. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `options.dimensions` | `string[]` | `["query"]` | Dimensions: `query`, `page`, `device`, `country`, `searchAppearance` | | `options.dateRange` | `string \| SearchConsoleDateRange` | `"30d"` | Date range | | `options.rowLimit` | `number` | `1000` | Max rows | | `options.startRow` | `number` | `0` | Pagination offset | | `options.save` | `boolean` | `true` | Save results to JSON | **Returns:** `Promise<SearchAnalyticsResponse>` — Rows with `{keys, clicks, impressions, ctr, position}`. ### `getTopQueries(dateRange?)` Get top 100 search queries by clicks. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `dateRange` | `string \| SearchConsoleDateRange` | `"30d"` | Date range | **Returns:** `Promise<SearchAnalyticsResponse>` ### `getTopPages(dateRange?)` Get top 100 pages by search impressions. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `dateRange` | `string \| SearchConsoleDateRange` | `"30d"` | Date range | **Returns:** `Promise<SearchAnalyticsResponse>` ### `getDevicePerformance(dateRange?)` Get search performance breakdown by device type (desktop, mobile, tablet). | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `dateRange` | `string \| SearchConsoleDateRange` | `"30d"` | Date range | **Returns:** `Promise<SearchAnalyticsResponse>` ### `getCountryPerformance(dateRange?)` Get search performance by country (top 50). | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `dateRange` | `string \| SearchConsoleDateRange` | `"30d"` | Date range | **Returns:** `Promise<SearchAnalyticsResponse>` ### `getSearchAppearance(dateRange?)` Get search appearance data (rich results, AMP, etc.). | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `dateRange` | `string \| SearchConsoleDateRange` | `"30d"` | Date range | **Returns:** `Promise<SearchAnalyticsResponse>` --- ## Indexing API Import: `from './api/indexing.js'` ### `requestIndexing(url, options?)` Request Google to re-crawl a single URL. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `url` | `string` | required | Full URL to request indexing for | | `options.save` | `boolean` | `true` | Save results to JSON | **Returns:** `Promise<UrlNotificationResult>` — `{url, type: 'URL_UPDATED', notifyTime}`. ### `requestIndexingBatch(urls, options?)` Request re-crawling for multiple URLs (processed sequentially to avoid rate limits). | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `urls` | `string[]` | required | Array of URLs | | `options.save` | `boolean` | `true` | Save results to JSON | **Returns:** `Promise<UrlNotificationResult[]>` ### `removeFromIndex(url, options?)` Request URL removal from Google's index. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `url` | `string` | required | URL to remove | | `options.save` | `boolean` | `true` | Save results to JSON | **Returns:** `Promise<UrlNotificationResult>` — `{url, type: 'URL_DELETED', notifyTime}`. ### `inspectUrl(url, options?)` Check a URL's index status, mobile usability, and rich results. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `url` | `string` | required | URL to inspect | | `options.save` | `boolean` | `true` | Save results to JSON | **Returns:** `Promise<UrlInspectionResult>` — Contains `indexStatus.verdict` ('PASS' | 'FAIL' | 'NEUTRAL'), `coverageState`, `lastCrawlTime`, `mobileUsability`, `richResults`. --- ## Bulk Lookup API Import: `from './api/bulk-lookup.js'` ### `normalizeUrls(urls)` Normalize page paths: trim whitespace, add leading slash. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `urls` | `string[]` | required | Array of page paths | **Returns:** `string[]` — Normalized paths. ### `buildUrlFilter(urls)` Build a GA4 dimension filter expression for a list of page paths. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `urls` | `string[]` | required | Normalized page paths | **Returns:** `DimensionFilterExpression | null` ### `getMetricsForUrls(urls, options?)` Get GA4 metrics for specific page paths (bulk lookup). | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `urls` | `string[]` | required | Page paths (e.g., `["/pricing", "/about"]`) | | `options.dateRange` | `string \| DateRange` | `"30d"` | Date range | | `options.metrics` | `string[]` | `["screenPageViews", "activeUsers", "averageSessionDuration", "bounceRate", "engagementRate"]` | Metrics to retrieve | | `options.save` | `boolean` | `true` | Save results to JSON | **Returns:** `Promise<ReportResponse>` — Metrics for each URL. --- ## Storage Import: `from './core/storage.js'` ### `saveResult<T>(data, category, operation, extraInfo?)` Save result data to a timestamped JSON file with metadata wrapper. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `data` | `T` | required | Data to save | | `category` | `string` | required | Category directory (e.g., `"reports"`, `"realtime"`) | | `operation` | `string` | required | Operation name (e.g., `"page_views"`) | | `extraInfo` | `string` | `undefined` | Optional extra info for filename | **Returns:** `string` — Full path to the saved file. ### `loadResult<T>(filepath)` Load a previously saved result from a JSON file. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `filepath` | `string` | required | Path to the JSON file | **Returns:** `SavedResult<T> | null` ### `listResults(category, limit?)` List saved result files for a category, sorted newest first. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `category` | `string` | required | Category to list | | `limit` | `number` | `undefined` | Max results to return | **Returns:** `string[]` — Array of file paths. ### `getLatestResult<T>(category, operation?)` Get the most recent result for a category, optionally filtered by operation. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `category` | `string` | required | Category to search | | `operation` | `string` | `undefined` | Filter by operation name | **Returns:** `SavedResult<T> | null` ``` ### scripts/src/index.ts ```typescript /** * GA4 Analytics Toolkit - Main Entry Point * * Simple interface for Google Analytics 4 data analysis. * All results are automatically saved to the /results directory with timestamps. * * Usage: * import { siteOverview, trafficAnalysis } from './index.js'; * const overview = await siteOverview('30d'); */ // Re-export all API functions export * from './api/reports.js'; export * from './api/realtime.js'; export * from './api/metadata.js'; export * from './api/searchConsole.js'; export * from './api/indexing.js'; export * from './api/bulk-lookup.js'; // Re-export core utilities export { getClient, getPropertyId, getSearchConsoleClient, getIndexingClient, getSiteUrl, resetClient } from './core/client.js'; export { saveResult, loadResult, listResults, getLatestResult } from './core/storage.js'; export { getSettings, validateSettings } from './config/settings.js'; // Import for orchestration functions import { runReport, getPageViews, getTrafficSources, getUserDemographics, getEventCounts, getConversions, parseDateRange, type DateRange, } from './api/reports.js'; import { getActiveUsers, getRealtimeEvents, getRealtimePages } from './api/realtime.js'; import { getPropertyMetadata } from './api/metadata.js'; import { saveResult } from './core/storage.js'; import { getTopQueries, getTopPages as getSearchConsoleTopPages, getDevicePerformance, getCountryPerformance, type SearchConsoleDateRange, } from './api/searchConsole.js'; import { requestIndexing, inspectUrl } from './api/indexing.js'; // ============================================================================ // HIGH-LEVEL ORCHESTRATION FUNCTIONS // ============================================================================ /** * Comprehensive site overview - combines multiple reports */ export async function siteOverview(dateRange?: string | DateRange) { console.log('\n📊 Generating site overview...'); const results: Record<string, unknown> = {}; console.log(' → Getting page views...'); results.pageViews = await getPageViews(dateRange); console.log(' → Getting traffic sources...'); results.trafficSources = await getTrafficSources(dateRange); console.log(' → Getting user demographics...'); results.demographics = await getUserDemographics(dateRange); console.log(' → Getting event counts...'); results.events = await getEventCounts(dateRange); // Save combined results const dateStr = typeof dateRange === 'string' ? dateRange : 'custom'; saveResult(results, 'reports', 'site_overview', dateStr); console.log('✅ Site overview complete\n'); return results; } /** * Deep dive on traffic sources */ export async function trafficAnalysis(dateRange?: string | DateRange) { console.log('\n🚗 Analyzing traffic sources...'); const results: Record<string, unknown> = {}; console.log(' → Getting traffic sources...'); results.sources = await getTrafficSources(dateRange); console.log(' → Getting session data by source...'); results.sessions = await runReport({ dimensions: ['sessionSource', 'sessionMedium'], metrics: ['sessions', 'engagedSessions', 'averageSessionDuration', 'bounceRate'], dateRange, }); console.log(' → Getting new vs returning users...'); results.newVsReturning = await runReport({ dimensions: ['newVsReturning'], metrics: ['activeUsers', 'sessions', 'conversions'], dateRange, }); const dateStr = typeof dateRange === 'string' ? dateRange : 'custom'; saveResult(results, 'reports', 'traffic_analysis', dateStr); console.log('✅ Traffic analysis complete\n'); return results; } /** * Content performance analysis */ export async function contentPerformance(dateRange?: string | DateRange) { console.log('\n📄 Analyzing content performance...'); const results: Record<string, unknown> = {}; console.log(' → Getting page views...'); results.pages = await getPageViews(dateRange); console.log(' → Getting landing pages...'); results.landingPages = await runReport({ dimensions: ['landingPage'], metrics: ['sessions', 'activeUsers', 'bounceRate', 'averageSessionDuration'], dateRange, }); console.log(' → Getting exit pages...'); results.exitPages = await runReport({ dimensions: ['pagePath'], metrics: ['exits', 'screenPageViews'], dateRange, }); const dateStr = typeof dateRange === 'string' ? dateRange : 'custom'; saveResult(results, 'reports', 'content_performance', dateStr); console.log('✅ Content performance analysis complete\n'); return results; } /** * User behavior analysis */ export async function userBehavior(dateRange?: string | DateRange) { console.log('\n👤 Analyzing user behavior...'); const results: Record<string, unknown> = {}; console.log(' → Getting demographics...'); results.demographics = await getUserDemographics(dateRange); console.log(' → Getting events...'); results.events = await getEventCounts(dateRange); console.log(' → Getting engagement metrics...'); results.engagement = await runReport({ dimensions: ['date'], metrics: ['activeUsers', 'engagedSessions', 'engagementRate', 'averageSessionDuration'], dateRange, }); const dateStr = typeof dateRange === 'string' ? dateRange : 'custom'; saveResult(results, 'reports', 'user_behavior', dateStr); console.log('✅ User behavior analysis complete\n'); return results; } /** * Compare two date ranges */ export async function compareDateRanges( range1: DateRange, range2: DateRange, dimensions: string[] = ['date'], metrics: string[] = ['activeUsers', 'sessions', 'screenPageViews'] ) { console.log('\n📈 Comparing date ranges...'); console.log(` → Getting data for ${range1.startDate} to ${range1.endDate}...`); const period1 = await runReport({ dimensions, metrics, dateRange: range1, save: false, }); console.log(` → Getting data for ${range2.startDate} to ${range2.endDate}...`); const period2 = await runReport({ dimensions, metrics, dateRange: range2, save: false, }); const comparison = { period1: { dateRange: range1, data: period1 }, period2: { dateRange: range2, data: period2 }, }; saveResult(comparison, 'reports', 'date_comparison'); console.log('✅ Date range comparison complete\n'); return comparison; } /** * Get current live data snapshot */ export async function liveSnapshot() { console.log('\n⚡ Getting live data snapshot...'); const results: Record<string, unknown> = {}; console.log(' → Getting active users...'); results.activeUsers = await getActiveUsers(); console.log(' → Getting current pages...'); results.currentPages = await getRealtimePages(); console.log(' → Getting current events...'); results.currentEvents = await getRealtimeEvents(); saveResult(results, 'realtime', 'snapshot'); console.log('✅ Live snapshot complete\n'); return results; } // ============================================================================ // SEARCH CONSOLE ORCHESTRATION FUNCTIONS // ============================================================================ /** * Comprehensive Search Console overview */ export async function searchConsoleOverview(dateRange?: string | SearchConsoleDateRange) { console.log('\n🔍 Generating Search Console overview...'); const results: Record<string, unknown> = {}; console.log(' → Getting top queries...'); results.topQueries = await getTopQueries(dateRange); console.log(' → Getting top pages...'); results.topPages = await getSearchConsoleTopPages(dateRange); console.log(' → Getting device performance...'); results.devicePerformance = await getDevicePerformance(dateRange); console.log(' → Getting country performance...'); results.countryPerformance = await getCountryPerformance(dateRange); const dateStr = typeof dateRange === 'string' ? dateRange : 'custom'; saveResult(results, 'searchconsole', 'overview', dateStr); console.log('✅ Search Console overview complete\n'); return results; } /** * Deep dive into keyword/query analysis */ export async function keywordAnalysis(dateRange?: string | SearchConsoleDateRange) { console.log('\n🔑 Analyzing keywords...'); const results: Record<string, unknown> = {}; console.log(' → Getting top queries...'); results.queries = await getTopQueries(dateRange); console.log(' → Getting device breakdown for queries...'); results.deviceBreakdown = await getDevicePerformance(dateRange); const dateStr = typeof dateRange === 'string' ? dateRange : 'custom'; saveResult(results, 'searchconsole', 'keyword_analysis', dateStr); console.log('✅ Keyword analysis complete\n'); return results; } /** * SEO page performance analysis */ export async function seoPagePerformance(dateRange?: string | SearchConsoleDateRange) { console.log('\n📄 Analyzing SEO page performance...'); const results: Record<string, unknown> = {}; console.log(' → Getting top pages by clicks...'); results.topPages = await getSearchConsoleTopPages(dateRange); console.log(' → Getting country breakdown...'); results.countryBreakdown = await getCountryPerformance(dateRange); const dateStr = typeof dateRange === 'string' ? dateRange : 'custom'; saveResult(results, 'searchconsole', 'seo_page_performance', dateStr); console.log('✅ SEO page performance analysis complete\n'); return results; } /** * Request re-indexing for updated URLs */ export async function reindexUrls(urls: string[]) { console.log(`\n🔄 Requesting re-indexing for ${urls.length} URL(s)...`); const results: Array<{ url: string; status: string; error?: string }> = []; for (const url of urls) { try { console.log(` → Requesting indexing: ${url}`); const result = await requestIndexing(url, { save: false }); results.push({ url, status: 'submitted', ...result }); } catch (error) { console.log(` ✗ Failed: ${url}`); results.push({ url, status: 'failed', error: String(error) }); } } saveResult(results, 'indexing', 'reindex_batch'); console.log('✅ Re-indexing requests complete\n'); return results; } /** * Check index status for URLs */ export async function checkIndexStatus(urls: string[]) { console.log(`\n🔎 Checking index status for ${urls.length} URL(s)...`); const results: Array<{ url: string; indexed: boolean; status: unknown }> = []; for (const url of urls) { try { console.log(` → Inspecting: ${url}`); const result = await inspectUrl(url, { save: false }); results.push({ url, indexed: result.indexStatus.verdict === 'PASS', status: result.indexStatus, }); } catch (error) { console.log(` ✗ Failed: ${url}`); results.push({ url, indexed: false, status: { error: String(error) } }); } } saveResult(results, 'indexing', 'index_status_check'); console.log('✅ Index status check complete\n'); return results; } // ============================================================================ // UTILITY FUNCTIONS // ============================================================================ /** * Get available dimensions and metrics */ export async function getAvailableFields() { console.log('\n📋 Getting available fields...'); const metadata = await getPropertyMetadata(); console.log(` → Found ${metadata.dimensions?.length || 0} dimensions`); console.log(` → Found ${metadata.metrics?.length || 0} metrics`); console.log('✅ Field retrieval complete\n'); return metadata; } // Print help when run directly if (process.argv[1] === new URL(import.meta.url).pathname) { console.log(` GA4 Analytics Toolkit ===================== GA4 High-level functions: - siteOverview(dateRange?) Comprehensive site snapshot - trafficAnalysis(dateRange?) Deep dive on sources - contentPerformance(dateRange?) Top pages analysis - userBehavior(dateRange?) Engagement patterns - compareDateRanges(range1, range2) Period comparison - liveSnapshot() Real-time data Search Console functions: - searchConsoleOverview(dateRange?) Combined SEO snapshot - keywordAnalysis(dateRange?) Query/keyword analysis - seoPagePerformance(dateRange?) Page-level SEO metrics - getTopQueries(dateRange?) Top search queries - getTopPages(dateRange?) Top pages by clicks - getDevicePerformance(dateRange?) Mobile vs desktop - getCountryPerformance(dateRange?) Traffic by country Indexing functions: - reindexUrls(urls) Request re-indexing for URLs - checkIndexStatus(urls) Check if URLs are indexed - requestIndexing(url) Request single URL re-crawl - inspectUrl(url) Inspect URL index status Low-level GA4 functions: - runReport({ dimensions, metrics, dateRange }) - getPageViews(dateRange?) - getTrafficSources(dateRange?) - getUserDemographics(dateRange?) - getEventCounts(dateRange?) - getActiveUsers() - getRealtimeEvents() - getPropertyMetadata() All results are automatically saved to /results directory. `); } ``` --- ## Skill Companion Files > Additional files collected from the skill directory layout. ### _meta.json ```json { "owner": "adamkristopher", "slug": "ga4-analytics", "displayName": "GA4 Analytics", "latest": { "version": "1.0.0", "publishedAt": 1769464446311, "commit": "https://github.com/clawdbot/skills/commit/fe69a60d70765c4bfc23bee0cc302cf060e28cdd" }, "history": [] } ``` ### scripts/package-lock.json ```json { "name": "ga4-toolkit", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ga4-toolkit", "version": "1.0.0", "dependencies": { "@google-analytics/data": "^4.9.0", "@googleapis/indexing": "^6.0.1", "@googleapis/searchconsole": "^6.0.1", "dotenv": "^16.4.7", "googleapis": "^170.0.0", "tsx": "^4.19.2", "typescript": "^5.7.2" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ "ppc64" ], "license": "MIT", "optional": true, "os": [ "aix" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/android-arm": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ "arm" ], "license": "MIT", "optional": true, "os": [ "android" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "cpu": [ "arm64" ], "license": "MIT", "optional": true, "os": [ "android" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/android-x64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", "cpu": [ "x64" ], "license": "MIT", "optional": true, "os": [ "android" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "cpu": [ "arm64" ], "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", "cpu": [ "x64" ], "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "cpu": [ "arm64" ], "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", "cpu": [ "x64" ], "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "cpu": [ "arm" ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "cpu": [ "arm64" ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "cpu": [ "ia32" ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "cpu": [ "loong64" ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", "cpu": [ "mips64el" ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "cpu": [ "ppc64" ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "cpu": [ "riscv64" ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "cpu": [ "s390x" ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", "cpu": [ "x64" ], "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/netbsd-arm64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", "cpu": [ "arm64" ], "license": "MIT", "optional": true, "os": [ "netbsd" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", "cpu": [ "x64" ], "license": "MIT", "optional": true, "os": [ "netbsd" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/openbsd-arm64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", "cpu": [ "arm64" ], "license": "MIT", "optional": true, "os": [ "openbsd" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", "cpu": [ "x64" ], "license": "MIT", "optional": true, "os": [ "openbsd" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/openharmony-arm64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", "cpu": [ "arm64" ], "license": "MIT", "optional": true, "os": [ "openharmony" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", "cpu": [ "x64" ], "license": "MIT", "optional": true, "os": [ "sunos" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "cpu": [ "arm64" ], "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "cpu": [ "ia32" ], "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ "x64" ], "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { "node": ">=18" } }, "node_modules/@google-analytics/data": { "version": "4.12.1", "resolved": "https://registry.npmjs.org/@google-analytics/data/-/data-4.12.1.tgz", "integrity": "sha512-LzyrkVrnVUTYTmdmHayOZoroc+YA9GHEUrkSSuiXSmMSNbesuWy/MoTXugC1V7+8PCGqb2eQ1UtVVv/2BCAQYA==", "license": "Apache-2.0", "dependencies": { "google-gax": "^4.0.3" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@googleapis/indexing": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/@googleapis/indexing/-/indexing-6.0.1.tgz", "integrity": "sha512-JQuHax6UaTv9Y/sYsmlyRe5nZvPKPjHfe6AhM2wnl3xg2iuD6MGAU3wX3SdjOiM6r6iEggGNTfMdrtsZhuR5AA==", "license": "Apache-2.0", "dependencies": { "googleapis-common": "^8.0.0" }, "engines": { "node": ">=12.0.0" } }, "node_modules/@googleapis/searchconsole": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/@googleapis/searchconsole/-/searchconsole-6.0.1.tgz", "integrity": "sha512-aSBv0yp9HV9BgqX3bUj2DRY2kaIwcoN6ift+fKwPXj8eMoccJZzceQNshxt44V2ECkqwn7gvVEXVYTkw6zaTeQ==", "license": "Apache-2.0", "dependencies": { "googleapis-common": "^8.0.0" }, "engines": { "node": ">=12.0.0" } }, "node_modules/@grpc/grpc-js": { "version": "1.14.3", "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", "license": "Apache-2.0", "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" }, "engines": { "node": ">=12.10.0" } }, "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", "license": "Apache-2.0", "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.3", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" }, "engines": { "node": ">=6" } }, "node_modules/@grpc/proto-loader": { "version": "0.7.15", "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", "license": "Apache-2.0", "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.2.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" }, "engines": { "node": ">=6" } }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "license": "ISC", "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" }, "engines": { "node": ">=12" } }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/@isaacs/cliui/node_modules/ansi-styles": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" }, "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" }, "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/@js-sdsl/ordered-map": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/js-sdsl" } }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "license": "MIT", "optional": true, "engines": { "node": ">=14" } }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/codegen": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "node_modules/@protobufjs/float": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "license": "BSD-3-Clause" }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "license": "MIT", "engines": { "node": ">= 10" } }, "node_modules/@types/caseless": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", "license": "MIT" }, "node_modules/@types/long": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", "license": "MIT" }, "node_modules/@types/node": { "version": "25.0.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", "license": "MIT", "dependencies": { "undici-types": "~7.16.0" } }, "node_modules/@types/request": { "version": "2.48.13", "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.13.tgz", "integrity": "sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==", "license": "MIT", "dependencies": { "@types/caseless": "*", "@types/node": "*", "@types/tough-cookie": "*", "form-data": "^2.5.5" } }, "node_modules/@types/tough-cookie": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "license": "MIT" }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "license": "MIT", "dependencies": { "event-target-shim": "^5.0.0" }, "engines": { "node": ">=6.5" } }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", "engines": { "node": ">= 14" } }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/feross" }, { "type": "patreon", "url": "https://www.patreon.com/feross" }, { "type": "consulting", "url": "https://feross.org/support" } ], "license": "MIT" }, "node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", "license": "MIT", "engines": { "node": "*" } }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" }, "engines": { "node": ">= 0.4" } }, "node_modules/call-bound": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" }, "engines": { "node": ">=12" } }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, "engines": { "node": ">=7.0.0" } }, "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" }, "engines": { "node": ">= 8" } }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "license": "MIT", "engines": { "node": ">= 12" } }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" }, "engines": { "node": ">=6.0" }, "peerDependenciesMeta": { "supports-color": { "optional": true } } }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", "engines": { "node": ">=0.4.0" } }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "license": "BSD-2-Clause", "engines": { "node": ">=12" }, "funding": { "url": "https://dotenvx.com" } }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/duplexify": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", "license": "MIT", "dependencies": { "end-of-stream": "^1.4.1", "inherits": "^2.0.3", "readable-stream": "^3.1.1", "stream-shift": "^1.0.2" } }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" } }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "license": "MIT", "dependencies": { "once": "^1.4.0" } }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/es-set-tostringtag": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" } }, "node_modules/esbuild": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "hasInstallScript": true, "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { "node": ">=18" }, "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" } }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, "node_modules/fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/jimmywarting" }, { "type": "paypal", "url": "https://paypal.me/jimmywarting" } ], "license": "MIT", "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" }, "engines": { "node": "^12.20 || >= 14.13" } }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/form-data": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" }, "engines": { "node": ">= 0.12" } }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", "license": "MIT", "dependencies": { "fetch-blob": "^3.1.2" }, "engines": { "node": ">=12.20.0" } }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/gaxios": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", "node-fetch": "^2.6.9", "uuid": "^9.0.1" }, "engines": { "node": ">=14" } }, "node_modules/gcp-metadata": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", "license": "Apache-2.0", "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" }, "engines": { "node": ">=14" } }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/get-tsconfig": { "version": "4.13.0", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" }, "funding": { "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, "node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/google-auth-library": { "version": "9.15.1", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^6.1.1", "gcp-metadata": "^6.1.0", "gtoken": "^7.0.0", "jws": "^4.0.0" }, "engines": { "node": ">=14" } }, "node_modules/google-gax": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.6.1.tgz", "integrity": "sha512-V6eky/xz2mcKfAd1Ioxyd6nmA61gao3n01C+YeuIwu3vzM9EDR6wcVzMSIbLMDXWeoi9SHYctXuKYC5uJUT3eQ==", "license": "Apache-2.0", "dependencies": { "@grpc/grpc-js": "^1.10.9", "@grpc/proto-loader": "^0.7.13", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^4.0.0", "google-auth-library": "^9.3.0", "node-fetch": "^2.7.0", "object-hash": "^3.0.0", "proto3-json-serializer": "^2.0.2", "protobufjs": "^7.3.2", "retry-request": "^7.0.0", "uuid": "^9.0.1" }, "engines": { "node": ">=14" } }, "node_modules/google-logging-utils": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", "license": "Apache-2.0", "engines": { "node": ">=14" } }, "node_modules/googleapis": { "version": "170.1.0", "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-170.1.0.tgz", "integrity": "sha512-RLbc7yG6qzZqvAmGcgjvNIoZ7wpcCFxtc+HN+46etxDrlO4a8l5Cb7NxNQGhV91oRmL7mt56VoRoypAtEQEIKg==", "license": "Apache-2.0", "dependencies": { "google-auth-library": "^10.2.0", "googleapis-common": "^8.0.0" }, "engines": { "node": ">=18" } }, "node_modules/googleapis-common": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-8.0.1.tgz", "integrity": "sha512-eCzNACUXPb1PW5l0ULTzMHaL/ltPRADoPgjBlT8jWsTbxkCp6siv+qKJ/1ldaybCthGwsYFYallF7u9AkU4L+A==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", "gaxios": "^7.0.0-rc.4", "google-auth-library": "^10.1.0", "qs": "^6.7.0", "url-template": "^2.0.8" }, "engines": { "node": ">=18.0.0" } }, "node_modules/googleapis-common/node_modules/gaxios": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" }, "engines": { "node": ">=18" } }, "node_modules/googleapis-common/node_modules/gcp-metadata": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", "license": "Apache-2.0", "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" }, "engines": { "node": ">=18" } }, "node_modules/googleapis-common/node_modules/google-auth-library": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" }, "engines": { "node": ">=18" } }, "node_modules/googleapis-common/node_modules/google-logging-utils": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", "license": "Apache-2.0", "engines": { "node": ">=14" } }, "node_modules/googleapis-common/node_modules/gtoken": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", "license": "MIT", "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" }, "engines": { "node": ">=18" } }, "node_modules/googleapis-common/node_modules/node-fetch": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "license": "MIT", "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" }, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/node-fetch" } }, "node_modules/googleapis/node_modules/gaxios": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" }, "engines": { "node": ">=18" } }, "node_modules/googleapis/node_modules/gcp-metadata": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", "license": "Apache-2.0", "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" }, "engines": { "node": ">=18" } }, "node_modules/googleapis/node_modules/google-auth-library": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" }, "engines": { "node": ">=18" } }, "node_modules/googleapis/node_modules/google-logging-utils": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", "license": "Apache-2.0", "engines": { "node": ">=14" } }, "node_modules/googleapis/node_modules/gtoken": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", "license": "MIT", "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" }, "engines": { "node": ">=18" } }, "node_modules/googleapis/node_modules/node-fetch": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "license": "MIT", "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" }, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/node-fetch" } }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/gtoken": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", "license": "MIT", "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-tostringtag": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, "engines": { "node": ">= 0.4" } }, "node_modules/http-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "license": "MIT", "dependencies": { "@tootallnate/once": "2", "agent-base": "6", "debug": "4" }, "engines": { "node": ">= 6" } }, "node_modules/http-proxy-agent/node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", "dependencies": { "debug": "4" }, "engines": { "node": ">= 6.0.0" } }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", "dependencies": { "agent-base": "^7.1.2", "debug": "4" }, "engines": { "node": ">= 14" } }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "license": "MIT", "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, "funding": { "url": "https://github.com/sponsors/isaacs" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", "license": "MIT", "dependencies": { "bignumber.js": "^9.0.0" } }, "node_modules/jwa": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "license": "MIT", "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "node_modules/jws": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "license": "MIT", "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "license": "MIT" }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" } }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", "deprecated": "Use your platform's native DOMException instead", "funding": [ { "type": "github", "url": "https://github.com/sponsors/jimmywarting" }, { "type": "github", "url": "https://paypal.me/jimmywarting" } ], "license": "MIT", "engines": { "node": ">=10.5.0" } }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, "engines": { "node": "4.x || >=6.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "peerDependenciesMeta": { "encoding": { "optional": true } } }, "node_modules/object-hash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "license": "MIT", "engines": { "node": ">= 6" } }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", "dependencies": { "wrappy": "1" } }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/proto3-json-serializer": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", "license": "Apache-2.0", "dependencies": { "protobufjs": "^7.2.5" }, "engines": { "node": ">=14.0.0" } }, "node_modules/protobufjs": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" }, "engines": { "node": ">=12.0.0" } }, "node_modules/qs": { "version": "6.14.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" }, "engines": { "node": ">= 6" } }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, "node_modules/retry-request": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", "license": "MIT", "dependencies": { "@types/request": "^2.48.8", "extend": "^3.0.2", "teeny-request": "^9.0.0" }, "engines": { "node": ">=14" } }, "node_modules/rimraf": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", "license": "ISC", "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/feross" }, { "type": "patreon", "url": "https://www.patreon.com/feross" }, { "type": "consulting", "url": "https://feross.org/support" } ], "license": "MIT" }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, "engines": { "node": ">=8" } }, "node_modules/shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/side-channel-list": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/side-channel-map": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/side-channel-weakmap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "license": "ISC", "engines": { "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/stream-events": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", "license": "MIT", "dependencies": { "stubs": "^3.0.0" } }, "node_modules/stream-shift": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", "license": "MIT" }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" } }, "node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" } }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, "engines": { "node": ">=8" } }, "node_modules/stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", "license": "MIT" }, "node_modules/teeny-request": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", "license": "Apache-2.0", "dependencies": { "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.9", "stream-events": "^1.0.5", "uuid": "^9.0.0" }, "engines": { "node": ">=14" } }, "node_modules/teeny-request/node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", "dependencies": { "debug": "4" }, "engines": { "node": ">= 6.0.0" } }, "node_modules/teeny-request/node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "license": "MIT", "dependencies": { "agent-base": "6", "debug": "4" }, "engines": { "node": ">= 6" } }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, "node_modules/tsx": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "license": "MIT", "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "bin": { "tsx": "dist/cli.mjs" }, "engines": { "node": ">=18.0.0" }, "optionalDependencies": { "fsevents": "~2.3.3" } }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { "node": ">=14.17" } }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT" }, "node_modules/url-template": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", "license": "BSD" }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "license": "MIT", "engines": { "node": ">= 8" } }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "license": "BSD-2-Clause" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" }, "engines": { "node": ">= 8" } }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" }, "engines": { "node": ">=12" } }, "node_modules/yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "license": "ISC", "engines": { "node": ">=12" } } } } ``` ### scripts/package.json ```json { "name": "ga4-toolkit", "version": "1.0.0", "type": "module", "dependencies": { "@google-analytics/data": "^4.9.0", "@googleapis/indexing": "^6.0.1", "@googleapis/searchconsole": "^6.0.1", "dotenv": "^16.4.7", "googleapis": "^170.0.0", "tsx": "^4.19.2", "typescript": "^5.7.2" }, "engines": { "node": ">=18.0.0" } } ``` ### scripts/setup.sh ```bash #!/bin/bash cd "$(dirname "$0")" && npm install ``` ### scripts/src/api/bulk-lookup.ts ```typescript /** * Bulk URL Lookup - Get GA4 metrics for specific page paths * * This module provides a convenient way to look up analytics data * for a list of specific URLs, similar to a bulk URL lookup field. */ import { getClient, getPropertyId } from '../core/client.js'; import { saveResult } from '../core/storage.js'; import { getSettings } from '../config/settings.js'; import type { ReportResponse, DateRange } from './reports.js'; /** * Options for bulk URL lookup */ export interface BulkLookupOptions { /** Date range (e.g., "7d", "30d") or explicit dates */ dateRange?: string | DateRange; /** Custom metrics to retrieve (defaults to standard page metrics) */ metrics?: string[]; /** Whether to save results to file (default: true) */ save?: boolean; } /** * Dimension filter expression for GA4 API */ export interface DimensionFilterExpression { filter: { fieldName: string; inListFilter?: { values: string[]; caseSensitive?: boolean; }; stringFilter?: { matchType: string; value: string; caseSensitive?: boolean; }; }; } /** * Default metrics for bulk URL lookup */ const DEFAULT_METRICS = [ 'screenPageViews', 'activeUsers', 'averageSessionDuration', 'bounceRate', 'engagementRate', ]; /** * Normalize URLs to ensure consistent format * * - Trims whitespace * - Adds leading slash if missing * - Preserves trailing slashes * * @param urls - Array of URLs to normalize * @returns Normalized URL array */ export function normalizeUrls(urls: string[]): string[] { return urls.map(url => { // Trim whitespace let normalized = url.trim(); // Add leading slash if missing if (!normalized.startsWith('/')) { normalized = '/' + normalized; } return normalized; }); } /** * Build a dimension filter expression for the given URLs * * @param urls - Array of page paths to filter by * @returns Filter expression or null if no URLs provided */ export function buildUrlFilter(urls: string[]): DimensionFilterExpression | null { if (urls.length === 0) { return null; } return { filter: { fieldName: 'pagePath', inListFilter: { values: urls, caseSensitive: false, }, }, }; } /** * Parse shorthand date range (e.g., "7d", "30d") to GA4 date range format */ function parseDateRange(range: string | DateRange | undefined): DateRange { if (!range) { const settings = getSettings(); range = settings.defaultDateRange; } if (typeof range === 'object') { return range; } // Parse shorthand like "7d", "30d", "90d" const match = range.match(/^(\d+)d$/); if (match) { const days = parseInt(match[1], 10); return { startDate: `${days}daysAgo`, endDate: 'today', }; } // Default to 30 days return { startDate: '30daysAgo', endDate: 'today', }; } /** * Get GA4 metrics for specific page paths (bulk URL lookup) * * @param urls - Array of page paths to look up (e.g., ['/pricing', '/about']) * @param options - Optional configuration * @returns Report response with metrics for the specified URLs * * @example * ```typescript * // Get metrics for specific pages * const result = await getMetricsForUrls(['/pricing', '/about', '/blog']); * * // With custom date range and metrics * const result = await getMetricsForUrls(['/pricing'], { * dateRange: '7d', * metrics: ['sessions', 'bounceRate'], * }); * ``` */ export async function getMetricsForUrls( urls: string[], options: BulkLookupOptions = {} ): Promise<ReportResponse> { const { dateRange, metrics = DEFAULT_METRICS, save = true } = options; // Normalize URLs const normalizedUrls = normalizeUrls(urls); // Handle empty URL array if (normalizedUrls.length === 0) { return { rows: [], rowCount: 0, }; } // Build filter const dimensionFilter = buildUrlFilter(normalizedUrls); // Get client and property const client = getClient(); const property = getPropertyId(); const parsedDateRange = parseDateRange(dateRange); // Build and execute request const request = { property, dateRanges: [parsedDateRange], dimensions: [{ name: 'pagePath' }, { name: 'pageTitle' }], metrics: metrics.map(name => ({ name })), dimensionFilter, }; const [response] = await client.runReport(request); // Save results if requested if (save) { const dateStr = typeof dateRange === 'string' ? dateRange : 'custom'; saveResult(response, 'reports', 'bulk_url_lookup', dateStr); } return response as ReportResponse; } ``` ### scripts/src/api/indexing.ts ```typescript /** * Indexing API - Request re-indexing and URL inspection */ import { getIndexingClient, getSearchConsoleClient, getSiteUrl } from '../core/client.js'; import { saveResult } from '../core/storage.js'; /** * Indexing request options */ export interface IndexingOptions { save?: boolean; } /** * URL notification result */ export interface UrlNotificationResult { url: string; type: 'URL_UPDATED' | 'URL_DELETED'; notifyTime: string; } /** * URL inspection result */ export interface UrlInspectionResult { inspectionResultLink?: string; indexStatus: { verdict: 'PASS' | 'FAIL' | 'NEUTRAL'; coverageState: string; robotsTxtState?: string; indexingState?: string; lastCrawlTime?: string; pageFetchState?: string; googleCanonical?: string; userCanonical?: string; crawledAs?: string; }; mobileUsability?: { verdict: string; issues?: unknown[]; }; richResults?: { verdict: string; detectedItems?: unknown[]; }; } /** * Request indexing for a single URL (notify Google to re-crawl) * * @param url - The URL to request indexing for * @param options - Optional settings (save to file, etc.) * @returns Notification result with timestamp */ export async function requestIndexing(url: string, options: IndexingOptions = {}): Promise<UrlNotificationResult> { const { save = true } = options; const client = getIndexingClient(); const response = await client.urlNotifications.publish({ requestBody: { url, type: 'URL_UPDATED', }, }); const result: UrlNotificationResult = { url: response.data.urlNotificationMetadata?.url || url, type: 'URL_UPDATED', notifyTime: response.data.urlNotificationMetadata?.latestUpdate?.notifyTime || new Date().toISOString(), }; if (save) { saveResult(result, 'indexing', 'request_indexing'); } return result; } /** * Request indexing for multiple URLs * * @param urls - Array of URLs to request indexing for * @param options - Optional settings * @returns Array of notification results */ export async function requestIndexingBatch(urls: string[], options: IndexingOptions = {}): Promise<UrlNotificationResult[]> { const { save = true } = options; const results: UrlNotificationResult[] = []; // Process URLs sequentially to avoid rate limiting for (const url of urls) { const result = await requestIndexing(url, { save: false }); results.push(result); } if (save) { saveResult(results, 'indexing', 'batch_indexing'); } return results; } /** * Request URL removal from index * * @param url - The URL to request removal for * @param options - Optional settings * @returns Notification result */ export async function removeFromIndex(url: string, options: IndexingOptions = {}): Promise<UrlNotificationResult> { const { save = true } = options; const client = getIndexingClient(); const response = await client.urlNotifications.publish({ requestBody: { url, type: 'URL_DELETED', }, }); const result: UrlNotificationResult = { url: response.data.urlNotificationMetadata?.url || url, type: 'URL_DELETED', notifyTime: response.data.urlNotificationMetadata?.latestRemove?.notifyTime || new Date().toISOString(), }; if (save) { saveResult(result, 'indexing', 'remove_from_index'); } return result; } /** * Inspect a URL to check its index status * * @param url - The URL to inspect * @param options - Optional settings * @returns URL inspection result with index status */ export async function inspectUrl(url: string, options: IndexingOptions = {}): Promise<UrlInspectionResult> { const { save = true } = options; const client = getSearchConsoleClient(); const siteUrl = getSiteUrl(); const response = await client.urlInspection.index.inspect({ requestBody: { inspectionUrl: url, siteUrl, }, }); const inspectionResult = response.data.inspectionResult; const result: UrlInspectionResult = { inspectionResultLink: inspectionResult?.inspectionResultLink || undefined, indexStatus: { verdict: (inspectionResult?.indexStatusResult?.verdict as 'PASS' | 'FAIL' | 'NEUTRAL') || 'NEUTRAL', coverageState: inspectionResult?.indexStatusResult?.coverageState || 'Unknown', robotsTxtState: inspectionResult?.indexStatusResult?.robotsTxtState || undefined, indexingState: inspectionResult?.indexStatusResult?.indexingState || undefined, lastCrawlTime: inspectionResult?.indexStatusResult?.lastCrawlTime || undefined, pageFetchState: inspectionResult?.indexStatusResult?.pageFetchState || undefined, googleCanonical: inspectionResult?.indexStatusResult?.googleCanonical || undefined, userCanonical: inspectionResult?.indexStatusResult?.userCanonical || undefined, crawledAs: inspectionResult?.indexStatusResult?.crawledAs || undefined, }, mobileUsability: inspectionResult?.mobileUsabilityResult ? { verdict: inspectionResult.mobileUsabilityResult.verdict || 'NEUTRAL', issues: inspectionResult.mobileUsabilityResult.issues || [], } : undefined, richResults: inspectionResult?.richResultsResult ? { verdict: inspectionResult.richResultsResult.verdict || 'NEUTRAL', detectedItems: inspectionResult.richResultsResult.detectedItems || [], } : undefined, }; if (save) { saveResult(result, 'indexing', 'url_inspection'); } return result; } ``` ### scripts/src/api/metadata.ts ```typescript /** * Metadata API - Available dimensions and metrics */ import { getClient, getPropertyId } from '../core/client.js'; import { saveResult } from '../core/storage.js'; /** * Dimension metadata */ export interface DimensionMetadata { apiName: string; uiName: string; description: string; } /** * Metric metadata */ export interface MetricMetadata { apiName: string; uiName: string; description: string; } /** * Full property metadata response */ export interface MetadataResponse { name?: string; dimensions?: DimensionMetadata[]; metrics?: MetricMetadata[]; } /** * Get all available dimensions for the property */ export async function getAvailableDimensions(save = true): Promise<MetadataResponse> { const client = getClient(); const property = getPropertyId(); const [response] = await client.getMetadata({ name: `${property}/metadata`, }); const result = { dimensions: response.dimensions || [], }; if (save) { saveResult(result, 'metadata', 'dimensions'); } return result as MetadataResponse; } /** * Get all available metrics for the property */ export async function getAvailableMetrics(save = true): Promise<MetadataResponse> { const client = getClient(); const property = getPropertyId(); const [response] = await client.getMetadata({ name: `${property}/metadata`, }); const result = { metrics: response.metrics || [], }; if (save) { saveResult(result, 'metadata', 'metrics'); } return result as MetadataResponse; } /** * Get full property metadata (dimensions and metrics) */ export async function getPropertyMetadata(save = true): Promise<MetadataResponse> { const client = getClient(); const property = getPropertyId(); const [response] = await client.getMetadata({ name: `${property}/metadata`, }); if (save) { saveResult(response, 'metadata', 'full'); } return response as MetadataResponse; } ``` ### scripts/src/api/realtime.ts ```typescript /** * Realtime API - Live GA4 data */ import { getClient, getPropertyId } from '../core/client.js'; import { saveResult } from '../core/storage.js'; /** * Realtime report response structure */ export interface RealtimeResponse { dimensionHeaders?: Array<{ name: string }>; metricHeaders?: Array<{ name: string }>; rows?: Array<{ dimensionValues: Array<{ value: string }>; metricValues: Array<{ value: string }>; }>; rowCount?: number; } /** * Get current active users */ export async function getActiveUsers(save = true): Promise<RealtimeResponse> { const client = getClient(); const property = getPropertyId(); const [response] = await client.runRealtimeReport({ property, dimensions: [{ name: 'unifiedScreenName' }], metrics: [{ name: 'activeUsers' }], }); if (save) { saveResult(response, 'realtime', 'active_users'); } return response as RealtimeResponse; } /** * Get current event data */ export async function getRealtimeEvents(save = true): Promise<RealtimeResponse> { const client = getClient(); const property = getPropertyId(); const [response] = await client.runRealtimeReport({ property, dimensions: [{ name: 'eventName' }], metrics: [{ name: 'eventCount' }], }); if (save) { saveResult(response, 'realtime', 'events'); } return response as RealtimeResponse; } /** * Get currently viewed pages */ export async function getRealtimePages(save = true): Promise<RealtimeResponse> { const client = getClient(); const property = getPropertyId(); const [response] = await client.runRealtimeReport({ property, dimensions: [{ name: 'unifiedScreenName' }], metrics: [{ name: 'screenPageViews' }], }); if (save) { saveResult(response, 'realtime', 'pages'); } return response as RealtimeResponse; } /** * Get realtime traffic sources */ export async function getRealtimeSources(save = true): Promise<RealtimeResponse> { const client = getClient(); const property = getPropertyId(); const [response] = await client.runRealtimeReport({ property, dimensions: [{ name: 'firstUserSource' }, { name: 'firstUserMedium' }], metrics: [{ name: 'activeUsers' }], }); if (save) { saveResult(response, 'realtime', 'sources'); } return response as RealtimeResponse; } ``` ### scripts/src/api/reports.ts ```typescript /** * Reports API - Standard GA4 report generation */ import { getClient, getPropertyId } from '../core/client.js'; import { saveResult } from '../core/storage.js'; import { getSettings } from '../config/settings.js'; /** * Date range configuration */ export interface DateRange { startDate: string; endDate: string; } /** * Report options */ export interface ReportOptions { dimensions: string[]; metrics: string[]; dateRange?: string | DateRange; filters?: Record<string, string>; orderBy?: string[]; limit?: number; save?: boolean; } /** * Report response structure */ export interface ReportResponse { dimensionHeaders?: Array<{ name: string }>; metricHeaders?: Array<{ name: string }>; rows?: Array<{ dimensionValues: Array<{ value: string }>; metricValues: Array<{ value: string }>; }>; rowCount?: number; metadata?: Record<string, unknown>; } /** * Parse shorthand date range (e.g., "7d", "30d") to GA4 date range format */ export function parseDateRange(range: string | DateRange | undefined): DateRange { if (!range) { const settings = getSettings(); range = settings.defaultDateRange; } if (typeof range === 'object') { return range; } // Parse shorthand like "7d", "30d", "90d" const match = range.match(/^(\d+)d$/); if (match) { const days = parseInt(match[1], 10); return { startDate: `${days}daysAgo`, endDate: 'today', }; } // Default to 30 days return { startDate: '30daysAgo', endDate: 'today', }; } /** * Run a custom GA4 report */ export async function runReport(options: ReportOptions): Promise<ReportResponse> { const { dimensions, metrics, dateRange, filters, orderBy, limit, save = true, } = options; const client = getClient(); const property = getPropertyId(); const parsedDateRange = parseDateRange(dateRange); const request = { property, dateRanges: [parsedDateRange], dimensions: dimensions.map(name => ({ name })), metrics: metrics.map(name => ({ name })), ...(limit && { limit }), }; const [response] = await client.runReport(request); if (save) { const operation = dimensions.join('_') || 'custom'; const extra = typeof dateRange === 'string' ? dateRange : undefined; saveResult(response, 'reports', operation, extra); } return response as ReportResponse; } /** * Get page view data */ export async function getPageViews(dateRange?: string | DateRange): Promise<ReportResponse> { return runReport({ dimensions: ['pagePath', 'pageTitle'], metrics: ['screenPageViews', 'activeUsers', 'averageSessionDuration'], dateRange, }); } /** * Get traffic source data */ export async function getTrafficSources(dateRange?: string | DateRange): Promise<ReportResponse> { return runReport({ dimensions: ['sessionSource', 'sessionMedium', 'sessionCampaignName'], metrics: ['sessions', 'activeUsers', 'newUsers', 'bounceRate'], dateRange, }); } /** * Get user demographic data (country, device, browser) */ export async function getUserDemographics(dateRange?: string | DateRange): Promise<ReportResponse> { return runReport({ dimensions: ['country', 'deviceCategory', 'browser'], metrics: ['activeUsers', 'sessions', 'newUsers'], dateRange, }); } /** * Get event count data */ export async function getEventCounts(dateRange?: string | DateRange): Promise<ReportResponse> { return runReport({ dimensions: ['eventName'], metrics: ['eventCount', 'eventCountPerUser', 'activeUsers'], dateRange, }); } /** * Get conversion data */ export async function getConversions(dateRange?: string | DateRange): Promise<ReportResponse> { return runReport({ dimensions: ['eventName', 'sessionSource'], metrics: ['conversions', 'totalRevenue'], dateRange, }); } /** * Get e-commerce revenue data */ export async function getEcommerceRevenue(dateRange?: string | DateRange): Promise<ReportResponse> { return runReport({ dimensions: ['date', 'transactionId'], metrics: ['totalRevenue', 'ecommercePurchases', 'averagePurchaseRevenue'], dateRange, }); } ``` ### scripts/src/api/searchConsole.ts ```typescript /** * Search Console API - Google Search Console data retrieval */ import { getSearchConsoleClient, getSiteUrl } from '../core/client.js'; import { saveResult } from '../core/storage.js'; import { getSettings } from '../config/settings.js'; /** * Date range configuration for Search Console */ export interface SearchConsoleDateRange { startDate: string; endDate: string; } /** * Search analytics query options */ export interface SearchAnalyticsOptions { dimensions?: string[]; dateRange?: string | SearchConsoleDateRange; rowLimit?: number; startRow?: number; save?: boolean; } /** * Search analytics row structure */ export interface SearchAnalyticsRow { keys: string[]; clicks: number; impressions: number; ctr: number; position: number; } /** * Search analytics response structure */ export interface SearchAnalyticsResponse { rows?: SearchAnalyticsRow[]; responseAggregationType?: string; } /** * Parse shorthand date range (e.g., "7d", "30d") to Search Console date format * Note: Search Console requires YYYY-MM-DD format, not GA4's "NdaysAgo" format */ export function parseSearchConsoleDateRange(range: string | SearchConsoleDateRange | undefined): SearchConsoleDateRange { if (!range) { const settings = getSettings(); range = settings.defaultDateRange; } if (typeof range === 'object') { return range; } // Parse shorthand like "7d", "30d", "90d" const match = range.match(/^(\d+)d$/); if (match) { const days = parseInt(match[1], 10); const endDate = new Date(); const startDate = new Date(); startDate.setDate(startDate.getDate() - days); return { startDate: startDate.toISOString().split('T')[0], endDate: endDate.toISOString().split('T')[0], }; } // Default to 30 days const endDate = new Date(); const startDate = new Date(); startDate.setDate(startDate.getDate() - 30); return { startDate: startDate.toISOString().split('T')[0], endDate: endDate.toISOString().split('T')[0], }; } /** * Query search analytics data */ export async function querySearchAnalytics(options: SearchAnalyticsOptions): Promise<SearchAnalyticsResponse> { const { dimensions = ['query'], dateRange, rowLimit = 1000, startRow = 0, save = true, } = options; const client = getSearchConsoleClient(); const siteUrl = getSiteUrl(); const parsedDateRange = parseSearchConsoleDateRange(dateRange); const response = await client.searchanalytics.query({ siteUrl, requestBody: { startDate: parsedDateRange.startDate, endDate: parsedDateRange.endDate, dimensions, rowLimit, startRow, }, }); const result = response.data as SearchAnalyticsResponse; if (save) { const operation = dimensions.join('_') || 'query'; const extra = typeof dateRange === 'string' ? dateRange : undefined; saveResult(result, 'searchconsole', operation, extra); } return result; } /** * Get top search queries */ export async function getTopQueries(dateRange?: string | SearchConsoleDateRange): Promise<SearchAnalyticsResponse> { return querySearchAnalytics({ dimensions: ['query'], dateRange, rowLimit: 100, }); } /** * Get top pages by search performance */ export async function getTopPages(dateRange?: string | SearchConsoleDateRange): Promise<SearchAnalyticsResponse> { return querySearchAnalytics({ dimensions: ['page'], dateRange, rowLimit: 100, }); } /** * Get search performance by device type */ export async function getDevicePerformance(dateRange?: string | SearchConsoleDateRange): Promise<SearchAnalyticsResponse> { return querySearchAnalytics({ dimensions: ['device'], dateRange, }); } /** * Get search performance by country */ export async function getCountryPerformance(dateRange?: string | SearchConsoleDateRange): Promise<SearchAnalyticsResponse> { return querySearchAnalytics({ dimensions: ['country'], dateRange, rowLimit: 50, }); } /** * Get search appearance data (rich results, AMP, etc.) */ export async function getSearchAppearance(dateRange?: string | SearchConsoleDateRange): Promise<SearchAnalyticsResponse> { return querySearchAnalytics({ dimensions: ['searchAppearance'], dateRange, }); } ``` ### scripts/src/config/settings.ts ```typescript /** * Settings Module - Environment configuration for GA4 API */ import { config } from 'dotenv'; import { join } from 'path'; // Load .env file from current working directory config(); /** * Settings interface for GA4 API configuration */ export interface Settings { /** GA4 Property ID */ propertyId: string; /** Service account email */ clientEmail: string; /** Service account private key */ privateKey: string; /** Default date range for reports (e.g., "30d", "7d") */ defaultDateRange: string; /** Directory path for storing results */ resultsDir: string; /** Search Console site URL (e.g., "https://example.com") */ siteUrl: string; } /** * Validation result from validateSettings() */ export interface ValidationResult { valid: boolean; errors: string[]; } /** * Get current settings from environment variables */ export function getSettings(): Settings { return { propertyId: process.env.GA4_PROPERTY_ID || '', clientEmail: process.env.GA4_CLIENT_EMAIL || '', privateKey: (process.env.GA4_PRIVATE_KEY || '').replace(/\\n/g, '\n'), defaultDateRange: process.env.GA4_DEFAULT_DATE_RANGE || '30d', resultsDir: join(process.cwd(), 'results'), siteUrl: process.env.SEARCH_CONSOLE_SITE_URL || '', }; } /** * Validate that all required settings are present */ export function validateSettings(): ValidationResult { const settings = getSettings(); const errors: string[] = []; if (!settings.propertyId) { errors.push('GA4_PROPERTY_ID is required'); } if (!settings.clientEmail) { errors.push('GA4_CLIENT_EMAIL is required'); } if (!settings.privateKey) { errors.push('GA4_PRIVATE_KEY is required'); } return { valid: errors.length === 0, errors, }; } ``` ### scripts/src/core/client.ts ```typescript /** * GA4 API Client - Singleton wrapper for BetaAnalyticsDataClient * Also includes Search Console and Indexing API clients */ import { BetaAnalyticsDataClient } from '@google-analytics/data'; import { searchconsole } from '@googleapis/searchconsole'; import { indexing } from '@googleapis/indexing'; import { google } from 'googleapis'; import { getSettings, validateSettings } from '../config/settings.js'; // Singleton client instances let clientInstance: BetaAnalyticsDataClient | null = null; let searchConsoleClientInstance: ReturnType<typeof searchconsole> | null = null; let indexingClientInstance: ReturnType<typeof indexing> | null = null; /** * Get the GA4 Analytics Data API client (singleton) * * @returns The BetaAnalyticsDataClient instance * @throws Error if credentials are invalid */ export function getClient(): BetaAnalyticsDataClient { if (clientInstance) { return clientInstance; } const validation = validateSettings(); if (!validation.valid) { throw new Error(`Invalid GA4 credentials: ${validation.errors.join(', ')}`); } const settings = getSettings(); clientInstance = new BetaAnalyticsDataClient({ credentials: { client_email: settings.clientEmail, private_key: settings.privateKey, }, }); return clientInstance; } /** * Get the GA4 property ID formatted for API calls * * @returns Property ID with "properties/" prefix */ export function getPropertyId(): string { const settings = getSettings(); return `properties/${settings.propertyId}`; } /** * Reset the client singleton (useful for testing) */ export function resetClient(): void { clientInstance = null; searchConsoleClientInstance = null; indexingClientInstance = null; } /** * Get Google Auth client for Search Console and Indexing APIs */ function getGoogleAuth() { const settings = getSettings(); return new google.auth.GoogleAuth({ credentials: { client_email: settings.clientEmail, private_key: settings.privateKey, }, scopes: [ 'https://www.googleapis.com/auth/webmasters.readonly', 'https://www.googleapis.com/auth/indexing', ], }); } /** * Get the Search Console API client (singleton) * * @returns The Search Console client instance * @throws Error if credentials are invalid */ export function getSearchConsoleClient(): ReturnType<typeof searchconsole> { if (searchConsoleClientInstance) { return searchConsoleClientInstance; } const validation = validateSettings(); if (!validation.valid) { throw new Error(`Invalid credentials: ${validation.errors.join(', ')}`); } const auth = getGoogleAuth(); searchConsoleClientInstance = searchconsole({ version: 'v1', auth }); return searchConsoleClientInstance; } /** * Get the Indexing API client (singleton) * * @returns The Indexing client instance * @throws Error if credentials are invalid */ export function getIndexingClient(): ReturnType<typeof indexing> { if (indexingClientInstance) { return indexingClientInstance; } const validation = validateSettings(); if (!validation.valid) { throw new Error(`Invalid credentials: ${validation.errors.join(', ')}`); } const auth = getGoogleAuth(); indexingClientInstance = indexing({ version: 'v3', auth }); return indexingClientInstance; } /** * Get the Search Console site URL * * @returns Site URL from settings */ export function getSiteUrl(): string { const settings = getSettings(); return settings.siteUrl; } ``` ### scripts/src/core/storage.ts ```typescript /** * Storage Module - Auto-save results to JSON files with metadata */ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from 'fs'; import { join } from 'path'; import { getSettings } from '../config/settings.js'; /** * Metadata wrapper for saved results */ export interface ResultMetadata { savedAt: string; category: string; operation: string; propertyId: string; extraInfo?: string; } /** * Wrapped result with metadata */ export interface SavedResult<T = unknown> { metadata: ResultMetadata; data: T; } /** * Generate timestamp string for filenames: YYYYMMDD_HHMMSS */ function getTimestamp(): string { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); return `${year}${month}${day}_${hours}${minutes}${seconds}`; } /** * Sanitize string for use in filename */ function sanitizeFilename(str: string): string { return str.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase(); } /** * Save result data to a JSON file with metadata wrapper * * @param data - The data to save * @param category - Category directory (e.g., 'reports', 'realtime') * @param operation - Operation name (e.g., 'page_views', 'traffic_sources') * @param extraInfo - Optional extra info for filename * @returns The full path to the saved file */ export function saveResult<T>( data: T, category: string, operation: string, extraInfo?: string ): string { const settings = getSettings(); const categoryDir = join(settings.resultsDir, category); // Ensure category directory exists if (!existsSync(categoryDir)) { mkdirSync(categoryDir, { recursive: true }); } // Build filename const timestamp = getTimestamp(); const sanitizedOperation = sanitizeFilename(operation); const sanitizedExtra = extraInfo ? `__${sanitizeFilename(extraInfo)}` : ''; const filename = `${timestamp}__${sanitizedOperation}${sanitizedExtra}.json`; const filepath = join(categoryDir, filename); // Build wrapped result const result: SavedResult<T> = { metadata: { savedAt: new Date().toISOString(), category, operation, propertyId: settings.propertyId, ...(extraInfo && { extraInfo }), }, data, }; // Write to file writeFileSync(filepath, JSON.stringify(result, null, 2), 'utf-8'); return filepath; } /** * Load a saved result from a JSON file * * @param filepath - Path to the JSON file * @returns The parsed result or null if file doesn't exist */ export function loadResult<T = unknown>(filepath: string): SavedResult<T> | null { if (!existsSync(filepath)) { return null; } try { const content = readFileSync(filepath, 'utf-8'); return JSON.parse(content) as SavedResult<T>; } catch { return null; } } /** * List saved result files for a category * * @param category - Category to list results for * @param limit - Maximum number of results to return * @returns Array of file paths, sorted by date descending (newest first) */ export function listResults(category: string, limit?: number): string[] { const settings = getSettings(); const categoryDir = join(settings.resultsDir, category); if (!existsSync(categoryDir)) { return []; } const files = readdirSync(categoryDir) .filter(f => f.endsWith('.json')) .map(f => join(categoryDir, f)) .sort((a, b) => { // Sort by filename (which starts with timestamp) descending const nameA = a.split('/').pop() || ''; const nameB = b.split('/').pop() || ''; return nameB.localeCompare(nameA); }); if (limit !== undefined) { return files.slice(0, limit); } return files; } /** * Get the most recent result for a category/operation * * @param category - Category to search * @param operation - Optional operation to filter by * @returns The most recent result or null */ export function getLatestResult<T = unknown>( category: string, operation?: string ): SavedResult<T> | null { let files = listResults(category); if (operation) { const sanitized = sanitizeFilename(operation); files = files.filter(f => f.includes(`__${sanitized}`)); } if (files.length === 0) { return null; } return loadResult<T>(files[0]); } ``` ### scripts/tsconfig.json ```json { "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "lib": ["ES2022"], "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "declaration": true, "declarationMap": true, "sourceMap": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "tests"] } ```